Compare commits

..

145 Commits

Author SHA1 Message Date
Andrew Scott b2b903957a release: cut the v21.2.2 release 2026-03-09 11:11:43 -07:00
Matthieu Riegler bd72571583 refactor(devtools): prevent spamming the message bus
This commit adds some state on the bus to prevent sending unecessary messages to the main window.

(cherry picked from commit 263b819a75)
2026-03-09 10:46:08 -07:00
Kam 628f3f99db docs: add Turkish community translation of Angular documentation
add a link to the community-driven Turkish translation of Angular
documentation to make it easier for Turkish-speaking developers to
discover localized docs.

The translation project is maintained by the community and hosted at:
https://github.com/erkamyaman/angular-tr

Providing documentation in developers' native languages helps lower
the barrier to entry and supports wider Angular adoption in the
Turkish-speaking developer community.

(cherry picked from commit 03465360bd)
2026-03-09 10:29:25 -07:00
hawkgs 79ac570c26 refactor(devtools): fix browser-specific styles infra
Fix browser-specific styles infrastructure. PR #62786 cleans up part of the code, but there are still services that attempt to load these stylesheets on `main`.

(cherry picked from commit ed3dc10fea)
2026-03-09 10:22:02 -07:00
Angular Robot 276c12a349 build: update dependency node to v22.22.1
See associated pull request for more information.
2026-03-06 15:32:15 -08:00
Angular Robot 104ac117ea docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-03-06 15:04:42 -08:00
Angular Robot c2a74548d9 build: update all github actions
See associated pull request for more information.
2026-03-06 14:36:16 -08:00
Kristiyan Kostadinov e7d1d8def3 refactor(core): add tracing for component creation
Adds tracing support for component creations.

(cherry picked from commit c19066c741)
2026-03-06 17:48:27 +00:00
Kristiyan Kostadinov 4a390b6c73 refactor(core): track the tracing service in the LView environment
Adds the `TracingService` to the `LView[ENVIRONMENT]` so we don't have to inject it everywhere.

(cherry picked from commit e433ba9a80)
2026-03-06 17:48:27 +00:00
Anushka Geeta Singh 5a486a65cf docs(router): Add example of relative navigation and multiple array values
Adds an example to docs with relative navigation and multiple values in
the commands array.

(cherry picked from commit 418cb61f2f)
2026-03-06 17:47:44 +00:00
Kristiyan Kostadinov c822bf8e76 fix(compiler-cli): always parenthesize object literals in TCB
This is a follow-up to #67381 which introduced a subtle bug where depending on the type checking configuration, we may put an object literal directly in the TCB body which the TS compiler ends up interpreting as a block. These changes resolve the issue by always wrapping the literal in parentheses.
2026-03-05 14:12:25 -08:00
Kristiyan Kostadinov 31e2c99007 refactor(compiler-cli): resolve presubmit issues
Resolves issues caught during the presubmit.
2026-03-05 14:12:25 -08:00
Kristiyan Kostadinov f8b6bd0cbe refactor(compiler-cli): escape quotes used in string expressions
TypeScript has functionality that automatically escapes quotes in string literals. These changes update the places where we may need to do the same ourselves.
2026-03-05 14:12:25 -08:00
Kristiyan Kostadinov 5349b027b8 refactor(compiler-cli): delete unused utilities
Deletes utilities that we no longer use.
2026-03-05 14:12:25 -08:00
Kristiyan Kostadinov 8f8d67dc57 refactor(compiler-cli): initial decoupling from TypeScript factory APIs
Initial pass to move usages of TS `factory` APIs to the new `TcbExpr`.
2026-03-05 14:12:25 -08:00
Kristiyan Kostadinov 23153a06dd refactor(compiler-cli): introduce new primitive for generating TCB code
Introduces the `TcbExpr` class that will be used to generate TCB code without going through TypeScript's factory APIs.
2026-03-05 14:12:25 -08:00
Alan Agius fd3b4413f3 docs: add a new section on nonces and caching considerations to the security guide.
(cherry picked from commit e5f99edda5)
2026-03-05 18:03:35 +00:00
Leon Senft 670d1660c4 feat(forms): add 'blur' option to debounce rule
Expands the `debounce` rule configuration to accept `'blur'`. When this option
is provided, the rule will delay model synchronization until the field loses
focus (is touched). This introduces a debouncer that defers resolution
until the framework automatically aborts pending debounces upon touch events.

(cherry picked from commit c767d678cf)
2026-03-05 17:55:18 +00:00
Angular Robot 8ca0237d99 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-03-04 16:27:39 -08:00
Joey Perrott 3576019c0f build: no longer require release mode for releases
This commit updates the github.mjs configuration to set requireReleaseModeForRelease to false, removing the requirement to use release mode for releases in the angular/angular repository.

(cherry picked from commit a6941adce8)
2026-03-04 22:48:59 +00:00
Doug Parker 05d022d5e6 fix(compiler-cli): ignore generated ngDevMode signal branch for code coverage
The Angular compiler unconditionally adds a debug name transform for signals
which generates a conditional on `ngDevMode` (e.g., `ngDevMode ? { debugName: "xyz" } : []`).
During testing, `ngDevMode` is true, so the true branch executes but the
false branch is never executed. Consequently, coverage tools report the
false branch as an untested line/branch, preventing 100% test coverage.

This commit adds a synthetic `/* istanbul ignore next */` comment to the
generated false branch so that Istanbul ignores it. We only include the
istanbul comment (instead of additionally including c8) to focus on the
established standard for Angular CLI/Karma coverage while maintaining
compatibility with modern Vitest setups, since @vitest/coverage-v8 now
natively respects the fallback istanbul comment.

Fixes #64583

(cherry picked from commit dc4cf649b6)
2026-03-04 22:42:57 +00:00
Andrew Scott 1df1697c6e fix(compiler): prevent mutation of children array in RecursiveVisitor
RecursiveVisitor.visitIfBlockBranch was permanently mutating the children array by pushing the expressionAlias into it. This change clones the array before pushing to avoid this side effect.

(cherry picked from commit 72a17afaf3)
2026-03-04 22:41:36 +00:00
Andrew Scott 05476ea21b release: bump VSCode extension version to 21.2.2 (#67449) 2026-03-04 12:40:40 -08:00
Jessica Janiuk f38169d0a7 release: cut the v21.2.1 release 2026-03-04 11:34:05 -08:00
Jessica Janiuk 6d7446bc6c refactor(core): account for shadow roots in animations
This adds a util function to get the proper target, properly accounting for shadow roots.

(cherry picked from commit fda08b7a89)
2026-03-04 18:16:43 +00:00
SkyZeroZx c25c84d7dc docs: add customization options for browser URL in routing guide
(cherry picked from commit 9208421290)
2026-03-04 18:10:29 +00:00
Angular Robot f4160c2073 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-03-04 10:01:57 -08:00
Jessica Janiuk 93c6dc6395 Revert "refactor(http): Improves base64 encoding/decoding with feature detection (#67002)"
This reverts commit aafeb1d2bd.

(cherry picked from commit 5338b5912c)
2026-03-04 17:19:14 +00:00
Jessica Janiuk 76431ed3e3 Revert "fix(http): correctly cache blob responses in transfer cache (#67002)"
This reverts commit 1f057afaac.

(cherry picked from commit 7eb33713b9)
2026-03-04 17:19:14 +00:00
Jessica Janiuk b4ec3cc4e4 fix(core): prevent child animation elements from being orphaned
When routing between two different routes, child animations were not finishing, causing elements to be left behind in the dom. The fix ensures the proper fallback is handled to avoid automatically cancelled custom events. This ensures the animation-fallback cancelling the animation actually completes, and ensures the element is removed.

fixes: #67400
(cherry picked from commit 9e64147b73)
2026-03-04 16:21:41 +00:00
Angular Robot 84e79f5add build: update pnpm to v10.30.3
See associated pull request for more information.
2026-03-04 08:03:41 -08:00
SkyZeroZx 2eecf0d9a2 docs: add documentation for dynamic validation schemas
(cherry picked from commit 9fa36d1a71)
2026-03-04 16:01:36 +00:00
Kristiyan Kostadinov da57d1af73 build: use TypeScript 5.9 for patch builds
Partially rolls back to using TypeScript 5.9 for the builds on the patch branch, because we bundle our TypeScript version with the language service which can introduce unexpected breakages for users.

Note that we still allow users to install TypeScript 6.
2026-03-04 07:59:23 -08:00
Georgi Serev 58ea4d2ee8 docs(docs-infra): add Azerbaijani community translation
Add https://angular.az (Azerbaijani) to the community translations section.

(cherry picked from commit e8676b5412)
2026-03-04 15:58:23 +00:00
SkyZeroZx 277ade97ac fix(http): correctly cache blob responses in transfer cache (#67002)
Previously, Blob values were passed to `Uint8Array` this resulted in silently producing an empty array (length = 0) without throwing an error, leading to empty cached data

PR Close #67002
2026-03-04 15:56:59 +00:00
SkyZeroZx aeb9b819d2 refactor(http): Improves base64 encoding/decoding with feature detection (#67002)
Use feature detection for `Uint8Array.prototype.toBase64` and
`Uint8Array.fromBase64`, falling back to the existing implementation
when native support is not available

PR Close #67002
2026-03-04 15:56:59 +00:00
Angular Robot 657d532ad1 build: update dependency preact-render-to-string to v6.6.6
See associated pull request for more information.
2026-03-03 14:48:18 -08:00
Angular Robot 8dafd2413a build: update dependency aspect_rules_jasmine to v2.0.4
See associated pull request for more information.
2026-03-03 14:46:02 -08:00
Angular Robot 796b9a714a build: update cross-repo angular dependencies
See associated pull request for more information.
2026-03-03 14:22:51 -08:00
Krueger01 dd551b1ff3 refactor(compiler-cli): update old angular.io references to angular.dev
Update comment references from the old site angular.io to the new site
angular.dev.

(cherry picked from commit 0e9d58ef09)
2026-03-03 22:22:23 +00:00
Andrew Scott 84adb2fb3b refactor(router): Permit deferring commit of traversal navigations
This updates the state manager to allow intercepting and deferring commits of traversal navigations.
The issues that were encountered in the past appear to be resolved in Chrome.
The behavior of redirect is still undefined in this case, so there is an added TODO.

(cherry picked from commit 778b748694)
2026-03-03 22:09:15 +00:00
Angular Robot 98343ea35e build: update cross-repo angular dependencies to b1b4bd0
See associated pull request for more information.
2026-03-03 10:36:54 -08:00
Angular Robot c4226a10d0 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-03-03 09:54:56 -08:00
Sai Kumar Kola 5e545069ac docs: fix side navigation bar foreground colors in both dark and light modes #67379
(cherry picked from commit 46c996a977)
2026-03-03 17:13:11 +00:00
SkyZeroZx ecf0bb4668 test(http): refactors HTTP client tests to use TestBed and providers
Updates HTTP client, JSONP, and XSRF tests to utilize `TestBed` for setup.

(cherry picked from commit ead6bb1f52)
2026-03-03 17:12:39 +00:00
Ben Hong 0d54c5f0e2 docs: modernize testing service guide
(cherry picked from commit 5955738bc4)
2026-03-03 17:12:03 +00:00
Angular Robot 8f6ca1d9f9 build: lock file maintenance
See associated pull request for more information.
2026-03-03 09:08:50 -08:00
Angular Robot 1f51c61372 build: update cross-repo angular dependencies to 2648276
See associated pull request for more information.
2026-03-02 12:22:46 -08:00
Jessica Janiuk f5e9a862c2 refactor(core): ignore infinite animations in animate api
This ensures that when calculating longest animations, we completely ignore infinite animations. This will prevent mistakes with using the API and hopefully catch any unexpected bugs.

fixes: #67350
(cherry picked from commit 9758ea9ee9)
2026-03-02 19:40:19 +00:00
Angular Robot 0c8a2858b9 build: update cross-repo angular dependencies to v21.2.0
See associated pull request for more information.
2026-03-02 11:32:52 -08:00
Matthieu Riegler a79e8afbad docs: update doc for provideZoneChangeDetection
fixes #67209

(cherry picked from commit a2d5048ff3)
2026-03-02 19:02:34 +00:00
SkyZeroZx 7b7faef739 docs: fixed note on event replay
(cherry picked from commit d793ee0bb7)
2026-03-02 18:59:21 +00:00
Joey Perrott 95afc09206 ci: update the path to the labeling github actions
Update the paths to the labeling github actions after the refactor in dev-infra

(cherry picked from commit 787db90e08)
2026-03-02 18:55:57 +00:00
kirjs 23cb9a4291 refactor(forms): address feedback
Refactor the normalizeFormArgs utility to include the internal FieldAdapter

(cherry picked from commit d2e33e86b9)
2026-03-02 16:46:47 +00:00
kirjs 0a863ba03f refactor(forms): hide adapter in public options
Moves adapter to internal options to prevent exposure but keep compatibility.

(cherry picked from commit 985d828f12)
2026-03-02 16:46:47 +00:00
Jessica Janiuk e923d88398 fix(core): Prevent removal of elements during drag and drop
This addresses a reported issue where elements were being fully removed from the DOM during drag and drop operations.

fixes: #67257
(cherry picked from commit 0b59cba85d)
2026-03-02 16:46:13 +00:00
Angular Robot 150f0b3c30 build: update cross-repo angular dependencies to 90e509f
See associated pull request for more information.
2026-03-02 08:42:55 -08:00
Angular Robot fc5f9d03cf build: update pnpm to v10.30.3
See associated pull request for more information.
2026-03-02 08:37:49 -08:00
Angular Robot 498d9cdda4 build: update cross-repo angular dependencies to 9cc4778
See associated pull request for more information.
2026-02-27 09:11:11 -08:00
Angular Robot fd8d29a157 build: update dependency bazel to v8.6.0
See associated pull request for more information.
2026-02-27 09:10:15 -08:00
Jessica Janiuk e2e9a9a531 fix(core): adds transfer cache to httpResource to fix hydration
This should prevent the microtask problem with hydration and httpResource.

fixes: #62897
(cherry picked from commit 88685cb3b6)
2026-02-27 17:09:26 +00:00
Matthieu Riegler 35fd8b567a build: use different default port for dev-app
Same as ADEV, currently the CLI prompts for another port if 4200 is busy but the toolchain doesn't support prompts.

By defaulting to 4201 we avoid regular CLI apps from preventing to run the dev-app

(cherry picked from commit 9bb4f02bef)
2026-02-27 17:04:19 +00:00
Angular Robot 4e9648d184 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-26 14:32:59 -08:00
Angular Robot 54a0541013 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-26 13:42:33 -08:00
Joey Perrott 9c0587f755 build: update to latest sha of dev-infra repo
Update to the latest sha of the dev-infra repo

(cherry picked from commit d1ebbbe9bc)
2026-02-26 20:42:17 +00:00
Joey Perrott ac06941bcf build: update to latest sha of dev-infra repo
Update to the latest sha of the dev-infra repo

(cherry picked from commit 385b9485c4)
2026-02-26 20:22:25 +00:00
Joey Perrott 907415c801 ci: set up automated issue labeling
Sets up automated issue labeling action

(cherry picked from commit 37b4d16357)
2026-02-26 20:22:25 +00:00
Angular Robot 225a56677e build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-26 10:40:54 -08:00
Joey Perrott 944181869d ci: set up linting for upcoming agent skills
Add validation/linting on CI for agent skills files

(cherry picked from commit 078da3cbda)
2026-02-26 18:04:30 +00:00
Joey Perrott 3d3e6cbf20 build: update repository locations for bazel rules
The repositories for `rules_sass`, `rules_angular`, and `rules_browsers`
were recently transferred from devversion to the angular organization.
This commit updates the internal bazel module rules to rely on
the new repository locations.

(cherry picked from commit b4f68acaa1)
2026-02-26 17:57:15 +00:00
Angular Robot 2158bf8bb8 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-26 09:44:02 -08:00
Matthieu Riegler 1b46b6db18 docs: add info around route level services
fixes #67313

(cherry picked from commit 95f12c87aa)
2026-02-26 16:20:02 +00:00
Matthieu Riegler f23f3e7f5f docs: add mention of identity tracking for field iterations
related to #66711

(cherry picked from commit 7d92d4454b)
2026-02-26 15:52:58 +00:00
Angular Robot 9414da5b7e build: update all github actions
See associated pull request for more information.
2026-02-26 07:50:54 -08:00
Angular Robot 8078abe893 build: update bazel dependencies
See associated pull request for more information.
2026-02-26 07:50:17 -08:00
Joey Perrott 661e800302 release: bump VSCode extension version to 21.2.1 2026-02-25 15:34:03 -08:00
Angular Robot c960a6e492 build: update cross-repo angular dependencies to v21.2.0
See associated pull request for more information.
2026-02-25 15:11:49 -08:00
Angular Robot 2eeb4b5666 build: update cross-repo angular dependencies to v21.2.0
See associated pull request for more information.
2026-02-25 12:47:33 -08:00
Hien 8f19cf8d84 docs(forms): fix casing of 'FormField' to 'formField' in templates
(cherry picked from commit de0cc817cd)
2026-02-25 20:46:06 +00:00
Jessica Janiuk 50a9ed190d release: cut the v21.2.0 release 2026-02-25 11:29:08 -08:00
Angular Robot e437980659 build: update cross-repo angular dependencies
See associated pull request for more information.

Closes #67275 as a pr takeover
2026-02-25 10:26:00 -08:00
Angular Robot d3ab1ea5f2 build: lock file maintenance
See associated pull request for more information.
2026-02-25 08:52:48 -08:00
Sonu Kapoor fe7b26e403 docs(forms): transformedValue parse error wiring
(cherry picked from commit 547ed65b6f)
2026-02-25 16:13:16 +00:00
SkyZeroZx 70e4c7fbab refactor(common): log a warning when a KeyValuePipe receives a signal
Add signal warning for `KeyValuePipe` and consolidates the `invalidPipeArgumentError` function into a `utils`

(cherry picked from commit 97ed3d9e85)
2026-02-25 16:11:34 +00:00
Angular Robot a25f74238e build: update jasmine dependencies to v6.1.0
See associated pull request for more information.
2026-02-25 07:57:47 -08:00
Angular Robot 61ad0f8b90 build: update pnpm to v10.30.2
See associated pull request for more information.
2026-02-25 07:54:45 -08:00
Matthieu Riegler a86c3214c2 ci: update pullapprove for manual_api docs
The markdown files shouldn't require dev-infra approval.

(cherry picked from commit 0cd00b91ef)
2026-02-24 23:23:34 +00:00
Anushka Geeta Singh 706cfecaee docs: update README to clarify app setup confirmation
Improved wording to clarify how beginners can verify their environment and IDE are correctly set up after running the app.

(cherry picked from commit bdbb7cc7af)
2026-02-24 22:22:32 +00:00
Angular Robot 63cb3f4fa8 build: update bazel dependencies
See associated pull request for more information.
2026-02-24 14:10:29 -08:00
Matthieu Riegler 787cd875b0 refactor(vscode-extension): Add support for exhaustive type check in the syntax
`@default never` will be considered a keyword on its own.

(cherry picked from commit db11e74b3d)
2026-02-24 19:23:50 +00:00
Matthieu Riegler 1a151f2dae docs: add docs for @switch Exhaustive type checking
(cherry picked from commit 9dc3ca44ec)
2026-02-24 18:53:47 +00:00
Munir Issa Said 6b112e6c6c docs: fix roadmap introduction text
(cherry picked from commit 035b151a17)
2026-02-24 18:52:38 +00:00
Doug Parker 7d58b798c6 fix(core): block creation of sensitive URI attributes from ICU messages
Translators are not allowed to write HTML which creates URI attributes. I opted to ban any values going into an attribute at all, to prevent even links to malicious content, rather than just sanitizing URIs.

I also converted this blocklist into an allowlist. Now, we only allowing setting known attributes (while sanitizing URI attributes). This significantly reduces risk of missing a vulnerable attribute and does not require an exhaustive list of all potential attributes.

BREAKING CHANGE: Angular now only applies known attributes from HTML in translated ICU content. Unknown attributes are dropped and not rendered.

(cherry picked from commit 306f367899)
2026-02-24 18:50:41 +00:00
Alan Agius ab1c84eed9 docs: consolidate validation rules for Host and X-Forwarded-Host headers into a single point.
(cherry picked from commit c57ba8c6e6)
2026-02-24 17:24:34 +00:00
Matthieu Riegler 103a44f690 docs: improve responsiveness of the homepage
This fixes some layout issues when on mobile.

(cherry picked from commit 9e0acc5930)
2026-02-24 17:23:43 +00:00
Shuaib Hasan Akib de9092eb23 docs: inline documentation and remove unused references
Replaces referenced doc blocks with inline documentation,
cleans up unused referenced code, and fixes broken list formatting

(cherry picked from commit 8739a22d28)
2026-02-24 17:22:54 +00:00
Jens Kuehlers 0b0b34565a docs: minor roadmap update (Ecosystem/vitest)
(cherry picked from commit dea858979e)
2026-02-24 17:22:13 +00:00
Angular Robot b17a7e062f build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-24 09:21:23 -08:00
Shuaib Hasan Akib 7c976c3453 docs: remove inconsistent padding in update page
Removes extra padding applied to the update page so its layout
matches the rest of the documentation where no padding exists
between the title and content.

(cherry picked from commit 15b51d0777)
2026-02-24 17:20:55 +00:00
SkyZeroZx eb6bce0ff5 docs: Add image loader config with height parameter
(cherry picked from commit c7dc7054dc)
2026-02-23 23:30:35 +00:00
Suguru Inatomi 30e486a41c fix(docs-infra): use shared heading ID generation logic
This commit extracts the heading ID generation logic into a shared utility
and updates both the route generation script and the markdown pipeline to use it.

This ensures consistency between the generated routes and the rendered
documentation, and fixes an issue where custom heading IDs (`{#id}`)
were ignored during route generation.

Fixes #67200

(cherry picked from commit bedfcb5644)
2026-02-23 23:16:55 +00:00
Matthieu Riegler a73d5435d8 docs: Add a compiler entry to the roadmap
(cherry picked from commit fbabd092f3)
2026-02-23 23:11:30 +00:00
marktechson b9d3fe3b44 docs: update and reformat the roadmap
(cherry picked from commit 53c1a1cee7)
2026-02-23 21:28:43 +00:00
Angular Robot c9ecf9e40b build: update cross-repo angular dependencies to v21.2.0-rc.1
See associated pull request for more information.
2026-02-23 13:17:58 -08:00
Shuaib Hasan Akib 2f213de593 refactor(core): remove outdated TODO comments
Cleans up TODO comments that are no longer planned

Fixes #67191

(cherry picked from commit 5ac1c025f9)
2026-02-23 18:19:57 +00:00
Matthieu Riegler ec32591dd7 docs: mention browser mode debugging
(cherry picked from commit a9ac7f87d7)
2026-02-23 18:19:27 +00:00
Miles Malerba 02b7f5e4f7 ci: remove mmalerba from reviewers
remove mmalerba from reviewers

(cherry picked from commit 0af9e3e9ee)
2026-02-23 18:19:02 +00:00
Angular Robot 31bf694599 build: update pnpm to v10.30.1
See associated pull request for more information.
2026-02-23 09:12:54 -08:00
Miles Malerba bdfb60f3e3 fix(forms): use consistent error format returned from parse
Aligns the errors returned from the `parse` function in
`transformedValue` to use the same convention as the rest of signal
forms (a property called `error` that can contain a single error or list
of errors)

(cherry picked from commit 23fd8fa586)
2026-02-23 17:11:55 +00:00
SkyZeroZx e99b92a1f0 docs: add Route Loading Strategies section and update navigation links
(cherry picked from commit 163dd8ee38)
2026-02-23 17:11:15 +00:00
Alan Agius 5c24de5ee8 docs: move server-side security and host validation documentation from ssr.md to security.md.
(cherry picked from commit 099b4a8bbb)
2026-02-23 17:08:56 +00:00
Alan Agius fad5def327 feat(docs-infra): add hideDollar option to hide the dollar sign prefix in shell code blocks.
The dollar sign is not always required.

(cherry picked from commit efcf76ea61)
2026-02-23 17:08:56 +00:00
Alan Agius de2eab6820 docs: convert SSR guide's important note list to HTML <ul> for proper rendering.
(cherry picked from commit 75fd6c0ff2)
2026-02-23 17:08:56 +00:00
Alan Agius ea1aeb3d45 docs: add documentation for SSR security and host validation, including details on allowedHosts configuration
This document adds more information about `allowedHost` option

(cherry picked from commit 944aefe854)
2026-02-23 17:08:55 +00:00
Jaime Burgos 35f0663ce8 refactor(core): guards stringify calls with ngDevMode
The `stringify` function is only needed for debugging purposes and
should not be called in production mode.

(cherry picked from commit c89d94bd58)
2026-02-20 21:24:31 +00:00
SkyZeroZx 2eeeabb760 fix(common): fix LCP image detection with duplicate URLs
Addresses an issue where the LCP image observer incorrectly identified LCP elements when the same image URL was used multiple times on a page

Fixes #53278

(cherry picked from commit 38749698d0)
2026-02-20 21:23:27 +00:00
Jessica Janiuk 87372893ea ci: update ng-dev config and pullapprove
This updates the pullapprove to remove the auto labeling of requires: TGP and instead moves it to the ng-dev config for pr updates.

(cherry picked from commit d7ddebca90)
2026-02-20 19:02:14 +00:00
cexbrayat 631f60d1f9 fix(forms): preserve parse errors when parse returns value
Fixes #67170 by keeping the errors even a value is returned from the parse function.

(cherry picked from commit fe25c57a5c)
2026-02-20 18:28:57 +00:00
Ben Hong 390efd51e7 docs: add new debugging and troubleshooting di guide
(cherry picked from commit 13e019a1bb)
2026-02-20 18:01:16 +00:00
splincode c2cedd1954 refactor(compiler-cli): improve diagnostic with help link
Add help link to extended template diagnostic messages to provide
users with additional guidance and documentation resources. This
enhancement improves developer experience by making it easier to
understand and resolve complex template issues through direct
access to relevant Angular documentation with detailed examples
and explanations for each diagnostic type.

(cherry picked from commit 66b472e2bc)
2026-02-20 17:40:21 +00:00
Matthieu Riegler 3c4deaa52b refactor(common): log a warning when a JsonPipe receives a signal
The JsonPipe does not unwrap signals and `JSON.stringify` will return `undefined` for signals.
To avoid confusion, we log a warning when a signal is passed to the pipe.

(cherry picked from commit 0d652ba4da)
2026-02-20 17:17:05 +00:00
Matthieu Riegler b5eea233df docs: improve signal demo example
(cherry picked from commit b7cf37d3e1)
2026-02-20 17:15:37 +00:00
SkyZeroZx c0cb6040f8 fix(compiler-cli): detect uninvoked functions in defer trigger expressions
Wrap `@defer` trigger expressions (`when`, `prefetch when`, `hydrate when`)
in a conditional context within the TCB to enable TypeScript's TS2774
diagnostic for detecting functions used without invocation.

Previously, signals and functions passed to `when` triggers without
parentheses would silently evaluate to truthy, causing unexpected behavior.
Now the compiler reports an error when a function is used as a condition
without being called.

(cherry picked from commit f90e5565e0)
2026-02-20 16:57:08 +00:00
Bas ten Feld fb901f7eda docs(docs-infra): fix typo 'requiredSync' instead of 'requireSync'
(cherry picked from commit 57edfaf435)
2026-02-20 16:53:24 +00:00
Matthieu Riegler 17da2c392e docs(docs-infra): remove toString from the API docs
(cherry picked from commit fc8140cff4)
2026-02-20 16:52:01 +00:00
Angular Robot 6cf0100fe4 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-19 18:12:11 -08:00
kbrilla d2137928e8 perf(language-service): use lightweight project warmup for Angular analysis
avoid per-file semantic diagnostics work when warming up a newly loaded project.
add ensureProjectAnalyzed() to the language-service API and use it from the server startup path.
implement warmup through public compiler API access with existing perf tracing, and add legacy test coverage for the new warmup flow.

(cherry picked from commit 39f62fa408)
2026-02-20 02:11:06 +00:00
Matthieu Riegler 1d4783c01c refactor(language-service): split tests to reduce risk of timeouts
Because we've had to many timeouts recently

(cherry picked from commit 550b3ba01b)
2026-02-20 02:10:34 +00:00
SkyZeroZx 9ee99c7cc0 docs: update NgTemplateOutlet documentation with injector
(cherry picked from commit 67b22d509a)
2026-02-20 00:06:29 +00:00
Angular Robot 5b8a403220 build: update rules_browsers digest to ceb5275
See associated pull request for more information.

Closes #67141 as a pr takeover
2026-02-19 16:01:02 -08:00
SkyZeroZx 9c8f2dab7d docs: add section on reactive context and async operations
(cherry picked from commit f68078b3e3)
2026-02-19 23:59:59 +00:00
SkyZeroZx 9f1a78ca74 docs(docs-infra): preserve content before docs-card-container in adev
The tokenizer regex pattern `[^<]*` was consuming all non-`<` content
before custom HTML tags, causing lost content.

Changed to `\s*` which only allows leading whitespace, letting marked
properly tokenize preceding content.

(cherry picked from commit bc473cf60f)
2026-02-19 23:48:49 +00:00
Angular Robot d45440eda4 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-19 13:31:08 -08:00
Matthieu Riegler eb7c593fda ci: add zone.js to minimumReleaseAgeExclude
This is a package we control the release of.

(cherry picked from commit d501506aa6)
2026-02-19 20:41:24 +00:00
Walter Breakell 48802fd7ed docs: fix grammar, syntax, and broken link in elements guide
(cherry picked from commit 7d21996b1e)
2026-02-19 20:39:04 +00:00
Joey Perrott 5cea3576cf docs: update changelog to note no changes in 21.1.5 release
(cherry picked from commit e8cc910965)
2026-02-19 19:23:48 +00:00
Angular Robot 913bb6ad14 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-19 11:22:42 -08:00
Angular Robot 79f1cd462e docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-02-19 09:03:46 -08:00
SkyZeroZx 4ea3cafdc8 docs(docs-infra): fixed missing gray color palette definitions for light and dark modes
Adds the missing gray color palette definitions for both light and dark modes

(cherry picked from commit 1771d54fb7)
2026-02-19 16:55:02 +00:00
Angular Robot 94b9a3ef70 build: update all github actions
See associated pull request for more information.
2026-02-19 08:52:00 -08:00
Angular Robot c5afb88d9a build: update cross-repo angular dependencies to v21.2.0-rc.0
See associated pull request for more information.
2026-02-19 08:51:02 -08:00
Andrew Scott f58f9c892e release: cut the zone.js-0.16.1 release
(cherry picked from commit ac8b5ff938)
2026-02-18 16:26:41 -08:00
Andrew Scott 7f33cbeb6c release: bump VSCode extension version to 21.2.0 (#67134) 2026-02-18 15:48:34 -08:00
Andrew Scott dd630ef955 build: update version target to prep for 21.2.0 release for vscode extension (#67132)
build: update version target to prep for 21.2.0 release for vscode extension
2026-02-18 15:14:45 -08:00
Matthew Beck 13fd2b1aa3 release: cut the v21.2.0-rc.0 release 2026-02-18 14:38:30 -08:00
4423 changed files with 213560 additions and 302114 deletions
+1
View File
@@ -0,0 +1 @@
../.gemini/skills
-178
View File
@@ -1,178 +0,0 @@
---
name: adev-writing-guide
description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. You MUST use this skill any time you plan to create, edit, or review documentation files in `adev/` or `adev/src/content`.
---
# Angular Documentation (adev) Writing Guide
This skill provides comprehensive guidelines for authoring content in `adev/src/content`. It combines Google's technical writing standards with Angular-specific markdown conventions, components, and best practices.
## I. Google Technical Writing Guidelines
### Tone and Content
- **Be conversational and friendly:** Maintain a helpful yet professional tone. Avoid being overly casual.
- **Write accessibly:** Ensure documentation is understandable to a diverse global audience, including non-native English speakers.
- **Audience-first:** Focus on what the user needs to do, not just what the system does.
- **Avoid pre-announcing:** Do not mention unreleased features or make unsupported claims.
- **Use descriptive link text:** Link text should clearly indicate the destination (e.g., avoid "click here").
### Language and Grammar
- **Use second person ("you"):** Address the reader directly.
- **Prefer active voice:** Clearly state who or what is performing the action (e.g., "The system generates a token" vs "A token is generated").
- **Standard American English:** Use standard American spelling and punctuation.
- **Conditional clauses first:** Place "if" or "when" clauses before the instruction (e.g., "If you encounter an error, check the logs").
- **Define terms:** Introduce new or unfamiliar terms/acronyms upon first use.
- **Consistent terminology:** Use the same term for the same concept throughout the document.
- **Conciseness:** Aim for one idea per sentence. Keep sentences short.
### Formatting and Organization
- **Sentence case for headings:** Capitalize only the first word and proper nouns in titles and headings.
- **Lists:**
- **Numbered lists:** Use for sequential steps or prioritized items.
- **Bulleted lists:** Use for unordered collections of items.
- **Description lists:** Use for term-definition pairs.
- **Serial commas:** Use the Oxford comma (comma before the last item in a list of three or more).
- **Code formatting:** Use code font for code-related text (filenames, variables, commands).
- **UI Elements:** formatting user interface elements in **bold**.
- **Date formatting:** Use unambiguous formats (e.g., "September 4, 2024" rather than "9/4/2024").
- **Structure:** Use logical hierarchy with clear introductions and navigation. Headings should be task-based where possible.
### Images and Code Samples
- **Images:** Use simple, clear illustrations to enhance understanding.
- **Captions:** Write captions that support the image.
- **Code Samples:**
- Ensure code is correct and builds without errors.
- Follow language-specific conventions.
- **Comments:** Focus on _why_, not _what_. Avoid commenting on obvious code.
### Reference Hierarchy
1. Project-specific style guidelines (if any exist in `CONTRIBUTING.md` or similar).
2. Google Developer Documentation Style Guide.
3. Merriam-Webster (spelling).
4. Chicago Manual of Style (non-technical).
5. Microsoft Writing Style Guide (technical).
---
## II. Angular Documentation Specifics
### Code Blocks
Use the appropriate language identifier for syntax highlighting:
- **TypeScript (Angular):** Use `angular-ts` when TypeScript code examples contain inline templates.
- **HTML (Angular):** Use `angular-html` for Angular templates.
- **TypeScript (Generic):** Use `ts` for plain TypeScript.
- **HTML (Generic):** Use `html` for plain HTML.
- **Shell/Terminal:** Use `shell` or `bash`.
- **Mermaid Diagrams:** Use `mermaid`.
#### Attributes
You can enhance code blocks with attributes in curly braces `{}` after the language identifier:
- `header="Title"`: Adds a title to the code block.
- `linenums`: Enables line numbering.
- `highlight="[1, 3-5]"`: Highlights specific lines.
- `hideCopy`: Hides the copy button.
- `prefer`: Marks code as a preferred example (green border/check).
- `avoid`: Marks code as an example to avoid (red border/cross).
**Example:**
````markdown
```angular-ts {header:"My Component", linenums, highlight="[2]"}
@Component({
selector: 'my-app',
template: '<h1>Hello</h1>',
})
export class App {}
```
````
#### `<docs-code>` Component
For more advanced code block features, use the `<docs-code>` component:
- `path`: Path to a source file (e.g., `adev/src/content/examples/...`).
- `header`: Custom header text.
- `language`: Language identifier (e.g., `angular-ts`).
- `linenums`: Boolean attribute.
- `highlight`: Array of line numbers/ranges (e.g., `[[3,7], 9]`).
- `diff`: Path to diff file.
- `visibleLines`: Range of lines to show initially (collapsible).
- `region`: Region to extract from source file.
- `preview`: Boolean. Renders a live preview (StackBlitz). _Only works with standalone examples._
- `hideCode`: Boolean. Collapses code by default.
**Multifile Example:**
```html
<docs-code-multifile path="..." preview>
<docs-code path="..." />
<docs-code path="..." />
</docs-code-multifile>
```
### Alerts / Admonitions
Use specific keywords followed by a colon for alerts. These render as styled blocks.
- `NOTE:` For ancillary information.
- `TIP:` For helpful hints or shortcuts.
- `IMPORTANT:` For crucial information.
- `CRITICAL:` For warnings about potential data loss or severe issues.
- `TODO`: For incomplete documentation.
- `QUESTION:` To pose a question to the reader.
- `SUMMARY:` For section summaries.
- `TLDR:` For concise summaries.
- `HELPFUL:` For best practices.
**Example:**
```markdown
TIP: Use `ng serve` to run your application locally.
```
### Custom Components
- **Cards (`<docs-card>`):**
- Must be inside `<docs-card-container>`.
- Attributes: `title`, `link`, `href`.
- **Callouts (`<docs-callout>`):**
- Attributes: `title`, `important`, `critical`.
- **Pills (`<docs-pill>`):**
- Must be inside `<docs-pill-row>`.
- Attributes: `title`, `href`.
- **Steps / Workflow (`<docs-step>`):**
- Must be inside `<docs-workflow>`.
- Attributes: `title`.
- **Tabs (`<docs-tab>`):**
- Must be inside `<docs-tab-group>`.
- Attributes: `label`.
- **Videos (`<docs-video>`):**
- Attributes: `src` (YouTube embed URL), `alt`.
### Images
Use standard markdown syntax with optional attributes for sizing and loading behavior.
- `#small`, `#medium`: Append to image URL for sizing.
- `{loading: 'lazy'}`: Add attribute for lazy loading.
**Example:**
```markdown
![Alt Text](path/to/image.png#medium {loading: 'lazy'})
```
### Headers
- Use markdown headers (`#`, `##`, `###`).
- Ensure a logical hierarchy (don't skip levels).
- `h2` and `h3` are most common for content structure.
-135
View File
@@ -1,135 +0,0 @@
---
name: PR Review
description: Guidelines and tools for reviewing pull requests in the Angular repository.
---
# PR Review Guidelines
When reviewing a pull request for the `angular` repository, follow these essential guidelines to ensure high-quality contributions:
1. **Context & Ecosystem**:
- Keep in mind that this is the core Angular framework. Changes here can impact millions of developers.
- Be mindful of backwards compatibility. Breaking changes require strict approval processes and deprecation periods.
2. **Key Focus Areas**:
- **Comprehensive Reviews**: You **MUST always** perform a deep, comprehensive review of the _entire_ pull request. If the user asks you to look into a specific issue, file, or area of concern, you must investigate that specific area _in addition to_ reviewing the rest of the PR's substantive changes. Do not terminate your review after addressing only the user's focal point.
- **Package-Specific & Topic Guidelines**: Check if there are specific guidelines for the package or topic being modified in the `reference/` directory (e.g., `reference/router.md` or `reference/object_create_null.md`). Always prioritize these rules for their respective areas.
- **Prototype Collision & `Object.create(null)` PRs**: When reviewing PRs that swap `{}` for `Object.create(null)`, consult `reference/object_create_null.md` for technical evaluation criteria and rules.
- **Commit Messages**: Evaluate the quality of commit messages. They should explain the _why_ behind the change, not just the _what_. Someone should be able to look at the commit history years from now and clearly understand the context and reasoning for the change.
- **Code Cleanliness**: Ensure the code is readable, maintainable, and follows Angular's project standards.
- **Performance**: Look out for code that might negatively impact runtime performance or bundle size, particularly in hot paths like change detection or rendering.
- **Testing**: Ensure all new logic has comprehensive tests, including edge cases. **Do NOT run tests locally** as part of your review process. CI handles this automatically, and running tests locally is redundant and inefficient.
- **API Design**: Ensure new public APIs are well-designed, consistent with existing APIs, and properly documented.
- **Payload Size**: Pay attention to the impact of changes on the final client payload size.
3. **Execution Workflow**:
Determine the appropriate review method. If the user explicitly asks for a `remote` or `local` review in their request, that takes precedence (e.g. "leave comments on the PR" implies `remote`). Otherwise, use the GitHub MCP or available scripts to determine if the review should be `local` or `remote`.
**Common Review Practices (Applies to both Local and Remote)**
- **Preparation & Checklist**:
- First, create a task list (e.g., in `task.md`) that you can easily reference containing **all** the review requirements from the "Key Focus Areas" section (Commit Messages, Performance, Testing, etc.), along with any specific review notes or requests from the user.
- Before doing an in-depth review, expand this list into more detailed items of what you plan to explore and verify in the PR.
- As you conduct the review, check off items in this list, adding your assessment or findings underneath each item.
- At the end of your review, refer back to the checklist to ensure every single requirement was completely verified.
- **Fetch PR Metadata Safely**: When you need to read the PR description or context, do NOT use `gh pr view <PR_NUMBER>` by itself, as its default GraphQL query may fail due to lack of `read:org` and `read:discussion` token scopes. Instead, use `read_url_content` on the PR URL or use `gh pr view <PR_NUMBER> --json title,body,state,author`.
- **Check Existing Comments First**: Before formulating feedback, use the GitHub MCP or available scripts to fetch existing comments on the PR. Review this feedback to avoid duplicate comments, and incorporate its insights into your own review process.
- **Constructive Feedback**: Provide clear, actionable, and polite feedback. Explain the _why_ behind your suggestions or edits. Do **NOT** leave inline comments purely to praise, agree with, or acknowledge a correct implementation detail, as this clutters the review. If you want to praise the PR, do so in the single general PR comment.
**A. Local Code Review (If the PR is owned by the author requesting the review)**
- **Checkout**: Check out the PR branch locally (if it doesn't already exist, fetch it). If checking out the branch fails due to a worktree claim (e.g. "fatal: '<branch>' is already used by worktree at '<path>'"), do the review in that directory.
- **Review & Edit**: Execute the review directly on the code. Instead of adding inline PR comments for suggestions, format the codebase or apply the edits directly to the files.
- **Feedback**: Summarize the review findings and the concrete changes you made in a message to the user, referencing the completed items from your checklist.
- **Do NOT Commit or Push**: Leave the changes uncommitted in the working directory so the user can easily review the pending edits locally. Let the user know the changes are ready for their review, but do not ask for approval to push.
- **Resolve Comments**: Once the user confirms the changes are good and should be committed/pushed, respond to the existing comments as 'resolved' using the GitHub MCP or available scripts.
**B. Remote Code Review (For all other PRs)**
- **Batching Comments (MCP Server - Preferred)**: If you have the GitHub MCP Server configured, you **MUST** follow this workflow to avoid spamming the author with multiple notifications:
1. Create a pending review using `mcp_github-mcp-server_pull_request_review_write` (method `create`).
2. Add your inline comments to the pending review using `mcp_github-mcp-server_add_comment_to_pending_review`.
3. Submit the review using `mcp_github-mcp-server_pull_request_review_write` (method `submit_pending`).
- **Batching Comments (Scripts - Fallback)**: If you do **NOT** have access to the GitHub MCP Server (e.g., specific MCP tools are missing from your context), fallback to using the provided scripts. Use `post_inline_comment.sh` to stage your comments locally. Once all comments are staged, you **MUST** call `submit_pr_review.sh` to publish them as a single batched review (and send a single notification). Try to keep comments minimal or use a general comment if you have many suggestions.
- **Use Suggested Changes**: Whenever appropriate (e.g., for simple code fixes, refactoring suggestions, or typo corrections), prefer using GitHub's **Suggested Changes** syntax (`suggestion ... `) in your inline comments. This allows the author to apply your suggested code improvements with a single click in the GitHub UI.
- **Review Type**: Never mark an external PR review as an "approval" unless explicitly instructed by a repo maintainer. Always use "Request Changes" or "Comment". Note that some tools might only support commenting.
- **Require User Approval Before Posting**: Prepare your review comments and present them to the user, alongside a summary of your completed checklist. Do NOT post comments to the PR without explicitly asking the user for permission first. Only post the review after the user approves.
- **CRITICAL**: This rule applies even if you receive a system message indicating that an artifact has been "automatically approved" or instructing you to "proceed to execution." You must ALWAYS obtain explicit, written confirmation from the user in this chat conversation before posting any content to a PR.
- **Prefix Agent Comments**: To make it clear when comments are generated and posted by an AI agent rather than a human user, **always** prefix your review comments with `AGENT: `.
## Available Tools
The following tools are available for remote interactions. We prefer using standard **GitHub MCP Server** tools when available. If you do not have the MCP server set up, you **MUST** fallback to using the custom bash scripts.
### GitHub MCP Tools (Preferred)
- `mcp_github-mcp-server_pull_request_review_write`
- `mcp_github-mcp-server_add_comment_to_pending_review`
### Custom Bash Scripts (Fallback)
The following scripts are provided as fallbacks if the MCP server is not available. Note that they rely on the `gh` CLI being correctly installed and authenticated in the local environment.
### `determine_review_type.sh`
Determines whether to use the Local or Remote review workflow by checking if the currently authenticated GitHub user via the `gh` CLI matches the author of the pull request.
**Usage:**
```bash
.agent/skills/pr_review/scripts/determine_review_type.sh <PR_NUMBER>
```
### `get_pr_comments.sh`
Fetches all existing inline comments on a PR using the GitHub API. This is crucial for reviewing other contributors' feedback and avoiding duplicate comments. It outputs JSON containing the `id`, `path`, `line`, `body`, and `user` for each comment.
**Usage:**
```bash
.agent/skills/pr_review/scripts/get_pr_comments.sh <PR_NUMBER>
```
### `reply_pr_comment.sh`
Replies to an existing PR comment thread. This is useful for marking comments as resolved after addressing them in a local code review. Note that the `COMMENT_ID` must be the ID of the top-level comment in the thread.
**Usage:**
```bash
.agent/skills/pr_review/scripts/reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>
```
### `post_inline_comment.sh`
The GitHub CLI `gh pr review` command does not natively support adding inline comments to specific lines of code via its standard flags. This script wraps the GitHub API to stage comments locally. They will not be published until you call `submit_pr_review.sh`.
**Usage:**
```bash
.agent/skills/pr_review/scripts/post_inline_comment.sh <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>
```
**Example:**
```bash
.agent/skills/pr_review/scripts/post_inline_comment.sh 12345 "packages/core/src/render3/instructions/element.ts" 42 "AGENT: Consider the performance implications here."
```
### `submit_pr_review.sh`
Submits all locally staged inline comments as a single batched review via the GitHub Pull Request Reviews API.
**Usage:**
```bash
.agent/skills/pr_review/scripts/submit_pr_review.sh <PR_NUMBER> <EVENT_TYPE> [BODY]
```
**Options:**
- `EVENT_TYPE`: Must be `COMMENT`, `APPROVE`, or `REQUEST_CHANGES`. Never use `APPROVE` for external PRs.
- `BODY`: (Optional) A general summary comment for the review.
**Example:**
```bash
.agent/skills/pr_review/scripts/submit_pr_review.sh 12345 COMMENT "AGENT: I have left a few inline suggestions for your consideration."
```
@@ -1,38 +0,0 @@
# Rules for `Object.create(null)` and Prototype Collision Prevention
This guide outlines the technical rules and evaluation criteria for using `Object.create(null)` versus standard object literals (`{}`) or `Map` in the Angular codebase.
---
## 1. When `Object.create(null)` is Appropriate
Using `Object.create(null)` (or `Map`) is appropriate when **all** of the following conditions are met:
1. The object is used as an **internal key-value lookup map or set**.
2. The keys are **arbitrary or untrusted dynamic strings** (e.g., URL query parameters in `$locationShim`, HTML sanitizer tag sets, or `jsaction` DOM event-type resolvers).
3. Property existence is checked via direct indexing or key checks (e.g., `map[key] !== undefined` or `key in map`), where a key matching an `Object.prototype` member (such as `'toString'`, `'constructor'`, or `'hasOwnProperty'`) causes false positive matches or incorrect behavior.
---
## 2. Handling Public API and Boundary Objects
If an object receives untrusted dynamic keys **and** is exposed to public consumers or third-party code (e.g., `SimpleChanges` in `ngOnChanges`):
- **Do NOT blindly change the object to `Object.create(null)`**: Stripping `Object.prototype` from public objects is a breaking API change. Consumer code calling `.hasOwnProperty()`, `.toString()`, `.valueOf()`, or using string interpolation (`` `${obj}` ``) will fail at runtime (`TypeError: obj.hasOwnProperty is not a function`).
- **Safe Alternatives for Public Objects:**
- **`Object.hasOwn(obj, key)`**: Use `Object.hasOwn` for internal framework property lookups instead of direct index or `in` checks. This prevents prototype collision during internal reads without breaking the object's prototype for consumers.
- **Input Key Sanitization**: Filter or delete dangerous key names (`__proto__`, `constructor`, `prototype`) when populating the object.
- **`Map` or Custom Classes**: For new public APIs requiring key-value stores with dynamic keys, prefer `Map<K, V>` or dedicated classes with explicit `.get()` and `.has()` methods.
- **Deprecation / Breaking Change Process**: If changing a public object's prototype to `null` is unavoidable, it must follow Angular's formal deprecation and major version breaking change process.
---
## 3. When `Object.create(null)` Should NOT Be Used
Do not replace `{}` with `Object.create(null)` in the following scenarios:
1. **Fixed-Shape Structs and DTOs:** Objects with hardcoded static property names (e.g., `let sortedBreakpoints: {breakpoints?: number[]} = {}`). `Object.assign({}, ...)` only copies _own_ enumerable properties, so prototype properties on sources are never copied.
2. **Numeric-Key Maps:** Objects indexed by numbers (e.g., `tasksByHandleId: {[id: number]: Task}`). Numeric keys do not collide with `Object.prototype` string members.
3. **Reference Sentinels:** Objects used purely for reference identity checks (e.g., `const EMPTY_OBJECT = {}` or `const IN_PROGRESS_RESOLUTION = {}`).
4. **Internal Compiler AST and Visitor State:** Temporary objects with internally generated keys where untrusted user input cannot poison key names.
5. **Hot Performance Paths and Size-Critical Bundles:** Standard `{}` literals use V8 fast hidden classes and monomorphic inline caching. `Object.create(null)` forces V8 dictionary mode and increases minified bundle size (e.g., in inline polyfills like `event-dispatch-contract` or SSR hydration bundles).
@@ -1,7 +0,0 @@
# Router PR Review Guidelines
When reviewing pull requests that modify the Angular Router (`packages/router`), pay special attention to the following:
- **Timing Sensitivity**: The router is extremely sensitive to timing changes. Any changes that alter the asynchronous timing of navigations, resolvers, or guards are almost always breaking changes and must be scrutinized carefully.
- **Testing Practices**: Tests should usually use the `RouterTestingHarness`. Many existing tests are older and do not use this harness. Do not blindly follow the shape of existing tests when writing or reviewing new ones; encourage the use of modern testing utilities.
- **Feature Justification**: Changes to router core code should be well-justified. Consider whether the change is proven to be a core developer ask, such as resolving a highly upvoted GitHub issue or addressing a critical bug.
@@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# determine_review_type.sh <PR_NUMBER>
# Determines if the PR should be reviewed locally or remotely based on author.
if [ -z "$1" ]; then
echo "Usage: determine_review_type.sh <PR_NUMBER>"
exit 1
fi
PR_NUMBER=$1
# Get current authenticated user
CURRENT_USER=$(gh api user -q .login 2>/dev/null)
if [ $? -ne 0 ]; then
echo "Error: Could not determine current GitHub user. Are you logged in to gh?"
exit 1
fi
# Get PR author
PR_AUTHOR=$(gh pr view "$PR_NUMBER" --json author -q .author.login 2>/dev/null)
if [ $? -ne 0 ]; then
echo "Error: Could not retrieve PR information for $PR_NUMBER."
exit 1
fi
if [ "$CURRENT_USER" = "$PR_AUTHOR" ]; then
echo "local"
else
echo "remote"
fi
@@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# get_pr_comments.sh
# Fetches existing inline comments on a PR to avoid duplicate reviews.
# Usage: ./get_pr_comments.sh <PR_NUMBER>
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <PR_NUMBER>"
exit 1
fi
PR_NUMBER="$1"
# Ensure gh cli is installed
if ! command -v gh &> /dev/null; then
echo "Error: gh CLI could not be found. Please install and authenticate."
exit 1
fi
# Get the current repository (e.g., angular/angular)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# Fetch comments
gh api \
--paginate \
-H "Accept: application/vnd.github+json" \
"/repos/${REPO}/pulls/${PR_NUMBER}/comments" \
--jq '.[] | {id: .id, path: .path, line: .line, body: .body, user: .user.login}'
@@ -1,38 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# post_inline_comment.sh
# Adds an inline comment to a specific line in a PR via the GitHub API.
# Usage: ./post_inline_comment.sh <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>
if [ "$#" -lt 4 ]; then
echo "Usage: $0 <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>"
exit 1
fi
PR_NUMBER="$1"
FILE_PATH="$2"
LINE="$3"
BODY="$4"
# Ensure gh cli is installed
if ! command -v gh &> /dev/null; then
echo "Error: gh CLI could not be found. Please install and authenticate."
exit 1
fi
# Get the current repository (e.g., angular/angular)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
echo "Staging inline comment for PR #${PR_NUMBER} on ${FILE_PATH}:${LINE}..."
COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json"
if [ ! -f "$COMMENT_FILE" ]; then
echo "[]" > "$COMMENT_FILE"
fi
# Append the new comment to the JSON array
jq --arg path "${FILE_PATH}" --argjson line "${LINE}" --arg body "${BODY}" \
'. += [{"path": $path, "line": $line, "body": $body}]' "$COMMENT_FILE" > "${COMMENT_FILE}.tmp" && mv "${COMMENT_FILE}.tmp" "$COMMENT_FILE"
echo "Comment successfully staged locally. Remember to call submit_pr_review.sh when finished to publish all comments as a single review!"
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>
# Replies to an existing PR comment thread. Note: COMMENT_ID must be the ID of the top-level comment in the thread you are replying to.
if [ "$#" -lt 3 ]; then
echo "Usage: reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>"
exit 1
fi
PR_NUMBER="$1"
COMMENT_ID="$2"
BODY="$3"
# Ensure gh cli is installed
if ! command -v gh &> /dev/null; then
echo "Error: gh CLI could not be found. Please install and authenticate."
exit 1
fi
# Get the current repository (e.g., angular/angular)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# Reply to the thread using the provided comment ID
gh api \
--silent \
--method POST \
-H "Accept: application/vnd.github+json" \
"/repos/${REPO}/pulls/${PR_NUMBER}/comments/${COMMENT_ID}/replies" \
-f body="$BODY"
@@ -1,50 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# submit_pr_review.sh
# Submits a batched PR review using comments previously staged by post_inline_comment.sh
# Usage: ./submit_pr_review.sh <PR_NUMBER> <EVENT_TYPE> [BODY]
# EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <PR_NUMBER> <EVENT_TYPE> [BODY]"
echo "EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES"
exit 1
fi
PR_NUMBER="$1"
EVENT="$2"
BODY="${3:-}"
COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json"
if ! command -v gh &> /dev/null; then
echo "Error: gh CLI could not be found. Please install and authenticate."
exit 1
fi
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# Check if there are staged comments
COMMENTS="[]"
if [ -f "$COMMENT_FILE" ]; then
COMMENTS=$(cat "$COMMENT_FILE")
fi
echo "Submitting review for PR #${PR_NUMBER}..."
# Create the payload
PAYLOAD_FILE="/tmp/angular_pr_${PR_NUMBER}_payload.json"
jq -n --arg event "$EVENT" --arg body "$BODY" --argjson comments "$COMMENTS" \
'{event: $event, body: $body, comments: $comments}' > "$PAYLOAD_FILE"
# Post the review using the GitHub Pull Request Reviews API
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
--input "$PAYLOAD_FILE"
echo "Review submitted successfully!"
rm -f "$COMMENT_FILE"
rm -f "$PAYLOAD_FILE"
@@ -1,104 +0,0 @@
---
name: reference-signal-forms
description: Explains the mental model and architecture of the code under `packages/forms/signals`. You MUST use this skill any time you plan to work with code in `packages/forms/signals`
---
# Signal Forms Architecture
The `packages/forms/signals` directory contains the signal-based forms API for Angular.
This system differs significantly from the existing Reactive and Template-driven forms.
## Mental Model
1. **Model-Driven**: The system is built around a `WritableSignal<T>` which serves as the **single source of truth**.
Unlike Reactive Forms where the `FormControl` holds the value, here the `Signal` holds the value.
The form is merely a _view_ or _projection_ of that signal, adding form-specific state (validity, dirty, touched).
2. **Proxy-Based Traversal**: The form API (`form(signal)`) returns a `FieldTree`. This object is a **Proxy**.
It allows accessing nested fields (e.g., `myForm.user.name`) without manually creating control groups.
Accessing a property on the proxy lazily resolves or creates the corresponding `FieldNode`.
3. **Schema-Based Logic**: Validation, disabled state, and other metadata are defined separately via **Schemas**.
Schemas are applied to the form structure using functions like `apply`, `applyEach` (for arrays), and `applyWhen`.
This separates the _structure_ of the data from the _rules_ governing it.
4. **Directives as Glue**: The `[formField]` directive binds a DOM element (native input or custom control) to a `FieldNode`.
It handles:
- Syncing the value between the DOM and the Signal.
- Reflecting state (valid, touched, etc.) to the UI.
- Handling user interaction events (blur, input).
## Key Components
### 1. `FieldNode` (`src/field/node.ts`)
The central internal class representing a single field in the form graph. It aggregates several state managers:
- `structure`: Manages parent/child relationships and signal slicing.
- `validationState`: Computes `valid`, `invalid`, `errors` signals.
- `nodeState`: Tracks `touched`, `dirty`, and derived logical state.
- `metadataState`: Stores metadata like `min`, `max`, `required`.
- `submitState`: Tracks submission status and server errors.
### 2. `FieldValidationState` (`src/field/validation.ts`)
Implements `ValidationState` and manages the complexity of validation:
- **Synchronous Errors**: Derived from schema rules.
- **Asynchronous Errors**: Handled via signals, including 'pending' states.
- **Tree Errors**: Errors that bubble up or are targeted at specific fields.
- **Submission Errors**: Server-side errors injected imperatively via `submit()`.
### 3. `FormField` Directive (`src/directive/form_field.ts`)
The bridge between the `FieldNode` and the DOM.
- Selector: `[formField]`
- It supports:
- **Native Elements**: `<input>`, `<select>`, `<textarea>`.
- **Custom Controls**: Components implementing `FormValueControl` or `FormCheckboxControl`.
- **Legacy Interop**: Components implementing `ControlValueAccessor` (via `InteropNgControl`).
### 4. `Schema` (`src/api/structure.ts` & `src/api/rules`)
Defines the behavior.
- Created via `schema(fn)`.
- Applied via `apply(path, schema)`.
- Rules include validators (`required`, `pattern`, `min`, `max`) and state modifiers (`disabled`, `hidden`).
## Data Flow
1. **Read**: `form.field().value()` reads directly from the underlying signal (projected to the specific path).
2. **Write**: Writing to the form (e.g., via UI) updates the underlying signal.
3. **Validation**: A computed effect observes the value signal and runs validators defined in the schema.
## Usage Example (Conceptual)
```typescript
// 1. Define Model
const user = signal({name: '', age: 0});
// 2. Define Schema
const userRules = schema((u) => {
required(u.name);
min(u.age, 18);
});
// 3. Create Form
const userForm = form(user, userRules); // OR apply(userForm, userRules)
// 4. Bind in Template
// <input [formField]="userForm.name">
```
## Important Files
- `packages/forms/signals/src/api/structure.ts`: Public API entry points (`form`, `apply`).
- `packages/forms/signals/src/api/control.ts`: Interfaces for custom controls (`FormUiControl`).
- `packages/forms/signals/src/field/node.ts`: The `FieldNode` implementation.
- `packages/forms/signals/src/directive/form_field.ts`: The `[formField]` directive.
## Supplemental Information
- [Compiler & Core Integration](references/integration.md): Details how `[formField]` hooks into type-checking and the runtime.
@@ -1,46 +0,0 @@
# Signal Forms Integration: Compiler & Core
This document explains how the Signal Forms system hooks into the Angular compiler and runtime to provide seamless type-checking and efficient updates.
## 1. Compiler Integration (Type Checking)
The `packages/compiler` package contains specific logic to support `[formField]`. This is primarily handled in `src/typecheck/ops/signal_forms.ts`.
### Key Mechanisms:
- **Detection**: The compiler identifies a directive as a "Field Directive" if it has the `ɵNgFieldDirective` property or comes from `@angular/forms/signals`.
- **Synthetic Binding Expansion**: When you write `<input [formField]="mySignal" />`, the type checker doesn't just check `formField`. It synthetically expands this into a set of bindings for validation:
- `[value]="mySignal()"` (or `checked` for checkboxes)
- `[disabled]="mySignal.disabled()"`
- `[required]="mySignal.required()"`
- ...and so on.
- This ensures that `mySignal` (the `FieldNode`) has all the necessary properties to drive the form control.
- **Conflict Detection**: It actively prevents "double binding". If you bind `[formField]`, you are _banned_ from also binding `[value]`, `[disabled]`, `[required]`, etc., as the signal form should be the single source of truth.
- **Element Type Validation**:
- **Native Elements**: Checks that the signal's value type matches the element type (e.g., `<input type="checkbox">` requires a `boolean` signal).
- **Custom Controls**: Detects if a custom component is a "Form Value Control" (has a `value` input/output) or "Form Checkbox Control" (has a `checked` input/output) and validates against that.
### Relevant Files:
- `packages/compiler/src/typecheck/ops/signal_forms.ts`: The core Signal Forms type-checking logic, including `TcbNativeFieldOp`.
## 2. Core Runtime Integration
The `packages/core` package provides the low-level instructions that power the `FormField` directive. This allows it to do things normal directives cannot, like efficiently syncing state without change detection overhead for every property.
### Key Mechanisms:
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`, which causes the compiler to install `ɵɵControlFeature`.
- **`ɵɵcontrol` Instructions**: A `formField` binding causes the compiler to emit:
- `ɵɵcontrolCreate`: Called during the creation phase.
- `ɵɵcontrol`: Called during the update phase.
- **`ControlDirectiveHost`**: These instructions provide the directive with a `ControlDirectiveHost`. This is a privileged interface that allows the `FormField` directive to:
- **Access the Element**: Get direct access to the native element or component instance.
- **Set Inputs**: Write directly to inputs of _other directives_ on the same node (e.g., setting the `value` input of a custom control).
- **Listen to Outputs**: Subscribe to outputs of other directives (e.g., `valueChange`).
- **Bypass Templates**: It effectively acts as a "meta-directive" that manages the bindings for you, bypassing the need for explicit template syntax for every property.
### Relevant Files:
- `packages/core/src/render3/instructions/control.ts`: Implementation of `ɵɵcontrol` instructions.
- `packages/forms/signals/src/directive/form_field.ts`: The directive that implements the hook.
-56
View File
@@ -1,56 +0,0 @@
---
description: Find and fix flaky tests in the repository
---
Investigate flaky tests in the repo and propose fixes to improve stability.
High-level process:
1. Run tests in the repo to look for flakes.
- Consider using Bazel's `--runs_per_test` flag to easily find
flakes.
- Be cognizant of not exhausting all the resources on the current
machine, run a subset of tests at a time such as
`bazel test //packages/core/...`.
2. Once you find some flakes, focus on one at a time.
3. Create a new branch named `flakes/${relevantNameFromTest}`.
4. Reproduce the flake to the best of your ability.
- Consider using `--test_env JASMINE_RANDOM_SEED=1234` to
replicate the broken test ordering.
5. Debug the test to understand the failure mode.
- Consider temporarily disabling / skipping other tests with `xit`
and `fit` to narrow down where the flake might be coming from if
multiple tests are influencing each other.
- Consider temporarily ignoring Firefox tests with
`--test_tag_filters -firefox` if the flake does not appear to be
browser specific.
- Consider using `--test_sharding_strategy disabled` to run the
test in a single shard.
- Try to understand why the test was _flaky_, not just why it
_failed_. Understanding the inconsistency is important to
finding the correct fix.
6. Attempt a fix and validate with `--runs_per_test`.
- Iterate on the fix until you have something which appears to
work.
- If you find yourself stuck and not making meaningful progress,
note down what you've learned/where you're struggling, commit
what you have, look for another flake to fix, and continue. At
the end, surface to the user what you failed to fix.
- Don't try to make significant changes to Angular's runtime
behavior, focus just on making the test pass/fail consistently.
7. Commit the change with relevant details in the commit message and
move on to the next test.
- Be sure to include your theory of why the test was flaky and
how this fix eliminates or reduces that flakiness.
8. Iterate as many times as the user requests you to (default 5
branches if not otherwise specified).
9. Once you can't find any flaky tests or have iterated as many times
as requested, stop and inform the user what you found and fixed.
Additional notes:
- Multiple fixes including the same/related files can go in the same
commit or multiple commits on the same branch.
- Distinct test fixes should go in different branches, make a new one
for each investigation.
- You may push these branches to `origin`, but do not create PRs for
them.
+10
View File
@@ -159,6 +159,16 @@ common --incompatible_allow_tags_propagation
# )
build --nosandbox_default_allow_network
##################################
# Saucelabs tests settings #
# Turn on these settings with #
# --config=saucelabs #
##################################
# For saucelabs tests we don't want to enable flaky test attempts. Karma has its own integrated
# retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times.
test:saucelabs --flaky_test_attempts=1
################
# Flag Aliases #
################
+1 -1
View File
@@ -1 +1 @@
8.8.0
8.6.0
+3
View File
@@ -0,0 +1,3 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 100
+1 -2
View File
@@ -7,5 +7,4 @@ code_review:
help: false
summary: false
code_review: false
ignore_patterns:
- pnpm-lock.yaml
ignore_patterns: []
+178
View File
@@ -0,0 +1,178 @@
---
name: adev-writing-guide
description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. Use when authoring or reviewing content in adev/src/content.
---
# Angular Documentation (adev) Writing Guide
This skill provides comprehensive guidelines for authoring content in `adev/src/content`. It combines Google's technical writing standards with Angular-specific markdown conventions, components, and best practices.
## I. Google Technical Writing Guidelines
### Tone and Content
- **Be conversational and friendly:** Maintain a helpful yet professional tone. Avoid being overly casual.
- **Write accessibly:** Ensure documentation is understandable to a diverse global audience, including non-native English speakers.
- **Audience-first:** Focus on what the user needs to do, not just what the system does.
- **Avoid pre-announcing:** Do not mention unreleased features or make unsupported claims.
- **Use descriptive link text:** Link text should clearly indicate the destination (e.g., avoid "click here").
### Language and Grammar
- **Use second person ("you"):** Address the reader directly.
- **Prefer active voice:** Clearly state who or what is performing the action (e.g., "The system generates a token" vs "A token is generated").
- **Standard American English:** Use standard American spelling and punctuation.
- **Conditional clauses first:** Place "if" or "when" clauses before the instruction (e.g., "If you encounter an error, check the logs").
- **Define terms:** Introduce new or unfamiliar terms/acronyms upon first use.
- **Consistent terminology:** Use the same term for the same concept throughout the document.
- **Conciseness:** Aim for one idea per sentence. Keep sentences short.
### Formatting and Organization
- **Sentence case for headings:** Capitalize only the first word and proper nouns in titles and headings.
- **Lists:**
- **Numbered lists:** Use for sequential steps or prioritized items.
- **Bulleted lists:** Use for unordered collections of items.
- **Description lists:** Use for term-definition pairs.
- **Serial commas:** Use the Oxford comma (comma before the last item in a list of three or more).
- **Code formatting:** Use code font for code-related text (filenames, variables, commands).
- **UI Elements:** formatting user interface elements in **bold**.
- **Date formatting:** Use unambiguous formats (e.g., "September 4, 2024" rather than "9/4/2024").
- **Structure:** Use logical hierarchy with clear introductions and navigation. Headings should be task-based where possible.
### Images and Code Samples
- **Images:** Use simple, clear illustrations to enhance understanding.
- **Captions:** Write captions that support the image.
- **Code Samples:**
- Ensure code is correct and builds without errors.
- Follow language-specific conventions.
- **Comments:** Focus on _why_, not _what_. Avoid commenting on obvious code.
### Reference Hierarchy
1. Project-specific style guidelines (if any exist in `CONTRIBUTING.md` or similar).
2. Google Developer Documentation Style Guide.
3. Merriam-Webster (spelling).
4. Chicago Manual of Style (non-technical).
5. Microsoft Writing Style Guide (technical).
---
## II. Angular Documentation Specifics
### Code Blocks
Use the appropriate language identifier for syntax highlighting:
- **TypeScript (Angular):** Use `angular-ts` when TypeScript code examples contain inline templates.
- **HTML (Angular):** Use `angular-html` for Angular templates.
- **TypeScript (Generic):** Use `ts` for plain TypeScript.
- **HTML (Generic):** Use `html` for plain HTML.
- **Shell/Terminal:** Use `shell` or `bash`.
- **Mermaid Diagrams:** Use `mermaid`.
#### Attributes
You can enhance code blocks with attributes in curly braces `{}` after the language identifier:
- `header="Title"`: Adds a title to the code block.
- `linenums`: Enables line numbering.
- `highlight="[1, 3-5]"`: Highlights specific lines.
- `hideCopy`: Hides the copy button.
- `prefer`: Marks code as a preferred example (green border/check).
- `avoid`: Marks code as an example to avoid (red border/cross).
**Example:**
````markdown
```angular-ts {header:"My Component", linenums, highlight="[2]"}
@Component({
selector: 'my-app',
template: '<h1>Hello</h1>',
})
export class App {}
```
````
#### `<docs-code>` Component
For more advanced code block features, use the `<docs-code>` component:
- `path`: Path to a source file (e.g., `adev/src/content/examples/...`).
- `header`: Custom header text.
- `language`: Language identifier (e.g., `angular-ts`).
- `linenums`: Boolean attribute.
- `highlight`: Array of line numbers/ranges (e.g., `[[3,7], 9]`).
- `diff`: Path to diff file.
- `visibleLines`: Range of lines to show initially (collapsible).
- `region`: Region to extract from source file.
- `preview`: Boolean. Renders a live preview (StackBlitz). _Only works with standalone examples._
- `hideCode`: Boolean. Collapses code by default.
**Multifile Example:**
```html
<docs-code-multifile path="..." preview>
<docs-code path="..." />
<docs-code path="..." />
</docs-code-multifile>
```
### Alerts / Admonitions
Use specific keywords followed by a colon for alerts. These render as styled blocks.
- `NOTE:` For ancillary information.
- `TIP:` For helpful hints or shortcuts.
- `IMPORTANT:` For crucial information.
- `CRITICAL:` For warnings about potential data loss or severe issues.
- `TODO`: For incomplete documentation.
- `QUESTION:` To pose a question to the reader.
- `SUMMARY:` For section summaries.
- `TLDR:` For concise summaries.
- `HELPFUL:` For best practices.
**Example:**
```markdown
TIP: Use `ng serve` to run your application locally.
```
### Custom Components
- **Cards (`<docs-card>`):**
- Must be inside `<docs-card-container>`.
- Attributes: `title`, `link`, `href`.
- **Callouts (`<docs-callout>`):**
- Attributes: `title`, `important`, `critical`.
- **Pills (`<docs-pill>`):**
- Must be inside `<docs-pill-row>`.
- Attributes: `title`, `href`.
- **Steps / Workflow (`<docs-step>`):**
- Must be inside `<docs-workflow>`.
- Attributes: `title`.
- **Tabs (`<docs-tab>`):**
- Must be inside `<docs-tab-group>`.
- Attributes: `label`.
- **Videos (`<docs-video>`):**
- Attributes: `src` (YouTube embed URL), `alt`.
### Images
Use standard markdown syntax with optional attributes for sizing and loading behavior.
- `#small`, `#medium`: Append to image URL for sizing.
- `{loading: 'lazy'}`: Add attribute for lazy loading.
**Example:**
```markdown
![Alt Text](path/to/image.png#medium {loading: 'lazy'})
```
### Headers
- Use markdown headers (`#`, `##`, `###`).
- Ensure a logical hierarchy (don't skip levels).
- `h2` and `h3` are most common for content structure.
@@ -0,0 +1,104 @@
---
name: reference-signal-forms
description: Explains the mental model and architecture of the code under `packages/forms/signals`. You MUST use this skill any time you plan to work with code in `packages/forms/signals`
---
# Signal Forms Architecture
The `packages/forms/signals` directory contains an experimental, signal-based forms API for Angular.
This system differs significantly from the existing Reactive and Template-driven forms.
## Mental Model
1. **Model-Driven**: The system is built around a `WritableSignal<T>` which serves as the **single source of truth**.
Unlike Reactive Forms where the `FormControl` holds the value, here the `Signal` holds the value.
The form is merely a _view_ or _projection_ of that signal, adding form-specific state (validity, dirty, touched).
2. **Proxy-Based Traversal**: The form API (`form(signal)`) returns a `FieldTree`. This object is a **Proxy**.
It allows accessing nested fields (e.g., `myForm.user.name`) without manually creating control groups.
Accessing a property on the proxy lazily resolves or creates the corresponding `FieldNode`.
3. **Schema-Based Logic**: Validation, disabled state, and other metadata are defined separately via **Schemas**.
Schemas are applied to the form structure using functions like `apply`, `applyEach` (for arrays), and `applyWhen`.
This separates the _structure_ of the data from the _rules_ governing it.
4. **Directives as Glue**: The `[formField]` directive binds a DOM element (native input or custom control) to a `FieldNode`.
It handles:
- Syncing the value between the DOM and the Signal.
- Reflecting state (valid, touched, etc.) to the UI.
- Handling user interaction events (blur, input).
## Key Components
### 1. `FieldNode` (`src/field/node.ts`)
The central internal class representing a single field in the form graph. It aggregates several state managers:
- `structure`: Manages parent/child relationships and signal slicing.
- `validationState`: Computes `valid`, `invalid`, `errors` signals.
- `nodeState`: Tracks `touched`, `dirty`, `pristine`.
- `metadataState`: Stores metadata like `min`, `max`, `required`.
- `submitState`: Tracks submission status and server errors.
### 2. `ValidationState` (`src/field/validation.ts`)
Manages the complexity of validation:
- **Synchronous Errors**: Derived from schema rules.
- **Asynchronous Errors**: Handled via signals, including 'pending' states.
- **Tree Errors**: Errors that bubble up or are targeted at specific fields.
- **Submission Errors**: Server-side errors injected imperatively via `submit()`.
### 3. `FormField` Directive (`src/directive/form_field_directive.ts`)
The bridge between the `FieldNode` and the DOM.
- Selector: `[formField]`
- It supports:
- **Native Elements**: `<input>`, `<select>`, `<textarea>`.
- **Custom Controls**: Components implementing `FormUiControl` or `FormValueControl`.
- **Legacy Interop**: Components implementing `ControlValueAccessor` (via `InteropNgControl`).
### 4. `Schema` (`src/api/structure.ts` & `src/api/rules`)
Defines the behavior.
- Created via `schema(fn)`.
- Applied via `apply(path, schema)`.
- Rules include validators (`required`, `pattern`, `min`, `max`) and state modifiers (`disabled`, `hidden`).
## Data Flow
1. **Read**: `form.field.value()` reads directly from the underlying signal (projected to the specific path).
2. **Write**: Writing to the form (e.g., via UI) updates the underlying signal.
3. **Validation**: A computed effect observes the value signal and runs validators defined in the schema.
## Usage Example (Conceptual)
```typescript
// 1. Define Model
const user = signal({name: '', age: 0});
// 2. Define Schema
const userRules = schema((u) => {
required(u.name);
min(u.age, 18);
});
// 3. Create Form
const userForm = form(user, userRules); // OR apply(userForm, userRules)
// 4. Bind in Template
// <input [formField]="userForm.name">
```
## Important Files
- `packages/forms/signals/src/api/structure.ts`: Public API entry points (`form`, `apply`).
- `packages/forms/signals/src/api/control.ts`: Interfaces for custom controls (`FormUiControl`).
- `packages/forms/signals/src/field/node.ts`: The `FieldNode` implementation.
- `packages/forms/signals/src/directive/form_field_directive.ts`: The `[formField]` directive.
## Supplemental Information
- [Compiler & Core Integration](references/integration.md): Details how `[formField]` hooks into type-checking and the runtime.
@@ -0,0 +1,46 @@
# Signal Forms Integration: Compiler & Core
This document explains how the Signal Forms system hooks into the Angular compiler and runtime to provide seamless type-checking and efficient updates.
## 1. Compiler-CLI Integration (Type Checking)
The `packages/compiler-cli` package contains specific logic to support `[formField]`. This is primarily handled in `src/ngtsc/typecheck/src/ops/signal_forms.ts`.
### Key Mechanisms:
- **Detection**: The compiler identifies a directive as a "Field Directive" if it has the `ɵNgFieldDirective` property or comes from `@angular/forms/signals`.
- **Synthetic Binding Expansion**: When you write `<input [formField]="mySignal" />`, the type checker doesn't just check `formField`. It synthetically expands this into a set of bindings for validation:
- `[value]="mySignal()"` (or `checked` for checkboxes)
- `[disabled]="mySignal.disabled()"`
- `[required]="mySignal.required()"`
- ...and so on.
- This ensures that `mySignal` (the `FieldNode`) has all the necessary properties to drive the form control.
- **Conflict Detection**: It actively prevents "double binding". If you bind `[formField]`, you are _banned_ from also binding `[value]`, `[disabled]`, `[required]`, etc., as the signal form should be the single source of truth.
- **Element Type Validation**:
- **Native Elements**: Checks that the signal's value type matches the element type (e.g., `<input type="checkbox">` requires a `boolean` signal).
- **Custom Controls**: Detects if a custom component is a "Form Value Control" (has a `value` input/output) or "Form Checkbox Control" (has a `checked` input/output) and validates against that.
### Relevant Files:
- `packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts`: The core logic for `TcbNativeFieldOp` and `SignalFormFieldOp`.
## 2. Core Runtime Integration
The `packages/core` package provides the low-level instructions that power the `FormField` directive. This allows it to do things normal directives cannot, like efficiently syncing state without change detection overhead for every property.
### Key Mechanisms:
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`.
- **`ɵɵcontrol` Instructions**: When the compiler sees `ɵngControlCreate`, it emits:
- `ɵɵcontrolCreate`: Called during the creation phase.
- `ɵɵcontrol`: Called during the update phase.
- **`ControlDirectiveHost`**: These instructions provide the directive with a `ControlDirectiveHost`. This is a privileged interface that allows the `FormField` directive to:
- **Access the Element**: Get direct access to the native element or component instance.
- **Set Inputs**: Write directly to inputs of _other directives_ on the same node (e.g., setting the `value` input of a custom control).
- **Listen to Outputs**: Subscribe to outputs of other directives (e.g., `valueChange`).
- **Bypass Templates**: It effectively acts as a "meta-directive" that manages the bindings for you, bypassing the need for explicit template syntax for every property.
### Relevant Files:
- `packages/core/src/render3/instructions/control.ts`: Implementation of `ɵɵcontrol` instructions.
- `packages/forms/signals/src/directive/form_field_directive.ts`: The directive that implements the hook.
-2
View File
@@ -43,5 +43,3 @@ c702e8af0b2144d97b93171dc2806ed1a0346762
6d3f5752f204a5a30f3c09fbf6d4a510a4522bcb
3fa7b2b136696464e0a91b5ec25c6adf272d4d6b
ad65f44877f22caadbc8b977b5b720c3c86fcc45
# commit that changed formatting
698b0288bee60b8c5926148b79b5b93f454098db
@@ -11,6 +11,7 @@ esbuild_checked_in(
config = "esbuild.conf.js",
entry_point = ":lib/main.mts",
external = [
"undici",
"pnpapi",
],
metafile = False,
@@ -35,5 +36,7 @@ ts_project(
"//:node_modules/@actions/github",
"//:node_modules/@angular/ng-dev",
"//:node_modules/@types/node",
"//:node_modules/@types/tmp",
"//:node_modules/tmp",
],
)
@@ -1,17 +1,15 @@
import {writeFileSync, mkdtempSync} from 'node:fs';
import {join} from 'node:path';
import {tmpdir} from 'node:os';
import {fileSync} from 'tmp';
import {writeSync} from 'node:fs';
import {getInput, setSecret} from '@actions/core';
let credentialFilePath: undefined | string;
export function getCredentialFilePath(): string {
if (credentialFilePath === undefined) {
const tmpDir = mkdtempSync(join(tmpdir(), 'credential-'));
const filePath = join(tmpDir, 'credential.json');
writeFileSync(filePath, getInput('serviceKey', {required: true}));
setSecret(filePath);
credentialFilePath = filePath;
const tmpFile = fileSync({postfix: '.json'});
writeSync(tmpFile.fd, getInput('serviceKey', {required: true}));
setSecret(tmpFile.name);
credentialFilePath = tmpFile.name;
}
return credentialFilePath;
}
@@ -1,9 +1,6 @@
import {
fetchLongTermSupportBranchesFromNpm,
ActiveReleaseTrains,
AuthenticatedGitClient,
ReleaseConfig,
} from '@angular/ng-dev';
import {fetchLongTermSupportBranchesFromNpm, ActiveReleaseTrains} from '@angular/ng-dev';
import {ReleaseConfig} from '@angular/ng-dev';
import {AuthenticatedGitClient} from '@angular/ng-dev';
export interface Deployment {
branch: string;
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@
"types": ["node"],
"lib": ["es2021"],
"experimentalDecorators": true,
"strict": true,
"skipLibCheck": true,
"noImplicitOverride": true,
"esModuleInterop": true,
@@ -12,4 +13,4 @@
"declaration": true,
"sourceMap": true
}
}
}
@@ -0,0 +1,40 @@
name: 'Saucelabs legacy test job'
description: 'Runs tests against Saucelabs (outside of Bazel)'
runs:
using: 'composite'
steps:
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Starting Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh run &
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- name: Preparing Bazel-generated fixtures required in legacy tests
shell: bash
run: |
# Locale files are needed for i18n tests running within Saucelabs. These are added
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
# can succeed. Note that the base locale and currencies files are checked-in, so
# we do not need to re-generate those through Bazel.
mkdir -p packages/common/locales/extra
cp dist/bin/packages/common/locales/*.ts packages/common/locales
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
- name: Build bundle of tests to run on Saucelabs
shell: bash
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
- name: Wait and confirm Saucelabs tunnel has connected
shell: bash
run: ./tools/saucelabs/sauce-service.sh ready-wait
- name: Running tests on Saucelabs.
shell: bash
run: KARMA_WEB_TEST_MODE=SL_REQUIRED pnpm karma start ./karma-js.conf.js --single-run
- name: Stop Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh stop
+4 -6
View File
@@ -21,21 +21,19 @@ jobs:
(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@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
# `snapshot-build` config is used to stamp the exact version with sha in the footer.
run: pnpm bazel build //adev:build.production --config=snapshot-build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
workflow-artifact-name: 'adev-preview'
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY || 'bypassed_for_forks' }}
triggering-label: 'adev: preview'
pull-number: '${{github.event.pull_request.number}}'
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
deploy-directory: './dist/bin/adev/dist/browser'
+5 -11
View File
@@ -8,7 +8,6 @@
name: Deploying adev preview to Firebase
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
workflow_run:
workflows: ['Build adev for preview deployment']
types: [completed]
@@ -30,23 +29,18 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: '${{secrets.GITHUB_TOKEN}}'
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- name: Configure Firebase deploy target
working-directory: ./
run: |
# We can use `npx` as the Firebase deploy actions uses it too.
# Use stable version release
npx -y firebase-tools@15.15.0 target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
npx -y firebase-tools@15.15.0 target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
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@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
@@ -2,7 +2,6 @@ name: DevInfra
on:
push:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, labeled]
@@ -15,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/branch-manager@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+5 -22
View File
@@ -25,34 +25,20 @@ jobs:
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
reactions: 'rocket'
- uses: alessbell/pull-request-comment-branch@ef3408c9757d05f89cb525036383033a313758a0 # v2.1.0
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
id: comment-branch
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Specify repository as the PR branch might be from a fork.
repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}}
# Checkout the pull request and assume it being trusted given we've checked
# that the action was triggered by a team member.
ref: ${{steps.comment-branch.outputs.head_sha}}
# We cannot use `angular/dev-infra/github-actions/npm/checkout-and-setup-node` here
# because it does not support checking out from a fork (as it lacks a `repository` input).
# Thus, we checkout and setup Node/pnpm manually.
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
- name: Install pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
ref: ${{steps.comment-branch.outputs.head_ref}}
- run: pnpm install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
bazelrc: ./.bazelrc.user
@@ -63,10 +49,7 @@ jobs:
COMMENT_BODY: ${{ github.event.comment.body }}
run: pnpm benchmarks prepare-for-github-action "$COMMENT_BODY"
- env:
COMPARE_SHA: ${{steps.info.outputs.compareSha}}
BENCHMARK_TARGET: ${{steps.info.outputs.benchmarkTarget}}
run: pnpm benchmarks run-compare "$COMPARE_SHA" "$BENCHMARK_TARGET"
- run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} ${{steps.info.outputs.benchmarkTarget}}
id: benchmark
name: Running benchmark
+38 -28
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -33,7 +33,7 @@ jobs:
- name: Validate angular robot configuration
run: pnpm ng-dev ngbot verify
- name: Validate agent skills
run: pnpm ng-dev misc validate-skills
run: pnpm ng-dev ai skills validate
- name: Confirm code builds with typescript as expected
run: pnpm check-tooling-setup
@@ -41,13 +41,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -57,7 +57,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
uses: cypress-io/github-action@bc22e01685c56e89e7813fd8e26f33dc47f87e15 # v7.1.5
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -69,11 +69,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -85,11 +85,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -102,11 +102,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -121,11 +121,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -138,11 +138,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -154,11 +154,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -188,21 +188,31 @@ jobs:
- run: pnpm -C packages/zone.js jest:test
- run: pnpm -C packages/zone.js jest:nodetest
- run: pnpm -C packages/zone.js vitest:test
- run: pnpm -C packages/zone.js vitest-globals:test
- run: pnpm -C packages/zone.js electrontest
- run: pnpm -C packages/zone.js/test/typings test
# saucelabs:
# runs-on: ubuntu-latest
# env:
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
# steps:
# - name: Initialize environment
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
# - name: Install node modules
# run: pnpm install --frozen-lockfile
# - uses: ./.github/actions/saucelabs-legacy
adev-deploy:
needs: [adev]
if: needs.adev.result == 'success'
runs-on: ubuntu-latest-8core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Setting `persist-credentials: false` prevents the github-action account from being the
# account that is attempted to be used for authentication, instead the remote is set to
@@ -37,7 +37,7 @@ jobs:
ANGULAR_READONLY_GITHUB_TOKEN: ${{ secrets.READONLY_GITHUB_TOKEN }}
- name: Create a PR (if necessary)
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
push-to-fork: 'angular-robot/angular'
+5 -4
View File
@@ -1,7 +1,6 @@
name: DevInfra
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, synchronize, reopened]
issues:
@@ -16,7 +15,8 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/labeling/pull-request@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/labeling/pull-request@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
labels: '{"requires: TGP": ["packages/core/primitives/**/{*,.*}"]}'
@@ -24,14 +24,15 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/post-approval-changes@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/post-approval-changes@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
issue_labels:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/labeling/issue@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/labeling/issue@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }}
+2 -3
View File
@@ -1,7 +1,6 @@
name: Google Internal Tests Enforcement
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, reopened, synchronize]
@@ -14,8 +13,8 @@ jobs:
statuses: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: angular/dev-infra/github-actions/google-internal-tests@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/google-internal-tests@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+32
View File
@@ -0,0 +1,32 @@
name: Manual jobs
on:
workflow_dispatch:
inputs: {}
jobs:
# Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but
# kept as "runnable" for debugging/stabilization effort purposes.
bazel-saucelabs:
runs-on: ubuntu-latest
env:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Set up Sauce Tunnel Daemon
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
- name: Run all saucelabs bazel tests
run: |
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
pnpm bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
+2 -4
View File
@@ -1,8 +1,6 @@
name: Merge Ready
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
on: pull_request_target
# Declare default permissions as read only.
permissions: {}
@@ -11,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@183403ae13b785698eaf13c819dda55b9fed430b # main
- uses: angular/dev-infra/github-actions/unified-status-check@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+5 -10
View File
@@ -7,6 +7,7 @@ on:
permissions:
contents: 'read'
id-token: 'write'
defaults:
run:
@@ -20,7 +21,7 @@ jobs:
workflows: ${{ steps.workflows.outputs.workflows }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- id: workflows
@@ -30,17 +31,14 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: list
permissions:
contents: 'read'
id-token: 'write'
strategy:
matrix:
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
# We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
@@ -52,7 +50,4 @@ jobs:
project_id: 'internal-200822'
workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular'
service_account: 'measures-uploader@internal-200822.iam.gserviceaccount.com'
- run: pnpm ng-dev perf workflows --name "$WORKFLOW" --commit-sha "$COMMIT_SHA"
env:
WORKFLOW: ${{ matrix.workflow }}
COMMIT_SHA: ${{ github.sha }}
- run: pnpm ng-dev perf workflows --name ${{ matrix.workflow }} --commit-sha ${{github.sha}}
+34 -24
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -33,13 +33,13 @@ jobs:
- name: Confirm code builds with typescript as expected
run: pnpm check-tooling-setup
- name: Validate agent skills
run: pnpm ng-dev misc validate-skills
run: pnpm ng-dev ai skills validate
- name: Check commit message
run: pnpm ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
- name: Check code format
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
- name: Check Package Licenses
uses: angular/dev-infra/github-actions/linting/licenses@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/linting/licenses@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
@@ -47,13 +47,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run unit tests
@@ -61,7 +61,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
uses: cypress-io/github-action@bc22e01685c56e89e7813fd8e26f33dc47f87e15 # v7.1.5
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -73,11 +73,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run CI tests for framework
@@ -88,7 +88,7 @@ jobs:
ASPECT_RULES_JS_FROZEN_PNPM_LOCK: '1'
- name: Upload GRPC logs (for debugging of RBE issues)
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
path: /tmp/rbe-grpc.log
retention-days: 1
@@ -97,11 +97,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run integration CI tests for framework
@@ -112,11 +112,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -129,11 +129,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -144,11 +144,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/setup@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
uses: angular/dev-infra/github-actions/bazel/configure-remote@e4dcda13e469f50a6bd92667e2ded8b98173bf2c
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: |
@@ -174,6 +174,16 @@ jobs:
- run: pnpm -C packages/zone.js jest:test
- run: pnpm -C packages/zone.js jest:nodetest
- run: pnpm -C packages/zone.js vitest:test
- run: pnpm -C packages/zone.js vitest-globals:test
- run: pnpm -C packages/zone.js electrontest
- run: pnpm -C packages/zone.js/test/typings test
# saucelabs:
# runs-on: ubuntu-latest
# env:
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
# steps:
# - name: Initialize environment
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
# - name: Install node modules
# run: pnpm install --frozen-lockfile
# - uses: ./.github/actions/saucelabs-legacy
-24
View File
@@ -1,24 +0,0 @@
name: Publish Release
on:
push:
branches:
- main
- '[0-9]+.[0-9]+.x'
workflow_dispatch: # Allow manual trigger for verification
# Ensure only one release runs at a time PER BRANCH to avoid race conditions.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read # Required to checkout the repository
id-token: write # Required for NPM provenance in reusable workflow
jobs:
release:
uses: angular/dev-infra/.github/workflows/reusable-release.yml@183403ae13b785698eaf13c819dda55b9fed430b # main
secrets:
wombot-token: ${{ secrets.WOMBOT_TOKEN }}
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+52
View File
@@ -0,0 +1,52 @@
name: OpenSSF Scorecard
on:
branch_protection_rule:
schedule:
- cron: '0 0 * * 0'
push:
branches: [main]
workflow_dispatch:
# Declare default permissions as read only.
permissions:
contents: read
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results
id-token: write
actions: read
contents: read
steps:
- name: 'Checkout code'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
# Upload the results as artifacts.
- name: 'Upload artifact'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
sarif_file: results.sarif
-1
View File
@@ -10,7 +10,6 @@
"packages/compiler-cli/private/bazel.ts",
"packages/compiler-cli/private/localize.ts",
"packages/compiler-cli/private/tooling.ts",
"packages/compiler-cli/private/hybrid_analysis.ts",
"packages/compiler-cli/private/babel.d.ts",
"packages/compiler-cli/src/bin/**",
"packages/core/schematics/utils/tsurge/helpers/angular_devkit/**",
+2 -16
View File
@@ -7,14 +7,7 @@ export const release = {
publishRegistry: 'https://wombat-dressing-room.appspot.com',
representativeNpmPackage: '@angular/core',
npmPackages: [
{
name: '@angular/animations',
deprecated: {
version: '>=20.2.0-next.3',
message:
'@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.',
},
},
{name: '@angular/animations'},
{name: '@angular/common'},
{name: '@angular/compiler-cli'},
{name: '@angular/compiler'},
@@ -24,14 +17,7 @@ export const release = {
{name: '@angular/language-server'},
{name: '@angular/language-service'},
{name: '@angular/localize'},
{
name: '@angular/platform-browser-dynamic',
deprecated: {
version: '>=20.1.0-next.0',
message:
'@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.',
},
},
{name: '@angular/platform-browser-dynamic'},
{name: '@angular/platform-browser'},
{name: '@angular/platform-server'},
{name: '@angular/router'},
+1
View File
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "es2020",
"module": "Node16",
"noEmit": true,
+12
View File
@@ -0,0 +1,12 @@
# Yarn Berry doesn't check engines at all, so pnpm shouldn't either.
engine-strict = false
# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on
# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what
# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules)
hoist=false
# Avoid pnpm auto-installing peer dependencies. We want to be explicit about our versions used
# for peer dependencies, avoiding potential mismatches. In addition, it ensures we can continue
# to rely on peer dependency placeholders substituted via Bazel.
auto-install-peers=false
+1 -1
View File
@@ -1 +1 @@
24.21.0
22.22.1
+1 -1
View File
@@ -53,7 +53,7 @@ function readPackage(pkg, context) {
if (pkg.version === '0.0.0-PLACEHOLDER' && localAngularPackages.has(key)) {
pkg.dependencies = {
...pkg.dependencies,
[key]: 'workspace:*',
[key]: 'workspace: *',
};
delete pkg.peerDependencies[key];
-4
View File
@@ -38,10 +38,6 @@ adev/src/content/aria/**/*.json
adev/src/content/cli/**/*.json
adev/src/content/cdk/**/*.json
# Example package.json containing docregion (`// #docregion`) markers, which are
# not valid JSON and cannot be processed by Prettier's json-stringify parser.
adev/src/content/examples/schematics-for-libraries/projects/my-lib/package.json
# Antigravity rules
.agent/rules/agents.md
+47 -36
View File
@@ -36,9 +36,8 @@
version: 3
availability:
users_unavailable:
- devversion
#availability:
# users_unavailable: []
# Meta field that goes unused by PullApprove to allow for defining aliases to be
# used throughout the config.
@@ -100,11 +99,11 @@ groups:
reviewers:
users:
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- atscott
- crisbeto
- devversion
- ~thePunderWoman
- thePunderWoman
- kirjs
- JoostK
- ~amishne
@@ -120,7 +119,6 @@ groups:
conditions:
- >
contains_any_globs(files.exclude('packages/core/primitives/*'), [
'.agent/skills/**/{*,.*}',
'contributing-docs/public-api-surface.md',
'dev-app/**/{*,.*}',
'integration/**/{*,.*}',
@@ -138,7 +136,6 @@ groups:
'packages/platform-browser/**/{*,.*}',
'packages/platform-browser-dynamic/**/{*,.*}',
'packages/platform-server/**/{*,.*}',
'packages/private/**/{*,.*}',
'packages/ssr/**/{*,.*}',
'packages/router/**/{*,.*}',
'packages/service-worker/**/{*,.*}',
@@ -148,12 +145,12 @@ groups:
reviewers:
users:
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- atscott
- crisbeto
- devversion
- kirjs
- ~thePunderWoman
- thePunderWoman
- ~pkozlowski-opensource
- JeanMeche
- ~amishne
@@ -203,13 +200,12 @@ groups:
users:
- ~JiaLiPassion
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- atscott
- crisbeto
- devversion
- kirjs
- alan-agius4
- ~thePunderWoman
- thePunderWoman
- ~pkozlowski-opensource
- ~amishne
- ~leonsenft
@@ -247,20 +243,20 @@ groups:
- >
contains_any_globs(files, [
'adev/**/{*,.*}',
'tools/manual_api_docs/blocks/*.md',
'tools/manual_api_docs/elements/*.md',
'tools/manual_api_docs/blocks/**/*.md',
'tools/manual_api_docs/elements/**/*.md',
])
reviewers:
users:
- alan-agius4
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- atscott
- bencodezen
- crisbeto
- kirjs
- JeanMeche
- ~thePunderWoman
- thePunderWoman
- devversion
- josephperrott
- ~pkozlowski-opensource
@@ -286,14 +282,13 @@ groups:
reviewers:
users:
- AleksanderBodurri
- csmick
- ~devversion
- dgp1130
- eduhmc
- hawkgs
- JeanMeche
- hybrist
- ~josephperrott
- ~milomg
- JeanMeche
- milomg
# =========================================================
# Dev-infra
@@ -303,10 +298,9 @@ groups:
conditions:
- >
contains_any_globs(files
.exclude('.pullapprove.yml')
.exclude('tools/manual_api_docs/blocks/*.md')
.exclude('tools/manual_api_docs/elements/*.md')
.exclude('.agent/**/*.md'),
.exclude('.pullapprove.yml')
.exclude('tools/manual_api_docs/blocks/**/*.md')
.exclude('tools/manual_api_docs/elements/**/*.md'),
[
'{*,.*}',
'.agent/**/{*,.*}',
@@ -323,14 +317,18 @@ groups:
'modules/{*,.*}',
'packages/{*,.*}',
'packages/examples/test-utils/**/{*,.*}',
'packages/private/**/{*,.*}',
'packages/examples/{*,.*}',
'scripts/**/{*,.*}',
'third_party/**/{*,.*}',
'tools/contributing-stats/**/{*,.*}',
'tools/gulp-tasks/**/{*,.*}',
'tools/legacy-saucelabs/**/{*,.*}',
'tools/manual_api_docs/**/{*,.*}',
'tools/pnpm-patches/**/{*,.*}',
'tools/rxjs/**/{*,.*}',
'tools/saucelabs-daemon/**/{*,.*}',
'tools/saucelabs/**/{*,.*}',
'tools/symbol-extractor/**/{*,.*}',
'tools/testing/**/{*,.*}',
'tools/tslint/**/{*,.*}',
@@ -374,10 +372,10 @@ groups:
])
reviewers:
users:
- ~AndrewKushnir
- AndrewKushnir
- ~alxhub
- atscott
- ~thePunderWoman
- thePunderWoman
- ~pkozlowski-opensource
- kirjs
- crisbeto
@@ -410,11 +408,10 @@ groups:
reviewers:
users:
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- atscott
- kirjs
- JeanMeche
- ~thePunderWoman
- thePunderWoman
- ~pkozlowski-opensource
- ~amishne
- ~leonsenft
@@ -437,7 +434,7 @@ groups:
])
reviewers:
users:
- MarkTechson
- marktechson
- kirjs
- ~JeanMeche
- ~dgp1130
@@ -459,10 +456,10 @@ groups:
reviewers:
users:
- ~alxhub
- ~AndrewKushnir
- AndrewKushnir
- andrewseguin
- dgp1130
- ~thePunderWoman
- thePunderWoman
- josephperrott
# =========================================================
@@ -481,12 +478,26 @@ groups:
users:
- ~pkozlowski-opensource # Pawel Kozlowski
- ~alxhub # Alex Rickabaugh
- ~thePunderWoman # Jessica Janiuk
- ~AndrewKushnir # Andrew Kushnir
- thePunderWoman # Jessica Janiuk
- AndrewKushnir # Andrew Kushnir
- atscott # Andrew Scott
# External team required reviews
primitives-shared:
<<: *defaults
conditions:
- >
contains_any_globs(files, [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- csmick # Cameron Smick
- mturco # Matt Turco
- iteriani # Thomas Nguyen
- tbondwilkinson # Tom Wilkinson
- rahatarmanahmed # Rahat Ahmed
- e-cline # Ethan Cline
- rockymeza # Rocky Meza
- ENAML # Ethan Cline
####################################################################################
# Override managed result groups
+21 -1
View File
@@ -1,3 +1,4 @@
load("@aspect_rules_js//js:defs.bzl", "js_library")
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@devinfra//bazel/validation:defs.bzl", "validate_ts_version_matching")
load("@npm//:defs.bzl", "npm_link_all_packages")
@@ -7,6 +8,8 @@ package(default_visibility = ["//visibility:public"])
exports_files([
"LICENSE",
"karma-js.conf.js",
"browser-providers.conf.js",
"package.json",
])
@@ -22,7 +25,7 @@ filegroup(
)
validate_ts_version_matching(
module_bazel = "MODULE.bazel",
module_lock_file = "MODULE.bazel.lock",
package_json = "package.json",
)
@@ -31,6 +34,14 @@ alias(
actual = "//packages:tsconfig-build.json",
)
js_library(
name = "browser-providers",
srcs = [
"browser-providers.conf.d.ts",
"browser-providers.conf.js",
],
)
copy_to_bin(
name = "angularjs_scripts",
srcs = [
@@ -53,6 +64,15 @@ config_setting(
values = {"stamp": "true"},
)
alias(
name = "sauce_connect",
actual = select({
"@devinfra//bazel/constraints:linux_x64": "@sauce_connect_linux_amd64//:bin/sc",
"@devinfra//bazel/constraints:macos_x64": "@sauce_connect_mac//:bin/sc",
"@devinfra//bazel/constraints:macos_arm64": "@sauce_connect_mac//:bin/sc",
}),
)
# When enabled, this flag substitutes dependency versions with snapshot repositories
# for all packages in this repository. Note that this does not apply to peer
# dependencies, as they must be installed directly.
+9 -1783
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -4967,9 +4967,9 @@ To learn about the release highlights and our CLI-powered automated update workf
- **ivy:** i18n - render legacy message ids in `$localize` if requested ([#32937](https://github.com/angular/angular/issues/32937)) ([bcbf3e4](https://github.com/angular/angular/commit/bcbf3e4))
- **language-service:** module definitions on directive hover ([#32763](https://github.com/angular/angular/issues/32763)) ([0d186dd](https://github.com/angular/angular/commit/0d186dd)), closes [#32565](https://github.com/angular/angular/issues/32565)
- **ngcc:** expose `--create-ivy-entry-points` option on ivy-ngcc ([#33049](https://github.com/angular/angular/issues/33049)) ([b2b917d](https://github.com/angular/angular/commit/b2b917d)), closes [/github.com/angular/angular/pull/32999#issuecomment-539937368](https://github.com/angular/angular/pull/32999/issues/issuecomment-539937368)
- update rxjs peerDependencies minimum requirement to 6.5.3 ([#32812](https://github.com/angular/angular/issues/32812)) ([66658c4](https://github.com/angular/angular/commit/66658c4))
- update rxjs peerDependencies minimum requirment to 6.5.3 ([#32812](https://github.com/angular/angular/issues/32812)) ([66658c4](https://github.com/angular/angular/commit/66658c4))
- **ivy:** support ng-add in localize package ([#32791](https://github.com/angular/angular/issues/32791)) ([e41cbfb](https://github.com/angular/angular/commit/e41cbfb))
- **language-service:** allow retrieving synchronized analyzed NgModules ([#32779](https://github.com/angular/angular/issues/32779)) ([98feee7](https://github.com/angular/angular/commit/98feee7))
- **language-service:** allow retreiving synchronized analyzed NgModules ([#32779](https://github.com/angular/angular/issues/32779)) ([98feee7](https://github.com/angular/angular/commit/98feee7))
- **service-worker:** remove deprecated `versionedFiles` option ([#32862](https://github.com/angular/angular/issues/32862)) ([5d5c94d](https://github.com/angular/angular/commit/5d5c94d))
- **language-service:** expose determining the NgModule of a Directive ([#32710](https://github.com/angular/angular/issues/32710)) ([2846505](https://github.com/angular/angular/commit/2846505)), closes [#32565](https://github.com/angular/angular/issues/32565)
- **bazel:** support ts_library targets as entry-points for ng_package ([#32610](https://github.com/angular/angular/issues/32610)) ([217db9b](https://github.com/angular/angular/commit/217db9b))
@@ -5114,7 +5114,7 @@ To learn about the release highlights and our CLI-powered automated update workf
We assume you will fetch rules_nodejs in your WORKSPACE file, and no other dependencies remain here.
Simply remove any calls to this function and the corresponding load statement.
- typescript 3.4 and 3.5 are no longer supported, please update to typescript 3.6
- We no longer directly have a direct dependency on `tslib`. Instead it is now listed a `peerDependency`.
- We no longer directly have a direct depedency on `tslib`. Instead it is now listed a `peerDependency`.
Users not using the CLI will need to manually install `tslib` via;
@@ -6617,7 +6617,7 @@ For example:
- **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
- **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
- **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
- **platform-server:** don't reflect innerHTML property to attribute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
- **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
- **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
<!-- CHANGELOG SPLIT MARKER -->
@@ -9193,7 +9193,7 @@ Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 re
- **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
- **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
- **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
- **core:** properly destroy embedded Views attached to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
- **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
- **core:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
- **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
- **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
+2 -2
View File
@@ -71,7 +71,7 @@ You can file new issues by selecting from our [new issue templates](https://gith
### <a name="pr-quality"></a> Contribution Quality
We strongly value open source contribution and pull requests from community contributors. Please note that every pull request is reviewed and merged by an actual person on the team, which does take time and effort. That is time and effort that does take away from other valuable work. With that in mind we have a minimum set of expectations that are required of any community contribution pull request that is opened.
We strongly value open source contribution and pull requests from community contributors. Please note that every pull request is reviewed and merged by an actual person on the team, which does take time and effort. That is time and effort that does take away from other valuable work. With that in mind we have an minimum set of expectations that are required of any community contribution pull request that is opened.
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR that relates to your submission.
- You don't want to duplicate existing efforts.
@@ -238,7 +238,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
- For corporations, we'll need you to
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
If you have more than one GitHub account, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests.
If you have more than one GitHub accounts, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests.
The following documents can help you sort out issues with GitHub accounts and multiple email addresses:
+53 -37
View File
@@ -4,42 +4,42 @@ module(
name = "angular",
)
bazel_dep(name = "rules_pkg", version = "1.3.0")
bazel_dep(name = "rules_nodejs", version = "6.7.5")
bazel_dep(name = "aspect_rules_ts", version = "3.10.1")
bazel_dep(name = "aspect_rules_js", version = "3.4.1")
bazel_dep(name = "aspect_rules_esbuild", version = "0.27.0")
bazel_dep(name = "rules_pkg", version = "1.2.0")
bazel_dep(name = "rules_nodejs", version = "6.7.3")
bazel_dep(name = "aspect_rules_ts", version = "3.8.5")
bazel_dep(name = "aspect_rules_js", version = "2.9.2")
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0")
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4")
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
bazel_dep(name = "bazel_skylib", version = "1.9.2")
bazel_dep(name = "bazel_lib", version = "3.7.2")
bazel_dep(name = "tar.bzl", version = "0.10.8")
bazel_dep(name = "yq.bzl", version = "0.4.0")
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
bazel_dep(name = "tar.bzl", version = "0.9.0")
bazel_dep(name = "yq.bzl", version = "0.3.5")
bazel_dep(name = "rules_angular")
git_override(
module_name = "rules_angular",
commit = "5fa856469c642490a6e381a05d7f58f60fad1913",
commit = "d746c4f75e42cffe389d1ab077f4639be2bc78d1",
remote = "https://github.com/angular/rules_angular.git",
)
bazel_dep(name = "devinfra")
git_override(
module_name = "devinfra",
commit = "183403ae13b785698eaf13c819dda55b9fed430b",
commit = "e4dcda13e469f50a6bd92667e2ded8b98173bf2c",
remote = "https://github.com/angular/dev-infra.git",
)
bazel_dep(name = "rules_sass")
git_override(
module_name = "rules_sass",
commit = "bf9d6564f57b4df94b4915086ad0e7ad49a95f37",
commit = "1184a80751a21af8348f308abc5b38a41f26850e",
remote = "https://github.com/angular/rules_sass.git",
)
bazel_dep(name = "rules_browsers")
git_override(
module_name = "rules_browsers",
commit = "c37398d63f5e990d618d02f4c6fb2fefe40f1a79",
commit = "8231142fc5516323b040883bf774ba8362645387",
remote = "https://github.com/angular/rules_browsers.git",
)
@@ -49,15 +49,15 @@ use_repo(yq, "yq_toolchains")
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
node.toolchain(
node_repositories = {
"24.21.0-darwin_arm64": ("node-v24.21.0-darwin-arm64.tar.gz", "node-v24.21.0-darwin-arm64", "bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057"),
"24.21.0-darwin_amd64": ("node-v24.21.0-darwin-x64.tar.gz", "node-v24.21.0-darwin-x64", "1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097"),
"24.21.0-linux_arm64": ("node-v24.21.0-linux-arm64.tar.xz", "node-v24.21.0-linux-arm64", "6ad1325edbdb5649c379b75a237147a666c95d4f9ae8d340fef2d1575d289ad2"),
"24.21.0-linux_ppc64le": ("node-v24.21.0-linux-ppc64le.tar.xz", "node-v24.21.0-linux-ppc64le", "1936fd64623a2f98d1fb31b456686d10c30e92639899cfefdefa8835d22adccd"),
"24.21.0-linux_s390x": ("node-v24.21.0-linux-s390x.tar.xz", "node-v24.21.0-linux-s390x", "2ef7e2ecbf7a6c2f3d08d106f6b2f279419c6dac89fe91b8cad193af7890082c"),
"24.21.0-linux_amd64": ("node-v24.21.0-linux-x64.tar.xz", "node-v24.21.0-linux-x64", "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6"),
"24.21.0-windows_amd64": ("node-v24.21.0-win-x64.zip", "node-v24.21.0-win-x64", "158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541"),
"22.22.1-darwin_arm64": ("node-v22.22.1-darwin-arm64.tar.gz", "node-v22.22.1-darwin-arm64", "679ad4966339e4ef4900f57996714864e4211b898825bb840c3086c419fbcef2"),
"22.22.1-darwin_amd64": ("node-v22.22.1-darwin-x64.tar.gz", "node-v22.22.1-darwin-x64", "07b13722d558790fca20bb1ecf61bde24b7a4863111f7be77fc57251a407359a"),
"22.22.1-linux_arm64": ("node-v22.22.1-linux-arm64.tar.xz", "node-v22.22.1-linux-arm64", "0f3550d58d45e5d3cf7103d9e3f69937f09fe82fb5dd474c66a5d816fa58c9ee"),
"22.22.1-linux_ppc64le": ("node-v22.22.1-linux-ppc64le.tar.xz", "node-v22.22.1-linux-ppc64le", "a65a44cf0224505f052b90357b763dbc1ea9148f4f5f2284f0596cf2000f819b"),
"22.22.1-linux_s390x": ("node-v22.22.1-linux-s390x.tar.xz", "node-v22.22.1-linux-s390x", "e08b3a73d0bd840e008f589e4be4a2ef3d4a0c59015f4f20a04ed7fc968042a2"),
"22.22.1-linux_amd64": ("node-v22.22.1-linux-x64.tar.xz", "node-v22.22.1-linux-x64", "9a6bc82f9b491279147219f6a18add1e18424dce90d41d2a5fcd69d4924ba3aa"),
"22.22.1-windows_amd64": ("node-v22.22.1-win-x64.zip", "node-v22.22.1-win-x64", "877cb93829e14fffbbc7903e7d8037336c9a79f3ea43c5d0b8c2379b79da56de"),
},
node_version = "24.21.0",
node_version = "22.22.1",
)
use_repo(node, "nodejs_toolchains")
use_repo(node, "nodejs_darwin_amd64")
@@ -71,8 +71,8 @@ use_repo(node, "nodejs_windows_amd64")
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
pnpm.pnpm(
name = "pnpm",
pnpm_version = "11.24.0",
pnpm_version_integrity = "sha512-vSfjRel23LC+C3oSKCF7BJqBfiGx81XJDb59xGZxiVqLwebQbCRVRQXqk+oLRfSJon7Bv7yN5qlln8oPFvoAAA==",
pnpm_version = "10.30.3",
pnpm_version_integrity = "sha512-yWHR4KLY41TsqlFmuCJRZmi39Ey1vZUSLVkN2Bki9gb1RzttI+xKW+Bef80Y6EiNR9l4u+mBhy8RRdBumnQAFw==",
)
use_repo(pnpm, "pnpm")
@@ -86,7 +86,6 @@ npm.npm_translate_lock(
"//adev:package.json",
"//adev/shared-docs:package.json",
"//adev/shared-docs/pipeline/api-gen:package.json",
"//devtools:package.json",
"//integration:package.json",
"//modules:package.json",
"//packages/animations:package.json",
@@ -110,11 +109,7 @@ npm.npm_translate_lock(
"//tools/bazel/rules_angular_store:package.json",
"//vscode-ng-language-service/integration/project:package.json",
],
lifecycle_hooks = {
# sleep requires node-gyp rebuild but the native module is not needed in Bazel;
# disable the install hook to avoid failing builds when node-gyp is unavailable.
"sleep": [],
},
npmrc = "//:.npmrc",
pnpm_lock = "//:pnpm-lock.yaml",
)
use_repo(npm, "npm")
@@ -122,20 +117,22 @@ use_repo(npm, "npm")
rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext")
rules_ts_ext.deps(
name = "angular_npm_typescript",
# Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.2 | jq -r '.dist.integrity'
ts_integrity = "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
ts_version = "6.0.3",
# Obtained by: curl --silent https://registry.npmjs.org/typescript/5.9.3 | jq -r '.dist.integrity'
ts_integrity = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
ts_version = "5.9.3",
)
use_repo(rules_ts_ext, **{"npm_typescript": "angular_npm_typescript"})
# TODO: Figure out how to make ng_project update whenever the packages/core::pkg target changes.
rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular")
rules_angular.setup(
name = "angular_rules_angular_configurable_deps",
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
typescript = "//:node_modules/typescript",
use_repo_rule("@rules_angular//setup:repositories.bzl", "configurable_deps_repo")(
name = "rules_angular_configurable_deps",
angular_compiler_cli = "@angular//:node_modules/@angular/compiler-cli",
typescript = "@angular//:node_modules/typescript",
)
use_repo(rules_angular, rules_angular_configurable_deps = "angular_rules_angular_configurable_deps")
override_repo(rules_angular, "rules_angular_configurable_deps")
register_toolchains(
"@devinfra//bazel/git-toolchain:git_linux_toolchain",
@@ -163,3 +160,22 @@ cldr_xml_data(
"https://github.com/unicode-org/cldr/releases/download/release-%s/core.zip" % CLDR_VERSION: "d5ee2abac64158c04884a722f8ef4830ea22b6c74aac20185be2838db8eda788",
},
)
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Fetch sauce connect (tool to open Saucelabs tunnel for Saucelabs browser tests)
http_archive(
name = "sauce_connect_linux_amd64",
build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""",
sha256 = "26b9c3630f441b47854b6032f7eca6f1d88d3f62e50ee44c27015d71a5155c36",
strip_prefix = "sc-4.8.2-linux",
url = "https://saucelabs.com/downloads/sc-4.8.2-linux.tar.gz",
)
http_archive(
name = "sauce_connect_mac",
build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""",
sha256 = "28277ce81ef9ab84f5b87b526258920a8ead44789a5034346e872629bbf38089",
strip_prefix = "sc-4.8.2-osx",
url = "https://saucelabs.com/downloads/sc-4.8.2-osx.zip",
)
+3632 -358
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -67,7 +67,7 @@ Install the Angular CLI globally:
npm install -g @angular/cli
```
Create a workspace:
Create workspace:
```
ng new [PROJECT NAME]
+1 -3
View File
@@ -16,7 +16,6 @@ exports_files([
APPLICATION_FILES = [
"//adev/src/assets/images",
"//adev/src/assets/images/v21-event:v21-event-images",
"//adev/src/assets/images/v22-event:v22-event-images",
"//adev/src/assets/others",
"//adev/src/assets/previews",
"//adev/src/assets:tutorials",
@@ -71,6 +70,7 @@ APPLICATION_DEPS = [
":node_modules/fflate",
":node_modules/marked",
":node_modules/ngx-progressbar",
":node_modules/open-in-idx",
":node_modules/tailwindcss",
":node_modules/typescript",
":node_modules/@types/dom-navigation",
@@ -84,7 +84,6 @@ APPLICATION_DEPS = [
":node_modules/@algolia/client-common",
":node_modules/@algolia/requester-browser-xhr",
":node_modules/@algolia/requester-node-http",
":node_modules/beasties",
":node_modules/@lezer/css",
":node_modules/@lezer/html",
":node_modules/@lezer/lr",
@@ -98,7 +97,6 @@ APPLICATION_DEPS = [
":node_modules/@shikijs/langs",
":node_modules/@shikijs/engine-oniguruma",
":node_modules/@shikijs/core",
":node_modules/@shikijs/primitive",
":node_modules/@shikijs/engine-javascript",
":node_modules/@shikijs/types",
":node_modules/@shikijs/vscode-textmate",
+9 -6
View File
@@ -7,7 +7,9 @@
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
"style": "scss",
"standalone": true,
"changeDetection": "OnPush"
}
},
"root": ".",
@@ -31,8 +33,7 @@
"src/robots.txt",
"src/llms.txt",
"src/assets",
"src/assets/images/v21-event",
"src/assets/images/v22-event"
"src/assets/images/v21-event"
],
"styles": ["@angular/docs/styles/global-styles.scss", "./src/local-styles.scss"],
"scripts": [],
@@ -70,16 +71,18 @@
"buildTarget": "web-ui:build",
"headers": {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "credentialless"
"Cross-Origin-Embedder-Policy": "require-corp"
}
}
},
"test": {
"builder": "@angular/build:unit-test",
"options": {
"options": {
"runner": "karma",
"browsers": ["ChromeHeadlessNoSandbox"],
"include": ["src/app/**/*.spec.ts"]
"include": [
"src/app/**/*.spec.ts"
]
}
}
}
+2 -2
View File
@@ -49,7 +49,7 @@
"headers": [
{
"key": "Cross-Origin-Embedder-Policy",
"value": "credentialless"
"value": "require-corp"
}
]
},
@@ -71,7 +71,7 @@
},
{
"key": "Cross-Origin-Embedder-Policy",
"value": "credentialless"
"value": "require-corp"
}
]
}
+54 -55
View File
@@ -1,105 +1,104 @@
{
"dependencies": {
"@algolia/client-common": "5.57.0",
"@algolia/client-search": "5.57.0",
"@algolia/requester-browser-xhr": "5.57.0",
"@algolia/requester-node-http": "5.57.0",
"@algolia/client-common": "5.48.0",
"@algolia/client-search": "5.48.0",
"@algolia/requester-browser-xhr": "5.48.0",
"@algolia/requester-node-http": "5.48.0",
"@angular/animations": "workspace:*",
"@angular/aria": "22.2.0-next.5",
"@angular/build": "22.2.0-next.7",
"@angular/cdk": "22.2.0-next.5",
"@angular/cli": "22.2.0-next.7",
"@angular/aria": "21.2.0",
"@angular/build": "21.2.0",
"@angular/cdk": "21.2.0",
"@angular/cli": "21.2.0",
"@angular/common": "workspace:*",
"@angular/compiler": "workspace:*",
"@angular/compiler-cli": "workspace:*",
"@angular/core": "workspace:*",
"@angular/docs": "workspace:*",
"@angular/forms": "workspace:*",
"@angular/material": "22.2.0-next.5",
"@angular/material": "21.2.0",
"@angular/platform-browser": "workspace:*",
"@angular/platform-server": "workspace:*",
"@angular/router": "workspace:*",
"@angular/ssr": "22.2.0-next.7",
"@codemirror/autocomplete": "6.20.3",
"@codemirror/commands": "6.11.0",
"@angular/ssr": "21.2.0",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.1",
"@codemirror/lang-angular": "0.1.4",
"@codemirror/lang-css": "6.3.1",
"@codemirror/lang-html": "6.4.12",
"@codemirror/lang-javascript": "6.2.5",
"@codemirror/lang-html": "6.4.11",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-sass": "6.0.2",
"@codemirror/language": "6.12.4",
"@codemirror/lint": "6.9.7",
"@codemirror/search": "6.7.1",
"@codemirror/state": "6.7.1",
"@codemirror/view": "6.43.9",
"@lezer/common": "1.5.2",
"@lezer/css": "1.3.6",
"@codemirror/language": "6.12.1",
"@codemirror/lint": "6.9.3",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.12",
"@lezer/common": "1.5.1",
"@lezer/css": "1.3.0",
"@lezer/highlight": "1.2.3",
"@lezer/html": "1.3.13",
"@lezer/javascript": "1.5.4",
"@lezer/lr": "1.4.10",
"@lezer/lr": "1.4.8",
"@lezer/sass": "1.1.0",
"@marijn/find-cluster-break": "1.0.4",
"@shikijs/core": "^4.0.0",
"@shikijs/engine-javascript": "^4.0.0",
"@shikijs/engine-oniguruma": "^4.0.0",
"@shikijs/langs": "^4.0.0",
"@shikijs/primitive": "^4.0.0",
"@shikijs/themes": "^4.0.0",
"@shikijs/types": "^4.0.0",
"@marijn/find-cluster-break": "1.0.2",
"@shikijs/core": "^3.21.0",
"@shikijs/engine-javascript": "^3.21.0",
"@shikijs/engine-oniguruma": "^3.21.0",
"@shikijs/langs": "^3.21.0",
"@shikijs/themes": "^3.21.0",
"@shikijs/types": "^3.21.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@stackblitz/sdk": "1.11.1",
"@types/dom-navigation": "1.0.7",
"@stackblitz/sdk": "1.11.0",
"@types/dom-navigation": "1.0.6",
"@types/jasmine": "6.0.0",
"@types/jsdom": "30.0.0",
"@types/node": "24.13.3",
"@typescript/vfs": "1.6.4",
"@webcontainer/api": "1.6.4",
"@types/jsdom": "27.0.0",
"@types/node": "24.10.11",
"@typescript/vfs": "1.6.2",
"@webcontainer/api": "1.6.1",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"algoliasearch": "5.57.0",
"algoliasearch": "5.48.0",
"angular-split": "20.0.0",
"beasties": "0.5.4",
"ccount": "^2.0.1",
"character-entities-html4": "^2.1.0",
"character-entities-legacy": "^3.0.0",
"comma-separated-tokens": "^2.0.3",
"crelt": "1.0.7",
"diff": "9.0.0",
"crelt": "1.0.6",
"diff": "8.0.3",
"emoji-regex": "10.6.0",
"fflate": "0.8.3",
"fflate": "0.8.2",
"hast-util-to-html": "^9.0.5",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"jasmine-core": "6.3.0",
"jsdom": "30.0.1",
"jsdom": "28.0.0",
"jasmine-core": "6.1.0",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.3.0",
"marked": "18.0.11",
"mermaid": "11.17.2",
"karma-jasmine-html-reporter": "2.2.0",
"marked": "17.0.1",
"mermaid": "11.12.2",
"ngx-progressbar": "14.0.0",
"playwright-core": "1.62.1",
"preact": "10.29.8",
"preact-render-to-string": "6.7.0",
"prettier": "3.9.6",
"open-in-idx": "0.1.1",
"playwright-core": "1.58.1",
"preact": "10.28.3",
"preact-render-to-string": "6.6.6",
"prettier": "3.8.1",
"property-information": "^7.1.0",
"rxjs": "7.8.2",
"shiki": "4.4.3",
"shiki": "3.22.0",
"space-separated-tokens": "^2.0.2",
"stringify-entities": "^4.0.4",
"style-mod": "4.1.3",
"tinyglobby": "0.2.17",
"tinyglobby": "0.2.15",
"tslib": "2.8.1",
"typescript": "6.0.3",
"typescript": "5.9.3",
"w3c-keyname": "2.2.8",
"zwitch": "^2.0.4"
},
"devDependencies": {
"autoprefixer": "10.5.4",
"autoprefixer": "10.4.24",
"karma": "~6.4.4",
"postcss": "8.5.26",
"postcss": "8.5.6",
"tailwindcss": "3.4.19"
}
}
-7
View File
@@ -51,11 +51,4 @@ js_run_binary(
outs = ["defined-routes.json"],
chdir = package_name(),
tool = ":generate_route",
# Public so the api-gen rendering pipeline (used from //packages/...) can validate guide
# links against the same set of routes defined for the live site.
visibility = [
"//adev:__subpackages__",
"//packages:__subpackages__",
"//tools:__subpackages__",
],
)
+22 -21
View File
@@ -7,7 +7,7 @@
*/
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries/index.js';
import {extractHeadingIds, findDuplicateIds} from '../../shared-docs/pipeline/shared/heading.mjs';
import {getIdFromHeading} from '../../shared-docs/pipeline/shared/heading.mjs';
import {NavigationItem} from '@angular/docs';
import {writeFileSync, readFileSync} from 'fs';
import {join, resolve} from 'path';
@@ -20,7 +20,7 @@ const contentRoot = resolve(process.cwd(), '../../src/content');
* in a JSON file. This file then used by other bazel targets to know which routes are valid.
*/
function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, string[]>): string[] {
function extractRoutes(items: NavigationItem[]): string[] {
const routes: string[] = [];
for (const item of items) {
if (item.path && !item.path.startsWith('http')) {
@@ -29,11 +29,7 @@ function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, st
const content = readFileSync(join(contentRoot, `${item.contentPath}.md`), {
encoding: 'utf-8',
});
const headings = extractHeadingIds(content);
const duplicates = findDuplicateIds(headings);
if (duplicates.length > 0) {
duplicatesByPage.set(`${item.contentPath}.md`, duplicates);
}
const headings = extractHeadings(content);
routes.push(
...headings.map(
(heading) => `${item.path}#${heading.toLowerCase().replace(/\s+/g, '-')}`,
@@ -42,28 +38,33 @@ function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, st
}
}
if (item.children) {
routes.push(...extractRoutes(item.children, duplicatesByPage));
routes.push(...extractRoutes(item.children));
}
}
return routes;
}
function extractHeadings(content: string): string[] {
const headings = content
.split('\n')
// Top level heading (H1) are used for the page title only
// and yes, headings can have leading spaces
.filter((line) => line.trim().startsWith('##'))
.map((line) => line.replace(/^#+\s*/, '').trim());
const stepRegex = /<docs-step[^>]*title="([^"]*)"/g;
let match;
while ((match = stepRegex.exec(content)) !== null) {
headings.push(match[1]);
}
return headings.map((heading: string) => getIdFromHeading(heading));
}
function main() {
const allRoutes: string[] = [];
const duplicatesByPage = new Map<string, string[]>();
allRoutes.push(...extractRoutes(ALL_ITEMS, duplicatesByPage));
if (duplicatesByPage.size > 0) {
const details = Array.from(duplicatesByPage)
.map(([page, ids]) => ` ${page}: ${ids.map((id) => `#${id}`).join(', ')}`)
.join('\n');
throw new Error(
`Headings must produce a unique anchor id within a page, otherwise every link to the ` +
`anchor resolves to the first heading that claims it. Give the later heading its own ` +
`id with the \`{#custom-id}\` syntax.\n${details}`,
);
}
allRoutes.push(...extractRoutes(ALL_ITEMS));
const uniqueRoutes = Array.from(new Set(allRoutes.filter((r) => !!r)));
+1
View File
@@ -2,6 +2,7 @@
"compileOnSave": false,
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
@@ -10,8 +10,6 @@ import {get} from 'node:https';
import {posix} from 'node:path';
const GITHUB_API = 'https://api.github.com/repos/';
const SHA_REGEX = /^[0-9a-f]{40}$/i;
const BRANCH_REGEX = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
export class GithubClient {
#token;
@@ -32,12 +30,6 @@ export class GithubClient {
* @returns Promise<string[]>
*/
async getAffectedFiles(baseSha, headSha) {
if (!SHA_REGEX.test(baseSha)) {
throw new Error(`Invalid base SHA: ${baseSha}`);
}
if (!SHA_REGEX.test(headSha)) {
throw new Error(`Invalid head SHA: ${headSha}`);
}
const {files} = JSON.parse(await this.#httpGet(`${this.#api}/compare/${baseSha}...${headSha}`));
return files.map((f) => f.filename);
}
@@ -49,9 +41,6 @@ export class GithubClient {
* @returns Promise<string>
*/
async getShaForBranch(branch) {
if (!BRANCH_REGEX.test(branch)) {
throw new Error(`Invalid branch name: ${branch}`);
}
const sha = await this.#httpGet(`${this.#api}/commits/${branch}`, {
headers: {Accept: 'application/vnd.github.VERSION.sha'},
});
@@ -60,7 +49,7 @@ export class GithubClient {
throw new Error(`Unable to extract the SHA for '${branch}'.`);
}
return sha.trim();
return sha;
}
#httpGet(url, options = {}) {
@@ -8,7 +8,7 @@
//tslint:disable:no-console
import assert from 'node:assert';
import {execFileSync} from 'node:child_process';
import {execSync} from 'node:child_process';
import {existsSync, constants as fsConstants} from 'node:fs';
import {
copyFile,
@@ -41,16 +41,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
await readFile(buildInfoPath, 'utf-8'),
);
const shaRegex = /^[0-9a-f]{40}$/i;
const branchRegex = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
if (!shaRegex.test(storedSha)) {
throw new Error(`Invalid SHA in build info: ${storedSha}`);
}
if (!branchRegex.test(storedBranch)) {
throw new Error(`Invalid branch name in build info: ${storedBranch}`);
}
assert(process.env.ANGULAR_READONLY_GITHUB_TOKEN);
const githubApi = new GithubClient(
repo,
@@ -73,10 +63,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
downstreamBranch = storedBranch;
}
if (!shaRegex.test(latestSha)) {
throw new Error(`Invalid SHA resolved: ${latestSha}`);
}
console.log(`Comparing ${storedSha}...${latestSha}.`);
const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
@@ -92,18 +78,14 @@ export async function updateAssets({repo, assetsPath, destPath}) {
try {
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
execFileSync('git', ['init'], execOptions);
execFileSync(
'git',
['remote', 'add', 'origin', `https://github.com/${repo}.git`],
execOptions,
);
execSync('git init', execOptions);
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
// fetch a commit
execFileSync('git', ['fetch', 'origin', latestSha], execOptions);
execSync(`git fetch origin ${latestSha}`, execOptions);
// reset this repository's main branch to the commit of interest
execFileSync('git', ['reset', '--hard', 'FETCH_HEAD'], execOptions);
execSync('git reset --hard FETCH_HEAD', execOptions);
// get sha when files where changed
shaWhenFilesChanged = execFileSync('git', ['rev-list', '-1', latestSha, `${assetsPath}/`], {
shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
encoding: 'utf8',
cwd: temporaryDir,
stdio: ['ignore', 'pipe', 'ignore'],
@@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component} from '@angular/core';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'docs-algolia-icon',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './algolia-icon.component.html',
})
export class AlgoliaIcon {}
@@ -6,16 +6,17 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component, computed, inject} from '@angular/core';
import {RouterLink} from '@angular/router';
import {NavigationItem} from '../../interfaces/index';
import {ChangeDetectionStrategy, Component, inject, computed} from '@angular/core';
import {NavigationState} from '../../services/index';
import {NavigationItem} from '../../interfaces/index';
import {RouterLink} from '@angular/router';
@Component({
selector: 'docs-breadcrumb',
imports: [RouterLink],
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Breadcrumb {
private readonly navigationState = inject(NavigationState);
@@ -1,16 +1,8 @@
@if (!hasAccepted()) {
<div class="docs-cookies-popup docs-invert-mode">
<div class="docs-cookies-popup-header">
<img
class="docs-cookies-popup-angie"
src="assets/images/angie/greeting.svg"
alt=""
aria-hidden="true"
/>
<p>This site uses cookies from Google to deliver its services and to analyze traffic.</p>
</div>
<p>This site uses cookies from Google to deliver its services and to analyze traffic.</p>
<div class="docs-cookies-popup-actions">
<div>
<a
href="https://policies.google.com/technologies/cookies"
target="_blank"
@@ -15,34 +15,15 @@
border-radius: 0.25rem;
font-size: 0.875rem;
max-width: 265px;
transition:
background-color 0.3s ease,
border-color 0.3s ease,
color 0.3s ease;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
.docs-cookies-popup-header {
display: flex;
gap: 0.75rem;
align-items: center;
}
.docs-cookies-popup-angie {
flex-shrink: 0;
width: 52px;
height: auto;
}
.docs-cookies-popup-actions {
> div {
display: flex;
gap: 0.5rem;
align-items: center;
width: 100%;
margin-block-start: 1rem;
.docs-primary-btn {
flex: 1;
}
}
p {
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component, inject, signal} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
import {LOCAL_STORAGE} from '../../providers/index';
import {setCookieConsent} from '../../utils';
@@ -21,6 +21,7 @@ export const STORAGE_KEY = 'docs-accepts-cookies';
selector: 'docs-cookie-popup',
templateUrl: './cookie-popup.component.html',
styleUrls: ['./cookie-popup.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CookiePopup {
private readonly localStorage = inject(LOCAL_STORAGE);
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component, inject, input, signal} from '@angular/core';
import {Clipboard} from '@angular/cdk/clipboard';
import {Component, inject, input, signal} from '@angular/core';
import {MatTooltip} from '@angular/material/tooltip';
import {IconComponent} from '../icon/icon.component';
@@ -15,7 +15,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
@Component({
selector: 'docs-copy-link-button',
template: `<docs-icon></docs-icon>`,
template: `<docs-icon>{{ showCopySuccess() ? 'check' : 'link' }}</docs-icon>`,
styles: `
:host {
cursor: pointer;
@@ -31,13 +31,6 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
:host(.docs-copy-link-success) {
color: var(--bright-blue);
}
docs-icon::before {
content: '\\e250'; /* codepoint for "link" */
font-family: 'Material Symbols Outlined';
}
:host(.docs-copy-link-success) docs-icon::before {
content: '\\e668'; /* codepoint for "check" */
}
`,
hostDirectives: [
{
@@ -52,6 +45,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
'[class.docs-copy-link-success]': 'showCopySuccess()',
},
imports: [IconComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CopyLinkButton {
readonly href = input.required<string>();
@@ -8,13 +8,13 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Clipboard} from '@angular/cdk/clipboard';
import {Component, signal} from '@angular/core';
import {By} from '@angular/platform-browser';
import {
CONFIRMATION_DISPLAY_TIME_MS,
CopySourceCodeButton,
} from './copy-source-code-button.component';
import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
import {By} from '@angular/platform-browser';
import {Clipboard} from '@angular/cdk/clipboard';
const SUCCESSFULLY_COPY_CLASS_NAME = 'docs-copy-source-code-button-success';
const FAILED_COPY_CLASS_NAME = 'docs-copy-source-code-button-failed';
@@ -99,6 +99,7 @@ describe('CopySourceCodeButton', () => {
<button docs-copy-source-code></button>
`,
imports: [CopySourceCodeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
})
class CodeSnippetWrapper {
code = signal('');
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Clipboard} from '@angular/cdk/clipboard';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
@@ -15,6 +15,7 @@ import {
inject,
signal,
} from '@angular/core';
import {Clipboard} from '@angular/cdk/clipboard';
import {IconComponent} from '../icon/icon.component';
export const REMOVED_LINE_CLASS_NAME = '.line.remove';
@@ -32,6 +33,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 2000;
'[class.docs-copy-source-code-button-success]': 'showCopySuccess()',
'[class.docs-copy-source-code-button-failed]': 'showCopyFailure()',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CopySourceCodeButton {
private readonly changeDetector = inject(ChangeDetectorRef);
@@ -7,10 +7,18 @@
*/
import {DOCUMENT} from '@angular/common';
import {Component, afterNextRender, computed, inject, signal} from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
afterNextRender,
computed,
inject,
signal,
} from '@angular/core';
@Component({
selector: 'docs-icon',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'class': 'material-symbols-outlined',
'[style.font-size.px]': 'fontSize()',
@@ -24,7 +24,6 @@ ng_project(
"//adev/shared-docs/interfaces",
"//adev/shared-docs/pipes",
"//adev/shared-docs/services",
"//adev/shared-docs/utils",
],
)
@@ -9,17 +9,11 @@
>
@for (itemGroup of groupItems(navigationItems, preserveOtherCategoryOrder); track $index) {
@let groupLabel = itemGroup[0];
@let group = itemGroup[1];
@let items = group.items;
@let items = itemGroup[1];
@let firstItem = items[0];
@if (groupLabel && collapsableLevel() !== firstItem.level && items.length > 1) {
<li class="docs-navigation-group">
<span>{{ groupLabel }}</span>
<ng-container
*ngTemplateOutlet="itemStatus; context: {$implicit: {status: group.status}}"
/>
</li>
<li class="docs-navigation-group">{{ groupLabel }}</li>
}
@for (item of items; track $index) {
<li
@@ -33,7 +27,7 @@
[href]="item.path"
target="_blank"
[matTooltip]="item.label"
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
[matTooltipDisabled]="itemLabel.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
@@ -59,9 +53,9 @@
matrixParams: 'ignored',
fragment: 'ignored',
}"
(click)="emitClickOnLink(item)"
(click)="emitClickOnLink()"
[matTooltip]="item.label"
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
[matTooltipDisabled]="itemLabel.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
@@ -82,7 +76,7 @@
<div
class="docs-secondary-nav-header"
[matTooltip]="item.label"
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
[matTooltipDisabled]="itemLabel.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
@@ -112,7 +106,7 @@
item.children && item.level === expandableLevel() && !item.isExpanded
"
[matTooltip]="item.label"
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
[matTooltipDisabled]="itemLabel.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
@@ -6,7 +6,7 @@
list-style: none;
overflow-y: auto;
overflow-x: hidden;
height: 100dvh;
height: 100vh;
padding: 0;
margin: 0;
padding-block: 1.5rem;
@@ -162,12 +162,11 @@ a,
}
}
.docs-external-link.docs-faceted-list-item-text {
.docs-external-link {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 100%;
gap: 0.5rem;
&::after {
content: 'open_in_new';
@@ -175,11 +174,7 @@ a,
font-size: 1.1rem;
color: var(--quinary-contrast);
transition: color 0.3s ease;
margin-inline-end: 0.2rem;
}
a:hover &::after {
color: var(--primary-contrast);
margin-inline-end: 0.4rem;
}
}
@@ -193,12 +188,8 @@ a,
}
.docs-navigation-group {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.5rem 0.5rem 1rem;
background: var(--septenary-contrast);
&:not(:first-child) {
margin-top: 2rem;
}
@@ -31,32 +31,6 @@ const navigationItems: NavigationItem[] = [
},
];
const navigationItemsWithCategoryStatus: NavigationItem[] = [
{
label: 'Forms',
level: 1,
categoriesStatus: [{'Signal Forms': 'new'}],
children: [],
},
];
navigationItemsWithCategoryStatus[0].children = [
{
label: 'Overview',
path: 'guide/forms',
level: 2,
category: 'Signal Forms',
parent: navigationItemsWithCategoryStatus[0],
},
{
label: 'Signals',
path: 'guide/forms/signals',
level: 2,
category: 'Signal Forms',
parent: navigationItemsWithCategoryStatus[0],
},
];
describe('NavigationList', () => {
let component: NavigationList;
let fixture: ComponentFixture<NavigationList>;
@@ -84,20 +58,6 @@ describe('NavigationList', () => {
expect(nonClickableItem.length).toBe(1);
});
it('should show category status on grouped navigation headers', async () => {
fixture.componentRef.setInput('navigationItems', [...navigationItemsWithCategoryStatus]);
await fixture.whenStable();
const categoryGroup = fixture.debugElement.query(By.css('.docs-navigation-group'));
const categoryStatus = fixture.debugElement.query(
By.css('.docs-navigation-group .docs-new-item'),
);
expect(categoryGroup.nativeElement.innerText).toContain('Signal Forms');
expect(categoryStatus).toBeTruthy();
expect(categoryStatus.nativeElement.innerText).toBe('New');
});
it('should append `docs-navigation-list-dropdown` when isDropdownView is true', async () => {
fixture.componentRef.setInput('isDropdownView', true);
await fixture.whenStable();
@@ -193,6 +153,5 @@ describe('NavigationList', () => {
class FakeNavigationListState {
isOpened = signal(true);
activeNavigationItem = signal(navigationItems.at(1));
crossCategoryOrigin = signal<NavigationItem | undefined>(undefined);
toggleItem(item: NavigationItem) {}
}
@@ -7,13 +7,12 @@
*/
import {NgTemplateOutlet} from '@angular/common';
import {Component, inject, input, output} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject, input, output} from '@angular/core';
import {MatTooltip} from '@angular/material/tooltip';
import {RouterLink, RouterLinkActive} from '@angular/router';
import {NavigationItem} from '../../interfaces/index';
import {IsActiveNavigationItem} from '../../pipes';
import {NavigationState} from '../../services/index';
import {isMobile} from '../../utils';
import {IconComponent} from '../icon/icon.component';
@Component({
@@ -28,6 +27,7 @@ import {IconComponent} from '../icon/icon.component';
],
templateUrl: './navigation-list.component.html',
styleUrls: ['./navigation-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavigationList {
readonly navigationItems = input.required<NavigationItem[]>();
@@ -39,15 +39,10 @@ export class NavigationList {
readonly linkClicked = output<void>();
protected readonly labelTruncationThreshold = 27;
private readonly navigationState = inject(NavigationState);
private readonly crossCategoryOrigin = this.navigationState.crossCategoryOrigin;
readonly activeItem = this.navigationState.activeNavigationItem;
protected readonly isMobile = isMobile;
toggle(item: NavigationItem): void {
if (
item.level === 1 &&
@@ -56,19 +51,10 @@ export class NavigationList {
) {
return;
}
const prevParentItem = this.crossCategoryOrigin();
if (prevParentItem) {
this.crossCategoryOrigin.set(undefined);
this.navigationState.toggleItem(prevParentItem);
return;
}
this.navigationState.toggleItem(item);
}
emitClickOnLink(item: NavigationItem): void {
if (item.isCrossReferenced) {
this.crossCategoryOrigin.set(item.parent);
}
emitClickOnLink(): void {
this.linkClicked.emit();
}
@@ -76,27 +62,14 @@ export class NavigationList {
return items.some((item) => !!item.category);
}
private getCategoryStatus(item: NavigationItem, category: string): 'new' | 'updated' | undefined {
const categoriesStatus = item.parent?.categoriesStatus;
if (!categoriesStatus) {
return undefined;
}
return categoriesStatus.find((status) => status[category])?.[category];
}
protected groupItems(
items: NavigationItem[],
preserveOtherCategoryOrder: boolean,
): Map<string, {items: NavigationItem[]; status: 'new' | 'updated' | undefined}> {
): Map<string, NavigationItem[]> {
const hasCategories = this.hasCategories(items);
if (hasCategories) {
const others: NavigationItem[] = [];
const categorizedItems = new Map<
string,
{items: NavigationItem[]; status: 'new' | 'updated' | undefined}
>();
const categorizedItems = new Map<string, NavigationItem[]>();
for (const item of items) {
const category = item.category || 'Other';
if (!preserveOtherCategoryOrder && category === 'Other') {
@@ -104,20 +77,16 @@ export class NavigationList {
continue;
}
if (!categorizedItems.has(category)) {
categorizedItems.set(category, {
items: [],
status: this.getCategoryStatus(item, category),
});
categorizedItems.set(category, []);
}
categorizedItems.get(category)!.items.push(item);
categorizedItems.get(category)!.push(item);
}
if (others.length) {
categorizedItems.set('Other', {items: others, status: undefined});
categorizedItems.set('Other', others);
}
return categorizedItems;
} else {
return new Map([['', {items, status: undefined}]]);
return new Map([['', items]]);
}
}
}
@@ -29,7 +29,6 @@ ng_project(
"//adev/shared-docs/pipes",
"//adev/shared-docs/providers",
"//adev/shared-docs/services",
"//adev/shared-docs/utils",
],
)
@@ -1,4 +1,4 @@
<dialog #searchDialog id="docsSearchDialog">
<dialog #searchDialog>
<div class="docs-search-container" (docsClickOutside)="closeSearchDialog()">
<docs-text-field
[autofocus]="true"
@@ -27,16 +27,14 @@
</i>
</span>
<!-- Page title and package badge -->
<span class="docs-search-result__label__text">
<span [innerHtml]="result.labelHtml"></span>
@if (result.package) {
<span
[innerHTML]="result.package"
class="docs-search-result__label__package"
></span>
}
</span>
<!-- Page title -->
<span [innerHtml]="result.labelHtml"></span>
@if (result.package) {
<span
[innerHTML]="result.package"
class="docs-search-result__label__package"
></span>
}
</p>
@if (result.subLabelHtml) {
@@ -65,22 +63,12 @@
<docs-search-history />
} @else {
<div class="docs-search-results docs-mini-scroll-track">
@if (emptyState() === 'start') {
@if (!resultsResource.hasValue()) {
<div class="docs-search-results__start-typing">
<img
src="assets/images/angie/magnifying-glass.svg"
class="docs-search-angie"
aria-hidden="true"
/>
<span>Start typing to see results</span>
</div>
} @else {
} @else if (searchResults().length === 0) {
<div class="docs-search-results__no-results">
<img
src="assets/images/angie/question.svg"
class="docs-search-angie"
aria-hidden="true"
/>
<span>No results found</span>
</div>
}
@@ -46,7 +46,6 @@ dialog {
.docs-search-result-icon {
display: inline-block;
flex-shrink: 0;
i {
display: flex;
@@ -92,20 +91,7 @@ dialog {
&__label {
font-weight: 600;
&__text {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.25rem 0.5rem;
flex: 1;
min-width: 0;
overflow-wrap: break-word;
> * {
min-width: 0;
}
}
flex-wrap: wrap;
&__package {
font-size: 0.75rem;
@@ -150,20 +136,10 @@ dialog {
.docs-search-results__start-typing,
.docs-search-results__no-results {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.75rem;
color: var(--quaternary-contrast);
}
.docs-search-angie {
width: 210px;
height: auto;
aspect-ratio: 1;
margin-block: -1rem -0.5rem;
}
.docs-search-footer {
display: flex;
align-items: center;
@@ -14,7 +14,7 @@ import {Router, provideRouter} from '@angular/router';
import {SearchDialog} from './search-dialog.component';
import {ENVIRONMENT, WINDOW} from '../../providers';
import {ALGOLIA_CLIENT, Search} from '../../services';
import {FakeEventTarget, timeout, useAutoTick} from '../../testing/index';
import {FakeEventTarget} from '../../testing/index';
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
import {SearchResult} from '../../interfaces';
@@ -27,10 +27,8 @@ describe('SearchDialog', () => {
let search: Search;
useAutoTick();
beforeEach(async () => {
searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]}));
searchResults.and.returnValue([]);
TestBed.configureTestingModule({
imports: [SearchDialog],
@@ -57,9 +55,6 @@ describe('SearchDialog', () => {
// Fire the request
TestBed.inject(ApplicationRef).tick();
// The delay from debounced (200ms)
await timeout(300);
// Wait for the resource to resolve
await TestBed.inject(ApplicationRef).whenStable();
@@ -90,9 +85,6 @@ describe('SearchDialog', () => {
// Fire the request
TestBed.inject(ApplicationRef).tick();
// The delay from debounced (200ms)
await timeout(300);
// Wait for the resource to resolve
await TestBed.inject(ApplicationRef).whenStable();
@@ -104,7 +96,7 @@ describe('SearchDialog', () => {
});
it('should display `Start typing to see results` message when there are no provided query', () => {
searchResults.and.returnValue(Promise.resolve(undefined));
searchResults.and.returnValue(undefined);
const startTypingContainer = fixture.debugElement.query(
By.css('.docs-search-results__start-typing'),
@@ -113,35 +105,6 @@ describe('SearchDialog', () => {
expect(startTypingContainer).toBeTruthy();
});
it('should keep the `No results found` message while re-querying instead of flickering back to `Start typing`', async () => {
const appRef = TestBed.inject(ApplicationRef);
// Settle on an empty result set so `No results found` is shown.
search.searchQuery.set('foobarbaz');
searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]}));
appRef.tick();
await timeout(300);
await appRef.whenStable();
expect(fixture.debugElement.query(By.css('.docs-search-results__no-results'))).toBeTruthy();
// Edit the query so a new search goes in flight and stays pending.
let resolveReload!: (value: unknown) => void;
searchResults.and.returnValue(new Promise((resolve) => (resolveReload = resolve)));
search.searchQuery.set('foobarba');
appRef.tick();
// The delay from debounced (200ms), after which the pending request is loading.
await timeout(300);
// While the re-query is loading it must not revert to the `Start typing` state.
expect(fixture.debugElement.query(By.css('.docs-search-results__start-typing'))).toBeNull();
expect(fixture.debugElement.query(By.css('.docs-search-results__no-results'))).toBeTruthy();
resolveReload({results: [{hits: []}]});
await appRef.whenStable();
});
it('should display list of the search results when results exist', async () => {
search.searchQuery.set('fakeQuery');
searchResults.and.returnValue(Promise.resolve({results: [{hits: fakeSearchResults}]}));
@@ -149,9 +112,6 @@ describe('SearchDialog', () => {
// Fire the request
TestBed.inject(ApplicationRef).tick();
// The delay from debounced (200ms)
await timeout(300);
// Wait for the resource to resolve
await TestBed.inject(ApplicationRef).whenStable();
@@ -8,6 +8,7 @@
import {
afterNextRender,
ChangeDetectionStrategy,
Component,
DestroyRef,
effect,
@@ -32,10 +33,10 @@ import {RelativeLink} from '../../pipes';
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
import {SearchHistoryComponent} from '../search-history/search-history.component';
import {TextField} from '../text-field/text-field.component';
import {getRelativeUrl} from '../../utils';
@Component({
selector: 'docs-search-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ClickOutside,
TextField,
@@ -56,7 +57,7 @@ export class SearchDialog {
readonly history = inject(SearchHistory);
private readonly search = inject(Search);
private readonly relativeLink = new RelativeLink();
private readonly router = inject(Router);
private readonly window = inject(WINDOW);
private readonly injector = inject(Injector);
@@ -67,7 +68,6 @@ export class SearchDialog {
readonly resultsResource = this.search.resultsResource;
readonly searchResults = this.search.searchResults;
readonly emptyState = this.search.emptyState;
searchForm = form(this.search.searchQuery);
@@ -122,7 +122,7 @@ export class SearchDialog {
return;
}
this.router.navigateByUrl(getRelativeUrl(activeItemLink));
this.router.navigateByUrl(this.relativeLink.transform(activeItemLink));
this.onClose.emit();
}
}
@@ -28,7 +28,6 @@ ng_project(
"//adev/shared-docs/directives",
"//adev/shared-docs/pipes",
"//adev/shared-docs/services",
"//adev/shared-docs/utils",
],
)
@@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {NgTemplateOutlet} from '@angular/common';
import {
afterNextRender,
ChangeDetectionStrategy,
Component,
DestroyRef,
effect,
@@ -17,13 +16,14 @@ import {
Injector,
viewChildren,
} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {Router, RouterLink} from '@angular/router';
import {toSignal} from '@angular/core/rxjs-interop';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {NgTemplateOutlet} from '@angular/common';
import {SearchItem} from '../../directives';
import {RelativeLink} from '../../pipes';
import {SearchHistory} from '../../services';
import {getRelativeUrl} from '../../utils';
import {RelativeLink} from '../../pipes';
import {SearchItem} from '../../directives';
@Component({
selector: 'docs-search-history',
@@ -34,6 +34,7 @@ import {getRelativeUrl} from '../../utils';
'(document:keydown)': 'onKeydown($event)',
'(document:mousemove)': 'onMouseMove($event)',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchHistoryComponent {
protected readonly items = viewChildren(SearchItem);
@@ -42,6 +43,7 @@ export class SearchHistoryComponent {
private readonly injector = inject(Injector);
private readonly router = inject(Router);
private readonly relativeLink = new RelativeLink();
private readonly keyManager = new ActiveDescendantKeyManager(
this.items,
this.injector,
@@ -99,7 +101,7 @@ export class SearchHistoryComponent {
const activeItemLink = this.keyManager.activeItem?.item()?.url;
if (activeItemLink) {
const url = getRelativeUrl(activeItemLink);
const url = this.relativeLink.transform(activeItemLink);
this.router.navigateByUrl(url);
}
}
@@ -16,7 +16,6 @@ ng_project(
],
deps = [
"//adev:node_modules/@angular/aria",
"//adev:node_modules/@angular/cdk",
"//adev:node_modules/@angular/common",
"//adev:node_modules/@angular/core",
"//adev:node_modules/@angular/forms",
@@ -1,94 +1,61 @@
<div ngCombobox #combobox="ngCombobox" class="docs-combobox-container" [(expanded)]="popupExpanded">
<div #origin class="docs-select-input-container">
<div ngCombobox #outerCombobox="ngCombobox" [readonly]="true" [disabled]="disabled()">
<div class="docs-select-input-container">
<input
ngComboboxInput
[attr.id]="id()"
[attr.name]="name()"
[value]="displayValue()"
placeholder="Select an option"
placeholder="Select an option..."
readonly
[tabindex]="-1"
/>
<span class="material-symbols-outlined docs-select-arrow" translate="no" aria-hidden="true"
>arrow_drop_down</span
>
</div>
<ng-template
[cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
[cdkConnectedOverlayOpen]="popupExpanded()"
[cdkConnectedOverlayDisableClose]="false"
(overlayOutsideClick)="popupExpanded.set(false)"
>
<ng-template ngComboboxPopup [combobox]="combobox" popupType="dialog">
<div class="docs-select-popover">
<div class="docs-select-dialog" ngComboboxWidget>
<div class="example-combobox-container">
<div class="docs-select-search-container">
<span
class="material-symbols-outlined docs-select-search-icon"
translate="no"
aria-hidden="true"
>search</span
>
<input
ngCombobox
#innerCombobox="ngCombobox"
#searchInput
class="docs-select-search-input"
placeholder="Search..."
[(value)]="searchString"
[alwaysExpanded]="true"
(keydown.escape)="onSearchEscape($event)"
/>
</div>
<div aria-live="polite" class="cdk-visually-hidden">
{{ filteredOptions().length === 0 ? 'No results found for ' + searchString() : '' }}
</div>
<ng-template ngComboboxPopup [combobox]="innerCombobox">
<div class="example-popup example-popup-no-margin">
@if (filteredOptions().length === 0) {
<div class="docs-select-no-results">
<img
src="assets/images/angie/question.svg"
class="docs-select-no-results-angie"
aria-hidden="true"
/>
<span>No results found</span>
</div>
}
<div
#listbox="ngListbox"
ngListbox
[(value)]="selectedValues"
[multi]="false"
ngComboboxWidget
class="docs-select-listbox"
focusMode="activedescendant"
tabindex="-1"
selectionMode="explicit"
(click)="onCommit()"
(keydown.enter)="onCommit()"
[activeDescendant]="listbox.activeDescendant()"
[class.example-empty]="filteredOptions().length === 0"
>
@for (option of filteredOptions(); track option.value) {
<div
ngOption
[value]="option.value"
[label]="option.label"
class="docs-select-option"
>
<span class="docs-select-option-label">{{ option.label }}</span>
</div>
}
</div>
</div>
</ng-template>
</div>
<ng-template ngComboboxPopupContainer>
<dialog ngComboboxDialog class="docs-select-dialog">
<div ngCombobox #innerCombobox="ngCombobox" filterMode="manual" [alwaysExpanded]="true">
<div class="docs-select-search-container">
<span
class="material-symbols-outlined docs-select-search-icon"
translate="no"
aria-hidden="true"
>search</span
>
<input
ngComboboxInput
class="docs-select-search-input"
placeholder="Search..."
[(value)]="searchString"
/>
</div>
<ng-template ngComboboxPopupContainer>
@if (filteredOptions().length === 0) {
<div class="docs-select-no-results">No results found</div>
}
<div ngListbox [(values)]="selectedValues" class="docs-select-listbox">
@for (option of filteredOptions(); track option.value) {
<div
ngOption
[value]="option.value"
[label]="option.label"
class="docs-select-option"
>
<span class="docs-select-option-label">{{ option.label }}</span>
<span
class="material-symbols-outlined docs-select-check-icon"
translate="no"
aria-hidden="true"
>check</span
>
</div>
}
</div>
</ng-template>
</div>
</ng-template>
</dialog>
</ng-template>
</div>
@@ -1,16 +1,14 @@
:host {
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngCombobox] {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
}
.docs-combobox-container {
border: 1px solid var(--border-color);
border-radius: 0.25rem;
overflow: hidden;
}
.docs-select-input-container {
@@ -18,25 +16,15 @@
position: relative;
align-items: center;
border-radius: 0.25rem;
}
input {
width: 100%;
border: none;
outline: none;
font-size: 1rem;
padding: 0.7rem 1rem 0.7rem;
background-color: var(--septenary-contrast);
color: var(--primary-contrast);
[ngComboboxInput] {
border-radius: 0.25rem;
}
&[readonly] {
cursor: pointer;
padding: 0.7rem 1rem;
}
&[aria-expanded='true'] + .docs-select-arrow {
transform: rotate(180deg);
}
}
[ngComboboxInput][readonly='true'] {
cursor: pointer;
padding: 0.7rem 1rem;
}
[ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) {
@@ -44,10 +32,6 @@
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent);
}
.docs-select-popover {
width: 100%;
}
.docs-select-arrow {
width: 24px;
height: 24px;
@@ -62,19 +46,22 @@
transition: transform 0.2s ease;
}
[ngComboboxInput] [ngCombobox] {
[ngComboboxInput][aria-expanded='true'] + .docs-select-arrow {
transform: rotate(180deg);
}
[ngComboboxInput] {
width: 100%;
border: none;
outline: none;
font-size: 1rem;
padding: 0.7rem 1rem 0.7rem;
padding: 0.7rem 1rem 0.7rem 2.5rem;
background-color: var(--septenary-contrast);
color: var(--primary-contrast);
}
.docs-select-dialog {
position: absolute;
box-sizing: border-box;
width: 100%;
left: auto;
right: auto;
top: auto;
@@ -82,7 +69,6 @@
padding: 0;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
overflow: hidden;
background-color: var(--septenary-contrast);
color: inherit;
@@ -149,10 +135,6 @@
}
.docs-select-no-results {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.75rem;
text-align: center;
font-size: 0.875rem;
@@ -160,18 +142,11 @@
opacity: 0.7;
}
.docs-select-no-results-angie {
width: 88px;
height: auto;
aspect-ratio: 1;
}
.docs-select-listbox {
display: flex;
flex-direction: column;
max-height: 12rem;
overflow: auto;
overscroll-behavior: contain;
padding: 0.25rem;
}
@@ -196,8 +171,16 @@
color: var(--vivid-pink);
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}
&:not([aria-selected='true']) .docs-select-check-icon {
display: none;
}
}
.docs-select-option-label {
flex: 1;
}
.docs-select-check-icon {
font-size: 0.9rem;
}
@@ -1,4 +1,4 @@
/**
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
@@ -6,21 +6,28 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {
Combobox,
ComboboxDialog,
ComboboxInput,
ComboboxPopupContainer,
} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {
afterRenderEffect,
ChangeDetectionStrategy,
Component,
computed,
input,
model,
signal,
untracked,
viewChild,
} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';
import {FormsModule} from '@angular/forms';
type SelectOptionValue = string;
type SelectOptionValue = string | number | boolean;
export interface SelectOption {
label: string;
@@ -29,9 +36,18 @@ export interface SelectOption {
@Component({
selector: 'docs-select',
templateUrl: 'select.component.html',
styleUrl: 'select.component.css',
imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
Combobox,
ComboboxDialog,
ComboboxInput,
ComboboxPopupContainer,
FormsModule,
Listbox,
Option,
],
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
})
export class Select implements FormValueControl<string | null> {
readonly value = model<string | null>(null);
@@ -39,13 +55,14 @@ export class Select implements FormValueControl<string | null> {
readonly id = input.required<string>({alias: 'selectId'});
readonly name = input.required<string>();
readonly options = input.required<SelectOption[]>();
readonly disabled = input(false);
readonly listbox = viewChild(Listbox);
readonly combobox = viewChild(Combobox);
readonly dialog = viewChild(ComboboxDialog);
readonly listbox = viewChild<Listbox<SelectOptionValue>>(Listbox);
readonly combobox = viewChild<Combobox<SelectOptionValue>>(Combobox);
readonly searchString = signal('');
readonly popupExpanded = signal(false);
readonly filteredOptions = computed(() => {
const search = this.searchString().toLowerCase();
if (!search) {
@@ -67,36 +84,39 @@ export class Select implements FormValueControl<string | null> {
constructor() {
afterRenderEffect(() => {
this.listbox()?.scrollActiveItemIntoView();
if (this.dialog() && this.combobox()?.expanded()) {
untracked(() => this.listbox()?.gotoFirst());
this.positionDialog();
}
});
}
onCommit() {
const values = this.selectedValues();
if (values.length) {
this.value.set(values[0]);
this.popupExpanded.set(false);
afterRenderEffect(() => {
const selected = this.selectedValues();
if (selected.length > 0) {
untracked(() => this.dialog()?.close());
this.value.set(selected[0] as string);
this.searchString.set('');
}
});
afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView());
}
// TODO: Improve once CDK overlay is fixed https://github.com/angular/components/issues/32504
private positionDialog(): void {
const dialog = this.dialog();
const combobox = this.combobox();
if (!dialog || !combobox) {
return;
}
}
/** Dismisses the dialog overlay on Escape key. */
onSearchEscape(event: Event) {
this.popupExpanded.set(false);
this.combobox()?.element.focus();
}
const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
const scrollY = window.scrollY;
/** Handles keydown events on the clear button. */
onKeydown(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.clear();
this.popupExpanded.set(false);
event.stopPropagation();
if (comboboxRect) {
dialog.element.style.width = `${comboboxRect.width}px`;
dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`;
dialog.element.style.left = `${comboboxRect.left}px`;
}
}
/** Clears the search query and all selected options. */
clear(): void {
this.searchString.set('');
this.value.set(null);
}
}
@@ -6,11 +6,12 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component, input, model} from '@angular/core';
import {ChangeDetectionStrategy, Component, input, model} from '@angular/core';
import {FormCheckboxControl} from '@angular/forms/signals';
@Component({
selector: 'docs-slide-toggle',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './slide-toggle.component.html',
styleUrls: ['./slide-toggle.component.scss'],
})
@@ -8,6 +8,7 @@
import {
afterRenderEffect,
ChangeDetectionStrategy,
Component,
computed,
ElementRef,
@@ -32,6 +33,7 @@ let idCounter = 0;
host: {
class: 'docs-tab-group',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabGroup {
private readonly _renderer = inject(Renderer2);
@@ -6,14 +6,23 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
input,
inject,
afterNextRender,
signal,
} from '@angular/core';
import {Location, ViewportScroller} from '@angular/common';
import {afterNextRender, Component, DestroyRef, inject, input, signal} from '@angular/core';
import {TableOfContentsLevel} from '../../interfaces/index';
import {TableOfContentsLoader} from '../../services';
import {IconComponent} from '../icon/icon.component';
@Component({
selector: 'docs-table-of-contents',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './table-of-contents.component.html',
styleUrls: ['./table-of-contents.component.scss'],
imports: [IconComponent],
@@ -6,12 +6,21 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {afterNextRender, Component, ElementRef, input, model, viewChild} from '@angular/core';
import {
afterNextRender,
ChangeDetectionStrategy,
Component,
ElementRef,
input,
model,
viewChild,
} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';
import {IconComponent} from '../icon/icon.component';
@Component({
selector: 'docs-text-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [IconComponent],
templateUrl: './text-field.component.html',
styleUrls: ['./text-field.component.scss'],
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component, inject, input, linkedSignal} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject, input, linkedSignal} from '@angular/core';
import {ExternalLink} from '../../directives';
import {LOCAL_STORAGE} from '../../providers';
import {IconComponent} from '../icon/icon.component';
@@ -18,6 +18,7 @@ export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-';
imports: [ExternalLink, IconComponent],
templateUrl: './top-level-banner.component.html',
styleUrl: './top-level-banner.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TopLevelBannerComponent {
private readonly localStorage = inject(LOCAL_STORAGE);
@@ -71,7 +71,6 @@ ts_project(
"//adev/shared-docs/interfaces",
"//adev/shared-docs/providers",
"//adev/shared-docs/services",
"//adev/shared-docs/utils",
],
)
@@ -132,7 +132,6 @@
.docs-page-title {
display: flex;
justify-content: space-between;
align-items: baseline;
h1 {
margin-block: 0;
@@ -20,7 +20,6 @@ import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-co
import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component';
import {TableOfContents} from '../../table-of-contents/table-of-contents.component';
import {Clipboard} from '@angular/cdk/clipboard';
import {isFirefox} from '../../../utils';
describe('DocViewer', () => {
let exampleContentSpy: jasmine.SpyObj<ExampleViewerContentLoader>;
@@ -66,23 +65,6 @@ describe('DocViewer', () => {
</div>
`;
const exampleContentWithVideoFacade = `
<div class="docs-video-container">
<a
class="docs-video-facade"
href="https://www.youtube.com/watch?v=abc123&autoplay=1"
target="_blank"
rel="noopener"
aria-label="Play video: Test video"
data-video-src="https://www.youtube.com/embed/abc123"
data-video-title="Test video"
>
<img class="docs-video-thumbnail" src="https://i.ytimg.com/vi/abc123/maxresdefault.jpg" alt="" loading="lazy" />
<span class="docs-video-play-button" aria-hidden="true"></span>
</a>
</div>
`;
const exampleContentWithHeadings = `
<h2>Heading h2</h2>
<h3>Heading h3</h3>
@@ -238,16 +220,6 @@ describe('DocViewer', () => {
expect(copyButton).toBeTruthy();
});
it('should not leak any icon text content (so it stays out of the search index)', () => {
const fixture = TestBed.createComponent(CopyLinkButton);
fixture.componentRef.setInput('href', '#test-section');
fixture.componentRef.setInput('label', 'Test Section');
fixture.componentRef.setInput('matTooltip', 'Copy link to Test Section');
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('');
});
it('should copy link to clipboard when copy button is clicked', async () => {
const clipboard = TestBed.inject(Clipboard);
const clipboardSpy = spyOn(clipboard, 'copy').and.returnValue(true);
@@ -265,25 +237,4 @@ describe('DocViewer', () => {
// necessary to undo this location change by going back in the history.
window.history.back();
});
it('should upgrade a video facade to an iframe in browsers that can embed it', async () => {
const fixture = TestBed.createComponent(DocViewer);
fixture.componentRef.setInput('docContent', exampleContentWithVideoFacade);
await fixture.whenStable();
const iframe = fixture.nativeElement.querySelector('iframe.docs-video');
const facade = fixture.nativeElement.querySelector('a.docs-video-facade');
if (isFirefox) {
// Firefox can't load the cross-origin embed under COEP, so the facade stays a link.
expect(facade).toBeTruthy();
expect(iframe).toBeNull();
} else {
expect(iframe).toBeTruthy();
expect(iframe.getAttribute('src')).toContain('youtube.com/embed/abc123');
expect(iframe.hasAttribute('credentialless')).toBeTrue();
expect(facade).toBeNull();
}
});
});

Some files were not shown because too many files have changed in this diff Show More