mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
603 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 203a3d59a0 | |||
| c16b5e8290 | |||
| 79d8736b70 | |||
| 1c55c49b30 | |||
| d5c631bf36 | |||
| 5b4add27b6 | |||
| e88a12d5b3 | |||
| c9a1c6f1c7 | |||
| 4772da70b5 | |||
| cdedd00d12 | |||
| 0427d7ac7e | |||
| 79a7e02c61 | |||
| 4fd5409090 | |||
| 57ac8063de | |||
| 9a91e0f589 | |||
| e750e4edcf | |||
| a603338fe8 | |||
| 27235f14b6 | |||
| 659b88d138 | |||
| b75aca1d74 | |||
| d232ea143f | |||
| 617dc53280 | |||
| 087e06abce | |||
| 93264b53e5 | |||
| 68d111c841 | |||
| 17610fa9b8 | |||
| 7fa791e965 | |||
| 7967d28966 | |||
| a15a01276e | |||
| b2e5a20c60 | |||
| 694e6aeb97 | |||
| 876f90ab5d | |||
| 1faa61f573 | |||
| 9bb34a2397 | |||
| 1ea8a144dc | |||
| ba19d80e70 | |||
| 24d8aa63ef | |||
| 044cb553b4 | |||
| f88b77b129 | |||
| 2f26159572 | |||
| abc0b15635 | |||
| 4ed58458b7 | |||
| 101302bba0 | |||
| 96ab999698 | |||
| c4d6dad394 | |||
| 0004eed9ff | |||
| 469fe25fe1 | |||
| 9932439277 | |||
| a398c558c0 | |||
| a169dd0c0c | |||
| 0bacc03bdb | |||
| de6ec5f152 | |||
| 6dc3388bd3 | |||
| 24177e973b | |||
| 16b7fcaafd | |||
| fd54911521 | |||
| 19daa075e8 | |||
| 3c12cd7ab0 | |||
| 17dbf8b8e2 | |||
| 58ed76be93 | |||
| 543df3dca5 | |||
| 65f9115d45 | |||
| 049e4ab596 | |||
| e8d01f995b | |||
| c509ba43f1 | |||
| aaf199f282 | |||
| 648edaa508 | |||
| 755356c1bc | |||
| 66ecf4c274 | |||
| 74e6ce5d23 | |||
| cfa80dc107 | |||
| ec9cc38015 | |||
| cbbd9f3f19 | |||
| 836aeba01d | |||
| d4fdba5679 | |||
| 7c863d728c | |||
| 2a4e3f5373 | |||
| 5104a89b30 | |||
| 708131dc01 | |||
| 20ea149a85 | |||
| 2d2eaec2ae | |||
| 40e8577faf | |||
| dae3e24d14 | |||
| 036a2fdc0c | |||
| 9140b027fa | |||
| 857f29b9e9 | |||
| 1e424863eb | |||
| 8fc9ff0e9e | |||
| cb460f81cc | |||
| a18b6b9284 | |||
| ef553363da | |||
| 4736efc94f | |||
| 069dcd11c8 | |||
| 0d7f679d0f | |||
| 24c4c77870 | |||
| cb34d35db7 | |||
| 7af3237935 | |||
| 6312d63b47 | |||
| 7e2fe81b74 | |||
| 86f0f4adf8 | |||
| 90625890c0 | |||
| bc1d323345 | |||
| 6ec7a42b95 | |||
| 187ba05f1e | |||
| 25cfccbadf | |||
| 26e40c7f89 | |||
| 493c412706 | |||
| 0db75ab5b1 | |||
| 0a53f96094 | |||
| dc27bea972 | |||
| 326a6023ca | |||
| ea4c9b5dcb | |||
| 57b0271543 | |||
| a9afc43d7d | |||
| abafebd24e | |||
| ac8eb5b943 | |||
| 21741384f4 | |||
| c1d9b71c4a | |||
| ec61ef0895 | |||
| 5ce3190da7 | |||
| a9e8c3cb0b | |||
| c8b34623d9 | |||
| 9040ea4d19 | |||
| af5da6db66 | |||
| 83a3aa58f3 | |||
| 8e366e8911 | |||
| 58a96e0f50 | |||
| 45064f1ae1 | |||
| a6275cfa54 | |||
| 14e66533ec | |||
| a2e5f483f5 | |||
| 13ade13a15 | |||
| 8c0e7733d6 | |||
| f812e72b80 | |||
| 3f65485e94 | |||
| 2a39e64288 | |||
| 00a9fd04c0 | |||
| 95e84c5e0b | |||
| cd14750028 | |||
| 9b47003d26 | |||
| 114b840a54 | |||
| 60dc59ba24 | |||
| 063954a6f4 | |||
| 12027f5815 | |||
| 6478c191b9 | |||
| 0dbf48b799 | |||
| d6998af34b | |||
| 848a3fe87e | |||
| a97a6db168 | |||
| 485fb1d309 | |||
| 2f42366b2c | |||
| c67cd2678d | |||
| 3b58058456 | |||
| 487b87ab2b | |||
| 3c9559b340 | |||
| 640540ada3 | |||
| 9d8da59f8b | |||
| f29ecadc17 | |||
| 535245a326 | |||
| 9805c179d6 | |||
| 295c26d78d | |||
| 1be5fc0955 | |||
| a7bc603263 | |||
| 72d22ba7ee | |||
| 7a2facae8a | |||
| 0c8f3f08ee | |||
| bea0de3488 | |||
| 4fc1581bbc | |||
| 81e9489ef2 | |||
| a0b788952d | |||
| 5f6323c36d | |||
| 6ae408847c | |||
| c22af72f75 | |||
| 5b9f896009 | |||
| baf5830bc9 | |||
| a31f65d86c | |||
| 44108f2f87 | |||
| efca206ed8 | |||
| 787367831e | |||
| d121e4e91f | |||
| 2b3d3b0fe1 | |||
| 6be88040d1 | |||
| dffeac8386 | |||
| b2e3b3e22e | |||
| 6f3b7a73b2 | |||
| df01a5cb23 | |||
| ba6d7fe018 | |||
| 0fcef65cea | |||
| dbd6f386ea | |||
| 8f6affdd64 | |||
| aab7fb8654 | |||
| 00cb3339ba | |||
| 92a28a77b1 | |||
| cf3de8351f | |||
| 836c1e1bb2 | |||
| 955e978978 | |||
| 115dc23317 | |||
| 4604be2502 | |||
| 6d61f2df3a | |||
| fbc8c567a2 | |||
| 935fa8fbe0 | |||
| 7d88c5f6eb | |||
| 53bd876266 | |||
| a26f281b52 | |||
| dee50f1d78 | |||
| 91486aaf07 | |||
| 07920d96d4 | |||
| 42272a0db6 | |||
| b65dd2327e | |||
| 710a24a7d0 | |||
| 0fad36eff2 | |||
| 291deac663 | |||
| f01b7183d2 | |||
| ac9cd6108f | |||
| 7f1c55755d | |||
| fafcb0d23f | |||
| 492ad4698a | |||
| 13bf5b7007 | |||
| ef974647b0 | |||
| db1a8ebdb4 | |||
| 6a13fd573d | |||
| 6cb3ade035 | |||
| 6c68f4ca71 | |||
| 4b81784317 | |||
| 6df5f0e0f5 | |||
| f6a9292943 | |||
| cfcce8c880 | |||
| cf0a0f3fab | |||
| 31a047f186 | |||
| f671f86ac2 | |||
| b07b39de78 | |||
| 221226e0a1 | |||
| 6bcdaec268 | |||
| f3b8a35933 | |||
| e480a9fd63 | |||
| 31a1575334 | |||
| baeb671334 | |||
| f763d2666f | |||
| 49dca36880 | |||
| 2580eccc2f | |||
| 98376f2c09 | |||
| f5307bf119 | |||
| 151a544277 | |||
| 4e200bf13b | |||
| 5de7575be8 | |||
| a523801b25 | |||
| 290e26a2ab | |||
| 95e4806ce3 | |||
| cc8dbb61c4 | |||
| 64d9e658a4 | |||
| b393e3727e | |||
| 78f817c358 | |||
| 60f979e01f | |||
| eb44643bdf | |||
| 561cb1d8da | |||
| 031a1a8c8e | |||
| 24c6b0eafb | |||
| a2a5646253 | |||
| d033540d0f | |||
| 8b6fd8dd18 | |||
| c8ff0be38f | |||
| a00beec2e0 | |||
| 76aba9ef0e | |||
| a55c8bb0e0 | |||
| 3831942771 | |||
| 30757bd4d5 | |||
| f5872c9921 | |||
| 7c066a4af4 | |||
| 6a1d4ed667 | |||
| e46162d3e4 | |||
| 0fbdb63bbc | |||
| e1f84a31dc | |||
| aa2d815648 | |||
| 54a7b33245 | |||
| 38aab6aa9d | |||
| 12ffb25e94 | |||
| cb5d9a480e | |||
| 825718f2b1 | |||
| 0b7e7fd3a5 | |||
| 22ff9cacce | |||
| 6b164dc83a | |||
| c01af4302f | |||
| 01efb1b807 | |||
| b16fc2610a | |||
| cfb42ee2c8 | |||
| 47756d81e5 | |||
| 90018f9327 | |||
| 7b60b5196f | |||
| 35e998effb | |||
| 158b1984bf | |||
| 27bf371e32 | |||
| 56ec37dcbc | |||
| d5fd614836 | |||
| e2f3ae51df | |||
| f61519ab90 | |||
| b651648db6 | |||
| a548c0333e | |||
| 7a95cccf50 | |||
| 44c48a4835 | |||
| 2cea80c6e2 | |||
| 7d6752a806 | |||
| 354340d643 | |||
| e3950b4e1f | |||
| 881bb4d719 | |||
| 6c7d2b6fe3 | |||
| 96d554d8e7 | |||
| e994305d70 | |||
| a87d50639b | |||
| 7e5d71de3e | |||
| bd8ac477c3 | |||
| 1ce0f7b07a | |||
| a08232793d | |||
| 8c3d41b5b3 | |||
| 387051b078 | |||
| d6fec2fc75 | |||
| 4626691198 | |||
| 5c6f3f8ec0 | |||
| 70d30c28e0 | |||
| 6240613477 | |||
| 227de98201 | |||
| a46c8cbf4e | |||
| 1d31a0c129 | |||
| bd0f811405 | |||
| f729215f0a | |||
| bd275b1b26 | |||
| 181d5755ee | |||
| e1c0493135 | |||
| 4f125c5f9a | |||
| c30dde257b | |||
| 1cc87b26cb | |||
| da429c59b6 | |||
| fc6b2f2669 | |||
| fecd141dfa | |||
| 6b83328577 | |||
| d4c20aca71 | |||
| a86661ff24 | |||
| 590c61889f | |||
| b44f2f9e47 | |||
| a48d65cdf4 | |||
| 42899c5aa0 | |||
| 2367b038fb | |||
| d8eab6f807 | |||
| dab522f011 | |||
| 8400533555 | |||
| 43acd44cf8 | |||
| cab7e003b3 | |||
| 2c3daebc5d | |||
| 91dbcb5eab | |||
| fa9bca23f7 | |||
| 51d73ed957 | |||
| 9c2be715a3 | |||
| c267f54bc3 | |||
| 83067b3ef2 | |||
| 09e905ad67 | |||
| 57404d4723 | |||
| 1c92609719 | |||
| e5bd6e9ccc | |||
| 5a816b8739 | |||
| c0d7bac111 | |||
| 3afc9d0cdb | |||
| de136105aa | |||
| 95accb07b4 | |||
| a2ba5482c3 | |||
| 842b2cceb6 | |||
| 6988a0070e | |||
| 236b448364 | |||
| 1f9ff84e28 | |||
| 7ff5152547 | |||
| 80e7a0f8fa | |||
| 739c0d27ec | |||
| a8e57a1fab | |||
| 32f417f77f | |||
| c99e5cf168 | |||
| 2ce2ad1c42 | |||
| 0d88e25311 | |||
| 79b21662fd | |||
| f9b226f9dc | |||
| 6d39551f53 | |||
| 5dd4bd6cf5 | |||
| 6d4271c340 | |||
| 0717c08b7d | |||
| 2837342852 | |||
| 1841d7903b | |||
| 90af00a07c | |||
| e06a25b6c3 | |||
| 8358dcd7e3 | |||
| cc13d6f1cd | |||
| 0754122232 | |||
| bf3281a6a5 | |||
| 356da9c9f6 | |||
| 4e4c678180 | |||
| 0f37b12a17 | |||
| 7c950794ea | |||
| c9a3708e55 | |||
| ddef3ac9a4 | |||
| 935c1816fd | |||
| 2e12e277c5 | |||
| 3bd85fb7b0 | |||
| af46256043 | |||
| 34a59967d7 | |||
| 792026bc7d | |||
| d2fca84bc4 | |||
| 1f5039bbd6 | |||
| 696f003553 | |||
| e5c3b25496 | |||
| b9ea2d6900 | |||
| abb4a9bad3 | |||
| d517a75c9b | |||
| 96fd73dc5d | |||
| fdd82857b1 | |||
| ddd9df68bb | |||
| 59b6ec6be8 | |||
| 3f456821b5 | |||
| 2aaddd3f64 | |||
| 6c58252521 | |||
| d8280ace7d | |||
| b2c38399f9 | |||
| 65ccaa4169 | |||
| 8156b3d4ec | |||
| 59a3475d77 | |||
| 6e82ae5871 | |||
| de3454a507 | |||
| 0451e7eabc | |||
| e355f05b9a | |||
| 595009ecf4 | |||
| 1d7d53caa3 | |||
| 6a11e9e8b4 | |||
| 877ba3e3c1 | |||
| 3861a73135 | |||
| e74b578bd9 | |||
| b14f0dc5d6 | |||
| 4461cefa4f | |||
| ce54ead2b5 | |||
| f6c2b39812 | |||
| 8e17dd719d | |||
| 57b9798cf6 | |||
| 2579c7121e | |||
| d7397fb29b | |||
| e40e55d902 | |||
| ffd0b4c30b | |||
| 5dd2822523 | |||
| 7e8a214474 | |||
| e92eda9a31 | |||
| 499c22251d | |||
| becbcdcff0 | |||
| 384d7aacd0 | |||
| 612986bcd3 | |||
| da1b71e0a5 | |||
| 6a01d62b9d | |||
| befd197933 | |||
| 9376e7f05e | |||
| 576338a9f5 | |||
| 8580542153 | |||
| 57630c3769 | |||
| f1d42cef87 | |||
| 48816e8c22 | |||
| d3cc0e998f | |||
| 13b16b905f | |||
| 40f75364d4 | |||
| 2b0698def0 | |||
| 6858149eb1 | |||
| a803c89a21 | |||
| 957effa404 | |||
| ea6d75291f | |||
| 403d217a59 | |||
| 1acff00468 | |||
| 4c878f90d2 | |||
| 938007df9d | |||
| cd903cc9f4 | |||
| 9b32d9148e | |||
| 0536b61bb8 | |||
| df68ad4731 | |||
| 6d0362cb44 | |||
| 1660f667e0 | |||
| 1dd8558f82 | |||
| c9b1ddff4d | |||
| 54bc384661 | |||
| bd9e91ecf7 | |||
| 4e00851c04 | |||
| 54fed68bbf | |||
| 114a2f8153 | |||
| 7002cfc811 | |||
| 0d23baa740 | |||
| 3c08f91a01 | |||
| 20993f7a87 | |||
| 6ade8c9c8c | |||
| e4df686ec6 | |||
| 1fa5020739 | |||
| d2df9fb59b | |||
| fe82292583 | |||
| 439e83a974 | |||
| 4ade911e66 | |||
| d2f8412322 | |||
| 00dee8384c | |||
| ceb7f1eaf0 | |||
| f069b3a71a | |||
| ec1e652f8a | |||
| a87586538a | |||
| 6a3a7b0d13 | |||
| 0c1cf7ac6b | |||
| 394c0fb719 | |||
| 6795cccbbb | |||
| 96ad3bf065 | |||
| 3f7d081112 | |||
| 2560115793 | |||
| 9060cebc02 | |||
| db81193ce6 | |||
| af6a75a837 | |||
| f206d1041f | |||
| f5aed23924 | |||
| 8473675db2 | |||
| 0c66e2424c | |||
| 9d565cd6d6 | |||
| 182d574fa3 | |||
| 569f57daa9 | |||
| dfc77e5f58 | |||
| 6005fe2d70 | |||
| 1dc14d9853 | |||
| 129b5d8bde | |||
| 7a064e372a | |||
| 81c315ec6e | |||
| 4dce8766f8 | |||
| 0b4c013aac | |||
| 958346107f | |||
| a207421ada | |||
| 7cc9f193fe | |||
| 30f993a356 | |||
| aaf00092c8 | |||
| 44f08baf63 | |||
| b4c1ffbfa1 | |||
| 4567a090c9 | |||
| fc3cde6cff | |||
| 1a8a5cf68f | |||
| 866d0f422d | |||
| b00dd23846 | |||
| 6fbe371b2c | |||
| edd060efbf | |||
| 1091239d2d | |||
| dc6ca34d4c | |||
| 33bd523be3 | |||
| 08553046bf | |||
| 603f30cab0 | |||
| eccbf36dbd | |||
| 419855629d | |||
| 37768da011 | |||
| 1e941f6f84 | |||
| 920f072824 | |||
| 37d627dbd4 | |||
| 1032c1e1a5 | |||
| 8e4a7ab52b | |||
| 3fec271fa3 | |||
| f9ef762bc7 | |||
| e87ac245e0 | |||
| 39a6f62551 | |||
| 96267922fe | |||
| ba37440cbf | |||
| 5d3f66d1a6 | |||
| 36993f3459 | |||
| d811d82a12 | |||
| c6e4cf12f0 | |||
| a318e6ac7c | |||
| c9cde3ab10 | |||
| 35fd10a9fd | |||
| e760fd7865 | |||
| 4e135947e6 | |||
| 6fe4f44b1d | |||
| 920820a1a8 | |||
| 6735f5f634 | |||
| 2d197f0c65 | |||
| e5720edb46 | |||
| eaf735da58 | |||
| bd1e78d746 | |||
| 81a287a79a | |||
| 2fa5e702de | |||
| 65c2f02ccb | |||
| a6be2e221f | |||
| 6ae68f39b9 | |||
| 450f360bd7 | |||
| c1dc86d186 | |||
| a58301f980 | |||
| b2ba678e73 | |||
| 2d98726ec9 | |||
| 030a68b3df | |||
| 6fd863d6e1 | |||
| ba9fc2419e | |||
| e90f5e5f22 | |||
| 8e5d54a3d5 | |||
| 54f7578d55 | |||
| a6fcf75423 | |||
| 99e7629159 | |||
| 6f19117a6a | |||
| ffe9b1fcc2 | |||
| 0792424df2 | |||
| 5c70148d20 | |||
| 65b460448e | |||
| 4cd1b3d38b | |||
| 4e9d6878a5 | |||
| 2bf088e7bb | |||
| 2e5ee1c198 | |||
| 371e26f6b3 | |||
| d50d3b85bd | |||
| 073ebfe09e | |||
| 926db6dd36 |
+4
-1
@@ -16,7 +16,6 @@ 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
|
||||
@@ -39,3 +38,7 @@ 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/
|
||||
@@ -1,38 +0,0 @@
|
||||
# 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'
|
||||
@@ -1,50 +0,0 @@
|
||||
# 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- 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
|
||||
|
||||
+28
-40
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
@@ -110,35 +110,20 @@ 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
@@ -153,15 +138,15 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
@@ -197,13 +182,13 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -215,7 +200,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -223,9 +208,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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -233,6 +218,7 @@ 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/
|
||||
@@ -243,6 +229,7 @@ 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
|
||||
@@ -251,6 +238,7 @@ 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
|
||||
|
||||
@@ -262,7 +250,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -270,9 +258,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/feature-request@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
- 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
|
||||
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@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
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@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
|
||||
+1
-23
@@ -5,18 +5,7 @@ import {FormatConfig} from '@angular/ng-dev';
|
||||
*/
|
||||
export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'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}',
|
||||
],
|
||||
'matchers': ['**/*.{yaml,yml}'],
|
||||
},
|
||||
'clang-format': {
|
||||
'matchers': [
|
||||
@@ -42,17 +31,6 @@ 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,6 +31,8 @@
|
||||
"packages/http/**",
|
||||
"**/.gitignore",
|
||||
"**/.gitkeep",
|
||||
"**/yarn.lock",
|
||||
"**/package.json",
|
||||
"**/third_party/**",
|
||||
"**/tsconfig-build.json",
|
||||
"**/tsconfig-tsec.json",
|
||||
|
||||
@@ -1150,7 +1150,6 @@ groups:
|
||||
- josephperrott
|
||||
- mgechev
|
||||
- twerske
|
||||
- ~jeanmeche
|
||||
|
||||
# =========================================================
|
||||
# Dev-infra
|
||||
@@ -1395,7 +1394,6 @@ groups:
|
||||
- devversion # Paul Gschwendtner
|
||||
- dgp1130 # Doug Parker
|
||||
- dylhunn # Dylan Hunn
|
||||
- jeanmeche # Matthieu Riegler
|
||||
- jelbourn # Jeremy Elbourn
|
||||
- jessicajaniuk # Jessica Janiuk
|
||||
- JiaLiPassion # Jia Li
|
||||
|
||||
-137
@@ -1,102 +1,3 @@
|
||||
<a name="17.1.0"></a>
|
||||
# 17.1.0 (2024-01-17)
|
||||
### 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)) |
|
||||
| [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 |
|
||||
| -- | -- | -- |
|
||||
| [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 |
|
||||
| -- | -- | -- |
|
||||
| [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 |
|
||||
| -- | -- | -- |
|
||||
| [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 -->
|
||||
|
||||
<a name="17.0.7"></a>
|
||||
# 17.0.7 (2023-12-13)
|
||||
### compiler
|
||||
@@ -649,44 +550,6 @@ Live long and prosper 🖖🏻
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="16.2.12"></a>
|
||||
# 16.2.12 (2023-11-02)
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [03f4050636](https://github.com/angular/angular/commit/03f4050636a385cf2e920b06e7ec94d6b5f32383) | fix | remove `finish` listener once player is destroyed ([#51136](https://github.com/angular/angular/pull/51136)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e092184a5c](https://github.com/angular/angular/commit/e092184a5c3d98f4be329e4037c9039c1b420d75) | fix | apply fixed_srcset_width values only to fixed srcsets ([#52486](https://github.com/angular/angular/pull/52486)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b3b4ae4c3a](https://github.com/angular/angular/commit/b3b4ae4c3ae5f307e68e3a57f84c9e2dfc9938fb) | fix | properly emit literal types in input coercion function arguments ([#52437](https://github.com/angular/angular/pull/52437)) |
|
||||
| [873c4f2454](https://github.com/angular/angular/commit/873c4f2454716520881f9d684364bdd6ab6ef722) | fix | use originally used module specifier for transform functions ([#52437](https://github.com/angular/angular/pull/52437)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="16.2.11"></a>
|
||||
# 16.2.11 (2023-10-25)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [54ea3b65c3](https://github.com/angular/angular/commit/54ea3b65c35254b220054541f2fbc52384486156) | fix | emit provider configured event when a service is configured with `providedIn` ([#52365](https://github.com/angular/angular/pull/52365)) |
|
||||
| [78533324dc](https://github.com/angular/angular/commit/78533324dcda2e884fbe489aaa6b37fa9a6fadbc) | fix | get root and platform injector providers in special cases ([#52365](https://github.com/angular/angular/pull/52365)) |
|
||||
| [019a0f4c22](https://github.com/angular/angular/commit/019a0f4c225d6bb6f741b37bf272b62899fe1f69) | fix | load global utils before creating platform injector in the standalone case ([#52365](https://github.com/angular/angular/pull/52365)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b79b4aca91](https://github.com/angular/angular/commit/b79b4aca914aefda1605fc8d069ac8d84283f1b3) | fix | `RouterTestingHarness` should throw if a component is expected but navigation fails ([#52357](https://github.com/angular/angular/pull/52357)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="16.2.10"></a>
|
||||
# 16.2.10 (2023-10-18)
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="16.2.9"></a>
|
||||
# 16.2.9 (2023-10-11)
|
||||
### forms
|
||||
|
||||
@@ -61,25 +61,6 @@ 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-e0ec7b60641d7f6369be45d8d02663fd50f320be.patch",
|
||||
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-b2cec2dcba358f9dc7111800a3d65738f57abe8f.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",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/src/assets/tutorials
|
||||
/src/assets/previews
|
||||
+17
-78
@@ -1,34 +1,24 @@
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect")
|
||||
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",
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.json",
|
||||
"tsconfig.worker.json",
|
||||
"//:angular.json",
|
||||
"adev/tsconfig.app.json",
|
||||
"adev/tsconfig.json",
|
||||
"adev/tsconfig.worker.json",
|
||||
] + glob(
|
||||
["src/**/*"],
|
||||
["adev/src/**/*"],
|
||||
exclude = [
|
||||
"src/**/*.spec.ts",
|
||||
"adev/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.
|
||||
"src/generated/**/*",
|
||||
"adev/src/generated/**/*",
|
||||
],
|
||||
)
|
||||
|
||||
TEST_FILES = APPLICATION_FILES + [
|
||||
"karma.conf.js",
|
||||
"tsconfig.spec.json",
|
||||
] + glob(
|
||||
["**/*.spec.ts"],
|
||||
)
|
||||
|
||||
APPLICATION_DEPS = link_packages([
|
||||
APPLICATION_DEPS = [
|
||||
"@npm//@angular-devkit/build-angular",
|
||||
"@npm//@angular/animations",
|
||||
"@npm//@angular/cdk",
|
||||
@@ -48,48 +38,7 @@ APPLICATION_DEPS = link_packages([
|
||||
"@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",
|
||||
@@ -100,13 +49,16 @@ architect(
|
||||
name = "build",
|
||||
args = [
|
||||
"angular-dev:build",
|
||||
"--output-path=build",
|
||||
"--output-path=build-app",
|
||||
],
|
||||
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(
|
||||
@@ -121,20 +73,7 @@ architect(
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
# 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"],
|
||||
)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
/* 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'}),
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,84 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.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),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {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),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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, '');
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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));
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import 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>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.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'],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {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;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.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;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.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;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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}/**`),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
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}`;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights 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 {ENVIRONMENT, WINDOW} from '@angular/docs';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {AnalyticsService} from './analytics.service';
|
||||
|
||||
describe('AnalyticsService', () => {
|
||||
@@ -36,7 +36,6 @@ 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 '@angular/docs/testing';
|
||||
import {FakeChangeDetectorRef} from '../../../../../shared/src/lib/utils/test-utils.spec';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
|
||||
const files = [
|
||||
|
||||
@@ -8,9 +8,16 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import type {FileSystemTree} from '@webcontainer/api';
|
||||
|
||||
import type {TutorialConfig, TutorialMetadata} from '@angular/docs';
|
||||
import {TUTORIALS_ASSETS_WEB_PATH} from '../editor/constants';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
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 {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
|
||||
describe('EmbeddedTutorialManager', () => {
|
||||
@@ -245,7 +252,13 @@ describe('EmbeddedTutorialManager', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const commonSourceCode: FileSystemTree = {
|
||||
'common.ts': {
|
||||
file: {
|
||||
contents: 'code',
|
||||
},
|
||||
},
|
||||
};
|
||||
const metadata: TutorialMetadata = {
|
||||
tutorialFiles: {'app.js': ''},
|
||||
openFiles: ['app.js'],
|
||||
@@ -258,13 +271,14 @@ 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(2);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(service.tutorialId()).toEqual(tutorial);
|
||||
expect(service.tutorialFilesystemTree()).toEqual(tutorialSourceCode);
|
||||
@@ -275,6 +289,8 @@ 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!);
|
||||
});
|
||||
@@ -283,11 +299,15 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.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`)
|
||||
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`)
|
||||
.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
|
||||
|
||||
await expectAsync(service.fetchAndSetTutorialFiles(tutorial)).toBeRejectedWithError(
|
||||
@@ -295,15 +315,38 @@ 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_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
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(
|
||||
JSON.stringify({
|
||||
@@ -325,12 +368,15 @@ describe('EmbeddedTutorialManager', () => {
|
||||
it('should trigger shouldReInstallDependencies if new metadata has different dependencies', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
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(
|
||||
JSON.stringify({
|
||||
@@ -358,10 +404,14 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
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(
|
||||
JSON.stringify({
|
||||
@@ -387,10 +437,14 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
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(
|
||||
JSON.stringify({
|
||||
@@ -418,12 +472,16 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.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`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -449,12 +507,16 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.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`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -477,12 +539,16 @@ describe('EmbeddedTutorialManager', () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.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`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
const allFiles = ['file1.ts', 'file2.ts'];
|
||||
|
||||
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
|
||||
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -13,7 +13,8 @@ import {BehaviorSubject} from 'rxjs';
|
||||
import {TutorialMetadata} from '@angular/docs';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_ASSETS_WEB_PATH
|
||||
TUTORIALS_METADATA_WEB_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
@@ -132,15 +133,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 {};
|
||||
return commonFiles;
|
||||
}
|
||||
|
||||
private async fetchTutorialSourceCode(tutorial: string): Promise<FileSystemTree> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing source code for tutorial ${tutorial}`);
|
||||
|
||||
@@ -148,7 +149,7 @@ export class EmbeddedTutorialManager {
|
||||
}
|
||||
|
||||
private async fetchTutorialMetadata(tutorial: string): Promise<TutorialMetadata> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing metadata for ${tutorial}`);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {TutorialType} from '@angular/docs';
|
||||
import {
|
||||
FakeWebContainer,
|
||||
FakeWebContainerProcess,
|
||||
} from '@angular/docs/testing';
|
||||
} from '../../../../shared/src/lib/utils/test-utils.spec';
|
||||
import {AlertManager} from './alert-manager.service';
|
||||
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
import {LoadingStep} from './enums/loading-steps';
|
||||
|
||||
@@ -11,8 +11,7 @@ import {By} from '@angular/platform-browser';
|
||||
|
||||
import {Terminal} from './terminal.component';
|
||||
import {TerminalHandler, TerminalType} from './terminal-handler.service';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {FakeEventTarget} from '@angular/docs/testing';
|
||||
import {FakeEventTarget, WINDOW} from '@angular/docs';
|
||||
|
||||
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/playground/routes.json';
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/routes/playground.json';
|
||||
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
EmbeddedEditor,
|
||||
EmbeddedTutorialManager,
|
||||
NodeRuntimeSandbox
|
||||
} from '../../editor';
|
||||
} from '@angular/docs';
|
||||
|
||||
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
|
||||
? `/${routeData.nextStep}`
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.nextStep}`
|
||||
: undefined;
|
||||
this.previousStepPath = routeData.previousStep
|
||||
? `/${routeData.previousStep}`
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.previousStep}`
|
||||
: undefined;
|
||||
|
||||
this.nextTutorialPath.set(
|
||||
routeData.nextTutorial ? `/${routeData.nextTutorial}` : null,
|
||||
routeData.nextTutorial ? `/${PagePrefix.TUTORIALS}/${routeData.nextTutorial}` : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
|
||||
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 TUTORIALS_SUB_NAVIGATION_DATA_JSON from '../../src/assets/tutorials/routes/tutorials.json';
|
||||
|
||||
import {DefaultPage} from './core/enums/pages';
|
||||
import {getApiNavigationItems} from './features/references/helpers/manifest.helper';
|
||||
@@ -938,8 +937,7 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
];
|
||||
|
||||
export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
FIRST_APP_TUTORIAL_NAV_DATA,
|
||||
LEARN_ANGULAR_TUTORIAL_NAV_DATA,
|
||||
...TUTORIALS_SUB_NAVIGATION_DATA_JSON,
|
||||
{
|
||||
path: DefaultPage.TUTORIALS,
|
||||
contentPath: 'tutorials/home',
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
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 = {
|
||||
"**/": "",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,736 @@
|
||||
{
|
||||
"@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}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
@@ -1,13 +0,0 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "icons",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "images",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
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",
|
||||
],
|
||||
)
|
||||
@@ -1,22 +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
|
||||
*/
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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>>> = {
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "textures",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-3
@@ -29,14 +29,17 @@ describe('testability example', () => {
|
||||
let waitWithResultScript = function (done: any) {
|
||||
let rootEl = document.querySelector('example-app');
|
||||
let testability = window.getAngularTestability(rootEl);
|
||||
testability.whenStable(() => {
|
||||
done();
|
||||
testability.whenStable((didWork: boolean, tasks: any) => {
|
||||
done(tasks);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
element(by.css('.start-button')).click();
|
||||
|
||||
browser.driver.executeAsyncScript(waitWithResultScript).then(() => {
|
||||
browser.driver.executeAsyncScript(waitWithResultScript).then((result: any[]) => {
|
||||
let pendingTask = result[0];
|
||||
expect(pendingTask.data.delay).toEqual(5000);
|
||||
expect(pendingTask.source).toEqual('setTimeout');
|
||||
expect(element(by.css('.status')).getText()).not.toContain('done');
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -72,5 +72,4 @@ 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,5 +5,4 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,5 +5,4 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -11,5 +11,4 @@ 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`.
|
||||
|
||||
IMPORTANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
IMPORANT: 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/<project-name>/browser
|
||||
npx http-server -p 8080 -c-1 dist/
|
||||
|
||||
</docs-code>
|
||||
|
||||
|
||||
@@ -15,5 +15,4 @@ 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,5 +31,4 @@ 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,5 +8,4 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:components.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -240,13 +240,10 @@ 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 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.
|
||||
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.
|
||||
|
||||
```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,5 +9,4 @@ 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 you are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
Knowing when your are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
|
||||
## Class constructors
|
||||
|
||||
@@ -33,7 +33,6 @@ 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,6 +12,9 @@ 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,5 +22,4 @@ 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,5 +49,4 @@ 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,5 +5,4 @@ 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.request.method).toBe('GET');
|
||||
expect(req.method).toBe('GET');
|
||||
|
||||
// Flushing the request causes it to complete, delivering the result.
|
||||
req.flush(DEFAULT_CONFIG);
|
||||
|
||||
@@ -19,5 +19,4 @@ 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][UnicodeCldrDevelopmentCoreSpecificationLocaleIDs].
|
||||
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798].
|
||||
|
||||
* 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/index/cldr-spec "Core Specification | Unicode CLDR Project"
|
||||
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/development/core-specification "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"
|
||||
[UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798]: https://cldr.unicode.org/development/core-specification#h.vgyyng33o798 "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
|
||||
|
||||
@@ -7,5 +7,4 @@ generate_guides(
|
||||
]),
|
||||
data = [
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -14,5 +14,4 @@ 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,5 +18,4 @@ 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,5 +8,4 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:signals.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -27,5 +27,4 @@ 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,5 +45,4 @@ 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,5 +8,4 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -15,5 +15,4 @@ generate_guides(
|
||||
"//adev/src/assets/images:templates.svg",
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,5 +8,4 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:roadmap.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -9,5 +9,4 @@ 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`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `assets`, `index`, `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,7 +418,6 @@ 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
|
||||
|
||||
@@ -509,16 +508,3 @@ 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,5 +9,4 @@ 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__"],
|
||||
)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# 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,5 +5,4 @@ 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