mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95efcdd019 | |||
| a3787bdfa6 | |||
| 454d5e9c86 | |||
| d99bc8aeb6 | |||
| f81a4365fe | |||
| d4c84a97e8 | |||
| df828e9947 | |||
| d57c1f50b4 | |||
| 8f936a4111 | |||
| 35ee10e357 | |||
| 7ed3927082 | |||
| 9e22d60763 | |||
| 93845373a3 | |||
| ff5575ad0b | |||
| 3e35f7415e | |||
| 24290bcab7 | |||
| 5c1bbf2e50 | |||
| 6186e5237f | |||
| 3e1384048e | |||
| b774e22d8e | |||
| ec03e462f3 | |||
| bbbe477f47 | |||
| 711cb41626 | |||
| 4be253483d | |||
| 30f00d887d | |||
| 3aea2e6872 | |||
| c8ee52897e | |||
| 5ae85e4849 | |||
| cc34e5fb07 | |||
| 1d1c413169 | |||
| ee68df9be9 | |||
| a057b61d1f | |||
| c67647b8c2 | |||
| 3d9e8b2109 | |||
| 8737544d69 | |||
| a566c5ce44 | |||
| 65ff0f8246 | |||
| e227275087 | |||
| 7dd56736e8 | |||
| ff51e0933b | |||
| b4c7167d90 | |||
| 4fd78a99d2 | |||
| d0ce0110e8 | |||
| a7f8d9dc08 | |||
| f80fabbf27 | |||
| 455b64524f | |||
| b0e0f0014e | |||
| df6c2057f2 | |||
| f9781f9804 | |||
| 616df433c9 | |||
| e1ac52a0b4 | |||
| 6007af9651 | |||
| 66e940aebf | |||
| 0accc64cc0 | |||
| 08233b8374 | |||
| e1b9b662a0 | |||
| d04f98d713 | |||
| dc62446ef7 | |||
| 4ca81b09e8 | |||
| 426bcb999a | |||
| 6581ac94cd | |||
| e9c659a486 | |||
| 760b1f3d0b | |||
| 02b8e11e1e | |||
| a2087a1978 | |||
| 1af3ae3dee | |||
| af6f6e6448 | |||
| 1f8c53cd0c | |||
| 79fbc4eaba | |||
| 8ca0b0515d | |||
| df685ed7c5 | |||
| a2aa23d8b5 | |||
| b780d39612 | |||
| d28cf00df6 | |||
| 6eb5e37d6f | |||
| 7d1d604916 | |||
| 869ebc54e9 | |||
| 4af7e0c96a | |||
| 9bbc415408 | |||
| debe484188 | |||
| 3877635741 | |||
| 863be4b698 | |||
| b2066d4922 | |||
| f6a32c03f4 | |||
| 7862686a60 | |||
| 74099a3d4a | |||
| 39ea8b356e | |||
| e934f3a581 | |||
| 40b0467d3e | |||
| 4906ba7b18 | |||
| 81379ef52c | |||
| eba017f8c7 | |||
| b51f386ac7 | |||
| 4c5366829b | |||
| 82a1ce0f32 | |||
| 55b202f0c7 | |||
| 977884a0c8 | |||
| 1df95cd1c1 | |||
| 7f5ae52c02 | |||
| 4a016886fa | |||
| 4b4941046c | |||
| d7147507e4 | |||
| cfab5a59d6 | |||
| eee0af0599 | |||
| 6fcfe2b0ce | |||
| 05ed60a0f9 | |||
| 5978b3d132 | |||
| 60dfcc209e | |||
| c2dd703d2f | |||
| 5996d3fa6b | |||
| acf1793d7b | |||
| 0dad149514 | |||
| eb2e879a00 | |||
| 2dedc4a969 | |||
| 09baed082b | |||
| 2d7d4e2cf0 | |||
| e7330560cd | |||
| 7ff44f9424 | |||
| d0b95d5877 | |||
| a468a5e965 | |||
| e2b598852f | |||
| 1be6b0a58a | |||
| f7c02e1321 | |||
| 4a87a8ac10 | |||
| dfcf0d5882 | |||
| a5a9b408e2 | |||
| 91f250dab7 | |||
| 5e84d9c8c3 | |||
| b7a717ed10 | |||
| a27da867f9 | |||
| f4bd5a33d2 | |||
| 36318dbd41 | |||
| 56ff04607c | |||
| 56ce8dabc5 | |||
| 659b921ab1 | |||
| 4e2f0b7f0d | |||
| c948128902 | |||
| 1d95a832e3 | |||
| 32f908ab70 | |||
| 560ae24511 | |||
| 7fb4a37f7d | |||
| caf3eec51a | |||
| 69b384c0d1 | |||
| 3eaa006c6f | |||
| 1c63eddb7c | |||
| 58f2b74807 | |||
| c59a4dcd77 | |||
| ec661a3f0b | |||
| 3584a230e4 | |||
| fed831fe0a | |||
| 1a6eaa0fea | |||
| dd052dc0d6 | |||
| 4cb4e984fb | |||
| ce41575e34 | |||
| 33b5707ee9 | |||
| 330242595e | |||
| 1731988e72 | |||
| 163e7ad6b7 | |||
| 2b9a850789 | |||
| 043b1ff8be | |||
| 6586b99daa | |||
| e5f02052cb | |||
| d315e2c4fa | |||
| 9e2e5d6920 | |||
| c4de4e1f89 | |||
| 8d58595478 | |||
| cc74ebfdf6 | |||
| cafc3b0081 | |||
| 3212935f4d | |||
| 89dd890046 | |||
| 9ecf0b31ca | |||
| 0b1144563b | |||
| be54cc852c | |||
| 3e7195764a | |||
| f89030b83e | |||
| 12181b9914 | |||
| d49333edc3 | |||
| fb7c58cda7 | |||
| 22b95de9bc | |||
| 8e2178792d | |||
| c16e9a7cf0 | |||
| 502f300757 | |||
| 00cbdc99fd | |||
| 4d8925b053 | |||
| a5543f78c6 | |||
| 16bfaaf152 | |||
| 478d622265 | |||
| df8a825910 | |||
| 6c8faaa769 | |||
| 8bf752539f | |||
| b40bb22a66 | |||
| 8460a4380c | |||
| d485cfef28 | |||
| 73faf94273 | |||
| 2ef1bb960e | |||
| 4c8e8e3714 | |||
| 3a689c2050 | |||
| 872e7f25fe | |||
| aecb675fa5 | |||
| dda656e054 | |||
| e149ebf228 | |||
| b06b24b504 | |||
| d4973ff9b0 | |||
| 1b65931428 | |||
| e52a6cf949 | |||
| 13e89bcbd7 | |||
| 1892904070 | |||
| 7bb312fcf6 | |||
| c9879457f6 | |||
| 7ef5d9deef | |||
| 39767e764f | |||
| d1e7ce1b2d | |||
| 06286ce413 | |||
| ea71ff8ef7 | |||
| a5565d176a | |||
| 98277dacab | |||
| d7e7409e81 | |||
| d28ce0a4cf | |||
| 6a347d36eb | |||
| 635c3b4fa8 | |||
| e8f00423fa | |||
| cba1b902da | |||
| db6b4a6bc4 | |||
| 606de5166e |
+1
-4
@@ -16,6 +16,7 @@ integration/cli-hello-world/node_modules
|
||||
integration/cli-hello-world-ivy-i18n/node_modules
|
||||
integration/cli-hello-world-lazy/node_modules
|
||||
integration/cli-hello-world-mocha/node_modules
|
||||
integration/cli-signal-inputs/node_modules
|
||||
integration/defer/node_modules
|
||||
integration/dynamic-compiler/node_modules
|
||||
integration/forms/node_modules
|
||||
@@ -38,7 +39,3 @@ integration/typings_test_ts49/node_modules
|
||||
integration/typings_test_ts50/node_modules
|
||||
integration/typings_test_ts51/node_modules
|
||||
integration/typings_test_ts52/node_modules
|
||||
|
||||
|
||||
# Temporarily ignore all of adev
|
||||
adev/
|
||||
@@ -0,0 +1,38 @@
|
||||
# This workflow builds the previews for pull requests when a certain label is applied.
|
||||
# The actual deployment happens as part of a dedicated second workflow to avoid security
|
||||
# issues where the building would otherwise occur in an authorized context where secrets
|
||||
# could be leaked. More details can be found here:
|
||||
|
||||
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
||||
|
||||
name: Build adev for preview deployment
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, labeled]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
adev-build:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'adev: preview') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
|
||||
deploy-directory: './dist/bin/adev/build/browser'
|
||||
@@ -0,0 +1,50 @@
|
||||
# This workflow runs whenever the ADEV build workflow has completed. Deployment happens
|
||||
# as part of a dedicated second workflow to avoid security issues where the building would
|
||||
# otherwise occur in an authorized context where secrets could be leaked.
|
||||
#
|
||||
# More details can be found here:
|
||||
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
||||
|
||||
name: Deploying adev preview to Firebase
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Build adev for preview deployment']
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
# Needed in order to be able to comment on the pull request.
|
||||
pull-requests: write
|
||||
# Needed in order to checkout the repository
|
||||
contents: read
|
||||
# Needed in order to retrieve the artifacts from the previous job
|
||||
actions: read
|
||||
|
||||
env:
|
||||
PREVIEW_PROJECT: ng-dev-previews
|
||||
PREVIEW_SITE: ng-dev-previews-fw
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # tag=v3
|
||||
with:
|
||||
token: '${{secrets.GITHUB_TOKEN}}'
|
||||
|
||||
- name: Configure Firebase deploy target
|
||||
working-directory: ./
|
||||
run: |
|
||||
# We can use `npx` as the Firebase deploy actions uses it too.
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
firebase-config-dir: './adev'
|
||||
firebase-public-dir: './adev/build/browser'
|
||||
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
|
||||
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
# the number of concurrent actions is determined based on the host resources.
|
||||
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
workflow-artifact-name: 'aio'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
|
||||
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'aio'
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Starting Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
|
||||
+40
-28
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -94,15 +94,15 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
@@ -110,20 +110,35 @@ jobs:
|
||||
- name: Check generated bundle sizes
|
||||
run: yarn --cwd aio payload-size
|
||||
|
||||
adev:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel test --config=aio_local_deps //adev:test
|
||||
|
||||
aio-local:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
@@ -138,15 +153,15 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
@@ -182,13 +197,13 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -200,7 +215,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -208,9 +223,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -218,7 +233,6 @@ jobs:
|
||||
//packages/zone.js/bundles:zone.umd.js \
|
||||
//packages/zone.js:npm_package \
|
||||
//packages/zone.js/test/closure:closure_js \
|
||||
//packages/zone.js:zone_externs
|
||||
|
||||
- run: |
|
||||
mkdir -p packages/zone.js/build/
|
||||
@@ -229,7 +243,6 @@ jobs:
|
||||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-mix.umd.js ./packages/zone.js/test/extra/
|
||||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-patch-electron.umd.js ./packages/zone.js/test/extra/
|
||||
cp dist/bin/packages/zone.js/test/closure/zone.closure.mjs ./packages/zone.js/build/test/zone.closure.mjs
|
||||
cp dist/bin/packages/zone.js/zone_externs.js ./packages/zone.js/build/zone_externs.js
|
||||
|
||||
# Install
|
||||
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
|
||||
@@ -238,7 +251,6 @@ jobs:
|
||||
- run: yarn --cwd packages/zone.js jest:test
|
||||
- run: yarn --cwd packages/zone.js jest:nodetest
|
||||
- run: yarn --cwd packages/zone.js electrontest
|
||||
- run: yarn --cwd packages/zone.js closuretest
|
||||
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd packages/zone.js/test/typings test
|
||||
|
||||
@@ -250,7 +262,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -258,9 +270,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/feature-request@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -22,15 +22,15 @@ jobs:
|
||||
version: ['https://next.angular.io/', 'https://angular.io/']
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- name: Install node modules in aio
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Run basic e2e and deployment config tests.
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
|
||||
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Generate `events.json`
|
||||
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
|
||||
+23
-1
@@ -5,7 +5,18 @@ import {FormatConfig} from '@angular/ng-dev';
|
||||
*/
|
||||
export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'matchers': ['**/*.{yaml,yml}'],
|
||||
'matchers': [
|
||||
'**/*.{yaml,yml}',
|
||||
'devtools/**/*.{js,ts}',
|
||||
'tools/**/*.{js,ts}',
|
||||
'modules/**/*.{js,ts}',
|
||||
'scripts/**/*.{js,ts}',
|
||||
'packages/animations/**/*.{js,ts}',
|
||||
'packages/bazel/**/*.{js,ts}',
|
||||
'packages/benchpress/**/*.{js,ts}',
|
||||
'packages/elements/**/*.{js,ts}',
|
||||
'packages/misc/**/*.{js,ts}',
|
||||
],
|
||||
},
|
||||
'clang-format': {
|
||||
'matchers': [
|
||||
@@ -31,6 +42,17 @@ export const format: FormatConfig = {
|
||||
'!packages/common/src/i18n/currencies.ts',
|
||||
// Temporarily disable formatting for adev
|
||||
'!adev/**',
|
||||
|
||||
// Migrated to prettier
|
||||
'!devtools/**/*.{js,ts}',
|
||||
'!tools/**/*.{js,ts}',
|
||||
'!modules/**/*.{js,ts}',
|
||||
'!scripts/**/*.{js,ts}',
|
||||
'!packages/animations/**/*.{js,ts}',
|
||||
'!packages/bazel/**/*.{js,ts}',
|
||||
'!packages/benchpress/**/*.{js,ts}',
|
||||
'!packages/elements/**/*.{js,ts}',
|
||||
'!packages/misc/**/*.{js,ts}',
|
||||
],
|
||||
},
|
||||
'buildifier': true,
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
"packages/http/**",
|
||||
"**/.gitignore",
|
||||
"**/.gitkeep",
|
||||
"**/yarn.lock",
|
||||
"**/package.json",
|
||||
"**/third_party/**",
|
||||
"**/tsconfig-build.json",
|
||||
"**/tsconfig-tsec.json",
|
||||
|
||||
@@ -1150,6 +1150,7 @@ groups:
|
||||
- josephperrott
|
||||
- mgechev
|
||||
- twerske
|
||||
- ~jeanmeche
|
||||
|
||||
# =========================================================
|
||||
# Dev-infra
|
||||
@@ -1394,6 +1395,7 @@ groups:
|
||||
- devversion # Paul Gschwendtner
|
||||
- dgp1130 # Doug Parker
|
||||
- dylhunn # Dylan Hunn
|
||||
- jeanmeche # Matthieu Riegler
|
||||
- jelbourn # Jeremy Elbourn
|
||||
- jessicajaniuk # Jessica Janiuk
|
||||
- JiaLiPassion # Jia Li
|
||||
|
||||
+84
-153
@@ -1,39 +1,99 @@
|
||||
<a name="17.1.0-next.4"></a>
|
||||
# 17.1.0-next.4 (2023-12-13)
|
||||
<a name="17.1.0"></a>
|
||||
# 17.1.0 (2024-01-17)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b98d8f79ed](https://github.com/angular/angular/commit/b98d8f79ed4979fb91661dbdfb6266a917b41474) | fix | handle ambient types in input transform function ([#51474](https://github.com/angular/angular/pull/51474)) |
|
||||
| [79ff91a813](https://github.com/angular/angular/commit/79ff91a813e544929cb5eb5f9aab762a9f3d0435) | fix | allow TS jsDocParsingMode host option to be programmatically set ([#53126](https://github.com/angular/angular/pull/53126)) |
|
||||
| [5613051a8b](https://github.com/angular/angular/commit/5613051a8bd2626ae347292807b2bf21085c4c02) | fix | allow TS jsDocParsingMode host option to be programmatically set again ([#53292](https://github.com/angular/angular/pull/53292)) |
|
||||
| [df8a825910](https://github.com/angular/angular/commit/df8a825910951bebf34a4eede42f3ce5cd3e6fb7) | fix | project empty block root node ([#53620](https://github.com/angular/angular/pull/53620)) |
|
||||
| [478d622265](https://github.com/angular/angular/commit/478d6222650884478314985e3d5132587c4f670c) | fix | project empty block root node in template pipeline ([#53620](https://github.com/angular/angular/pull/53620)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9e5456912a](https://github.com/angular/angular/commit/9e5456912a699315f1f5e8458d35df946c01dc12) | fix | generate less type checking code in for loops ([#53515](https://github.com/angular/angular/pull/53515)) |
|
||||
| [abdc7e4578](https://github.com/angular/angular/commit/abdc7e45786667e4283912024a641975f1917d97) | feat | support type-checking for generic signal inputs ([#53521](https://github.com/angular/angular/pull/53521)) |
|
||||
| [e620b3a724](https://github.com/angular/angular/commit/e620b3a724cb615af24b7779c0ab492d24efb8cc) | fix | add compiler option to disable control flow content projection diagnostic ([#53311](https://github.com/angular/angular/pull/53311)) |
|
||||
| [4c1d69e288](https://github.com/angular/angular/commit/4c1d69e2880f22745c820eee630d10071e4fa86b) | fix | add diagnostic for control flow that prevents content projection ([#53190](https://github.com/angular/angular/pull/53190)) |
|
||||
| [76ceebad04](https://github.com/angular/angular/commit/76ceebad047f62972654a8c934c77d8d02d9fa14) | fix | do not throw fatal error if extended type check fails ([#53896](https://github.com/angular/angular/pull/53896)) |
|
||||
| [1a6eaa0fea](https://github.com/angular/angular/commit/1a6eaa0fea1024b919e17ac9d2e8c07df7916de8) | fix | input transform in local compilation mode ([#53645](https://github.com/angular/angular/pull/53645)) |
|
||||
| [56a76d73e0](https://github.com/angular/angular/commit/56a76d73e037aeea1975808d5c51608fd23d4fa6) | fix | modify `getConstructorDependencies` helper to work with reflection host after the previous change ([#52215](https://github.com/angular/angular/pull/52215)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2565121851](https://github.com/angular/angular/commit/25651218512becb3a491b1c94d643a5066a6d3cb) | fix | Avoid refreshing a host view twice when using transplanted views ([#53021](https://github.com/angular/angular/pull/53021)) |
|
||||
| [629343f247](https://github.com/angular/angular/commit/629343f24741760e76d98ea1005debaf4775bfc7) | fix | Multiple subscribers to ApplicationRef.isStable should all see values ([#53541](https://github.com/angular/angular/pull/53541)) |
|
||||
| [42f4f70e97](https://github.com/angular/angular/commit/42f4f70e97b47f7ffcf15e8af2c1a3fa3f393903) | fix | remove signal equality check short-circuit ([#53446](https://github.com/angular/angular/pull/53446)) |
|
||||
| [f35adcb9b2](https://github.com/angular/angular/commit/f35adcb9b255741515d54bbce694f4cd34aac249) | fix | update feature usage marker ([#53542](https://github.com/angular/angular/pull/53542)) |
|
||||
| [1fc5442947](https://github.com/angular/angular/commit/1fc5442947b07be7c11fe7c79b9e69f9458c8108) | perf | avoid changes Observable creation on QueryList ([#53498](https://github.com/angular/angular/pull/53498)) |
|
||||
| [e3a6bf9b6c](https://github.com/angular/angular/commit/e3a6bf9b6c3bef03df9bfc8f05b817bc875cbad6) | perf | optimize memory allocation when reconcilling lists ([#52245](https://github.com/angular/angular/pull/52245)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a02767956a](https://github.com/angular/angular/commit/a02767956a00e0b44b690411f01584f6660c83ea) | fix | CF Migration - ensure bound ngIfElse cases ignore line breaks ([#53435](https://github.com/angular/angular/pull/53435)) |
|
||||
| [6aa1bb78e8](https://github.com/angular/angular/commit/6aa1bb78e8b9ca866b3c2d84f4005a0e67f2b6bc) | fix | cf migration - undo changes when html fails to parse post migration ([#53530](https://github.com/angular/angular/pull/53530)) |
|
||||
| [cc02852ac4](https://github.com/angular/angular/commit/cc02852ac44bfea13e8b9d5768694a094fc2c4b5) | fix | CF migration only remove newlines of changed template content ([#53508](https://github.com/angular/angular/pull/53508)) |
|
||||
| [ce1076785c](https://github.com/angular/angular/commit/ce1076785c98ecc9660637cafa4a62a63be8d315) | fix | cf migration validate structure of ngswitch before migrating ([#53530](https://github.com/angular/angular/pull/53530)) |
|
||||
| [1f5c8bf116](https://github.com/angular/angular/commit/1f5c8bf116783c25234ab7fa301a0be842e58007) | fix | ensure we do not overwrite prior template replacements in migration ([#53393](https://github.com/angular/angular/pull/53393)) |
|
||||
| [79f791543b](https://github.com/angular/angular/commit/79f791543b607eea1bb6a28b9c744760ba4fd028) | fix | fix cf migration import removal when errors occur ([#53502](https://github.com/angular/angular/pull/53502)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fdb9cb7a5b](https://github.com/angular/angular/commit/fdb9cb7a5be4695abaa60efc89ecd50ddb9e9e6e) | fix | Get correct base path when using "." as base href when serving from the file:// protocol. ([#53547](https://github.com/angular/angular/pull/53547)) |
|
||||
| [863be4b698](https://github.com/angular/angular/commit/863be4b6981dc60ca0610b0e61d2ba1f5759e2a3) | feat | expose new `input` API for signal-based inputs ([#53872](https://github.com/angular/angular/pull/53872)) |
|
||||
| [94096c6ede](https://github.com/angular/angular/commit/94096c6ede67436a349ae07901f2bb418bf9f461) | feat | support TypeScript 5.3 ([#52572](https://github.com/angular/angular/pull/52572)) |
|
||||
| [69b384c0d1](https://github.com/angular/angular/commit/69b384c0d16f631741339d8757c32ef08260cfce) | fix | `SignalNode` reactive node incorrectly exposing unset field ([#53571](https://github.com/angular/angular/pull/53571)) |
|
||||
| [6f79507ea7](https://github.com/angular/angular/commit/6f79507ea7f272d8d09250e222ca831f407867d8) | fix | Change defer block fixture default behavior to playthrough ([#53956](https://github.com/angular/angular/pull/53956)) |
|
||||
| [32f908ab70](https://github.com/angular/angular/commit/32f908ab70f1b9ed3f92df1cae05ddde68932404) | fix | do not accidentally inherit input transforms when overridden ([#53571](https://github.com/angular/angular/pull/53571)) |
|
||||
| [bdd61c768a](https://github.com/angular/angular/commit/bdd61c768a28b56c68634b99c036986499829f45) | fix | replace assertion with more intentional error ([#52234](https://github.com/angular/angular/pull/52234)) |
|
||||
| [0daca457bb](https://github.com/angular/angular/commit/0daca457bb5bb6ffe14b7037264f8497eb5b3daf) | fix | TestBed should still use the microtask queue to schedule effects ([#53843](https://github.com/angular/angular/pull/53843)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [48c5041687](https://github.com/angular/angular/commit/48c50416872313f23d2759232c990cf8d2de6996) | fix | provide more actionable error message when route is not matched in production mode ([#53523](https://github.com/angular/angular/pull/53523)) |
|
||||
| [5c1d441029](https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4) | feat | Add info property to `NavigationExtras` ([#53303](https://github.com/angular/angular/pull/53303)) |
|
||||
| [50d7916278](https://github.com/angular/angular/commit/50d79162785bb8d3e158a7a4a3733f4c75d3b127) | feat | Add router configuration to resolve navigation promise on error ([#48910](https://github.com/angular/angular/pull/48910)) |
|
||||
| [a5a9b408e2](https://github.com/angular/angular/commit/a5a9b408e2eb64dcf1d3ca16da4897649dd2fc34) | feat | Add transient info to RouterLink input ([#53784](https://github.com/angular/angular/pull/53784)) |
|
||||
| [726530a9af](https://github.com/angular/angular/commit/726530a9af9c8daf7295cc3548f24e70f380d70e) | feat | Allow `onSameUrlNavigation: 'ignore'` in `navigateByUrl` ([#52265](https://github.com/angular/angular/pull/52265)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.9"></a>
|
||||
# 17.0.9 (2024-01-10)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c22b513b3f](https://github.com/angular/angular/commit/c22b513b3f45c49baf4d6e571735aa4aa33b7845) | fix | remove unused parameters from the ngClass constructor ([#53831](https://github.com/angular/angular/pull/53831)) |
|
||||
| [bd9f89d1c8](https://github.com/angular/angular/commit/bd9f89d1c8e295f00ef3399c6bedca4e2ce0e89e) | fix | server-side rendering error when using in-memory scrolling ([#53683](https://github.com/angular/angular/pull/53683)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [92fd6cc42e](https://github.com/angular/angular/commit/92fd6cc42e0a217f4575404ea8e2af462f14ae18) | fix | generate less code for advance instructions ([#53845](https://github.com/angular/angular/pull/53845)) |
|
||||
| [6a41961fbd](https://github.com/angular/angular/commit/6a41961fbdf921f7a3ab82e92185eab751c0d153) | fix | ignore empty switch blocks ([#53776](https://github.com/angular/angular/pull/53776)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7309463697](https://github.com/angular/angular/commit/7309463697110d848781bfe81f04b6070c759928) | fix | interpolatedSignalNotInvoked diagnostic ([#53585](https://github.com/angular/angular/pull/53585)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [441db5123f](https://github.com/angular/angular/commit/441db5123f368c5fe3a505a79b97309e8400250f) | fix | `afterRender` hooks now only run on `ApplicationRef.tick` ([#52455](https://github.com/angular/angular/pull/52455)) |
|
||||
| [f9120d79cb](https://github.com/angular/angular/commit/f9120d79cb88a9f14c4baa6981f71a5afbd984e1) | fix | allow effect to be used inside an ErrorHandler ([#53713](https://github.com/angular/angular/pull/53713)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e92c86b77f](https://github.com/angular/angular/commit/e92c86b77ff7d400c034740ee0ad68acb626a22f) | fix | Fix empty switch case offset bug in cf migration ([#53839](https://github.com/angular/angular/pull/53839)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [91cb16fde9](https://github.com/angular/angular/commit/91cb16fde9ff68bcdc760428c47f4ebf3e476bd2) | fix | Do not delete global Event ([#53659](https://github.com/angular/angular/pull/53659)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.8"></a>
|
||||
# 17.0.8 (2023-12-21)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [de5c9ca8e9](https://github.com/angular/angular/commit/de5c9ca8e9a026ad752aab348bd137f647cc3cc9) | fix | correctly intercept index in loop tracking function ([#53604](https://github.com/angular/angular/pull/53604)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d79489255a](https://github.com/angular/angular/commit/d79489255a48a55b136746856af0d8be51bbe665) | fix | avoid repeated work when parsing version ([#53598](https://github.com/angular/angular/pull/53598)) |
|
||||
| [513fee871e](https://github.com/angular/angular/commit/513fee871eb5d1c8a12bfe64878276b1d9c07705) | fix | tree shake version class ([#53598](https://github.com/angular/angular/pull/53598)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [eb7c29c7b6](https://github.com/angular/angular/commit/eb7c29c7b64a64853c7f7691912835240b62dfeb) | fix | cf migration - detect and error when result is invalid i18n nesting ([#53638](https://github.com/angular/angular/pull/53638)) |
|
||||
| [ed936ba0e9](https://github.com/angular/angular/commit/ed936ba0e9d5323095aa486dc58d8b088b4d64cc) | fix | cf migration - detect and error when result is invalid i18n nesting ([#53638](https://github.com/angular/angular/pull/53638)) ([#53639](https://github.com/angular/angular/pull/53639)) |
|
||||
| [5c2f2539e2](https://github.com/angular/angular/commit/5c2f2539e27bd18fd586c5977d853cd827f7b004) | fix | cf migration - ensure full check runs for all imports ([#53637](https://github.com/angular/angular/pull/53637)) |
|
||||
| [817dc1b27f](https://github.com/angular/angular/commit/817dc1b27fcf32db8f8d8417bdd46a1763460f11) | fix | cf migration - fix bug in attribute formatting ([#53636](https://github.com/angular/angular/pull/53636)) |
|
||||
| [7ac60bab9a](https://github.com/angular/angular/commit/7ac60bab9a4bb7a5af037a2de47339bd7837b157) | fix | cf migration - improve import declaration handling ([#53622](https://github.com/angular/angular/pull/53622)) |
|
||||
| [c3f85e51a9](https://github.com/angular/angular/commit/c3f85e51a970aa458b920835573fa4e392e6f909) | fix | cf migration - preserve indentation on attribute strings ([#53625](https://github.com/angular/angular/pull/53625)) |
|
||||
| [e73205ff5a](https://github.com/angular/angular/commit/e73205ff5ae4c382924266520f271b56972a0db6) | fix | cf migration - stop removing empty newlines from i18n blocks ([#53578](https://github.com/angular/angular/pull/53578)) |
|
||||
| [886aa7b2a9](https://github.com/angular/angular/commit/886aa7b2a99a301eb6e35dc1a59ef918f0bea348) | fix | Fix cf migration bug with parsing for loop conditions properly ([#53558](https://github.com/angular/angular/pull/53558)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0696ab6a5b](https://github.com/angular/angular/commit/0696ab6a5bea8acd6dafde488151150bf3332b79) | fix | Should not freeze original object used for route data ([#53635](https://github.com/angular/angular/pull/53635)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
@@ -76,47 +136,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.0-next.3"></a>
|
||||
# 17.1.0-next.3 (2023-12-06)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5613051a8b](https://github.com/angular/angular/commit/5613051a8bd2626ae347292807b2bf21085c4c02) | fix | allow TS jsDocParsingMode host option to be programmatically set again ([#53292](https://github.com/angular/angular/pull/53292)) |
|
||||
| [77ac4cd324](https://github.com/angular/angular/commit/77ac4cd32491d0c994cb4ea50372601c955cec3d) | fix | generate proper code for nullish coalescing in styling host bindings ([#53305](https://github.com/angular/angular/pull/53305)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e620b3a724](https://github.com/angular/angular/commit/e620b3a724cb615af24b7779c0ab492d24efb8cc) | fix | add compiler option to disable control flow content projection diagnostic ([#53311](https://github.com/angular/angular/pull/53311)) |
|
||||
| [d7a83f9521](https://github.com/angular/angular/commit/d7a83f95213cdd5d3ceefbc95fa1190856b0198c) | fix | avoid conflicts with built-in global variables in for loop blocks ([#53319](https://github.com/angular/angular/pull/53319)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [77939a3bd3](https://github.com/angular/angular/commit/77939a3bd39073d07e24797a4632ec2d2a6b92e0) | fix | cleanup signal consumers for all views ([#53351](https://github.com/angular/angular/pull/53351)) |
|
||||
| [899f6c4a12](https://github.com/angular/angular/commit/899f6c4a12127f87aeedf47ee128ce949ebe717c) | fix | handle hydration of multiple nodes projected in a single slot ([#53270](https://github.com/angular/angular/pull/53270)) |
|
||||
| [4b23221b4e](https://github.com/angular/angular/commit/4b23221b4e5f8be7bcffc8ace255143653550d3d) | fix | support hydration for cases when content is re-projected using ng-template ([#53304](https://github.com/angular/angular/pull/53304)) |
|
||||
| [82609d471c](https://github.com/angular/angular/commit/82609d471c9802b847a5654918eca1ba3ebb29b3) | fix | support swapping hydrated views in `@for` loops ([#53274](https://github.com/angular/angular/pull/53274)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c1e8c477b](https://github.com/angular/angular/commit/1c1e8c477b8d5c8deecf05744bd1d5f7c86d2e14) | fix | CF migration - ensure NgIfElse attributes are properly removed ([#53298](https://github.com/angular/angular/pull/53298)) |
|
||||
| [2998d482dd](https://github.com/angular/angular/commit/2998d482dd3f1f5ff7f08260c3947ded74dac126) | fix | CF Migration - Fix case of aliases on i18n ng-templates preventing removal ([#53299](https://github.com/angular/angular/pull/53299)) |
|
||||
| [aad5e5bd0e](https://github.com/angular/angular/commit/aad5e5bd0e7f6581f51fc3a23c98a7f11219a8f8) | fix | CF Migration add support for ngIf with just a then ([#53297](https://github.com/angular/angular/pull/53297)) |
|
||||
| [6f75471307](https://github.com/angular/angular/commit/6f75471307a9458d0cffd0ee2bbf4190640c3c3a) | fix | CF Migration fix missing alias for bound ngifs ([#53296](https://github.com/angular/angular/pull/53296)) |
|
||||
| [2a5a8f6f05](https://github.com/angular/angular/commit/2a5a8f6f052961c010a68a05d868f50220f2fcf2) | fix | Change CF Migration ng-template placeholder generation and handling ([#53394](https://github.com/angular/angular/pull/53394)) |
|
||||
| [03e2f1bb25](https://github.com/angular/angular/commit/03e2f1bb25693d1a5f4e53fc4f4cd1937cf6bda1) | fix | fix regexp for else and then in cf migration ([#53257](https://github.com/angular/angular/pull/53257)) |
|
||||
| [f4a96a9160](https://github.com/angular/angular/commit/f4a96a9160927903c38e172f6375c4bc5f8e0905) | fix | handle aliases on bound ngIf migrations ([#53261](https://github.com/angular/angular/pull/53261)) |
|
||||
| [5a0ed28c9d](https://github.com/angular/angular/commit/5a0ed28c9d3f1c7507feee482e37c3346d52b033) | fix | handle nested ng-template replacement safely in CF migration ([#53368](https://github.com/angular/angular/pull/53368)) |
|
||||
| [01b18a4248](https://github.com/angular/angular/commit/01b18a4248f068df33c0ca8a2d62ef2fc69f941c) | fix | handle templates outside of component in cf migration ([#53368](https://github.com/angular/angular/pull/53368)) |
|
||||
| [9834fd2738](https://github.com/angular/angular/commit/9834fd27387ed5edc65bed56aa003fc45d250420) | fix | remove setting that removes comments in CF migration ([#53350](https://github.com/angular/angular/pull/53350)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5c1d441029](https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4) | feat | Add info property to `NavigationExtras` ([#53303](https://github.com/angular/angular/pull/53303)) |
|
||||
| [50d7916278](https://github.com/angular/angular/commit/50d79162785bb8d3e158a7a4a3733f4c75d3b127) | feat | Add router configuration to resolve navigation promise on error ([#48910](https://github.com/angular/angular/pull/48910)) |
|
||||
| [1940280d27](https://github.com/angular/angular/commit/1940280d27bb3ece585d6345dbd742d208f46912) | fix | Ensure canMatch guards run on wildcard routes ([#53239](https://github.com/angular/angular/pull/53239)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.6"></a>
|
||||
# 17.0.6 (2023-12-06)
|
||||
### compiler
|
||||
@@ -156,43 +175,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.0-next.2"></a>
|
||||
# 17.1.0-next.2 (2023-11-29)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [79ff91a813](https://github.com/angular/angular/commit/79ff91a813e544929cb5eb5f9aab762a9f3d0435) | fix | allow TS jsDocParsingMode host option to be programmatically set ([#53126](https://github.com/angular/angular/pull/53126)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4c1d69e288](https://github.com/angular/angular/commit/4c1d69e2880f22745c820eee630d10071e4fa86b) | fix | add diagnostic for control flow that prevents content projection ([#53190](https://github.com/angular/angular/pull/53190)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [58cf389d80](https://github.com/angular/angular/commit/58cf389d8095dd522a998b6b9a7d7b8da1656644) | fix | avoid stale provider info when TestBed.overrideProvider is used ([#52918](https://github.com/angular/angular/pull/52918)) |
|
||||
| [c7c7ea9813](https://github.com/angular/angular/commit/c7c7ea9813f6dcf91c096bb37d36bfe0c715a04f) | fix | inherit host directives ([#52992](https://github.com/angular/angular/pull/52992)) |
|
||||
| [b35c6731e5](https://github.com/angular/angular/commit/b35c6731e51de9c33707010fc780cbaa559be6c3) | fix | Reattached views that are dirty from a signal update should refresh ([#53001](https://github.com/angular/angular/pull/53001)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dbca1c9d61](https://github.com/angular/angular/commit/dbca1c9d618be4d195d779ef80606c6d22f7c977) | fix | Add ngForTemplate support to control flow migration ([#53076](https://github.com/angular/angular/pull/53076)) |
|
||||
| [53912fdf74](https://github.com/angular/angular/commit/53912fdf74e1224f95edc3dd550c51451e613c44) | fix | allows colons in ngIf else cases to migrate ([#53076](https://github.com/angular/angular/pull/53076)) |
|
||||
| [fadfee4324](https://github.com/angular/angular/commit/fadfee43247acec7e450d5cc929a0a6780a54bd1) | fix | cf migration fix migrating empty switch default ([#53237](https://github.com/angular/angular/pull/53237)) |
|
||||
| [b2aeaf5d97](https://github.com/angular/angular/commit/b2aeaf5d97327842a5bb657e9f02bb4f1358304b) | fix | CF migration log warning when collection aliasing detected in `@for` ([#53238](https://github.com/angular/angular/pull/53238)) |
|
||||
| [c6326289f8](https://github.com/angular/angular/commit/c6326289f87089b3415ba0f3e371206b8a396b66) | fix | cf migration removes unnecessary bound ngifelse attribute ([#53236](https://github.com/angular/angular/pull/53236)) |
|
||||
| [e6f10e81d2](https://github.com/angular/angular/commit/e6f10e81d2486b5cc1f468777b7e1057d3f09dc5) | fix | control flow migration formatting fixes ([#53076](https://github.com/angular/angular/pull/53076)) |
|
||||
| [6291c8db09](https://github.com/angular/angular/commit/6291c8db092c0ab67f6789cc03c19dfa0bdc251d) | fix | fix off by one issue with template removal in CF migration ([#53255](https://github.com/angular/angular/pull/53255)) |
|
||||
| [f1b7d40057](https://github.com/angular/angular/commit/f1b7d400575a6a02a2fc2b642c2b8c15cca9b3c4) | fix | fixes CF migration i18n ng-template offsets ([#53212](https://github.com/angular/angular/pull/53212)) |
|
||||
| [a738b48717](https://github.com/angular/angular/commit/a738b48717e16613b34c3e50480cd49f2a31858f) | fix | fixes control flow migration common module removal ([#53076](https://github.com/angular/angular/pull/53076)) |
|
||||
| [a3599515bb](https://github.com/angular/angular/commit/a3599515bb2513fbcb2899284c7f6b4bc021ddaf) | fix | properly handle ngIfThen cases in CF migration ([#53256](https://github.com/angular/angular/pull/53256)) |
|
||||
| [8a52674faa](https://github.com/angular/angular/commit/8a52674faacefd4042726383fdf0aed59a04fb7e) | fix | Update CF migration to skip templates with duplicate ng-template names ([#53204](https://github.com/angular/angular/pull/53204)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [29e0834c4d](https://github.com/angular/angular/commit/29e0834c4deecfa8bf384b5e4359796c8123afcd) | fix | Resolvers in different parts of the route tree should be able to execute together ([#52934](https://github.com/angular/angular/pull/52934)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.5"></a>
|
||||
# 17.0.5 (2023-11-29)
|
||||
### core
|
||||
@@ -222,39 +204,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.0-next.1"></a>
|
||||
# 17.1.0-next.1 (2023-11-20)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [29c5416d14](https://github.com/angular/angular/commit/29c5416d14638a05a894269aa5dbe67e98754418) | fix | remove `load` on image once it fails to load ([#52990](https://github.com/angular/angular/pull/52990)) |
|
||||
| [7affa57754](https://github.com/angular/angular/commit/7affa5775427e92ef6e949c879765b7c8aa172da) | fix | scan images once page is loaded ([#52991](https://github.com/angular/angular/pull/52991)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ec2d6e7b9c](https://github.com/angular/angular/commit/ec2d6e7b9c2b386247d1320ee89f8e3ac5e5a0dd) | fix | changed after checked error in for loops ([#52935](https://github.com/angular/angular/pull/52935)) |
|
||||
| [406049b95e](https://github.com/angular/angular/commit/406049b95e5234f17a7a18839ac848640f53fdde) | fix | generate i18n instructions for blocks ([#52958](https://github.com/angular/angular/pull/52958)) |
|
||||
| [d9d566d315](https://github.com/angular/angular/commit/d9d566d31540582d73201675d0b8ed901261669e) | fix | nested for loops incorrectly calculating computed variables ([#52931](https://github.com/angular/angular/pull/52931)) |
|
||||
| [5fb707f81a](https://github.com/angular/angular/commit/5fb707f81aee43751e61d2ed0861afc9b85bc85a) | fix | produce placeholder for blocks in i18n bundles ([#52958](https://github.com/angular/angular/pull/52958)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b4d022e230](https://github.com/angular/angular/commit/b4d022e230ca141b12437949d11dc384bfe5c082) | fix | add diagnostic for control flow that prevents content projection ([#52726](https://github.com/angular/angular/pull/52726)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ed0fbd4071](https://github.com/angular/angular/commit/ed0fbd4071339b1af22d82bac07d51c6c41790cd) | fix | cleanup loading promise when no dependencies are defined ([#53031](https://github.com/angular/angular/pull/53031)) |
|
||||
| [1ce31d819b](https://github.com/angular/angular/commit/1ce31d819b2e4f4425a41f07167a6edce98e77e1) | fix | handle local refs when `getDeferBlocks` is invoked in tests ([#52973](https://github.com/angular/angular/pull/52973)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e33f6e0f1a](https://github.com/angular/angular/commit/e33f6e0f1a483cad908fa6d7376d62332797499c) | fix | control flow migration fails for async pipe with unboxing of observable ([#52756](https://github.com/angular/angular/pull/52756)) ([#52972](https://github.com/angular/angular/pull/52972)) |
|
||||
| [5564d020cd](https://github.com/angular/angular/commit/5564d020cdcea8273b65cf69c45c3f935195af66) | fix | Fixes control flow migration if then else case ([#53006](https://github.com/angular/angular/pull/53006)) |
|
||||
| [28f6cbf9c9](https://github.com/angular/angular/commit/28f6cbf9c91f957b4926fe34610387e1f1919d4f) | fix | fixes migrations of nested switches in control flow ([#53010](https://github.com/angular/angular/pull/53010)) |
|
||||
| [e090b48bf8](https://github.com/angular/angular/commit/e090b48bf8534761d46523be57a7889a325bcdec) | fix | tweaks to formatting in control flow migration ([#53058](https://github.com/angular/angular/pull/53058)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.4"></a>
|
||||
# 17.0.4 (2023-11-20)
|
||||
### common
|
||||
@@ -288,24 +237,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.0-next.0"></a>
|
||||
# 17.1.0-next.0 (2023-11-15)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [56a76d73e0](https://github.com/angular/angular/commit/56a76d73e037aeea1975808d5c51608fd23d4fa6) | fix | modify `getConstructorDependencies` helper to work with reflection host after the previous change ([#52215](https://github.com/angular/angular/pull/52215)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [94096c6ede](https://github.com/angular/angular/commit/94096c6ede67436a349ae07901f2bb418bf9f461) | feat | support TypeScript 5.3 ([#52572](https://github.com/angular/angular/pull/52572)) |
|
||||
| [bdd61c768a](https://github.com/angular/angular/commit/bdd61c768a28b56c68634b99c036986499829f45) | fix | replace assertion with more intentional error ([#52234](https://github.com/angular/angular/pull/52234)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [726530a9af](https://github.com/angular/angular/commit/726530a9af9c8daf7295cc3548f24e70f380d70e) | feat | Allow `onSameUrlNavigation: 'ignore'` in `navigateByUrl` ([#52265](https://github.com/angular/angular/pull/52265)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.0.3"></a>
|
||||
# 17.0.3 (2023-11-15)
|
||||
### animations
|
||||
|
||||
@@ -61,6 +61,25 @@ Get started with Angular, learn the fundamentals and explore advanced topics on
|
||||
- [Lazy Loading][lazyloading]
|
||||
- [Animations][animations]
|
||||
|
||||
### Local Development
|
||||
|
||||
To contribute to Angular docs, you can setup a local environment with the following commands:
|
||||
|
||||
```bash
|
||||
# Clone Angular repo
|
||||
git clone https://github.com/angular/angular.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd angular
|
||||
|
||||
# Install dependencies
|
||||
yarn
|
||||
|
||||
# Build and run local dev server
|
||||
# Note: Initial build will take some time
|
||||
yarn docs
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -80,7 +80,7 @@ yarn_install(
|
||||
"//:.yarnrc",
|
||||
"//:tools/npm-patches/@bazel+jasmine+5.8.1.patch",
|
||||
"//tools:postinstall-patches.js",
|
||||
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-b2cec2dcba358f9dc7111800a3d65738f57abe8f.patch",
|
||||
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-e0ec7b60641d7f6369be45d8d02663fd50f320be.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+concatjs+5.8.1.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+esbuild+5.7.1.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+protractor+5.7.1.patch",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
/src/assets/tutorials
|
||||
/src/assets/previews
|
||||
+78
-17
@@ -1,24 +1,34 @@
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect")
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# All source and configuration files required to build the docs app
|
||||
APPLICATION_FILES = [
|
||||
"//:angular.json",
|
||||
"adev/tsconfig.app.json",
|
||||
"adev/tsconfig.json",
|
||||
"adev/tsconfig.worker.json",
|
||||
"angular.json",
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.json",
|
||||
"tsconfig.worker.json",
|
||||
] + glob(
|
||||
["adev/src/**/*"],
|
||||
["src/**/*"],
|
||||
exclude = [
|
||||
"adev/src/**/*.spec.ts",
|
||||
"src/**/*.spec.ts",
|
||||
# Temporarily exclude generated sources produced by the non-bazel
|
||||
# build until the whole project is built by bazel and this directory
|
||||
# isn't needed.
|
||||
"adev/src/generated/**/*",
|
||||
"src/generated/**/*",
|
||||
],
|
||||
)
|
||||
|
||||
APPLICATION_DEPS = [
|
||||
TEST_FILES = APPLICATION_FILES + [
|
||||
"karma.conf.js",
|
||||
"tsconfig.spec.json",
|
||||
] + glob(
|
||||
["**/*.spec.ts"],
|
||||
)
|
||||
|
||||
APPLICATION_DEPS = link_packages([
|
||||
"@npm//@angular-devkit/build-angular",
|
||||
"@npm//@angular/animations",
|
||||
"@npm//@angular/cdk",
|
||||
@@ -38,7 +48,48 @@ APPLICATION_DEPS = [
|
||||
"@npm//ogl",
|
||||
"@npm//rxjs",
|
||||
"@npm//typescript",
|
||||
]
|
||||
"//adev/src/assets/images",
|
||||
"//adev/src/assets/textures",
|
||||
"//adev/src/assets/previews",
|
||||
"//adev/src/assets:tutorials",
|
||||
"//adev/src/assets/icons",
|
||||
"//adev/src/assets:api",
|
||||
"//adev/src/assets:content",
|
||||
"@npm//@typescript/vfs",
|
||||
"@npm//@codemirror/state",
|
||||
"@npm//@codemirror/view",
|
||||
"@npm//@codemirror/language",
|
||||
"@npm//@codemirror/commands",
|
||||
"@npm//@codemirror/search",
|
||||
"@npm//@codemirror/autocomplete",
|
||||
"@npm//@codemirror/lint",
|
||||
"@npm//@codemirror/lang-html",
|
||||
"@npm//@codemirror/lang-angular",
|
||||
"@npm//@codemirror/lang-css",
|
||||
"@npm//@codemirror/lang-sass",
|
||||
"@npm//@codemirror/lang-javascript",
|
||||
"@npm//@lezer/highlight",
|
||||
"@npm//@lezer/javascript",
|
||||
"@npm//@lezer/common",
|
||||
"@npm//xterm",
|
||||
"@npm//xterm-addon-fit",
|
||||
"@npm//angular-split",
|
||||
])
|
||||
|
||||
TEST_DEPS = APPLICATION_DEPS + link_packages([
|
||||
"@npm//@angular/platform-browser-dynamic",
|
||||
"@npm//@angular/build-tooling/bazel/browsers/chromium",
|
||||
"@npm//@types/jasmine",
|
||||
"@npm//@types/node",
|
||||
"@npm//assert",
|
||||
"@npm//jasmine",
|
||||
"@npm//jasmine-core",
|
||||
"@npm//karma-chrome-launcher",
|
||||
"@npm//karma-coverage",
|
||||
"@npm//karma-jasmine",
|
||||
"@npm//karma-jasmine-html-reporter",
|
||||
"//aio/tools:windows-chromium-path",
|
||||
])
|
||||
|
||||
copy_to_bin(
|
||||
name = "application_files_bin",
|
||||
@@ -49,16 +100,13 @@ architect(
|
||||
name = "build",
|
||||
args = [
|
||||
"angular-dev:build",
|
||||
"--output-path=build-app",
|
||||
"--output-path=build",
|
||||
],
|
||||
chdir = "$(RULEDIR)",
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
output_dir = True,
|
||||
# When building with local packages (--config=aio_local_deps), RBE complains about
|
||||
# the input tree being too large (> 70,000 files).
|
||||
tags = ["no-remote-exec"],
|
||||
)
|
||||
|
||||
architect(
|
||||
@@ -73,7 +121,20 @@ architect(
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
# When building with local packages (--config=aio_local_deps), RBE complains about
|
||||
# the input tree being too large (> 70,000 files).
|
||||
tags = ["no-remote-exec"],
|
||||
)
|
||||
|
||||
architect_test(
|
||||
name = "test",
|
||||
args = [
|
||||
"angular-dev:test",
|
||||
"--no-watch",
|
||||
],
|
||||
chdir = package_name(),
|
||||
data = TEST_DEPS + TEST_FILES,
|
||||
env = {
|
||||
"CHROME_BIN": "../$(CHROMIUM)",
|
||||
},
|
||||
toolchains = [
|
||||
"@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"$schema": "../node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": ".",
|
||||
"projects": {
|
||||
@@ -12,30 +12,32 @@
|
||||
"changeDetection": "OnPush"
|
||||
}
|
||||
},
|
||||
"root": "adev",
|
||||
"sourceRoot": "adev/src",
|
||||
"root": ".",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "adev",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"externalDependencies": [
|
||||
"path"
|
||||
],
|
||||
"outputPath": "dist/angular-dev",
|
||||
"index": "adev/src/index.html",
|
||||
"browser": "adev/src/main.ts",
|
||||
"server": "adev/src/main.server.ts",
|
||||
// TODO(josephperrott): enabled prerender
|
||||
"prerender": false,
|
||||
"polyfills": ["adev/src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "adev/tsconfig.app.json",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"polyfills": ["src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"adev/src/favicon.ico",
|
||||
"adev/src/robots.txt",
|
||||
"adev/src/assets"
|
||||
"src/favicon.ico",
|
||||
"src/robots.txt",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": ["@angular/docs/styles/global-styles.scss"],
|
||||
"scripts": [],
|
||||
"webWorkerTsConfig": "adev/tsconfig.worker.json",
|
||||
"webWorkerTsConfig": "tsconfig.worker.json",
|
||||
"optimization": {
|
||||
"fonts": {
|
||||
// TODO(josephperrott): enabled inline scripts
|
||||
@@ -86,6 +88,7 @@
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"include": ["src/app"],
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["@angular/docs/styles/global-styles.scss"],
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"hosting": {
|
||||
"target": "angular-docs",
|
||||
"public": "./build/browser",
|
||||
"ignore": ["**/.*"],
|
||||
"headers": [
|
||||
{
|
||||
"source": "assets/tutorials/common/*.jpg",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cross-Origin-Resource-Policy",
|
||||
"value": "cross-origin"
|
||||
},
|
||||
{
|
||||
"key": "Access-Control-Allow-Origin",
|
||||
"value": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cache-Control",
|
||||
"value": "public,max-age=31536000,immutable"
|
||||
},
|
||||
{
|
||||
"key": "Access-Control-Allow-Origin",
|
||||
"value": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/[0-9a-f][0-9a-f][0-9a-f].[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].js",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cross-Origin-Embedder-Policy",
|
||||
"value": "require-corp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/@(ngsw-worker.js|ngsw.json)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cache-Control",
|
||||
"value": "no-cache"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/**",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cross-Origin-Opener-Policy",
|
||||
"value": "same-origin"
|
||||
},
|
||||
|
||||
{
|
||||
"key": "Cross-Origin-Embedder-Policy",
|
||||
"value": "require-corp"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
const {getAdjustedChromeBinPathForWindows} = require('../aio/tools/windows-chromium-path');
|
||||
|
||||
process.env.CHROME_BIN = getAdjustedChromeBinPathForWindows();
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
{'reporter:jasmine-seed': ['type', JasmineSeedReporter]},
|
||||
],
|
||||
proxies: {
|
||||
'/dummy/image': 'src/assets/images/logos/angular/angular.png',
|
||||
},
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
random: true,
|
||||
seed: '',
|
||||
},
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/site'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml', 'jasmine-seed'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
customLaunchers: {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
|
||||
},
|
||||
},
|
||||
browsers: ['ChromeHeadlessNoSandbox'],
|
||||
browserNoActivityTimeout: 60000,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Helpers
|
||||
function JasmineSeedReporter(baseReporterDecorator) {
|
||||
baseReporterDecorator(this);
|
||||
|
||||
this.onBrowserComplete = (browser, result) => {
|
||||
const seed = result.order && result.order.random && result.order.seed;
|
||||
if (seed) this.write(`${browser}: Randomized with seed ${seed}.\n`);
|
||||
};
|
||||
|
||||
this.onRunComplete = () => undefined;
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
/* tslint:disable:no-console */
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {readFileSync} from 'fs';
|
||||
import {glob} from 'glob';
|
||||
import {join} from 'path';
|
||||
|
||||
import {SUB_NAVIGATION_DATA} from '../../src/app/sub-navigation-data';
|
||||
|
||||
// PATHS
|
||||
const PROJECT_FOLDER_PATH = join(__dirname, '../../src');
|
||||
|
||||
// SKIP Files
|
||||
const SKIPED_ORPHAN = ['kitchen-sink', 'error', 'examples'];
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const contentDir = join(PROJECT_FOLDER_PATH, 'content');
|
||||
const mdFiles = await retrieveAllMarkdownFiles(contentDir);
|
||||
|
||||
const allowedPaths = new Set([
|
||||
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getPaths(item)),
|
||||
SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getPaths(item)),
|
||||
SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getPaths(item)),
|
||||
]);
|
||||
|
||||
const contentPaths = new Set([
|
||||
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getContentPaths(item)),
|
||||
...SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getContentPaths(item)),
|
||||
...SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getContentPaths(item)),
|
||||
]);
|
||||
|
||||
// Regular expression to match relative links
|
||||
const regex = /\]\((?!https?:\/\/)([^)]+)\)/g;
|
||||
|
||||
const orphanFiles: string[] = [];
|
||||
for (const {path} of mdFiles) {
|
||||
if (
|
||||
!contentPaths.has(path.slice(0, -3)) &&
|
||||
!path.startsWith('tutorials') &&
|
||||
!SKIPED_ORPHAN.some((skip) => path.startsWith(skip))
|
||||
) {
|
||||
orphanFiles.push(path.slice(0, -3));
|
||||
}
|
||||
}
|
||||
if (orphanFiles.length > 0) {
|
||||
console.log('======== ORPHAN FILES =========\n');
|
||||
orphanFiles.forEach((file) => console.warn(file));
|
||||
}
|
||||
|
||||
const deadLinks: {path: string; links: string[]}[] = [];
|
||||
for (const {content, path} of mdFiles) {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const relativeLink = match[1].split('#')[0];
|
||||
if (!allowedPaths.has(relativeLink) && relativeLink.startsWith('guide')) {
|
||||
matches.push(relativeLink);
|
||||
}
|
||||
}
|
||||
if (matches.length) {
|
||||
deadLinks.push({path, links: matches});
|
||||
}
|
||||
}
|
||||
if (deadLinks.length > 0) {
|
||||
console.warn('\n\n======== DEAD RELATIVE LINKS =========\n');
|
||||
deadLinks.forEach(({path, links}) => {
|
||||
console.warn(path);
|
||||
links.forEach((path) => console.log(`\t ${path}`));
|
||||
});
|
||||
}
|
||||
|
||||
if (deadLinks.length > 0 || orphanFiles.length > 0) {
|
||||
throw new Error('Check fails');
|
||||
}
|
||||
}
|
||||
|
||||
function getPaths(navItem: NavigationItem): string[] {
|
||||
return [
|
||||
...(navItem.path ? [navItem.path] : []),
|
||||
...(navItem.children ?? []).flatMap((item) => getPaths(item)),
|
||||
];
|
||||
}
|
||||
|
||||
function getContentPaths(navItem: NavigationItem): string[] {
|
||||
return [
|
||||
...(navItem.contentPath ? [navItem.contentPath] : []),
|
||||
...(navItem.children ?? []).flatMap((item) => getContentPaths(item)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search the provided directory for all markdown files and
|
||||
* asyncronously load them.
|
||||
*/
|
||||
async function retrieveAllMarkdownFiles(
|
||||
baseDir: string,
|
||||
): Promise<{path: string; content: string}[]> {
|
||||
const files = await glob('**/*.md', {
|
||||
root: baseDir,
|
||||
cwd: baseDir,
|
||||
ignore: ['**/node_modules/**'],
|
||||
});
|
||||
|
||||
return files.map((fullPathToFile) => ({
|
||||
path: fullPathToFile,
|
||||
content: readFileSync(join(baseDir, fullPathToFile), {encoding: 'utf-8'}),
|
||||
}));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# Tutorial Scripts
|
||||
|
||||
The tutorials scripts are responsible for generating the tutorials source code and metadata by reading the tutorials content from [`src/content/tutorials`](/src/content/tutorials).
|
||||
|
||||
_See [the tutorials content README](/src/content/tutorials/README.md) for more information about the tutorials content._
|
||||
|
||||
For each tutorial, the following JSON files are generated and stored at `src/assets/tutorials`:
|
||||
|
||||
- `source-code/<tutorial-pathname>.json`: contains the tutorial source code in the [WebContainer](https://webcontainers.io) `FileSystemTree` format. It's
|
||||
used to mount the tutorial files in the WebContainer. See the WebContainer docs for more info: <https://webcontainers.io/guides/working-with-the-file-system>
|
||||
- `metadata/<tutorial-pathname>.json`: the tutorial metadata has all the information necessary to manage the tutorial in app.
|
||||
It contains the tutorial `config.json`, the previous and next step, the next tutorial, and the code editor file paths and contents.
|
||||
- `routes.json`: defines all routes for the tutorials, used at [`src/app/sub-navigation-data.ts`](/src/app/sub-navigation-data.ts) to populate the tutorials nav.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
yarn generate-tutorials
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
|
||||
import {PagePrefix} from '../../src/app/core/enums/pages';
|
||||
|
||||
import type {TutorialNavigationData, TutorialNavigationItem, TutorialStep} from './tutorials-types';
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {TUTORIALS_ROUTES_NODE_PATH} from './utils/node-constants';
|
||||
|
||||
/** the step is used only in this function to sort the nav items */
|
||||
export type TutorialNavigationItemWithStep = TutorialNavigationItem & {
|
||||
tutorialData: TutorialNavigationData & {
|
||||
step: TutorialStep['step'];
|
||||
};
|
||||
};
|
||||
|
||||
export async function buildTutorialsNavigationItems(
|
||||
initialNavigationItems: TutorialNavigationItemWithStep[],
|
||||
) {
|
||||
const sortedInitialNavigationItems = initialNavigationItems.sort((a, b) => {
|
||||
if (!a.tutorialData?.step || !b.tutorialData?.step) return 0;
|
||||
|
||||
if (a.tutorialData.step < b.tutorialData.step) return -1;
|
||||
else if (a.tutorialData.step > b.tutorialData.step) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const addTutorialPagePrefixToNavItemPath = (path: string) => `${PagePrefix.TUTORIALS}/${path}`;
|
||||
|
||||
const navItemMap = new Map<NavigationItem['path'], TutorialNavigationItem>();
|
||||
|
||||
for (const navItem of sortedInitialNavigationItems) {
|
||||
const pathParts = navItem.path.split('/');
|
||||
const topLevelPath = pathParts[0];
|
||||
const topLevelNavItem = navItemMap.get(topLevelPath);
|
||||
|
||||
const isNavItemTopLevelPath = pathParts.length === 1;
|
||||
|
||||
const newNavItem: TutorialNavigationItem = {
|
||||
path: addTutorialPagePrefixToNavItemPath(navItem.path),
|
||||
label: navItem.label,
|
||||
contentPath: navItem.contentPath,
|
||||
tutorialData: navItem.tutorialData,
|
||||
};
|
||||
|
||||
if (!topLevelNavItem) {
|
||||
// create new map entry
|
||||
navItemMap.set(
|
||||
topLevelPath,
|
||||
isNavItemTopLevelPath ? newNavItem : ({children: [newNavItem]} as TutorialNavigationItem),
|
||||
);
|
||||
} else if (isNavItemTopLevelPath) {
|
||||
// add top level path to existing map entry
|
||||
navItemMap.set(topLevelPath, {
|
||||
...newNavItem,
|
||||
...topLevelNavItem,
|
||||
});
|
||||
} else if (!topLevelNavItem.children) {
|
||||
// create children in existing map entry
|
||||
topLevelNavItem.children = [newNavItem];
|
||||
|
||||
navItemMap.set(topLevelPath, topLevelNavItem);
|
||||
} else {
|
||||
// add children to existing map entry
|
||||
topLevelNavItem.children.push(newNavItem);
|
||||
|
||||
navItemMap.set(topLevelPath, topLevelNavItem);
|
||||
}
|
||||
}
|
||||
|
||||
const tutorialNavigationItems = Array.from(navItemMap.values());
|
||||
|
||||
await createDirectoryAndWriteFile(
|
||||
TUTORIALS_ROUTES_NODE_PATH,
|
||||
JSON.stringify(tutorialNavigationItems),
|
||||
);
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
import {mkdir, rm, writeFile} from 'fs/promises';
|
||||
import {basename, join, sep} from 'path';
|
||||
|
||||
import {
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
PLAYGROUND_ROUTE_NODE_PATH,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_COMMON_NODE_PATH,
|
||||
TUTORIALS_METADATA_NODE_PATH,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_SOURCE_CODE_NODE_PATH,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import {createDirectoryAndWriteFile, getFilesContents, globWithCwdPath} from './utils/filesystem';
|
||||
|
||||
import {
|
||||
TutorialNavigationItemWithStep,
|
||||
buildTutorialsNavigationItems,
|
||||
} from './generate-tutorials-routes';
|
||||
|
||||
import {copyCommonAssetsToAppAssets, getTutorialCommonFiles} from './tutorial-type/common';
|
||||
import {getHomepagePlaygroundFiles} from './tutorial-type/homepage';
|
||||
import {getIntroAndStepsTutorialFiles} from './tutorial-type/intro-and-steps';
|
||||
import {getTutorialPlaygroundFiles, isPlaygroundRouteData} from './tutorial-type/playground';
|
||||
import {PlaygroundFiles, TutorialFiles} from './tutorials-types';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
} from './utils/web-constants';
|
||||
|
||||
main();
|
||||
|
||||
/**
|
||||
* Builds and stores the tutorials source codes and metadata into JSON files.
|
||||
* These files are later used to load the tutorials into the WebContainer file system
|
||||
* and manage the tutorials in the embedded editor.
|
||||
*
|
||||
* @see https://webcontainers.io/guides/working-with-the-file-system
|
||||
*/
|
||||
export async function main() {
|
||||
const consoleTimeLabel = 'Tutorials files build done in';
|
||||
|
||||
// Informs the developers that this script is running and how much time it took
|
||||
console.info('Building tutorials files...');
|
||||
console.time(consoleTimeLabel);
|
||||
|
||||
const tutorialsFiles = await getTutorialsFiles();
|
||||
|
||||
// Clean up the directory before writing the files
|
||||
await rm(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}).catch(() => {
|
||||
// `rm` throws if the directory doesn't exist, meaning this script is
|
||||
// being executed for the first time
|
||||
});
|
||||
await Promise.all([
|
||||
mkdir(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}),
|
||||
mkdir(TUTORIALS_SOURCE_CODE_NODE_PATH, {recursive: true}),
|
||||
mkdir(TUTORIALS_METADATA_NODE_PATH, {recursive: true}),
|
||||
]);
|
||||
|
||||
const writeFilesPromises: Promise<void>[] = [];
|
||||
const tutorialsRoutes: TutorialNavigationItemWithStep[] = [];
|
||||
|
||||
tutorialsFiles.forEach(({sourceCode, metadata, route, sourceCodeZip}, tutorialName) => {
|
||||
if (sourceCode) {
|
||||
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.json');
|
||||
const fileContents = JSON.stringify(sourceCode);
|
||||
|
||||
if (tutorialName.includes(sep)) {
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
|
||||
} else {
|
||||
writeFilesPromises.push(writeFile(filePath, fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
const filePath = join(TUTORIALS_METADATA_NODE_PATH, tutorialName + '.json');
|
||||
const fileContents = JSON.stringify(metadata);
|
||||
|
||||
if (tutorialName.includes(sep)) {
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
|
||||
} else {
|
||||
writeFilesPromises.push(writeFile(filePath, fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
if (route) {
|
||||
if (isPlaygroundRouteData(route)) {
|
||||
writeFilesPromises.push(
|
||||
createDirectoryAndWriteFile(PLAYGROUND_ROUTE_NODE_PATH, JSON.stringify(route)),
|
||||
);
|
||||
} else {
|
||||
tutorialsRoutes.push({
|
||||
...route,
|
||||
path: tutorialName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceCodeZip) {
|
||||
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.zip');
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, sourceCodeZip));
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
...writeFilesPromises,
|
||||
copyCommonAssetsToAppAssets(),
|
||||
buildTutorialsNavigationItems(tutorialsRoutes),
|
||||
]);
|
||||
|
||||
console.timeEnd(consoleTimeLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tutorials source code and generate a Map representing the files
|
||||
* and contents that will be stored in the filesystem.
|
||||
*
|
||||
* Those files are later used by the `/tutorials` pages.
|
||||
*
|
||||
* The map key is the project name and the value contain the contents for each
|
||||
* .json file.
|
||||
*/
|
||||
async function getTutorialsFiles(): Promise<Map<string, TutorialFiles | PlaygroundFiles>> {
|
||||
const [tutorialsProjects, commonTutorialFiles] = await Promise.all([
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
ignore: [...(GLOB_OPTIONS.ignore as string[]), '*.md', TUTORIALS_COMMON_DIRECTORY],
|
||||
maxDepth: 1,
|
||||
cwd: TUTORIALS_PROJECT_PATH,
|
||||
}),
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: TUTORIALS_COMMON_NODE_PATH,
|
||||
nodir: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const tutorialsFiles = new Map<string, TutorialFiles | PlaygroundFiles>();
|
||||
|
||||
// tutorials depends on the common project files so here we handle it first
|
||||
const commonFilesContents = await getFilesContents(commonTutorialFiles);
|
||||
|
||||
tutorialsFiles.set(
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
await getTutorialCommonFiles(commonTutorialFiles, commonFilesContents),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
tutorialsProjects.map(async (tutorialPath) => {
|
||||
const tutorialName = basename(tutorialPath);
|
||||
|
||||
const [tutorialDirectories, tutorialFiles] = await Promise.all([
|
||||
globWithCwdPath('**/', {...GLOB_OPTIONS, cwd: tutorialPath}),
|
||||
globWithCwdPath('**', {...GLOB_OPTIONS, nodir: true, cwd: tutorialPath}),
|
||||
]);
|
||||
|
||||
try {
|
||||
if (tutorialName === TUTORIALS_PLAYGROUND_DIRECTORY) {
|
||||
const playgroundFiles = await getTutorialPlaygroundFiles(commonFilesContents);
|
||||
|
||||
playgroundFiles.forEach((playgroundFiles, playgroundName) => {
|
||||
tutorialsFiles.set(playgroundName, playgroundFiles);
|
||||
});
|
||||
} else if (tutorialName === TUTORIALS_HOMEPAGE_DIRECTORY) {
|
||||
const homepageFiles = await getHomepagePlaygroundFiles(commonFilesContents);
|
||||
tutorialsFiles.set(TUTORIALS_HOMEPAGE_DIRECTORY, homepageFiles);
|
||||
} else if (
|
||||
tutorialDirectories.some((directory) => directory.includes(STEPS_DIRECTORY)) &&
|
||||
tutorialDirectories.some((directory) => directory.includes(INTRO_DIRECTORY))
|
||||
) {
|
||||
const tutorialStepsFiles = await getIntroAndStepsTutorialFiles(
|
||||
tutorialName,
|
||||
tutorialDirectories,
|
||||
tutorialFiles,
|
||||
commonFilesContents,
|
||||
);
|
||||
|
||||
tutorialStepsFiles.forEach((stepFiles, stepName) => {
|
||||
tutorialsFiles.set(stepName, stepFiles);
|
||||
});
|
||||
} else {
|
||||
throw `Could not find "${INTRO_DIRECTORY}" and/or "${STEPS_DIRECTORY}" directories.`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`\n❌ Failed to parse tutorial at ${TUTORIALS_PROJECT_PATH}/${tutorialName}`);
|
||||
console.error(`\t${error}\n`);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return tutorialsFiles;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {join} from 'path';
|
||||
import {glob} from 'glob';
|
||||
import {input} from '@inquirer/prompts';
|
||||
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
import {parseConfig} from './tutorials-config';
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from './utils/node-constants';
|
||||
import {validatePathAnswer} from './utils/cli';
|
||||
|
||||
createTemplate();
|
||||
|
||||
async function createTemplate() {
|
||||
const existingTemplates = await glob('**/', {
|
||||
...GLOB_OPTIONS,
|
||||
absolute: false,
|
||||
cwd: TUTORIALS_PLAYGROUND_NODE_PATH,
|
||||
maxDepth: 1,
|
||||
});
|
||||
|
||||
const path = await askTemplatePath(existingTemplates);
|
||||
const title = await input({message: 'What is the title for the template?'});
|
||||
|
||||
const templatePath = `${TUTORIALS_PLAYGROUND_NODE_PATH}/${path}`;
|
||||
|
||||
const config = await parseConfig({
|
||||
type: TutorialType.EDITOR_ONLY,
|
||||
title,
|
||||
openFiles: ['src/main.ts'],
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createDirectoryAndWriteFile(join(templatePath, CONFIG_FILE), JSON.stringify(config, null, 2)),
|
||||
createDirectoryAndWriteFile(join(templatePath, 'src', 'main.ts'), ''),
|
||||
]);
|
||||
|
||||
console.info(`\n✅ Template created at ${templatePath}`);
|
||||
}
|
||||
|
||||
function askTemplatePath(existingTemplates: string[]) {
|
||||
const placeholder = 'path-to-template';
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the template?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingTemplates, placeholder),
|
||||
});
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {join} from 'path';
|
||||
import {select, input, confirm} from '@inquirer/prompts';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {
|
||||
CONFIG_FILE,
|
||||
CONTENT_PLACEHOLDER,
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIAL_CONTENT_FILENAME,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import type {TutorialConfig} from './tutorials-types';
|
||||
import {parseConfig} from './tutorials-config';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
TutorialType,
|
||||
} from './utils/web-constants';
|
||||
import {validatePathAnswer} from './utils/cli';
|
||||
|
||||
type TutorialFields = TutorialConfig & {
|
||||
path: string;
|
||||
};
|
||||
|
||||
type TutorialToCreate = {
|
||||
intro: TutorialFields;
|
||||
steps: Array<TutorialFields & {step: number}>;
|
||||
};
|
||||
|
||||
createTutorial();
|
||||
|
||||
async function createTutorial() {
|
||||
const existingTutorials = await glob('**/', {
|
||||
ignore: [
|
||||
...(GLOB_OPTIONS.ignore as string[]),
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
],
|
||||
cwd: TUTORIALS_PROJECT_PATH,
|
||||
maxDepth: 1,
|
||||
});
|
||||
|
||||
const introPath = await askTutorialPath(existingTutorials);
|
||||
const introType = await askTutorialType('What is the type of the tutorial intro?');
|
||||
const introTitle = await input({message: 'What is the title for the tutorial intro?'});
|
||||
const introNextTutorial = await askAboutNextTutorial(existingTutorials);
|
||||
|
||||
const tutorialToCreate: TutorialToCreate = {
|
||||
intro: {
|
||||
type: introType as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: introTitle,
|
||||
path: introPath,
|
||||
openFiles: [''],
|
||||
nextTutorial: introNextTutorial,
|
||||
},
|
||||
steps: [],
|
||||
};
|
||||
|
||||
let addMoreSteps = true;
|
||||
let step = 1;
|
||||
|
||||
while (addMoreSteps) {
|
||||
console.info(`\nStep ${step}`);
|
||||
const path = await askTutorialStepPath(tutorialToCreate.steps);
|
||||
const type = await askTutorialType('What is the type of the step?');
|
||||
const title = await input({message: 'What is the title of the step?'});
|
||||
|
||||
tutorialToCreate.steps.push({
|
||||
step,
|
||||
type: type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
path,
|
||||
title,
|
||||
openFiles: [''],
|
||||
});
|
||||
|
||||
step++;
|
||||
|
||||
console.info();
|
||||
addMoreSteps = await confirm({message: 'Would you like to add another step?', default: true});
|
||||
}
|
||||
|
||||
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${tutorialToCreate.intro.path}`;
|
||||
|
||||
await Promise.all([
|
||||
createIntroFiles(tutorialToCreate.intro),
|
||||
...createStepsFiles(tutorialPath, tutorialToCreate.steps),
|
||||
]);
|
||||
|
||||
console.info(`\n✅ Tutorial created at ${tutorialPath}`);
|
||||
}
|
||||
|
||||
function askTutorialType(message: string = 'What is the type of the tutorial?') {
|
||||
return select<TutorialConfig['type']>({
|
||||
message: message,
|
||||
choices: [
|
||||
{
|
||||
name: 'CLI only: a tutorial for the Angular CLI in-browser unsing the embedded terminal',
|
||||
value: TutorialType.CLI,
|
||||
},
|
||||
{name: 'Local app: a tutorial for building an app locally', value: TutorialType.LOCAL},
|
||||
{
|
||||
name: 'Embedded editor: a tutorial for building an app in-browser using the embedded editor',
|
||||
value: TutorialType.EDITOR,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function askAboutNextTutorial(
|
||||
existingTutorials: string[],
|
||||
message: string = 'What should be the next tutorial?',
|
||||
) {
|
||||
return select({
|
||||
message,
|
||||
choices: [
|
||||
...existingTutorials.map((tutorial) => ({name: tutorial, value: tutorial})),
|
||||
{name: 'None', value: undefined},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function askTutorialPath(existingTutorials: string[]) {
|
||||
const placeholder = 'path-to-tutorial';
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the tutorial?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingTutorials, placeholder),
|
||||
});
|
||||
}
|
||||
|
||||
function askTutorialStepPath(createdSteps: TutorialToCreate['steps']) {
|
||||
const placeholder = 'path-to-step';
|
||||
|
||||
const existingSteps = createdSteps.map((step) => step.path);
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the step?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingSteps, placeholder),
|
||||
});
|
||||
}
|
||||
|
||||
async function createIntroFiles(intro: TutorialToCreate['intro']): Promise<Promise<void>[]> {
|
||||
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${intro.path}`;
|
||||
|
||||
const config = await parseConfig({
|
||||
type: intro.type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: intro.title,
|
||||
openFiles: intro.openFiles,
|
||||
nextTutorial: intro.nextTutorial,
|
||||
});
|
||||
|
||||
return [
|
||||
createDirectoryAndWriteFile(
|
||||
join(tutorialPath, INTRO_DIRECTORY, CONFIG_FILE),
|
||||
JSON.stringify(config, null, 2),
|
||||
),
|
||||
createDirectoryAndWriteFile(
|
||||
join(tutorialPath, INTRO_DIRECTORY, TUTORIAL_CONTENT_FILENAME),
|
||||
CONTENT_PLACEHOLDER,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function createStepsFiles(
|
||||
tutorialPath: string,
|
||||
steps: TutorialToCreate['steps'],
|
||||
): Promise<Promise<void>[]>[] {
|
||||
const tutorialStepsPath = join(tutorialPath, STEPS_DIRECTORY);
|
||||
|
||||
return steps
|
||||
.map(async (step) => {
|
||||
const stepPath = join(tutorialStepsPath, `${step.step}-${step.path}`);
|
||||
|
||||
const config = await parseConfig({
|
||||
type: step.type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: step.title,
|
||||
openFiles: step.openFiles,
|
||||
});
|
||||
|
||||
return [
|
||||
createDirectoryAndWriteFile(`${stepPath}/${CONFIG_FILE}`, JSON.stringify(config, null, 2)),
|
||||
createDirectoryAndWriteFile(
|
||||
`${stepPath}/${TUTORIAL_CONTENT_FILENAME}`,
|
||||
CONTENT_PLACEHOLDER,
|
||||
),
|
||||
];
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {copyFile, mkdir} from 'fs/promises';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {filesAndContentsToRecord} from '../utils/filesystem';
|
||||
import {TUTORIALS_COMMON_DIRECTORY} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
import type {FileAndContent, TutorialFiles} from '../tutorials-types';
|
||||
import {TUTORIALS_COMMON_ASSETS_DEST, TUTORIALS_COMMON_ASSETS_SRC} from '../utils/node-constants';
|
||||
|
||||
const BEFORE_COMMON_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_COMMON_DIRECTORY}\/`);
|
||||
|
||||
export async function getTutorialCommonFiles(
|
||||
files: string[],
|
||||
filesContents: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const filesPath = files.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
|
||||
|
||||
const filesContentsWithoutPath = filesContents.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTree = getFileSystemTree(
|
||||
filesPath,
|
||||
filesAndContentsToRecord(filesContentsWithoutPath),
|
||||
);
|
||||
|
||||
return {sourceCode: fileSystemTree};
|
||||
}
|
||||
|
||||
export function getCleanFilePath(path: string) {
|
||||
return path.replace(BEFORE_COMMON_PATH_REGEX, '');
|
||||
}
|
||||
|
||||
export async function copyCommonAssetsToAppAssets() {
|
||||
const [commonAssets] = await Promise.all([
|
||||
// List of files for the assets from the tutorial common directory/app.
|
||||
glob(['**/*.png', '**/*.jpg'], {
|
||||
cwd: TUTORIALS_COMMON_ASSETS_SRC,
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
}),
|
||||
|
||||
// Ensure the common directory for tutorial assets exists.
|
||||
mkdir(TUTORIALS_COMMON_ASSETS_DEST, {recursive: true}),
|
||||
]);
|
||||
|
||||
await Promise.all(
|
||||
commonAssets.map((asset) =>
|
||||
copyFile(asset, asset.replace(TUTORIALS_COMMON_ASSETS_SRC, TUTORIALS_COMMON_ASSETS_DEST)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
globWithCwdPath,
|
||||
} from '../utils/filesystem';
|
||||
import {
|
||||
GLOB_OPTIONS,
|
||||
TUTORIALS_COMMON_NODE_PATH,
|
||||
TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
} from '../utils/node-constants';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
} from '../tutorials-types';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
import {TUTORIALS_HOMEPAGE_DIRECTORY, TutorialType} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
const BEFORE_HOMEPAGE_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_HOMEPAGE_DIRECTORY}\/`);
|
||||
|
||||
export async function getHomepagePlaygroundFiles(
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const homepagePlaygroundFiles = await globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
nodir: true,
|
||||
});
|
||||
|
||||
const config = await getTutorialConfig(homepagePlaygroundFiles);
|
||||
|
||||
if (config.type !== TutorialType.EDITOR_ONLY)
|
||||
throw new Error(`Homepage must be of type "${TutorialType.EDITOR_ONLY}"`);
|
||||
|
||||
const filesContents = await getFilesContents(homepagePlaygroundFiles);
|
||||
|
||||
const tutorialFiles = getTutorialFiles(filesContents, commonFiles);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(
|
||||
TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
config.openFiles,
|
||||
Object.keys(tutorialFiles),
|
||||
);
|
||||
} else {
|
||||
config.openFiles = Object.keys(tutorialFiles);
|
||||
}
|
||||
|
||||
const hasPackageJson = homepagePlaygroundFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(homepagePlaygroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
|
||||
}
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
homepagePlaygroundFiles,
|
||||
commonFiles.map(({path}) => path),
|
||||
getCleanFilePath,
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCode: getSourceCode(homepagePlaygroundFiles, filesContents),
|
||||
metadata: {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
dependencies,
|
||||
tutorialFiles,
|
||||
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSourceCode(tutorialFiles: string[], filesContents: FileAndContent[]) {
|
||||
// TODO: reuse this as it's duplicated in getTutorialFiles
|
||||
const fileSystemTreeContents = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove steps paths from file contents
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTreeFiles = tutorialFiles
|
||||
.filter((filename) => shouldUseFileInWebContainer(filename))
|
||||
.map(getCleanFilePath);
|
||||
|
||||
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
|
||||
}
|
||||
|
||||
function getTutorialFiles(
|
||||
filesContents: FileAndContent[],
|
||||
commonFiles: FileAndContent[],
|
||||
): FileAndContentRecord {
|
||||
const playgroundFilesForCodeEditor = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(getCleanFilePath(path)))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const commonFilesForCodeEditor = commonFiles
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
return {
|
||||
...filesAndContentsToRecord(commonFilesForCodeEditor),
|
||||
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
|
||||
};
|
||||
}
|
||||
|
||||
function getCleanFilePath(path: string) {
|
||||
if (path.includes(TUTORIALS_COMMON_NODE_PATH)) return getCleanCommonFilePath(path);
|
||||
|
||||
return path.replace(BEFORE_HOMEPAGE_PATH_REGEX, '');
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename, dirname, join} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
getZipBuffer,
|
||||
globWithCwdPath,
|
||||
recordToFilesAndContents,
|
||||
} from '../utils/filesystem';
|
||||
import {
|
||||
ANSWER_DIRECTORY,
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
} from '../utils/node-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
import {TutorialNavigationItemWithStep} from '../generate-tutorials-routes';
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import type {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
TutorialConfig,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
TutorialStep,
|
||||
} from '../tutorials-types';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_DIST_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
TutorialType,
|
||||
} from '../utils/web-constants';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
|
||||
export const TUTORIALS_DIRECTORY_REGEX = /^[a-z-0-9]*$/;
|
||||
export const STEPS_DIRECTORY_REGEX = /^([0-9]|[0-9][0-9])-[A-Za-z-0-9]*$/;
|
||||
const BEFORE_STEP_PATH_REGEX = new RegExp(`.*\/${STEPS_DIRECTORY}\/[^\/]+\/`);
|
||||
const BEFORE_INTRO_PATH_REGEX = new RegExp(`.*\/${INTRO_DIRECTORY}\/`);
|
||||
|
||||
export async function getIntroAndStepsTutorialFiles(
|
||||
tutorialName: string,
|
||||
tutorialDirectories: string[],
|
||||
tutorialFiles: string[],
|
||||
commonTutorialFiles: FileAndContent[],
|
||||
): Promise<Map<string, TutorialFiles>> {
|
||||
const tutorialSteps = getTutorialSteps(tutorialName, tutorialDirectories);
|
||||
|
||||
const introAndStepsFiles = new Map<TutorialStep['url'], TutorialFiles>();
|
||||
|
||||
const tutorialCommonFiles = tutorialFiles.filter((file) =>
|
||||
isCommonTutorialFile(tutorialName, file),
|
||||
);
|
||||
|
||||
const introFiles = await getTutorialIntroFiles(
|
||||
tutorialName,
|
||||
[
|
||||
...tutorialCommonFiles,
|
||||
...tutorialFiles.filter((file) => file.includes(`/${INTRO_DIRECTORY}/`)),
|
||||
],
|
||||
tutorialSteps,
|
||||
commonTutorialFiles,
|
||||
);
|
||||
|
||||
introAndStepsFiles.set(tutorialName, introFiles);
|
||||
|
||||
await Promise.all(
|
||||
tutorialSteps.map(async (step) => {
|
||||
const stepFiles = tutorialFiles.filter((file) => file.includes(step.path));
|
||||
|
||||
let nextTutorial;
|
||||
if (introFiles.route && 'tutorialData' in introFiles.route) {
|
||||
nextTutorial = introFiles.route.tutorialData.nextTutorial;
|
||||
}
|
||||
|
||||
const stepSourceCodeAndMetadata = await getStepFiles(
|
||||
[...tutorialCommonFiles, ...stepFiles],
|
||||
{
|
||||
...step,
|
||||
nextTutorial,
|
||||
},
|
||||
commonTutorialFiles,
|
||||
);
|
||||
|
||||
introAndStepsFiles.set(step.url, stepSourceCodeAndMetadata);
|
||||
}),
|
||||
);
|
||||
|
||||
return introAndStepsFiles;
|
||||
}
|
||||
|
||||
async function getTutorialIntroFiles(
|
||||
tutorialName: string,
|
||||
introTutorialFiles: string[],
|
||||
tutorialSteps: TutorialStep[],
|
||||
commonFilesContents: FileAndContent[],
|
||||
) {
|
||||
return getStepFiles(
|
||||
introTutorialFiles,
|
||||
{
|
||||
step: 0,
|
||||
name: tutorialName,
|
||||
path: dirname(introTutorialFiles[0]),
|
||||
nextStep: tutorialSteps.find((step) => step.step === 0 || step.step === 1)?.url, // assume the first tutorialSteps array item is the first step
|
||||
url: tutorialName,
|
||||
},
|
||||
commonFilesContents,
|
||||
);
|
||||
}
|
||||
|
||||
async function getStepFiles(
|
||||
stepFiles: string[],
|
||||
step: TutorialStep,
|
||||
commonFilesContents: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const config = await getTutorialConfig(stepFiles);
|
||||
|
||||
// if src is present in the config, add files to the step files
|
||||
if (config.src) {
|
||||
const srcFiles = await getExternalTutorialFiles(step, config.src);
|
||||
|
||||
stepFiles.push(...srcFiles);
|
||||
}
|
||||
|
||||
const filesContents = await getFilesContents(stepFiles);
|
||||
|
||||
const route: Omit<TutorialNavigationItemWithStep, 'path'> = {
|
||||
label: config.title,
|
||||
|
||||
contentPath: getTutorialContentPath(stepFiles),
|
||||
|
||||
tutorialData: {
|
||||
type: config.type,
|
||||
title: config.title,
|
||||
|
||||
step: step.step,
|
||||
nextStep: step.nextStep,
|
||||
previousStep: step.previousStep,
|
||||
nextTutorial: config.nextTutorial ?? step.nextTutorial,
|
||||
},
|
||||
};
|
||||
|
||||
if (config.type === TutorialType.LOCAL) {
|
||||
const hasFilesToDownload =
|
||||
filesContents.filter(({path}) => shouldUseFileInWebContainer(path)).length > 0;
|
||||
|
||||
if (!hasFilesToDownload) {
|
||||
return {route};
|
||||
} else {
|
||||
const filesToDownload = getFilesToDownload(commonFilesContents, filesContents);
|
||||
|
||||
route.tutorialData.sourceCodeZipPath = join(
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
step.url + '.zip',
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCodeZip: await getZipBuffer(filesToDownload),
|
||||
route,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const tutorialFiles = getTutorialFiles(filesContents);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(step.path, config.openFiles, Object.keys(tutorialFiles));
|
||||
} else if (config.type === TutorialType.EDITOR) {
|
||||
config.openFiles = Object.keys(tutorialFiles);
|
||||
}
|
||||
|
||||
const hasPackageJson = stepFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(stepFiles, packageJson, getPackageJsonFromFiles(commonFilesContents));
|
||||
}
|
||||
|
||||
const fileSystemTreeContents = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({path: getCleanFilePath(path), content}));
|
||||
|
||||
const fileSystemTreeFiles = stepFiles.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
stepFiles,
|
||||
commonFilesContents.map(({path}) => path),
|
||||
getCleanFilePath,
|
||||
);
|
||||
|
||||
const metadata: TutorialMetadata = {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
tutorialFiles,
|
||||
answerFiles: await getAnswerFiles(step, filesContents, config.answerSrc),
|
||||
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
dependencies,
|
||||
};
|
||||
|
||||
return {
|
||||
metadata,
|
||||
sourceCode: getFileSystemTree(
|
||||
fileSystemTreeFiles,
|
||||
filesAndContentsToRecord(fileSystemTreeContents),
|
||||
),
|
||||
route,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTutorialSteps(
|
||||
tutorialName: string,
|
||||
tutorialDirectories: string[],
|
||||
): TutorialStep[] {
|
||||
const stepsDirectories = tutorialDirectories.filter((directory) => {
|
||||
const isStepDirectory = directory.includes(`${tutorialName}/${STEPS_DIRECTORY}/`);
|
||||
if (!isStepDirectory) return false;
|
||||
|
||||
const stepPathParts = directory.split('/');
|
||||
|
||||
// steps directory is always right after STEPS_DIRECTORY in the path, e.g. `learn-angular/steps/1-getting-started`
|
||||
const stepDirectoryIndex = stepPathParts.indexOf(STEPS_DIRECTORY) + 1;
|
||||
const step = stepPathParts[stepDirectoryIndex];
|
||||
|
||||
return step === stepPathParts.at(-1);
|
||||
});
|
||||
|
||||
const steps: TutorialStep[] = [];
|
||||
|
||||
for (const stepDirectoryPath of stepsDirectories) {
|
||||
const stepDirectory = basename(stepDirectoryPath);
|
||||
|
||||
// force the tutorials steps to follow the pattern steps/{step}-{name}
|
||||
if (!STEPS_DIRECTORY_REGEX.test(stepDirectory))
|
||||
throw `Invalid step directory '${stepDirectory}'. The step directory must start with a number > 0, followed by a hyphen and a name. For example: '1-intro'`;
|
||||
|
||||
const [step, ...nameParts] = stepDirectory.replace(`${STEPS_DIRECTORY}/`, '').split('-');
|
||||
const name = nameParts.join('-');
|
||||
|
||||
steps.push({
|
||||
step: Number(step),
|
||||
name,
|
||||
path: stepDirectoryPath,
|
||||
url: join(tutorialName, name),
|
||||
});
|
||||
}
|
||||
|
||||
return steps.map((step) => {
|
||||
const previousStep = steps.find(({step: previousStep}) => previousStep === step.step - 1);
|
||||
const nextStep = steps.find(({step: nextStep}) => nextStep === step.step + 1);
|
||||
|
||||
return {
|
||||
...step,
|
||||
nextStep: nextStep?.url,
|
||||
previousStep: previousStep?.url ?? tutorialName,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter files that won't be used in the WebContainer.
|
||||
*/
|
||||
function getTutorialFiles(
|
||||
tutorialFilesContents: FileAndContent[],
|
||||
): Record<FileAndContent['path'], FileAndContent['content']> {
|
||||
const tutorialFiles = tutorialFilesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
return filesAndContentsToRecord(tutorialFiles);
|
||||
}
|
||||
|
||||
async function getAnswerFiles(
|
||||
step: TutorialStep,
|
||||
filesContents: FileAndContent[],
|
||||
answerSrc: TutorialConfig['answerSrc'],
|
||||
): Promise<FileAndContentRecord> {
|
||||
const removeAnswerPathFromFiles = (filesAndContents: FileAndContent[]) =>
|
||||
filesAndContents.map(({path, content}) => ({
|
||||
path: path.replace(`${ANSWER_DIRECTORY}/`, ''),
|
||||
content,
|
||||
}));
|
||||
|
||||
if (answerSrc) {
|
||||
const answerSrcFiles = await getExternalTutorialFiles(step, answerSrc);
|
||||
|
||||
const answerFiles = await getFilesContents(answerSrcFiles);
|
||||
|
||||
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
|
||||
}
|
||||
|
||||
const answerFiles = filesContents.filter(({path}) => path.includes(`${ANSWER_DIRECTORY}/`));
|
||||
|
||||
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files external to the current tutorial directory.
|
||||
*/
|
||||
async function getExternalTutorialFiles(
|
||||
step: TutorialStep,
|
||||
relativePath: string,
|
||||
): Promise<string[]> {
|
||||
const externalFilesPath = join(step.path, relativePath);
|
||||
|
||||
const files = await globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: externalFilesPath,
|
||||
nodir: true,
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function getTutorialContentPath(stepFiles: string[]) {
|
||||
const tutorialContentPath = stepFiles.find((filename) => filename.endsWith('README.md'));
|
||||
|
||||
if (!tutorialContentPath) {
|
||||
const tutorialPath = dirname(stepFiles[0]);
|
||||
|
||||
throw `Tutorial at ${tutorialPath} is missing a README.md for content`;
|
||||
}
|
||||
|
||||
const contentPathWithDistPath = tutorialContentPath.replace(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_DIST_PATH,
|
||||
);
|
||||
|
||||
return contentPathWithDistPath.replace(/\.[^.]+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the file path by making it relative to the project root.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* getCleanFilePath('path-to-tutorial/intro/src/app/app.component.ts') => 'src/app/app.component.ts'
|
||||
* getCleanFilePath('path-to-tutorial/steps/1-step/src/app/app.component.ts') => 'src/app/app.component.ts'
|
||||
* ```
|
||||
*/
|
||||
function getCleanFilePath(path: string) {
|
||||
if (path.includes(TUTORIALS_COMMON_DIRECTORY)) return getCleanCommonFilePath(path);
|
||||
if (path.includes(STEPS_DIRECTORY)) return path.replace(BEFORE_STEP_PATH_REGEX, '');
|
||||
|
||||
return path.replace(BEFORE_INTRO_PATH_REGEX, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge common project files with the step files to get the final files to download.
|
||||
*/
|
||||
function getFilesToDownload(
|
||||
commonFilesContents: FileAndContent[],
|
||||
stepFilesWithoutAnswer: FileAndContent[],
|
||||
) {
|
||||
const commonFilesWithoutPath = commonFilesContents.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const stepFilesWithoutPath = stepFilesWithoutAnswer
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
// use object to easily override common files with step files
|
||||
const filesToDownload = {
|
||||
...filesAndContentsToRecord(commonFilesWithoutPath),
|
||||
...filesAndContentsToRecord(stepFilesWithoutPath),
|
||||
};
|
||||
|
||||
return recordToFilesAndContents(filesToDownload);
|
||||
}
|
||||
|
||||
function isCommonTutorialFile(tutorialPath: string, filePath: string) {
|
||||
return filePath.includes(join(tutorialPath, TUTORIALS_COMMON_DIRECTORY));
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {glob} from 'glob';
|
||||
import {basename, join, sep} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
globWithCwdPath,
|
||||
} from '../utils/filesystem';
|
||||
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from '../utils/node-constants';
|
||||
import {
|
||||
DEFAULT_PLAYGROUND_TEMPLATE,
|
||||
STARTER_PLAYGROUND_TEMPLATE,
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
TutorialType,
|
||||
} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
PlaygroundFiles,
|
||||
PlaygroundRouteData,
|
||||
PlaygroundTemplate,
|
||||
TutorialConfig,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
} from '../tutorials-types';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
// used to avoid recreating a regex in loops
|
||||
const beforePlaygroundPathRegexCache = new Map<string, RegExp>();
|
||||
|
||||
export async function getTutorialPlaygroundFiles(
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<Map<string, PlaygroundFiles>> {
|
||||
const cwd = TUTORIALS_PLAYGROUND_NODE_PATH;
|
||||
|
||||
const [playgroundProjects, playgroundFiles] = await Promise.all([
|
||||
glob('*/', {
|
||||
ignore: [TUTORIALS_COMMON_DIRECTORY],
|
||||
cwd,
|
||||
}),
|
||||
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
nodir: true,
|
||||
cwd,
|
||||
}),
|
||||
]);
|
||||
|
||||
const playgroundCommonFiles = playgroundFiles.filter(isPlaygroundCommonFile);
|
||||
|
||||
const playgroundFilesMap = new Map<string, PlaygroundFiles>();
|
||||
|
||||
const templates: PlaygroundTemplate[] = [];
|
||||
|
||||
await Promise.all(
|
||||
playgroundProjects.map(async (project) => {
|
||||
const projectFiles = playgroundFiles.filter((file) =>
|
||||
file.startsWith(`${cwd}${sep}${project}${sep}`),
|
||||
);
|
||||
|
||||
if (!projectFiles.length) {
|
||||
throw new Error(
|
||||
`Project at ${join(TUTORIALS_PLAYGROUND_NODE_PATH, project)} has no files.`,
|
||||
);
|
||||
}
|
||||
|
||||
const projectFilesAndPlaygroundCommonFiles = [...playgroundCommonFiles, ...projectFiles];
|
||||
|
||||
const projectPath = join(TUTORIALS_PLAYGROUND_DIRECTORY, project);
|
||||
|
||||
const {sourceCode, metadata, route} = await getPlaygroundFiles(
|
||||
projectPath,
|
||||
projectFilesAndPlaygroundCommonFiles,
|
||||
commonFiles,
|
||||
);
|
||||
|
||||
// store only sourceCode and metadata per playground template
|
||||
playgroundFilesMap.set(projectPath, {sourceCode, metadata});
|
||||
|
||||
// compose playground templates from each project route object
|
||||
if (route) templates.push(...route!.templates);
|
||||
}),
|
||||
);
|
||||
|
||||
const defaultTemplate = templates.find((template) =>
|
||||
template.path.endsWith(DEFAULT_PLAYGROUND_TEMPLATE),
|
||||
);
|
||||
if (!defaultTemplate) {
|
||||
throw new Error(`Invalid default playground template path '${DEFAULT_PLAYGROUND_TEMPLATE}'`);
|
||||
}
|
||||
|
||||
const starterTemplate = templates.find((template) =>
|
||||
template.path.endsWith(STARTER_PLAYGROUND_TEMPLATE),
|
||||
);
|
||||
if (!starterTemplate) {
|
||||
throw new Error(`Invalid starter playground template path '${STARTER_PLAYGROUND_TEMPLATE}'`);
|
||||
}
|
||||
|
||||
playgroundFilesMap.set(TUTORIALS_PLAYGROUND_DIRECTORY, {
|
||||
route: {
|
||||
templates: templates.sort((a, b) => {
|
||||
const aPath = a.path.toLowerCase();
|
||||
const bPath = b.path.toLowerCase();
|
||||
|
||||
if (aPath < bPath) return -1;
|
||||
if (aPath > bPath) return 1;
|
||||
return 0;
|
||||
}),
|
||||
defaultTemplate,
|
||||
starterTemplate,
|
||||
},
|
||||
});
|
||||
|
||||
return playgroundFilesMap;
|
||||
}
|
||||
|
||||
async function getPlaygroundFiles(
|
||||
project: string,
|
||||
playgroundFiles: string[],
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<PlaygroundFiles> {
|
||||
const config = await getTutorialConfig(playgroundFiles);
|
||||
|
||||
if (config.type !== TutorialType.EDITOR_ONLY)
|
||||
throw new Error(`Playground must be of type "${TutorialType.EDITOR_ONLY}"`);
|
||||
|
||||
const filesContents = await getFilesContents(playgroundFiles);
|
||||
|
||||
const projectFiles = getProjectFiles(project, config.openFiles, filesContents, commonFiles);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(
|
||||
TUTORIALS_PLAYGROUND_NODE_PATH,
|
||||
config.openFiles,
|
||||
Object.keys(projectFiles),
|
||||
);
|
||||
}
|
||||
|
||||
const hasPackageJson = playgroundFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(playgroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
|
||||
}
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
playgroundFiles,
|
||||
commonFiles.map(({path}) => path),
|
||||
(path: string) => getCleanFilePath(project, path),
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCode: getSourceCode(project, playgroundFiles, filesContents),
|
||||
metadata: {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
dependencies,
|
||||
tutorialFiles: projectFiles,
|
||||
openFiles: config.openFiles ?? Object.keys(projectFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(projectFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
},
|
||||
route: {
|
||||
templates: [{path: project, label: config.title}],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getProjectFiles(
|
||||
project: string,
|
||||
openFiles: TutorialConfig['openFiles'],
|
||||
playgroundFilesContents: FileAndContent[],
|
||||
commonFiles: FileAndContent[],
|
||||
): FileAndContentRecord {
|
||||
const playgroundFilesForCodeEditor = playgroundFilesContents
|
||||
// remove files that should not be shown in the code editor
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove playground path from file paths
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(project, path),
|
||||
content,
|
||||
}));
|
||||
|
||||
if (!openFiles) return filesAndContentsToRecord(playgroundFilesForCodeEditor);
|
||||
|
||||
const commonFilesForCodeEditor = commonFiles
|
||||
// remove files that should not be shown in the code editor
|
||||
.filter(({path}) => shouldUseFileInWebContainer(getCleanCommonFilePath(path)))
|
||||
// remove common path from file paths
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const commonAndPlaygroundFiles = {
|
||||
...filesAndContentsToRecord(commonFilesForCodeEditor),
|
||||
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
|
||||
};
|
||||
|
||||
const openFilesConfigWithContents: FileAndContentRecord = {};
|
||||
|
||||
for (const openFile of openFiles) {
|
||||
if (commonAndPlaygroundFiles[openFile]) {
|
||||
openFilesConfigWithContents[openFile] = commonAndPlaygroundFiles[openFile];
|
||||
} else {
|
||||
throw `At ${TUTORIALS_PLAYGROUND_NODE_PATH}/${CONFIG_FILE}.\n\tCould not find file "${openFile}" in playground or common files`;
|
||||
}
|
||||
}
|
||||
|
||||
return openFilesConfigWithContents;
|
||||
}
|
||||
|
||||
export function getSourceCode(
|
||||
project: string,
|
||||
tutorialFiles: string[],
|
||||
filesContents: FileAndContent[],
|
||||
) {
|
||||
const fileSystemTreeContents = filesContents
|
||||
// filter out config files
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove steps paths from file contents
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(project, path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTreeFiles = tutorialFiles
|
||||
.filter(shouldUseFileInWebContainer)
|
||||
.map((filename) => getCleanFilePath(project, filename));
|
||||
|
||||
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a regex that matches the string before the project name in a path.
|
||||
*
|
||||
* This is used to remove the project name from the path, keeping only the path
|
||||
* relative to the project root.
|
||||
*/
|
||||
function getBeforePlaygroundProjectRegex(project: string): RegExp {
|
||||
const projectRegex = beforePlaygroundPathRegexCache.get(project);
|
||||
if (projectRegex) return projectRegex;
|
||||
|
||||
const newRegex = new RegExp(`.*\/${project}\/`);
|
||||
beforePlaygroundPathRegexCache.set(project, newRegex);
|
||||
|
||||
return newRegex;
|
||||
}
|
||||
|
||||
function getCleanFilePath(project: string, path: string) {
|
||||
if (isPlaygroundCommonFile(path)) {
|
||||
return getCleanCommonFilePath(path);
|
||||
}
|
||||
|
||||
return path.replace(getBeforePlaygroundProjectRegex(project), '');
|
||||
}
|
||||
|
||||
export function isPlaygroundRouteData(
|
||||
routeData: PlaygroundRouteData | TutorialFiles['route'],
|
||||
): routeData is PlaygroundRouteData {
|
||||
return (routeData as PlaygroundRouteData).templates !== undefined;
|
||||
}
|
||||
|
||||
function isPlaygroundCommonFile(path: string) {
|
||||
return path.includes(TUTORIALS_COMMON_DIRECTORY);
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename, join} from 'path';
|
||||
import {readdir} from 'fs/promises';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {getFileContents} from './utils/filesystem';
|
||||
import {
|
||||
CONFIG_FILE,
|
||||
CONFIG_KEYS,
|
||||
REQUIRED_CONFIGS,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import type {TutorialConfig} from './tutorials-types';
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
|
||||
export async function getTutorialConfig(tutorialFiles: string[]): Promise<TutorialConfig> {
|
||||
const configFilePath = tutorialFiles.find((file) => basename(file) === CONFIG_FILE);
|
||||
|
||||
if (!configFilePath) throw `Can't find ${CONFIG_FILE}`;
|
||||
|
||||
const configFileContents = await getFileContents(configFilePath);
|
||||
|
||||
let config: TutorialConfig;
|
||||
|
||||
try {
|
||||
config = JSON.parse(String(configFileContents));
|
||||
config = await parseConfig(config);
|
||||
|
||||
return config;
|
||||
} catch (err) {
|
||||
throw `${configFilePath.replace(`${TUTORIALS_PROJECT_PATH}/`, '')}: ${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function parseConfig(config: TutorialConfig): Promise<TutorialConfig> {
|
||||
const tutorialConfigKeys = Object.keys(config);
|
||||
|
||||
REQUIRED_CONFIGS.forEach((requiredConfig) => {
|
||||
if (!tutorialConfigKeys.includes(requiredConfig)) {
|
||||
throw `missing required config '${requiredConfig}'`;
|
||||
}
|
||||
});
|
||||
|
||||
for (const key of tutorialConfigKeys) {
|
||||
// @ts-expect-error
|
||||
if (!CONFIG_KEYS.includes(key)) throw `invalid config '${key}'`;
|
||||
}
|
||||
|
||||
if (config.nextTutorial) {
|
||||
if (typeof config.nextTutorial !== 'string') {
|
||||
throw '"nextTutorial" must be a string';
|
||||
}
|
||||
|
||||
const path = join(TUTORIALS_PROJECT_PATH, config.nextTutorial);
|
||||
|
||||
// check if nextTutorial directory exists
|
||||
try {
|
||||
await readdir(path);
|
||||
} catch (error) {
|
||||
throw `the nextTutorial "${config.nextTutorial}" doesn't exist at ${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.src) {
|
||||
if (typeof config.src !== 'string') {
|
||||
throw '"src" must be a string';
|
||||
}
|
||||
|
||||
// check if src directory exists
|
||||
const srcFiles = await glob(`**/${config.src}`, {cwd: TUTORIALS_PROJECT_PATH});
|
||||
|
||||
if (!srcFiles.length) throw `could not find "src" "${config.src}"`;
|
||||
}
|
||||
|
||||
if (config.answerSrc) {
|
||||
if (typeof config.answerSrc !== 'string') {
|
||||
throw '"answerSrc" must be a string';
|
||||
}
|
||||
|
||||
// check if answerSrc directory exists
|
||||
const answerSrcFiles = await glob(`**/${config.answerSrc}`, {cwd: TUTORIALS_PROJECT_PATH});
|
||||
|
||||
if (!answerSrcFiles.length) throw `could not find "answerSrc" "${config.answerSrc}"`;
|
||||
}
|
||||
|
||||
switch (config.type) {
|
||||
case TutorialType.EDITOR:
|
||||
assertOpenFiles(config.openFiles);
|
||||
|
||||
return config;
|
||||
|
||||
case TutorialType.CLI:
|
||||
return config;
|
||||
|
||||
case TutorialType.LOCAL:
|
||||
return config;
|
||||
|
||||
case TutorialType.EDITOR_ONLY:
|
||||
assertOpenFiles(config.openFiles);
|
||||
|
||||
return config;
|
||||
|
||||
default:
|
||||
throw 'invalid config';
|
||||
}
|
||||
}
|
||||
|
||||
function assertOpenFiles(
|
||||
openFiles: TutorialConfig['openFiles'],
|
||||
): asserts openFiles is string[] | undefined {
|
||||
if (openFiles === undefined) return;
|
||||
|
||||
const errorMessage = '"openFiles" must be an array of strings';
|
||||
|
||||
if (!Array.isArray(openFiles)) {
|
||||
throw errorMessage;
|
||||
}
|
||||
|
||||
for (const openFile of openFiles) {
|
||||
if (typeof openFile !== 'string') throw errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if openFiles exist in tutorial files.
|
||||
*/
|
||||
export function validateOpenFilesConfig(
|
||||
tutorialPath: string,
|
||||
openFiles: NonNullable<TutorialConfig['openFiles']>,
|
||||
tutorialFiles: string[],
|
||||
) {
|
||||
for (const openFile of openFiles) {
|
||||
if (!tutorialFiles.includes(openFile)) {
|
||||
throw `At: ${tutorialPath}/${CONFIG_FILE}\n\tCould not find "${openFile}" in the tutorial files`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import type {FileSystemTree} from '@webcontainer/api';
|
||||
import type {TutorialNavigationItemWithStep} from './generate-tutorials-routes';
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
|
||||
/**
|
||||
* Represents the contents of the tutorial files to be generated by the build script
|
||||
*/
|
||||
export type TutorialFiles = {
|
||||
sourceCode?: FileSystemTree;
|
||||
metadata?: TutorialMetadata;
|
||||
sourceCodeZip?: Buffer;
|
||||
route?: Omit<TutorialNavigationItemWithStep, 'path'>;
|
||||
};
|
||||
|
||||
export type PlaygroundFiles = {
|
||||
sourceCode?: FileSystemTree;
|
||||
metadata?: TutorialMetadata;
|
||||
sourceCodeZip?: undefined;
|
||||
route?: PlaygroundRouteData;
|
||||
};
|
||||
|
||||
/** Represents the contents of the tutorial config file */
|
||||
export type TutorialMetadata = {
|
||||
type: TutorialConfig['type'];
|
||||
|
||||
/** a record of all tutorials filenames and its contents */
|
||||
tutorialFiles: FileAndContentRecord;
|
||||
|
||||
/** a record of filenames and contents for the tutorial answer */
|
||||
answerFiles?: FileAndContentRecord;
|
||||
|
||||
/** files that are part of the project but are not visible in the code editor */
|
||||
hiddenFiles: string[];
|
||||
|
||||
/**
|
||||
* All files in the project, used to find the difference between new and old projects
|
||||
* when changing projects
|
||||
*/
|
||||
allFiles: string[];
|
||||
|
||||
openFiles: NonNullable<TutorialConfig['openFiles']>;
|
||||
|
||||
/** whether a package.json exists */
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type TutorialStep = {
|
||||
step: number;
|
||||
name: string;
|
||||
path: string;
|
||||
url: string;
|
||||
nextStep?: TutorialStep['url'];
|
||||
previousStep?: TutorialStep['url'];
|
||||
nextTutorial?: string;
|
||||
};
|
||||
|
||||
export type TutorialConfig =
|
||||
| EditorTutorialConfig
|
||||
| LocalTutorialConfig
|
||||
| CliTutorialConfig
|
||||
| EditorOnlyTutorialConfig;
|
||||
|
||||
export interface TutorialConfigBase {
|
||||
type: TutorialType;
|
||||
|
||||
/** The tutorial title */
|
||||
title: string;
|
||||
|
||||
/** The name of the tutorial folder that will be started after the current one ends. */
|
||||
nextTutorial?: string;
|
||||
|
||||
/** The path to the tutorial src folder when it's external to the tutorial */
|
||||
src?: string;
|
||||
|
||||
/** The path to the tutorial answer folder when it's external to the tutorial */
|
||||
answerSrc?: string;
|
||||
|
||||
/** An array of files to be open in the editor */
|
||||
openFiles?: string[];
|
||||
}
|
||||
|
||||
/** Represents a tutorial config with all the embedded editor components enabled */
|
||||
export interface EditorTutorialConfig extends TutorialConfigBase {
|
||||
type: TutorialType.EDITOR;
|
||||
}
|
||||
|
||||
/** Represents a tutorial config that won't use the embedded editor */
|
||||
export interface LocalTutorialConfig extends TutorialConfigBase {
|
||||
type: TutorialType.LOCAL;
|
||||
|
||||
// fields that must be undefined for local app tutorials
|
||||
openFiles?: undefined;
|
||||
src?: undefined;
|
||||
answerSrc?: undefined;
|
||||
}
|
||||
|
||||
/** Represents a tutorial config that supports only the interactive terminal for the Angular CLI */
|
||||
export type CliTutorialConfig = Omit<LocalTutorialConfig, 'type'> & {
|
||||
type: TutorialType.CLI;
|
||||
};
|
||||
|
||||
export type EditorOnlyTutorialConfig = Omit<EditorTutorialConfig, 'type'> & {
|
||||
type: TutorialType.EDITOR_ONLY;
|
||||
};
|
||||
|
||||
export type FileAndContent = {
|
||||
path: string;
|
||||
content: string | Buffer;
|
||||
};
|
||||
|
||||
export type FileAndContentRecord = Record<FileAndContent['path'], FileAndContent['content']>;
|
||||
|
||||
export type TutorialNavigationItem = {
|
||||
path: NonNullable<NavigationItem['path']>;
|
||||
label: NonNullable<NavigationItem['label']>;
|
||||
children?: TutorialNavigationItem[];
|
||||
parent?: TutorialNavigationItem;
|
||||
contentPath?: string;
|
||||
tutorialData: TutorialNavigationData;
|
||||
};
|
||||
|
||||
export type TutorialNavigationData = {
|
||||
type: TutorialConfig['type'];
|
||||
title: TutorialConfig['title'];
|
||||
nextStep?: string;
|
||||
previousStep?: string;
|
||||
nextTutorial?: string;
|
||||
sourceCodeZipPath?: string;
|
||||
};
|
||||
|
||||
export type PlaygroundRouteData = {
|
||||
templates: PlaygroundTemplate[];
|
||||
defaultTemplate?: PlaygroundTemplate;
|
||||
starterTemplate?: PlaygroundTemplate;
|
||||
};
|
||||
|
||||
export type PlaygroundTemplate = Required<Pick<NavigationItem, 'path' | 'label'>>;
|
||||
|
||||
// Note: only the fields being used are defined in this type
|
||||
export interface PackageJson {
|
||||
dependencies: Record<string, string>;
|
||||
devDependencies: Record<string, string>;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {glob} from 'glob';
|
||||
import {TUTORIALS_CONTENT_NODE_PATH} from './utils/node-constants';
|
||||
import {dirname} from 'path';
|
||||
import {execSync} from 'child_process';
|
||||
|
||||
main();
|
||||
|
||||
/**
|
||||
* This script updates the package-lock.json files for all tutorials
|
||||
* after manually changing the dependencies in the package.json files
|
||||
*/
|
||||
async function main() {
|
||||
const tutorialsPackageJsons = await glob('**/package.json', {
|
||||
ignore: ['**/node_modules'],
|
||||
absolute: true,
|
||||
cwd: TUTORIALS_CONTENT_NODE_PATH,
|
||||
});
|
||||
|
||||
for (const path of tutorialsPackageJsons) {
|
||||
const directory = dirname(path);
|
||||
|
||||
console.info(`\nUpdating lock file at ${directory}\n`);
|
||||
execSync('npm install --package-lock-only', {
|
||||
cwd: directory,
|
||||
stdio: [null, null, 'inherit'],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {TUTORIALS_DIRECTORY_REGEX} from '../tutorial-type/intro-and-steps';
|
||||
|
||||
export function validatePathAnswer(
|
||||
answer: string,
|
||||
existingPaths: string[],
|
||||
placeholder: string,
|
||||
): string | true {
|
||||
if (answer === placeholder) {
|
||||
return 'Please define the path';
|
||||
}
|
||||
|
||||
if (!TUTORIALS_DIRECTORY_REGEX.test(answer)) {
|
||||
return `The path must be in the format of "${placeholder}"`;
|
||||
}
|
||||
|
||||
if (existingPaths.includes(answer)) {
|
||||
return `'${answer}' already exists`;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename, dirname} from 'path';
|
||||
import {mkdir, readFile, writeFile} from 'fs/promises';
|
||||
import {GlobOptionsWithFileTypesFalse, glob} from 'glob';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
import type {FileAndContent, FileAndContentRecord, PackageJson} from '../tutorials-types';
|
||||
|
||||
const fileContentsCache = new Map<string, string | Buffer>();
|
||||
|
||||
export async function getFileContents(path: string): Promise<string | Buffer> {
|
||||
if (fileContentsCache.get(path)) {
|
||||
return fileContentsCache.get(path)!;
|
||||
}
|
||||
|
||||
const fileBuffer = await readFile(path);
|
||||
|
||||
if (isBufferImage(fileBuffer)) {
|
||||
fileContentsCache.set(path, fileBuffer);
|
||||
return fileBuffer;
|
||||
}
|
||||
|
||||
fileContentsCache.set(path, fileBuffer.toString());
|
||||
return fileBuffer.toString();
|
||||
}
|
||||
|
||||
export async function getFilesContents(files: string[]): Promise<FileAndContent[]> {
|
||||
const filesContents: FileAndContent[] = [];
|
||||
|
||||
await Promise.all(
|
||||
files.map((path) =>
|
||||
getFileContents(path).then((fileContents) => {
|
||||
filesContents.push({path, content: fileContents});
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return filesContents;
|
||||
}
|
||||
|
||||
export async function createDirectoryAndWriteFile(path: string, fileContents: string | Buffer) {
|
||||
await mkdir(dirname(path), {recursive: true}).catch(() => {
|
||||
// ignore error if directory already exists
|
||||
});
|
||||
|
||||
await writeFile(path, fileContents);
|
||||
}
|
||||
|
||||
export function filesAndContentsToRecord(filesAndContents: FileAndContent[]): FileAndContentRecord {
|
||||
return Object.fromEntries(filesAndContents.map(({path, content}) => [path, content]));
|
||||
}
|
||||
|
||||
export function recordToFilesAndContents(filesRecord: FileAndContentRecord): FileAndContent[] {
|
||||
return Object.entries(filesRecord).map(([path, content]) => ({path, content}));
|
||||
}
|
||||
|
||||
export async function getZipBuffer(files: FileAndContent[]): Promise<Buffer> {
|
||||
const zip = new JSZip();
|
||||
|
||||
for (const file of files) {
|
||||
zip.file(file.path, file.content, {binary: true});
|
||||
}
|
||||
|
||||
return await zip.generateAsync({type: 'nodebuffer'});
|
||||
}
|
||||
|
||||
export async function globWithCwdPath(
|
||||
pattern: string | string[],
|
||||
optionsWithCwd: Omit<GlobOptionsWithFileTypesFalse, 'cwd'> & {cwd: string},
|
||||
) {
|
||||
const files = await glob(pattern, optionsWithCwd);
|
||||
|
||||
return getFilesWithCwdPath(optionsWithCwd.cwd, files).sort();
|
||||
}
|
||||
|
||||
export function getFilesWithCwdPath(cwdPath: string, files: string[]) {
|
||||
return files.map((file) => file.substring(file.indexOf(cwdPath)));
|
||||
}
|
||||
|
||||
function isBufferImage(buffer: Buffer) {
|
||||
return isBufferPng(buffer) || isBufferJpg(buffer) || isBufferGif(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a PNG file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferPng(buffer: Buffer) {
|
||||
const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, pngSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a JPG file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferJpg(buffer: Buffer) {
|
||||
const jpgSignature = [0xff, 0xd8, 0xff];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, jpgSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a GIF file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferGif(buffer: Buffer) {
|
||||
const gifSignature = [0x47, 0x49, 0x46];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, gifSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the initial bytes of a buffer matches the expected bytes.
|
||||
*/
|
||||
function bufferMatchInitialBytes(buffer: Buffer, expectedInitialBytes: number[]) {
|
||||
for (const [index, byte] of expectedInitialBytes.entries()) {
|
||||
if (buffer[index] !== byte) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getPackageJsonFromFiles(filesAndContents: FileAndContent[]): PackageJson {
|
||||
const packageJson = filesAndContents.find(({path}) => basename(path) === 'package.json');
|
||||
|
||||
if (!packageJson)
|
||||
throw new Error(`Could not find ${dirname(filesAndContents[0].path)}/package.json`);
|
||||
|
||||
let parsedPackageJsonContent: PackageJson;
|
||||
|
||||
try {
|
||||
parsedPackageJsonContent = JSON.parse(packageJson.content as string);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not JSON.parse ${dirname(filesAndContents[0].path)}/package.json`);
|
||||
}
|
||||
|
||||
return parsedPackageJsonContent;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename, dirname} from 'path';
|
||||
import {PackageJson} from '../tutorials-types';
|
||||
import {shouldUseFileInWebContainer} from './webcontainers';
|
||||
|
||||
export function validatePackageJson(
|
||||
files: string[],
|
||||
projectPackageJson: PackageJson,
|
||||
commonPackageJson: PackageJson,
|
||||
): void {
|
||||
if (!files.some((file) => basename(file) === 'package-lock.json')) {
|
||||
throw new Error(
|
||||
`Tutorial at ${dirname(
|
||||
files[0],
|
||||
)} has a package.json but is missing a package-lock.json. A lock file is required for faster installs.`,
|
||||
);
|
||||
}
|
||||
|
||||
const commonDependencies = {
|
||||
...commonPackageJson.dependencies,
|
||||
...commonPackageJson.devDependencies,
|
||||
};
|
||||
|
||||
const projectDependencies = {
|
||||
...projectPackageJson.dependencies,
|
||||
...projectPackageJson.devDependencies,
|
||||
};
|
||||
|
||||
const projectDependenciesNames = Object.keys(projectDependencies);
|
||||
|
||||
for (const dependency of Object.keys(commonDependencies)) {
|
||||
if (!projectDependenciesNames.includes(dependency)) {
|
||||
throw new Error(
|
||||
`Tutorial at ${dirname(files[0])} is missing '${dependency}' as a dependency.`,
|
||||
);
|
||||
} else {
|
||||
const commonDependencyVersion = commonDependencies[dependency];
|
||||
const projectDependencyVersion = projectDependencies[dependency];
|
||||
|
||||
if (commonDependencyVersion !== projectDependencyVersion) {
|
||||
throw new Error(
|
||||
`${dirname(
|
||||
files[0],
|
||||
)} has a different version of '${dependency}' than the common package.json.
|
||||
The common version is ${commonDependencyVersion}.
|
||||
The project version is ${projectDependencyVersion}.
|
||||
|
||||
Please update the project package.json to match the common package.json, or update both.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllFiles(
|
||||
projectFiles: string[],
|
||||
commonFiles: string[],
|
||||
getCleanFilePath: (file: string) => string,
|
||||
) {
|
||||
const commonAndProjectPaths = [...commonFiles, ...projectFiles]
|
||||
.map((file) => getCleanFilePath(file))
|
||||
.filter(shouldUseFileInWebContainer);
|
||||
|
||||
const uniquePaths = Array.from(new Set(commonAndProjectPaths));
|
||||
|
||||
return uniquePaths;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
import {join} from 'path';
|
||||
import type {TutorialConfigBase} from '../tutorials-types';
|
||||
import {
|
||||
TUTORIALS_ASSETS_METADATA_DIRECTORY,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
} from './web-constants';
|
||||
import {GlobOptionsWithFileTypesFalse} from 'glob';
|
||||
|
||||
export const CONTENT_PATH = join('projects', 'angular-dev', 'src', 'content');
|
||||
export const TUTORIALS_PROJECT_PATH = join(CONTENT_PATH, 'tutorials');
|
||||
export const TUTORIALS_ASSETS_NODE_PATH = join(
|
||||
'projects',
|
||||
'angular-dev',
|
||||
'src',
|
||||
'assets',
|
||||
'tutorials',
|
||||
);
|
||||
|
||||
export const TUTORIALS_PLAYGROUND_NODE_PATH = join(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_HOMEPAGE_NODE_PATH = join(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_COMMON_NODE_PATH = join(TUTORIALS_PROJECT_PATH, TUTORIALS_COMMON_DIRECTORY);
|
||||
|
||||
/** The common location where the tutorial assets are stored. */
|
||||
export const TUTORIALS_COMMON_ASSETS_SRC = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
|
||||
|
||||
/** The common location for the tutorial assets to be served from. */
|
||||
export const TUTORIALS_COMMON_ASSETS_DEST = join(TUTORIALS_ASSETS_NODE_PATH, 'common');
|
||||
|
||||
export const TUTORIALS_SOURCE_CODE_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_METADATA_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_METADATA_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_CONTENT_NODE_PATH = join(
|
||||
'projects',
|
||||
'angular-dev',
|
||||
'src',
|
||||
'content',
|
||||
'tutorials',
|
||||
);
|
||||
export const TUTORIALS_ROUTES_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
'tutorials.json',
|
||||
);
|
||||
export const PLAYGROUND_ROUTE_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
'playground.json',
|
||||
);
|
||||
export const TUTORIALS_COMMON_ASSETS_PATH = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
|
||||
|
||||
export const IGNORED_BASENAMES = [
|
||||
'.DS_Store',
|
||||
'LICENSE',
|
||||
'.gitignore',
|
||||
'.editorconfig',
|
||||
'.gitkeep',
|
||||
'favicon.ico',
|
||||
];
|
||||
export const IGNORED_EXTENSIONS = ['.map'];
|
||||
export const IGNORED_DIRECTORIES = ['.git'];
|
||||
|
||||
export const TUTORIAL_CONTENT_FILENAME = 'README.md';
|
||||
export const ANSWER_DIRECTORY = 'answer';
|
||||
export const INTRO_DIRECTORY = 'intro';
|
||||
export const STEPS_DIRECTORY = 'steps';
|
||||
export const CONFIG_FILE = 'config.json';
|
||||
export const CONFIG_KEYS: Array<keyof TutorialConfigBase> = [
|
||||
'title',
|
||||
'type',
|
||||
'nextTutorial',
|
||||
'src',
|
||||
'answerSrc',
|
||||
'openFiles',
|
||||
];
|
||||
export const REQUIRED_CONFIGS: Array<keyof Pick<TutorialConfigBase, 'title' | 'type'>> = [
|
||||
'title',
|
||||
'type',
|
||||
];
|
||||
|
||||
export const CONTENT_PLACEHOLDER = 'Insert content here';
|
||||
|
||||
export const GLOB_OPTIONS: GlobOptionsWithFileTypesFalse = {
|
||||
dot: false,
|
||||
absolute: true,
|
||||
posix: true,
|
||||
ignore: [
|
||||
'.',
|
||||
'**/node_modules/**',
|
||||
...IGNORED_BASENAMES.map((ignoredBasename) => `**/${ignoredBasename}`),
|
||||
...IGNORED_EXTENSIONS.map((ext) => `**/*${ext}`),
|
||||
...IGNORED_DIRECTORIES.map((dir) => `**/${dir}/**`),
|
||||
],
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
export const TUTORIALS_ASSETS_WEB_PATH = '/assets/tutorials';
|
||||
export const TUTORIALS_DIST_PATH = 'tutorials';
|
||||
|
||||
export const TUTORIALS_PLAYGROUND_DIRECTORY = 'playground';
|
||||
|
||||
/** the playground template that loads by default when entering the /playground page */
|
||||
export const DEFAULT_PLAYGROUND_TEMPLATE = '0-minigame';
|
||||
export const STARTER_PLAYGROUND_TEMPLATE = '3-hello-world';
|
||||
|
||||
export const TUTORIALS_HOMEPAGE_DIRECTORY = 'homepage';
|
||||
export const TUTORIALS_COMMON_DIRECTORY = 'common';
|
||||
export const TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY = 'source-code';
|
||||
export const TUTORIALS_ASSETS_METADATA_DIRECTORY = 'metadata';
|
||||
export const TUTORIALS_ASSETS_ROUTES_DIRECTORY = 'routes';
|
||||
|
||||
export const TUTORIALS_SOURCE_CODE_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY}`;
|
||||
export const TUTORIALS_METADATA_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_METADATA_DIRECTORY}`;
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {basename, dirname, extname} from 'path';
|
||||
import {DirectoryNode, FileNode, FileSystemTree} from '@webcontainer/api';
|
||||
import {ANSWER_DIRECTORY, CONFIG_FILE} from './node-constants';
|
||||
import {FileAndContent, FileAndContentRecord} from '../tutorials-types';
|
||||
|
||||
/**
|
||||
* Create a WebContainer's FileSystemTree from a list of files and its contents
|
||||
*/
|
||||
export function getFileSystemTree(files: string[], filesContents: FileAndContentRecord) {
|
||||
const fileSystemTree: FileSystemTree = {};
|
||||
|
||||
for (let filepath of files) {
|
||||
const dir = dirname(filepath);
|
||||
const filename = basename(filepath);
|
||||
|
||||
if (dir === '.') {
|
||||
const fileNode: FileNode = {file: {contents: filesContents[filepath]}};
|
||||
|
||||
fileSystemTree[filename] = fileNode;
|
||||
} else {
|
||||
const dirParts = dir.split('/');
|
||||
|
||||
buildFileSystemTree(fileSystemTree, dirParts, filename, filesContents[filepath]);
|
||||
}
|
||||
}
|
||||
|
||||
return fileSystemTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a WebContainer's file system tree object recursively, mutating the
|
||||
* `fileSystemTree` parameter.
|
||||
*
|
||||
* @see https://webcontainers.io/api#filesystemtree
|
||||
*/
|
||||
export function buildFileSystemTree(
|
||||
fileSystemTree: FileSystemTree,
|
||||
fileDirectories: string[],
|
||||
filename: FileAndContent['path'],
|
||||
fileContents: FileAndContent['content'],
|
||||
): void {
|
||||
if (fileDirectories.length === 1) {
|
||||
const directory = fileDirectories[0];
|
||||
|
||||
const fileNode: FileNode = {file: {contents: fileContents}};
|
||||
|
||||
fileSystemTree[directory] = {
|
||||
...fileSystemTree[directory],
|
||||
|
||||
directory: {
|
||||
...(fileSystemTree[directory]
|
||||
? (fileSystemTree[directory] as DirectoryNode).directory
|
||||
: undefined),
|
||||
|
||||
[filename]: fileNode,
|
||||
},
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const nextDirectory = fileDirectories.shift();
|
||||
if (!nextDirectory) return;
|
||||
|
||||
if (!fileSystemTree[nextDirectory]) {
|
||||
fileSystemTree[nextDirectory] = {directory: {}};
|
||||
}
|
||||
|
||||
buildFileSystemTree(
|
||||
(fileSystemTree[nextDirectory] as DirectoryNode).directory,
|
||||
fileDirectories,
|
||||
filename,
|
||||
fileContents,
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldUseFileInWebContainer(filename: string) {
|
||||
return (
|
||||
basename(filename) !== CONFIG_FILE &&
|
||||
['.md', '.png', '.jpg'].includes(extname(filename)) === false &&
|
||||
!filename.includes(`${ANSWER_DIRECTORY}/`)
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '@angular/core';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {ENVIRONMENT, WINDOW} from '@angular/docs';
|
||||
import {AnalyticsService} from './analytics.service';
|
||||
|
||||
describe('AnalyticsService', () => {
|
||||
@@ -36,6 +36,7 @@ describe('AnalyticsService', () => {
|
||||
|
||||
injector = Injector.create({
|
||||
providers: [
|
||||
{provide: ENVIRONMENT, useValue: {}},
|
||||
{provide: AnalyticsService, deps: [WINDOW]},
|
||||
{provide: WINDOW, useFactory: () => mockWindow, deps: []},
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
|
||||
|
||||
import {CodeEditor, REQUIRED_FILES} from './code-editor.component';
|
||||
import {CodeMirrorEditor} from './code-mirror-editor.service';
|
||||
import {FakeChangeDetectorRef} from '../../../../../shared/src/lib/utils/test-utils.spec';
|
||||
import {FakeChangeDetectorRef} from '@angular/docs/testing';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
|
||||
const files = [
|
||||
|
||||
@@ -8,16 +8,9 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import type {FileSystemTree} from '@webcontainer/api';
|
||||
|
||||
import type {
|
||||
TutorialConfig,
|
||||
TutorialMetadata,
|
||||
} from '@angular/docs';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_METADATA_WEB_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
TutorialType,
|
||||
} from '@angular/docs';
|
||||
import type {TutorialConfig, TutorialMetadata} from '@angular/docs';
|
||||
import {TUTORIALS_ASSETS_WEB_PATH} from '../editor/constants';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
|
||||
describe('EmbeddedTutorialManager', () => {
|
||||
@@ -252,13 +245,7 @@ describe('EmbeddedTutorialManager', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const commonSourceCode: FileSystemTree = {
|
||||
'common.ts': {
|
||||
file: {
|
||||
contents: 'code',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const metadata: TutorialMetadata = {
|
||||
tutorialFiles: {'app.js': ''},
|
||||
openFiles: ['app.js'],
|
||||
@@ -271,14 +258,13 @@ describe('EmbeddedTutorialManager', () => {
|
||||
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
fetchMock.and.returnValues(
|
||||
Promise.resolve(new Response(JSON.stringify(commonSourceCode))),
|
||||
Promise.resolve(new Response(JSON.stringify(tutorialSourceCode))),
|
||||
Promise.resolve(new Response(JSON.stringify(metadata))),
|
||||
);
|
||||
|
||||
await service.fetchAndSetTutorialFiles(tutorial);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(service.tutorialId()).toEqual(tutorial);
|
||||
expect(service.tutorialFilesystemTree()).toEqual(tutorialSourceCode);
|
||||
@@ -289,8 +275,6 @@ describe('EmbeddedTutorialManager', () => {
|
||||
|
||||
expect(service.type()).toEqual(metadata.type);
|
||||
|
||||
expect(service.commonFilesystemTree()).toEqual(commonSourceCode);
|
||||
|
||||
expect(service.hiddenFiles()).toEqual(metadata.hiddenFiles);
|
||||
expect(service.answerFiles()).toEqual(metadata.answerFiles!);
|
||||
});
|
||||
@@ -299,15 +283,11 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`)
|
||||
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
|
||||
|
||||
await expectAsync(service.fetchAndSetTutorialFiles(tutorial)).toBeRejectedWithError(
|
||||
@@ -315,38 +295,15 @@ describe('EmbeddedTutorialManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the tutorial common project cannot be fetched', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
await expectAsync(service.fetchAndSetTutorialFiles(tutorial)).toBeRejectedWithError(
|
||||
'Missing source code for tutorial common',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set shouldReinstallDependencies if project did not change', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -368,15 +325,12 @@ describe('EmbeddedTutorialManager', () => {
|
||||
it('should trigger shouldReInstallDependencies if new metadata has different dependencies', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -404,14 +358,10 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -437,14 +387,10 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -472,16 +418,12 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -507,16 +449,12 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -539,16 +477,12 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -13,8 +13,7 @@ import {BehaviorSubject} from 'rxjs';
|
||||
import {TutorialMetadata} from '@angular/docs';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_METADATA_WEB_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
TUTORIALS_ASSETS_WEB_PATH
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
@@ -133,15 +132,15 @@ export class EmbeddedTutorialManager {
|
||||
async fetchCommonFiles(): Promise<FileSystemTree> {
|
||||
if (this.commonFilesystemTree() !== null) return this.commonFilesystemTree() as FileSystemTree;
|
||||
|
||||
const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
|
||||
//const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
|
||||
|
||||
this.tutorialFilesystemTree.set(commonFiles);
|
||||
//this.tutorialFilesystemTree.set(commonFiles);
|
||||
|
||||
return commonFiles;
|
||||
return {};
|
||||
}
|
||||
|
||||
private async fetchTutorialSourceCode(tutorial: string): Promise<FileSystemTree> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing source code for tutorial ${tutorial}`);
|
||||
|
||||
@@ -149,7 +148,7 @@ export class EmbeddedTutorialManager {
|
||||
}
|
||||
|
||||
private async fetchTutorialMetadata(tutorial: string): Promise<TutorialMetadata> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing metadata for ${tutorial}`);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {TutorialType} from '@angular/docs';
|
||||
import {
|
||||
FakeWebContainer,
|
||||
FakeWebContainerProcess,
|
||||
} from '../../../../shared/src/lib/utils/test-utils.spec';
|
||||
} from '@angular/docs/testing';
|
||||
import {AlertManager} from './alert-manager.service';
|
||||
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
import {LoadingStep} from './enums/loading-steps';
|
||||
|
||||
@@ -11,7 +11,8 @@ import {By} from '@angular/platform-browser';
|
||||
|
||||
import {Terminal} from './terminal.component';
|
||||
import {TerminalHandler, TerminalType} from './terminal-handler.service';
|
||||
import {FakeEventTarget, WINDOW} from '@angular/docs';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {FakeEventTarget} from '@angular/docs/testing';
|
||||
|
||||
describe('Terminal', () => {
|
||||
let component: Terminal;
|
||||
|
||||
@@ -25,7 +25,7 @@ import {ClickOutside, NavigationList} from '@angular/docs';
|
||||
import {injectAsync} from '../../core/services/inject-async';
|
||||
import {EmbeddedTutorialManager} from '../../editor/index';
|
||||
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/routes/playground.json';
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playground/routes.json';
|
||||
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
EmbeddedEditor,
|
||||
EmbeddedTutorialManager,
|
||||
NodeRuntimeSandbox
|
||||
} from '@angular/docs';
|
||||
} from '../../editor';
|
||||
|
||||
import {mockAsyncProvider} from '../../core/services/inject-async';
|
||||
import Tutorial from './tutorial.component';
|
||||
|
||||
@@ -214,14 +214,14 @@ export default class Tutorial implements AfterViewInit {
|
||||
this.shouldRenderContent.set(routeData.type !== TutorialType.EDITOR_ONLY);
|
||||
|
||||
this.nextStepPath = routeData.nextStep
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.nextStep}`
|
||||
? `/${routeData.nextStep}`
|
||||
: undefined;
|
||||
this.previousStepPath = routeData.previousStep
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.previousStep}`
|
||||
? `/${routeData.previousStep}`
|
||||
: undefined;
|
||||
|
||||
this.nextTutorialPath.set(
|
||||
routeData.nextTutorial ? `/${PagePrefix.TUTORIALS}/${routeData.nextTutorial}` : null,
|
||||
routeData.nextTutorial ? `/${routeData.nextTutorial}` : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
|
||||
import TUTORIALS_SUB_NAVIGATION_DATA_JSON from '../../src/assets/tutorials/routes/tutorials.json';
|
||||
import FIRST_APP_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/first-app/routes.json';
|
||||
import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/learn-angular/routes.json';
|
||||
|
||||
import {DefaultPage} from './core/enums/pages';
|
||||
import {getApiNavigationItems} from './features/references/helpers/manifest.helper';
|
||||
@@ -937,7 +938,8 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
];
|
||||
|
||||
export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
...TUTORIALS_SUB_NAVIGATION_DATA_JSON,
|
||||
FIRST_APP_TUTORIAL_NAV_DATA,
|
||||
LEARN_ANGULAR_TUTORIAL_NAV_DATA,
|
||||
{
|
||||
path: DefaultPage.TUTORIALS,
|
||||
contentPath: 'tutorials/home',
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
|
||||
package(default_visibility = ["//adev:__subpackages__"])
|
||||
|
||||
copy_to_directory(
|
||||
name = "content",
|
||||
srcs = [
|
||||
"//adev/src/content/best-practices",
|
||||
"//adev/src/content/best-practices/runtime-performance",
|
||||
"//adev/src/content/ecosystem",
|
||||
"//adev/src/content/ecosystem/service-workers",
|
||||
"//adev/src/content/guide",
|
||||
"//adev/src/content/guide/animations",
|
||||
"//adev/src/content/guide/components",
|
||||
"//adev/src/content/guide/di",
|
||||
"//adev/src/content/guide/directives",
|
||||
"//adev/src/content/guide/forms",
|
||||
"//adev/src/content/guide/http",
|
||||
"//adev/src/content/guide/i18n",
|
||||
"//adev/src/content/guide/ngmodules",
|
||||
"//adev/src/content/guide/pipes",
|
||||
"//adev/src/content/guide/routing",
|
||||
"//adev/src/content/guide/signals",
|
||||
"//adev/src/content/guide/templates",
|
||||
"//adev/src/content/guide/testing",
|
||||
"//adev/src/content/introduction",
|
||||
"//adev/src/content/introduction/essentials",
|
||||
"//adev/src/content/reference",
|
||||
"//adev/src/content/reference/configs",
|
||||
"//adev/src/content/reference/errors",
|
||||
"//adev/src/content/reference/extended-diagnostics",
|
||||
"//adev/src/content/reference/migrations",
|
||||
"//adev/src/content/tools",
|
||||
"//adev/src/content/tools/cli",
|
||||
"//adev/src/content/tools/libraries",
|
||||
"//adev/src/content/tutorials",
|
||||
"//adev/src/content/tutorials/first-app:first-app-guides",
|
||||
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
|
||||
"//packages/animations:animations_docs",
|
||||
"//packages/animations/browser:animations_browser_docs",
|
||||
"//packages/animations/browser/testing:animations_browser_testing_docs",
|
||||
"//packages/common:common_docs",
|
||||
"//packages/common/http:http_docs",
|
||||
"//packages/common/http/testing:http_testing_docs",
|
||||
"//packages/common/testing:common_testing_docs",
|
||||
"//packages/common/upgrade:common_upgrade_docs",
|
||||
"//packages/core:core_docs",
|
||||
"//packages/core/rxjs-interop:core_rxjs-interop_docs",
|
||||
"//packages/core/testing:core_testing_docs",
|
||||
"//packages/elements:elements_docs",
|
||||
"//packages/forms:forms_docs",
|
||||
"//packages/localize:localize_docs",
|
||||
"//packages/platform-browser:platform-browser_docs",
|
||||
"//packages/platform-browser-dynamic:platform-browser_dynamic_docs",
|
||||
"//packages/platform-browser-dynamic/testing:platform-browser_dynamic_testing_docs",
|
||||
"//packages/platform-browser/animations:platform-browser_animations_docs",
|
||||
"//packages/platform-browser/animations/async:platform-browser_animations_async_docs",
|
||||
"//packages/platform-browser/testing:platform-browser_testing_docs",
|
||||
"//packages/platform-server:platform-server_docs",
|
||||
"//packages/platform-server/testing:platform-server_testing_docs",
|
||||
"//packages/router:router_docs",
|
||||
"//packages/router/testing:router_testing_docs",
|
||||
"//packages/router/upgrade:router_upgrade_docs",
|
||||
"//packages/service-worker:service-worker_docs",
|
||||
"//packages/upgrade:upgrade_docs",
|
||||
"//packages/upgrade/static:upgrade_static_docs",
|
||||
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/src/content": "",
|
||||
"packages/**/": "api/",
|
||||
},
|
||||
)
|
||||
|
||||
copy_to_directory(
|
||||
name = "tutorials",
|
||||
srcs = [
|
||||
"//adev/src/content/tutorials/first-app",
|
||||
"//adev/src/content/tutorials/homepage",
|
||||
"//adev/src/content/tutorials/learn-angular",
|
||||
"//adev/src/content/tutorials/playground",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/**/tutorials/*/": "",
|
||||
},
|
||||
)
|
||||
|
||||
copy_to_directory(
|
||||
name = "api",
|
||||
srcs = [
|
||||
"//packages:docs_api_manifest",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"**/": "",
|
||||
},
|
||||
)
|
||||
@@ -1,736 +0,0 @@
|
||||
{
|
||||
"@angular/animations": [
|
||||
{"name": "AnimationEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationPlayer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimateTimings", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "AnimationBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AnimationOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimateChildOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationMetadataType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "AnimationFactory", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NoopAnimationPlayer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AUTO_STYLE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "AnimationMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationTriggerMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStateMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationTransitionMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationReferenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationQueryMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationKeyframesSequenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStyleMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateChildMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateRefMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationSequenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationGroupMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationQueryOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStaggerMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "trigger", "type": "function", "isDeprecated": false},
|
||||
{"name": "animate", "type": "function", "isDeprecated": false},
|
||||
{"name": "group", "type": "function", "isDeprecated": false},
|
||||
{"name": "sequence", "type": "function", "isDeprecated": false},
|
||||
{"name": "style", "type": "function", "isDeprecated": false},
|
||||
{"name": "state", "type": "function", "isDeprecated": false},
|
||||
{"name": "keyframes", "type": "function", "isDeprecated": false},
|
||||
{"name": "transition", "type": "function", "isDeprecated": false},
|
||||
{"name": "animation", "type": "function", "isDeprecated": false},
|
||||
{"name": "animateChild", "type": "function", "isDeprecated": false},
|
||||
{"name": "useAnimation", "type": "function", "isDeprecated": false},
|
||||
{"name": "query", "type": "function", "isDeprecated": false},
|
||||
{"name": "stagger", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/animations/browser": [
|
||||
{"name": "NoopAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AnimationDriver", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/animations/browser/testing": [
|
||||
{"name": "MockAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MockAnimationPlayer", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common": [
|
||||
{"name": "DatePipeConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "XhrFactory", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "registerLocaleData", "type": "function", "isDeprecated": false},
|
||||
{"name": "JsonPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "ViewportScroller", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgIf", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "I18nSelectPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "LowerCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "SlicePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "NgLocalization", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CommonModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "NgTemplateOutlet", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PlatformLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgStyle", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgComponentOutlet", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isPlatformBrowser", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgPlural", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NumberFormatStyle", "type": "enum", "isDeprecated": false},
|
||||
{"name": "ImageLoaderConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "KeyValue", "type": "interface", "isDeprecated": false},
|
||||
{"name": "I18nPluralPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "PopStateEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HashLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForOfContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DOCUMENT", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DecimalPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "Location", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isPlatformServer", "type": "function", "isDeprecated": false},
|
||||
{"name": "KeyValuePipe", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Plural", "type": "enum", "isDeprecated": false},
|
||||
{"name": "provideCloudflareLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DATE_PIPE_DEFAULT_TIMEZONE", "type": "constant", "isDeprecated": true},
|
||||
{"name": "isPlatformWorkerApp", "type": "function", "isDeprecated": true},
|
||||
{"name": "ImageLoader", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgFor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForOf", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideImgixLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormStyle", "type": "enum", "isDeprecated": false},
|
||||
{"name": "provideImageKitLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgSwitch", "type": "directive", "isDeprecated": false},
|
||||
{"name": "provideCloudinaryLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isPlatformWorkerUi", "type": "function", "isDeprecated": true},
|
||||
{"name": "NgLocaleLocalization", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PRECONNECT_CHECK_BLOCKLIST", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgClass", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "formatDate", "type": "function", "isDeprecated": false},
|
||||
{"name": "TranslationWidth", "type": "enum", "isDeprecated": false},
|
||||
{"name": "IMAGE_LOADER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormatWidth", "type": "enum", "isDeprecated": false},
|
||||
{"name": "DATE_PIPE_DEFAULT_OPTIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DatePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "AsyncPipe", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "APP_BASE_HREF", "type": "constant", "isDeprecated": false},
|
||||
{"name": "PathLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgPluralCase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LOCATION_INITIALIZED", "type": "constant", "isDeprecated": false},
|
||||
{"name": "LocationChangeEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NumberSymbol", "type": "enum", "isDeprecated": false},
|
||||
{"name": "LocationChangeListener", "type": "interface", "isDeprecated": false},
|
||||
{"name": "BrowserPlatformLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PercentPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "NgOptimizedImage", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "formatCurrency", "type": "function", "isDeprecated": false},
|
||||
{"name": "WeekDay", "type": "enum", "isDeprecated": false},
|
||||
{"name": "getLocaleId", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDayPeriods", "type": "function", "isDeprecated": false},
|
||||
{"name": "CurrencyPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "formatPercent", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgSwitchCase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleDayNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleMonthNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "formatNumber", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleEraNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgSwitchDefault", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleFirstDayOfWeek", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgIfContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleWeekEndRange", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDateFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleTimeFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "TitleCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "getLocaleDateTimeFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleNumberSymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "UpperCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "getLocaleNumberFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencySymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencyName", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencyCode", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocalePluralCase", "type": "constant", "isDeprecated": false},
|
||||
{"name": "getLocaleExtraDayPeriodRules", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleExtraDayPeriods", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDirection", "type": "function", "isDeprecated": false},
|
||||
{"name": "Time", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "getCurrencySymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "getNumberOfCurrencyDigits", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/http": [
|
||||
{"name": "HttpContextToken", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpParameterCodec", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpEventType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HttpHeaders", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpHandler", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpUrlEncodingCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpClientXsrfModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpInterceptor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpTransferCacheOptions", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpFeatureKind", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HttpProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "FetchBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpXsrfTokenExtractor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpXhrBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpDownloadProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "provideHttpClient", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpUploadProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpClient", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpSentEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpRequest", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpClientModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpUserEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "JsonpClientBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpHandlerFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpEvent", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpClientJsonpModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpResponseBase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpParamsOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpInterceptorFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withInterceptors", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpParams", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withInterceptorsFromDi", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpHeaderResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withXsrfConfiguration", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withNoXsrfProtection", "type": "function", "isDeprecated": false},
|
||||
{"name": "withJsonpSupport", "type": "function", "isDeprecated": false},
|
||||
{"name": "withRequestsMadeViaParent", "type": "function", "isDeprecated": false},
|
||||
{"name": "HTTP_INTERCEPTORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "HttpErrorResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withFetch", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpStatusCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "JsonpInterceptor", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/testing": [
|
||||
{"name": "MockLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SpyLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideLocationMocks", "type": "function", "isDeprecated": false},
|
||||
{"name": "MockPlatformLocationConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "MOCK_PLATFORM_LOCATION_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MockPlatformLocation", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/upgrade": [
|
||||
{"name": "UrlCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LocationUpgradeConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "$locationShim", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LOCATION_UPGRADE_CONFIGURATION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "LocationUpgradeModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AngularJSUrlCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "$locationShimProvider", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core": [
|
||||
{"name": "SecurityContext", "type": "enum", "isDeprecated": false},
|
||||
{"name": "SchemaMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewEncapsulation", "type": "enum", "isDeprecated": false},
|
||||
{"name": "Version", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ChangeDetectionStrategy", "type": "enum", "isDeprecated": false},
|
||||
{"name": "PipeTransform", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ErrorHandler", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DoBootstrap", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RendererType2", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ApplicationConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Signal", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "untracked", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ApplicationModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "Sanitizer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ProviderToken", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CreateComputedOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "assertNotInReactiveContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectionToken", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ForwardRefFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "WritableSignal", "type": "interface", "isDeprecated": false},
|
||||
{"name": "platformCore", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ENVIRONMENT_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgModuleRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueDiffer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DestroyRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "StateKey", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "getModuleFactory", "type": "function", "isDeprecated": true},
|
||||
{"name": "RendererFactory2", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgIterable", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ComponentRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "QueryList", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "computed", "type": "function", "isDeprecated": false},
|
||||
{"name": "createComponent", "type": "function", "isDeprecated": false},
|
||||
{"name": "mergeApplicationConfig", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectableProvider", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "isSignal", "type": "function", "isDeprecated": false},
|
||||
{"name": "INJECTOR", "type": "constant", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "runInInjectionContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "IterableDiffer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ChangeDetectorRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Injector", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderPhase", "type": "enum", "isDeprecated": false},
|
||||
{"name": "forwardRef", "type": "function", "isDeprecated": false},
|
||||
{"name": "EffectCleanupFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "makeStateKey", "type": "function", "isDeprecated": false},
|
||||
{"name": "CUSTOM_ELEMENTS_SCHEMA", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TemplateRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ValueEqualityFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgZone", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ModuleWithComponentFactories", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "ComponentFactoryResolver", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "DefaultIterableDiffer", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "getNgModuleById", "type": "function", "isDeprecated": false},
|
||||
{"name": "createNgModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "ElementRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CreateSignalOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "EmbeddedViewRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DebugEventListener", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IterableChanges", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NO_ERRORS_SCHEMA", "type": "constant", "isDeprecated": false},
|
||||
{"name": "asNativeElements", "type": "function", "isDeprecated": false},
|
||||
{"name": "APP_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "signal", "type": "function", "isDeprecated": false},
|
||||
{"name": "KeyValueChanges", "type": "interface", "isDeprecated": false},
|
||||
{"name": "TransferState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Compiler", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "RendererStyleFlags2", "type": "enum", "isDeprecated": false},
|
||||
{"name": "Renderer2", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "makeEnvironmentProviders", "type": "function", "isDeprecated": false},
|
||||
{"name": "DebugNode", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Attribute", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "Inject", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "PLATFORM_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Query", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ImportProvidersSource", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "PLATFORM_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "createNgModuleRef", "type": "constant", "isDeprecated": true},
|
||||
{"name": "NgModuleFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "assertInInjectionContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "importProvidersFrom", "type": "function", "isDeprecated": false},
|
||||
{"name": "resolveForwardRef", "type": "function", "isDeprecated": false},
|
||||
{"name": "PACKAGE_ROOT_URL", "type": "constant", "isDeprecated": true},
|
||||
{"name": "Testability", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueChangeRecord", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LOCALE_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ComponentFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "ANIMATION_MODULE_TYPE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Optional", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "KeyValueDifferFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewContainerRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "CSP_NONCE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Injectable", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "EnvironmentInjector", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueDiffers", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IterableChangeRecord", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DebugElement", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderRef", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ComponentMirror", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DEFAULT_CURRENCY_CODE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "APP_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "CompilerOptions", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "APP_BOOTSTRAP_LISTENER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TrackByFunction", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Self", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "ApplicationInitStatus", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "COMPILER_OPTIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "inject", "type": "function", "isDeprecated": false},
|
||||
{"name": "EventEmitter", "type": "constant", "isDeprecated": false},
|
||||
{"name": "CompilerFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "TRANSLATIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "reflectComponentType", "type": "function", "isDeprecated": false},
|
||||
{"name": "SkipSelf", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "TRANSLATIONS_FORMAT", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MissingTranslationStrategy", "type": "enum", "isDeprecated": false},
|
||||
{"name": "afterRender", "type": "function", "isDeprecated": false},
|
||||
{"name": "ContentChildren", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "IterableDifferFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgModule", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "EffectRef", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Host", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "createEnvironmentInjector", "type": "function", "isDeprecated": false},
|
||||
{"name": "IterableDiffers", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CreateEffectOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgProbeToken", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "createPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "effect", "type": "function", "isDeprecated": false},
|
||||
{"name": "afterNextRender", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestabilityRegistry", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ContentChild", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "GetTestability", "type": "interface", "isDeprecated": false},
|
||||
{"name": "setTestabilityGetter", "type": "function", "isDeprecated": false},
|
||||
{"name": "Directive", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "createPlatformFactory", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewChildren", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "assertPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "destroyPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "getPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgZoneOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewChild", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "isStandalone", "type": "function", "isDeprecated": false},
|
||||
{"name": "BootstrapOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "PlatformRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Component", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "Pipe", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "getDebugNode", "type": "function", "isDeprecated": false},
|
||||
{"name": "Predicate", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Input", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "ApplicationRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Output", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "HostBinding", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "HostListener", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "provideZoneChangeDetection", "type": "function", "isDeprecated": false},
|
||||
{"name": "@if", "type": "block", "isDeprecated": false},
|
||||
{"name": "@for", "type": "block", "isDeprecated": false},
|
||||
{"name": "@switch", "type": "block", "isDeprecated": false},
|
||||
{"name": "@defer", "type": "block", "isDeprecated": false},
|
||||
{"name": "ng-content", "type": "element", "isDeprecated": false},
|
||||
{"name": "ng-template", "type": "element", "isDeprecated": false},
|
||||
{"name": "ng-container", "type": "element", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core/rxjs-interop": [
|
||||
{"name": "ToObservableOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "takeUntilDestroyed", "type": "function", "isDeprecated": false},
|
||||
{"name": "ToSignalOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "toObservable", "type": "function", "isDeprecated": false},
|
||||
{"name": "toSignal", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core/testing": [
|
||||
{"name": "waitForAsync", "type": "function", "isDeprecated": false},
|
||||
{"name": "MetadataOverride", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ComponentFixture", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "resetFakeAsyncZone", "type": "function", "isDeprecated": false},
|
||||
{"name": "DeferBlockFixture", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "TestComponentRenderer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "fakeAsync", "type": "function", "isDeprecated": false},
|
||||
{"name": "ComponentFixtureAutoDetect", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ComponentFixtureNoNgZone", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TestModuleMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "async", "type": "function", "isDeprecated": true},
|
||||
{"name": "tick", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestBedStatic", "type": "interface", "isDeprecated": false},
|
||||
{"name": "TestEnvironmentOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ModuleTeardownOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "flush", "type": "function", "isDeprecated": false},
|
||||
{"name": "discardPeriodicTasks", "type": "function", "isDeprecated": false},
|
||||
{"name": "flushMicrotasks", "type": "function", "isDeprecated": false},
|
||||
{"name": "getTestBed", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestBed", "type": "constant", "isDeprecated": false},
|
||||
{"name": "inject", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectSetupWrapper", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withModule", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/elements": [
|
||||
{"name": "NgElementStrategyEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgElementStrategy", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElementConstructor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElementStrategyFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElement", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "WithProperties", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgElementConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "createCustomElement", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/forms": [
|
||||
{"name": "ControlValueAccessor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ControlContainer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgControl", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Form", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AbstractFormGroupDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControlDirective", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "FormControlState", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RangeValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormsModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "CheckboxControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NumberValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControlOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgModelGroup", "type": "directive", "isDeprecated": false},
|
||||
{"name": "SelectControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormGroupName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "COMPOSITION_BUFFER_MODE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DefaultValueAccessor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForm", "type": "directive", "isDeprecated": false},
|
||||
{"name": "ValidationErrors", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "FormControlDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormArray", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SelectMultipleControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormGroupDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControlName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "Validator", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SetDisabledStateOption", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "FormControlStatus", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ReactiveFormsModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "FormGroup", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ControlConfig", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NG_VALIDATORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgModel", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NgControlStatus", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NgControlStatusGroup", "type": "directive", "isDeprecated": false},
|
||||
{"name": "RadioControlValueAccessor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NG_ASYNC_VALIDATORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormArrayName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControlOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "FormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Validators", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgSelectOption", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MaxValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NG_VALUE_ACCESSOR", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MinValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AsyncValidator", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RequiredValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "CheckboxRequiredValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControl", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EmailValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ValidatorFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AsyncValidatorFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "MinLengthValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NonNullableFormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MaxLengthValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "PatternValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "UntypedFormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UntypedFormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "UntypedFormArray", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormArray", "type": "constant", "isDeprecated": false},
|
||||
{"name": "UntypedFormGroup", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormGroup", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormRecord", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isFormRecord", "type": "constant", "isDeprecated": false}
|
||||
],
|
||||
"@angular/localize": [
|
||||
{"name": "loadTranslations", "type": "function", "isDeprecated": false},
|
||||
{"name": "clearTranslations", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser": [
|
||||
{"name": "By", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MetaDefinition", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "enableDebugTools", "type": "function", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Title", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EVENT_MANAGER_PLUGINS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "EventManager", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HydrationFeatureKind", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HydrationFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "makeStateKey", "type": "constant", "isDeprecated": true},
|
||||
{"name": "disableDebugTools", "type": "function", "isDeprecated": false},
|
||||
{"name": "SafeValue", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Meta", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SafeHtml", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SafeStyle", "type": "interface", "isDeprecated": false},
|
||||
{"name": "withNoHttpTransferCache", "type": "function", "isDeprecated": false},
|
||||
{"name": "HAMMER_GESTURE_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "SafeScript", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ApplicationConfig", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "HammerLoader", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "SafeUrl", "type": "interface", "isDeprecated": false},
|
||||
{"name": "REMOVE_STYLES_ON_COMPONENT_DESTROY", "type": "constant", "isDeprecated": false},
|
||||
{"name": "SafeResourceUrl", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HAMMER_LOADER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "withHttpTransferCacheOptions", "type": "function", "isDeprecated": false},
|
||||
{"name": "bootstrapApplication", "type": "function", "isDeprecated": false},
|
||||
{"name": "DomSanitizer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "TransferState", "type": "constant", "isDeprecated": false},
|
||||
{"name": "HammerGestureConfig", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "StateKey", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "EventManagerPlugin", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideClientHydration", "type": "function", "isDeprecated": false},
|
||||
{"name": "createApplication", "type": "function", "isDeprecated": false},
|
||||
{"name": "provideProtractorTestingSupport", "type": "function", "isDeprecated": false},
|
||||
{"name": "platformBrowser", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HammerModule", "type": "ng_module", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser-dynamic": [
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "JitCompilerFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "RESOURCE_CACHE_PROVIDER", "type": "constant", "isDeprecated": true},
|
||||
{"name": "platformBrowserDynamic", "type": "constant", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser-dynamic/testing": [
|
||||
{"name": "platformBrowserDynamicTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserDynamicTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser/animations": [
|
||||
{"name": "BrowserAnimationsModuleConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "BrowserAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideAnimations", "type": "function", "isDeprecated": false},
|
||||
{"name": "NoopAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideNoopAnimations", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser/testing": [
|
||||
{"name": "platformBrowserTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-server": [
|
||||
{"name": "PlatformConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "PlatformState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "provideServerRendering", "type": "function", "isDeprecated": false},
|
||||
{"name": "ServerTransferStateModule", "type": "ng_module", "isDeprecated": true},
|
||||
{"name": "INITIAL_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BEFORE_APP_SERIALIZED", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ServerModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "platformServer", "type": "constant", "isDeprecated": false},
|
||||
{"name": "platformDynamicServer", "type": "constant", "isDeprecated": true},
|
||||
{"name": "renderModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "renderApplication", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-server/testing": [
|
||||
{"name": "platformServerTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ServerTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router": [
|
||||
{"name": "UrlHandlingStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "mapToCanMatch", "type": "function", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "PRIMARY_OUTLET", "type": "constant", "isDeprecated": false},
|
||||
{"name": "OutletContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DetachedRouteHandle", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "TitleStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IsActiveMatchOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "OnSameUrlNavigation", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "createUrlTreeFromSnapshot", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadingStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLinkActive", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ChildrenOutletContexts", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLink", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLinkWithHref", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EventType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "InitialNavigation", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ViewTransitionsFeatureOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Params", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "mapToCanActivate", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterOutletContract", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadAllModules", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ParamMap", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ROUTES", "type": "constant", "isDeprecated": false},
|
||||
{"name": "RouterEvent", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "mapToCanActivateChild", "type": "function", "isDeprecated": false},
|
||||
{"name": "NoPreloading", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ViewTransitionInfo", "type": "interface", "isDeprecated": false},
|
||||
{"name": "provideRouter", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterPreloader", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterConfigOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "mapToCanDeactivate", "type": "function", "isDeprecated": false},
|
||||
{"name": "BaseRouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DeprecatedGuard", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "NavigationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlCreationOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "mapToResolve", "type": "function", "isDeprecated": false},
|
||||
{"name": "Router", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DefaultTitleStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Routes", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "convertToParamMap", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "defaultUrlMatcher", "type": "function", "isDeprecated": false},
|
||||
{"name": "UrlMatchResult", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ActivatedRoute", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlMatcher", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterOutlet", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NavigationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideRoutes", "type": "function", "isDeprecated": true},
|
||||
{"name": "Data", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ResolveData", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NavigationCancellationCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "DefaultExport", "type": "interface", "isDeprecated": false},
|
||||
{"name": "InMemoryScrollingOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "InMemoryScrollingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "LoadChildrenCallback", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withInMemoryScrolling", "type": "function", "isDeprecated": false},
|
||||
{"name": "NavigationExtras", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NavigationSkippedCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "NavigationCancel", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Navigation", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LoadChildren", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "QueryParamsHandling", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UrlTree", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ExtraOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RunGuardsAndResolvers", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NavigationSkipped", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Route", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NavigationError", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlSegmentGroup", "type": "undecorated_class", "isDeprecated": false},
|
||||
{
|
||||
"name": "EnabledBlockingInitialNavigationFeature",
|
||||
"type": "type_alias",
|
||||
"isDeprecated": false
|
||||
},
|
||||
{"name": "ROUTER_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "RoutesRecognized", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "InitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UrlSegment", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "GuardsCheckStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withEnabledBlockingInitialNavigation", "type": "function", "isDeprecated": false},
|
||||
{"name": "ROUTER_CONFIGURATION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ActivatedRouteSnapshot", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "GuardsCheckEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ResolveStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlSerializer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DisabledInitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ResolveEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DefaultUrlSerializer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withDisabledInitialNavigation", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouteConfigLoadStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouteConfigLoadEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DebugTracingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ChildActivationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withDebugTracing", "type": "function", "isDeprecated": false},
|
||||
{"name": "ChildActivationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterStateSnapshot", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ActivationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ActivationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withPreloading", "type": "function", "isDeprecated": false},
|
||||
{"name": "Scroll", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Event", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterConfigurationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withRouterConfig", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterHashLocationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withHashLocation", "type": "function", "isDeprecated": false},
|
||||
{"name": "NavigationErrorHandlerFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withNavigationErrorHandler", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewTransitionsFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withComponentInputBinding", "type": "function", "isDeprecated": false},
|
||||
{"name": "CanActivate", "type": "interface", "isDeprecated": true},
|
||||
{"name": "withViewTransitions", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterFeatures", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanActivateFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanActivateChild", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanActivateChildFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanDeactivate", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanDeactivateFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanMatch", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanMatchFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "Resolve", "type": "interface", "isDeprecated": true},
|
||||
{"name": "ResolveFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanLoad", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanLoadFn", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "NavigationBehaviorOptions", "type": "interface", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router/testing": [
|
||||
{"name": "RouterTestingModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterTestingHarness", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router/upgrade": [
|
||||
{"name": "RouterUpgradeInitializer", "type": "constant", "isDeprecated": false},
|
||||
{"name": "locationSyncBootstrapListener", "type": "function", "isDeprecated": false},
|
||||
{"name": "setUpLocationSync", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/service-worker": [
|
||||
{"name": "SwUpdate", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ServiceWorkerModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "SwPush", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NoNewVersionDetectedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionDetectedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionInstallationFailedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionReadyEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionEvent", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UnrecoverableStateEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SwRegistrationOptions", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideServiceWorker", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/upgrade": [
|
||||
{"name": "UpgradeAdapter", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "UpgradeAdapterRef", "type": "undecorated_class", "isDeprecated": true}
|
||||
],
|
||||
"@angular/upgrade/static": [
|
||||
{"name": "UpgradeModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "downgradeModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "UpgradeComponent", "type": "directive", "isDeprecated": false}
|
||||
],
|
||||
"@angular/upgrade/static/testing": [
|
||||
{"name": "createAngularJSTestingModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "createAngularTestingModule", "type": "function", "isDeprecated": false}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "icons",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "images",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
# TODO(josephperrott): Generate previews from source instead of using already generated files
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "previews",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
|
||||
* Keys has to be equal to paths written down in the docs markdown files.
|
||||
*/
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "textures",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"currentVersion": "stable",
|
||||
"historicalVersionsLinkPattern": "https://v{{version}}.angular.dev",
|
||||
"mainVersions": [
|
||||
{
|
||||
"version": "stable",
|
||||
"url": "https://angular.dev"
|
||||
},
|
||||
{
|
||||
"version": "rc",
|
||||
"url": "https://rc.angular.dev"
|
||||
},
|
||||
{
|
||||
"version": "next",
|
||||
"url": "https://next.angular.dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
-6
@@ -29,17 +29,14 @@ describe('testability example', () => {
|
||||
let waitWithResultScript = function (done: any) {
|
||||
let rootEl = document.querySelector('example-app');
|
||||
let testability = window.getAngularTestability(rootEl);
|
||||
testability.whenStable((didWork: boolean, tasks: any) => {
|
||||
done(tasks);
|
||||
testability.whenStable(() => {
|
||||
done();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
element(by.css('.start-button')).click();
|
||||
|
||||
browser.driver.executeAsyncScript(waitWithResultScript).then((result: any[]) => {
|
||||
let pendingTask = result[0];
|
||||
expect(pendingTask.data.delay).toEqual(5000);
|
||||
expect(pendingTask.source).toEqual('setTimeout');
|
||||
browser.driver.executeAsyncScript(waitWithResultScript).then(() => {
|
||||
expect(element(by.css('.status')).getText()).not.toContain('done');
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -72,4 +72,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts",
|
||||
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -11,4 +11,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/service-worker-getting-started:src/app/log-update.service.ts",
|
||||
"//adev/src/content/examples/service-worker-getting-started:src/app/prompt-update.service.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ For example, imagine the following scenario:
|
||||
1. After some time, a new version of the application is deployed to the server.
|
||||
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.
|
||||
|
||||
IMPORANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
IMPORTANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
|
||||
1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache.
|
||||
Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
|
||||
|
||||
@@ -110,7 +110,7 @@ Make a change to the application, and watch the service worker install the updat
|
||||
<docs-code language="shell">
|
||||
|
||||
ng build
|
||||
npx http-server -p 8080 -c-1 dist/
|
||||
npx http-server -p 8080 -c-1 dist/<project-name>/browser
|
||||
|
||||
</docs-code>
|
||||
|
||||
|
||||
@@ -15,4 +15,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/security:src/app/inner-html-binding.component.ts",
|
||||
"//adev/src/content/examples/ssr:server.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -31,4 +31,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/animations:src/app/open-close.component.ts",
|
||||
"//adev/src/content/examples/animations:src/app/status-slider.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:components.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -240,10 +240,13 @@ In the example below, the prefetching starts when a browser becomes idle and the
|
||||
|
||||
## Testing
|
||||
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests are in "paused" (`@placeholder`) state, so that you can manually transition between states.
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests will play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to `Manual` in the TestBed configuration.
|
||||
|
||||
```typescript
|
||||
it('should render a defer block in different states', async () => {
|
||||
// configures the defer block behavior to start in "paused" state for manual control.
|
||||
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
|
||||
|
||||
@Component({
|
||||
// ...
|
||||
template: `
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/assets/images:dependency_injection.svg",
|
||||
"//adev/src/content/examples/resolution-modifiers:src/app/self/self.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ The injection context is available in these situations:
|
||||
* In the `factory` function specified for an `InjectionToken`.
|
||||
* Within a stack frame that is run in a injection context.
|
||||
|
||||
Knowing when your are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
Knowing when you are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
|
||||
## Class constructors
|
||||
|
||||
@@ -33,6 +33,7 @@ class MyComponent {
|
||||
Some APIs are designed to be run in an injection context. This is the case, for example, of the router guards. It allows the use of [`inject`](api/core/inject) to access a service within the guard function.
|
||||
|
||||
Here is an example for `CanActivateFn`
|
||||
|
||||
<docs-code language="typescript" highlight="[3]">
|
||||
const canActivateTeam: CanActivateFn =
|
||||
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
|
||||
|
||||
@@ -12,9 +12,6 @@ With hierarchical dependency injection, you can isolate sections of the applicat
|
||||
|
||||
## Types of injector hierarchies
|
||||
|
||||
Injectors in Angular have rules that you can leverage to achieve the desired visibility of injectables in your applications.
|
||||
By understanding these rules, you can determine whether to declare a provider at the application level, in a Component, or in a Directive.
|
||||
|
||||
Angular has two injector hierarchies:
|
||||
|
||||
| Injector hierarchies | Details |
|
||||
|
||||
@@ -22,4 +22,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/built-in-directives:src/app/item-switch.component.ts",
|
||||
"//adev/src/content/examples/structural-directives:src/app/app.component.html",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -49,4 +49,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.html",
|
||||
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ const configPromise = firstValueFrom(config$);
|
||||
const req = httpTesting.expectOne('/api/config', 'Request to load the configuration');
|
||||
|
||||
// We can assert various properties of the request if desired.
|
||||
expect(req.method).toBe('GET');
|
||||
expect(req.request.method).toBe('GET');
|
||||
|
||||
// Flushing the request causes it to complete, delivering the result.
|
||||
req.flush(DEFAULT_CONFIG);
|
||||
|
||||
@@ -19,4 +19,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/i18n:src/app/app.component.ts",
|
||||
"//adev/src/content/examples/i18n:src/main.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ Angular uses the Unicode *locale identifier* \(Unicode locale ID\) to find the c
|
||||
<docs-callout title="Unicode locale ID">
|
||||
|
||||
* A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification][UnicodeCldrDevelopmentCoreSpecification].
|
||||
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798].
|
||||
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationLocaleIDs].
|
||||
|
||||
* CLDR and Angular use [BCP 47 tags][RfcEditorInfoBcp47] as the base for the locale ID
|
||||
|
||||
@@ -61,6 +61,6 @@ To change the source locale of your project for the build, complete the followin
|
||||
|
||||
[RfcEditorInfoBcp47]: https://www.rfc-editor.org/info/bcp47 "BCP 47 | RFC Editor"
|
||||
|
||||
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/development/core-specification "Core Specification | Unicode CLDR Project"
|
||||
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/index/cldr-spec "Core Specification | Unicode CLDR Project"
|
||||
|
||||
[UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798]: https://cldr.unicode.org/development/core-specification#h.vgyyng33o798 "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
|
||||
[UnicodeCldrDevelopmentCoreSpecificationLocaleID]: https://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
|
||||
|
||||
@@ -7,4 +7,5 @@ generate_guides(
|
||||
]),
|
||||
data = [
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -14,4 +14,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/pipes:src/app/hero-async-message.component.ts",
|
||||
"//adev/src/content/examples/pipes:src/app/power-booster.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -18,4 +18,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/routing-with-urlmatcher:src/app/app.routes.ts",
|
||||
"//adev/src/content/examples/routing-with-urlmatcher:src/app/profile/profile.component.html",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:signals.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -27,4 +27,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.html",
|
||||
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -45,4 +45,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/testing:src/testing/async-observable-helpers.ts",
|
||||
"//adev/src/content/examples/testing:src/testing/index.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -15,4 +15,5 @@ generate_guides(
|
||||
"//adev/src/assets/images:templates.svg",
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:roadmap.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/angular-compiler-options:tsconfig.app.json",
|
||||
"//adev/src/content/examples/angular-compiler-options:tsconfig.json",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -224,7 +224,7 @@ For details of those options and their possible values, see the [Angular CLI Ref
|
||||
|
||||
## Complex configuration values
|
||||
|
||||
The `assets`, `index`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `assets`, `index`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `sourceMap` and `optimization` options can be set to a simple boolean value. They can also be given a complex value using the configuration file.
|
||||
|
||||
The following sections provide more details of how these complex values are used in each case.
|
||||
@@ -418,6 +418,7 @@ Several options can be used to fine-tune the optimization of an application.
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `minify` | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values. | `boolean` | `true` |
|
||||
| `inlineCritical` | Extract and inline critical CSS definitions to improve [First Contentful Paint](https://web.dev/first-contentful-paint). | `boolean` | `true` |
|
||||
| `removeSpecialComments` | Remove comments in global CSS that contains `@license` or `@preserve` or that starts with `//!` or `/*!`. | `boolean` | `true` |
|
||||
|
||||
#### Fonts optimization options
|
||||
|
||||
@@ -508,3 +509,16 @@ When supplying the value as a string the filename of the specified path will be
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `input` | The path of a file to use for the application's generated HTML index. | `string` | None (required) |
|
||||
| `output` | The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path. | `string` | `index.html` |
|
||||
|
||||
### Output path configuration
|
||||
|
||||
The `outputPath` option can be either a String which will be used as the `base` value or an Object for more fine-tune configuration.
|
||||
|
||||
Several options can be used to fine-tune the output structure of an application.
|
||||
|
||||
| Options | Details | Value type | Default value |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `base` | Specify the output path relative to workspace root. | `string` | |
|
||||
| `browser` | The output directory name for your browser build is within the base output path. This can be safely served to users. | `string` | `browser` |
|
||||
| `server` | The output directory name of your server build within the output path base. | `string` | `server` |
|
||||
| `media` | The output directory name for your media files located within the output browser directory. These media files are commonly referred to as resources in CSS files. | `string` | `media` |
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/errors:cyclic-imports/child.component.ts",
|
||||
"//adev/src/content/examples/errors:cyclic-imports/parent.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Required input is accessed before a value is set.
|
||||
|
||||
@description
|
||||
A required input was accessed but no value was bound.
|
||||
|
||||
This can happen when a required input is accessed too early in your directive or component.
|
||||
This is commonly happening when the input is read as part of class construction.
|
||||
|
||||
Inputs are guaranteed to be available in the `ngOnInit` lifecycle hook and afterwards.
|
||||
|
||||
## Fixing the error
|
||||
|
||||
Access the required input in reactive contexts.
|
||||
For example, in the template itself, inside a `computed`, or inside an effect.
|
||||
|
||||
Alternatively, access the input inside the `ngOnInit` lifecycle hook, or later.
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user