Compare commits

..

85 Commits

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

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