Compare commits

...

258 Commits

Author SHA1 Message Date
Andrew Kushnir 289ddc9e29 release: cut the v21.1.4 release 2026-02-11 09:49:26 -08:00
Matt Lewis 2b99eaa019 fix(core): capture animation dependencies eagerly to avoid destroyed injector
Animation runner functions (runEnterAnimation, runLeaveAnimations,
runLeaveAnimationFunction) execute asynchronously from the animation
queue via afterNextRender. By that time the lView injector may have
been destroyed, causing lView[INJECTOR].get(NgZone) to throw NG0205.

Move the NgZone and MAX_ANIMATION_TIMEOUT lookups into the setup
instructions (ɵɵanimateEnter, ɵɵanimateLeave, ɵɵanimateLeaveListener)
which run synchronously during template processing when the injector
is guaranteed to be valid, and pass them through the closures.

(cherry picked from commit bd2868e915)
2026-02-10 13:31:27 -08:00
Weddly Rodrigues bdcd1b3ca0 docs: rename loadUser mock API to getUserData
(cherry picked from commit c6ca1cd725)
2026-02-10 13:30:48 -08:00
Matthieu Riegler d9ec23e46b test: add test about mapped attributes to input
The dynamic component has `[value]` in its selector and this has always been reflected as a DOM attribute on the dynamically created host element, which is now also synced into the component instance.

fixes #60157

(cherry picked from commit e229328b39)
2026-02-10 13:29:35 -08:00
Charles Lyding a5bd444880 docs: remove legacy e2e configuration from i18n example
Removes the unused 'e2e' configuration block using '@angular-devkit/build-angular:private-protractor' from the i18n example configuration.

(cherry picked from commit 8b4eec6683)
2026-02-10 13:28:52 -08:00
Matthieu Riegler 083f0fa263 fix(vscode-extension): Highlight function calls with optional chaining
eg: `foo?.()` should be highlighted as `foo()`.

fixes #65513

(cherry picked from commit 4f8d3995f0)
2026-02-10 09:42:07 -08:00
SkyZeroZx 2c3f3cc336 refactor(forms): use optional chaining for safer method calls in form directives
Simplifies null checks by leveraging optional chaining when invoking
optional callbacks

(cherry picked from commit 8b3b069be7)
2026-02-10 07:43:02 -08:00
SkyZeroZx ae3c8fbc38 refactor(compiler-cli): removes reflector parameter from wrapTypeReference
The `wrapTypeReference` function no longer needs the `reflector` because it only uses the `clazz` parameter to create a `WrappedNodeExpr`.

(cherry picked from commit e7fa177923)
2026-02-10 07:42:29 -08:00
moraisacr c6bbf403a3 docs(forms): replace 'legacy' with 'existing' in migration guide and navigation
Updates terminology from 'Legacy Forms' to 'Existing Forms' in:
- Migration guide documentation
- Navigation entries label

Backport of #66969
2026-02-09 15:57:57 -08:00
Matthieu Riegler 6c14e3af2e build: update Jasmine to 6.0.0
Jasmine enables `forbidDuplicateNames: true` by default. So we also need to desambiguate duplicate spec names.
2026-02-09 15:57:12 -08:00
Angular Robot 2fe2398b33 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-09 15:53:17 -08:00
Jaime Burgos 19542a30b1 test(common): remove zone-based testing utilities
Removes usages of zone-based helpers such as `fakeAsync` , `tick`
`waitForAsync` as part of the migration to zoneless tests.

Completes the transition to zoneless.

(cherry picked from commit c6d7500203)
2026-02-09 14:47:40 -08:00
Jessica Janiuk d6aeac504c fix(core): Fix flakey test due to document injection
This fixes the test issues with the transfer state by using the DOCUMENT from src/document.

(cherry picked from commit e53c8abaf9)
2026-02-09 14:16:29 -08:00
SkyZeroZx 3905015ccc fix(http): correctly parse ArrayBuffer and Blob in transfer cache
Encodes arraybuffer and blob response bodies as base64 when storing in the transfer cache, ensuring correct retrieval and usage on the client side.

Fixes #66827

(cherry picked from commit cb1163e5e5)
2026-02-09 12:32:17 -08:00
SkyZeroZx 6f5c233f1d refactor(common): extract argument assertion
Deduplicates string argument validation logic across pipes
by introducing a local assertion helper

(cherry picked from commit 66e8385b6a)
2026-02-09 12:26:10 -08:00
Kristiyan Kostadinov caab23dfe6 fix(compiler): add geolocation element to schema
A new `geolocation` tag was recently added to Chrome. These changes update the schema to account for it.

See https://developer.chrome.com/blog/geolocation-html-element

(cherry picked from commit 11834a4274)
2026-02-09 12:25:41 -08:00
Alan Agius 17cc82872a ci: add myself to pull approve dev-infra reviewers
Add myself to the dev-infra reviewers.

(cherry picked from commit d019ee3c0b)
2026-02-09 12:22:22 -08:00
Angular Robot 6855296a36 build: update pnpm to v10.29.1
See associated pull request for more information.
2026-02-09 12:21:48 -08:00
SkyZeroZx c3600b3d99 docs(docs-infra): fix heading ID generation and display
Ensures that custom heading IDs are properly handled

(cherry picked from commit dd15efb7c3)
2026-02-09 12:20:18 -08:00
SkyZeroZx 1f82b6aea7 docs: add missing error codes to the error overview list
(cherry picked from commit fd293d3397)
2026-02-09 12:19:44 -08:00
SkyZeroZx 29f70072a8 docs(docs-infra): simplify external link rendering for pill
Renders the external link icon for docs pills using CSS instead of a `docs-icon` component.

(cherry picked from commit 0b9d1cfb7b)
2026-02-09 12:19:12 -08:00
Miles Malerba fe7f25403b build: configure gemini cli to look for AGENTS.md
By default gemini cli looks for GEMINI.md, these settings change it to
consider AGENTS.md as well

(cherry picked from commit 85d6a2bc6e)
2026-02-09 12:15:22 -08:00
Miles Malerba 0f8dddc7f0 docs: add gemini custom command for generating skills
Adds a Gemini CLI custom command (`/explain`) to generate skills for
working with code packages in this repo. Usage:

1. Add skill for a new package:
  `/explain packages/common`
2. Update the skill for a package:
  `/explain Update the skill for packages/core`
3. Add supplemental information:
  `/explain Add supplemental info to the signal forms skill about how
   schema logic is implemented`

Also adds a few skills for working with some of the packages I commonly
interact with, including:

- packages/forms/signals
- packages/core
- packages/compiler-cli

(cherry picked from commit 7ddd10bb44)
2026-02-09 12:14:19 -08:00
Andrew Kushnir e3729c6327 Revert "refactor(service-worker): remove zone-based testing utilities"
This reverts commit 12b770dd3e.
2026-02-09 10:46:38 -08:00
Matthieu Riegler c298694a79 refactor(benchpress): remove zone-based testing utilities
Transition to zoneless.

(cherry picked from commit 4b3b149ba1)
2026-02-09 07:55:17 -08:00
Matthieu Riegler 12b770dd3e refactor(service-worker): remove zone-based testing utilities
Transition to zoneless.

(cherry picked from commit ec26d38778)
2026-02-09 07:55:17 -08:00
Matthieu Riegler 3f0fbaa0bc refactor(compiler): remove zone-based testing utilities
Having zone.js here already wasn't necessary.

(cherry picked from commit a1441a432d)
2026-02-09 07:55:16 -08:00
Shuaib Hasan Akib 4311ba7290 refactor(common): remove unused imports and standalone: false from examples
Cleans up example code by removing explicit `standalone: false`, since
standalone components are the default and recommended approach.

(cherry picked from commit 14f16226aa)
2026-02-09 07:53:37 -08:00
aparziale 054972c8e0 docs: Signal forms custom validator with 'fieldTree' field in validation result error
Update docs for fieldTree property

fixes #66687

(cherry picked from commit 63d828aa33)
2026-02-06 15:11:03 -08:00
Kristiyan Kostadinov c633697262 Revert "fix(compiler-cli): update diagnostic to flag no-op arrow functions in listeners"
This reverts commit a78a7f651a.
2026-02-06 11:13:02 -08:00
Kristiyan Kostadinov 8743c9daac docs: remove non-existant method
Removes the reference to the `focusFirstItem` method since it doesn't exist.

Fixes https://github.com/angular/components/issues/32741.

(cherry picked from commit 460dbbb8e9)
2026-02-06 10:20:35 -08:00
SkyZeroZx 0d1acd0165 feat(forms): support signal-based schemas in validateStandardSchema
Allow `validateStandardSchema()` to consume a computed schema so
validation rules stay in sync when the schema changes over time.

This supports schemas stored in computed signals (e.g. zod schemas that
depend on input signals) and ensures the effective schema updates after
initialization instead of being captured once.

Fixes #66867

(cherry picked from commit 24c0c5a180)
2026-02-06 07:40:51 -08:00
Kristiyan Kostadinov a78a7f651a fix(compiler-cli): update diagnostic to flag no-op arrow functions in listeners
Based on the discussion in #66294: now that we support arrow functions in event listeners, developers may write out something like `(click)="() => expr"` which will be a no-op. These changes update the existing diagnostic for uninvoked expressions in listeners to account for it.

(cherry picked from commit 2ea6dfc6c9)
2026-02-06 07:39:22 -08:00
Charles Lyding f279f414ca refactor(language-service): use type-only TypeScript imports in some files
Standardize on `import type ts from 'typescript'` across several files
in the language-service package.

This ensures that these files do not have a runtime dependency on a
specific version of the TypeScript module, instead relying on the
instance provided by the host during initialization.

Note: This change is not comprehensive. Several files still require
runtime access to the TypeScript module and will require further
refactoring to fully decouple the dependency.

(cherry picked from commit 108252bf67)
2026-02-06 07:38:53 -08:00
aparziale 653c7b96e8 docs: Images not show
Fixed path in optimization image tutorial

(cherry picked from commit 6550b9d776)
2026-02-06 07:38:07 -08:00
Angular Robot 83c6142379 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-06 07:36:03 -08:00
Matthieu Riegler de3cc5e8ec docs: callout for optional fields and native controls.
(cherry picked from commit fd5ff5c1a9)
2026-02-06 07:35:18 -08:00
Shuaib Hasan Akib 50c0110fc4 docs: fix wrong line highlight in component queries example
(cherry picked from commit e79e7994d4)
2026-02-06 07:34:01 -08:00
Shuaib Hasan Akib 8b4c213cd3 docs: use self-closing tag
Aligns template syntax with modern Angular recommendations and improves readability.

(cherry picked from commit 12afc1d164)
2026-02-06 07:33:22 -08:00
Angular Robot be4ffb68eb build: update rules_browsers digest to e08ae33
See associated pull request for more information.
2026-02-05 17:16:33 -08:00
SkyZeroZx 0729181d34 test(compiler): remove zone-based testing utilities
Removes usages of zone-based helpers such as
`waitForAsync` as part of the migration to zoneless tests.

Completes the transition to zoneless.

(cherry picked from commit 2a0241a665)
2026-02-05 16:56:59 -08:00
SkyZeroZx 91885623cd docs: remove Component suffix in reference and tools
(cherry picked from commit 6883889123)
2026-02-05 16:55:30 -08:00
Angular Robot 52745a23e5 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-05 16:53:25 -08:00
Angular Robot bc6302cd4a build: update all github actions
See associated pull request for more information.
2026-02-05 16:52:54 -08:00
Alan Agius 13ffadb063 build: remove dagre-d3-es pnpm patch from MODULE.bazel
This file no longer exist

(cherry picked from commit 85dd096c11)
2026-02-05 16:50:21 -08:00
Matthieu Riegler c0cbdcc41b ci: exclude standard_schema from sync.
This file isn't sync into G3 and this should prevent the CI from requiring a presubmit when the file is modified.

(cherry picked from commit 3e2ed305d1)
2026-02-05 16:49:47 -08:00
Shuaib Hasan Akib 2d5ed88f54 docs: improve type safety in custom validator documentation
Replace `field: any` with proper SchemaPath typing in Signal Forms custom
validation examples to match built-in validator patterns.

fixes: #66887

Update adev/src/content/guide/forms/signals/validation.md

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
(cherry picked from commit ff2c0053c8)
2026-02-05 16:49:12 -08:00
Leon Senft 70668befde release: cut the v21.1.3 release 2026-02-04 16:06:24 -08:00
Matthieu Riegler 767c6b4754 fix(vscode-extension): support highlighting for class bindings with brackets
Tailwind classes can often be quite complex strings. This change adds supports for classes with backets.

fixes #66818

(cherry picked from commit 01ed57f297)
2026-02-04 15:45:08 -08:00
SkyZeroZx 1bf6bfc07f test(forms): remove zone-based testing utilities
Removes usages of zone-based helpers such as `fakeAsync` , `tick`
`waitForAsync` as part of the migration to zoneless tests.

Completes the transition to zoneless.

(cherry picked from commit 7dfbacbcf8)
2026-02-04 15:38:13 -08:00
Walter Breakell 70313f13fe docs: fix syntax errors and improve grammar in navigation guide
(cherry picked from commit d6585fe837)
2026-02-04 15:37:21 -08:00
Jef f9b69cc67b docs: fix typo
Add missing n to componet(s)

(cherry picked from commit 81876c2583)
2026-02-04 15:36:52 -08:00
Ben Hong 52dfcd9865 docs: add guidance on submit and novalidate for signal forms
(cherry picked from commit 38428945ac)
2026-02-04 15:36:22 -08:00
Angular Robot d793dde00f build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-04 15:30:54 -08:00
Kristiyan Kostadinov 04d518bc26 refactor(forms): work around internal issue
Works around an internal property renaming issue by changing how we declare the interface for `InteropNgControl`.

(cherry picked from commit 680d99b1c3)
2026-02-04 14:25:48 -08:00
David Neil 2cf4da0ea1 fix(core): hold constructors weakly in DepsTracker cache
Otherwise a component can be held in memory longer than
the component constructor is reachable from application code.

(cherry picked from commit cab5ddd526)
2026-02-03 12:44:27 -08:00
Matthieu Riegler f15d33d68b docs(docs-infra): extract call signature return type.
fixes #66799

(cherry picked from commit 43d61ecbb2)
2026-02-03 12:31:40 -08:00
Shuaib Hasan Akib 33e8cde034 docs: generalize wording for CSP nonce configuration options
Replaces “one of two ways” with “one of the following ways” to avoid
hardcoding the number of supported approaches and keep the
documentation accurate if options change.

fixes: #66861
(cherry picked from commit cfa7664361)
2026-02-03 12:27:15 -08:00
Angular Robot 8343b05ad7 build: lock file maintenance
See associated pull request for more information.
2026-02-03 12:26:06 -08:00
Matthieu Riegler 443654ac77 docs(docs-infra): fix foot width
This was due to a regression introduced by #66825

fixes #66880

(cherry picked from commit 9c032972a8)
2026-02-03 12:25:46 -08:00
David Neil 49a36f4cc7 perf(router): Use .bind to avoid holding other closures in memory
In many JS runtimes all closures created in the same scope share a context
this means that data held in one of the closures is not collected until all of the closures are collected.
This change prevents the returned promise from holding a reaction that holds the entire `Router` object in memory.

(cherry picked from commit 3867cd8554)
2026-02-03 12:25:02 -08:00
Angular Robot 6505a26ee0 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-02-02 15:11:22 -08:00
Shahar Har-Shuv 96095f435e docs: Rename Field directive -> FormField in form-logic.md
(cherry picked from commit f29fcd882a)
2026-02-02 14:41:03 -08:00
Alex Rickabaugh 2b254bc050 fix(core): linkedSignal.update should propagate errors
Unlike a normal `signal()`, a `linkedSignal()` can be in an error state when
its computation fails. Currently, there's a bug where `linkedSignal.update`
does not account for this error state, and will pass the internal `ERRORED`
`Symbol` as the current value to the updater function.

This commit fixes the issue by having `update()` check and throw the error
instead of calling the updater function.

(cherry picked from commit aff9e36a98)
2026-02-02 12:21:36 -08:00
Kristiyan Kostadinov e5110b4fa1 fix(core): export DirectiveWithBindings
Exports the `DirectiveWithBindings` interface since it's part of the public API of `createComponent`.

Fixes #66851.

(cherry picked from commit 8ab433abdd)
2026-02-02 11:08:45 -08:00
SkyZeroZx f1e4446071 feat(docs-infra): Implements select component using Angular Aria
Replaces the native select element with a custom combobox implementation for improved accessibility and styling.

(cherry picked from commit 70615117a2)
2026-02-02 11:07:58 -08:00
AleksanderBodurri 79519409cd feat(devtools): promote router tree to stable
Previous the router tree was an opt-in feature that required manual enablement in settings.

Now the router tree is enabled by default whenever the application supports it and routes are detected.

(cherry picked from commit 35046d3dba)
2026-02-02 11:06:45 -08:00
SkyZeroZx 4729e804ac docs(docs-infra): Fix border color issue on aria select when closing on outside click
Prevents incorrect border color from appearing on close triggered by an outside click

(cherry picked from commit cdbb957334)
2026-02-02 11:04:48 -08:00
dominicbachmann 575f8a39c7 docs(animations): fix small typo in animations migrations docs
(cherry picked from commit a64a90dbbe)
2026-02-02 10:18:10 -08:00
Jaime Burgos f80e6c2f77 docs: remove component suffix router guide
(cherry picked from commit bd27aa2cf3)
2026-02-02 10:17:41 -08:00
SkyZeroZx bbd5cf82b1 test(forms): migrate reactive forms and value accessors to zoneless
Migrates reactive forms, template-driven forms, and value accessors to zoneless tests.

(cherry picked from commit 4aae3379c2)
2026-02-02 10:17:03 -08:00
SkyZeroZx c64db1d0f3 docs: update llms.txt
(cherry picked from commit 825122b6d2)
2026-02-02 10:14:07 -08:00
kirjs 5301711b4e docs: rename agents.md to AGENTS.md
(cherry picked from commit 19aa1eb95e)
2026-02-02 10:10:45 -08:00
kirjs 92ac00d507 docs: add PR section to agents.md
(cherry picked from commit a8d0155303)
2026-02-02 10:10:45 -08:00
kirjs fdd7a4deb5 docs: add agent configuration guide
Adds agents.md to the root directory to provide context and instructions for AI agents working in this repository.

(cherry picked from commit 9306abe883)
2026-02-02 10:10:45 -08:00
SkyZeroZx df7bf134fe docs(docs-infra): ensure code preview copy preserves spaces
Preserve whitespace when copying code examples from the docs. Fixes the copy handler so copied snippets keep original spacing.

Fixes #66790

(cherry picked from commit 522c5ba868)
2026-02-02 10:09:39 -08:00
Jaime Burgos 665b33ecdc docs: update zoneless guide
(cherry picked from commit 3d09d8e831)
2026-02-02 10:05:26 -08:00
Angular Robot eb2803237b build: update cross-repo angular dependencies
See associated pull request for more information.
2026-02-02 09:59:35 -08:00
SkyZeroZx 6f75b6e3f6 fix(forms): Resolves debounce promise on abort in debounceForDuration
Ensures the promise returned by the debouncer resolves
when aborted, preventing potential hangs for awaiting consumers.
Fixes #66646

(cherry picked from commit b1bf535f8e)
2026-01-30 09:19:47 -08:00
Charles Lyding efb64bf9fd build(language-service): remove local development build script
The standalone build.sh script for the language service is no longer
required as the vscode-ng-language-service repository has been merged
into this monorepo. Local development and testing of the extension
can now be handled via the integrated build system.

(cherry picked from commit f9bcccc2b9)
2026-01-29 13:16:53 -08:00
Matthieu Riegler e0a260a425 docs(docs-infra): wrap getTextOfJSDocComment
This commits adds a wrapper around `ts.getTextOfJSDocComment` because of bugs that won't be fixed by the TS team (see microsoft/TypeScript#63027)

(cherry picked from commit 72dfb4d92a)
2026-01-29 13:16:05 -08:00
Angular Robot 8dfbb8b5d5 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-29 12:30:49 -08:00
Angular Robot 3845f7e930 build: update github/codeql-action action to v4.32.0
See associated pull request for more information.
2026-01-29 12:29:18 -08:00
Shuaib Hasan Akib cc9a58a2ae feat(docs-infra): increase table-of-contents width for large screens
Add media query overrides to expand the table-of-contents width on extra-large desktops.

fixes: #66823
(cherry picked from commit 6990f88d97)
2026-01-29 12:24:04 -08:00
Angular Robot e3681b7b5f build: update dependency tar.bzl to v0.8.1
See associated pull request for more information.
2026-01-29 12:23:31 -08:00
Jessica Janiuk 70a5b651be fix(core): prevent element duplication with dynamic components
When dynamic components are rapidly added and removed with animate.leave and animate.enter, a leave animation might fire before the enter animation could, causing an element to be retained. This fix prevents that from occuring by clearing the enter animations in this case.

fixes: #66794
(cherry picked from commit c66a19f0de)
2026-01-29 12:18:05 -08:00
Charles Lyding 4c7126d23b fix(localize): add support for unit-test builder in ng-add schematic
This commit updates the @angular/localize ng-add schematic to support
the @angular/build:unit-test builder. It ensures that @angular/localize
is added to the types array in the TypeScript configuration file
associated with the unit-test target. If no tsConfig is specified in
the target options, it defaults to tsconfig.spec.json in the project root.

(cherry picked from commit 1c3b1cf18d)
2026-01-29 12:17:19 -08:00
Andrew Scott d6268c0bbb fix(router): limit UrlParser recursion depth to prevent stack overflow
Deeply nested parentheses in URLs (e.g. `(a/(b/(c...)))`) trigger recursive calls in `UrlParser`, which can lead to a `RangeError: Maximum call stack size exceeded`. While such errors are generally caught by the framework, relying on the runtime's stack limit is unpredictable across different environments and engine states (e.g. varying stack sizes in different browsers or Node.js versions).

The deeply nested parentheses  can cause a stack overflow. While such URLs can be valid (e.g., `(a/(b/(c...)))`) and serialize to simple paths (e.g., /a/b/c), excessive nesting is unreasonable and likely malicious or accidental.

Linear paths (e.g. /a/b/c/d) are parsed iteratively and do NOT trigger recursion. Only parentheses trigger recursion.

This commit introduces a recursion depth limit of 50. If parsing exceeds this depth, the router will now throw a specific `UNPARSABLE_URL` error with the message "URL is too deep". This ensures a deterministic failure mode that is easier for applications to handle than a crash or generic RangeError.

The limit of 50 is chosen as it should accommodate any reasonable application URL structure (including complex named outlets) while providing a safe upper bound against abusive payloads.

This is essentially a refactor of the error state:

* Before: RangeError (System says "I'm out of stack memory")
* After: RuntimeError (Validator says "Input is invalid")

This provides:

* Semantic Correctness: The error now correctly blames the input ("URL too deep"), not the environment ("Stack full").
* Cross-Platform Consistency: The limit is the same in Chrome, Firefox, Node, and Deno, regardless of their internal recursion limits.
* Fast Failure: We stop at depth 50 instead of depth ~15,000, saving those cycles (though CPU cost is negligible either way).

"wide" URLs are now theoretically more expensive than "deep" URLs (because deep ones fail fast), but both are well within safe bounds for any reasonable input size.

(cherry picked from commit 458bc4a2c8)
2026-01-29 12:15:25 -08:00
SkyZeroZx 0a4c647ef0 refactor(core): Remove unsued properties in differ factories
Simplifies differ factories by removing unnecessary constructors and properties.

Also removes the formatError function as it is no longer used.

(cherry picked from commit 0c2efc0d46)
2026-01-29 12:06:37 -08:00
JakobDev b9b934363a docs: Add missing link in HostListener documentation
(cherry picked from commit 0e04233a7c)
2026-01-29 11:56:48 -08:00
SkyZeroZx cfe7340270 refactor(core): remove unused restriction parameter
Removes the `restriction` parameter from `registerAppScopedDispatcher` and `registerGlobalDispatcher`.

(cherry picked from commit d072791f13)
2026-01-28 20:54:49 +00:00
Andrew Scott 4f0d29d92a release: cut the v21.1.2 release 2026-01-28 20:36:08 +00:00
Angular Robot 290b2814e1 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-28 19:28:34 +00:00
Leon Senft 9f99b14882 fix(forms): only touch visible, interactive fields on submit
Don't touch hidden, disabled, or readonly fields on submit, since they
don't contribute to form validity. This also prevents errors from
appearing immediately if they're later made interactive.

Fix #66344

(cherry picked from commit e682e53113)
2026-01-28 18:56:06 +00:00
Angular Robot 3de87e1eda build: update pnpm to v10.28.2
See associated pull request for more information.
2026-01-28 18:36:21 +00:00
Angular Robot affd2362eb build: lock file maintenance
See associated pull request for more information.
2026-01-28 18:27:17 +00:00
Angular Robot 11657fb0b1 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-01-28 18:23:34 +00:00
Matthieu Riegler 7242da24fb docs: reword docs on standalone.
fixes #66773

(cherry picked from commit 611f6a49d1)
2026-01-28 18:16:58 +00:00
Leon Senft 31e6f0d7dc test(forms): submit behavior while validation is pending
Ensure `submit()` behaves as expected while a form is pending.

- Submission is not blocked by pending validation.
- Submission errors prevent pending validation errors from appearing
  after they resolve on the same field.
- Submission errors don't prevent pending validation errors from
  appearing after they resolve on subfields.

(cherry picked from commit 01bfb83fc9)
2026-01-28 00:15:33 +00:00
Jaime Burgos 6601f06e44 test(common): enables zoneless change detection in tests
Adds `provideZonelessChangeDetection` to TestBed configurations in  `ngComponentOutlet` , `ngOptimizedImage` , `ngTemplateOutlet` and `ngPlural`.

(cherry picked from commit 4448356313)
2026-01-28 00:08:50 +00:00
Shuaib Hasan Akib fe69342545 docs: fix wrong line highlights
Corrects incorrect line highlighting in code examples so the highlighted
lines match the intended sections being explained.

(cherry picked from commit 160a8b4be7)
2026-01-26 23:56:45 +00:00
Andrew Scott c57b0355b5 fix(language-service): Detect local project version on creation
This updates the language service to use the detected version of angular
core in the given project on load rather than the minimum detected
version in the workspace

(cherry picked from commit 8a7cbd4668)
2026-01-26 23:51:34 +00:00
Matthieu Riegler 06952b4f0f docs(docs-infra): remove unused examples
(cherry picked from commit 899df39c7c)
2026-01-26 23:19:39 +00:00
Shuaib Hasan Akib c133a2c1eb docs: normalize spacing in animation example boxes
Simplifies box padding and removes default paragraph margins to ensure consistent spacing and alignment
in the animation examples.

(cherry picked from commit ceefceb004)
2026-01-26 22:59:32 +00:00
SkyZeroZx 3151bc38a0 docs: Updates form documentation (#66732)
Updates the form documentation to link to the API reference for the `form()` function.

Also removes unused `HttpClient` import from the async validation example.

PR Close #66732
2026-01-26 22:46:12 +00:00
SkyZeroZx 49e3c25d62 docs(docs-infra): Exempts more symbols from automatic linking (#66732)
Extends the list of symbols that should not be
automatically linked for forms.

PR Close #66732
2026-01-26 22:46:11 +00:00
Shuaib Hasan Akib b4227fb3f1 docs: apply prefer/avoid blocks in UI with comments
Apply inline comments with proper Prefer and Avoid UI blocks so
guidance is more visible and consistent across the documentation.

(cherry picked from commit 8efee1c71d)
2026-01-26 22:33:15 +00:00
Matthieu Riegler 89525ec6b7 docs: add mentions of the global target syntax.
(cherry picked from commit 87a72f3dd0)
2026-01-26 22:31:14 +00:00
Georgi Serev 1234e873d8 docs(docs-infra): update the look of api items state labels
Update how dev preview, experimental and deprecated labels are visualized in the API list to avoid ambiguity with the current design on larger screens.

(cherry picked from commit 9625780259)
2026-01-26 22:30:22 +00:00
SkyZeroZx 359470979b refactor(core): remove index from text node creation
Removes unnecessary `index` parameter from `_locateOrCreateTextNode`
and `locateOrCreateTextNodeImpl` functions.

(cherry picked from commit 1df564b9f9)
2026-01-26 22:22:59 +00:00
Shuaib Hasan Akib 27c7b60d81 docs: fix wrong line highlight in lightweight injection token for API definition example
(cherry picked from commit ca9b5bd658)
2026-01-26 22:22:04 +00:00
huangkevin-apr 72ed76feb3 docs(docs-infra): improve accessibility of search dialog trigger
- Add explicit aria-label for screen reader users
- Preserve title tooltip for mouse users

(cherry picked from commit b3f2bad593)
2026-01-26 22:21:40 +00:00
Damian Sire 278cfd3879 docs: fix Genkit documentation URL
Update the Genkit Angular integration documentation link from the old path
/docs/angular/ to the current correct path /docs/frameworks/angular/

This ensures developers are directed to the correct and up-to-date
documentation for integrating Genkit with Angular applications.

(cherry picked from commit ffe40f4bfa)
2026-01-26 22:20:17 +00:00
Modeste ASSIONGBON 5903bf7c98 docs: fix typo in designing-your-form-model.md
"A form model model" -> duplicated word
"to save bytes me might want" -> "we" instead of "me"

(cherry picked from commit 19b59df2dc)
2026-01-26 22:19:54 +00:00
SkyZeroZx 051ba061a0 refactor(core): improve tree-shaking
Refactors getNodeInjectable to avoid pulling in stringifyForError production builds, reducing unnecessary symbols in production bundles

(cherry picked from commit 8feb541060)
2026-01-26 22:19:00 +00:00
AleksanderBodurri 01b035ab03 feat(devtools): mark special element injector providers as internal
Differentiates special providers like ElementRef from other providers in the DevTools UI.

(cherry picked from commit 105274fe32)
2026-01-26 22:18:06 +00:00
Ben Hong 4742c703df docs: add signal forms - form logic guide
Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
(cherry picked from commit 1d1777931e)
2026-01-26 22:14:01 +00:00
Andrew Scott 21ecdc036a fix(router): Do not intercept reload events with Navigation integration
This commit prevents the Router from intercepting reload navigations
in the navigate event listener. This would convert hard page reloads
to SPA navigations.

fixes #66746
2026-01-26 20:58:49 +00:00
Angular Robot 2499238c59 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-23 20:05:38 +00:00
SkyZeroZx e6ea328cf4 docs: update bootstrapApplication docs
(cherry picked from commit 85122cb12d)
2026-01-23 11:45:20 -08:00
Angular Robot b203ab0881 build: update bazel dependencies
See associated pull request for more information.
2026-01-23 18:43:23 +00:00
Matthieu Riegler f7eb996ad5 docs: update signal form tutorials to use FormField
(cherry picked from commit ddd3198dc2)
2026-01-22 17:02:28 -08:00
Angular Robot d9e7ab3db6 build: update all github actions
See associated pull request for more information.
2026-01-23 00:36:13 +00:00
SkyZeroZx 458e21e0fe docs(docs-infra): Exempts more symbols from automatic linking
Extends the list of symbols that should not be
automatically linked.

(cherry picked from commit 30b50e7142)
2026-01-22 15:01:43 -08:00
Matthieu Riegler e4c9ff7a34 build: Update prettier to 3.8
This version adds support for `angular-html` and `angular-ts` formatting in our markdown files.

(cherry picked from commit 145e77d09d)
2026-01-22 21:13:21 +00:00
Kristiyan Kostadinov c9043fb876 refactor(compiler-cli): remove deprecated signature usage
The signature for `createImportClause` was deprecated in TS 5.9. These changes switch to the non-deprecated one.

(cherry picked from commit dbc1452de3)
2026-01-22 21:02:42 +00:00
Angular Robot 61dbdee6fb build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-22 19:46:02 +00:00
Andrew Scott 27eeff9be6 release: bump VSCode extension version to 21.1.1 (#66714) 2026-01-22 10:31:57 -08:00
Alon Mishne f577f8cb5c release: cut the v21.1.1 release 2026-01-21 21:58:24 +00:00
Angular Robot 643f0f4b22 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-21 12:43:47 -08:00
Matthieu Riegler ad9e473e73 docs(docs-infra): fix regression on docs-tabs
(cherry picked from commit 4ca2722d6f)
2026-01-21 20:43:07 +00:00
Matthieu Riegler 18842266de docs(docs-infra): Handle additional description format
Ex: https://angular.dev/api/router/withExperimentalPlatformNavigation
(cherry picked from commit f0b1061791)
2026-01-21 19:37:11 +00:00
Matthieu Riegler 223ff74891 build: don't substitute binary files
Previously, ng_package applied version stamping to all source files, which corrupted binary files like SQLite databases.

This change ignores text substitution for files passed to the `data` attribute, ensuring binary assets are preserved intact in the final package.

fixes #66637

(cherry picked from commit b885851cbe)
2026-01-21 18:35:08 +00:00
JoostK 0e1f1ed573 fix(compiler-cli): drop .tsx extension for generated relative imports
When synthesizing an import corresponding with a .tsx file, the extension
would not be removed unlike regular .ts files. Adjust the import generators
to also drop any .tsx extension from module specifiers.

Closes #66262

(cherry picked from commit d94b19a92f)
2026-01-21 18:33:17 +00:00
aparziale b9c995e7d3 refactor: Delete counterproductive aria-label
Delete counterproductive aria-label in ADEV

fixes #66604

(cherry picked from commit 0fa8099d50)
2026-01-21 18:25:38 +00:00
Shuaib Hasan Akib 0d0a86be29 docs: cleanup copy button and $ from output
(cherry picked from commit 1f86de019f)
2026-01-21 18:24:21 +00:00
Angular Robot 8078739e62 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-21 10:22:39 -08:00
Angular Robot 60aceefc75 build: update pnpm to v10.28.1
See associated pull request for more information.
2026-01-21 10:19:09 -08:00
Andrew Scott 696e827391 refactor(language-server): split session into separate handlers
Breaks up the large session.ts file into separate handlers and consolidates the code related to completions in the server into the handler file
2026-01-21 10:12:47 -08:00
Kristiyan Kostadinov 6a5620442f fix(docs-infra): use focus-visible for focus styling
`focus-visible` should lead to a better experience, compared to showing the state on any focus.

(cherry picked from commit b275206a49)
2026-01-21 01:17:06 +00:00
Shuaib Hasan Akib 62d847325d refactor(common): cleanup unused code in doc regions
Cleans unused imports and code
that is not referenced within the associated docregion blocks.

(cherry picked from commit 6284126da5)
2026-01-21 00:31:13 +00:00
Kristiyan Kostadinov 3c8d69794c fix(docs-infra): don't use private symbol
Fixes that the tab group was referring to a private symbol from the CDK.

(cherry picked from commit 94a8f797e5)
2026-01-21 00:15:22 +00:00
Kristiyan Kostadinov 93f3cca5e9 fix(docs-infra): use components directly
Switches the Material and CDK components to be used directly, instead of using the modules. This allows better diagnostics for unused directives.

(cherry picked from commit 508d9c34d5)
2026-01-21 00:14:49 +00:00
Kristiyan Kostadinov ea70b003b0 refactor(compiler): remove unused symbols
Removes symbols that aren't used anymore from the identifiers map.

(cherry picked from commit e4c09776b0)
2026-01-21 00:14:19 +00:00
Kristiyan Kostadinov 5d1b950324 docs: fix serif font
Fixes some headers that were using a serif font unintentionally.

(cherry picked from commit 094cdfe0b5)
2026-01-21 00:12:55 +00:00
Matthieu Riegler e6642496b0 ci: add template for vscode dedicated issues
We didn't have an entry since we migrated the vscode extension repo to the fw repo.

(cherry picked from commit 75c40e7ac7)
2026-01-20 18:24:00 +00:00
Leon Senft 26c2fe6db2 refactor(forms): remove unused API
Remove `SubmittedStatus` which is no longer used.

(cherry picked from commit d0ba332292)
2026-01-20 18:20:01 +00:00
SkyZeroZx e828f1c765 docs: Update ngComponentOutlet binding and hydration note
(cherry picked from commit 5dd17d7b81)
2026-01-20 18:14:34 +00:00
SkyZeroZx 01e98ded2e docs(docs-infra): : Exempts animation-related symbols from linking
Exempts 'group()', 'keyframes', and '@keyframes' symbols from automatic linking.

(cherry picked from commit 00455105cf)
2026-01-20 18:13:55 +00:00
SkyZeroZx 4a1dcb2d82 docs: remove suffix in elements , templates , components & signals
(cherry picked from commit 6f48b4f5ed)
2026-01-20 18:13:28 +00:00
Shuaib Hasan Akib 58720dbc56 refactor(forms): update Reactive Forms guide URL
Updates the Reactive Forms documentation link to the new `guide/forms/reactive-forms` path after the recent docs restructure.

(cherry picked from commit 4adbc4fa19)
2026-01-20 18:11:51 +00:00
Angular Robot 9cc0f4d003 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-01-20 10:03:37 -08:00
Joey Perrott 82593de5a4 fix(docs-infra): support providing links to multiple symbols within a single line of a code block
Previously our system only found the first matching symboling to link to within a code block on each line, now we set up a link for all of the discovered symbols on each line

Fixes #65403

(cherry picked from commit a88f353a45)
2026-01-20 17:35:36 +00:00
omar-almasry11 068bb9d3c5 fix(docs-infra): improve skip-to-main-content method to focus <main> element instead of h1
Update the skip-to-main-content behavior to focus the <main> element when present, with a fallback to the first heading for legacy layouts without a main landmark.

(cherry picked from commit 30b78bdc71)
2026-01-20 17:35:01 +00:00
Damian Sire eb6af58446 docs: fix spelling errors in documentation
Fix 6 spelling errors across documentation files:
- components-scenarios.md: 'recieved' → 'received', 'stubbe' → 'stubbed', 'controle' → 'control'
- overview.md: 'dependant' → 'dependent'
- creating-libraries.md: 'to to' → 'to'
- enter-and-leave.md: 'a an' → 'an'

(cherry picked from commit 6dff1cf85d)
2026-01-20 17:32:31 +00:00
Matthieu Riegler 760e2aaa79 docs: Add mention of "documentation"
This should help to bring adev at the top when searching for "angular documentation" or "angular docs"

(cherry picked from commit 2080dd1fec)
2026-01-20 17:31:42 +00:00
Maciej Ślusarz 85cf7f0daa docs: Fix typo in image loader step of Optimizing Images chapter
(cherry picked from commit 8b9bdd0a63)
2026-01-20 17:30:46 +00:00
Angular Robot f0bcf876ec build: lock file maintenance
See associated pull request for more information.
2026-01-20 09:28:39 -08:00
Jessica Janiuk 6b34e5f444 docs: Add section on animate.leave for clarity
There has been a bunch of confusion as to how `animate.leave` works on child nodes when the non-animated parent is removed. This section addition should clarify that.

(cherry picked from commit 5d46554e3f)
2026-01-16 21:26:48 +00:00
Angular Robot ac50dbc8e1 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-16 10:25:28 -08:00
Kristiyan Kostadinov 32ad1e09b4 build: move zone.js build off deprecated flag
The Zone.js build was depending on the `--outFile` flag from TypeScript which is deprecated. These changes switch to using `--outDir` and copying the files out of the directory instead.

(cherry picked from commit 52bc0208f9)
2026-01-16 17:28:50 +00:00
Miles Malerba cb75f9ce85 fix(forms): fix control value syncing on touch
Previously we were unconditionally setting the control value back into
the value, regardless of if it had actually been changed. This PR
changes the logic to flush the pending sync on touch if there is one, or
just skip it if there isn't.

(cherry picked from commit e7b2dde6d1)
2026-01-16 17:25:31 +00:00
Kristiyan Kostadinov ded654dc6b build: initial test of TypeScript 6
Resolves some initial test failures after updating to TypeScript 6.

(cherry picked from commit d9c980a958)
2026-01-15 21:41:05 +00:00
Kristiyan Kostadinov 05adfcf8f2 fix(core): handle Set in class bindings
Currently migrating from `[ngClass]` to `[class]` isn't entirely supported, because `[ngClass]` supports `Set` values while `[class]` ignores them.

These changes add a bit of logic to bring them closer together and make the migration easier.

(cherry picked from commit 4831a9f676)
2026-01-15 21:40:02 +00:00
Jeevan Mahesha aa39f87021 docs: update directive name from [field] to [formField] in signal-forms documentation
(cherry picked from commit 417c385ddc)
2026-01-15 19:41:07 +00:00
Alan Agius 0643f541f9 docs: clarify that FESM can depend on shared chunks
Closes #66569

(cherry picked from commit c7eee8300f)
2026-01-15 19:05:45 +00:00
Miles Malerba d89a80a970 feat(forms): Ability to manually register a form field binding in signal forms
This PR adds the ability to manually register a binding with the
`FormField` directive. This is useful for a lower-level implementation
that takes the field tree as an `input()` rather than relying on the
automatic binding from `FormUiControl`.

(cherry picked from commit 5974cd0afc)
2026-01-15 19:03:32 +00:00
Joey Perrott 0f3f346ec4 fix(docs-infra): use public folder instead of assets folder for static files in adev tutorials
Use public folder rather than assets folder after the change in angular-cli's defaults for angular.json

Fixes #58981

(cherry picked from commit f2cf96b519)
2026-01-15 18:59:30 +00:00
Angular Robot 74017eb355 build: update github/codeql-action action to v4.31.10
See associated pull request for more information.
2026-01-15 10:57:40 -08:00
Angular Robot 72877b6c60 build: update bazel dependencies
See associated pull request for more information.
2026-01-15 10:55:45 -08:00
Matthieu Riegler c0446da35f docs(docs-infra): prevent heading from linking symbols
(cherry picked from commit a792315f09)
2026-01-15 16:59:18 +00:00
Angular Robot ae4e2e02d0 build: update cross-repo angular dependencies to v21.1.0
See associated pull request for more information.
2026-01-14 15:05:15 -08:00
Angular Robot 7f9990710e build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-14 12:21:32 -08:00
Jessica Janiuk 6027c9128c release: cut the v21.1.0 release 2026-01-14 11:44:32 -08:00
Joey Perrott b108ece707 ci: update algolia synonym updater to use node directly rather than tsx
With new version of node we can import ts files directly in our scripts rather than using tsx

(cherry picked from commit 3f13db87e2)
2026-01-14 17:26:43 +00:00
Angular Robot 3cc549c723 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-14 08:59:24 -08:00
Angular Robot e817f8c830 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-01-14 08:51:44 -08:00
Matthieu Riegler 1ea5c97703 feat(forms): allow focusing bound control from field state
Allows focusing the assocated bound control from the `FieldState`.
2026-01-14 08:26:54 -08:00
SkyZeroZx d81f8ea0f6 docs(docs-infra): Exempts animation-related symbols from linking
Exempts 'animation', 'transition', and 'trigger' symbols from automatic linking.

(cherry picked from commit 724e917f7a)
2026-01-14 16:24:53 +00:00
Shuaib Hasan Akib 654ed6512b docs: add IMPORTANT prefix to pipe usage warning and
Adds an IMPORTANT label to emphasize the guidance about avoiding impure
pipes due to potential performance impact.

(cherry picked from commit c9f584b918)
2026-01-14 16:11:03 +00:00
Jessica Janiuk 134742fb7b Revert "refactor(core): remove ng when invoking enableProdMode"
This reverts commit cb5879f566.

(cherry picked from commit e673eb6df2)
2026-01-14 16:09:02 +00:00
SkyZeroZx 5594a535c1 docs(docs-infra): use Signals Forms focusBoundControl
Removes direct `ViewChild` access from `TextField` components and relies on
Signals Forms via the `focusBoundControl`  to manage focus.

(cherry picked from commit 074666b2e8)
2026-01-14 16:00:49 +00:00
Angular Robot 5097df84b4 build: update dependency node to v22.22.0
See associated pull request for more information.
2026-01-14 07:58:48 -08:00
Angular Robot c4784bd39e build: update dependency bazel to v8.5.1
See associated pull request for more information.
2026-01-14 07:58:21 -08:00
Alan Agius d576ea577d fix(docs-infra): remove trailing slash from sitemap URLs
Fixes an issue where a trailing slash was sometimes added to the end of URLs in the sitemap, causing unnecessary redirects.

Example of problematic URL:
```xml
<loc>https://angular.dev/</loc>
```

(cherry picked from commit 710a4bf02b)
2026-01-14 15:57:13 +00:00
Alan Agius 230b65cc81 build: remove tsx dependency and update release script to use node directly
Node.js can run typescript directly.

(cherry picked from commit 95e8c2fdee)
2026-01-14 15:56:42 +00:00
Andrew Scott 6d3b09326a build: Update vscode release script
- Ensure there is a GITHUB_TOKEN environment variable at the start so we can push the release
- More robust handling for finding releaser's fork if it's not 'origin'

(cherry picked from commit 227acddd3a)
2026-01-13 21:08:30 +00:00
Matthieu Riegler 9a764e0f5f refactor(core): remove ng when invoking enableProdMode
This was caught while investigating a leak.

(cherry picked from commit cb5879f566)
2026-01-13 19:39:53 +00:00
Andrew Scott 9c1a5a5433 build: update cherrypick changelog to stash
there are often some formatting/newline changes caused by other steps. just stash anything

(cherry picked from commit 029685bd93)
2026-01-13 19:20:20 +00:00
Kristiyan Kostadinov a649fc8f57 fix(vscode-extension): add syntax highlighting for arrow functions
Updates the syntax definition to handle arrow functions. The definition is largely based on TypeScript's own syntax highlighting since it's quite complex.

(cherry picked from commit b95753eb7d)
2026-01-13 19:09:30 +00:00
Kristiyan Kostadinov 8f16846dd9 fix(vscode-extension): add syntax highlighting for spread/rest expressions
Updates the syntax definition to include spread/rest expressions.

(cherry picked from commit c70a6a6ecc)
2026-01-13 19:09:29 +00:00
Jeremy Elbourn 114d03f6a6 docs: update OnChanges API reference w/ signal input
We had some reports of LLMs claiming that `ngOnChanges` does not include
changes for signal-based inputs. This is wrong (and the LLMs just made
it up), but we can update the docs at least to demonstrate `ngOnChanges`
with signal-based inputs.

Previously, the API reference used a real test as a code example, but
updating the test to a signal-based input is more involved than this
change needs to call for.

(cherry picked from commit 871fa1b2dd)
2026-01-13 17:04:28 +00:00
Angular Robot bf5db2b8d3 build: lock file maintenance
See associated pull request for more information.
2026-01-13 08:59:44 -08:00
Angular Robot c8b1b15143 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-01-13 08:44:21 -08:00
lyutails 559797c19d docs: update hierarchical-dependency-injection.md
VillainsService highly likely misspelled and then written as VillainService with 's' missing

(cherry picked from commit 910768f5cc)
2026-01-13 16:31:45 +00:00
Alan Agius 33ea12a797 build: remove @angular-devkit/architect-cli from dev dependencies
This dependency is not used.
2026-01-13 08:30:17 -08:00
Alan Agius 1dd06f5e5c ci: update Renovate configuration to replace Bazel module dependency command
This uses `ng-dev misc sync-module-bazel`, add `MODULE.bazel` to file filters, and remove specific dependency name matching.

(cherry picked from commit 6e82167481)
2026-01-13 16:28:47 +00:00
Jessica Janiuk 76a6984d7b release: bump VSCode extension version to 21.1.0 2026-01-12 16:35:27 -08:00
Andrew Scott f5ed514b41 build: improve changelog generation and tag fetching in release script
- Fetch tags explicitly to ensure local availability.
- Implement `getPreviousTag` to reliably determine the base for changelog generation, falling back to the latest `vsix-*` tag if the specific previous version tag is missing.
- Filter changelog commits by subject to exclude duplicates (e.g. cherry-picks) that are already present in the previous release history but have different hashes.

(cherry picked from commit 69da6390b1)
2026-01-13 00:06:39 +00:00
Andrew Scott 3396943fd0 build: fix changelog generation for last rev
updates changelog generation to work when releasing the first final
version.

(cherry picked from commit 78755771e1)
2026-01-12 22:53:23 +00:00
Andrew Scott eecbdc078e build: Set version of vscode to rc
this allows correct computation of the last version in teh release
script. we should update the rc branching to ensure this happens
automatically maybe
2026-01-12 14:49:27 -08:00
SkyZeroZx 40864e85da docs(docs-infra): replace deprecated Router.isActive with isActive (#66430)
Updates the view transition logic to use the isActive function instead of the deprecated Router.isActive

PR Close #66430
2026-01-12 22:42:33 +00:00
SkyZeroZx b1c68e66a7 docs: update examples to use isActive instead of deprecated Router.isActive (#66430)
PR Close #66430
2026-01-12 22:42:33 +00:00
Andrew Scott 757e0cfd26 build: use tsx for release script
I otherwise get Unknown file extension ".mts". using tsx fixes it for me

(cherry picked from commit e34365cfd5)
2026-01-12 22:41:17 +00:00
Matthieu Riegler 532633301b fix(forms): Ensure the control instruction comes after the other bindings
Prior to this change, binding to radio value was sensitive to the order in which `value` & `formField` where binding in the template.
The compiler change makes that order non-important.

fixes #66402

(cherry picked from commit 65fa5b5439)
2026-01-12 21:49:23 +00:00
cexbrayat 8474277b49 test(forms): improve test coverage for multiple pattern validators
Add comprehensive test cases to validate behavior when multiple
pattern validators are applied to the same field.

These tests clarify that multiple patterns operate with AND logic,
where each pattern is validated independently and produces its own
error when it doesn't match.

(cherry picked from commit 7fd076bdd3)
2026-01-12 21:48:20 +00:00
Shuaib Hasan Akib 5ad2e1bae0 docs: replace old https://angular.io/license with https://angular.dev/license
(cherry picked from commit 1b4dcc01ff)
2026-01-12 21:41:34 +00:00
Mikhail Efanov ab86c4dfaf build: update dev container configuration
build: update dev container configuration

build: update dev container configuration

Update dev container configuration files to support latest environment.
- Use customizations.vscode structure instead of deprecated extensions property.
- Update bazel extension to BazelBuild.vscode-bazel.
- Ignore .pnpm-store directory.
- Update ports list
- Use forwardPorts instead appPort

Document the usage of Dev Containers in building-and-testing-angular.md.

(cherry picked from commit 27276374ab)
2026-01-12 21:39:34 +00:00
Shuaib Hasan Akib 0587bb77ad fix(docs-infra): use margin instead of padding to prevent heading hover overflow
Replaced padding-block-start with margin-block-start on
.docs-reference-section-heading to prevent anchor hover and click areas from extending into empty space above the heading. This ensures hover behavior aligns with the visible text.

(cherry picked from commit f53f878e0f)
2026-01-12 21:38:50 +00:00
Matthieu Riegler 79f979a673 docs(docs-infra): Throw build error on invalid guide links
The list of valid links is generated from navigation data configuration in the ADEV app.
Redirections are knowingly exclude so we stop referencing them.

(cherry picked from commit c09f5352f6)
2026-01-12 21:38:23 +00:00
Shuaib Hasan Akib cc066cb31f fix(docs-infra): prevent duplicate description rendering for block API entries
Block entries (@if, @defer, @for,@let, @switch) were falling back to the generic
DocsReference template, causing the description to appear twice - once in
the header section and once in the main content area.

This commit adds a dedicated rendering path for block entries:
- Creates BlockEntryRenderable type and associated transforms
- Adds BlockReference template that uses RawHtml directly
- Modifies HeaderApi to accept hideDescription prop
- Updates processing and rendering pipelines to handle blocks

The fix ensures block documentation displays only one description section
while preserving the existing behavior for all other API entry types.

Update adev/shared-docs/pipeline/api-gen/rendering/transforms/block-transforms.mts

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
(cherry picked from commit 8bdd98ef41)
2026-01-12 21:37:58 +00:00
Roshith 3932bab633 docs: fix spelling mistake in documentation
correct typo where "youself" was written instead of "yourself"

(cherry picked from commit 933ca9e5dc)
2026-01-12 21:37:00 +00:00
Joey Perrott e66a8ef0da build: begin requring usage of release mode for releases
Start requiring the usage of the release merge-mode for allowing releases to be made from the repository

(cherry picked from commit 4d6a6aafee)
2026-01-12 21:36:22 +00:00
Angular Robot ed5b62a140 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-12 13:34:56 -08:00
Angelo Parziale 1f47e885fd build(docs-infra): Update dependency @xterm/xterm to v6
Update dependency @xterm/xterm to v6 and fix style

(cherry picked from commit 4d983e9904)
2026-01-12 18:16:28 +00:00
SkyZeroZx d1f6bcdacd docs: remove Component suffix in animations, HTTP, and SSR guides
Removes the unnecessary `Component` suffix from examples.
Also fixes button styling in the animations examples, which previously did not render correctly in dark mode.

(cherry picked from commit 511d9ca9d2)
2026-01-12 18:07:09 +00:00
Shuaib Hasan Akib ed292e398a docs: correct line number highlighting in query examples
Updates incorrect highlighted line numbers so they match the intended
code being referenced in the documentation.

(cherry picked from commit c63bc58e0f)
2026-01-12 18:04:18 +00:00
SkyZeroZx 3954dc278b refactor(http): remove redundant providedIn: 'root' in XSRF_HEADER_NAME
Removes unnecessary `providedIn: 'root'` declarations from injection token

(cherry picked from commit e38c1bf743)
2026-01-12 18:00:30 +00:00
SkyZeroZx 8cf3a72d57 refactor(core): Add missing OnDestroy to zone and zoneless change detection schedulers
Implements the OnDestroy interface in NgZoneChangeDetectionScheduler and ChangeDetectionSchedulerImpl

(cherry picked from commit fa6e82b569)
2026-01-12 18:00:29 +00:00
Matthieu Riegler 481557e69f docs(docs-infra): prevent from showing duplicate usage blocks in functions API docs
fixes #66398

(cherry picked from commit b22835579e)
2026-01-12 17:50:16 +00:00
Matthieu Riegler 13a7ee8424 docs(docs-infra): Show API decorators docs
fixes #66129

(cherry picked from commit a056413520)
2026-01-12 17:41:28 +00:00
Matthieu Riegler fd3253e093 docs(docs-infra): Add community russian translation
fixes #65720

(cherry picked from commit 33b2d9905d)
2026-01-12 17:40:11 +00:00
Matthieu Riegler addccead9b docs: change language section to "community translations"
(cherry picked from commit ad699cebb5)
2026-01-12 17:30:22 +00:00
Angular Robot 8f451b5647 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-12 08:55:39 -08:00
omar-almasry11 e4dcd72926 fix(docs-infra): improve symbolic green contrast for WCAG compliance
Adjusts the symbolic green color to meet WCAG contrast requirements in documentation styles.

(cherry picked from commit 405b05607c)
2026-01-12 16:50:37 +00:00
Jeevan Mahesha 204aacadb4 docs: update testing guide to include Vitest and improve authGuard test setup
(cherry picked from commit 66b40d4fb3)
2026-01-12 16:32:14 +00:00
Angular Robot e9cb3cbefb build: update pnpm to v10.28.0
See associated pull request for more information.
2026-01-12 08:31:22 -08:00
Matthieu Riegler 4871131136 docs: improve separation between enter and leave example
fixes #66359

(cherry picked from commit 7f1b790f84)
2026-01-12 16:18:20 +00:00
Kristiyan Kostadinov fb21801827 build: update license for vscode extension
The vscode extension still has the old license headers pointing pointing to AIO.

(cherry picked from commit f0dba6deb9)
2026-01-12 16:17:43 +00:00
aparziale 3de709f41b docs(docs-infra): Angular Aria listbox shows placeholder for "Selection modes"
Improved documentation for the aria listbox component. Added an example comparing selectionMode input.

Fixes: #65682
(cherry picked from commit e7f24014cf)
2026-01-12 16:17:17 +00:00
Angular Robot 47d89c8ca2 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-12 08:15:35 -08:00
SkyZeroZx c7d6053797 refactor(compiler-cli): update error message links to versioned docs (#66374)
Error message links now point to the archived documentation site (v*.angular.dev)
so that referenced content matches the framework version in use.

See angular#44650

PR Close #66374
2026-01-09 22:33:53 +00:00
SkyZeroZx 03e2b36826 refactor(core): update error message links to versioned docs (#66374)
Error message links now point to the archived documentation site (v*.angular.dev)
so that referenced content matches the framework version in use.

See angular#44650

PR Close #66374
2026-01-09 22:33:52 +00:00
Miles Malerba 29f074a93f fix(forms): Rename signal form [field] to [formField]
This completes the rename started in #66136. `[field]` is too generic of
a selector for the forms system to own, and likely to cause naming
collisions with existing components. Therefore it is being renamed to
`[formField]`

(cherry picked from commit 5671f2cc07)
2026-01-09 22:33:13 +00:00
tomer953 62d7533ca6 fix(migrations): prevent duplicate imports in common-to-standalone migration
The common-to-standalone migration did not check for existing imports
when adding needed imports after removing CommonModule. This change adds deduplication
logic to filter out imports that already exist in the imports array
before adding them, ensuring each import appears only once.

(cherry picked from commit 008e20ca56)
2026-01-09 22:30:52 +00:00
Angular Robot a59f51f0e6 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-09 11:26:35 -08:00
Alan Agius a201d33e0c ci: add pnpm and typescript exclusions to a Renovate package rule.
This will be handled in dev-infra preset, see: https://github.com/angular/dev-infra/pull/3340

(cherry picked from commit 99db2e9065)
2026-01-09 18:41:58 +00:00
fisker 0875deab8d refactor(compiler): switch Binary.isAssignmentOperation to type guard function
Improve `Binary.isAssignmentOperation` types

(cherry picked from commit 4fb24cadee)
2026-01-09 17:59:33 +00:00
Angular Robot dd37fd821e build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-09 09:56:00 -08:00
SkyZeroZx 7b396e8bb2 docs(docs-infra): avoid persistent AbortSignal listener in wait debounce in search
Cleans up the abort listener once the timeout resolves to prevent it from
remaining attached longer than necessary.

(cherry picked from commit 94b707957a)
2026-01-09 17:38:11 +00:00
Yahya-Almubarak 24b0920755 docs: replace UnambiguousRoleValidator in asyc-valdotors part form-validation.md
The async validators part of document uses uniqueRoleValidator and not UnambiguousRoleValidator.

(cherry picked from commit e3b3290998)
2026-01-09 17:26:22 +00:00
Devin Chasanoff f3a7b3ab67 docs: add ai tutor signal forms docs
(cherry picked from commit 09234753e1)
2026-01-09 17:25:43 +00:00
Angular Robot 1c055aa268 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-09 09:05:45 -08:00
kirjs 0c23d02944 docs(forms): Clarify returning errors from submit functions
Update outdate comment, and add a section to the docs

(cherry picked from commit 7d985ce08e)
2026-01-09 16:47:46 +00:00
fisker 83bac5adc9 refactor(compiler): tighten Unary.operator type
Improve `Unary.operator` type

(cherry picked from commit a7e470a1b3)
2026-01-09 16:47:05 +00:00
fisker e01dcae4c5 refactor: _ParseAST.isAssignmentOperator to type guard
Update `_ParseAST.isAssignmentOperator` to type guard

(cherry picked from commit 4d19408e9b)
2026-01-09 16:45:40 +00:00
fisker 4cdf4d5760 refactor(compiler): tighten Binary.operation type
Improve `Binary.operation` types

(cherry picked from commit 07a08ab9f8)
2026-01-09 16:45:39 +00:00
hawkgs f41d721ce0 refactor(devtools): style the profiler dialogs to match the current design
Decrease the font size and tone down the paddings and margins. Use `ng-button` instead of `mat-button`.

(cherry picked from commit e66aeac8d0)
2026-01-09 16:23:21 +00:00
hawkgs 0fd8477f3f refactor(devtools): add secondary type button
Add a secondary style/type button to `ng-button`.

(cherry picked from commit bea5840864)
2026-01-09 16:23:21 +00:00
SkyZeroZx 5dd2f2bb98 docs(docs-infra): improve label fallback logic in search results
Improves label fallback in search results by providing a fallback to the top-level category when a more specific label is missing.

(cherry picked from commit 3a65814f53)
2026-01-09 15:58:36 +00:00
SkyZeroZx ca57165c7e docs: add docs support for spread and rest operators in expression syntax
(cherry picked from commit 0d35088b56)
2026-01-09 15:58:11 +00:00
Angular Robot 4391c44801 build: update cross-repo angular dependencies to v21.1.0-next.4
See associated pull request for more information.
2026-01-09 07:50:14 -08:00
Alon Mishne 985bdeefd3 docs: Updated MCP docs to include latest experimental tools and recent renames
(cherry picked from commit c963f35d2c)
2026-01-09 15:48:26 +00:00
Matthieu Riegler 95bc06d6c3 Revert "docs: remove incorrectly listed operators in "Supported operators" table (Expression Syntax Guide) (#62092)"
This reverts commit ea71a1e8cd.

(cherry picked from commit 80a7419a16)
2026-01-09 15:47:52 +00:00
Angular Robot 2c3a067bb6 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-01-08 14:42:26 -08:00
Angular Robot 2c74475969 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-01-08 14:41:15 -08:00
Andrew Scott 74af7d85e5 refactor(core): Use the provided Document value rather than global in FakeNavigation
This commit ensures the Document used by `FakeNavigation` is the one
passed in the constructor rather than the global `document`, which may
be different.

(cherry picked from commit 49295778bc)
2026-01-08 21:28:23 +00:00
Jessica Janiuk 6ad2ef6bd8 release: cut the v21.1.0-rc.0 release 2026-01-08 12:50:38 -08:00
1279 changed files with 42457 additions and 46615 deletions
+1 -1
View File
@@ -1 +1 @@
8.5.0
8.5.1
+5 -12
View File
@@ -1,23 +1,16 @@
# Image metadata and config.
# Ideally, the Node.js version should match what we use on CI.
FROM cimg/node:18.13.0@sha256:45d0ee279f522c4562a464df71108104cc9b2187205333ffd0707d4f05589731
FROM cimg/node:22.21.1-browsers@sha256:eee8ed828d5a54196456a86ac6872c43325acad54e59632261e23b486c458992
LABEL name="Angular dev environment" \
description="This image can be used to create a dev environment for building Angular." \
vendor="angular" \
version="1.0"
EXPOSE 4000 4200 4433 5000 8080 9876
description="This image can be used to create a dev environment for building Angular." \
vendor="angular" \
version="1.0"
EXPOSE 4200 4201 4205 4206 9876
# Switch to `root`.
USER root
# Configure `Node.js`/`npm`.
RUN npm config --global set user root
# Go! (And keep going.)
CMD ["tail", "--follow", "/dev/null"]
+6 -5
View File
@@ -2,10 +2,11 @@
{
"name": "Angular dev container",
"dockerFile": "Dockerfile",
"appPort": [4000, 4200, 4433, 5000, 8080, 9876],
"forwardPorts": [4200, 4201, 4205, 4206, 9876],
"postCreateCommand": "pnpm install",
"extensions": [
"devondcarew.bazel-code",
"ms-vscode.vscode-typescript-tslint-plugin"
],
"customizations": {
"vscode": {
"extensions": ["BazelBuild.vscode-bazel", "ms-vscode.vscode-typescript-tslint-plugin"]
}
}
}
+70
View File
@@ -0,0 +1,70 @@
description = "Researches part of the codebase and creates a skill for it"
prompt="""
## Role
You are an experienced senior developer specializing in reverse engineering and understanding
complex code, prompt engineering, and context engineering.
Your task is to build a complete understanding of the mental model and architecture of the code,
and to create an efficient context file that allows a coding agent to understand and work with the
code.
## Guiding Principles
1. **Deep Analysis:** Understand the _why_ behind the code. Not just a surface level listing of
files and classes.
2. **Curiosity:** Be curious. If something is unclear, confusing, or unexpected you _MUST_
investigate further until you fully understand.
3. **Efficient Token Use:** The final output will be directly included into the context of the
coding assistant, so be sure to make efficient use of tokens. Think deeply, but only capture the
most important information for the coding agent in the final output
## Procedure
1. Fully explore the code before first, with a focus on understanding the architecture and how all
of the pieces fit together.
2. Develop an understanding of the mental model behind the architecture. _Why_ is the code is
architected this way? How should developers think about the system?
3. Document your findings using the given output format described in the next section.
## Output Format
Note the following placeholders below:
- `[package]`: is a unique identifier for the current code package (for example if you're looking at
code under `packages/forms/signals` you might use `signal-forms` as the `[package]`)
- `[code-location]`: is the location of the current code within the larger repository
- `[context]`: is the context to add to the coding agent when it is working with this code.
With those placeholders in mind, write a file create a new directory under `.gemini/skills` with the
name `reference-[package]/`, and under that directory add a `SKILL.md` file with the following
content:
```md
---
name: reference-[package]
description: Explains the mental model and architecture of the code under `[code-location]`. You MUST use this skill any time you plan to work with code in `[code-location]`
---
[context]
```
## Additional User Instructions
The user may have additional instructions.
- The user should indicate what package to research
- The user may indicate that they want to update a package that has already been researched. In this
case, read the existing skill and make any required updates based on your research
- The user may request supplemental information be added to the skill (if so, add this to the
existing skill in a new markdown file under a `references` directory within the skill. Add a
section to the main `SKILL.md` that mentions what supplemental information is available)
Here are the user's additional instructions:
{{args}}
"""
+5
View File
@@ -0,0 +1,5 @@
{
"context": {
"fileName": ["AGENTS.md", "GEMINI.md"]
}
}
@@ -0,0 +1,91 @@
---
name: reference-compiler-cli
description: Explains the mental model and architecture of the code under `packages/compiler-cli`. You MUST use this skill any time you plan to work with code in `packages/compiler-cli`
---
# Angular Compiler CLI (`ngtsc`) Architecture
## Overview
The `packages/compiler-cli` package contains the Angular Compiler (Ivy), often referred to as `ngtsc`. It is a wrapper around the TypeScript compiler (`tsc`) that extends it with Angular-specific capabilities.
The core goal of `ngtsc` is to compile Angular decorators (like `@Component`, `@Directive`, `@Pipe`) into static properties on the class (Ivy instructions, e.g., `static ɵcmp = ...`). It also performs template type checking and ahead-of-time (AOT) compilation.
## Mental Model
The compiler is designed as a **lazy, incremental, and partial** compilation pipeline.
1. **Wrapper Pattern**: `NgtscProgram` wraps the standard `ts.Program`. It intercepts calls to act as a drop-in replacement for standard tooling.
2. **Traits System**: Every class with an Angular decorator is considered a "Trait". The compiler manages the state of these traits through a state machine:
- **Pending**: Detected but not processed.
- **Analyzed**: Metadata extracted, template parsed (but dependencies not yet linked).
- **Resolved**: Dependencies (directives/pipes in template) resolved, import cycles handled.
- **Skipped**: Not an Angular class.
3. **Lazy Analysis**: Analysis only happens when necessary (e.g., when diagnostics are requested or emit is prepared).
4. **Output AST**: The compiler generates an intermediate "Output AST" (`o.Expression`) for the generated code, which is then translated into TypeScript AST nodes during the emit phase.
## Key Subsystems
### 1. Core Orchestration (`ngtsc/core`)
- **`NgtscProgram`**: The public API implementing `api.Program`. It manages the `ts.Program` and the `NgCompiler`.
- **`NgCompiler`**: The brain of the compiler. It orchestrates the compilation phases (Analysis, Resolution, Type Checking, Emit). It holds the `TraitCompiler`.
### 2. Trait Compilation (`ngtsc/transform`)
- **`TraitCompiler`**: Manages the lifecycle of "Traits". It iterates over source files, identifies decorated classes, and delegates to the appropriate `DecoratorHandler`.
- **`Trait`**: A state container for a class, holding its handler, analysis results, and resolution results.
### 3. Decorator Handlers (`ngtsc/annotations`)
- **`DecoratorHandler`**: An interface for handling specific decorators.
- **`ComponentDecoratorHandler`**: The most complex handler. It:
- Extracts metadata (selector, inputs, outputs).
- Parses the template.
- Resolves used directives and pipes (`R3TargetBinder`).
- Generates the `ɵcmp` instruction.
- **`DirectiveDecoratorHandler`**, **`PipeDecoratorHandler`**, **`NgModuleDecoratorHandler`**: Handle their respective decorators.
### 4. Template Type Checking (`ngtsc/typecheck`)
- **`TemplateTypeChecker`**: Generates "Type Check Blocks" (TCBs). A TCB is a block of TypeScript code that represents the template's logic in a way `tsc` can understand and check for errors.
- **`TypeCheckBlock`**: The actual generated code that validates bindings, events, and structural directives.
### 5. Metadata & Scope (`ngtsc/metadata`, `ngtsc/scope`)
- **`MetadataReader`**: Reads Angular metadata from source files (using `LocalMetadataRegistry`) and `.d.ts` files (using `DtsMetadataReader`).
- **`ScopeRegistry`**: Determines the "compilation scope" of a component (which directives/pipes are available to it), handling `NgModule` transitive exports and Standalone Component imports.
### 6. Emit & Transformation (`ngtsc/transform`)
- **`ivyTransformFactory`**: A TypeScript transformer factory.
- **`IvyCompilationVisitor`**: Visits classes, triggers compilation via `TraitCompiler`, and collects the Output AST.
- **`IvyTransformationVisitor`**: Translates the Output AST into TypeScript AST, injects the `static ɵ...` fields, and removes the original decorators.
## Compilation Phases
1. **Construction**: `NgtscProgram` creates `NgCompiler`, which sets up all registries and the `TraitCompiler`.
2. **Analysis** (`analyzeSync`):
- The `TraitCompiler` scans files.
- `DecoratorHandler`s extract metadata and parse templates.
- No cross-file resolution happens here (allowing for parallelism and caching).
3. **Resolution** (`resolve`):
- `TraitCompiler` resolves traits.
- Components link their templates to specific Directives and Pipes (found via `ScopeRegistry`).
- Import cycles are detected and handled (e.g., via "remote scoping").
4. **Type Checking**:
- `TemplateTypeChecker` creates TCBs for all components.
- TypeScript diagnostics are retrieved for these TCBs.
5. **Emit** (`prepareEmit`):
- `ivyTransformFactory` is created.
- TS `emit` is called.
- The transformers run, injecting the compiled Ivy instructions into the JS/DTS output.
## Important File Locations
- `packages/compiler-cli/src/ngtsc/program.ts`: Entry point (`NgtscProgram`).
- `packages/compiler-cli/src/ngtsc/core/src/compiler.ts`: Core logic (`NgCompiler`).
- `packages/compiler-cli/src/ngtsc/transform/src/trait.ts`: Trait state machine.
- `packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts`: Component compilation logic.
- `packages/compiler-cli/src/ngtsc/typecheck/src/template_type_checker.ts`: Type checking logic.
- `packages/compiler-cli/src/ngtsc/transform/src/transform.ts`: AST transformation logic.
+96
View File
@@ -0,0 +1,96 @@
---
name: reference-core
description: Explains the mental model and architecture of the code under `packages/core`. You MUST use this skill any time you plan to work with code in `packages/core`
---
# Angular Core (`packages/core`) Mental Model
This document outlines the architecture and mental model for `packages/core`, the heart of the Angular framework.
## 1. High-Level Architecture
`packages/core` contains the runtime logic for Angular. Its primary responsibilities are:
1. **Rendering (Ivy/Render3)**: Transforming templates into DOM updates.
2. **Dependency Injection (DI)**: Managing object creation and lifetime.
3. **Change Detection**: Synchronizing the model with the view.
4. **Reactivity**: Signals and Zone.js integration.
## 2. Rendering Engine (Ivy / Render3)
The rendering engine (located in `packages/core/src/render3`) uses an **instruction-based** approach.
### Key Concepts
- **Instructions**: The Angular compiler transforms templates into a sequence of instruction calls (e.g., `ɵɵelementStart`, `ɵɵtext`, `ɵɵproperty`). These instructions are executed at runtime to create and update the view.
- _Location_: `packages/core/src/render3/instructions`
- **LView (Logical View)**: An array containing the _state_ of a specific view instance. It holds:
- DOM nodes (`RElement`, `RText`).
- Binding values (for change detection).
- Directive/Component instances.
- _Context_: `packages/core/src/render3/interfaces/view.ts`
- **TView (Template View)**: An array containing the _static structure_ of a view. It is shared across all instances (`LView`s) of the same component/template. It holds:
- Property names for bindings.
- Node relationship information.
- Compiled directive definitions.
- _Context_: `packages/core/src/render3/interfaces/view.ts`
- **Memory Layout**: `LView` and `TView` are parallel arrays. Index `i` in `LView` corresponds to metadata at index `i` in `TView`.
- `HEADER`: Fixed size, contains context (Parent, Host, etc.).
- `DECLS`: Static nodes (elements, text, pipes).
- `VARS`: Binding values.
- `EXPANDO`: Dynamic data (host bindings, injectors).
### The Render Cycle
1. **Creation Mode**: Instructions create DOM nodes and store them in `LView`.
2. **Update Mode**: Instructions check current values against previous values stored in `LView`. If changed, they update the DOM.
## 3. Dependency Injection (DI)
DI in Angular is hierarchical and split into two systems that interact:
### Module Injector (`R3Injector`)
- Configured via `@NgModule.providers` or `providedIn: 'root'`.
- Stored in a hierarchy of `R3Injector` instances.
- _Location_: `packages/core/src/di/r3_injector.ts`
### Node Injector
- Configured via `@Component.providers` or `@Directive.providers`.
- **Not a class**, but a data structure embedded in the `LView` ("Expando" section).
- Uses **Bloom Filters** (`TView.data`) to quickly check if a token is present at a specific node index before traversing up the tree.
- Resolves tokens starting from the current node, walking up the view tree (Element Injector hierarchy), and falling back to the Module Injector if not found.
## 4. Change Detection
- **Dirty Checking**: Angular checks if values bound in templates have changed.
- **Strategies**:
- `Default`: Checks everything.
- `OnPush`: Checks only if inputs change, events fire, or signals update.
- **Signals**: The new reactivity primitive. Signals notify the scheduler when they change, potentially allowing for fine-grained updates (Zoneless).
## 5. Key Directories to Know
- `src/render3`: The Ivy rendering engine.
- `instructions`: The runtime instructions called by compiled code.
- `interfaces`: `LView`, `TView`, `TNode` definitions.
- `src/di`: Dependency injection system.
- `src/change_detection`: Change detection logic.
- `src/zone`: Zone.js integration.
- `src/signal`: Signals implementation (if present in this version, otherwise likely in `primitives`).
## 6. Conventions & Gotchas
- **Prefixes**: Private/Internal exports often start with `ɵ`.
- **Global State**: Ivy relies heavily on global state (e.g., `getLView()`) during instruction execution to avoid passing context arguments everywhere. This is for performance and code size.
- **Performance**: The code is highly optimized for performance and memory. You will see arrays used instead of objects, bitmasks, and manual memory management patterns. **Respect these patterns.**
## 7. How to Modify Core
1. **Understand the Instruction**: If modifying runtime behavior, find the corresponding instruction in `src/render3/instructions`.
2. **Check `LView`/`TView` Impact**: If adding state, understand where it fits in the `LView` array.
3. **Tests**: Core has extensive tests. Run them using Bazel.
@@ -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.
+48
View File
@@ -0,0 +1,48 @@
name: 'VSCode Angular Language Service extension'
description: Report an issue or suggest a feature for the VSCode Angular Language Service extension
body:
- type: checkboxes
id: bug-report-or-feature-request
attributes:
label: Is this a bug report or a feature request?
description: Select one.
options:
- label: Bug Report
- label: Feature Request
- type: textarea
id: reproduction-steps
attributes:
label: Please provide the steps to reproduce the issue [Bug Report only]
- type: textarea
id: expected-vs-actual-behavior
attributes:
label: Please provide the expected behavior vs the actual behavior you encountered [Bug Report only]
- type: textarea
id: screenshot
attributes:
label: Please provide a screenshot if possible [Bug Report only]
- type: textarea
id: exception-or-error
attributes:
label: Please provide the exception or error you saw [Bug Report only]
render: true
- type: textarea
id: description
attributes:
label: Description [Feature Request only]
- type: textarea
id: proposed-solution
attributes:
label: Proposed solution [Feature Request only]
- type: textarea
id: alternatives-considered
attributes:
label: Alternatives considered [Feature Request only]
@@ -1,33 +1,53 @@
import {Deployment} from './deployments.mjs';
import {join} from 'path';
import {readFileSync, writeFileSync} from 'fs';
export async function generateSitemap(deployment: Deployment, distDir: string) {
const servingUrlWithoutEndingSlash = deployment.servingUrl.endsWith('/')
? deployment.servingUrl.slice(0, -1)
: deployment.servingUrl;
import {join} from 'node:path';
import {readFile, writeFile} from 'node:fs/promises';
export async function generateSitemap(deployment: Deployment, distDir: string): Promise<void> {
/** Timestamp string used to of the last file update. */
const lastModifiedTimestamp = new Date().toISOString();
/** An object containing all of the routes available within the application. */
const routes = JSON.parse(readFileSync(join(distDir, 'prerendered-routes.json'), 'utf-8'));
const {routes} = JSON.parse(await readFile(join(distDir, 'prerendered-routes.json'), 'utf-8'));
const routePaths = Object.keys(routes);
/** The generated sitemap string. */
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${Object.keys(routes.routes)
.map((route) => {
const routeWithoutLeadingSlash = route.startsWith('/') ? route.slice(1) : route;
return `
<url>
<loc>${servingUrlWithoutEndingSlash}/${routeWithoutLeadingSlash}</loc>
${routePaths
.map(
(route) => `<url>
<loc>${joinUrlParts(deployment.servingUrl, route)}</loc>
<lastmod>${lastModifiedTimestamp}</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
`;
})
</url>`,
)
.join('')}
</urlset>`;
writeFileSync(join(distDir, 'browser', 'sitemap.xml'), sitemap, 'utf-8');
console.log(`Generated sitemap with ${Object.keys(routes.routes).length} entries.`);
await writeFile(join(distDir, 'browser', 'sitemap.xml'), sitemap, 'utf-8');
console.log(`Generated sitemap with ${routePaths.length} entries.`);
}
function joinUrlParts(...parts: string[]): string {
const normalizeParts: string[] = [];
for (const part of parts) {
if (part === '') {
// Skip any empty parts
continue;
}
let normalizedPart = part;
if (part[0] === '/') {
normalizedPart = normalizedPart.slice(1);
}
if (part.at(-1) === '/') {
normalizedPart = normalizedPart.slice(0, -1);
}
if (normalizedPart !== '') {
normalizeParts.push(normalizedPart);
}
}
return normalizeParts.join('/');
}
+215 -120
View File
@@ -762,7 +762,7 @@ var require_lib = __commonJS({
this._maxRetries = 1;
this._keepAlive = false;
this._disposed = false;
this.userAgent = userAgent2;
this.userAgent = this._getUserAgentWithOrchestrationId(userAgent2);
this.handlers = handlers || [];
this.requestOptions = requestOptions;
if (requestOptions) {
@@ -1187,6 +1187,15 @@ var require_lib = __commonJS({
}
return proxyAgent;
}
_getUserAgentWithOrchestrationId(userAgent2) {
const baseUserAgent = userAgent2 || "actions/http-client";
const orchId = process.env["ACTIONS_ORCHESTRATION_ID"];
if (orchId) {
const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, "_");
return `${baseUserAgent} actions_orchestration_id/${sanitizedId}`;
}
return baseUserAgent;
}
_performExponentialBackoff(retryNumber) {
return __awaiter(this, void 0, void 0, function* () {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
@@ -1491,7 +1500,7 @@ var require_summary = __commonJS({
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
var os_1 = __require("os");
var fs_1 = __require("fs");
var { access, appendFile, writeFile: writeFile3 } = fs_1.promises;
var { access, appendFile, writeFile: writeFile4 } = fs_1.promises;
exports.SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY";
exports.SUMMARY_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";
var Summary = class {
@@ -1549,7 +1558,7 @@ var require_summary = __commonJS({
return __awaiter(this, void 0, void 0, function* () {
const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
const filePath = yield this.filePath();
const writeFunc = overwrite ? writeFile3 : appendFile;
const writeFunc = overwrite ? writeFile4 : appendFile;
yield writeFunc(filePath, this._buffer, { encoding: "utf8" });
return this.emptyBuffer();
});
@@ -1822,9 +1831,13 @@ var require_io_util = __commonJS({
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() {
return m[k];
} });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() {
return m[k];
} };
}
Object.defineProperty(o, k2, desc);
} : function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
@@ -1835,18 +1848,30 @@ var require_io_util = __commonJS({
} : function(o, v) {
o["default"] = v;
});
var __importStar = exports && exports.__importStar || function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k in mod)
if (k !== "default" && Object.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
}
__setModuleDefault(result, mod);
return result;
};
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function(o2) {
var ar = [];
for (var k in o2)
if (Object.prototype.hasOwnProperty.call(o2, k))
ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
if (k[i] !== "default")
__createBinding(result, mod, k[i]);
}
__setModuleDefault(result, mod);
return result;
};
}();
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve5) {
@@ -1876,17 +1901,32 @@ var require_io_util = __commonJS({
};
var _a2;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0;
exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0;
exports.readlink = readlink;
exports.exists = exists;
exports.isDirectory = isDirectory;
exports.isRooted = isRooted;
exports.tryGetExecutablePath = tryGetExecutablePath;
exports.getCmdPath = getCmdPath;
var fs = __importStar(__require("fs"));
var path = __importStar(__require("path"));
_a2 = fs.promises, exports.chmod = _a2.chmod, exports.copyFile = _a2.copyFile, exports.lstat = _a2.lstat, exports.mkdir = _a2.mkdir, exports.open = _a2.open, exports.readdir = _a2.readdir, exports.readlink = _a2.readlink, exports.rename = _a2.rename, exports.rm = _a2.rm, exports.rmdir = _a2.rmdir, exports.stat = _a2.stat, exports.symlink = _a2.symlink, exports.unlink = _a2.unlink;
_a2 = fs.promises, exports.chmod = _a2.chmod, exports.copyFile = _a2.copyFile, exports.lstat = _a2.lstat, exports.mkdir = _a2.mkdir, exports.open = _a2.open, exports.readdir = _a2.readdir, exports.rename = _a2.rename, exports.rm = _a2.rm, exports.rmdir = _a2.rmdir, exports.stat = _a2.stat, exports.symlink = _a2.symlink, exports.unlink = _a2.unlink;
exports.IS_WINDOWS = process.platform === "win32";
function readlink(fsPath) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield fs.promises.readlink(fsPath);
if (exports.IS_WINDOWS && !result.endsWith("\\")) {
return `${result}\\`;
}
return result;
});
}
exports.UV_FS_O_EXLOCK = 268435456;
exports.READONLY = fs.constants.O_RDONLY;
function exists(fsPath) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield exports.stat(fsPath);
yield (0, exports.stat)(fsPath);
} catch (err) {
if (err.code === "ENOENT") {
return false;
@@ -1896,14 +1936,12 @@ var require_io_util = __commonJS({
return true;
});
}
exports.exists = exists;
function isDirectory(fsPath, useStat = false) {
return __awaiter(this, void 0, void 0, function* () {
const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath);
function isDirectory(fsPath_1) {
return __awaiter(this, arguments, void 0, function* (fsPath, useStat = false) {
const stats = useStat ? yield (0, exports.stat)(fsPath) : yield (0, exports.lstat)(fsPath);
return stats.isDirectory();
});
}
exports.isDirectory = isDirectory;
function isRooted(p) {
p = normalizeSeparators(p);
if (!p) {
@@ -1914,12 +1952,11 @@ var require_io_util = __commonJS({
}
return p.startsWith("/");
}
exports.isRooted = isRooted;
function tryGetExecutablePath(filePath, extensions) {
return __awaiter(this, void 0, void 0, function* () {
let stats = void 0;
try {
stats = yield exports.stat(filePath);
stats = yield (0, exports.stat)(filePath);
} catch (err) {
if (err.code !== "ENOENT") {
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
@@ -1942,7 +1979,7 @@ var require_io_util = __commonJS({
filePath = originalFilePath + extension;
stats = void 0;
try {
stats = yield exports.stat(filePath);
stats = yield (0, exports.stat)(filePath);
} catch (err) {
if (err.code !== "ENOENT") {
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
@@ -1953,7 +1990,7 @@ var require_io_util = __commonJS({
try {
const directory = path.dirname(filePath);
const upperName = path.basename(filePath).toUpperCase();
for (const actualName of yield exports.readdir(directory)) {
for (const actualName of yield (0, exports.readdir)(directory)) {
if (upperName === actualName.toUpperCase()) {
filePath = path.join(directory, actualName);
break;
@@ -1973,7 +2010,6 @@ var require_io_util = __commonJS({
return "";
});
}
exports.tryGetExecutablePath = tryGetExecutablePath;
function normalizeSeparators(p) {
p = p || "";
if (exports.IS_WINDOWS) {
@@ -1983,13 +2019,12 @@ var require_io_util = __commonJS({
return p.replace(/\/\/+/g, "/");
}
function isUnixExecutable(stats) {
return (stats.mode & 1) > 0 || (stats.mode & 8) > 0 && stats.gid === process.getgid() || (stats.mode & 64) > 0 && stats.uid === process.getuid();
return (stats.mode & 1) > 0 || (stats.mode & 8) > 0 && process.getgid !== void 0 && stats.gid === process.getgid() || (stats.mode & 64) > 0 && process.getuid !== void 0 && stats.uid === process.getuid();
}
function getCmdPath() {
var _a3;
return (_a3 = process.env["COMSPEC"]) !== null && _a3 !== void 0 ? _a3 : `cmd.exe`;
}
exports.getCmdPath = getCmdPath;
}
});
@@ -2000,9 +2035,13 @@ var require_io = __commonJS({
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() {
return m[k];
} });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() {
return m[k];
} };
}
Object.defineProperty(o, k2, desc);
} : function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
@@ -2013,18 +2052,30 @@ var require_io = __commonJS({
} : function(o, v) {
o["default"] = v;
});
var __importStar = exports && exports.__importStar || function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k in mod)
if (k !== "default" && Object.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
}
__setModuleDefault(result, mod);
return result;
};
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function(o2) {
var ar = [];
for (var k in o2)
if (Object.prototype.hasOwnProperty.call(o2, k))
ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
if (k[i] !== "default")
__createBinding(result, mod, k[i]);
}
__setModuleDefault(result, mod);
return result;
};
}();
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve5) {
@@ -2053,12 +2104,17 @@ var require_io = __commonJS({
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findInPath = exports.which = exports.mkdirP = exports.rmRF = exports.mv = exports.cp = void 0;
exports.cp = cp2;
exports.mv = mv;
exports.rmRF = rmRF;
exports.mkdirP = mkdirP;
exports.which = which;
exports.findInPath = findInPath;
var assert_1 = __require("assert");
var path = __importStar(__require("path"));
var ioUtil = __importStar(require_io_util());
function cp2(source, dest, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
function cp2(source_1, dest_1) {
return __awaiter(this, arguments, void 0, function* (source, dest, options = {}) {
const { force, recursive, copySourceDirectory } = readCopyOptions(options);
const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null;
if (destStat && destStat.isFile() && !force) {
@@ -2083,9 +2139,8 @@ var require_io = __commonJS({
}
});
}
exports.cp = cp2;
function mv(source, dest, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
function mv(source_1, dest_1) {
return __awaiter(this, arguments, void 0, function* (source, dest, options = {}) {
if (yield ioUtil.exists(dest)) {
let destExists = true;
if (yield ioUtil.isDirectory(dest)) {
@@ -2104,7 +2159,6 @@ var require_io = __commonJS({
yield ioUtil.rename(source, dest);
});
}
exports.mv = mv;
function rmRF(inputPath) {
return __awaiter(this, void 0, void 0, function* () {
if (ioUtil.IS_WINDOWS) {
@@ -2124,14 +2178,12 @@ var require_io = __commonJS({
}
});
}
exports.rmRF = rmRF;
function mkdirP(fsPath) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.ok(fsPath, "a path argument must be provided");
(0, assert_1.ok)(fsPath, "a path argument must be provided");
yield ioUtil.mkdir(fsPath, { recursive: true });
});
}
exports.mkdirP = mkdirP;
function which(tool, check) {
return __awaiter(this, void 0, void 0, function* () {
if (!tool) {
@@ -2155,7 +2207,6 @@ var require_io = __commonJS({
return "";
});
}
exports.which = which;
function findInPath(tool) {
return __awaiter(this, void 0, void 0, function* () {
if (!tool) {
@@ -2197,7 +2248,6 @@ var require_io = __commonJS({
return matches;
});
}
exports.findInPath = findInPath;
function readCopyOptions(options) {
const force = options.force == null ? true : options.force;
const recursive = Boolean(options.recursive);
@@ -2253,9 +2303,13 @@ var require_toolrunner = __commonJS({
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() {
return m[k];
} });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() {
return m[k];
} };
}
Object.defineProperty(o, k2, desc);
} : function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
@@ -2266,18 +2320,30 @@ var require_toolrunner = __commonJS({
} : function(o, v) {
o["default"] = v;
});
var __importStar = exports && exports.__importStar || function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k in mod)
if (k !== "default" && Object.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
}
__setModuleDefault(result, mod);
return result;
};
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function(o2) {
var ar = [];
for (var k in o2)
if (Object.prototype.hasOwnProperty.call(o2, k))
ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
if (k[i] !== "default")
__createBinding(result, mod, k[i]);
}
__setModuleDefault(result, mod);
return result;
};
}();
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve5) {
@@ -2306,7 +2372,8 @@ var require_toolrunner = __commonJS({
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.argStringToArray = exports.ToolRunner = void 0;
exports.ToolRunner = void 0;
exports.argStringToArray = argStringToArray;
var os2 = __importStar(__require("os"));
var events = __importStar(__require("events"));
var child = __importStar(__require("child_process"));
@@ -2669,7 +2736,6 @@ var require_toolrunner = __commonJS({
}
return args;
}
exports.argStringToArray = argStringToArray;
var ExecState = class _ExecState extends events.EventEmitter {
constructor(options, toolPath) {
super();
@@ -2697,7 +2763,7 @@ var require_toolrunner = __commonJS({
if (this.processClosed) {
this._setResult();
} else if (this.processExited) {
this.timeout = timers_1.setTimeout(_ExecState.HandleTimeout, this.delay, this);
this.timeout = (0, timers_1.setTimeout)(_ExecState.HandleTimeout, this.delay, this);
}
}
_debug(message) {
@@ -2742,9 +2808,13 @@ var require_exec = __commonJS({
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() {
return m[k];
} });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() {
return m[k];
} };
}
Object.defineProperty(o, k2, desc);
} : function(o, m, k, k2) {
if (k2 === void 0)
k2 = k;
@@ -2755,18 +2825,30 @@ var require_exec = __commonJS({
} : function(o, v) {
o["default"] = v;
});
var __importStar = exports && exports.__importStar || function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k in mod)
if (k !== "default" && Object.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
}
__setModuleDefault(result, mod);
return result;
};
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function(o2) {
var ar = [];
for (var k in o2)
if (Object.prototype.hasOwnProperty.call(o2, k))
ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null) {
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
if (k[i] !== "default")
__createBinding(result, mod, k[i]);
}
__setModuleDefault(result, mod);
return result;
};
}();
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve5) {
@@ -2795,7 +2877,8 @@ var require_exec = __commonJS({
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getExecOutput = exports.exec = void 0;
exports.exec = exec2;
exports.getExecOutput = getExecOutput;
var string_decoder_1 = __require("string_decoder");
var tr = __importStar(require_toolrunner());
function exec2(commandLine, args, options) {
@@ -2810,10 +2893,9 @@ var require_exec = __commonJS({
return runner.exec();
});
}
exports.exec = exec2;
function getExecOutput(commandLine, args, options) {
var _a2, _b2;
return __awaiter(this, void 0, void 0, function* () {
var _a2, _b2;
let stdout = "";
let stderr = "";
const stdoutDecoder = new string_decoder_1.StringDecoder("utf8");
@@ -2843,7 +2925,6 @@ var require_exec = __commonJS({
};
});
}
exports.getExecOutput = getExecOutput;
}
});
@@ -9139,7 +9220,7 @@ var require_lockfile = __commonJS({
}
const file = _ref22;
if (yield exists(file)) {
return readFile2(file);
return readFile3(file);
}
}
return null;
@@ -9158,7 +9239,7 @@ var require_lockfile = __commonJS({
})();
let readJsonAndFile = exports2.readJsonAndFile = (() => {
var _ref24 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) {
const file = yield readFile2(loc);
const file = yield readFile3(loc);
try {
return {
object: (0, (_map || _load_map()).default)(JSON.parse(stripBOM(file))),
@@ -9302,7 +9383,7 @@ var require_lockfile = __commonJS({
if (eol !== "\n") {
data = data.replace(/\n/g, eol);
}
yield writeFile3(path, data);
yield writeFile4(path, data);
});
return function writeFilePreservingEol2(_x30, _x31) {
return _ref31.apply(this, arguments);
@@ -9314,7 +9395,7 @@ var require_lockfile = __commonJS({
const file = (_path || _load_path()).default.join(dir, filename);
const fileLink = (_path || _load_path()).default.join(dir, filename + "-link");
try {
yield writeFile3(file, "test");
yield writeFile4(file, "test");
yield link(file, fileLink);
} catch (err) {
return false;
@@ -9404,7 +9485,7 @@ var require_lockfile = __commonJS({
};
})();
exports2.copy = copy;
exports2.readFile = readFile2;
exports2.readFile = readFile3;
exports2.readFileRaw = readFileRaw;
exports2.normalizeOS = normalizeOS;
var _fs;
@@ -9469,7 +9550,7 @@ var require_lockfile = __commonJS({
const lockQueue = exports2.lockQueue = new (_blockingQueue || _load_blockingQueue()).default("fs lock");
const readFileBuffer = exports2.readFileBuffer = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readFile);
const open = exports2.open = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.open);
const writeFile3 = exports2.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile);
const writeFile4 = exports2.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile);
const readlink = exports2.readlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readlink);
const realpath = exports2.realpath = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.realpath);
const readdir = exports2.readdir = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readdir);
@@ -9503,7 +9584,7 @@ var require_lockfile = __commonJS({
});
});
}
function readFile2(loc) {
function readFile3(loc) {
return _readFile(loc, "utf8").then(normalizeOS);
}
function readFileRaw(loc) {
@@ -35946,28 +36027,42 @@ async function getDeployments() {
}
// .github/actions/deploy-docs-site/lib/sitemap.mjs
import { join as join4 } from "path";
import { readFileSync as readFileSync4, writeFileSync } from "fs";
import { join as join4 } from "node:path";
import { readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
async function generateSitemap(deployment, distDir) {
const servingUrlWithoutEndingSlash = deployment.servingUrl.endsWith("/") ? deployment.servingUrl.slice(0, -1) : deployment.servingUrl;
const lastModifiedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
const routes = JSON.parse(readFileSync4(join4(distDir, "prerendered-routes.json"), "utf-8"));
const { routes } = JSON.parse(await readFile2(join4(distDir, "prerendered-routes.json"), "utf-8"));
const routePaths = Object.keys(routes);
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${Object.keys(routes.routes).map((route) => {
const routeWithoutLeadingSlash = route.startsWith("/") ? route.slice(1) : route;
return `
<url>
<loc>${servingUrlWithoutEndingSlash}/${routeWithoutLeadingSlash}</loc>
${routePaths.map((route) => `<url>
<loc>${joinUrlParts(deployment.servingUrl, route)}</loc>
<lastmod>${lastModifiedTimestamp}</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
`;
}).join("")}
</url>`).join("")}
</urlset>`;
writeFileSync(join4(distDir, "browser", "sitemap.xml"), sitemap, "utf-8");
console.log(`Generated sitemap with ${Object.keys(routes.routes).length} entries.`);
await writeFile3(join4(distDir, "browser", "sitemap.xml"), sitemap, "utf-8");
console.log(`Generated sitemap with ${routePaths.length} entries.`);
}
function joinUrlParts(...parts) {
const normalizeParts = [];
for (const part of parts) {
if (part === "") {
continue;
}
let normalizedPart = part;
if (part[0] === "/") {
normalizedPart = normalizedPart.slice(1);
}
if (part.at(-1) === "/") {
normalizedPart = normalizedPart.slice(0, -1);
}
if (normalizedPart !== "") {
normalizeParts.push(normalizedPart);
}
}
return normalizeParts.join("/");
}
// .github/actions/deploy-docs-site/lib/main.mts
@@ -36041,7 +36136,7 @@ tmp/lib/tmp.js:
* MIT Licensed
*)
@angular/ng-dev/bundles/chunk-5VPDBAHJ.mjs:
@angular/ng-dev/bundles/chunk-BPT5VOWG.mjs:
(*! Bundled license information:
yargs-parser/build/lib/string-utils.js:
@@ -36082,7 +36177,7 @@ tmp/lib/tmp.js:
*)
*)
@angular/ng-dev/bundles/chunk-H4EYXD2W.mjs:
@angular/ng-dev/bundles/chunk-M7CFQJ2K.mjs:
(*! Bundled license information:
@octokit/request-error/dist-src/index.js:
+2 -2
View File
@@ -5,9 +5,9 @@ runs:
using: 'composite'
steps:
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/saucelabs@469708f109a90884ca403d150d33079a3a5a8769
- name: Starting Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh run &
+4 -4
View File
@@ -21,17 +21,17 @@ 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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- 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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@469708f109a90884ca403d150d33079a3a5a8769
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: '${{secrets.GITHUB_TOKEN}}'
@@ -40,7 +40,7 @@ jobs:
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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@469708f109a90884ca403d150d33079a3a5a8769
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: angular/dev-infra/github-actions/branch-manager@469708f109a90884ca403d150d33079a3a5a8769
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+2 -2
View File
@@ -28,7 +28,7 @@ jobs:
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
id: comment-branch
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.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}}
@@ -38,7 +38,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
bazelrc: ./.bazelrc.user
+27 -27
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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -39,13 +39,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -55,7 +55,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@2ad32e649e4db26c07674ebae31a297601dbcbaf # v6.10.8
uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -67,11 +67,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -83,11 +83,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -100,11 +100,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -119,11 +119,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -136,11 +136,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -152,11 +152,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -206,11 +206,11 @@ jobs:
runs-on: ubuntu-latest-8core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
@@ -224,6 +224,6 @@ jobs:
configPath: 'adev/firebase.json'
distDir: 'dist/bin/adev/dist'
- name: Update Algolia synonym record
run: pnpm tsx adev/scripts/synonyms/update-synonyms.mts
run: node adev/scripts/synonyms/update-synonyms.mts
env:
ALGOLIA_KEY: ${{ secrets.ALGOLIA_SYNONYM_MANAGER }}
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.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@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
push-to-fork: 'angular-robot/angular'
+4 -4
View File
@@ -12,14 +12,14 @@ jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: angular/dev-infra/github-actions/pull-request-labeling@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/pull-request-labeling@469708f109a90884ca403d150d33079a3a5a8769
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: angular/dev-infra/github-actions/post-approval-changes@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/post-approval-changes@469708f109a90884ca403d150d33079a3a5a8769
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+2 -2
View File
@@ -13,8 +13,8 @@ jobs:
statuses: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: angular/dev-infra/github-actions/google-internal-tests@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: angular/dev-infra/github-actions/google-internal-tests@469708f109a90884ca403d150d33079a3a5a8769
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+4 -4
View File
@@ -13,15 +13,15 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/saucelabs@469708f109a90884ca403d150d33079a3a5a8769
- name: Set up Sauce Tunnel Daemon
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
- uses: angular/dev-infra/github-actions/unified-status-check@469708f109a90884ca403d150d33079a3a5a8769
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+3 -3
View File
@@ -21,7 +21,7 @@ jobs:
workflows: ${{ steps.workflows.outputs.workflows }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- id: workflows
@@ -36,9 +36,9 @@ jobs:
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- 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
+21 -21
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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -37,7 +37,7 @@ jobs:
- 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@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/linting/licenses@469708f109a90884ca403d150d33079a3a5a8769
with:
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
@@ -45,13 +45,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run unit tests
@@ -59,7 +59,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@2ad32e649e4db26c07674ebae31a297601dbcbaf # v6.10.8
uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -71,11 +71,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run CI tests for framework
@@ -95,11 +95,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run integration CI tests for framework
@@ -110,11 +110,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -127,11 +127,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -142,11 +142,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/setup@469708f109a90884ca403d150d33079a3a5a8769
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
uses: angular/dev-infra/github-actions/bazel/configure-remote@469708f109a90884ca403d150d33079a3a5a8769
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: |
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
steps:
- name: 'Checkout code'
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -47,6 +47,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
sarif_file: results.sarif
+3
View File
@@ -54,3 +54,6 @@ baseline.json
# Ignore cache created with the Angular CLI.
.angular/
# pnpm store
.pnpm-store
+1
View File
@@ -9,4 +9,5 @@ export const github = {
name: 'angular',
mainBranchName: 'main',
mergeMode: 'caretaker-only',
requireReleaseModeForRelease: true,
};
+1
View File
@@ -16,6 +16,7 @@
"packages/docs/**",
"packages/elements/schematics/**",
"packages/examples/**",
"packages/forms/signals/src/api/rules/validation/standard_schema.ts",
"packages/localize/**",
"packages/private/**",
"packages/service-worker/**",
+1 -1
View File
@@ -1 +1 @@
22.21.1
22.22.0
+1
View File
@@ -340,6 +340,7 @@ groups:
- author not in ["angular-robot"]
reviewers:
users:
- alan-agius4
- devversion
- josephperrott
+17
View File
@@ -0,0 +1,17 @@
This is the source code for the Angular framework. This guide outlines standard practices for AI agents working in this repository.
## Environment
- Use `pnpm` for package management.
- Use `pnpm bazel test //target` to run tests. Do not use `ng test`, or just `bazel`
## Key Documentation
- [Building and Testing](contributing-docs/building-and-testing-angular.md): definitive guide for running targets.
- [Coding Standards](contributing-docs/coding-standards.md): style guide for TypeScript and other files.
- [Commit Guidelines](contributing-docs/commit-message-guidelines.md): format for commit messages and PR titles.
## Pull Requests
- Use the `gh` CLI (GitHub CLI) for creating and managing pull requests.
- Avoid using browser tools for PR operations when possible to maintain workflow efficiency.
+132 -173
View File
@@ -1,3 +1,135 @@
<a name="21.1.4"></a>
# 21.1.4 (2026-02-11)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [caab23dfe6](https://github.com/angular/angular/commit/caab23dfe6acf06c3b859af091f5e078b08f1c4c) | fix | add geolocation element to schema |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2b99eaa019](https://github.com/angular/angular/commit/2b99eaa019b5551a2e2fcf9ff8cd0a796e1e857b) | fix | capture animation dependencies eagerly to avoid destroyed injector |
| [d6aeac504c](https://github.com/angular/angular/commit/d6aeac504c6181f15e5d8afdca3d9c3e3b32652c) | fix | Fix flakey test due to document injection |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [0d1acd0165](https://github.com/angular/angular/commit/0d1acd0165511b57ce853f29486d9b92d0215959) | feat | support signal-based schemas in validateStandardSchema |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [3905015ccc](https://github.com/angular/angular/commit/3905015ccc53399a606dd8e4f3c4d0cce628a08e) | fix | correctly parse ArrayBuffer and Blob in transfer cache |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.3"></a>
# 21.1.3 (2026-02-04)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2b254bc050](https://github.com/angular/angular/commit/2b254bc0508b73aab8991c3b1a9a703c339cb735) | fix | `linkedSignal.update` should propagate errors |
| [e5110b4fa1](https://github.com/angular/angular/commit/e5110b4fa155e4669ed507f3460d2d29026a28ab) | fix | export DirectiveWithBindings |
| [2cf4da0ea1](https://github.com/angular/angular/commit/2cf4da0ea11f5746eb7ae4dfd775f757576e4d98) | fix | hold constructors weakly in DepsTracker cache |
| [70a5b651be](https://github.com/angular/angular/commit/70a5b651be29f1421eb25150b560bfe154aad6bc) | fix | prevent element duplication with dynamic components |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [6f75b6e3f6](https://github.com/angular/angular/commit/6f75b6e3f60dc2a4f33e13562649931dc95eb52b) | fix | Resolves debounce promise on abort in debounceForDuration |
### localize
| Commit | Type | Description |
| -- | -- | -- |
| [4c7126d23b](https://github.com/angular/angular/commit/4c7126d23be3e43b1d5bd6f2fb13119d185c3682) | fix | add support for unit-test builder in ng-add schematic |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [d6268c0bbb](https://github.com/angular/angular/commit/d6268c0bbbdc92abaaaeb8eebee3bc45decab9c9) | fix | limit UrlParser recursion depth to prevent stack overflow |
| [49a36f4cc7](https://github.com/angular/angular/commit/49a36f4cc7254420bc34fff4e0f0242e00970280) | perf | Use .bind to avoid holding other closures in memory |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.2"></a>
# 21.1.2 (2026-01-28)
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [9f99b14882](https://github.com/angular/angular/commit/9f99b14882bc4f883aa33295856010a8bca900fa) | fix | only touch visible, interactive fields on submit |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [c57b0355b5](https://github.com/angular/angular/commit/c57b0355b51f5aee5abd822f203fc3bcc3e85acd) | fix | Detect local project version on creation |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [21ecdc036a](https://github.com/angular/angular/commit/21ecdc036a46c487d6c5b6bd25c2bbc3e53a60f9) | fix | Do not intercept reload events with Navigation integration |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.1"></a>
# 21.1.1 (2026-01-21)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [0e1f1ed573](https://github.com/angular/angular/commit/0e1f1ed5732f3bb4d5dfbd1f0ee5a5be840594e4) | fix | drop .tsx extension for generated relative imports |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [05adfcf8f2](https://github.com/angular/angular/commit/05adfcf8f26013ac20c38f2b08847b5142e4fd85) | fix | handle Set in class bindings |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [d89a80a970](https://github.com/angular/angular/commit/d89a80a970d9590df0509d8b94090904a99b7aca) | feat | Ability to manually register a form field binding in signal forms |
| [cb75f9ce85](https://github.com/angular/angular/commit/cb75f9ce85160b2e4359610c06294929ac1169c0) | fix | fix control value syncing on touch |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0"></a>
# 21.1.0 (2026-01-14)
## Deprecations
### upgrade
- `VERSION` from `@angular/upgrade` is deprecated. Please use the entry from `@angular/upgrade/static` instead.
### common
| Commit | Type | Description |
| -- | -- | -- |
| [d8790972be](https://github.com/angular/angular/commit/d8790972bea4c59a208219dd36d158b5d7e4fdde) | feat | Add custom transformations for Cloudflare and Cloudinary image loaders |
| [a6b8cb68af](https://github.com/angular/angular/commit/a6b8cb68afaded6999ee68f495512be1a9932ae4) | feat | support custom transformations in ImageKit and Imgix loaders |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [640693da8e](https://github.com/angular/angular/commit/640693da8e667c015662246152236585d9b24e7f) | feat | Add support for multiple swich cases matching |
| [0ad3adc7c6](https://github.com/angular/angular/commit/0ad3adc7c6d4094f1e3432a3f2e3bdc9862cb4fa) | fix | Support empty cases |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [99ad18a4ee](https://github.com/angular/angular/commit/99ad18a4ee82ecc5524106d1d403ccfa9bae2304) | feat | Add stability debugging utility |
| [a0dfa5fa86](https://github.com/angular/angular/commit/a0dfa5fa86f40520b0e368a021b3c72009a45e8e) | feat | support rest arguments in function calls |
| [6e18fa8bc9](https://github.com/angular/angular/commit/6e18fa8bc9d7e6801e2e89e635c2f759dc422317) | feat | support spread elements in array literals |
| [e407280ab5](https://github.com/angular/angular/commit/e407280ab53cde5f93c3a643457c848845c6ec8b) | feat | support spread expressions in object literals |
| [06be8034bb](https://github.com/angular/angular/commit/06be8034bb9b9adfc07ab0d40cd87c6ae5de02de) | fix | Microtask scheduling should be used after any application synchronization |
| [b4f584cf42](https://github.com/angular/angular/commit/b4f584cf42235c94bb8389fa55bc634e23d7b010) | fix | return `StaticProvider` for `providePlatformInitializer` |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [1ea5c97703](https://github.com/angular/angular/commit/1ea5c97703ad3c6d8e4cb1b4297eec57629ce117) | feat | allow focusing bound control from field state |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [ec9dc94cee](https://github.com/angular/angular/commit/ec9dc94ceeb3c026c64e01c6889b7f5c6fd25a66) | feat | add `context` to `createApplication` |
| [ab67988d2e](https://github.com/angular/angular/commit/ab67988d2e5242eff0034483f984428d684acd02) | feat | resolve JIT resources in `createApplication` |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [5edceffd04](https://github.com/angular/angular/commit/5edceffd0431f5a25e111a731db521e966b91f86) | feat | add controls for route cleanup |
| [a03c82564d](https://github.com/angular/angular/commit/a03c82564da6824e199ff48d5249ea8708040951) | feat | Add scroll behavior controls on router navigation |
| [e44839b016](https://github.com/angular/angular/commit/e44839b01640505e554fff16f24e08f282a557c0) | feat | Add standalone function to create a comptued for isActive |
| [c25d749d85](https://github.com/angular/angular/commit/c25d749d85374fff7745980cd9bb2673c661105a) | feat | Execute RunGuardsAndResolvers function in injection context |
| [1c00ab42f8](https://github.com/angular/angular/commit/1c00ab42f8714f2775ed75bbf3cdf0fd44ee32c3) | feat | extend paramters of RedirectFunction to include paramMap and queryParamMap |
| [7003e8d241](https://github.com/angular/angular/commit/7003e8d2417660f71b3a2a017aff3e650c8d9646) | feat | Publish Router's integration with platform Navigation API as experimental |
| [c84d372778](https://github.com/angular/angular/commit/c84d37277874cf7cbd7582a295d796ff113b9cc1) | feat | Support wildcard params with segments trailing ([#64737](https://github.com/angular/angular/pull/64737)) |
### upgrade
| Commit | Type | Description |
| -- | -- | -- |
| [75fe8f8af9](https://github.com/angular/angular/commit/75fe8f8af9488bae6f7068b64d44500643c5d63f) | refactor | deprecate `VERSION` export |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.8"></a>
# 21.0.8 (2026-01-08)
### core
@@ -65,53 +197,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0-next.4"></a>
# 21.1.0-next.4 (2025-12-17)
## Breaking Changes
### forms
- The shape of `SignalFormsConfig.classes` has changed
Previously each function in the `classes` map took a `FieldState`. Now
it takes a `Field` directive.
For example if you previously had:
```
provideSignalFormsConfig({
classes: {
'my-valid': (state) => state.valid()
}
})
```
You would need to update to:
```
provideSignalFormsConfig({
classes: {
'my-valid': ({state}) => state().valid()
}
})
```
-
### core
| Commit | Type | Description |
| -- | -- | -- |
| [06be8034bb](https://github.com/angular/angular/commit/06be8034bb9b9adfc07ab0d40cd87c6ae5de02de) | fix | Microtask scheduling should be used after any application synchronization |
| [b4f584cf42](https://github.com/angular/angular/commit/b4f584cf42235c94bb8389fa55bc634e23d7b010) | fix | return `StaticProvider` for `providePlatformInitializer` |
| [7be4ddef1c](https://github.com/angular/angular/commit/7be4ddef1ccb9a6b330b52b9bbdd174089755503) | fix | throw better errors for potential circular references |
| [f516370c8e](https://github.com/angular/angular/commit/f516370c8e8e96806bad757d43c857ab5f2051ed) | fix | use mutable ResponseInit type for RESPONSE_INIT token |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [348f149e8b](https://github.com/angular/angular/commit/348f149e8b06d6885f54bac4cf03a9481a8b19b7) | feat | pass field directive to class config |
| [ae0c59028a](https://github.com/angular/angular/commit/ae0c59028a2f393ea5716bf222db2c38e7a3989f) | refactor | rename field to fieldTree in FieldContext and ValidationError |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [9f5744a92d](https://github.com/angular/angular/commit/9f5744a92dda4219e93bf7e6236d32a93bc167e6) | fix | avoid interpolation highlighting inside @let |
| [ce1a4769f9](https://github.com/angular/angular/commit/ce1a4769f987e96e2a1b1cbc7654ed5bac9ce91d) | fix | Prevent language service from crashing on suggestion diagnostic errors |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.6"></a>
# 21.0.6 (2025-12-17)
## Breaking Changes (affecting only experimental features)
@@ -159,24 +244,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0-next.3"></a>
# 21.1.0-next.3 (2025-12-11)
### core
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------ |
| [4f6014a756a](https://github.com/angular/angular/commit/4f6014a756a95ff9cb55e91deb27039b7eb079ee) | fix | avoid false-positive deprecation when using `InjectionToken` with factory only |
### forms
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | ---- | --------------------------- |
| [d0097f7d0c2](https://github.com/angular/angular/commit/d0097f7d0c214c5c3d715e7f1dfa3723b31f4065) | fix | fix signal forms type error |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.5"></a>
# 21.0.5 (2025-12-11)
@@ -195,47 +262,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0-next.2"></a>
# 21.1.0-next.2 (2025-12-10)
## Deprecations
### upgrade
- `VERSION` from `@angular/upgrade` is deprecated. Please use the entry from `@angular/upgrade/static` instead.
### compiler
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | ---- | --------------------------------- |
| [ae1c0dc4900](https://github.com/angular/angular/commit/ae1c0dc49002665c10d4e44f530f4cb8d1e35b8d) | perf | chain query creation instructions |
### compiler-cli
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- |
| [8a3f3a91cf6](https://github.com/angular/angular/commit/8a3f3a91cf6919f11c1583afeb71b65488f8cba4) | fix | expand type for native controls with a dynamic type |
### forms
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------- |
| [aff8b248b35](https://github.com/angular/angular/commit/aff8b248b35931cbe7d644ad2594144b75064ca6) | feat | expose element on signal forms `Field` directive |
| [ebc5c2b083a](https://github.com/angular/angular/commit/ebc5c2b083a90fde6fced3521be434a414ef29dd) | feat | redo the signal forms metadata API |
| [9fe95665813](https://github.com/angular/angular/commit/9fe95665813e24544d00dcf463658067da745ac2) | fix | add signals for dirty, hidden, and pending states in custom controls |
| [14713d09923](https://github.com/angular/angular/commit/14713d09923b5315ac7c6f20acc06fc69ff0ed79) | fix | allow resetting with empty string |
| [b96f65a963f](https://github.com/angular/angular/commit/b96f65a963febab376f2a783cf4313a8b8140efe) | fix | memoize reads of child fields in signal forms ([#65802](https://github.com/angular/angular/pull/65802)) |
| [179b4cba67b](https://github.com/angular/angular/commit/179b4cba67b95563da27fd5f12dd2acc910a1a52) | fix | Reuse key in parent in compat structure |
### upgrade
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------- | -------- | -------------------------- |
| [75fe8f8af94](https://github.com/angular/angular/commit/75fe8f8af9488bae6f7068b64d44500643c5d63f) | refactor | deprecate `VERSION` export |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.4"></a>
# 21.0.4 (2025-12-10)
@@ -265,52 +291,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0-next.1"></a>
# 21.1.0-next.1 (2025-12-03)
### compiler
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | ----------------------------------------------------------------- |
| [1c6b0704fb](https://github.com/angular/angular/commit/1c6b0704fb63d051fab8acff84d076abfbc4893a) | fix | prevent XSS via SVG animation `attributeName` and MathML/SVG URLs |
### compiler-cli
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | --------------------------------------------------------- |
| [e30e61b789](https://github.com/angular/angular/commit/e30e61b7893a9b04ada54ab184c24e652dcd114e) | fix | avoid allocating an object for signals in production mode |
| [6773d3b97d](https://github.com/angular/angular/commit/6773d3b97d07dcbf7760c0c12d9384fa60f7082e) | fix | check that field radio button values are strings |
### core
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------ |
| [d8ab83ca82](https://github.com/angular/angular/commit/d8ab83ca82c36faacae9fc1a01e9eb9762e679fc) | fix | run animation queue in environment injector context |
| [886cf6c452](https://github.com/angular/angular/commit/886cf6c452abcf85b70058caeb080ff3a5c51cb9) | fix | unable to inject viewProviders when host directive with providers is present |
| [e6d5632a30](https://github.com/angular/angular/commit/e6d5632a3059a4920f19eb6b193ec7edb11645d4) | perf | tree shake unused dynamic `[field]` binding instructions ([#65599](https://github.com/angular/angular/pull/65599)) |
### forms
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | -------------------------------------------------------------------------------------------- |
| [7d1e502345](https://github.com/angular/angular/commit/7d1e50234515185ee73c8bdd39f7af2a26635ca2) | feat | Allows transforms on `FormUiControl` signals |
| [cd7ae7e2ce](https://github.com/angular/angular/commit/cd7ae7e2ce4648edf62c047b3e453c8b32539d4a) | fix | support dynamic `[field]` bindings ([#65599](https://github.com/angular/angular/pull/65599)) |
### http
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | ---------------------------------------------------- |
| [0659d11c85](https://github.com/angular/angular/commit/0659d11c854c9c6b886a2e925afef98d70bb005f) | fix | enable XSRF protection for same-origin absolute URLs |
### router
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | --------------------------------------------------- |
| [b74a0693f2](https://github.com/angular/angular/commit/b74a0693f2409812ad1ab02f19eb3fbe7ce1b83e) | fix | handle errors from view transition finished promise |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.3"></a>
# 21.0.3 (2025-12-03)
@@ -399,27 +379,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.1.0-next.0"></a>
# 21.1.0-next.0 (2025-11-25)
### platform-browser
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | -------------------------------------------- |
| [ec9dc94cee](https://github.com/angular/angular/commit/ec9dc94ceeb3c026c64e01c6889b7f5c6fd25a66) | feat | add `context` to `createApplication` |
| [ab67988d2e](https://github.com/angular/angular/commit/ab67988d2e5242eff0034483f984428d684acd02) | feat | resolve JIT resources in `createApplication` |
### router
| Commit | Type | Description |
| ------------------------------------------------------------------------------------------------ | ---- | -------------------------------------------------------------------------------------------------------- |
| [a03c82564d](https://github.com/angular/angular/commit/a03c82564da6824e199ff48d5249ea8708040951) | feat | Add scroll behavior controls on router navigation |
| [c25d749d85](https://github.com/angular/angular/commit/c25d749d85374fff7745980cd9bb2673c661105a) | feat | Execute RunGuardsAndResolvers function in injection context |
| [c84d372778](https://github.com/angular/angular/commit/c84d37277874cf7cbd7582a295d796ff113b9cc1) | feat | Support wildcard params with segments trailing ([#64737](https://github.com/angular/angular/pull/64737)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.14"></a>
# 20.3.14 (2025-11-25)
+22 -11
View File
@@ -5,27 +5,27 @@ module(
)
bazel_dep(name = "rules_pkg", version = "1.2.0")
bazel_dep(name = "rules_nodejs", version = "6.6.2")
bazel_dep(name = "aspect_rules_ts", version = "3.8.1")
bazel_dep(name = "aspect_rules_js", version = "2.8.3")
bazel_dep(name = "rules_nodejs", version = "6.7.3")
bazel_dep(name = "aspect_rules_ts", version = "3.8.3")
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.2")
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "aspect_bazel_lib", version = "2.22.2")
bazel_dep(name = "tar.bzl", version = "0.7.0")
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
bazel_dep(name = "tar.bzl", version = "0.8.1")
bazel_dep(name = "yq.bzl", version = "0.3.4")
bazel_dep(name = "rules_angular")
git_override(
module_name = "rules_angular",
commit = "7133b97252508f8528e5c5818a9a73cacc2e2a0e",
commit = "e975faa54a52caa45ff932ec806292ce0988d8d4",
remote = "https://github.com/devversion/rules_angular.git",
)
bazel_dep(name = "devinfra")
git_override(
module_name = "devinfra",
commit = "2ff164a0ca60e692f3d7e1a982bde9bf3379365e",
commit = "469708f109a90884ca403d150d33079a3a5a8769",
remote = "https://github.com/angular/dev-infra.git",
)
@@ -39,7 +39,7 @@ git_override(
bazel_dep(name = "rules_browsers")
git_override(
module_name = "rules_browsers",
commit = "5f0e50d17d15c70e6ab5546f1659338ae94c4072",
commit = "e08ae33c679d07b3b2fcc136658b787a81995bc5",
remote = "https://github.com/devversion/rules_browsers.git",
)
@@ -47,7 +47,18 @@ yq = use_extension("@yq.bzl//yq:extensions.bzl", "yq")
use_repo(yq, "yq_toolchains")
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
node.toolchain(node_version_from_nvmrc = "//:.nvmrc")
node.toolchain(
node_repositories = {
"22.22.0-darwin_arm64": ("node-v22.22.0-darwin-arm64.tar.gz", "node-v22.22.0-darwin-arm64", "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"),
"22.22.0-darwin_amd64": ("node-v22.22.0-darwin-x64.tar.gz", "node-v22.22.0-darwin-x64", "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"),
"22.22.0-linux_arm64": ("node-v22.22.0-linux-arm64.tar.xz", "node-v22.22.0-linux-arm64", "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"),
"22.22.0-linux_ppc64le": ("node-v22.22.0-linux-ppc64le.tar.xz", "node-v22.22.0-linux-ppc64le", "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"),
"22.22.0-linux_s390x": ("node-v22.22.0-linux-s390x.tar.xz", "node-v22.22.0-linux-s390x", "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"),
"22.22.0-linux_amd64": ("node-v22.22.0-linux-x64.tar.xz", "node-v22.22.0-linux-x64", "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"),
"22.22.0-windows_amd64": ("node-v22.22.0-win-x64.zip", "node-v22.22.0-win-x64", "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"),
},
node_version = "22.22.0",
)
use_repo(node, "nodejs_toolchains")
use_repo(node, "nodejs_darwin_amd64")
use_repo(node, "nodejs_darwin_arm64")
@@ -60,7 +71,8 @@ use_repo(node, "nodejs_windows_amd64")
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
pnpm.pnpm(
name = "pnpm",
pnpm_version = "10.16.1",
pnpm_version = "10.29.1",
pnpm_version_integrity = "sha512-SNriM2NaZFdoowKNGVRcrMFohjnusfNzTkLW1rlxr78iqhrJr1Khc9nDogwVhXz6QA8ZmU15ovYm/Mc/zNqbvA==",
)
use_repo(pnpm, "pnpm")
@@ -71,7 +83,6 @@ npm.npm_translate_lock(
"//:.pnpmfile.cjs",
"//:package.json",
"//:pnpm-workspace.yaml",
"//:tools/pnpm-patches/dagre-d3-es+7.0.11.patch",
"//adev:package.json",
"//adev/shared-docs:package.json",
"//adev/shared-docs/pipeline/api-gen:package.json",
+335 -51
View File
@@ -13,10 +13,10 @@
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.17.1/MODULE.bazel": "9b027af55f619c7c444cead71061578fab6587e5e1303fa4ed61d49d2b1a7262",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.2/MODULE.bazel": "30dfabbfae0139b1f0036e01c201dd4c0167da3017f0b7ef3820d78e07622989",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.2/MODULE.bazel": "6b735f3fdd64978e217c9725f4ff0d84bf606554c8e77d20e90977841d7ff2ed",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.2/source.json": "58fffa2d722cff47cb8d921c8bbed7701c53f233009d9ca82beb4a0fb8fb9418",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14",
@@ -27,14 +27,14 @@
"https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5",
"https://bcr.bazel.build/modules/aspect_rules_js/2.4.2/MODULE.bazel": "0d01db38b96d25df7ed952a5e96eac4b3802723d146961974bf020f6dd07591d",
"https://bcr.bazel.build/modules/aspect_rules_js/2.6.2/MODULE.bazel": "ed2a871f4ab8fbde0cab67c425745069d84ea64b64313fa1a2954017326511f5",
"https://bcr.bazel.build/modules/aspect_rules_js/2.8.3/MODULE.bazel": "807ce5f624124a8bc586c743394d174e85f0f9c6c4e0e2410b4088aebe790ac8",
"https://bcr.bazel.build/modules/aspect_rules_js/2.8.3/source.json": "c35cb4e04f61a09c17f8c569894b80de884b1e3dee2d33829704786e3f778037",
"https://bcr.bazel.build/modules/aspect_rules_js/2.9.2/MODULE.bazel": "93fd5b85e6e912fb0712cbab453c43271d4ea33a093f84fd587638fbc9f8c145",
"https://bcr.bazel.build/modules/aspect_rules_js/2.9.2/source.json": "4bff7c03ab387b60deb15649ba575688e62f2a71a7544cbc7a660b19ec473808",
"https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/MODULE.bazel": "296e3a053658c2af989ba9bd62a205e6d1fa84bdd6dd5249196546e6b84770ec",
"https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/source.json": "2fe8ac1ccb4de74bf884761e070010280b272d94e3997205b361b91c75409726",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.6.3/MODULE.bazel": "d09db394970f076176ce7bab5b5fa7f0d560fd4f30b8432ea5e2c2570505b130",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.7.0/MODULE.bazel": "5aace216caf88638950ef061245d23c36f57c8359e56e97f02a36f70bb09c50f",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/MODULE.bazel": "796622c65ae3008374fc2d77c32ddb4ef6da9fe891826ce648f70033a48b3667",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/source.json": "a7c4f332f5c21f4e63d073f8dda40bf278d5307499fb307b35058dba558f417a",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.3/MODULE.bazel": "a26c28ebcd0c0d50ab0708ac21fa48bd2dced3a4dad4c31a2fa48588b42ad762",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.3/source.json": "d17304791281168c42c5532b4b9e01dfb4bdb42d7bf784597b75f401211efc63",
"https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d",
"https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.6/MODULE.bazel": "cafb8781ad591bc57cc765dca5fefab08cf9f65af363d162b79d49205c7f8af7",
"https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679",
@@ -47,9 +47,11 @@
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e",
"https://bcr.bazel.build/modules/bazel_features/1.34.0/source.json": "dfa5c4b01110313153b484a735764d247fee5624bbab63d25289e43b151a657a",
"https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d",
"https://bcr.bazel.build/modules/bazel_features/1.39.0/source.json": "f63cbeb4c602098484d57001e5a07d31cb02bbccde9b5e2c9bf0b29d05283e93",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
@@ -122,7 +124,8 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
"https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4",
"https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
@@ -157,10 +160,11 @@
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
"https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e",
"https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d",
"https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.0/MODULE.bazel": "546d0cf79f36f9f6e080816045f97234b071c205f4542e3351bd4424282a8810",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d",
"https://bcr.bazel.build/modules/rules_nodejs/6.6.2/MODULE.bazel": "9fdb5e1d50246a25761f150fcc820dc47e4052330a8408451e628804f9ca64a6",
"https://bcr.bazel.build/modules/rules_nodejs/6.6.2/source.json": "6e8c1ecc64ff8da147c1620f862ad77d7b19c5d1b52b3aa5e847d5b3d0de4cc3",
"https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056",
"https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e",
@@ -194,8 +198,8 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468",
"https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351",
"https://bcr.bazel.build/modules/tar.bzl/0.7.0/MODULE.bazel": "cc1acd85da33c80e430b65219a620d54d114628df24a618c3a5fa0b65e988da9",
"https://bcr.bazel.build/modules/tar.bzl/0.7.0/source.json": "9becb80306f42d4810bfa16379fb48aad0b01ce5342bc12fe47dcd6af3ac4d7a",
"https://bcr.bazel.build/modules/tar.bzl/0.8.1/MODULE.bazel": "6ffe8907ed4c555bc94bd35a5a01411cc4470c6dace84f9cf487815409e077d1",
"https://bcr.bazel.build/modules/tar.bzl/0.8.1/source.json": "835f83b482facf6205ad8708cf2b2f6524d1d7b1075a90fe9bb540da761d6d2e",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072",
"https://bcr.bazel.build/modules/yq.bzl/0.2.0/MODULE.bazel": "6f3a675677db8885be4d607fde14cc51829715e3a879fb016eb9bf336786ce6d",
@@ -211,7 +215,7 @@
"moduleExtensions": {
"@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": {
"general": {
"bzlTransitiveDigest": "9odiC0alKLq5PUYv/CZiw2yiMHsGpxvEhPsqt//fRRk=",
"bzlTransitiveDigest": "c4i5gawrp4Au9UMb55EzQCePYwkrFqD9tFBN7GdHG5g=",
"usagesDigest": "ToTaCONCN/E05krnHXLM1kpV1zrHNxHrGpUip973II4=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@@ -412,8 +416,8 @@
},
"@@aspect_rules_js+//npm:extensions.bzl%pnpm": {
"general": {
"bzlTransitiveDigest": "zVV86HvMwDBJ8IsFt27k/Sjq0vCMPr8b8X9OAuprQ6w=",
"usagesDigest": "3ECQVM1bdy/C4w7vxglaG1gt0S98v0T14gP5wNiDgwY=",
"bzlTransitiveDigest": "HC+l+mTivq1p/KbcVQ+iV5QwYR+oKESJh827FY68SH8=",
"usagesDigest": "FyHJzX6Dg8T9aL1mxFMOq56ORkGs6jVdrjufJhhuHUs=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -422,11 +426,11 @@
"repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule",
"attributes": {
"package": "pnpm",
"version": "10.16.1",
"version": "10.29.1",
"root_package": "",
"link_workspace": "",
"link_packages": {},
"integrity": "sha512-DhVaomKduGcrSehHXaYiaqS96oX9zf3BU1CHSUbU88kfqvZMvcSl0auAAvRz1cP87c0ZeYnPA5D5ut08BGeHBg==",
"integrity": "sha512-SNriM2NaZFdoowKNGVRcrMFohjnusfNzTkLW1rlxr78iqhrJr1Khc9nDogwVhXz6QA8ZmU15ovYm/Mc/zNqbvA==",
"url": "",
"commit": "",
"patch_args": [
@@ -449,7 +453,7 @@
"repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links",
"attributes": {
"package": "pnpm",
"version": "10.16.1",
"version": "10.29.1",
"dev": false,
"root_package": "",
"link_packages": {},
@@ -566,7 +570,7 @@
},
"@@aspect_rules_ts+//ts:extensions.bzl%ext": {
"general": {
"bzlTransitiveDigest": "7k3bewVApw4Kc6Rpho1Rrs1nrW/5jphUA5Mh1iHE2U4=",
"bzlTransitiveDigest": "gyALvLdd/1AuLPFXhzNeabk2DMLHf32oxn8qKfEK8UA=",
"usagesDigest": "BIPOUwqzuuYqLOw/0p3h5GwHIDeBVXvNcxNoG5YlsHQ=",
"recordedFileInputs": {
"@@rules_browsers+//package.json": "84dc1ba9b1c667a25894e97218bd8f247d54f24bb694efb397a881be3c06a4c5"
@@ -633,7 +637,7 @@
"@@aspect_tools_telemetry+//:extension.bzl%telemetry": {
"general": {
"bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=",
"usagesDigest": "c8kIEi+rX5/1OpfMZW0mHrM7sAvw+nu7pOep2xSjFb4=",
"usagesDigest": "m4Fa2faQCHxeGwDkOwASSCEydzPyBW44x/rz9KgbgJQ=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -642,8 +646,8 @@
"repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository",
"attributes": {
"deps": {
"aspect_rules_ts": "3.8.1",
"aspect_rules_js": "2.8.3",
"aspect_rules_ts": "3.8.3",
"aspect_rules_js": "2.9.2",
"aspect_rules_esbuild": "0.25.0",
"aspect_rules_jasmine": "2.0.2",
"aspect_tools_telemetry": "0.3.3"
@@ -1086,8 +1090,8 @@
},
"@@rules_nodejs+//nodejs:extensions.bzl%node": {
"general": {
"bzlTransitiveDigest": "NwcLXHrbh2hoorA/Ybmcpjxsn/6avQmewDglodkDrgo=",
"usagesDigest": "QbHTNODxV2P0w/Zs/oELuqv6ovXgi81f6SUYmX7dNhc=",
"bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=",
"usagesDigest": "yiylduJft13ULV5TUSGMYtVEKVnH/fJkG3SYNNrzFsQ=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -1096,12 +1100,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "linux_amd64"
}
@@ -1110,12 +1149,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "linux_arm64"
}
@@ -1124,12 +1198,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "linux_s390x"
}
@@ -1138,12 +1247,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "linux_ppc64le"
}
@@ -1152,12 +1296,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "darwin_amd64"
}
@@ -1166,12 +1345,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "darwin_arm64"
}
@@ -1180,12 +1394,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "windows_amd64"
}
@@ -1194,12 +1443,47 @@
"repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories",
"attributes": {
"node_download_auth": {},
"node_repositories": {},
"node_repositories": {
"22.22.0-darwin_arm64": [
"node-v22.22.0-darwin-arm64.tar.gz",
"node-v22.22.0-darwin-arm64",
"5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"
],
"22.22.0-darwin_amd64": [
"node-v22.22.0-darwin-x64.tar.gz",
"node-v22.22.0-darwin-x64",
"5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"
],
"22.22.0-linux_arm64": [
"node-v22.22.0-linux-arm64.tar.xz",
"node-v22.22.0-linux-arm64",
"1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"
],
"22.22.0-linux_ppc64le": [
"node-v22.22.0-linux-ppc64le.tar.xz",
"node-v22.22.0-linux-ppc64le",
"d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"
],
"22.22.0-linux_s390x": [
"node-v22.22.0-linux-s390x.tar.xz",
"node-v22.22.0-linux-s390x",
"5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"
],
"22.22.0-linux_amd64": [
"node-v22.22.0-linux-x64.tar.xz",
"node-v22.22.0-linux-x64",
"9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"
],
"22.22.0-windows_amd64": [
"node-v22.22.0-win-x64.zip",
"node-v22.22.0-win-x64",
"c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"
]
},
"node_urls": [
"https://nodejs.org/dist/v{version}/{filename}"
],
"node_version": "20.19.5",
"node_version_from_nvmrc": "@@//:.nvmrc",
"node_version": "22.22.0",
"include_headers": false,
"platform": "windows_arm64"
}
@@ -1228,7 +1512,7 @@
},
"@@rules_python+//python/extensions:pip.bzl%pip": {
"general": {
"bzlTransitiveDigest": "39J6fxZx6VyebAMZs6LDsQSGw91SROMECqQx77bSJqE=",
"bzlTransitiveDigest": "d3ENjFH8qMwmOrkcb3c9JYqQ5hJ6owjfbSr24KY0Ugg=",
"usagesDigest": "/9NP3RV6/DWuNdYAsIU/8UCgCX0TdPUJr0X6O+0lrtk=",
"recordedFileInputs": {
"@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5",
@@ -4023,7 +4307,7 @@
"@@yq.bzl+//yq:extensions.bzl%yq": {
"general": {
"bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=",
"usagesDigest": "fKeGl7m4X6ZwRQS4Up/5Bzx+9laXzZyKEn6s9ODJQTc=",
"usagesDigest": "soFLPyR23RWNxgPKSnibWfWF51i2rx0N0Vs4WyvVNpE=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
+13
View File
@@ -25,6 +25,19 @@ APPLICATION_FILES = [
"//adev/src/assets:context",
"//adev/src/content/examples:embeddable",
"tailwind.config.js",
# For the routes that are generated at build time
"//adev/src/assets:docs_api_manifest",
"//adev/scripts/routes:generate_route",
"//adev/src/app/routing/navigation-entries",
"//adev/src/app/routing/navigation-entries:navigation_types",
"//adev/src/content/reference/errors:route-nav-items",
"//adev/src/content/reference/extended-diagnostics:route-nav-items",
"//adev/src/content/tutorials/deferrable-views",
"//adev/src/content/tutorials/first-app",
"//adev/src/content/tutorials/learn-angular",
"//adev/src/content/tutorials/signal-forms",
"//adev/src/content/tutorials/signals",
] + glob(
["src/**/*"],
exclude = ["src/**/*.spec.ts"],
+11 -11
View File
@@ -5,21 +5,21 @@
"@algolia/requester-browser-xhr": "5.46.2",
"@algolia/requester-node-http": "5.46.2",
"@angular/animations": "workspace:*",
"@angular/aria": "21.1.0-next.3",
"@angular/build": "21.1.0-next.3",
"@angular/cdk": "21.1.0-next.3",
"@angular/cli": "21.1.0-next.3",
"@angular/aria": "21.1.3",
"@angular/build": "21.1.3",
"@angular/cdk": "21.1.3",
"@angular/cli": "21.1.3",
"@angular/common": "workspace:*",
"@angular/compiler": "workspace:*",
"@angular/compiler-cli": "workspace:*",
"@angular/core": "workspace:*",
"@angular/docs": "workspace:*",
"@angular/forms": "workspace:*",
"@angular/material": "21.1.0-next.3",
"@angular/material": "21.1.3",
"@angular/platform-browser": "workspace:*",
"@angular/platform-server": "workspace:*",
"@angular/router": "workspace:*",
"@angular/ssr": "21.1.0-next.3",
"@angular/ssr": "21.1.3",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.1",
"@codemirror/lang-angular": "0.1.4",
@@ -42,25 +42,25 @@
"@marijn/find-cluster-break": "1.0.2",
"@stackblitz/sdk": "1.11.0",
"@types/dom-navigation": "1.0.6",
"@types/jasmine": "5.1.13",
"@types/jasmine": "6.0.0",
"@types/jsdom": "27.0.0",
"@types/node": "24.10.4",
"@typescript/vfs": "1.6.2",
"@webcontainer/api": "1.6.1",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "5.5.0",
"@xterm/xterm": "6.0.0",
"algoliasearch": "5.46.2",
"angular-split": "20.0.0",
"crelt": "1.0.6",
"diff": "8.0.2",
"emoji-regex": "10.6.0",
"fflate": "0.8.2",
"jasmine-core": "5.13.0",
"jasmine-core": "6.0.0",
"jsdom": "27.4.0",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0",
"karma-jasmine-html-reporter": "2.2.0",
"marked": "17.0.1",
"mermaid": "11.12.2",
"ngx-progressbar": "14.0.0",
@@ -68,7 +68,7 @@
"playwright-core": "1.57.0",
"preact": "10.28.2",
"preact-render-to-string": "6.6.5",
"prettier": "3.7.4",
"prettier": "3.8.0",
"rxjs": "7.8.2",
"shiki": "3.21.0",
"style-mod": "4.1.3",
+52
View File
@@ -0,0 +1,52 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("//adev/shared-docs:defaults.bzl", "js_binary", "js_run_binary")
package(default_visibility = ["//adev:__subpackages__"])
ts_project(
name = "generate_routes_lib",
srcs = [
"generate-routes.mts",
],
data = [
"//adev/src/assets:docs_api_manifest",
"//adev/src/content/tutorials/deferrable-views",
"//adev/src/content/tutorials/first-app",
"//adev/src/content/tutorials/learn-angular",
"//adev/src/content/tutorials/signal-forms",
"//adev/src/content/tutorials/signals",
],
tsconfig = {
"compilerOptions": {
"target": "ES2022",
"moduleResolution": "node",
"types": ["node"],
"rootDir": ".",
"skipLibCheck": True,
},
},
deps = [
"//adev:node_modules/@angular/docs",
"//adev:node_modules/@types/node",
"//adev/src/app/routing/navigation-entries",
"//adev/src/content/reference/errors:route-nav-items",
"//adev/src/content/reference/extended-diagnostics:route-nav-items",
],
)
js_binary(
name = "generate_route",
data = [
"generate-routes.mjs",
":generate_routes_lib",
"//adev/src/content:guide_files",
],
entry_point = ":generate-routes.mjs",
)
js_run_binary(
name = "run_generate_route",
outs = ["defined-routes.json"],
chdir = package_name(),
tool = ":generate_route",
)
+82
View File
@@ -0,0 +1,82 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries';
import {NavigationItem} from '@angular/docs';
import {writeFileSync, readFileSync} from 'fs';
import {join, resolve} from 'path';
const outputFile = 'defined-routes.json';
const contentRoot = resolve(process.cwd(), '../../src/content');
/**
* The scripts generates a list of all defined routes in the guides section and stores them
* in a JSON file. This file then used by other bazel targets to know which routes are valid.
*/
function extractRoutes(items: NavigationItem[]): string[] {
const routes: string[] = [];
for (const item of items) {
if (item.path && !item.path.startsWith('http')) {
routes.push(item.path);
if (item.contentPath) {
const content = readFileSync(join(contentRoot, `${item.contentPath}.md`), {
encoding: 'utf-8',
});
const headings = extractHeadings(content);
routes.push(
...headings.map(
(heading) => `${item.path}#${heading.toLowerCase().replace(/\s+/g, '-')}`,
),
);
}
}
if (item.children) {
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[] = [];
allRoutes.push(...extractRoutes(ALL_ITEMS));
const uniqueRoutes = Array.from(new Set(allRoutes.filter((r) => !!r)));
console.warn('Generated routes:', JSON.stringify(uniqueRoutes, null, 2));
writeFileSync(outputFile, JSON.stringify(uniqueRoutes, null, 2));
}
main();
// TODO: refactor so this function is shared with the generation pipeline (adev/shared-docs/pipeline/shared/marked/transformations/heading.mts)
function getIdFromHeading(heading: string): string {
return heading
.toLowerCase()
.replace(/\s|\//g, '-') // replace spaces and slashes with dashes
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
}
+1 -1
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {SynonymHit} from 'algoliasearch';
import {type SynonymHit} from 'algoliasearch';
/**
* List of synonyms to upload to algolia.
+22
View File
@@ -0,0 +1,22 @@
{
"compileOnSave": false,
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "bundler",
"preserveSymlinks": true,
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"allowImportingTsExtensions": true,
"lib": ["ES2022"],
"types": [],
"skipLibCheck": true
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
*/
import {algoliasearch} from 'algoliasearch';
import synonyms from './algolia-synonyms.mjs';
import synonyms from './algolia-synonyms.mts';
/** The name of the index being updated. */
const INDEX_NAME = 'angular_v17';
@@ -8,7 +8,7 @@
import {NgTemplateOutlet} from '@angular/common';
import {ChangeDetectionStrategy, Component, inject, input, output} from '@angular/core';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTooltip} from '@angular/material/tooltip';
import {RouterLink, RouterLinkActive} from '@angular/router';
import {NavigationItem} from '../../interfaces/index';
import {IsActiveNavigationItem} from '../../pipes';
@@ -23,7 +23,7 @@ import {IconComponent} from '../icon/icon.component';
IconComponent,
IsActiveNavigationItem,
NgTemplateOutlet,
MatTooltipModule,
MatTooltip,
],
templateUrl: './navigation-list.component.html',
styleUrls: ['./navigation-list.component.scss'],
@@ -3,7 +3,7 @@
<docs-text-field
[autofocus]="true"
[hideIcon]="true"
[field]="searchForm"
[formField]="searchForm"
[resetLabel]="'Clear the search'"
class="docs-search-input"
placeholder="Search docs"
@@ -7,14 +7,14 @@
*/
import {
afterNextRender,
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
Injector,
afterNextRender,
effect,
ElementRef,
inject,
Injector,
output,
viewChild,
viewChildren,
@@ -26,7 +26,7 @@ import {Search, SearchHistory} from '../../services';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {Field, form} from '@angular/forms/signals';
import {form, FormField} from '@angular/forms/signals';
import {Router, RouterLink} from '@angular/router';
import {fromEvent} from 'rxjs';
import {RelativeLink} from '../../pipes';
@@ -40,7 +40,7 @@ import {TextField} from '../text-field/text-field.component';
imports: [
ClickOutside,
TextField,
Field,
FormField,
SearchItem,
AlgoliaIcon,
RelativeLink,
@@ -54,7 +54,6 @@ export class SearchDialog {
readonly onClose = output();
readonly dialog = viewChild.required<ElementRef<HTMLDialogElement>>('searchDialog');
readonly items = viewChildren(SearchItem);
readonly textField = viewChild(TextField);
readonly history = inject(SearchHistory);
private readonly search = inject(Search);
@@ -98,7 +97,7 @@ export class SearchDialog {
}
// We want to select the pre-existing text on opening
// In order to change the search input with minimal user interaction.
this.textField()?.input().nativeElement.select();
this.searchForm().focusBoundControl();
},
});
@@ -15,6 +15,7 @@ ng_project(
"//adev/shared-docs/components:__pkg__",
],
deps = [
"//adev:node_modules/@angular/aria",
"//adev:node_modules/@angular/common",
"//adev:node_modules/@angular/core",
"//adev:node_modules/@angular/forms",
@@ -1,10 +1,61 @@
<select
[attr.id]="id()"
[attr.name]="name()"
[value]="value()"
(change)="setOption($any($event.target).value)"
>
@for (item of options(); track item) {
<option [value]="item.value" [selected]="value() === item.value">{{ item.label }}</option>
}
</select>
<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..."
readonly
/>
<span class="material-symbols-outlined docs-select-arrow" translate="no" aria-hidden="true"
>arrow_drop_down</span
>
</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>
</dialog>
</ng-template>
</div>
@@ -0,0 +1,186 @@
:host {
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngCombobox] {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
}
.docs-select-input-container {
display: flex;
position: relative;
align-items: center;
border-radius: 0.25rem;
}
[ngComboboxInput] {
border-radius: 0.25rem;
}
[ngComboboxInput][readonly='true'] {
cursor: pointer;
padding: 0.7rem 1rem;
}
[ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) {
outline: 1.5px solid var(--vivid-pink);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent);
}
.docs-select-arrow {
width: 24px;
height: 24px;
font-size: 20px;
display: grid;
place-items: center;
pointer-events: none;
padding: 0 0.5rem;
position: absolute;
right: 0;
opacity: 0.8;
transition: transform 0.2s ease;
}
[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 2.5rem;
background-color: var(--septenary-contrast);
color: var(--primary-contrast);
}
.docs-select-dialog {
position: absolute;
left: auto;
right: auto;
top: auto;
bottom: auto;
padding: 0;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
background-color: var(--septenary-contrast);
color: inherit;
.docs-select-input-container {
border-radius: 0;
}
[ngCombobox],
.docs-select-input-container {
border: none;
}
[ngComboboxInput] {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
[ngCombobox]:focus-within [ngComboboxInput] {
outline: none;
box-shadow: none;
}
.docs-select-search-input:focus {
outline: none;
box-shadow: none;
}
.docs-select-search-container {
border-bottom: 1px solid var(--border-color);
}
&::backdrop {
opacity: 0;
}
}
.docs-select-search-container {
position: relative;
display: flex;
align-items: center;
border-radius: 0;
}
.docs-select-search-icon {
width: 24px;
height: 24px;
font-size: 20px;
display: grid;
place-items: center;
pointer-events: none;
position: absolute;
padding: 0 0.5rem;
opacity: 0.8;
}
.docs-select-search-input {
width: 100%;
border: none;
outline: none;
font-size: 0.875rem;
padding: 0.5rem 0.75rem 0.5rem 2rem;
background-color: var(--septenary-contrast);
color: var(--primary-contrast);
}
.docs-select-no-results {
padding: 0.75rem;
text-align: center;
font-size: 0.875rem;
color: var(--primary-contrast);
opacity: 0.7;
}
.docs-select-listbox {
display: flex;
flex-direction: column;
max-height: 12rem;
overflow: auto;
padding: 0.25rem;
}
.docs-select-option {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.25rem;
&:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
&[data-active='true'] {
outline-offset: -2px;
outline: 2px solid var(--vivid-pink);
}
&[aria-selected='true'] {
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;
}
@@ -6,8 +6,26 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component, input, model} from '@angular/core';
import {
Combobox,
ComboboxDialog,
ComboboxInput,
ComboboxPopupContainer,
} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
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 | number | boolean;
@@ -19,11 +37,17 @@ export interface SelectOption {
@Component({
selector: 'docs-select',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
Combobox,
ComboboxDialog,
ComboboxInput,
ComboboxPopupContainer,
FormsModule,
Listbox,
Option,
],
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
host: {
class: 'docs-form-element',
},
})
export class Select implements FormValueControl<string | null> {
readonly value = model<string | null>(null);
@@ -33,11 +57,66 @@ export class Select implements FormValueControl<string | null> {
readonly options = input.required<SelectOption[]>();
readonly disabled = input(false);
setOption($event: SelectOptionValue): void {
if (this.disabled()) {
readonly dialog = viewChild(ComboboxDialog);
readonly listbox = viewChild<Listbox<SelectOptionValue>>(Listbox);
readonly combobox = viewChild<Combobox<SelectOptionValue>>(Combobox);
readonly searchString = signal('');
readonly filteredOptions = computed(() => {
const search = this.searchString().toLowerCase();
if (!search) {
return this.options();
}
return this.options().filter((option) => option.label.toLowerCase().includes(search));
});
readonly displayValue = computed(() => {
const currentValue = this.value();
if (currentValue === null) {
return '';
}
const option = this.options().find((opt) => opt.value === currentValue);
return option ? option.label : '';
});
readonly selectedValues = signal<SelectOptionValue[]>([]);
constructor() {
afterRenderEffect(() => {
if (this.dialog() && this.combobox()?.expanded()) {
untracked(() => this.listbox()?.gotoFirst());
this.positionDialog();
}
});
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;
}
this.value.set($event as string);
const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
const scrollY = window.scrollY;
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`;
}
}
}
@@ -32,4 +32,32 @@ describe('TabGroup', () => {
expect(tabs[0].textContent.trim()).toBe('tab 1');
expect(tabpanels[0].textContent.trim()).toBe('panel 1');
});
it('should generate unique ids for tabs and tabpanels', async () => {
const testPanel1 = document.createElement('div');
testPanel1.textContent = 'panel 1';
const testPanel2 = document.createElement('div');
testPanel2.textContent = 'panel 2';
fixture.componentRef.setInput('tabs', [
{label: 'tab 1', panel: testPanel1},
{label: 'tab 2', panel: testPanel2},
]);
await fixture.whenStable();
const tabs = fixture.nativeElement.querySelectorAll('.docs-tab');
const tabpanels = fixture.nativeElement.querySelectorAll('.docs-tab-panel');
expect(tabs[0].id).not.toBe(tabs[1].id);
expect(tabpanels[0].id).not.toBe(tabpanels[1].id);
const fixture2 = TestBed.createComponent(TabGroup);
fixture2.componentRef.setInput('tabs', [
{label: 'tab 1', panel: testPanel1},
{label: 'tab 2', panel: testPanel2},
]);
await fixture2.whenStable();
const tabs2 = fixture2.nativeElement.querySelectorAll('.docs-tab');
const tabpanels2 = fixture2.nativeElement.querySelectorAll('.docs-tab-panel');
expect(tabs[0].id).not.toBe(tabs2[0].id);
expect(tabpanels[0].id).not.toBe(tabpanels2[0].id);
});
});
@@ -7,18 +7,19 @@
*/
import {
Component,
input,
inject,
computed,
linkedSignal,
viewChildren,
afterRenderEffect,
Renderer2,
ElementRef,
ChangeDetectionStrategy,
Component,
computed,
ElementRef,
inject,
input,
linkedSignal,
Renderer2,
viewChildren,
} from '@angular/core';
import {_IdGenerator} from '@angular/cdk/a11y';
let idCounter = 0;
/**
* A simple tabs implementation with proper aria roles.
@@ -36,20 +37,24 @@ import {_IdGenerator} from '@angular/cdk/a11y';
})
export class TabGroup {
private readonly _renderer = inject(Renderer2);
private readonly _idGenerator = inject(_IdGenerator);
private readonly _tabpanels = viewChildren<ElementRef<HTMLDivElement>>('tabpanel');
readonly tabs = input<{label: string; panel: HTMLElement}[]>();
readonly computedTabs = computed(
() =>
this.tabs()?.map((tab) => ({
tabId: this._idGenerator.getId('docs-tab-'),
tabPanelId: this._idGenerator.getId('docs-tab-panel-'),
label: tab.label,
panel: tab.panel,
})) ?? [],
);
readonly computedTabs = computed(() => {
return (
this.tabs()?.map((tab) => {
const id = idCounter++;
return {
tabId: `docs-tab-${id}`,
tabPanelId: `docs-tab-panel-${id}`,
label: tab.label,
panel: tab.panel,
};
}) ?? []
);
});
readonly selectedTab = linkedSignal(() => this.computedTabs()[0]?.tabId);
@@ -8,11 +8,19 @@
top: 0;
height: fit-content;
width: 14rem;
padding-right: 2rem;
padding-right: 1rem;
max-height: 100vh;
overflow-y: scroll;
box-sizing: border-box;
@include mq.for-large-desktop-up {
width: 16rem;
}
@include mq.for-extra-large-desktop-up {
width: 25rem;
}
aside {
margin-bottom: 2rem;
}
@@ -9,7 +9,6 @@
import {
afterNextRender,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
ElementRef,
@@ -20,13 +19,13 @@ import {
Type,
} from '@angular/core';
import {DOCUMENT, NgComponentOutlet, NgTemplateOutlet} from '@angular/common';
import {MatTabsModule} from '@angular/material/tabs';
import {MatTab, MatTabGroup} from '@angular/material/tabs';
import {Clipboard} from '@angular/cdk/clipboard';
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
import {IconComponent} from '../../icon/icon.component';
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTooltip} from '@angular/material/tooltip';
export const CODE_LINE_NUMBER_CLASS_NAME = 'shiki-ln-number';
export const CODE_LINE_CLASS_NAME = 'line';
@@ -37,8 +36,9 @@ export const HIDDEN_CLASS_NAME = 'hidden';
selector: 'docs-example-viewer',
imports: [
CopySourceCodeButton,
MatTabsModule,
MatTooltipModule,
MatTabGroup,
MatTab,
MatTooltip,
IconComponent,
NgTemplateOutlet,
NgComponentOutlet,
+7 -1
View File
@@ -5,9 +5,15 @@ load("//adev/shared-docs/pipeline:_previews.bzl", _generate_previews = "generate
load("//adev/shared-docs/pipeline:_stackblitz.bzl", _generate_stackblitz = "generate_stackblitz")
load("//adev/shared-docs/pipeline:_tutorial.bzl", _generate_tutorial = "generate_tutorial")
generate_guides = _generate_guides
generate_stackblitz = _generate_stackblitz
generate_previews = _generate_previews
generate_playground = _generate_playground
generate_tutorial = _generate_tutorial
generate_nav_items = _generate_nav_items
def generate_guides(**kwargs):
_generate_guides(
api_manifest = "//adev/src/assets:docs_api_manifest",
defined_routes = "//adev/scripts/routes:defined-routes.json",
**kwargs
)
+2
View File
@@ -47,6 +47,7 @@ js_binary(
"//adev:node_modules/jsdom",
"//adev:node_modules/mermaid",
"//adev:node_modules/playwright-core",
"//adev:node_modules/typescript",
"@rules_browsers//browsers/chromium",
],
entry_point = "//adev/shared-docs/pipeline/guides:guides.mjs",
@@ -61,6 +62,7 @@ js_binary(
name = "markdown_no_mermaid",
data = [
"//adev:node_modules/jsdom",
"//adev:node_modules/typescript",
],
entry_point = "//adev/shared-docs/pipeline/guides:guides-no-mermaid.mjs",
visibility = ["//visibility:public"],
+11
View File
@@ -22,6 +22,11 @@ def _generate_guides(ctx):
else:
args.add("")
if ctx.attr.defined_routes:
args.add(ctx.file.defined_routes.path)
else:
args.add("")
# Determine the set of html output files. For each input markdown file, produce an html
# file with the same name (replacing the markdown extension with `.html`).
html_outputs = []
@@ -39,6 +44,8 @@ def _generate_guides(ctx):
inputs = ctx.files.srcs + ctx.files.data
if ctx.attr.api_manifest:
inputs.append(ctx.file.api_manifest)
if ctx.attr.defined_routes:
inputs.append(ctx.file.defined_routes)
if (ctx.attr.mermaid_blocks):
ctx.actions.run(
@@ -85,6 +92,10 @@ generate_guides = rule(
doc = """A file containing API entries to be used in the markdown.""",
allow_single_file = True,
),
"defined_routes": attr.label(
doc = """List of defined routes.""",
allow_single_file = [".json"],
),
"mermaid_blocks": attr.bool(
doc = """Whether to transform mermaid blocks.""",
default = False,
@@ -7,6 +7,7 @@ esbuild(
entry_point = ":index.mts",
external = [
"@angular/compiler-cli",
"typescript",
],
format = "esm",
output = "bin.mjs",
@@ -8,6 +8,7 @@ esbuild(
external = [
"jsdom",
"playwright-core",
"typescript",
],
format = "esm",
output = "bin.mjs",
@@ -126,11 +126,11 @@ export interface EnumEntry extends DocEntry {
/** Documentation entity for an Angular decorator. */
export interface DecoratorEntry extends DocEntry {
decoratorType: DecoratorType;
members: PropertyEntry[] | null;
signatures?: {
parameters: ParameterEntry[];
jsdocTags: JsDocTagEntry[];
}[];
members: MemberEntry[];
}
/** Documentation entity for an Angular directives and components. */
@@ -161,6 +161,9 @@ export type FunctionEntry = FunctionDefinitionEntry &
implementation: FunctionSignatureMetadata;
};
/** Documentation entity for a block. */
export interface BlockEntry extends DocEntry {}
/** Interface describing a function with overload signatures. */
export interface FunctionDefinitionEntry {
name: string;
@@ -7,6 +7,7 @@
*/
import {
BlockEntry,
ClassEntry,
ConstantEntry,
DecoratorEntry,
@@ -27,6 +28,7 @@ import {
import {CliCommand} from '../cli-entities.mjs';
import {
BlockEntryRenderable,
ClassEntryRenderable,
ConstantEntryRenderable,
DecoratorEntryRenderable,
@@ -107,6 +109,13 @@ export function isFunctionEntry(entry: DocEntry): entry is FunctionEntry {
return entry.entryType === EntryType.Function;
}
/** Gets whether the given entry represents a block */
export function isBlockEntry(entry: DocEntryRenderable): entry is BlockEntryRenderable;
export function isBlockEntry(entry: DocEntry): entry is BlockEntry;
export function isBlockEntry(entry: DocEntry): entry is BlockEntry {
return entry.entryType === EntryType.Block;
}
export function isInitializerApiFunctionEntry(
entry: DocEntryRenderable,
): entry is InitializerApiFunctionRenderable;
@@ -21,6 +21,7 @@ import {
ParameterEntry,
PipeEntry,
TypeAliasEntry,
EntryType,
} from '../entities.mjs';
import {CliCommand, CliOption} from '../cli-entities.mjs';
@@ -75,7 +76,11 @@ export type PipeEntryRenderable = PipeEntry &
members: MemberEntryRenderable[];
};
export type DecoratorEntryRenderable = DecoratorEntry & DocEntryRenderable & HasRenderableToc;
export type DecoratorEntryRenderable = Omit<DecoratorEntry, 'members'> &
DocEntryRenderable &
HasRenderableToc & {
members: PropertyEntryRenderable[];
};
/** Documentation entity for a TypeScript enum augmented transformed content for rendering. */
export type EnumEntryRenderable = EnumEntry &
@@ -102,6 +107,9 @@ export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata &
params: ParameterEntryRenderable[];
};
/** Documentation entity for a block augmented with transformed content for rendering. */
export type BlockEntryRenderable = DocEntry & DocEntryRenderable;
/** Sub-entry for a single class or enum member augmented with transformed content for rendering. */
export interface MemberEntryRenderable extends MemberEntry {
htmlDescription: string;
@@ -116,6 +124,10 @@ export interface MemberEntryRenderable extends MemberEntry {
experimental: {version: string | undefined} | undefined;
}
export interface PropertyEntryRenderable extends MemberEntryRenderable {
type: string;
}
/** Sub-entry for a class method augmented transformed content for rendering. */
export type MethodEntryRenderable = MemberEntryRenderable &
FunctionEntryRenderable & {
@@ -155,11 +167,9 @@ export type CliCommandRenderable = CliCommand & {
htmlDescription: string;
cards: CliCardRenderable[];
argumentsLabel: string;
hasOptions: boolean;
optionsLabel: string;
subcommands?: CliCommandRenderable[];
};
export interface InitializerApiFunctionRenderable
extends Omit<InitializerApiFunctionEntry, 'jsdocTags'>,
DocEntryRenderable,
HasRenderableToc {}
extends Omit<InitializerApiFunctionEntry, 'jsdocTags'>, DocEntryRenderable, HasRenderableToc {}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities.mjs';
import {JsDocTagEntry, MemberEntry, ParameterEntry, PropertyEntry} from '../entities.mjs';
import {
CodeLineRenderable,
@@ -14,6 +14,7 @@ import {
LinkEntryRenderable,
MemberEntryRenderable,
ParameterEntryRenderable,
PropertyEntryRenderable,
} from './renderables.mjs';
/** A doc entry that has jsdoc tags. */
@@ -21,6 +22,10 @@ export interface HasJsDocTags {
jsdocTags: JsDocTagEntry[];
}
export interface MaybeJsDocTags {
jsdocTags?: JsDocTagEntry[];
}
export interface HasAdditionalLinks {
additionalLinks: LinkEntryRenderable[];
}
@@ -56,6 +61,10 @@ export interface HasMembers {
members: MemberEntry[];
}
export interface HasPropertyMembers {
members: PropertyEntry[];
}
/** A doc entry that has members groups transformed for rendering. */
export interface HasRenderableMembersGroups {
membersGroups: Map<string, MemberEntryRenderable[]>;
@@ -66,6 +75,10 @@ export interface HasRenderableMembers {
members: MemberEntryRenderable[];
}
export interface HasRenderablePropertyMembers {
members: PropertyEntryRenderable[];
}
/** A doc entry that has an associated JS module name. */
export interface HasModuleName {
moduleName: string;
@@ -19,6 +19,7 @@ import {
isInitializerApiFunctionEntry,
isInterfaceEntry,
isTypeAliasEntry,
isBlockEntry,
} from './entities/categorization.mjs';
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables.mjs';
import {getClassRenderable} from './transforms/class-transforms.mjs';
@@ -39,6 +40,7 @@ import {
import {addModuleName} from './transforms/module-name.mjs';
import {addRepo} from './transforms/repo.mjs';
import {getTypeAliasRenderable} from './transforms/type-alias-transforms.mjs';
import {getBlockRenderable} from './transforms/block-transforms.mjs';
export async function getRenderable(
entry: DocEntry | CliCommand,
@@ -73,6 +75,9 @@ export async function getRenderable(
if (isInitializerApiFunctionEntry(entry)) {
return getInitializerApiFunctionRenderable(entry, moduleName, repo);
}
if (isBlockEntry(entry)) {
return getBlockRenderable(entry, moduleName, repo);
}
// Fallback to an uncategorized renderable.
return setEntryFlags(
@@ -18,6 +18,7 @@ import {
isInitializerApiFunctionEntry,
isInterfaceEntry,
isTypeAliasEntry,
isBlockEntry,
} from './entities/categorization.mjs';
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables.mjs';
import {ClassReference} from './templates/class-reference';
@@ -30,6 +31,7 @@ import {InitializerApiFunction} from './templates/initializer-api-function';
import {TypeAliasReference} from './templates/type-alias-reference';
import {DecoratorReference} from './templates/decorator-reference';
import {setCurrentSymbol} from './symbol-context.mjs';
import {BlockReference} from './templates/block-reference';
/** Given a doc entry, get the transformed version of the entry for rendering. */
export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderable): string {
@@ -59,6 +61,9 @@ export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderabl
if (isInitializerApiFunctionEntry(renderable)) {
return render(InitializerApiFunction(renderable));
}
if (isBlockEntry(renderable)) {
return render(BlockReference(renderable));
}
// Fall back rendering nothing while in development.
return render(DocsReference(renderable));
@@ -0,0 +1,23 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {h} from 'preact';
import {BlockEntryRenderable} from '../entities/renderables.mjs';
import {HeaderApi} from './header-api';
import {RawHtml} from './raw-html';
import {API_REFERENCE_CONTAINER} from '../styling/css-classes.mjs';
/** Component to render a block API reference document. */
export function BlockReference(entry: BlockEntryRenderable) {
return (
<div className={API_REFERENCE_CONTAINER}>
<HeaderApi entry={entry} hideDescription={true} />
<RawHtml value={entry.htmlDescription} />
</div>
);
}
@@ -12,15 +12,18 @@ import {
ParameterEntryRenderable,
} from '../entities/renderables.mjs';
import {PARAM_KEYWORD_CLASS_NAME, REFERENCE_MEMBER_CARD_ITEM} from '../styling/css-classes.mjs';
import {CodeSymbol} from './code-symbols';
import {DeprecatedLabel} from './deprecated-label';
import {Parameter} from './parameter';
import {RawHtml} from './raw-html';
import {CodeSymbol} from './code-symbols';
/**
* Component to render the method-specific parts of a class's API reference.
*/
export function ClassMethodInfo(props: {entry: FunctionSignatureMetadataRenderable}) {
export function ClassMethodInfo(props: {
entry: FunctionSignatureMetadataRenderable;
hideUsageNotes?: boolean;
}) {
const entry = props.entry;
return (
@@ -43,7 +46,7 @@ export function ClassMethodInfo(props: {entry: FunctionSignatureMetadataRenderab
<span className={PARAM_KEYWORD_CLASS_NAME}>@returns</span>
<CodeSymbol code={entry.returnType} />
</div>
{entry.htmlUsageNotes ? (
{entry.htmlUsageNotes && !props.hideUsageNotes ? (
<div className={'docs-usage-notes'}>
<span className={'docs-usage-notes-heading'}>Usage notes</span>
<RawHtml value={entry.htmlUsageNotes} />
@@ -28,6 +28,7 @@ import {RawHtml} from './raw-html';
import {DeprecationWarning} from './deprecation-warning';
import {codeToHtml} from '../../../shared/shiki.mjs';
import {getHighlighterInstance} from '../shiki/shiki.mjs';
import {getSymbolsAsApiEntries} from '../symbol-context.mjs';
/** Component to render a class API reference document. */
export function ClassReference(
@@ -43,6 +44,7 @@ export function ClassReference(
<RawHtml
value={codeToHtml(getHighlighterInstance(), (entry as PipeEntry).usage, {
language: 'angular-html',
apiEntries: getSymbolsAsApiEntries(),
})}
/>
</div>
@@ -26,19 +26,15 @@ export function CliCommandReference(entry: CliCommandRenderable) {
<code>
<div className={'shiki line cli'}>
ng {commandName(entry, command)}
{entry.argumentsLabel ? (
{entry.argumentsLabel && (
<button member-id={'Arguments'} className="shiki-ln-line-argument">
{entry.argumentsLabel}
</button>
) : (
<></>
)}
{entry.hasOptions ? (
{entry.optionsLabel && (
<button member-id={'Options'} className="shiki-ln-line-option">
[options]
{entry.optionsLabel}
</button>
) : (
<></>
)}
</div>
</code>
@@ -7,12 +7,45 @@
*/
import {h} from 'preact';
import {DecoratorEntryRenderable, PropertyEntryRenderable} from '../entities/renderables.mjs';
import {
API_REFERENCE_CONTAINER,
REFERENCE_MEMBER_CARD,
REFERENCE_MEMBER_CARD_BODY,
REFERENCE_MEMBER_CARD_HEADER,
REFERENCE_MEMBER_CARD_ITEM,
REFERENCE_MEMBERS,
} from '../styling/css-classes.mjs';
import {CodeSymbol} from './code-symbols';
import {HeaderApi} from './header-api';
import {RawHtml} from './raw-html';
import {SectionApi} from './section-api';
import {SectionDescription} from './section-description';
import {SectionUsageNotes} from './section-usage-notes';
import {SectionApi} from './section-api';
import {API_REFERENCE_CONTAINER} from '../styling/css-classes.mjs';
import {DecoratorEntryRenderable} from '../entities/renderables.mjs';
export const signatureCard = (
name: string,
member: PropertyEntryRenderable,
opts: {id: string},
) => {
return (
<div id={opts.id} class={REFERENCE_MEMBER_CARD}>
<header class={REFERENCE_MEMBER_CARD_HEADER}>
<h3>{name}</h3>
<div>
<CodeSymbol code={member.type} />
</div>
</header>
<div class={REFERENCE_MEMBER_CARD_BODY}>
<div className={`${REFERENCE_MEMBER_CARD_ITEM}`}>
<p>
<RawHtml value={member.htmlDescription} />
</p>
</div>
</div>
</div>
);
};
/** Component to render a decorator API reference document. */
export function DecoratorReference(entry: DecoratorEntryRenderable) {
@@ -20,6 +53,13 @@ export function DecoratorReference(entry: DecoratorEntryRenderable) {
<div className={API_REFERENCE_CONTAINER}>
<HeaderApi entry={entry} />
<SectionApi entry={entry} />
<div className={REFERENCE_MEMBERS}>
{entry.members.map((member, index) =>
signatureCard(member.name, member, {
id: `${member.name}_${index}`,
}),
)}
</div>
<SectionDescription entry={entry} />
<SectionUsageNotes entry={entry} />
</div>
@@ -22,23 +22,22 @@ import {printInitializerFunctionSignatureLine} from '../transforms/code-transfor
import {getFunctionMetadataRenderable} from '../transforms/function-transforms.mjs';
import {ClassMethodInfo} from './class-method-info';
import {CodeSymbol} from './code-symbols';
import {DeprecationWarning} from './deprecation-warning';
import {HeaderApi} from './header-api';
import {HighlightTypeScript} from './highlight-ts';
import {SectionApi} from './section-api';
import {SectionDescription} from './section-description';
import {SectionUsageNotes} from './section-usage-notes';
import {DeprecationWarning} from './deprecation-warning';
export const signatureCard = (
name: string,
signature: FunctionSignatureMetadataRenderable,
opts: {id: string},
printSignaturesAsHeader: boolean,
opts: {id: string; printSignaturesAsHeader: boolean; hideUsageNotes?: boolean},
) => {
return (
<div id={opts.id} class={REFERENCE_MEMBER_CARD}>
<header class={REFERENCE_MEMBER_CARD_HEADER}>
{printSignaturesAsHeader ? (
{opts.printSignaturesAsHeader ? (
<HighlightTypeScript
code={printInitializerFunctionSignatureLine(
name,
@@ -57,7 +56,7 @@ export const signatureCard = (
)}
</header>
<div class={REFERENCE_MEMBER_CARD_BODY}>
<ClassMethodInfo entry={signature} />
<ClassMethodInfo entry={signature} hideUsageNotes={opts.hideUsageNotes} />
</div>
</div>
);
@@ -74,16 +73,14 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
<DeprecationWarning entry={entry} />
<SectionApi entry={entry} />
<div className={REFERENCE_MEMBERS}>
{entry.signatures.map((s, i) =>
signatureCard(
s.name,
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
{
{entry.signatures.length > 1 &&
entry.signatures.map((s, i) =>
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
id: `${s.name}_${i}`,
},
printSignaturesAsHeader,
),
)}
printSignaturesAsHeader,
hideUsageNotes: true,
}),
)}
</div>
<SectionDescription entry={entry} />
@@ -21,6 +21,7 @@ import {DocsPillRow} from './docs-pill-row';
export function HeaderApi(props: {
entry: DocEntryRenderable | PipeEntryRenderable;
showFullDescription?: boolean;
hideDescription?: boolean;
}) {
const entry = props.entry;
@@ -60,12 +61,14 @@ export function HeaderApi(props: {
)}
</div>
<section
className={'docs-reference-description'}
dangerouslySetInnerHTML={{
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
}}
></section>
{!props.hideDescription && (
<section
className={'docs-reference-description'}
dangerouslySetInnerHTML={{
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
}}
></section>
)}
<DocsPillRow links={entry.additionalLinks} />
</header>
@@ -10,10 +10,14 @@ import {h} from 'preact';
import {RawHtml} from './raw-html';
import {codeToHtml} from '../../../shared/shiki.mjs';
import {getHighlighterInstance} from '../shiki/shiki.mjs';
import {getSymbolsAsApiEntries} from '../symbol-context.mjs';
/** Component to render a header of the CLI page. */
export function HighlightTypeScript(props: {code: string}) {
const result = codeToHtml(getHighlighterInstance(), props.code, {language: 'typescript'});
const result = codeToHtml(getHighlighterInstance(), props.code, {
language: 'typescript',
apiEntries: getSymbolsAsApiEntries(),
});
const withScrollTrack = result.replace(/^(<pre class="shiki)/, '$1 docs-mini-scroll-track');
return <RawHtml value={withScrollTrack} className="docs-code" />;
@@ -40,14 +40,10 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
<div class={REFERENCE_MEMBERS}>
{entry.callFunction.signatures.map((s, i) =>
signatureCard(
s.name,
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
{
id: `${s.name}_${i}`,
},
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
id: `${s.name}_${i}`,
printSignaturesAsHeader,
),
}),
)}
{entry.subFunctions.reduce(
@@ -59,8 +55,8 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
{
id: `${entry.name}_${s.name}_${i}`,
printSignaturesAsHeader,
},
printSignaturesAsHeader,
),
),
],
@@ -40,7 +40,7 @@ describe('CLI docs to html', () => {
const cliTocs = fragment.querySelectorAll('.docs-reference-cli-toc')!;
expect(cliTocs.length).toBe(2);
expect(cliTocs[0].textContent).toContain('ng component[name][options]');
expect(cliTocs[1].textContent).toContain('ng c[name][options]');
expect(cliTocs[0].textContent).toContain('ng component [name] [options]');
expect(cliTocs[1].textContent).toContain('ng c [name] [options]');
});
});
@@ -8,12 +8,12 @@
import {readFile} from 'fs/promises';
import {JSDOM} from 'jsdom';
import {getRenderable} from '../processing.mjs';
import {renderEntry} from '../rendering.mjs';
import {setSymbols} from '../symbol-context.mjs';
import {resolve} from 'path';
import {initHighlighter} from '../../../shared/shiki.mjs';
import {getRenderable} from '../processing.mjs';
import {renderEntry} from '../rendering.mjs';
import {setHighlighterInstance} from '../shiki/shiki.mjs';
import {setSymbols} from '../symbol-context.mjs';
// Note: The tests will probably break if the schema of the api extraction changes.
// All entries in the fake-entries are extracted from Angular's api.
@@ -69,7 +69,7 @@ describe('markdown to html', () => {
it('should render multiple {@link} blocks', () => {
const provideClientHydrationEntry = entries.get('provideClientHydration')!;
expect(provideClientHydrationEntry).toBeDefined();
const cardItem = provideClientHydrationEntry.querySelector('.docs-reference-card-item')!;
const cardItem = provideClientHydrationEntry.querySelector('.docs-api')!;
expect(cardItem.innerHTML).not.toContain('@link');
});
@@ -17,204 +17,207 @@ import {
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
describe('jsdoc transforms', () => {
it('should transform links', () => {
setCurrentSymbol('Router');
setSymbols(
Object.fromEntries([
['Route', 'test'],
['Router', 'test'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
describe('addHtmlAdditionalLinks', () => {
it('should transform links', () => {
setCurrentSymbol('Router');
setSymbols(
Object.fromEntries([
['Route', 'test'],
['Router', 'test'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
const entry = addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment: '[Angular](https://angular.io)',
},
{
name: 'see',
comment: '[Angular](https://angular.io "Angular")',
},
{
name: 'see',
comment: '{@link Route}',
},
{
name: 'see',
comment: '{@link Route Something else}',
},
{
name: 'see',
comment: '{@link #someMethod}',
},
{
name: 'see',
comment: '{@link #someMethodWithParenthesis()}',
},
{
name: 'see',
comment: '{@link someMethod()}',
},
{
name: 'see',
comment: '{@link FormGroup.someMethod()}',
},
{
name: 'see',
comment: '{@link https://angular.dev/api/core/ApplicationRef}',
},
{
name: 'see',
comment: '{@link https://angular.dev}',
},
{
name: 'see',
comment: '{@link /cli/build ng build}',
},
{
name: 'see',
comment: '{@link /ecosystem/rxjs-interop/output-interop Output Interop}',
},
],
moduleName: 'test',
});
expect(entry.additionalLinks[0]).toEqual({
label: 'Angular',
url: 'https://angular.io',
title: undefined,
});
expect(entry.additionalLinks[1]).toEqual({
label: 'Angular',
url: 'https://angular.io',
title: 'Angular',
});
expect(entry.additionalLinks[2]).toEqual({
label: 'Route',
url: '/api/test/Route',
});
expect(entry.additionalLinks[3]).toEqual({
label: 'Something else',
url: '/api/test/Route',
});
expect(entry.additionalLinks[4]).toEqual({
label: 'someMethod',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[5]).toEqual({
label: 'someMethodWithParenthesis()',
url: '/api/test/Router#someMethodWithParenthesis',
});
expect(entry.additionalLinks[6]).toEqual({
label: 'someMethod()',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[7]).toEqual({
label: 'FormGroup.someMethod()',
url: '/api/test/FormGroup#someMethod',
});
expect(entry.additionalLinks[8]).toEqual({
label: 'ApplicationRef',
url: 'https://angular.dev/api/core/ApplicationRef',
});
expect(entry.additionalLinks[9]).toEqual({
label: 'angular.dev',
url: 'https://angular.dev',
});
expect(entry.additionalLinks[10]).toEqual({
label: 'ng build',
url: '/cli/build',
});
expect(entry.additionalLinks[11]).toEqual({
label: 'Output Interop',
url: '/ecosystem/rxjs-interop/output-interop',
});
});
it('should convert backticks to code tags in markdown links', () => {
const entry = addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment:
'[Host view using `ViewContainerRef.createComponent`](guide/components/programmatic-rendering#host-view-using-viewcontainerrefcreatecomponent)',
},
{
name: 'see',
comment:
'[Popup attached to `document.body` with `createComponent` + `hostElement`](guide/components/programmatic-rendering#popup-attached-to-documentbody-with-createcomponent--hostelement)',
},
{
name: 'see',
comment: '[Method with `backticks` in title](https://example.com "Title with `code`")',
},
],
moduleName: 'test',
});
expect(entry.additionalLinks[0]).toEqual({
label: 'Host view using <code>ViewContainerRef.createComponent</code>',
url: 'guide/components/programmatic-rendering#host-view-using-viewcontainerrefcreatecomponent',
title: undefined,
});
expect(entry.additionalLinks[1]).toEqual({
label:
'Popup attached to <code>document.body</code> with <code>createComponent</code> + <code>hostElement</code>',
url: 'guide/components/programmatic-rendering#popup-attached-to-documentbody-with-createcomponent--hostelement',
title: undefined,
});
expect(entry.additionalLinks[2]).toEqual({
label: 'Method with <code>backticks</code> in title',
url: 'https://example.com',
title: 'Title with `code`',
});
});
it('should throw on invalid relatie @link', () => {
const entryFn = () =>
addHtmlAdditionalLinks({
const entry = addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment: '{@link cli/build ng build}',
comment: '[Angular](https://angular.io)',
},
{
name: 'see',
comment: '[Angular](https://angular.io "Angular")',
},
{
name: 'see',
comment: '{@link Route}',
},
{
name: 'see',
comment: '{@link Route Something else}',
},
{
name: 'see',
comment: '{@link #someMethod}',
},
{
name: 'see',
comment: '{@link #someMethodWithParenthesis()}',
},
{
name: 'see',
comment: '{@link someMethod()}',
},
{
name: 'see',
comment: '{@link FormGroup.someMethod()}',
},
{
name: 'see',
comment: '{@link https://angular.dev/api/core/ApplicationRef}',
},
{
name: 'see',
comment: '{@link https://angular.dev}',
},
{
name: 'see',
comment: '{@link /cli/build ng build}',
},
{
name: 'see',
comment: '{@link /ecosystem/rxjs-interop/output-interop Output Interop}',
},
],
moduleName: 'test',
});
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
expect(entry.additionalLinks[0]).toEqual({
label: 'Angular',
url: 'https://angular.io',
title: undefined,
});
expect(entry.additionalLinks[1]).toEqual({
label: 'Angular',
url: 'https://angular.io',
title: 'Angular',
});
expect(entry.additionalLinks[2]).toEqual({
label: 'Route',
url: '/api/test/Route',
});
expect(entry.additionalLinks[3]).toEqual({
label: 'Something else',
url: '/api/test/Route',
});
expect(entry.additionalLinks[4]).toEqual({
label: 'someMethod',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[5]).toEqual({
label: 'someMethodWithParenthesis()',
url: '/api/test/Router#someMethodWithParenthesis',
});
expect(entry.additionalLinks[6]).toEqual({
label: 'someMethod()',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[7]).toEqual({
label: 'FormGroup.someMethod()',
url: '/api/test/FormGroup#someMethod',
});
expect(entry.additionalLinks[8]).toEqual({
label: 'ApplicationRef',
url: 'https://angular.dev/api/core/ApplicationRef',
});
expect(entry.additionalLinks[9]).toEqual({
label: 'angular.dev',
url: 'https://angular.dev',
});
expect(entry.additionalLinks[10]).toEqual({
label: 'ng build',
url: '/cli/build',
});
expect(entry.additionalLinks[11]).toEqual({
label: 'Output Interop',
url: '/ecosystem/rxjs-interop/output-interop',
});
});
it('should convert backticks to code tags in markdown links', () => {
const entry = addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment:
'[Host view using `ViewContainerRef.createComponent`](guide/components/programmatic-rendering#host-view-using-viewcontainerrefcreatecomponent)',
},
{
name: 'see',
comment:
'[Popup attached to `document.body` with `createComponent` + `hostElement`](guide/components/programmatic-rendering#popup-attached-to-documentbody-with-createcomponent--hostelement)',
},
{
name: 'see',
comment: '[Method with `backticks` in title](https://example.com "Title with `code`")',
},
],
moduleName: 'test',
});
expect(entry.additionalLinks[0]).toEqual({
label: 'Host view using <code>ViewContainerRef.createComponent</code>',
url: 'guide/components/programmatic-rendering#host-view-using-viewcontainerrefcreatecomponent',
title: undefined,
});
expect(entry.additionalLinks[1]).toEqual({
label:
'Popup attached to <code>document.body</code> with <code>createComponent</code> + <code>hostElement</code>',
url: 'guide/components/programmatic-rendering#popup-attached-to-documentbody-with-createcomponent--hostelement',
title: undefined,
});
expect(entry.additionalLinks[2]).toEqual({
label: 'Method with <code>backticks</code> in title',
url: 'https://example.com',
title: 'Title with `code`',
});
});
it('should throw on invalid relatie @link', () => {
const entryFn = () =>
addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment: '{@link cli/build ng build}',
},
],
moduleName: 'test',
});
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
});
});
it('should parse markdown in descriptions', async () => {
setHighlighterInstance(await initHighlighter());
describe('addHtmlDescription', () => {
it('should parse markdown in descriptions', async () => {
setHighlighterInstance(await initHighlighter());
setSymbols(
Object.fromEntries([
['Route', 'test'],
['Router', 'angular/router'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
setSymbols(
Object.fromEntries([
['Route', 'test'],
['Router', 'angular/router'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
const entry = addHtmlDescription({
description: `
const entry = addHtmlDescription({
description: `
\`\`\`angular-ts
import { Router } from '@angular/router';
@@ -223,16 +226,36 @@ function setupRouter() {
}
\`\`\`
`,
moduleName: 'test',
moduleName: 'test',
});
// Should have some shiki variables (meaning the description was highlighted).
expect(entry.htmlDescription).toContain('--shiki');
// Having docs-code means that the description was parsed and formatted correctly (by the shared marked renderer)
expect(entry.htmlDescription).toContain('class="docs-code"');
expect(entry.htmlDescription).toContain('/api/angular/router/Router');
});
// Should have some shiki variables (meaning the description was highlighted).
expect(entry.htmlDescription).toContain('--shiki');
it('should transform entry with different description & description tag', () => {
const entry = addHtmlDescription({
'description':
"Enables interop with the browser's `Navigation` API for router navigations.",
'jsdocTags': [
{
'name': 'description',
'comment': 'This feature is _highly_ experimental ...',
},
],
'moduleName': 'platform-browser',
});
// Having docs-code means that the description was parsed and formatted correctly (by the shared marked renderer)
expect(entry.htmlDescription).toContain('class="docs-code"');
expect(entry.htmlDescription).toContain('/api/angular/router/Router');
expect(entry.htmlDescription).toBe(`<p>This feature is <em>highly</em> experimental ...</p>`);
expect(entry.shortHtmlDescription).toBe(
`<p>Enables interop with the browser's <code>Navigation</code> API for router navigations.</p>`,
);
});
});
it('should only mark as deprecated if all overloads are deprecated', () => {
@@ -0,0 +1,36 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {DocEntry} from '../entities.mjs';
import {BlockEntryRenderable} from '../entities/renderables.mjs';
import {
addHtmlAdditionalLinks,
addHtmlDescription,
addHtmlJsDocTagComments,
addHtmlUsageNotes,
setEntryFlags,
} from './jsdoc-transforms.mjs';
import {addModuleName} from './module-name.mjs';
import {addRepo} from './repo.mjs';
/** Given an unprocessed block entry, get the fully renderable block entry. */
export function getBlockRenderable(
blockEntry: DocEntry,
moduleName: string,
repo: string,
): BlockEntryRenderable {
return setEntryFlags(
addHtmlAdditionalLinks(
addHtmlDescription(
addHtmlUsageNotes(
addHtmlJsDocTagComments(addRepo(addModuleName(blockEntry, moduleName), repo)),
),
),
),
);
}
@@ -27,7 +27,7 @@ export function getCliRenderable(command: CliCommand): CliCommandRenderable {
}),
cards: getCliCardsRenderable(command),
argumentsLabel: getArgumentsLabel(command),
hasOptions: getOptions(command).length > 0,
optionsLabel: getOptionsLabel(command),
};
}
@@ -66,7 +66,12 @@ function getArgumentsLabel(command: CliCommand): string {
if (args.length === 0) {
return '';
}
return command.command.replace(`${command.name} `, '');
const label = command.command.replace(/^ng\s+/, '').replace(`${command.name} `, '');
return ` ${label}`;
}
function getOptionsLabel(command: CliCommand): string {
return getOptions(command).length > 0 ? ' [options]' : '';
}
function getArgs(command: CliCommand): CliOption[] {
@@ -39,7 +39,7 @@ import {
insertParenthesesForDecoratorInShikiHtml,
replaceKeywordFromShikiHtml,
} from '../shiki/shiki.mjs';
import {getSymbolUrl} from '../symbol-context.mjs';
import {getSymbolsAsApiEntries, getSymbolUrl} from '../symbol-context.mjs';
import {codeToHtml} from '../../../shared/shiki.mjs';
import {formatJs} from './format-code.mjs';
@@ -92,7 +92,10 @@ export async function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
let codeWithSyntaxHighlighting = codeToHtml(
getHighlighterInstance(),
formattedCode ?? metadata?.contents,
{language: 'typescript'},
{
language: 'typescript',
apiEntries: getSymbolsAsApiEntries(),
},
);
if (isDecoratorEntry(entry)) {
@@ -111,11 +114,6 @@ export async function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
// }
}
// Note: Don't expect enum value in signatures to be linked correctly
// as shiki already splits them into separate span blocks.
// Only the enum itself will recieve a link
codeWithSyntaxHighlighting = addApiLinksToHtml(codeWithSyntaxHighlighting);
// shiki returns the lines wrapped by 2 node : 1 pre node, 1 code node.
// As leveraging jsdom isn't trivial here, we rely on a regex to extract the line nodes
const pattern = /(.*?)<code.*?>(.*?)<\/code>(.*)/s;
@@ -16,6 +16,7 @@ import {
addHtmlUsageNotes,
setEntryFlags,
} from './jsdoc-transforms.mjs';
import {addRenderablePropertyMembers} from './member-transforms.mjs';
import {addModuleName} from './module-name.mjs';
import {addRepo} from './repo.mjs';
@@ -25,14 +26,24 @@ export async function getDecoratorRenderable(
moduleName: string,
repo: string,
): Promise<DecoratorEntryRenderable> {
const decoratorEntryWithMembers = {
...decoratorEntry,
members: decoratorEntry.members ?? [],
};
return setEntryFlags(
await addRenderableCodeToc(
addHtmlAdditionalLinks(
addHtmlUsageNotes(
addHtmlJsDocTagComments(
addHtmlDescription(addRepo(addModuleName(decoratorEntry, moduleName), repo)),
await addRenderablePropertyMembers(
addHtmlAdditionalLinks(
addHtmlUsageNotes(
addHtmlJsDocTagComments(
addHtmlDescription(
addRepo(addModuleName(decoratorEntryWithMembers, moduleName), repo),
),
),
),
),
decoratorEntry.name,
),
),
) as DecoratorEntryRenderable;
@@ -22,6 +22,7 @@ import {
HasModuleName,
HasRenderableJsDocTags,
HasStableFlag,
MaybeJsDocTags,
} from '../entities/traits.mjs';
import {parseMarkdown} from '../../../shared/marked/parse.mjs';
@@ -43,7 +44,7 @@ const jsDoclinkRegex = /\{\s*@link\s+([^}]+)\s*\}/;
const jsDoclinkRegexGlobal = new RegExp(jsDoclinkRegex.source, 'g');
/** Given an entity with a description, gets the entity augmented with an `htmlDescription`. */
export function addHtmlDescription<T extends HasDescription & HasModuleName>(
export function addHtmlDescription<T extends HasDescription & HasModuleName & MaybeJsDocTags>(
entry: T,
): T & HasHtmlDescription {
const firstParagraphRule = /(.*?)(?:\n\n|$)/s;
@@ -56,10 +57,17 @@ export function addHtmlDescription<T extends HasDescription & HasModuleName>(
?.comment ?? '';
}
const description = !!entry.description ? entry.description : jsDocDescription;
const shortTextMatch = description.match(firstParagraphRule);
let description = entry.description || jsDocDescription;
let shortDescription = description.match(firstParagraphRule)?.[0] ?? '';
// For the cases where the @description tag is after a short description
if (jsDocDescription && description !== jsDocDescription) {
shortDescription = entry.description;
description = jsDocDescription;
}
const htmlDescription = getHtmlForJsDocText(description).trim();
const shortHtmlDescription = getHtmlForJsDocText(shortTextMatch ? shortTextMatch[0] : '').trim();
const shortHtmlDescription = getHtmlForJsDocText(shortDescription).trim();
return {...entry, htmlDescription, shortHtmlDescription};
}
@@ -109,6 +117,7 @@ function getHtmlForJsDocText(text: string): string {
const parsed = parseMarkdown(mdToParse, {
apiEntries: getSymbolsAsApiEntries(),
highlighter: getHighlighterInstance(),
definedRoutes: [],
});
return addApiLinksToHtml(parsed);
}
@@ -136,6 +145,9 @@ function getHtmlAdditionalLinks<T extends HasJsDocTags>(entry: T): LinkEntryRend
.filter((tag) => tag.name === JS_DOC_SEE_TAG)
.map((tag) => tag.comment)
.map((comment) => {
// TODO: Throw when the comment is an absolute link.
// With TS 5.9 this is not possible as the ts api that extracts comments from tags strips the "http" part of links.
const markdownLinkMatch = comment.match(markdownLinkRule);
if (markdownLinkMatch) {
@@ -201,10 +213,6 @@ function convertLinks(text: string) {
}
function parseAtLink(link: string): {label: string; url: string} | undefined {
// Because of microsoft/TypeScript/issues/59679
// getTextOfJSDocComment introduces an extra space between the symbol and a trailing ()
link = link.replace(/ \(\)$/, '');
let [rawSymbol, description] = link.split(/\s(.+)/);
if (rawSymbol.startsWith('#')) {
rawSymbol = rawSymbol.substring(1);
@@ -10,7 +10,14 @@ import {MemberEntry, MemberTags, MemberType, type DocEntry} from '../entities.mj
import {isHiddenEntry} from '../entities/categorization.mjs';
import type {MemberEntryRenderable} from '../entities/renderables.mjs';
import {HasMembers, HasModuleName, HasRenderableMembers, HasRepo} from '../entities/traits.mjs';
import {
HasMembers,
HasModuleName,
HasPropertyMembers,
HasRenderableMembers,
HasRenderablePropertyMembers,
HasRepo,
} from '../entities/traits.mjs';
import {addRenderableCodeToc} from './code-transforms.mjs';
import {
@@ -85,8 +92,7 @@ export async function addRenderableMembers<T extends HasMembers & HasModuleName
): Promise<T & HasRenderableMembers> {
const members = (
await Promise.all(
// TODO: remove `?? []` when components repo is updated to include members array on type aliases.
(entry.members ?? [])
entry.members
.filter((member) => !isHiddenEntry(member))
.map((member) => {
if (member.memberType === MemberType.Interface) {
@@ -112,3 +118,11 @@ export async function addRenderableMembers<T extends HasMembers & HasModuleName
members,
};
}
export async function addRenderablePropertyMembers<
T extends HasPropertyMembers & HasModuleName & HasRepo,
>(entry: T, parentName: string): Promise<T & HasRenderablePropertyMembers> {
return {
...((await addRenderableMembers(entry, parentName)) as T & HasRenderablePropertyMembers),
};
}
@@ -14,6 +14,7 @@ esbuild(
external = [
"jsdom",
"playwright-core",
"typescript",
],
format = "esm",
output = "guides.mjs",
@@ -38,6 +39,7 @@ esbuild(
entry_point = ":index.mts",
external = [
"jsdom",
"typescript",
],
output = "guides-no-mermaid.mjs",
platform = "node",
+24 -12
View File
@@ -8,8 +8,8 @@
import {readFile, writeFile} from 'fs/promises';
import path from 'path';
import {initHighlighter} from '../shared/shiki.mjs';
import {parseMarkdownAsync} from '../shared/marked/parse.mjs';
import {initHighlighter} from '../shared/shiki.mjs';
import {hasUnknownAnchors} from './helpers.mjs';
type ApiManifest = ApiManifestPackage[];
@@ -21,33 +21,45 @@ interface ApiManifestPackage {
async function main() {
const [paramFilePath] = process.argv.slice(2);
const rawParamLines = (await readFile(paramFilePath, {encoding: 'utf8'})).split('\n');
const [srcs, outputFilenameExecRootRelativePath, apiManifestPath] = rawParamLines;
const [srcs, outputFilenameExecRootRelativePath, apiManifestPath, definedRoutesAsStr] =
rawParamLines;
// The highlighter needs to be setup asynchronously
// so we're doing it at the start of the pipeline
const highlighter = await initHighlighter();
let apiManifest: ApiManifest = [];
if (!apiManifestPath) {
throw new Error(
'No API manifest path provided to the markdown parser. Check the failing generate_guides target.',
);
}
const apiManifestStr = await readFile(apiManifestPath, {encoding: 'utf8'});
apiManifest = JSON.parse(apiManifestStr);
let definedRoutes: string[] = [];
if (!definedRoutesAsStr) {
throw new Error(
'No defined routes path provided to the markdown parser. Check the failing generate_guides target.',
);
}
const definedGuideRoutes = await readFile(definedRoutesAsStr, {encoding: 'utf8'});
definedRoutes = JSON.parse(definedGuideRoutes) as string[];
await Promise.all(
srcs.split(',').map(async (filePath) => {
if (!filePath.endsWith('.md')) {
throw new Error(`Input file "${filePath}" does not end in a ".md" file extension.`);
}
let apiManifest: ApiManifest = [];
if (apiManifestPath) {
try {
const apiManifestStr = await readFile(apiManifestPath, {encoding: 'utf8'});
apiManifest = JSON.parse(apiManifestStr);
} catch (error) {
console.warn('Failed to load API entries:', error);
}
}
const markdownContent = await readFile(filePath, {encoding: 'utf8'});
const htmlOutputContent = await parseMarkdownAsync(markdownContent, {
markdownFilePath: filePath,
apiEntries: mapManifestToEntries(apiManifest),
highlighter,
definedRoutes,
});
// The expected file name structure is the [name of the file].md.html.
@@ -17,7 +17,9 @@ ts_project(
"//adev/shared-docs/pipeline:__subpackages__",
],
deps = [
":linking",
"//adev:node_modules/shiki",
"//adev:node_modules/typescript",
],
)
@@ -20,6 +20,16 @@ const LINK_EXEMPT = new Set([
'hidden',
'state',
'group',
'animation',
'transition',
'trigger',
'group()',
'keyframes',
'@keyframes',
'input',
'Event',
'form',
'type',
]);
export function shouldLinkSymbol(symbol: string): boolean {
@@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Tokens, Token, RendererThis, TokenizerThis} from 'marked';
import {loadWorkspaceRelativeFile, anchorTarget} from '../../helpers.mjs';
import {setInsideLink} from '../../transformations/link.mjs';
import {RendererThis, Token, TokenizerThis, Tokens} from 'marked';
import {anchorTarget, loadWorkspaceRelativeFile} from '../../helpers.mjs';
import {AdevDocsRenderer} from '../../renderer.mjs';
interface DocsCardToken extends Tokens.Generic {
type: 'docs-card';
@@ -67,11 +67,13 @@ export const docsCardExtension = {
return undefined;
},
renderer(this: RendererThis, token: DocsCardToken) {
return token.imgSrc ? getCardWithSvgIllustration(this, token) : getStandardCard(this, token);
return token.imgSrc
? getCardWithSvgIllustration(this, token)
: getStandardCard(this.parser.renderer as AdevDocsRenderer, token);
},
};
function getStandardCard(renderer: RendererThis, token: DocsCardToken) {
function getStandardCard(renderer: AdevDocsRenderer, token: DocsCardToken) {
if (token.iconImgSrc && token.href) {
// We can assume that all icons are svg files since they are custom.
// We need to read svg content, instead of renering svg with `img`,
@@ -110,13 +112,11 @@ function getStandardCard(renderer: RendererThis, token: DocsCardToken) {
`;
}
function parseWithoutCreatingLinks(renderer: RendererThis, token: DocsCardToken) {
setInsideLink(true);
try {
return renderer.parser.parse(token.tokens);
} finally {
setInsideLink(false);
}
function parseWithoutCreatingLinks(renderer: AdevDocsRenderer, token: DocsCardToken) {
renderer.context.disableAutoLinking = true;
const parsed = renderer.parser.parse(token.tokens);
renderer.context.disableAutoLinking = false;
return parsed;
}
function getCardWithSvgIllustration(renderer: RendererThis, token: DocsCardToken) {
@@ -11,13 +11,18 @@ import {expandRangeStringValues} from './range.mjs';
import {JSDOM} from 'jsdom';
import {HighlighterGeneric} from 'shiki';
import {codeToHtml} from '../../../../shiki.mjs';
import {RendererContext} from '../../../renderer.mjs';
const lineNumberClassName: string = 'shiki-ln-number';
/**
* Updates the provided token's code value to include syntax highlighting.
*/
export function highlightCode(highlighter: HighlighterGeneric<any, any>, token: CodeToken) {
export function highlightCode(
highlighter: HighlighterGeneric<any, any>,
token: CodeToken,
context: RendererContext,
) {
// TODO(josephperrott): Handle mermaid usages i.e. language == mermaidClassName
if (token.language !== 'none' && token.language !== 'file') {
// Decode the code content to replace HTML entities to characters
@@ -27,7 +32,11 @@ export function highlightCode(highlighter: HighlighterGeneric<any, any>, token:
? new Set(expandRangeStringValues(token.highlight))
: undefined;
token.code = codeToHtml(highlighter, token.code, {language, highlight});
token.code = codeToHtml(highlighter, token.code, {
language,
highlight,
apiEntries: context.apiEntries,
});
}
if (token.linenums) {
@@ -50,7 +50,7 @@ export function formatCode(token: CodeToken, context: RendererContext): string {
}
extractRegions(token);
highlightCode(context.highlighter, token);
highlightCode(context.highlighter, token, context);
const containerEl = JSDOM.fragment(`
<div class="docs-code${token.style ? ' docs-code-' + token.style : ''}">
@@ -7,7 +7,8 @@
*/
import {Token, Tokens, RendererThis, TokenizerThis} from 'marked';
import {anchorTarget, isExternalLink} from '../../helpers.mjs';
import {anchorTarget} from '../../helpers.mjs';
import {AdevDocsRenderer} from '../../renderer.mjs';
interface DocsPillToken extends Tokens.Generic {
type: 'docs-pill';
@@ -59,13 +60,17 @@ export const docsPillExtension = {
renderer(this: RendererThis, token: DocsPillToken) {
const downloadAttr = token.download ? ` download="${token.download}"` : '';
const targetAttr = token.target ? ` target="${token.target}"` : anchorTarget(token.href);
const renderer = this.parser.renderer as AdevDocsRenderer;
if (!renderer.isKnownRoute(token.href)) {
throw new Error(
`Link target "${token.href}" is invalid in <docs-pill> in ${renderer.context.markdownFilePath} does not exist in the defined guide routes.`,
);
}
return `
<a class="docs-pill" href="${token.href}"${targetAttr}${downloadAttr}>
${this.parser.parseInline(token.tokens)}${
isExternalLink(token.href)
? '<docs-icon class="docs-icon-small">open_in_new</docs-icon>'
: ''
}
${this.parser.parseInline(token.tokens)}
</a>
`;
},
@@ -7,20 +7,32 @@
*/
import {Renderer} from 'marked';
import {linkRender} from './transformations/link.mjs';
import {tableRender} from './transformations/table.mjs';
import {listRender} from './transformations/list.mjs';
import {imageRender} from './transformations/image.mjs';
import {textRender} from './transformations/text.mjs';
import {headingRender} from './transformations/heading.mjs';
import {codespanRender} from './transformations/code.mjs';
import {HighlighterGeneric} from 'shiki';
import {codespanRender} from './transformations/code.mjs';
import {headingRender} from './transformations/heading.mjs';
import {imageRender} from './transformations/image.mjs';
import {linkRender} from './transformations/link.mjs';
import {listRender} from './transformations/list.mjs';
import {tableRender} from './transformations/table.mjs';
import {textRender} from './transformations/text.mjs';
export interface RendererContext {
/**
* Path of the markdown file being rendered.
*/
markdownFilePath?: string;
/**
* List of guide routes defined in the application.
*/
definedRoutes?: string[];
/**
* Map of API entries available for linking within the rendered markdown file.
*/
apiEntries?: Record<string, {moduleName: string; aliases?: string[]}>;
highlighter: HighlighterGeneric<any, any>;
headerIds: Map<string, number>;
/** In the case we want to disable auto-linking because anchor blocks might be incompatible where some code blocks are being rendered */
disableAutoLinking: boolean;
}
/**
@@ -46,6 +58,36 @@ export class AdevDocsRenderer extends Renderer {
return this.context.markdownFilePath?.includes('/content/guide') ?? false;
}
isKnownRoute(route: string): boolean {
route = route.startsWith('/') ? route.slice(1) : route;
route = route.split('?')[0]; // Remove query params
if (route.startsWith('api/') || route.startsWith('api#')) {
// Remove fragment for API routes, as we don't have enough info to check them.
route = route.split('#')[0];
}
if (!this.context.definedRoutes?.length) {
return true;
}
if (
route.startsWith('http') ||
route.startsWith('#') || // Anchor link within the same page
route.startsWith('mailto:') || // Should we have a regex to exclude any protocol?
route.startsWith('playground') ||
// TODO: Extract routes from the CDK as well
route.startsWith('api/cdk') ||
// TODO: Extract routes from Aria as well
route.startsWith('api/aria') ||
route.startsWith('tutorials') ||
route.startsWith('extended-diagnostics')
) {
return true;
}
return this.context.definedRoutes.includes(route);
}
override link = linkRender;
override table = tableRender;
override list = listRender;
@@ -53,34 +95,4 @@ export class AdevDocsRenderer extends Renderer {
override text = textRender;
override heading = headingRender;
override codespan = codespanRender;
getHeaderId(heading: string) {
const numberOfHeaderOccurrencesInTheDocument = this.context.headerIds.get(heading) ?? 0;
this.context.headerIds.set(heading, numberOfHeaderOccurrencesInTheDocument + 1);
// extract the extended markdown heading id
// ex: ## MyHeading {# myId}
const match = heading.match(/{#([\w-]+)}/);
let extractedId: string;
if (match) {
extractedId = match[1];
} else {
extractedId = heading
.toLowerCase()
.replace(/<code>(.*?)<\/code>/g, '$1') // remove <code>
.replace(/<strong>(.*?)<\/strong>/g, '$1') // remove <strong>
.replace(/<em>(.*?)<\/em>/g, '$1') // remove <em>
.replace(/\s|\//g, '-') // remove spaces and slashes
.replace(/gt;|lt;/g, '') // remove escaped < and >
.replace(/&#\d+;/g, '') // remove HTML entities
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
}
const headerId = numberOfHeaderOccurrencesInTheDocument
? `${extractedId}-${numberOfHeaderOccurrencesInTheDocument}`
: extractedId;
return headerId;
}
}
@@ -25,11 +25,10 @@ describe('markdown to html', () => {
expect(samePageEl.textContent?.trim()).toBe('Same Page');
});
it('should render external links with _blank target and iconography', () => {
it('should render external links with _blank target', () => {
const samePageEl = markdownDocument.querySelectorAll('a.docs-pill')[1];
expect(samePageEl.getAttribute('target')).toBe('_blank');
expect(samePageEl.textContent?.trim()).toContain('External Page');
expect(samePageEl.querySelector('docs-icon')?.textContent).toBe('open_in_new');
expect(samePageEl.textContent?.trim()).toBe('External Page');
});
it('should render internal links that are relative paths', () => {
@@ -11,15 +11,3 @@
###### The smallest! (h6)
##### Another "more smaller" (h5)
## Duplicate Anchor
## Duplicate Anchor
## `myClass.myMethod` is the best
## ステップ 2 - アプリケーションのレイアウトに新しいコンポーネントを追加
## My heading {# my-custom-id }
## Query for the `<h1>`
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {parseMarkdown} from '../../parse.mjs';
import {resolve} from 'node:path';
import {readFile} from 'fs/promises';
import {JSDOM} from 'jsdom';
import {resolve} from 'node:path';
import {parseMarkdown} from '../../parse.mjs';
import {rendererContext} from '../renderer-context.mjs';
describe('markdown to html', () => {
@@ -43,19 +43,26 @@ describe('markdown to html', () => {
}
});
it('increments when multiple duplicate header names are found', () => {
// In case there is a valid usecase for duplicate header ids, we should use custom ids (as demonstrated below)
it('uses same id when multiple duplicate header names are found', () => {
const markdownDocument = JSDOM.fragment(
parseMarkdown(
`
## Duplicate Anchor
## Duplicate Anchor`,
rendererContext,
),
);
const headers = markdownDocument.querySelectorAll('a.docs-anchor');
const knownRefs = new Set<string>();
for (const el of headers) {
const href = el.getAttribute('href');
expect(knownRefs.has(href!)).toBeFalse();
knownRefs.add(href!);
}
expect(headers[0].getAttribute('href')).toBe(headers[1].getAttribute('href'));
});
it('should remove code block markups', () => {
const h2List = markdownDocument.querySelectorAll('h2');
const h2 = h2List[3];
const markdownDocument = JSDOM.fragment(
parseMarkdown('## `myClass.myMethod` is the best', rendererContext),
);
const h2 = markdownDocument.querySelector('h2')!;
const h2Anchor = h2?.firstElementChild;
const h2HeaderId = h2?.getAttribute('id');
@@ -66,8 +73,13 @@ describe('markdown to html', () => {
});
it('should be able to extract non-ascii ids', () => {
const h2List = markdownDocument.querySelectorAll('h2');
const h2 = h2List[4];
const markdownDocument = JSDOM.fragment(
parseMarkdown(
'## ステップ 2 - アプリケーションのレイアウトに新しいコンポーネントを追加',
rendererContext,
),
);
const h2 = markdownDocument.querySelector('h2')!;
const h2Anchor = h2?.firstElementChild;
const h2HeaderId = h2?.getAttribute('id');
@@ -80,8 +92,11 @@ describe('markdown to html', () => {
});
it('should be able to extract custom ids', () => {
const h2List = markdownDocument.querySelectorAll('h2');
const h2 = h2List[5];
const markdownDocument = JSDOM.fragment(
parseMarkdown('## My heading {# my-custom-id }', rendererContext),
);
const h2 = markdownDocument.querySelector('h2')!;
const h2Anchor = h2?.firstElementChild;
const h2HeaderId = h2?.getAttribute('id');
@@ -89,11 +104,17 @@ describe('markdown to html', () => {
expect(h2HeaderId).toBe('my-custom-id');
expect(h2AnchorHref).toBe(`#${h2HeaderId}`);
// Verify that the custom ID syntax is removed from the displayed text
expect(h2Anchor?.textContent?.trim()).toBe('My heading');
expect(h2Anchor?.textContent).not.toContain('{#');
});
it('should be able to parse heading with a valid tag in a code block', () => {
const h2List = markdownDocument.querySelectorAll('h2');
const h2 = h2List[6];
const markdownDocument = JSDOM.fragment(
parseMarkdown('## Query for the `<h1>`', rendererContext),
);
const h2 = markdownDocument.querySelector('h2')!;
// The anchor element should be to only child
expect(h2.children.length).toBe(1);
@@ -101,4 +122,17 @@ describe('markdown to html', () => {
expect(h2.firstElementChild!.innerHTML).toBe('Query for the <code>&lt;h1&gt;</code>');
});
it('shoud now link symbols in headings', () => {
const markdownDocument = JSDOM.fragment(
parseMarkdown('## Hello **NEW** `Router` ', rendererContext),
);
const h2 = markdownDocument.querySelector('h2')!;
// The anchor element should be to only child, no nested anchor
expect(h2.children.length).toBe(1);
// We ensure that we still style the heading content
expect(markdownDocument.querySelector('strong')).toBeDefined();
});
});
@@ -25,6 +25,7 @@ export const rendererContext: RendererContext = {
Router: {moduleName: 'angular/router'},
},
headerIds: new Map<string, number>(),
disableAutoLinking: false,
};
export async function setHighlighter() {
@@ -9,29 +9,34 @@
import {Tokens} from 'marked';
import {AdevDocsRenderer} from '../renderer.mjs';
export function headingRender(this: AdevDocsRenderer, {depth, tokens}: Tokens.Heading): string {
const text = this?.parser.parseInline(tokens);
export function headingRender(
this: AdevDocsRenderer,
{depth, tokens, text: headingText, raw}: Tokens.Heading,
): string {
this.context.disableAutoLinking = true;
const parsedText = this?.parser.parseInline(tokens, this);
this.context.disableAutoLinking = false;
if (depth === 1) {
return `
<header class="docs-header">
<docs-breadcrumb></docs-breadcrumb>
${getPageTitle(text, this.context.markdownFilePath)}
${getPageTitle(parsedText, this.context.markdownFilePath)}
</header>
`;
}
// Nested anchor elements are invalid in HTML
// They might happen when we have a code block in a heading
// regex aren't perfect for that but this one should be "good enough"
const regex = /<a\s+(?:[^>]*?\s+)?href.*?>(.*?)<\/a>/gi;
const anchorLessText = text.replace(regex, '$1');
// extract the extended markdown heading id
// ex: ## MyHeading {# myId}
// This is recommended in case we end up having duplicate Ids but we still want the same heading text.
// We don't want to make Id generation stateful/too complex to handle duplicates automatically.
const customIdRegex = /{#\s*([\w-]+)\s*}/g;
const customId = customIdRegex.exec(anchorLessText)?.[1];
const link = customId ?? this.getHeaderId(anchorLessText);
const label = anchorLessText.replace(/`(.*?)`/g, '<code>$1</code>').replace(customIdRegex, '');
const customId = customIdRegex.exec(headingText)?.[1];
const link = customId ?? getIdFromHeading(headingText);
// Replace code backticks and remove custom ID syntax from the displayed label
let label = parsedText.replace(/`(.*?)`/g, '<code>$1</code>');
label = label.replace(/{#\s*[\w-]+\s*}/g, '').trim();
const normalizedLabel = label.replace(/<\/?code>/g, '');
return `
@@ -62,3 +67,10 @@ export function getPageTitle(text: string, filePath?: string): string {
}
</div>`;
}
function getIdFromHeading(heading: string): string {
return heading
.toLowerCase()
.replace(/\s|\//g, '-') // replace spaces and slashes with dashes
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
}
@@ -6,20 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Tokens} from 'marked';
import {anchorTarget} from '../helpers.mjs';
import {Renderer, Tokens} from 'marked';
import {AdevDocsRenderer} from '../renderer.mjs';
/**
* Tracks whether the current renderer is inside a link.
*
* This is necessary because nested links are invalid HTML and can cause rendering issues.
*/
let insideLink = false;
export function setInsideLink(value: boolean) {
insideLink = value;
}
export function linkRender(this: AdevDocsRenderer, {href, title, tokens}: Tokens.Link) {
// We have render-time check that we don't create absolute links (which are rendered as external links)
// in our guides
@@ -34,7 +24,13 @@ export function linkRender(this: AdevDocsRenderer, {href, title, tokens}: Tokens
);
}
if (insideLink) {
if (!this.isKnownRoute(href)) {
throw new Error(
`Link target "${href}" in ${this.context.markdownFilePath} does not exist in the defined guide routes.`,
);
}
if (this.context.disableAutoLinking) {
return this.parser.parseInline(tokens);
}
+45 -3
View File
@@ -6,8 +6,11 @@
* found in the LICENSE file at https://angular.dev/license
*/
import ts from 'typescript';
import {HighlighterGeneric, ShikiTransformer} from 'shiki';
import {ApiEntries, getSymbolUrl} from './linking.mjs';
const scanner = ts.createScanner(ts.ScriptTarget.Latest, true);
const LIGHT_THEME = 'github-light';
const DARK_THEME = 'github-dark';
@@ -36,6 +39,7 @@ export function codeToHtml(
highlighter: HighlighterGeneric<any, any>,
code: string,
config: {
apiEntries?: ApiEntries;
language?: string;
highlight?: Set<number>;
},
@@ -48,9 +52,12 @@ export function codeToHtml(
},
cssVariablePrefix: '--shiki-',
defaultColor: false,
transformers: [removeWhitespaceTransformer(), highlightTransformer(config.highlight)],
transformers: [
removeWhitespaceTransformer(),
highlightTransformer(config.highlight),
linkApiEntriesTransformer(config.apiEntries),
],
});
return html;
}
@@ -66,7 +73,7 @@ function highlightTransformer(highlight?: Set<number>): ShikiTransformer {
}
/** A custom transformer which removes all of the whitespace between lines of code in the generated output. */
function removeWhitespaceTransformer(highlight?: Set<number>): ShikiTransformer {
function removeWhitespaceTransformer(): ShikiTransformer {
return {
code(code) {
code.children = code.children.filter(
@@ -75,3 +82,38 @@ function removeWhitespaceTransformer(highlight?: Set<number>): ShikiTransformer
},
};
}
/** A custom transformer which adds a link to local API entries whenever a matching identifier is discovered in the code block. */
function linkApiEntriesTransformer(apiEntries?: ApiEntries): ShikiTransformer {
if (apiEntries === undefined) {
return {};
}
return {
preprocess(code, options) {
options.decorations ??= [];
scanner.setText(code);
let token = scanner.scan();
while (token !== ts.SyntaxKind.EndOfFileToken) {
if (token === ts.SyntaxKind.Identifier) {
const symbolUrl = getSymbolUrl(scanner.getTokenText(), apiEntries);
if (symbolUrl !== undefined) {
options.decorations.push({
transform: (el) => {
el.tagName = 'a';
el.properties['href'] = symbolUrl;
return el;
},
start: scanner.getTokenStart(),
end: scanner.getTokenEnd(),
});
}
}
token = scanner.scan();
}
return code;
},
};
}
@@ -47,7 +47,7 @@
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"assets": ["src/favicon.ico", "src/assets", "src/public"],
"styles": ["src/styles.css"],
"scripts": []
},

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