mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
311 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e14d5eadd5 | |||
| 528a93a5da | |||
| 32a830231e | |||
| 0d1a5b80c2 | |||
| 17cae6ae5f | |||
| 4f65bb34b2 | |||
| eddca4280b | |||
| 175343dfdb | |||
| e0b5078cf2 | |||
| 1e474f7cfa | |||
| 63a857b874 | |||
| c3d69aeaaa | |||
| 684e9fd53d | |||
| ff0af64ced | |||
| bb8cdd9566 | |||
| 17ffa19a2d | |||
| 6c341347b2 | |||
| 8c32f577f1 | |||
| aa5d23799b | |||
| 540536c386 | |||
| f603d4714f | |||
| b72b6b4710 | |||
| 3ed14c6354 | |||
| 236b80b6f9 | |||
| ca5b3c4d3e | |||
| 245bcdd607 | |||
| e2e7211530 | |||
| b351d493ea | |||
| 096a5c2105 | |||
| b1407e1add | |||
| b4a747a94c | |||
| 3ae69406cc | |||
| b52a6264ac | |||
| dc9581469f | |||
| 05d9b97cf9 | |||
| 6aafd87038 | |||
| d4c8a9a887 | |||
| 057cc6d09d | |||
| dea76c7a6e | |||
| a07d64b406 | |||
| a6d6842467 | |||
| 33a30e0e03 | |||
| a385743983 | |||
| c9f8f3afb6 | |||
| 910dcb6d6a | |||
| de12bc7e02 | |||
| a4f312060c | |||
| de533fe491 | |||
| ea1e34c4dd | |||
| 304222014a | |||
| 82192deda9 | |||
| e8265f4593 | |||
| c0496c1f39 | |||
| 2d2ba938da | |||
| ff8911fe52 | |||
| 72d7a47dfd | |||
| 2c6781071f | |||
| e40d378f3e | |||
| d715c366d9 | |||
| c91bd074c0 | |||
| 2ec9ff087f | |||
| 8102331f82 | |||
| 44fc79b7f7 | |||
| 39827a08fe | |||
| 04f5841eed | |||
| 958fc6ab8a | |||
| cbfd1ee571 | |||
| b0dc2fbfcb | |||
| ef204bdf7a | |||
| 4441a95119 | |||
| d150e2672e | |||
| 59f9912dc1 | |||
| bce82bdc34 | |||
| 2ae0912eea | |||
| e583f4c808 | |||
| 05bcc601ac | |||
| 9f61910923 | |||
| 7797671257 | |||
| 0b08e29f26 | |||
| 50761c8be4 | |||
| 8d56b17b95 | |||
| 4ad6d582d2 | |||
| f363ab41f1 | |||
| 0960592d3d | |||
| d04ddd73df | |||
| b682c62873 | |||
| fea25d1a60 | |||
| f916531ee1 | |||
| 8fd896e99a | |||
| 895c576d49 | |||
| 5fd4f0f854 | |||
| 429513bed0 | |||
| f22d9a7098 | |||
| cf7846bd62 | |||
| 9ea8cb6eea | |||
| 3c41e74fdd | |||
| efbba945bc | |||
| 7d54952e85 | |||
| 94b1b530dc | |||
| 0c9c270552 | |||
| 9b48cb2989 | |||
| bba5ed8e64 | |||
| 56c872dacc | |||
| c8902deb00 | |||
| 4b2008d0f2 | |||
| b453c0ce67 | |||
| a9ca90fc11 | |||
| 807cabd9e1 | |||
| 13f050d220 | |||
| d0cf2995a2 | |||
| a35d572b22 | |||
| 80fd08bcc8 | |||
| d8239c7836 | |||
| c1579163b7 | |||
| dbb15ebab2 | |||
| a6c49e0ea9 | |||
| 2c12339ab8 | |||
| a3048f231c | |||
| ae2f2497d3 | |||
| 2c7d3b5704 | |||
| 6bd2121817 | |||
| e8c047bd8d | |||
| a0ca5a50b2 | |||
| 1cdb322600 | |||
| c9585b7159 | |||
| 238b07fe90 | |||
| 3b3ab8b626 | |||
| e731bbdd14 | |||
| c7a18924a6 | |||
| 244e0b0b59 | |||
| 9bfe1a6065 | |||
| b658accf1f | |||
| 9be5e2c211 | |||
| 8818095a82 | |||
| 80c85ec8a5 | |||
| fb7a10fb18 | |||
| 1af0db5742 | |||
| a821e06e33 | |||
| a21be36e15 | |||
| 7aaa8b9b39 | |||
| d0481860bf | |||
| 880a57d4b3 | |||
| 1ba89d1128 | |||
| 831746eff1 | |||
| 9182b62b96 | |||
| 4d2a9394c3 | |||
| ad0156e056 | |||
| aa3220a4b5 | |||
| b4ab6ba2e8 | |||
| 73d6b01b47 | |||
| 3838554503 | |||
| 1148c93834 | |||
| 9f92312cde | |||
| 04b16dee8f | |||
| 57ce2b64ef | |||
| 5393d8ea2c | |||
| 675d4fb489 | |||
| 851ef77318 | |||
| 5800f17e19 | |||
| e19150d2b5 | |||
| 0e496c1d85 | |||
| 737777ef72 | |||
| fa26e7271f | |||
| 076d41c3f6 | |||
| a8f80c1f99 | |||
| bf948be4c2 | |||
| 23ea431c4e | |||
| 2fb767b139 | |||
| b10e305ccc | |||
| f0d19b88b1 | |||
| 67e0ba7e03 | |||
| ca328eab8f | |||
| faeeef678a | |||
| 4d786517d3 | |||
| 26c43d14ba | |||
| 1890c3008b | |||
| cbb2266984 | |||
| ea4847aae5 | |||
| 5fa3052ed1 | |||
| 86718fbbe1 | |||
| de5fbb7deb | |||
| f39896da99 | |||
| 21cab20dae | |||
| 2d28e9b392 | |||
| 5b46a63c08 | |||
| 03fb3cd463 | |||
| 67a73192bf | |||
| e5ed276f27 | |||
| 334ae10168 | |||
| 1f2dc72e8a | |||
| e3b0e5a0b3 | |||
| 10b0dd94e9 | |||
| 6f41efc689 | |||
| bfb8b177ac | |||
| 2feced366a | |||
| 5d755be01c | |||
| 8017617fd9 | |||
| ed2d324f9c | |||
| d1dcd16c5b | |||
| abbd8797bb | |||
| c06e3a0bb4 | |||
| 8ba2844da5 | |||
| 74698d58ec | |||
| a7e3d764e5 | |||
| 7907e982ee | |||
| adda6c5c10 | |||
| e7593653be | |||
| 21b1c3b2ee | |||
| 6c73aaca30 | |||
| c98eab794c | |||
| d45fced21b | |||
| ed48c41f65 | |||
| 75135586d6 | |||
| ff697707ba | |||
| 7d76103999 | |||
| 575f3023c1 | |||
| 4c86ed382d | |||
| 224e60ecb1 | |||
| 492e7560e6 | |||
| 09638ec7c3 | |||
| ab35383285 | |||
| 934e2732b3 | |||
| c20dabda2a | |||
| 62a97f7e4b | |||
| 106cf4ee90 | |||
| 024e307ab0 | |||
| 77b035eca6 | |||
| 4a9b715b3a | |||
| 420aa725ae | |||
| c499c578c9 | |||
| 0588133210 | |||
| 6416a31c1b | |||
| b2b903957a | |||
| bd72571583 | |||
| 628f3f99db | |||
| 79ac570c26 | |||
| 276c12a349 | |||
| 104ac117ea | |||
| c2a74548d9 | |||
| e7d1d8def3 | |||
| 4a390b6c73 | |||
| 5a486a65cf | |||
| c822bf8e76 | |||
| 31e2c99007 | |||
| f8b6bd0cbe | |||
| 5349b027b8 | |||
| 8f8d67dc57 | |||
| 23153a06dd | |||
| fd3b4413f3 | |||
| 670d1660c4 | |||
| 8ca0237d99 | |||
| 3576019c0f | |||
| 05d022d5e6 | |||
| 1df1697c6e | |||
| 05476ea21b | |||
| f38169d0a7 | |||
| 6d7446bc6c | |||
| c25c84d7dc | |||
| f4160c2073 | |||
| 93c6dc6395 | |||
| 76431ed3e3 | |||
| b4ec3cc4e4 | |||
| 84e79f5add | |||
| 2eecf0d9a2 | |||
| da57d1af73 | |||
| 58ea4d2ee8 | |||
| 277ade97ac | |||
| aeb9b819d2 | |||
| 657d532ad1 | |||
| 8dafd2413a | |||
| 796b9a714a | |||
| dd551b1ff3 | |||
| 84adb2fb3b | |||
| 98343ea35e | |||
| c4226a10d0 | |||
| 5e545069ac | |||
| ecf0bb4668 | |||
| 0d54c5f0e2 | |||
| 8f6ca1d9f9 | |||
| 1f51c61372 | |||
| f5e9a862c2 | |||
| 0c8a2858b9 | |||
| a79e8afbad | |||
| 7b7faef739 | |||
| 95afc09206 | |||
| 23cb9a4291 | |||
| 0a863ba03f | |||
| e923d88398 | |||
| 150f0b3c30 | |||
| fc5f9d03cf | |||
| 498d9cdda4 | |||
| fd8d29a157 | |||
| e2e9a9a531 | |||
| 35fd8b567a | |||
| 4e9648d184 | |||
| 54a0541013 | |||
| 9c0587f755 | |||
| ac06941bcf | |||
| 907415c801 | |||
| 225a56677e | |||
| 944181869d | |||
| 3d3e6cbf20 | |||
| 2158bf8bb8 | |||
| 1b46b6db18 | |||
| f23f3e7f5f | |||
| 9414da5b7e | |||
| 8078abe893 | |||
| 661e800302 | |||
| c960a6e492 | |||
| 2eeb4b5666 | |||
| 8f19cf8d84 |
@@ -1 +0,0 @@
|
||||
../.gemini/skills
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
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 Guidelines**: Check if there are specific guidelines for the package being modified in the `reference/` directory (e.g., `reference/router.md`). Always prioritize these rules for their respective packages.
|
||||
- **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.
|
||||
- **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."
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/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
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#!/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}'
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/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!"
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/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"
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
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.
|
||||
+1
-1
@@ -1 +1 @@
|
||||
8.5.1
|
||||
8.6.0
|
||||
|
||||
+2
-1
@@ -7,4 +7,5 @@ code_review:
|
||||
help: false
|
||||
summary: false
|
||||
code_review: false
|
||||
ignore_patterns: []
|
||||
ignore_patterns:
|
||||
- pnpm-lock.yaml
|
||||
|
||||
@@ -10271,7 +10271,7 @@ function isKeyOperator(operator) {
|
||||
function getValues(context3, operator, key, modifier) {
|
||||
var value = context3[key], result = [];
|
||||
if (isDefined(value) && value !== "") {
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
||||
value = value.toString();
|
||||
if (modifier && modifier !== "*") {
|
||||
value = value.substring(0, parseInt(modifier, 10));
|
||||
@@ -10452,6 +10452,125 @@ var endpoint = withDefaults(null, DEFAULTS);
|
||||
//
|
||||
var import_fast_content_type_parse = __toESM(require_fast_content_type_parse());
|
||||
|
||||
//
|
||||
var intRegex = /^-?\d+$/;
|
||||
var noiseValue = /^-?\d+n+$/;
|
||||
var originalStringify = JSON.stringify;
|
||||
var originalParse = JSON.parse;
|
||||
var customFormat = /^-?\d+n$/;
|
||||
var bigIntsStringify = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
var noiseStringify = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
var JSONStringify = (value, replacer, space) => {
|
||||
if ("rawJSON" in JSON) {
|
||||
return originalStringify(
|
||||
value,
|
||||
(key, value2) => {
|
||||
if (typeof value2 === "bigint")
|
||||
return JSON.rawJSON(value2.toString());
|
||||
if (typeof replacer === "function")
|
||||
return replacer(key, value2);
|
||||
if (Array.isArray(replacer) && replacer.includes(key))
|
||||
return value2;
|
||||
return value2;
|
||||
},
|
||||
space
|
||||
);
|
||||
}
|
||||
if (!value)
|
||||
return originalStringify(value, replacer, space);
|
||||
const convertedToCustomJSON = originalStringify(
|
||||
value,
|
||||
(key, value2) => {
|
||||
const isNoise = typeof value2 === "string" && noiseValue.test(value2);
|
||||
if (isNoise)
|
||||
return value2.toString() + "n";
|
||||
if (typeof value2 === "bigint")
|
||||
return value2.toString() + "n";
|
||||
if (typeof replacer === "function")
|
||||
return replacer(key, value2);
|
||||
if (Array.isArray(replacer) && replacer.includes(key))
|
||||
return value2;
|
||||
return value2;
|
||||
},
|
||||
space
|
||||
);
|
||||
const processedJSON = convertedToCustomJSON.replace(
|
||||
bigIntsStringify,
|
||||
"$1$2$3"
|
||||
);
|
||||
const denoisedJSON = processedJSON.replace(noiseStringify, "$1$2$3");
|
||||
return denoisedJSON;
|
||||
};
|
||||
var featureCache = /* @__PURE__ */ new Map();
|
||||
var isContextSourceSupported = () => {
|
||||
const parseFingerprint = JSON.parse.toString();
|
||||
if (featureCache.has(parseFingerprint)) {
|
||||
return featureCache.get(parseFingerprint);
|
||||
}
|
||||
try {
|
||||
const result = JSON.parse(
|
||||
"1",
|
||||
(_, __, context3) => !!context3?.source && context3.source === "1"
|
||||
);
|
||||
featureCache.set(parseFingerprint, result);
|
||||
return result;
|
||||
} catch {
|
||||
featureCache.set(parseFingerprint, false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
var convertMarkedBigIntsReviver = (key, value, context3, userReviver) => {
|
||||
const isCustomFormatBigInt = typeof value === "string" && customFormat.test(value);
|
||||
if (isCustomFormatBigInt)
|
||||
return BigInt(value.slice(0, -1));
|
||||
const isNoiseValue = typeof value === "string" && noiseValue.test(value);
|
||||
if (isNoiseValue)
|
||||
return value.slice(0, -1);
|
||||
if (typeof userReviver !== "function")
|
||||
return value;
|
||||
return userReviver(key, value, context3);
|
||||
};
|
||||
var JSONParseV2 = (text, reviver) => {
|
||||
return JSON.parse(text, (key, value, context3) => {
|
||||
const isBigNumber = typeof value === "number" && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER);
|
||||
const isInt = context3 && intRegex.test(context3.source);
|
||||
const isBigInt = isBigNumber && isInt;
|
||||
if (isBigInt)
|
||||
return BigInt(context3.source);
|
||||
if (typeof reviver !== "function")
|
||||
return value;
|
||||
return reviver(key, value, context3);
|
||||
});
|
||||
};
|
||||
var MAX_INT = Number.MAX_SAFE_INTEGER.toString();
|
||||
var MAX_DIGITS = MAX_INT.length;
|
||||
var stringsOrLargeNumbers = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
|
||||
var noiseValueWithQuotes = /^"-?\d+n+"$/;
|
||||
var JSONParse = (text, reviver) => {
|
||||
if (!text)
|
||||
return originalParse(text, reviver);
|
||||
if (isContextSourceSupported())
|
||||
return JSONParseV2(text, reviver);
|
||||
const serializedData = text.replace(
|
||||
stringsOrLargeNumbers,
|
||||
(text2, digits, fractional, exponential) => {
|
||||
const isString = text2[0] === '"';
|
||||
const isNoise = isString && noiseValueWithQuotes.test(text2);
|
||||
if (isNoise)
|
||||
return text2.substring(0, text2.length - 1) + 'n"';
|
||||
const isFractionalOrExponential = fractional || exponential;
|
||||
const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || digits.length === MAX_DIGITS && digits <= MAX_INT);
|
||||
if (isString || isFractionalOrExponential || isLessThanMaxSafeInt)
|
||||
return text2;
|
||||
return '"' + text2 + 'n"';
|
||||
}
|
||||
);
|
||||
return originalParse(
|
||||
serializedData,
|
||||
(key, value, context3) => convertMarkedBigIntsReviver(key, value, context3, reviver)
|
||||
);
|
||||
};
|
||||
|
||||
//
|
||||
var RequestError = class extends Error {
|
||||
name;
|
||||
@@ -10492,7 +10611,7 @@ var RequestError = class extends Error {
|
||||
};
|
||||
|
||||
//
|
||||
var VERSION2 = "10.0.7";
|
||||
var VERSION2 = "10.0.8";
|
||||
var defaults_default = {
|
||||
headers: {
|
||||
"user-agent": `octokit-request.js/${VERSION2} ${getUserAgent()}`
|
||||
@@ -10519,7 +10638,7 @@ async function fetchWrapper(requestOptions) {
|
||||
}
|
||||
const log = requestOptions.request?.log || console;
|
||||
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
||||
const body = isPlainObject2(requestOptions.body) || Array.isArray(requestOptions.body) ? JSON.stringify(requestOptions.body) : requestOptions.body;
|
||||
const body = isPlainObject2(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
||||
const requestHeaders = Object.fromEntries(
|
||||
Object.entries(requestOptions.headers).map(([name, value]) => [
|
||||
name,
|
||||
@@ -10618,7 +10737,7 @@ async function getResponseData(response) {
|
||||
let text = "";
|
||||
try {
|
||||
text = await response.text();
|
||||
return JSON.parse(text);
|
||||
return JSONParse(text);
|
||||
} catch (err) {
|
||||
return text;
|
||||
}
|
||||
@@ -14135,6 +14254,9 @@ function stripAnsi(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
|
||||
}
|
||||
if (!string.includes("\x1B") && !string.includes("\x9B")) {
|
||||
return string;
|
||||
}
|
||||
return string.replace(regex, "");
|
||||
}
|
||||
var ambiguousRanges = [161, 161, 164, 164, 167, 168, 170, 170, 173, 174, 176, 180, 182, 186, 188, 191, 198, 198, 208, 208, 215, 216, 222, 225, 230, 230, 232, 234, 236, 237, 240, 240, 242, 243, 247, 250, 252, 252, 254, 254, 257, 257, 273, 273, 275, 275, 283, 283, 294, 295, 299, 299, 305, 307, 312, 312, 319, 322, 324, 324, 328, 331, 333, 333, 338, 339, 358, 359, 363, 363, 462, 462, 464, 464, 466, 466, 468, 468, 470, 470, 472, 472, 474, 474, 476, 476, 593, 593, 609, 609, 708, 708, 711, 711, 713, 715, 717, 717, 720, 720, 728, 731, 733, 733, 735, 735, 768, 879, 913, 929, 931, 937, 945, 961, 963, 969, 1025, 1025, 1040, 1103, 1105, 1105, 8208, 8208, 8211, 8214, 8216, 8217, 8220, 8221, 8224, 8226, 8228, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8254, 8254, 8308, 8308, 8319, 8319, 8321, 8324, 8364, 8364, 8451, 8451, 8453, 8453, 8457, 8457, 8467, 8467, 8470, 8470, 8481, 8482, 8486, 8486, 8491, 8491, 8531, 8532, 8539, 8542, 8544, 8555, 8560, 8569, 8585, 8585, 8592, 8601, 8632, 8633, 8658, 8658, 8660, 8660, 8679, 8679, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9449, 9451, 9547, 9552, 9587, 9600, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 9886, 9887, 9919, 9919, 9926, 9933, 9935, 9939, 9941, 9953, 9955, 9955, 9960, 9961, 9963, 9969, 9972, 9972, 9974, 9977, 9979, 9980, 9982, 9983, 10045, 10045, 10102, 10111, 11094, 11097, 12872, 12879, 57344, 63743, 65024, 65039, 65533, 65533, 127232, 127242, 127248, 127277, 127280, 127337, 127344, 127373, 127375, 127376, 127387, 127404, 917760, 917999, 983040, 1048573, 1048576, 1114109];
|
||||
@@ -22856,6 +22978,7 @@ var require_stringify = __commonJS2({
|
||||
nullStr: "null",
|
||||
simpleKeys: false,
|
||||
singleQuote: null,
|
||||
trailingComma: false,
|
||||
trueStr: "true",
|
||||
verifyAliasOrder: true
|
||||
}, doc.schema.toStringOptions, options);
|
||||
@@ -23361,12 +23484,19 @@ ${indent}${line}` : "\n";
|
||||
if (comment)
|
||||
reqNewline = true;
|
||||
let str = stringify.stringify(item, itemCtx, () => comment = null);
|
||||
if (i < items.length - 1)
|
||||
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
|
||||
if (i < items.length - 1) {
|
||||
str += ",";
|
||||
} else if (ctx.options.trailingComma) {
|
||||
if (ctx.options.lineWidth > 0) {
|
||||
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
|
||||
}
|
||||
if (reqNewline) {
|
||||
str += ",";
|
||||
}
|
||||
}
|
||||
if (comment)
|
||||
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
||||
if (!reqNewline && (lines.length > linesAtValue || str.includes("\n")))
|
||||
reqNewline = true;
|
||||
lines.push(str);
|
||||
linesAtValue = lines.length;
|
||||
}
|
||||
@@ -26296,17 +26426,22 @@ var require_compose_node = __commonJS2({
|
||||
case "block-map":
|
||||
case "block-seq":
|
||||
case "flow-collection":
|
||||
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
||||
if (anchor)
|
||||
node.anchor = anchor.source.substring(1);
|
||||
try {
|
||||
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
||||
if (anchor)
|
||||
node.anchor = anchor.source.substring(1);
|
||||
} catch (error2) {
|
||||
const message = error2 instanceof Error ? error2.message : String(error2);
|
||||
onError(token, "RESOURCE_EXHAUSTION", message);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
|
||||
onError(token, "UNEXPECTED_TOKEN", message);
|
||||
node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError);
|
||||
isSrcToken = false;
|
||||
}
|
||||
}
|
||||
node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError));
|
||||
if (anchor && node.anchor === "")
|
||||
onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
|
||||
if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
|
||||
@@ -29043,12 +29178,16 @@ function withDefaults4(oldDefaults, newDefaults) {
|
||||
}
|
||||
var endpoint2 = withDefaults4(null, DEFAULTS2);
|
||||
var import_fast_content_type_parse2 = __toESM2(require_fast_content_type_parse2());
|
||||
var noiseValue = /^-?\d+n+$/;
|
||||
var originalStringify = JSON.stringify;
|
||||
var originalParse = JSON.parse;
|
||||
var JSONStringify = (value, replacer, space) => {
|
||||
var intRegex2 = /^-?\d+$/;
|
||||
var noiseValue2 = /^-?\d+n+$/;
|
||||
var originalStringify2 = JSON.stringify;
|
||||
var originalParse2 = JSON.parse;
|
||||
var customFormat2 = /^-?\d+n$/;
|
||||
var bigIntsStringify2 = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
var noiseStringify2 = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
var JSONStringify2 = (value, replacer, space) => {
|
||||
if ("rawJSON" in JSON) {
|
||||
return originalStringify(
|
||||
return originalStringify2(
|
||||
value,
|
||||
(key, value2) => {
|
||||
if (typeof value2 === "bigint")
|
||||
@@ -29063,13 +29202,11 @@ var JSONStringify = (value, replacer, space) => {
|
||||
);
|
||||
}
|
||||
if (!value)
|
||||
return originalStringify(value, replacer, space);
|
||||
const bigInts = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
const noise = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;
|
||||
const convertedToCustomJSON = originalStringify(
|
||||
return originalStringify2(value, replacer, space);
|
||||
const convertedToCustomJSON = originalStringify2(
|
||||
value,
|
||||
(key, value2) => {
|
||||
const isNoise = typeof value2 === "string" && Boolean(value2.match(noiseValue));
|
||||
const isNoise = typeof value2 === "string" && noiseValue2.test(value2);
|
||||
if (isNoise)
|
||||
return value2.toString() + "n";
|
||||
if (typeof value2 === "bigint")
|
||||
@@ -29082,16 +29219,46 @@ var JSONStringify = (value, replacer, space) => {
|
||||
},
|
||||
space
|
||||
);
|
||||
const processedJSON = convertedToCustomJSON.replace(bigInts, "$1$2$3");
|
||||
const denoisedJSON = processedJSON.replace(noise, "$1$2$3");
|
||||
const processedJSON = convertedToCustomJSON.replace(
|
||||
bigIntsStringify2,
|
||||
"$1$2$3"
|
||||
);
|
||||
const denoisedJSON = processedJSON.replace(noiseStringify2, "$1$2$3");
|
||||
return denoisedJSON;
|
||||
};
|
||||
var isContextSourceSupported = () => JSON.parse("1", (_, __, context3) => !!context3 && context3.source === "1");
|
||||
var JSONParseV2 = (text, reviver) => {
|
||||
const intRegex = /^-?\d+$/;
|
||||
var featureCache2 = /* @__PURE__ */ new Map();
|
||||
var isContextSourceSupported2 = () => {
|
||||
const parseFingerprint = JSON.parse.toString();
|
||||
if (featureCache2.has(parseFingerprint)) {
|
||||
return featureCache2.get(parseFingerprint);
|
||||
}
|
||||
try {
|
||||
const result = JSON.parse(
|
||||
"1",
|
||||
(_, __, context3) => !!context3?.source && context3.source === "1"
|
||||
);
|
||||
featureCache2.set(parseFingerprint, result);
|
||||
return result;
|
||||
} catch {
|
||||
featureCache2.set(parseFingerprint, false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
var convertMarkedBigIntsReviver2 = (key, value, context3, userReviver) => {
|
||||
const isCustomFormatBigInt = typeof value === "string" && customFormat2.test(value);
|
||||
if (isCustomFormatBigInt)
|
||||
return BigInt(value.slice(0, -1));
|
||||
const isNoiseValue = typeof value === "string" && noiseValue2.test(value);
|
||||
if (isNoiseValue)
|
||||
return value.slice(0, -1);
|
||||
if (typeof userReviver !== "function")
|
||||
return value;
|
||||
return userReviver(key, value, context3);
|
||||
};
|
||||
var JSONParseV22 = (text, reviver) => {
|
||||
return JSON.parse(text, (key, value, context3) => {
|
||||
const isBigNumber = typeof value === "number" && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER);
|
||||
const isInt = intRegex.test(context3.source);
|
||||
const isInt = context3 && intRegex2.test(context3.source);
|
||||
const isBigInt = isBigNumber && isInt;
|
||||
if (isBigInt)
|
||||
return BigInt(context3.source);
|
||||
@@ -29100,41 +29267,33 @@ var JSONParseV2 = (text, reviver) => {
|
||||
return reviver(key, value, context3);
|
||||
});
|
||||
};
|
||||
var JSONParse = (text, reviver) => {
|
||||
var MAX_INT2 = Number.MAX_SAFE_INTEGER.toString();
|
||||
var MAX_DIGITS2 = MAX_INT2.length;
|
||||
var stringsOrLargeNumbers2 = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
|
||||
var noiseValueWithQuotes2 = /^"-?\d+n+"$/;
|
||||
var JSONParse2 = (text, reviver) => {
|
||||
if (!text)
|
||||
return originalParse(text, reviver);
|
||||
if (isContextSourceSupported())
|
||||
return JSONParseV2(text, reviver);
|
||||
const MAX_INT = Number.MAX_SAFE_INTEGER.toString();
|
||||
const MAX_DIGITS = MAX_INT.length;
|
||||
const stringsOrLargeNumbers = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
|
||||
const noiseValueWithQuotes = /^"-?\d+n+"$/;
|
||||
const customFormat = /^-?\d+n$/;
|
||||
return originalParse2(text, reviver);
|
||||
if (isContextSourceSupported2())
|
||||
return JSONParseV22(text, reviver);
|
||||
const serializedData = text.replace(
|
||||
stringsOrLargeNumbers,
|
||||
stringsOrLargeNumbers2,
|
||||
(text2, digits, fractional, exponential) => {
|
||||
const isString = text2[0] === '"';
|
||||
const isNoise = isString && Boolean(text2.match(noiseValueWithQuotes));
|
||||
const isNoise = isString && noiseValueWithQuotes2.test(text2);
|
||||
if (isNoise)
|
||||
return text2.substring(0, text2.length - 1) + 'n"';
|
||||
const isFractionalOrExponential = fractional || exponential;
|
||||
const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || digits.length === MAX_DIGITS && digits <= MAX_INT);
|
||||
const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS2 || digits.length === MAX_DIGITS2 && digits <= MAX_INT2);
|
||||
if (isString || isFractionalOrExponential || isLessThanMaxSafeInt)
|
||||
return text2;
|
||||
return '"' + text2 + 'n"';
|
||||
}
|
||||
);
|
||||
return originalParse(serializedData, (key, value, context3) => {
|
||||
const isCustomFormatBigInt = typeof value === "string" && Boolean(value.match(customFormat));
|
||||
if (isCustomFormatBigInt)
|
||||
return BigInt(value.substring(0, value.length - 1));
|
||||
const isNoiseValue = typeof value === "string" && Boolean(value.match(noiseValue));
|
||||
if (isNoiseValue)
|
||||
return value.substring(0, value.length - 1);
|
||||
if (typeof reviver !== "function")
|
||||
return value;
|
||||
return reviver(key, value, context3);
|
||||
});
|
||||
return originalParse2(
|
||||
serializedData,
|
||||
(key, value, context3) => convertMarkedBigIntsReviver2(key, value, context3, reviver)
|
||||
);
|
||||
};
|
||||
var RequestError2 = class extends Error {
|
||||
name;
|
||||
@@ -29200,7 +29359,7 @@ async function fetchWrapper2(requestOptions) {
|
||||
}
|
||||
const log = requestOptions.request?.log || console;
|
||||
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
||||
const body = isPlainObject22(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
||||
const body = isPlainObject22(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify2(requestOptions.body) : requestOptions.body;
|
||||
const requestHeaders = Object.fromEntries(
|
||||
Object.entries(requestOptions.headers).map(([name, value]) => [
|
||||
name,
|
||||
@@ -29299,7 +29458,7 @@ async function getResponseData2(response) {
|
||||
let text = "";
|
||||
try {
|
||||
text = await response.text();
|
||||
return JSONParse(text);
|
||||
return JSONParse2(text);
|
||||
} catch (err) {
|
||||
return text;
|
||||
}
|
||||
@@ -32423,6 +32582,54 @@ var types = (
|
||||
return types2;
|
||||
}()
|
||||
);
|
||||
async function invokeWithRetry(fn, retries = 3, delay = 1e3) {
|
||||
let attempt = 0;
|
||||
while (attempt < retries) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (e) {
|
||||
attempt++;
|
||||
if (attempt >= retries) {
|
||||
throw e;
|
||||
}
|
||||
if (isGithubApiError(e) && e.status < 500) {
|
||||
throw e;
|
||||
}
|
||||
if (e instanceof GraphqlResponseError2) {
|
||||
if (!e.errors) {
|
||||
throw e;
|
||||
}
|
||||
if (e.errors.every((err) => ["NOT_FOUND", "FORBIDDEN", "BAD_USER_INPUT", "UNAUTHENTICATED"].includes(err.type))) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Log.warn(`GitHub API call failed (attempt ${attempt}/${retries}). Retrying in ${delay}ms...`);
|
||||
await new Promise((resolve22) => setTimeout(resolve22, delay));
|
||||
}
|
||||
}
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
function createRetryProxy(target) {
|
||||
return new Proxy(target, {
|
||||
get(targetObj, prop, receiver) {
|
||||
const value = Reflect.get(targetObj, prop, receiver);
|
||||
if (typeof value === "function") {
|
||||
return new Proxy(value, {
|
||||
apply(targetFn, thisArg, argArray) {
|
||||
return invokeWithRetry(() => targetFn.apply(targetObj, argArray));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof value === "object" && value !== null) {
|
||||
return createRetryProxy(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
apply(targetFn, thisArg, argArray) {
|
||||
return invokeWithRetry(() => targetFn.apply(thisArg, argArray));
|
||||
}
|
||||
});
|
||||
}
|
||||
var GithubClient = class {
|
||||
constructor(_octokitOptions) {
|
||||
this._octokitOptions = _octokitOptions;
|
||||
@@ -32435,18 +32642,18 @@ var GithubClient = class {
|
||||
},
|
||||
...this._octokitOptions
|
||||
});
|
||||
this.pulls = this._octokit.pulls;
|
||||
this.orgs = this._octokit.orgs;
|
||||
this.repos = this._octokit.repos;
|
||||
this.issues = this._octokit.issues;
|
||||
this.git = this._octokit.git;
|
||||
this.rateLimit = this._octokit.rateLimit;
|
||||
this.teams = this._octokit.teams;
|
||||
this.search = this._octokit.search;
|
||||
this.rest = this._octokit.rest;
|
||||
this.paginate = this._octokit.paginate;
|
||||
this.checks = this._octokit.checks;
|
||||
this.users = this._octokit.users;
|
||||
this.pulls = createRetryProxy(this._octokit.pulls);
|
||||
this.orgs = createRetryProxy(this._octokit.orgs);
|
||||
this.repos = createRetryProxy(this._octokit.repos);
|
||||
this.issues = createRetryProxy(this._octokit.issues);
|
||||
this.git = createRetryProxy(this._octokit.git);
|
||||
this.rateLimit = createRetryProxy(this._octokit.rateLimit);
|
||||
this.teams = createRetryProxy(this._octokit.teams);
|
||||
this.search = createRetryProxy(this._octokit.search);
|
||||
this.rest = createRetryProxy(this._octokit.rest);
|
||||
this.paginate = createRetryProxy(this._octokit.paginate);
|
||||
this.checks = createRetryProxy(this._octokit.checks);
|
||||
this.users = createRetryProxy(this._octokit.users);
|
||||
}
|
||||
};
|
||||
var AuthenticatedGithubClient = class extends GithubClient {
|
||||
@@ -32458,9 +32665,14 @@ var AuthenticatedGithubClient = class extends GithubClient {
|
||||
});
|
||||
}
|
||||
async graphql(queryObject, params2 = {}) {
|
||||
return await this._graphql(query(queryObject).toString(), params2);
|
||||
return invokeWithRetry(async () => {
|
||||
return await this._graphql(query(queryObject).toString(), params2);
|
||||
});
|
||||
}
|
||||
};
|
||||
function isGithubApiError(obj) {
|
||||
return obj instanceof Error && obj.constructor.name === "RequestError" && obj.request !== void 0;
|
||||
}
|
||||
function isDryRun() {
|
||||
return process.env["DRY_RUN"] !== void 0;
|
||||
}
|
||||
@@ -33174,24 +33386,28 @@ var requiresLabels = createTypedObject(RequiresLabel)({
|
||||
description: "This PR requires a passing TGP before merging is allowed"
|
||||
}
|
||||
});
|
||||
var FeatureLabel = class extends Label {
|
||||
var MiscLabel = class extends Label {
|
||||
};
|
||||
var featureLabels = createTypedObject(FeatureLabel)({
|
||||
FEATURE_IN_BACKLOG: {
|
||||
name: "feature: in backlog",
|
||||
description: "Feature request for which voting has completed and is now in the backlog"
|
||||
var miscLabels = createTypedObject(MiscLabel)({
|
||||
FEATURE: {
|
||||
name: "feature",
|
||||
description: "Label used to distinguish feature request from other issues"
|
||||
},
|
||||
FEATURE_VOTES_REQUIRED: {
|
||||
name: "feature: votes required",
|
||||
description: "Feature request which is currently still in the voting phase"
|
||||
GOOD_FIRST_ISSUE: {
|
||||
name: "good first issue",
|
||||
description: "Label noting a good first issue to be worked on by a community member"
|
||||
},
|
||||
FEATURE_UNDER_CONSIDERATION: {
|
||||
name: "feature: under consideration",
|
||||
description: "Feature request for which voting has completed and the request is now under consideration"
|
||||
HELP_WANTED: {
|
||||
name: "help wanted",
|
||||
description: "Label noting an issue which the team is looking for contribution from the community to fix"
|
||||
},
|
||||
FEATURE_INSUFFICIENT_VOTES: {
|
||||
name: "feature: insufficient votes",
|
||||
description: "Label to add when the not a sufficient number of votes or comments from unique authors"
|
||||
RENOVATE_MANAGED: {
|
||||
name: "renovate managed",
|
||||
description: "Label noting that a pull request will automatically be managed and rebased by renovate"
|
||||
},
|
||||
GEMINI_TRIAGED: {
|
||||
name: "gemini-triaged",
|
||||
description: "Label noting that an issue has been triaged by gemini"
|
||||
}
|
||||
});
|
||||
var allLabels = {
|
||||
@@ -33200,8 +33416,8 @@ var allLabels = {
|
||||
...mergeLabels,
|
||||
...targetLabels,
|
||||
...priorityLabels,
|
||||
...featureLabels,
|
||||
...requiresLabels
|
||||
...requiresLabels,
|
||||
...miscLabels
|
||||
};
|
||||
var import_which = __toESM2(require_lib2());
|
||||
var import_yaml = __toESM2(require_dist());
|
||||
@@ -33379,7 +33595,7 @@ tmp/lib/tmp.js:
|
||||
(* v8 ignore next -- @preserve *)
|
||||
(* v8 ignore else -- @preserve *)
|
||||
|
||||
@angular/ng-dev/bundles/chunk-WPUQDYAV.mjs:
|
||||
@angular/ng-dev/bundles/chunk-YN3IWAKJ.mjs:
|
||||
(*! Bundled license information:
|
||||
|
||||
yargs-parser/build/lib/string-utils.js:
|
||||
@@ -33420,7 +33636,7 @@ tmp/lib/tmp.js:
|
||||
*)
|
||||
*)
|
||||
|
||||
@angular/ng-dev/bundles/chunk-HWMRKQMP.mjs:
|
||||
@angular/ng-dev/bundles/chunk-TUTTLTAK.mjs:
|
||||
(*! Bundled license information:
|
||||
|
||||
@octokit/request-error/dist-src/index.js:
|
||||
|
||||
@@ -5,9 +5,9 @@ runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Starting Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
|
||||
@@ -21,17 +21,17 @@ jobs:
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- 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@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -34,11 +34,11 @@ jobs:
|
||||
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_ref}}
|
||||
ref: ${{steps.comment-branch.outputs.head_sha}}
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
+28
-26
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -32,6 +32,8 @@ 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 ai skills validate
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: pnpm check-tooling-setup
|
||||
|
||||
@@ -39,13 +41,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@e44ee0fa67251ab7e2d854d85b3a466577f47d14 # v7.1.3
|
||||
uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 # v7.1.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -67,11 +69,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -83,11 +85,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -100,11 +102,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -119,11 +121,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -136,11 +138,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -152,11 +154,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -206,11 +208,11 @@ jobs:
|
||||
runs-on: ubuntu-latest-8core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
|
||||
@@ -3,24 +3,36 @@ name: DevInfra
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
pull_request_labels:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/pull-request-labeling@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/labeling/pull-request@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
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: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -13,15 +13,15 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
workflows: ${{ steps.workflows.outputs.workflows }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- id: workflows
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- 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
|
||||
|
||||
+23
-21
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -32,12 +32,14 @@ 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 ai skills validate
|
||||
- name: Check commit message
|
||||
run: pnpm ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
|
||||
- name: Check code format
|
||||
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
|
||||
- name: Check Package Licenses
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
|
||||
|
||||
@@ -45,13 +47,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -59,7 +61,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@e44ee0fa67251ab7e2d854d85b3a466577f47d14 # v7.1.3
|
||||
uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 # v7.1.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -71,11 +73,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run CI tests for framework
|
||||
@@ -95,11 +97,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run integration CI tests for framework
|
||||
@@ -110,11 +112,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -127,11 +129,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -142,11 +144,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@16c5f6e199e3e1b67ae5a62385d36366bb9a429d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: |
|
||||
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@ export const github = {
|
||||
name: 'angular',
|
||||
mainBranchName: 'main',
|
||||
mergeMode: 'caretaker-only',
|
||||
requireReleaseModeForRelease: true,
|
||||
requireReleaseModeForRelease: false,
|
||||
};
|
||||
|
||||
+6
-6
@@ -243,8 +243,8 @@ groups:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'adev/**/{*,.*}',
|
||||
'tools/manual_api_docs/blocks/**/*.md',
|
||||
'tools/manual_api_docs/elements/**/*.md',
|
||||
'tools/manual_api_docs/blocks/*.md',
|
||||
'tools/manual_api_docs/elements/*.md',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
@@ -299,8 +299,8 @@ groups:
|
||||
- >
|
||||
contains_any_globs(files
|
||||
.exclude('.pullapprove.yml')
|
||||
.exclude('tools/manual_api_docs/blocks/**/*.md')
|
||||
.exclude('tools/manual_api_docs/elements/**/*.md'),
|
||||
.exclude('tools/manual_api_docs/blocks/*.md')
|
||||
.exclude('tools/manual_api_docs/elements/*.md'),
|
||||
[
|
||||
'{*,.*}',
|
||||
'.agent/**/{*,.*}',
|
||||
@@ -434,7 +434,7 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- marktechson
|
||||
- MarkTechson
|
||||
- kirjs
|
||||
- ~JeanMeche
|
||||
- ~dgp1130
|
||||
@@ -487,7 +487,7 @@ groups:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
contains_any_globs(files.exclude('packages/core/primitives/**/*spec.ts'), [
|
||||
'packages/core/primitives/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
|
||||
+178
@@ -1,3 +1,181 @@
|
||||
<a name="21.2.9"></a>
|
||||
# 21.2.9 (2026-04-15)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f603d4714f](https://github.com/angular/angular/commit/f603d4714fa184aad34a6f7f9ea4e79c8af3afac) | fix | escape forward slashes in transfer state to prevent crawler indexing |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [540536c386](https://github.com/angular/angular/commit/540536c386f2c735a700c2c9e2697a88dcb3d4ec) | fix | add CSP nonce support to JsonpClientBackend |
|
||||
| [63a857b874](https://github.com/angular/angular/commit/63a857b874172766451aa75ed3347ba50f0ee229) | fix | Don't on Passthru outside of reactive context |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e0b5078cf2](https://github.com/angular/angular/commit/e0b5078cf2ebe79a6de85e9123148ae948b3d81d) | fix | prevent SSRF bypasses via protocol-relative and backslash URLs |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [684e9fd53d](https://github.com/angular/angular/commit/684e9fd53daacb9e910f42d98c6017f9e5cb4180) | fix | normalize multiple leading slashes in URL parser |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.8"></a>
|
||||
# 21.2.8 (2026-04-08)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e40d378f3e](https://github.com/angular/angular/commit/e40d378f3e3e7e57a45c8fbd9565ee06a3a6a13f) | fix | handle nested brackets in host object bindings |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2c6781071f](https://github.com/angular/angular/commit/2c6781071f52d6378a002fba6611bb283fbb2fde) | fix | error for type parameter declarations |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [82192deda9](https://github.com/angular/angular/commit/82192deda9c07113835e6c85af3f2c8c8218cda0) | fix | handle missing serialized container hydration data |
|
||||
| [057cc6d09d](https://github.com/angular/angular/commit/057cc6d09d234f401a810cfdd3ad14127652b88b) | fix | remove obsolete iOS cursor pointer hack in event delegation |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7797671257](https://github.com/angular/angular/commit/7797671257350665e8b3ceb2bc6a0201829dd338) | fix | get quick info at local var location to align with TS semantics and support type narrowing |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.7"></a>
|
||||
# 21.2.7 (2026-04-01)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fea25d1a60](https://github.com/angular/angular/commit/fea25d1a60ecaba1599d9cd9b8df27109ed195c5) | fix | register SVG animation attributes in URL security context ([#67797](https://github.com/angular/angular/pull/67797)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bba5ed8e64](https://github.com/angular/angular/commit/bba5ed8e643b9c3f680e7e539c3d744ad6905e59) | fix | prevent recursive scope checks for invalid NgModule imports |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d04ddd73df](https://github.com/angular/angular/commit/d04ddd73dfc03f420afbdde964c5119f338af135) | fix | prevent binding unsafe attributes on SVG animation elements ([#67797](https://github.com/angular/angular/pull/67797)) |
|
||||
| [8fd896e99a](https://github.com/angular/angular/commit/8fd896e99a13855c6569f29efe7e578c301e13ee) | fix | resolve component import by exact specifier in route lazy-loading schematic |
|
||||
| [b682c62873](https://github.com/angular/angular/commit/b682c628731b86a4884e50abb2f5fa73ac0ad057) | fix | treat `object[data]` as resource URL context ([#67797](https://github.com/angular/angular/pull/67797)) |
|
||||
### localize
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3c41e74fdd](https://github.com/angular/angular/commit/3c41e74fdd279f683156b654699a9312a850add0) | fix | validate locale in getOutputPathFn to prevent path traversal |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0960592d3d](https://github.com/angular/angular/commit/0960592d3d4fad110d5598144fda9f2488520826) | fix | pass outlet context to split to fix empty path named outlets |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.6"></a>
|
||||
# 21.2.6 (2026-03-25)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b4ab6ba2e8](https://github.com/angular/angular/commit/b4ab6ba2e84a18309b0bb5dd68311ff1776b1cb4) | fix | avoid redundant image fetch on destroy with auto sizes |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [880a57d4b3](https://github.com/angular/angular/commit/880a57d4b34af5aa27cd5bee11fa218ade6444bb) | fix | prevent shimCssText from adding extra blank lines per CSS comment |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ad0156e056](https://github.com/angular/angular/commit/ad0156e056e60ffebfeb804fda70dce88d9475a8) | fix | fixes a regression with animate.leave and reordering |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [73d6b01b47](https://github.com/angular/angular/commit/73d6b01b47bb6762d182f1cd891f8ad4d7f688e1) | fix | inject migration not work in multi-project workspace with option path |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.5"></a>
|
||||
# 21.2.5 (2026-03-18)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [334ae10168](https://github.com/angular/angular/commit/334ae10168fdad15cd1390180e2994b4eb65349b) | fix | ensure generated code compiles |
|
||||
| [23ea431c4e](https://github.com/angular/angular/commit/23ea431c4ec45cbb4a7db9839969e7cb23b07f58) | fix | parse named HTML entities containing digits |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [26c43d14ba](https://github.com/angular/angular/commit/26c43d14baad1a6b3629a77825e702a97a4f8482) | fix | escape template literal in TCB |
|
||||
| [67e0ba7e03](https://github.com/angular/angular/commit/67e0ba7e03bb940639f0eafb3af45015e9727eac) | fix | generic types not filled out correctly in type check block |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1890c3008b](https://github.com/angular/angular/commit/1890c3008bbb41b7143b7ede09bed1f7704744fb) | fix | clean up dehydrated views during HMR component replacement |
|
||||
| [bf948be4c2](https://github.com/angular/angular/commit/bf948be4c2c88c604e428cba35e3b9e532bfe5b0) | fix | run linked signal equality check without reactive consumer |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [076d41c3f6](https://github.com/angular/angular/commit/076d41c3f6496eb6c6f84b54e2d2ca85c1b35e64) | fix | prevent trailing comma syntax errors after removing NgStyle |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e19150d2b5](https://github.com/angular/angular/commit/e19150d2b596e87c69bee61f478c3e9c7cbc8f67) | fix | preserve redirect policy on reconstructed asset requests |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.4"></a>
|
||||
# 21.2.4 (2026-03-12)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ed2d324f9c](https://github.com/angular/angular/commit/ed2d324f9cc12aab6cfa0569ef10b73243a62c65) | fix | disallow translations of iframe src |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [abbd8797bb](https://github.com/angular/angular/commit/abbd8797bbd3ae53a10033c39bd895b5b85a4fae) | fix | reverts "feat(core): add support for nested animations" |
|
||||
| [d1dcd16c5b](https://github.com/angular/angular/commit/d1dcd16c5b40291aa3fa2dc84d22842cd657b201) | fix | sanitize translated form attributes |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.3"></a>
|
||||
# 21.2.3 (2026-03-11)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [62a97f7e4b](https://github.com/angular/angular/commit/62a97f7e4b896b4b03a1ef25764db387ffecebe1) | fix | ensure definitions compile |
|
||||
| [21b1c3b2ee](https://github.com/angular/angular/commit/21b1c3b2ee2c8423782b111b93bd60eb6b453259) | fix | include signal debug names in their `toString()` representation |
|
||||
| [224e60ecb1](https://github.com/angular/angular/commit/224e60ecb1b90115baa702f1c06edc1d64d86187) | fix | sanitize translated attribute bindings with interpolations |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.2"></a>
|
||||
# 21.2.2 (2026-03-09)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1df1697c6e](https://github.com/angular/angular/commit/1df1697c6e3a6b1d302f7692b495146943faa12f) | fix | prevent mutation of children array in RecursiveVisitor |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c822bf8e76](https://github.com/angular/angular/commit/c822bf8e76611afde332b6625f5e7bae2fe9c3f3) | fix | always parenthesize object literals in TCB |
|
||||
| [05d022d5e6](https://github.com/angular/angular/commit/05d022d5e61cca7ac90d5b2b2ba3fc738b364ad9) | fix | ignore generated ngDevMode signal branch for code coverage |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [670d1660c4](https://github.com/angular/angular/commit/670d1660c40504e3f55e094c3ebbcccad14163f3) | feat | add 'blur' option to debounce rule |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.1"></a>
|
||||
# 21.2.1 (2026-03-04)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e2e9a9a531](https://github.com/angular/angular/commit/e2e9a9a531c9e9a69701e549f28354cc5d5edd77) | fix | adds transfer cache to httpResource to fix hydration |
|
||||
| [b4ec3cc4e4](https://github.com/angular/angular/commit/b4ec3cc4e41f2948ad0830eb14aa05d14fa3a9ed) | fix | prevent child animation elements from being orphaned |
|
||||
| [e923d88398](https://github.com/angular/angular/commit/e923d8839868c79989502ab3503e13d93c78516a) | fix | Prevent removal of elements during drag and drop |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [277ade97ac](https://github.com/angular/angular/commit/277ade97ac2a3a7f2a5b513acaa93e7663cdc55f) | fix | correctly cache blob responses in transfer cache ([#67002](https://github.com/angular/angular/pull/67002)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.0"></a>
|
||||
# 21.2.0 (2026-02-25)
|
||||
### common
|
||||
|
||||
+2
-2
@@ -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 an 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 a 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 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.
|
||||
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.
|
||||
|
||||
The following documents can help you sort out issues with GitHub accounts and multiple email addresses:
|
||||
|
||||
|
||||
+31
-33
@@ -6,41 +6,41 @@ module(
|
||||
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.3")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.8.5")
|
||||
bazel_dep(name = "aspect_rules_js", version = "2.9.2")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0")
|
||||
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.3")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.8.8")
|
||||
bazel_dep(name = "aspect_rules_js", version = "3.0.3")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.1")
|
||||
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4")
|
||||
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
|
||||
bazel_dep(name = "tar.bzl", version = "0.8.1")
|
||||
bazel_dep(name = "bazel_lib", version = "3.2.2")
|
||||
bazel_dep(name = "tar.bzl", version = "0.10.1")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.5")
|
||||
bazel_dep(name = "rules_angular")
|
||||
git_override(
|
||||
module_name = "rules_angular",
|
||||
commit = "d746c4f75e42cffe389d1ab077f4639be2bc78d1",
|
||||
remote = "https://github.com/devversion/rules_angular.git",
|
||||
commit = "46fdabe25ad865093d1be752c778ca4d81f222f5",
|
||||
remote = "https://github.com/angular/rules_angular.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "devinfra")
|
||||
git_override(
|
||||
module_name = "devinfra",
|
||||
commit = "16c5f6e199e3e1b67ae5a62385d36366bb9a429d",
|
||||
commit = "80659f606f547e99bd08aa15c98479371b3bacab",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_sass")
|
||||
git_override(
|
||||
module_name = "rules_sass",
|
||||
commit = "1184a80751a21af8348f308abc5b38a41f26850e",
|
||||
remote = "https://github.com/devversion/rules_sass.git",
|
||||
commit = "fa129d1ee0652fe0c63ef82d13dc20d4a4e5f919",
|
||||
remote = "https://github.com/angular/rules_sass.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_browsers")
|
||||
git_override(
|
||||
module_name = "rules_browsers",
|
||||
commit = "ceb52751831f3e6249aba83f688a612e57a8d14a",
|
||||
remote = "https://github.com/devversion/rules_browsers.git",
|
||||
commit = "dc3e432662fe9f99eb7f030a35a22d4ff77d54c7",
|
||||
remote = "https://github.com/angular/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 = {
|
||||
"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"),
|
||||
"22.22.2-darwin_arm64": ("node-v22.22.2-darwin-arm64.tar.gz", "node-v22.22.2-darwin-arm64", "db4b275b83736df67533529a18cc55de2549a8329ace6c7bcc68f8d22d3c9000"),
|
||||
"22.22.2-darwin_amd64": ("node-v22.22.2-darwin-x64.tar.gz", "node-v22.22.2-darwin-x64", "12a6abb9c2902cf48a21120da13f87fde1ed1b71a13330712949e8db818708ba"),
|
||||
"22.22.2-linux_arm64": ("node-v22.22.2-linux-arm64.tar.xz", "node-v22.22.2-linux-arm64", "e9e1930fd321a470e29bb68f30318bf58e3ecb4acb4f1533fb19c58328a091fe"),
|
||||
"22.22.2-linux_ppc64le": ("node-v22.22.2-linux-ppc64le.tar.xz", "node-v22.22.2-linux-ppc64le", "14045b5a5030d35ca0030fb7e870bd11a651eb9b57323ebc0021e8d78ac6bac9"),
|
||||
"22.22.2-linux_s390x": ("node-v22.22.2-linux-s390x.tar.xz", "node-v22.22.2-linux-s390x", "9e4a07c291b8949289c6ea8ee61b1d14666a4810feae776a8d1eb1f57e03a2fb"),
|
||||
"22.22.2-linux_amd64": ("node-v22.22.2-linux-x64.tar.xz", "node-v22.22.2-linux-x64", "88fd1ce767091fd8d4a99fdb2356e98c819f93f3b1f8663853a2dee9b438068a"),
|
||||
"22.22.2-windows_amd64": ("node-v22.22.2-win-x64.zip", "node-v22.22.2-win-x64", "7c93e9d92bf68c07182b471aa187e35ee6cd08ef0f24ab060dfff605fcc1c57c"),
|
||||
},
|
||||
node_version = "22.22.0",
|
||||
node_version = "22.22.2",
|
||||
)
|
||||
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 = "10.30.2",
|
||||
pnpm_version_integrity = "sha512-Ns3HB+e3lAqYjJwez4jQhPhRS1w/CF9TouJEwpIdOyVFvCDdTr4fwkX+7EY7spiuzqemPtH3aAuHfcY3nY0MtA==",
|
||||
pnpm_version = "10.33.0",
|
||||
pnpm_version_integrity = "sha512-EFaLtKavtYyes2MNqQzJUWQXq+vT+rvmc58K55VyjaFJHp21pUTHatjrdXD1xLs9bGN7LLQb/c20f6gjyGSTGQ==",
|
||||
)
|
||||
use_repo(pnpm, "pnpm")
|
||||
|
||||
@@ -117,22 +117,20 @@ 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.0-beta | jq -r '.dist.integrity'
|
||||
ts_integrity = "sha512-CldZdztDpQRLM1HC6WDQjQkQN5Ub5zRau737a1diGh3lPmb9oRsaWHk1y5iqK0o7+1bNJ0oXfEGRkAogFZBL+Q==",
|
||||
ts_version = "6.0.0-beta",
|
||||
# Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.1-rc | 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")
|
||||
|
||||
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",
|
||||
rules_angular.setup(
|
||||
name = "angular_rules_angular_configurable_deps",
|
||||
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
|
||||
typescript = "//:node_modules/typescript",
|
||||
)
|
||||
|
||||
override_repo(rules_angular, "rules_angular_configurable_deps")
|
||||
use_repo(rules_angular, rules_angular_configurable_deps = "angular_rules_angular_configurable_deps")
|
||||
|
||||
register_toolchains(
|
||||
"@devinfra//bazel/git-toolchain:git_linux_toolchain",
|
||||
|
||||
Generated
+333
-481
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -5,21 +5,21 @@
|
||||
"@algolia/requester-browser-xhr": "5.48.0",
|
||||
"@algolia/requester-node-http": "5.48.0",
|
||||
"@angular/animations": "workspace:*",
|
||||
"@angular/aria": "21.2.0-rc.0",
|
||||
"@angular/build": "21.2.0-rc.2",
|
||||
"@angular/cdk": "21.2.0-rc.0",
|
||||
"@angular/cli": "21.2.0-rc.2",
|
||||
"@angular/aria": "21.2.6",
|
||||
"@angular/build": "21.2.7",
|
||||
"@angular/cdk": "21.2.6",
|
||||
"@angular/cli": "21.2.7",
|
||||
"@angular/common": "workspace:*",
|
||||
"@angular/compiler": "workspace:*",
|
||||
"@angular/compiler-cli": "workspace:*",
|
||||
"@angular/core": "workspace:*",
|
||||
"@angular/docs": "workspace:*",
|
||||
"@angular/forms": "workspace:*",
|
||||
"@angular/material": "21.2.0-rc.0",
|
||||
"@angular/material": "21.2.6",
|
||||
"@angular/platform-browser": "workspace:*",
|
||||
"@angular/platform-server": "workspace:*",
|
||||
"@angular/router": "workspace:*",
|
||||
"@angular/ssr": "21.2.0-rc.2",
|
||||
"@angular/ssr": "21.2.7",
|
||||
"@codemirror/autocomplete": "6.20.0",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/lang-angular": "0.1.4",
|
||||
@@ -81,7 +81,7 @@
|
||||
"open-in-idx": "0.1.1",
|
||||
"playwright-core": "1.58.1",
|
||||
"preact": "10.28.3",
|
||||
"preact-render-to-string": "6.6.5",
|
||||
"preact-render-to-string": "6.6.6",
|
||||
"prettier": "3.8.1",
|
||||
"property-information": "^7.1.0",
|
||||
"rxjs": "7.8.2",
|
||||
@@ -91,7 +91,7 @@
|
||||
"style-mod": "4.1.3",
|
||||
"tinyglobby": "0.2.15",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "6.0.0-beta",
|
||||
"typescript": "5.9.3",
|
||||
"w3c-keyname": "2.2.8",
|
||||
"zwitch": "^2.0.4"
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
matrixParams: 'ignored',
|
||||
fragment: 'ignored',
|
||||
}"
|
||||
(click)="emitClickOnLink()"
|
||||
(click)="emitClickOnLink(item)"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
|
||||
@@ -153,5 +153,6 @@ describe('NavigationList', () => {
|
||||
class FakeNavigationListState {
|
||||
isOpened = signal(true);
|
||||
activeNavigationItem = signal(navigationItems.at(1));
|
||||
crossCategoryOrigin = signal<NavigationItem | undefined>(undefined);
|
||||
toggleItem(item: NavigationItem) {}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export class NavigationList {
|
||||
readonly linkClicked = output<void>();
|
||||
|
||||
private readonly navigationState = inject(NavigationState);
|
||||
private readonly crossCategoryOrigin = this.navigationState.crossCategoryOrigin;
|
||||
|
||||
readonly activeItem = this.navigationState.activeNavigationItem;
|
||||
|
||||
@@ -51,10 +52,19 @@ export class NavigationList {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const prevParentItem = this.crossCategoryOrigin();
|
||||
if (prevParentItem) {
|
||||
this.crossCategoryOrigin.set(undefined);
|
||||
this.navigationState.toggleItem(prevParentItem);
|
||||
return;
|
||||
}
|
||||
this.navigationState.toggleItem(item);
|
||||
}
|
||||
|
||||
emitClickOnLink(): void {
|
||||
emitClickOnLink(item: NavigationItem): void {
|
||||
if (item.isCrossReferenced) {
|
||||
this.crossCategoryOrigin.set(item.parent);
|
||||
}
|
||||
this.linkClicked.emit();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,16 @@
|
||||
</i>
|
||||
</span>
|
||||
|
||||
<!-- Page title -->
|
||||
<span [innerHtml]="result.labelHtml"></span>
|
||||
@if (result.package) {
|
||||
<span
|
||||
[innerHTML]="result.package"
|
||||
class="docs-search-result__label__package"
|
||||
></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>
|
||||
</p>
|
||||
|
||||
@if (result.subLabelHtml) {
|
||||
|
||||
@@ -46,6 +46,7 @@ dialog {
|
||||
|
||||
.docs-search-result-icon {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
@@ -91,7 +92,20 @@ dialog {
|
||||
|
||||
&__label {
|
||||
font-weight: 600;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
|
||||
&__package {
|
||||
font-size: 0.75rem;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
load("@aspect_bazel_lib//lib:copy_to_bin.bzl", _copy_to_bin = "copy_to_bin")
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", _copy_to_directory = "copy_to_directory")
|
||||
load("@aspect_rules_esbuild//esbuild:defs.bzl", _esbuild = "esbuild")
|
||||
load("@bazel_lib//lib:copy_to_bin.bzl", _copy_to_bin = "copy_to_bin")
|
||||
load("@bazel_lib//lib:copy_to_directory.bzl", _copy_to_directory = "copy_to_directory")
|
||||
load("@devinfra//bazel/private:path_relative_to_label.bzl", _path_relative_to_label = "path_relative_to_label")
|
||||
load(
|
||||
"//tools:defaults.bzl",
|
||||
|
||||
@@ -43,15 +43,6 @@ describe('ClickOutside', () => {
|
||||
|
||||
expect(clickedOutsideSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not docsClickOutside be emitted when user click inside `content` element', () => {
|
||||
const clickedOutsideSpy = spyOn(component, 'clickedOutside');
|
||||
const button = fixture.debugElement.query(By.css('button[id="ignoreThisButton"]'));
|
||||
|
||||
button.nativeElement.click();
|
||||
|
||||
expect(clickedOutsideSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -17,5 +17,6 @@ export interface NavigationItem {
|
||||
contentPath?: string;
|
||||
status?: 'new' | 'updated';
|
||||
category?: string;
|
||||
isCrossReferenced?: boolean;
|
||||
preserveOtherCategoryOrder?: boolean; // true by default
|
||||
}
|
||||
|
||||
@@ -59,10 +59,16 @@ export function ClassMember(props: {member: MemberEntryRenderable}) {
|
||||
const memberName = member.name;
|
||||
const displayName = member.displayName;
|
||||
const returnType = getMemberType(member);
|
||||
const label = displayName ?? memberName;
|
||||
|
||||
return (
|
||||
<div id={memberName} className={REFERENCE_MEMBER_CARD}>
|
||||
<header className={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
<h3>{displayName ?? memberName}</h3>
|
||||
<h3>
|
||||
<a class="docs-anchor" href={'#' + memberName}>
|
||||
{label}
|
||||
</a>
|
||||
</h3>
|
||||
{isClassMethodEntry(member) && member.signatures.length > 1 ? (
|
||||
<span>{member.signatures.length} overloads</span>
|
||||
) : returnType ? (
|
||||
|
||||
@@ -27,14 +27,14 @@ export function CliCommandReference(entry: CliCommandRenderable) {
|
||||
<div className={'shiki line cli'}>
|
||||
ng {commandName(entry, command)}
|
||||
{entry.argumentsLabel && (
|
||||
<button member-id={'Arguments'} className="shiki-ln-line-argument">
|
||||
<span member-id={'Arguments'} className="shiki-ln-line-argument">
|
||||
{entry.argumentsLabel}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{entry.optionsLabel && (
|
||||
<button member-id={'Options'} className="shiki-ln-line-option">
|
||||
<span member-id={'Options'} className="shiki-ln-line-option">
|
||||
{entry.optionsLabel}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</code>
|
||||
|
||||
@@ -13,6 +13,8 @@ import {setSymbols} from '../symbol-context.mjs';
|
||||
import {resolve} from 'path';
|
||||
import {initHighlighter} from '../../../shared/shiki.mjs';
|
||||
import {setHighlighterInstance} from '../shiki/shiki.mjs';
|
||||
import {renderEntry} from '../rendering.mjs';
|
||||
import {JSDOM} from 'jsdom';
|
||||
|
||||
// Note: The tests will probably break if the schema of the api extraction changes.
|
||||
// All entries in the fake-entries are extracted from Angular's api.
|
||||
@@ -60,4 +62,21 @@ describe('renderable', () => {
|
||||
expect(linkedSignal!.experimental).toBe(undefined);
|
||||
expect(linkedSignal!.stable).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should render docs-anchor links in class member card headers', () => {
|
||||
const viewRef = entries.get('ViewRef')!;
|
||||
expect(viewRef).toBeDefined();
|
||||
|
||||
const html = renderEntry(viewRef);
|
||||
const fragment = JSDOM.fragment(html);
|
||||
|
||||
const memberCards = fragment.querySelectorAll('.docs-reference-member-card');
|
||||
expect(memberCards.length).toBeGreaterThan(0);
|
||||
|
||||
for (const card of Array.from(memberCards)) {
|
||||
const id = card.getAttribute('id')!;
|
||||
const anchor = card.querySelector('h3 a.docs-anchor') as HTMLAnchorElement;
|
||||
expect(anchor.getAttribute('href')).toBe(`#${id}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,6 +95,7 @@ export async function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
|
||||
{
|
||||
language: 'typescript',
|
||||
apiEntries: getSymbolsAsApiEntries(),
|
||||
removeWhitespace: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {chromium} from 'playwright-core';
|
||||
import {Mermaid, MermaidConfig} from 'mermaid';
|
||||
import {resolve, join} from 'path';
|
||||
|
||||
const runfilesDir = process.env['RUNFILES']!;
|
||||
const runfilesDir = process.env['JS_BINARY__RUNFILES']!;
|
||||
// Declare mermarid in the context of this file so that typescript doesn't get upset when we
|
||||
// access it within the `page.evaluate` function. At runtime the context in with the method
|
||||
// is run difference than this file, but this makes typescript happy.
|
||||
|
||||
@@ -135,4 +135,29 @@ describe('markdown to html', () => {
|
||||
// We ensure that we still style the heading content
|
||||
expect(markdownDocument.querySelector('strong')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should strip all HTML tags from the aria-label attribute', () => {
|
||||
const markdownDocument = JSDOM.fragment(
|
||||
parseMarkdown(
|
||||
'## **Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)**',
|
||||
rendererContext,
|
||||
),
|
||||
);
|
||||
const h2Anchor = markdownDocument.querySelector('h2 > a');
|
||||
|
||||
expect(h2Anchor!.innerHTML).toBe(
|
||||
'<strong><span class="docs-emoji">Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)</span></strong>',
|
||||
);
|
||||
expect(h2Anchor!.getAttribute('aria-label')).toBe(
|
||||
'Link to Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape double quotes from the aria-label attribute', () => {
|
||||
const markdownDocument = JSDOM.fragment(parseMarkdown('## "Special" heading', rendererContext));
|
||||
const h2Anchor = markdownDocument.querySelector('h2 > a');
|
||||
|
||||
expect(h2Anchor!.innerHTML).toBe('"Special" heading');
|
||||
expect(h2Anchor!.getAttribute('aria-label')).toBe('Link to "Special" heading');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export function headingRender(
|
||||
// Replace code backticks and remove custom ID syntax from the displayed label
|
||||
let label = parsedText.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
label = label.replace(/{#\s*[\w-]+\s*}/g, '').trim();
|
||||
const normalizedLabel = label.replace(/<\/?code>/g, '');
|
||||
const normalizedLabel = label.replace(/<[^>]+>/g, '').replace(/"/g, '"');
|
||||
|
||||
return `
|
||||
<h${depth} id="${link}">
|
||||
|
||||
@@ -42,6 +42,7 @@ export function codeToHtml(
|
||||
apiEntries?: ApiEntries;
|
||||
language?: string;
|
||||
highlight?: Set<number>;
|
||||
removeWhitespace?: boolean;
|
||||
},
|
||||
): string {
|
||||
const html = highlighter.codeToHtml(code, {
|
||||
@@ -53,7 +54,7 @@ export function codeToHtml(
|
||||
cssVariablePrefix: '--shiki-',
|
||||
defaultColor: false,
|
||||
transformers: [
|
||||
removeWhitespaceTransformer(),
|
||||
...(config.removeWhitespace ? [removeWhitespaceTransformer()] : []),
|
||||
highlightTransformer(config.highlight),
|
||||
linkApiEntriesTransformer(config.apiEntries),
|
||||
],
|
||||
|
||||
@@ -21,6 +21,7 @@ export class NavigationState {
|
||||
private readonly _isMobileNavVisible = signal<boolean>(false);
|
||||
private readonly _level = linkedSignal(() => this._expandedItems().length);
|
||||
|
||||
readonly crossCategoryOrigin = signal<NavigationItem | undefined>(undefined);
|
||||
readonly primaryActiveRouteItem = signal<string | null>(null);
|
||||
activeNavigationItem = this._activeNavigationItem.asReadonly();
|
||||
expandedItems = this._expandedItems.asReadonly();
|
||||
|
||||
@@ -426,6 +426,14 @@
|
||||
.docs-ref-content {
|
||||
padding: 1rem 0;
|
||||
|
||||
.docs-ref-option-and-description {
|
||||
.docs-reference-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-block-start: 1px solid var(--senary-contrast);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Card Grid
|
||||
|
||||
@mixin docs-card() {
|
||||
$compact-breakpoint: 550px;
|
||||
|
||||
.docs-card-container-wrapper {
|
||||
border: 1px solid var(--senary-contrast);
|
||||
border-radius: 0.25rem;
|
||||
@@ -15,6 +17,11 @@
|
||||
h2 {
|
||||
padding: 2.5rem 1rem 2.5rem 2.5rem;
|
||||
min-width: 350px;
|
||||
|
||||
@container docs-content (max-width: $compact-breakpoint) {
|
||||
min-width: auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-fill-bg {
|
||||
@@ -57,7 +64,11 @@
|
||||
svg {
|
||||
fill-opacity: 1;
|
||||
|
||||
@container header (max-width: 550px) {
|
||||
@container header (max-width: $compact-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@container docs-content (max-width: $compact-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -71,6 +82,11 @@
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
|
||||
@container docs-content (max-width: $compact-breakpoint) {
|
||||
margin: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.docs-card {
|
||||
margin: 0;
|
||||
|
||||
@@ -252,7 +268,7 @@
|
||||
.docs-nav-card-svg {
|
||||
width: 350px;
|
||||
|
||||
@container header (max-width: 550px) {
|
||||
@container header (max-width: $compact-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,15 @@ $code-font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shiki.line.cli {
|
||||
span {
|
||||
color: var(--quaternary-contrast);
|
||||
font-family: var(--inter-font);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-light-mode .shiki {
|
||||
color: var(--shiki-light);
|
||||
background-color: var(--shiki-light-bg);
|
||||
@@ -46,7 +55,6 @@ $code-font-size: 0.875rem;
|
||||
/* Optional, if you also want font styles */
|
||||
font-style: var(--shiki-light-font-style);
|
||||
font-weight: var(--shiki-light-font-weight);
|
||||
text-decoration: var(--shiki-light-text-decoration);
|
||||
}
|
||||
|
||||
.shiki-ln-line-highlighted,
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
vertical-align: top;
|
||||
min-width: 10ch;
|
||||
}
|
||||
td:has(code) {
|
||||
min-width: 8ch;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-block-end: 1px solid var(--senary-contrast);
|
||||
}
|
||||
|
||||
@@ -41,3 +41,6 @@ export const setCookieConsent = (state: 'denied' | 'granted'): void => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This fixes the RollupError: Exported variable "global" is not defined.
|
||||
export {};
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
provideZonelessChangeDetection,
|
||||
provideEnvironmentInitializer,
|
||||
} from '@angular/core';
|
||||
import {UrlSerializer} from '@angular/router';
|
||||
import {
|
||||
DOCS_CONTENT_LOADER,
|
||||
ENVIRONMENT,
|
||||
@@ -33,6 +34,7 @@ import {CustomErrorHandler} from './core/services/errors-handling/error-handler'
|
||||
import {ExampleContentLoader} from './core/services/example-content-loader.service';
|
||||
import {routerProviders} from './routing/router_providers';
|
||||
import {TYPESCRIPT_VFS_WORKER_PROVIDER} from './editor/code-editor/workers/factory-provider';
|
||||
import {AdevUrlSerializer} from './core/services/routing/adev-url-serializer';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -53,5 +55,9 @@ export const appConfig: ApplicationConfig = {
|
||||
deps: [DOCUMENT],
|
||||
},
|
||||
TYPESCRIPT_VFS_WORKER_PROVIDER,
|
||||
{
|
||||
provide: UrlSerializer,
|
||||
useClass: AdevUrlSerializer,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -95,20 +95,27 @@
|
||||
<div>
|
||||
<h2>Community translations</h2>
|
||||
<ul>
|
||||
<!-- Sorted using Unicode Collation Algorithm (UCA) -->
|
||||
<li>
|
||||
<a href="https://angular.cn/" title="简体中文版">简体中文版</a>
|
||||
<a href="https://angular.az/" title="Azərbaycanca">Azərbaycanca</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://dev.angular.tw/" title="正體中文版">正體中文版</a>
|
||||
<a href="https://angular-docs.tr/" title="Türkçe">Türkçe</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://angular.jp/" title="日本語版">日本語版</a>
|
||||
<a href="https://angular-docs.ru/" title="Русский">Русский</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://angular.kr/" title="한국어">한국어</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://angular-docs.ru/" title="Русский">Русский</a>
|
||||
<a href="https://angular.jp/" title="日本語版">日本語版</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://angular.cn/" title="简体中文版">简体中文版</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://dev.angular.tw/" title="正體中文版">正體中文版</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -436,6 +436,28 @@
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
[href]="ngLinks.STACKOVERFLOW"
|
||||
cdkMenuItem
|
||||
title="Angular Stack Overflow"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<!-- Stack Overflow Icon -->
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 26 31"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m22.18 16.32.05.02-.02.01za19 19 0 0 0-2.45 4.14l-.03.07q-.94 2.19-1.29 4.6v.03a18 18 0 0 0-.05 4.85H.01v-4.88h15.94l.1-.6L.69 20.48l1.28-4.7 15.55 4.1.22-.46-13.96-7.96 2.47-4.22 14.16 8.07.34-.4L9.15 3.47 12.65 0l11.78 11.64 1.25 1.23q-1.97 1.5-3.5 3.44"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
height: 100dvh;
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 2rem;
|
||||
box-sizing: border-box;
|
||||
// Add a subtle border for the home page
|
||||
@@ -95,6 +94,7 @@
|
||||
width: 100%;
|
||||
padding-inline: calc(var(--layout-padding) - 1.25rem);
|
||||
height: auto;
|
||||
scrollbar-width: none;
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
color-mix(in srgb, var(--electric-violet), transparent 70%) 25%,
|
||||
color-mix(in srgb, var(--bright-blue), transparent 60%) 90%
|
||||
);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
opacity: 0.8;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--octonary-contrast);
|
||||
z-index: -1;
|
||||
}
|
||||
.adev-version-button {
|
||||
border: 1px solid var(--quinary-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
&.adev-nav-primary--deprecated {
|
||||
@@ -153,9 +166,11 @@
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-block-start: 1rem;
|
||||
|
||||
@include mq.for-tablet {
|
||||
flex-direction: row;
|
||||
padding-block-start: 0;
|
||||
}
|
||||
|
||||
// version dropdown button
|
||||
@@ -220,7 +235,9 @@
|
||||
|
||||
@include mq.for-tablet {
|
||||
flex-direction: row !important;
|
||||
align-items: center;
|
||||
margin-inline-end: 1.25rem;
|
||||
margin-top: 0.25rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DefaultUrlSerializer, UrlTree} from '@angular/router';
|
||||
|
||||
/**
|
||||
* Custom URL serializer extending the default behavior
|
||||
* with Adev-specific behavior.
|
||||
*/
|
||||
export class AdevUrlSerializer extends DefaultUrlSerializer {
|
||||
override parse(url: string): UrlTree {
|
||||
// Since the app host/server is decoding encoded forward slashes,
|
||||
// we perform this on the client as well in order to maintain
|
||||
// a consistent behavior between the two environments and
|
||||
// avoid opening a different page on client hydration (presumably, 404).
|
||||
url = url.replaceAll(/%2(F|f)/g, '/');
|
||||
|
||||
return super.parse(url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {UrlSerializer} from '@angular/router';
|
||||
import {AdevUrlSerializer} from './adev-url-serializer';
|
||||
|
||||
describe('AdevUrlSerializer', () => {
|
||||
let serializer: UrlSerializer;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: UrlSerializer,
|
||||
useClass: AdevUrlSerializer,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
serializer = TestBed.inject(UrlSerializer);
|
||||
});
|
||||
|
||||
it('should decode encoded forward slash (%2F)', () => {
|
||||
// Uppercase hex
|
||||
expect(serializer.parse('page%2Fabout').toString()).toBe('/page/about');
|
||||
|
||||
// Lowercase hex
|
||||
expect(serializer.parse('page%2fabout').toString()).toBe('/page/about');
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,8 @@ import {NodeRuntimeSandbox} from '../node-runtime-sandbox.service';
|
||||
import {TypingsLoader} from '../typings-loader.service';
|
||||
|
||||
import {FileAndContentRecord} from '@angular/docs';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
import {NodeRuntimeState} from '../node-runtime-state.service';
|
||||
import {CODE_EDITOR_EXTENSIONS} from './constants/code-editor-extensions';
|
||||
import {LANGUAGES} from './constants/code-editor-languages';
|
||||
import {getAutocompleteExtension} from './extensions/autocomplete';
|
||||
@@ -26,10 +28,9 @@ import {getDiagnosticsExtension} from './extensions/diagnostics';
|
||||
import {getTooltipExtension} from './extensions/tooltip';
|
||||
import {DiagnosticsState} from './services/diagnostics-state.service';
|
||||
import {TsVfsWorkerActions} from './workers/enums/actions';
|
||||
import {TYPESCRIPT_VFS_WORKER_FACTORY} from './workers/factory-provider';
|
||||
import {CodeChangeRequest} from './workers/interfaces/code-change-request';
|
||||
import {ActionMessage} from './workers/interfaces/message';
|
||||
import {NodeRuntimeState} from '../node-runtime-state.service';
|
||||
import {TYPESCRIPT_VFS_WORKER_FACTORY} from './workers/factory-provider';
|
||||
|
||||
export interface EditorFile {
|
||||
filename: string;
|
||||
@@ -81,6 +82,7 @@ export class CodeMirrorEditor {
|
||||
private readonly typingsLoader = inject(TypingsLoader);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly diagnosticsState = inject(DiagnosticsState);
|
||||
private readonly domSanitizer = inject(DomSanitizer);
|
||||
private readonly tsVfsWorkerFactory = inject(TYPESCRIPT_VFS_WORKER_FACTORY);
|
||||
private tsVfsWorker: Worker | null = null;
|
||||
|
||||
@@ -448,7 +450,12 @@ export class CodeMirrorEditor {
|
||||
this.sendRequestToTsVfs,
|
||||
this.diagnosticsState,
|
||||
),
|
||||
getTooltipExtension(this.eventManager$, this.currentFile, this.sendRequestToTsVfs),
|
||||
getTooltipExtension(
|
||||
this.eventManager$,
|
||||
this.currentFile,
|
||||
this.sendRequestToTsVfs,
|
||||
this.domSanitizer,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {getMarkedHtmlFromString, getTagsHtml} from './tooltip';
|
||||
|
||||
describe('getMarkedHtmlFromString', () => {
|
||||
it('sanitizes markdown HTML content before assigning to innerHTML', () => {
|
||||
const markdownContent = 'hello <img src=x onerror="alert(1)" />';
|
||||
const domSanitizer = TestBed.inject(DomSanitizer);
|
||||
|
||||
const result = getMarkedHtmlFromString(markdownContent, domSanitizer);
|
||||
|
||||
expect(result.innerHTML.trim()).toBe('<p>hello <img src="x"></p>');
|
||||
expect(result.innerHTML).not.toContain('onerror');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTagsHtml', () => {
|
||||
it('sanitizes JSDoc tag content before assigning to innerHTML', () => {
|
||||
const tags = [
|
||||
{
|
||||
name: 'example',
|
||||
text: [{text: 'hello <img src=x onerror="alert(1)" />'}],
|
||||
},
|
||||
] as any[];
|
||||
const domSanitizer = TestBed.inject(DomSanitizer);
|
||||
|
||||
const result = getTagsHtml(tags, domSanitizer);
|
||||
|
||||
expect(result.innerHTML).toContain('@example');
|
||||
expect(result.innerHTML).not.toContain('onerror');
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Signal} from '@angular/core';
|
||||
import {SecurityContext, Signal} from '@angular/core';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
|
||||
import {Tooltip, hoverTooltip} from '@codemirror/view';
|
||||
import {marked} from 'marked';
|
||||
import {Subject, filter, take} from 'rxjs';
|
||||
@@ -23,6 +25,7 @@ export const getTooltipExtension = (
|
||||
emitter: Subject<ActionMessage<DisplayTooltipResponse>>,
|
||||
currentFile: Signal<EditorFile>,
|
||||
sendRequestToTsVfs: (request: ActionMessage<DisplayTooltipRequest>) => void,
|
||||
domSanitizer: DomSanitizer,
|
||||
) => {
|
||||
return hoverTooltip(
|
||||
async (_, pos: number): Promise<Tooltip | null> => {
|
||||
@@ -58,9 +61,9 @@ export const getTooltipExtension = (
|
||||
|
||||
// use documentation if available as it's more informative than tags
|
||||
if (documentation?.[0]?.text) {
|
||||
tooltip.appendChild(getMarkedHtmlFromString(documentation[0]?.text));
|
||||
tooltip.appendChild(getMarkedHtmlFromString(documentation[0]?.text, domSanitizer));
|
||||
} else if (tags?.length) {
|
||||
tooltip.appendChild(getTagsHtml(tags));
|
||||
tooltip.appendChild(getTagsHtml(tags, domSanitizer));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -91,9 +94,13 @@ function forceTooltipScrollTop() {
|
||||
}
|
||||
}
|
||||
|
||||
function getMarkedHtmlFromString(content: string): HTMLDivElement {
|
||||
export function getMarkedHtmlFromString(
|
||||
content: string,
|
||||
domSanitizer: DomSanitizer,
|
||||
): HTMLDivElement {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked(content) as string;
|
||||
const sanitizedHtml = renderAndSanitizeMarkdownToHtml(content, domSanitizer);
|
||||
wrapper.innerHTML = sanitizedHtml;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
@@ -123,7 +130,7 @@ function getHtmlFromDisplayParts(displayParts: ts.SymbolDisplayPart[]): HTMLDivE
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function getTagsHtml(tags: ts.JSDocTagInfo[]): HTMLDivElement {
|
||||
export function getTagsHtml(tags: ts.JSDocTagInfo[], domSanitizer: DomSanitizer): HTMLDivElement {
|
||||
const tagsWrapper = document.createElement('div');
|
||||
|
||||
let contentString = '';
|
||||
@@ -138,7 +145,14 @@ function getTagsHtml(tags: ts.JSDocTagInfo[]): HTMLDivElement {
|
||||
}
|
||||
}
|
||||
|
||||
tagsWrapper.innerHTML = marked(contentString) as string;
|
||||
const sanitizedHtml = renderAndSanitizeMarkdownToHtml(contentString, domSanitizer);
|
||||
tagsWrapper.innerHTML = sanitizedHtml;
|
||||
|
||||
return tagsWrapper;
|
||||
}
|
||||
|
||||
function renderAndSanitizeMarkdownToHtml(content: string, domSanitizer: DomSanitizer): string {
|
||||
const markedHtml = marked(content) as string;
|
||||
const sanitizedHtml = domSanitizer.sanitize(SecurityContext.HTML, markedHtml) ?? '';
|
||||
return sanitizedHtml;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
<section class="explore-section" id="learn-more">
|
||||
<div class="title">
|
||||
<h2>What to learn more about Angular?</h2>
|
||||
<h2>Want to learn more about Angular?</h2>
|
||||
<div class="pattern"></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,8 +28,12 @@
|
||||
left: calc(var(--layout-padding) + var(--primary-nav-width));
|
||||
z-index: 1;
|
||||
|
||||
@include mq.for-tablet-down {
|
||||
justify-content: flex-start;
|
||||
@include mq.for-tablet-landscape-down {
|
||||
justify-content: center;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
@include mq.for-phone-only {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -182,7 +186,7 @@ section {
|
||||
}
|
||||
|
||||
.search-field {
|
||||
@include mq.for-tablet-down() {
|
||||
@include mq.for-tablet-landscape-down() {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -269,6 +273,11 @@ section {
|
||||
width: fit-content;
|
||||
margin: 0 auto 2rem;
|
||||
|
||||
@include mq.for-phone-only {
|
||||
width: auto;
|
||||
margin: 0 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.tab-background {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
border-radius: 0.25rem;
|
||||
padding: 0;
|
||||
transform: translateY(-0.7rem);
|
||||
background: var(--page-background);
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
|
||||
+60
-28
@@ -25,12 +25,14 @@
|
||||
border-block-end: 1px solid var(--senary-contrast);
|
||||
padding-block-end: 2rem;
|
||||
margin-block-end: 1rem;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.adev-reference-list-type-filter-label {
|
||||
margin-block: 2.5rem 1rem;
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
.adev-reference-list-type-filter {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
margin-block: 0;
|
||||
@@ -44,6 +46,7 @@
|
||||
}
|
||||
@container api-ref-page (max-width: 600px) {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
max-width: 500px;
|
||||
}
|
||||
@container api-ref-page (max-width: 500px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@@ -87,16 +90,70 @@
|
||||
|
||||
.adev-reference-list-query-filter {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
docs-text-field,
|
||||
docs-select {
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
@container api-ref-page (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
docs-text-field,
|
||||
docs-select {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adev-reference-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
|
||||
::ng-deep .mat-mdc-chip-listbox {
|
||||
@container api-ref-page (max-width: 600px) {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
|
||||
.mdc-evolution-chip-set__chips {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mat-mdc-chip-option {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@container api-ref-page (max-width: 350px) {
|
||||
.mdc-evolution-chip-set__chips {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adev-reference-list-empty {
|
||||
text-align: center;
|
||||
margin-block-start: 2rem;
|
||||
flex-basis: 100%;
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
@@ -107,28 +164,3 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.adev-reference-list-empty {
|
||||
flex-basis: 100%;
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-api-item-label-full {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.map-chip-option {
|
||||
min-width: 190px;
|
||||
}
|
||||
|
||||
.adev-reference-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
|
||||
label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="page docs-viewer">
|
||||
<main class="page docs-viewer">
|
||||
<h1 class="page-header" tabindex="-1">Update Guide</h1>
|
||||
<div class="wizard">
|
||||
<div>
|
||||
@@ -205,4 +205,4 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@use '@angular/docs/styles/media-queries' as mq;
|
||||
|
||||
$ver-dropdown-width: 200px;
|
||||
$ver-dropdown-width: clamp(165px, 20vw, 200px);
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
padding: var(--layout-padding) 0px;
|
||||
padding: var(--layout-padding);
|
||||
container: update-guide-page / inline-size;
|
||||
|
||||
.docs-viewer {
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class UpdateComponent {
|
||||
];
|
||||
protected from = this.versions.find((version) => version.name === '20.0')!;
|
||||
protected to = this.versions.find((version) => version.name === '21.0')!;
|
||||
protected futureVersion = 2100;
|
||||
protected futureVersion = 2200;
|
||||
|
||||
protected readonly steps: Step[] = RECOMMENDATIONS;
|
||||
|
||||
|
||||
@@ -496,6 +496,27 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
category: 'Signal Forms',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
label: 'Cross-field logic',
|
||||
path: 'guide/forms/signals/cross-field-logic',
|
||||
contentPath: 'guide/forms/signals/cross-field-logic',
|
||||
category: 'Signal Forms',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
label: 'Form submission',
|
||||
path: 'guide/forms/signals/form-submission',
|
||||
contentPath: 'guide/forms/signals/form-submission',
|
||||
category: 'Signal Forms',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
label: 'Schemas',
|
||||
path: 'guide/forms/signals/schemas',
|
||||
contentPath: 'guide/forms/signals/schemas',
|
||||
category: 'Signal Forms',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
label: 'Async operations',
|
||||
path: 'guide/forms/signals/async-operations',
|
||||
@@ -660,6 +681,7 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/routing/testing',
|
||||
contentPath: 'guide/routing/testing',
|
||||
status: 'new',
|
||||
isCrossReferenced: true,
|
||||
},
|
||||
{
|
||||
label: 'Debugging tests',
|
||||
@@ -863,6 +885,7 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
label: 'Route transition animations',
|
||||
path: 'guide/routing/route-transition-animations',
|
||||
contentPath: 'guide/routing/route-transition-animations',
|
||||
isCrossReferenced: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -875,7 +898,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
{
|
||||
label: 'Build with AI',
|
||||
status: 'new',
|
||||
children: [
|
||||
{
|
||||
label: 'Get Started',
|
||||
@@ -888,9 +910,10 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'ai/develop-with-ai',
|
||||
},
|
||||
{
|
||||
label: 'Design Patterns',
|
||||
path: 'ai/design-patterns',
|
||||
contentPath: 'ai/design-patterns',
|
||||
label: 'Agent Skills',
|
||||
path: 'ai/agent-skills',
|
||||
contentPath: 'ai/agent-skills',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
label: 'Angular CLI MCP Server setup',
|
||||
@@ -902,6 +925,11 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'ai/ai-tutor',
|
||||
contentPath: 'ai/ai-tutor',
|
||||
},
|
||||
{
|
||||
label: 'Design Patterns',
|
||||
path: 'ai/design-patterns',
|
||||
contentPath: 'ai/design-patterns',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1037,16 +1065,15 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
// TODO: create those guides
|
||||
// The signal debugging docs should also be added to the signal section
|
||||
// {
|
||||
// label: 'Signals',
|
||||
// path: 'tools/devtools/signals',
|
||||
// contentPath: 'tools/devtools/signals',
|
||||
// },
|
||||
// {
|
||||
// label: 'Router',
|
||||
// path: 'tools/devtools/router',
|
||||
// contentPath: 'tools/devtools/router',
|
||||
// }
|
||||
{
|
||||
label: 'Router Tree',
|
||||
path: 'tools/devtools/router',
|
||||
contentPath: 'tools/devtools/router',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -128,13 +128,8 @@ export const REDIRECT_ROUTES: Route[] = [
|
||||
redirectTo: '/tools/cli/build-system-migration#hot-module-replacement',
|
||||
},
|
||||
{
|
||||
path: 'guide',
|
||||
children: [
|
||||
{
|
||||
path: 'pipes',
|
||||
redirectTo: '/guide/templates/pipes',
|
||||
},
|
||||
],
|
||||
path: 'guide/pipes',
|
||||
redirectTo: '/guide/templates/pipes',
|
||||
},
|
||||
{
|
||||
path: 'guide/experimental/zoneless',
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,24 @@
|
||||
# Agent Skills
|
||||
|
||||
Agent Skills are specialized, domain-specific instructions and capabilities designed for AI agents like Gemini CLI. These skills provide architectural guidance, generate idiomatic Angular code, and help scaffold new projects using modern best practices.
|
||||
|
||||
By using Agent Skills, you can ensure that the AI agent you are working with has the most up-to-date information about Angular's conventions, reactivity models (like Signals), and project structure.
|
||||
|
||||
## Available Skills
|
||||
|
||||
The Angular team maintains a collection of official skills that are regularly updated to stay in sync with the latest framework improvements.
|
||||
|
||||
| Skill | Description |
|
||||
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`angular-developer`** | Generates Angular code and provides architectural guidance. Useful for creating components, services, or obtaining best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling, testing, or CLI tooling. |
|
||||
| **`angular-new-app`** | Creates a new Angular app using the Angular CLI. Provides important guidelines for effectively setting up and structuring a modern Angular application. |
|
||||
|
||||
## Using Agent Skills
|
||||
|
||||
Agent Skills are designed to be used with agentic coding tools like [Gemini CLI](https://geminicli.com/docs/cli/skills/), [Antigravity](https://antigravity.google/docs/skills) and more. Activating a skill loads the specific instructions and resources needed for that task.
|
||||
|
||||
To use these skills in your own environment you may follow the instructions for your specific tool or use a community tool like [skills.sh](https://skills.sh/).
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/angular/skills
|
||||
```
|
||||
@@ -115,4 +115,5 @@ To learn about LLM prompts and AI IDE setup, see the following guides:
|
||||
|
||||
<docs-pill-row>
|
||||
<docs-pill href="ai/develop-with-ai" title="LLM prompts and IDE setup"/>
|
||||
<docs-pill href="ai/agent-skills" title="Agent Skills"/>
|
||||
</docs-pill-row>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"branchName": "refs/heads/21.2.x",
|
||||
"sha": "a68740532edcf94dafd4a6862a8985f506718016"
|
||||
"sha": "0707c69d63e9962bab077dbc45770c25c8d26b7c"
|
||||
}
|
||||
@@ -44,16 +44,6 @@
|
||||
"description": "A global unique identifier for the panel.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "panelId",
|
||||
"type": "InputSignal<string>",
|
||||
"memberType": "property",
|
||||
"memberTags": [
|
||||
"readonly"
|
||||
],
|
||||
"description": "A local unique identifier for the panel, used to match with its trigger's `panelId`.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "visible",
|
||||
"type": "Signal<boolean>",
|
||||
@@ -165,7 +155,7 @@
|
||||
}
|
||||
],
|
||||
"generics": [],
|
||||
"description": "The content panel of an accordion item that is conditionally visible.\n\nThis directive is a container for the content that is shown or hidden. It requires\na `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`.\nThe content within the panel should be provided using an `ng-template` with the\n`ngAccordionContent` directive so that the content is not rendered on the page until the trigger\nis expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\nits content from assistive technologies when not visible.\n\n```html\n<div ngAccordionPanel panelId=\"unique-id-1\">\n <ng-template ngAccordionContent>\n <p>This content is lazily rendered and will be shown when the panel is expanded.</p>\n </ng-template>\n</div>\n```",
|
||||
"description": "The content panel of an accordion item that is conditionally visible.\n\nThis directive is a container for the content that is shown or hidden. It should\nexpose a template reference that will be used by the corresponding `ngAccordionTrigger`.\nThe content within the panel should be provided using an `ng-template` with the\n`ngAccordionContent` directive so that the content is not rendered on the page until the trigger\nis expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\nits content from assistive technologies when not visible.\n\n```html\n<div ngAccordionPanel #panel=\"ngAccordionPanel\">\n <ng-template ngAccordionContent>\n <p>This content is lazily rendered and will be shown when the panel is expanded.</p>\n </ng-template>\n</div>\n```",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "developerPreview",
|
||||
@@ -176,12 +166,12 @@
|
||||
"comment": "[Accordion](guide/aria/accordion)"
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * The content panel of an accordion item that is conditionally visible.\n *\n * This directive is a container for the content that is shown or hidden. It requires\n * a `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`.\n * The content within the panel should be provided using an `ng-template` with the\n * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger\n * is expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\n * its content from assistive technologies when not visible.\n *\n * ```html\n * <div ngAccordionPanel panelId=\"unique-id-1\">\n * <ng-template ngAccordionContent>\n * <p>This content is lazily rendered and will be shown when the panel is expanded.</p>\n * </ng-template>\n * </div>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"rawComment": "/**\n * The content panel of an accordion item that is conditionally visible.\n *\n * This directive is a container for the content that is shown or hidden. It should\n * expose a template reference that will be used by the corresponding `ngAccordionTrigger`.\n * The content within the panel should be provided using an `ng-template` with the\n * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger\n * is expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\n * its content from assistive technologies when not visible.\n *\n * ```html\n * <div ngAccordionPanel #panel=\"ngAccordionPanel\">\n * <ng-template ngAccordionContent>\n * <p>This content is lazily rendered and will be shown when the panel is expanded.</p>\n * </ng-template>\n * </div>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/src/aria/accordion/accordion-panel.ts",
|
||||
"startLine": 42,
|
||||
"endLine": 103
|
||||
"startLine": 34,
|
||||
"endLine": 88
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -199,6 +189,19 @@
|
||||
"description": "A reference to the trigger element.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "panel",
|
||||
"type": "InputSignal<AccordionPanel>",
|
||||
"memberType": "property",
|
||||
"memberTags": [
|
||||
"readonly",
|
||||
"input"
|
||||
],
|
||||
"description": "The associated AccordionPanel.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "panel",
|
||||
"isRequiredInput": true
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "InputSignal<any>",
|
||||
@@ -207,23 +210,20 @@
|
||||
"readonly",
|
||||
"input"
|
||||
],
|
||||
"description": "A unique identifier for the widget.",
|
||||
"description": "The unique identifier for the trigger.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "id",
|
||||
"isRequiredInput": false
|
||||
},
|
||||
{
|
||||
"name": "panelId",
|
||||
"type": "InputSignal<string>",
|
||||
"type": "Signal<any>",
|
||||
"memberType": "property",
|
||||
"memberTags": [
|
||||
"readonly",
|
||||
"input"
|
||||
"readonly"
|
||||
],
|
||||
"description": "A local unique identifier for the trigger, used to match with its panel's `panelId`.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "panelId",
|
||||
"isRequiredInput": true
|
||||
"description": "The unique identifier for the correspondingtrigger panel.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "disabled",
|
||||
@@ -263,6 +263,39 @@
|
||||
"description": "Whether the trigger is active.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "ngOnInit",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "ngOnInit",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "ngOnInit",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
},
|
||||
{
|
||||
"name": "expand",
|
||||
"signatures": [
|
||||
@@ -364,7 +397,7 @@
|
||||
}
|
||||
],
|
||||
"generics": [],
|
||||
"description": "The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n\nThis directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it\ncontrols. When clicked, it will expand or collapse the panel. It also handles keyboard\ninteractions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n`aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\nThe `disabled` input can be used to disable the trigger.\n\n```html\n<button ngAccordionTrigger panelId=\"unique-id-1\">\n Accordion Trigger Text\n</button>\n```",
|
||||
"description": "The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n\nThis directive requires the `panel` input be set to the template reference of the `ngAccordionPanel`\nit controls. When clicked, it will expand or collapse the panel. It also handles keyboard\ninteractions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n`aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\nThe `disabled` input can be used to disable the trigger.\n\n```html\n<button ngAccordionTrigger [panel]=\"panel\">\n Accordion Trigger Text\n</button>\n```",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "developerPreview",
|
||||
@@ -375,8 +408,10 @@
|
||||
"comment": "[Accordion](guide/aria/accordion)"
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it\n * controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * <button ngAccordionTrigger panelId=\"unique-id-1\">\n * Accordion Trigger Text\n * </button>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"implements": [],
|
||||
"rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires the `panel` input be set to the template reference of the `ngAccordionPanel`\n * it controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * <button ngAccordionTrigger [panel]=\"panel\">\n * Accordion Trigger Text\n * </button>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"implements": [
|
||||
"OnInit"
|
||||
],
|
||||
"isStandalone": true,
|
||||
"selector": "[ngAccordionTrigger]",
|
||||
"exportAs": [
|
||||
@@ -388,7 +423,7 @@
|
||||
"source": {
|
||||
"filePath": "/src/aria/accordion/accordion-trigger.ts",
|
||||
"startLine": 42,
|
||||
"endLine": 107
|
||||
"endLine": 112
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -536,7 +571,7 @@
|
||||
}
|
||||
],
|
||||
"generics": [],
|
||||
"description": "A container for a group of accordion items. It manages the overall state and\ninteractions of the accordion, such as keyboard navigation and expansion mode.\n\nThe `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\ncoordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\nIt supports both single and multiple expansion modes.\n\n```html\n<div ngAccordionGroup [multiExpandable]=\"true\" [(expandedPanels)]=\"expandedItems\">\n <div class=\"accordion-item\">\n <h3>\n <button ngAccordionTrigger panelId=\"item-1\">Item 1</button>\n </h3>\n <div ngAccordionPanel panelId=\"item-1\">\n <ng-template ngAccordionContent>\n <p>Content for Item 1.</p>\n </ng-template>\n </div>\n </div>\n <div class=\"accordion-item\">\n <h3>\n <button ngAccordionTrigger panelId=\"item-2\">Item 2</button>\n </h3>\n <div ngAccordionPanel panelId=\"item-2\">\n <ng-template ngAccordionContent>\n <p>Content for Item 2.</p>\n </ng-template>\n </div>\n </div>\n</div>\n```",
|
||||
"description": "A container for a group of accordion items. It manages the overall state and\ninteractions of the accordion, such as keyboard navigation and expansion mode.\n\nThe `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\ncoordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\nIt supports both single and multiple expansion modes.\n\n```html\n<div ngAccordionGroup [multiExpandable]=\"true\">\n <div class=\"accordion-item\">\n <h3>\n <button ngAccordionTrigger [panel]=\"panel1\">Item 1</button>\n </h3>\n <div ngAccordionPanel #panel1=\"ngAccordionPanel\">\n <ng-template ngAccordionContent>\n <p>Content for Item 1.</p>\n </ng-template>\n </div>\n </div>\n <div class=\"accordion-item\">\n <h3>\n <button ngAccordionTrigger [panel]=\"panel2\">Item 2</button>\n </h3>\n <div ngAccordionPanel #panel2=\"ngAccordionPanel\">\n <ng-template ngAccordionContent>\n <p>Content for Item 2.</p>\n </ng-template>\n </div>\n </div>\n</div>\n```",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "developerPreview",
|
||||
@@ -547,7 +582,7 @@
|
||||
"comment": "[Accordion](guide/aria/accordion)"
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n * <div ngAccordionGroup [multiExpandable]=\"true\" [(expandedPanels)]=\"expandedItems\">\n * <div class=\"accordion-item\">\n * <h3>\n * <button ngAccordionTrigger panelId=\"item-1\">Item 1</button>\n * </h3>\n * <div ngAccordionPanel panelId=\"item-1\">\n * <ng-template ngAccordionContent>\n * <p>Content for Item 1.</p>\n * </ng-template>\n * </div>\n * </div>\n * <div class=\"accordion-item\">\n * <h3>\n * <button ngAccordionTrigger panelId=\"item-2\">Item 2</button>\n * </h3>\n * <div ngAccordionPanel panelId=\"item-2\">\n * <ng-template ngAccordionContent>\n * <p>Content for Item 2.</p>\n * </ng-template>\n * </div>\n * </div>\n * </div>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n * <div ngAccordionGroup [multiExpandable]=\"true\">\n * <div class=\"accordion-item\">\n * <h3>\n * <button ngAccordionTrigger [panel]=\"panel1\">Item 1</button>\n * </h3>\n * <div ngAccordionPanel #panel1=\"ngAccordionPanel\">\n * <ng-template ngAccordionContent>\n * <p>Content for Item 1.</p>\n * </ng-template>\n * </div>\n * </div>\n * <div class=\"accordion-item\">\n * <h3>\n * <button ngAccordionTrigger [panel]=\"panel2\">Item 2</button>\n * </h3>\n * <div ngAccordionPanel #panel2=\"ngAccordionPanel\">\n * <ng-template ngAccordionContent>\n * <p>Content for Item 2.</p>\n * </ng-template>\n * </div>\n * </div>\n * </div>\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
|
||||
"implements": [],
|
||||
"isStandalone": true,
|
||||
"selector": "[ngAccordionGroup]",
|
||||
@@ -559,8 +594,8 @@
|
||||
],
|
||||
"source": {
|
||||
"filePath": "src/aria/accordion/accordion-group.ts",
|
||||
"startLine": 62,
|
||||
"endLine": 158
|
||||
"startLine": 60,
|
||||
"endLine": 120
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -569,28 +604,20 @@
|
||||
"Directive",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"input",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"inject",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"afterRenderEffect",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"signal",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"computed",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"WritableSignal",
|
||||
"inject",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"input",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
@@ -602,17 +629,25 @@
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"model",
|
||||
"OnInit",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"booleanAttribute",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"model",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"contentChildren",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"signal",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"Directionality",
|
||||
"@angular/cdk/bidi"
|
||||
@@ -645,10 +680,6 @@
|
||||
"AccordionPanel.id",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionPanel.panelId",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionPanel.visible",
|
||||
"@angular/aria/accordion"
|
||||
@@ -673,6 +704,10 @@
|
||||
"AccordionTrigger.element",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionTrigger.panel",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionTrigger.id",
|
||||
"@angular/aria/accordion"
|
||||
@@ -693,6 +728,10 @@
|
||||
"AccordionTrigger.active",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionTrigger.ngOnInit",
|
||||
"@angular/aria/accordion"
|
||||
],
|
||||
[
|
||||
"AccordionTrigger.expand",
|
||||
"@angular/aria/accordion"
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
"source": {
|
||||
"filePath": "src/aria/grid/grid-cell-widget.ts",
|
||||
"startLine": 42,
|
||||
"endLine": 134
|
||||
"endLine": 135
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -446,7 +446,7 @@
|
||||
"source": {
|
||||
"filePath": "/src/aria/grid/grid.ts",
|
||||
"startLine": 48,
|
||||
"endLine": 177
|
||||
"endLine": 178
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"branchName": "refs/heads/21.2.x",
|
||||
"sha": "fc8b94d631a5d105a87abc2a008a6e8f76d7f3f5"
|
||||
"sha": "4e2c69bca091e2218fd456afe797826f033a76f2"
|
||||
}
|
||||
@@ -482,19 +482,6 @@
|
||||
"endLine": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CDK_DROP_LIST_GROUP",
|
||||
"type": "any",
|
||||
"entryType": "constant",
|
||||
"rawComment": "/**\n * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\n * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */",
|
||||
"description": "Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\nalternative token to the actual `CdkDropListGroup` class which could cause unnecessary\nretention of the class and its directive metadata.",
|
||||
"jsdocTags": [],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts",
|
||||
"startLine": 16,
|
||||
"endLine": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CdkDragRelease",
|
||||
"entryType": "interface",
|
||||
@@ -533,6 +520,19 @@
|
||||
"endLine": 26
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CDK_DROP_LIST_GROUP",
|
||||
"type": "any",
|
||||
"entryType": "constant",
|
||||
"rawComment": "/**\n * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\n * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */",
|
||||
"description": "Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\nalternative token to the actual `CdkDropListGroup` class which could cause unnecessary\nretention of the class and its directive metadata.",
|
||||
"jsdocTags": [],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts",
|
||||
"startLine": 17,
|
||||
"endLine": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CDK_DRAG_PLACEHOLDER",
|
||||
"type": "any",
|
||||
@@ -559,70 +559,6 @@
|
||||
"endLine": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CdkDropListGroup",
|
||||
"isAbstract": false,
|
||||
"entryType": "undecorated_class",
|
||||
"members": [
|
||||
{
|
||||
"name": "disabled",
|
||||
"type": "boolean",
|
||||
"memberType": "property",
|
||||
"memberTags": [],
|
||||
"description": "Whether starting a dragging sequence from inside this group is disabled.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "ngOnDestroy",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "ngOnDestroy",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "ngOnDestroy",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
}
|
||||
],
|
||||
"generics": [
|
||||
{
|
||||
"name": "T"
|
||||
}
|
||||
],
|
||||
"description": "Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\nelements that are placed inside a `cdkDropListGroup` will be connected to each other\nautomatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\nfrom `cdkDropList`.",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\n * elements that are placed inside a `cdkDropListGroup` will be connected to each other\n * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\n * from `cdkDropList`.\n */",
|
||||
"implements": [
|
||||
"OnDestroy"
|
||||
],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts",
|
||||
"startLine": 26,
|
||||
"endLine": 42
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CdkDragPlaceholder",
|
||||
"isAbstract": false,
|
||||
@@ -706,6 +642,70 @@
|
||||
"endLine": 44
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CdkDropListGroup",
|
||||
"isAbstract": false,
|
||||
"entryType": "undecorated_class",
|
||||
"members": [
|
||||
{
|
||||
"name": "disabled",
|
||||
"type": "boolean",
|
||||
"memberType": "property",
|
||||
"memberTags": [],
|
||||
"description": "Whether starting a dragging sequence from inside this group is disabled.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "ngOnDestroy",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "ngOnDestroy",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "ngOnDestroy",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
}
|
||||
],
|
||||
"generics": [
|
||||
{
|
||||
"name": "T"
|
||||
}
|
||||
],
|
||||
"description": "Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\nelements that are placed inside a `cdkDropListGroup` will be connected to each other\nautomatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\nfrom `cdkDropList`.",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\n * elements that are placed inside a `cdkDropListGroup` will be connected to each other\n * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\n * from `cdkDropList`.\n */",
|
||||
"implements": [
|
||||
"OnDestroy"
|
||||
],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts",
|
||||
"startLine": 27,
|
||||
"endLine": 43
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CdkDragPreview",
|
||||
"isAbstract": false,
|
||||
@@ -6294,11 +6294,11 @@
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"OnDestroy",
|
||||
"Input",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
"Input",
|
||||
"OnDestroy",
|
||||
"@angular/core"
|
||||
],
|
||||
[
|
||||
@@ -6422,11 +6422,11 @@
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CDK_DROP_LIST_GROUP",
|
||||
"CdkDragRelease",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDragRelease",
|
||||
"CDK_DROP_LIST_GROUP",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
@@ -6438,11 +6438,11 @@
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup",
|
||||
"CdkDragPlaceholder",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDragPlaceholder",
|
||||
"CdkDropListGroup",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
@@ -6593,10 +6593,6 @@
|
||||
"DropListOrientation",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CDK_DROP_LIST_GROUP",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDragRelease",
|
||||
"@angular/cdk/drag-drop"
|
||||
@@ -6609,6 +6605,10 @@
|
||||
"CdkDragRelease.event",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CDK_DROP_LIST_GROUP",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CDK_DRAG_PLACEHOLDER",
|
||||
"@angular/cdk/drag-drop"
|
||||
@@ -6617,18 +6617,6 @@
|
||||
"CDK_DRAG_PREVIEW",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup.disabled",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup.ngOnDestroy",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDragPlaceholder",
|
||||
"@angular/cdk/drag-drop"
|
||||
@@ -6645,6 +6633,18 @@
|
||||
"CdkDragPlaceholder.ngOnDestroy",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup.disabled",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDropListGroup.ngOnDestroy",
|
||||
"@angular/cdk/drag-drop"
|
||||
],
|
||||
[
|
||||
"CdkDragPreview",
|
||||
"@angular/cdk/drag-drop"
|
||||
|
||||
@@ -1142,8 +1142,8 @@
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts",
|
||||
"startLine": 35,
|
||||
"endLine": 38
|
||||
"startLine": 34,
|
||||
"endLine": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1194,8 +1194,8 @@
|
||||
"rawComment": "/** Waits for angular to be ready after the page load. */",
|
||||
"source": {
|
||||
"filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts",
|
||||
"startLine": 65,
|
||||
"endLine": 68
|
||||
"startLine": 66,
|
||||
"endLine": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1635,8 +1635,8 @@
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts",
|
||||
"startLine": 71,
|
||||
"endLine": 150
|
||||
"startLine": 72,
|
||||
"endLine": 151
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"branchName": "refs/heads/21.2.x",
|
||||
"sha": "8e9555870edb3275b35d299613ef3cf9d3bfb73a"
|
||||
"sha": "a8aca91bb6d490f66b6bdc873e3e350b9a1addad"
|
||||
}
|
||||
@@ -109,7 +109,7 @@
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"description": "Include the styles for the root component directly within the `app.component.ts` file. Only CSS styles can be included inline. By default, a separate stylesheet file (e.g., `app.component.css`) is created."
|
||||
"description": "Include the styles for the root component directly within the `app.ts` file. Only CSS styles can be included inline. By default, a separate stylesheet file (e.g., `app.css`) is created."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
@@ -117,7 +117,7 @@
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"description": "Include the HTML template for the root component directly within the `app.component.ts` file. By default, a separate template file (e.g., `app.component.html`) is created."
|
||||
"description": "Include the HTML template for the root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created."
|
||||
},
|
||||
{
|
||||
"name": "minimal",
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"description": "Include the styles for the initial application's root component directly within the `app.component.ts` file. By default, a separate stylesheet file (e.g., `app.component.css`) is created."
|
||||
"description": "Include the styles for the initial application's root component directly within the `app.ts` file. By default, a separate stylesheet file (e.g., `app.css`) is created."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
@@ -99,7 +99,7 @@
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"description": "Include the HTML template for the initial application's root component directly within the `app.component.ts` file. By default, a separate template file (e.g., `app.component.html`) is created."
|
||||
"description": "Include the HTML template for the initial application's root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created."
|
||||
},
|
||||
{
|
||||
"name": "interactive",
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
{
|
||||
"name": "coverage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enables coverage reporting for tests."
|
||||
"description": "Enables coverage reporting for tests. If not specified, the coverage configuration from a runner configuration file will be used if present. Otherwise, coverage is disabled by default."
|
||||
},
|
||||
{
|
||||
"name": "coverage-exclude",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"command": "ng update [packages..]",
|
||||
"shortDescription": "Updates your workspace and its dependencies. See https://update.angular.dev/.",
|
||||
"longDescriptionRelativePath": "@angular/cli/src/commands/update/long-description.md",
|
||||
"longDescription": "Perform a basic update to the current stable release of the core framework and CLI by running the following command.\n\n```\nng update @angular/cli @angular/core\n```\n\nTo update to the next beta or pre-release version, use the `--next` option.\n\nTo update from one major version to another, use the format\n\n```\nng update @angular/cli@^<major_version> @angular/core@^<major_version>\n```\n\nWe recommend that you always update to the latest patch version, as it contains fixes we released since the initial major release.\nFor example, use the following command to take the latest 10.x.x version and use that to update.\n\n```\nng update @angular/cli@^10 @angular/core@^10\n```\n\nFor detailed information and guidance on updating your application, see the interactive [Angular Update Guide](https://update.angular.dev/).\n",
|
||||
"longDescription": "Perform a basic update to the current stable release of the core framework and CLI by running the following command.\n\n```\nng update @angular/cli @angular/core\n```\n\nTo update to the next beta or pre-release version, use the `--next` option.\n\nTo update from one major version to another, use the format\n\n```\nng update @angular/cli@^<major_version> @angular/core@^<major_version>\n```\n\nWe recommend that you always update to the latest patch version, as it contains fixes we released since the initial major release.\nFor example, use the following command to take the latest 21.x.x version and use that to update.\n\n```\nng update @angular/cli@^21 @angular/core@^21\n```\n\nFor detailed information and guidance on updating your application, see the interactive [Angular Update Guide](/update-guide).\n",
|
||||
"aliases": [],
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
|
||||
@@ -10,7 +10,7 @@ There are some niche use cases when you may want to maintain a custom build pipe
|
||||
|
||||
- You have an existing app using a different toolchain and you’d like to add Angular to it
|
||||
- You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
|
||||
- You’d like to create an short-lived experiment using your favorite build tool
|
||||
- You’d like to create a short-lived experiment using your favorite build tool
|
||||
|
||||
## What are the options?
|
||||
|
||||
|
||||
@@ -22,16 +22,4 @@ Angular v21 is being delivered to you as a brand new release adventure. With mod
|
||||
- Your first look at Signal Forms, our new streamlined, signal-based approach to forms in Angular
|
||||
- Exciting new details about the Angular Aria package
|
||||
|
||||
<div style="display: block; width: 80%; margin: 0 auto; margin-top: 20px;">
|
||||
<iframe
|
||||
credentialless
|
||||
width="560"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/DDAHORVzQ5g?si=B9Uv5vXzIKJfcLGr"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
<docs-video src="https://www.youtube.com/embed/DDAHORVzQ5g?si=B9Uv5vXzIKJfcLGr" title="Angular v21 Developer Event"/>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<!-- #docplaster -->
|
||||
<h2><code>animate.leave</code> Parent Sub-tree No Animation Example</h2>
|
||||
<h2><code>animate.leave</code> Parent Sub-tree Animation Example</h2>
|
||||
|
||||
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
|
||||
|
||||
@if (isShown()) {
|
||||
<!-- the child node with `animate.leave` will not animate out because the
|
||||
parent node removal happens first and will take the child dom tree
|
||||
with it. -->
|
||||
<div class="leave-parent">
|
||||
<!-- the child node with `animate.leave` will animate first
|
||||
before the parent node is removed from the DOM. -->
|
||||
<div class="leave-container" animate.leave="leaving">
|
||||
<p>Goodbye</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ngAccordionGroup class="basic-accordion">
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
<span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
Which attribute tells assistive tech whether the panel is open or closed?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -9,7 +9,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q1">
|
||||
<div ngAccordionPanel #panel1="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled>
|
||||
<span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
|
||||
How do you link the button to the content it controls?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -29,7 +29,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q2">
|
||||
<div ngAccordionPanel #panel2="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use the <code>aria-controls</code> attribute on the button, and set its value to match the
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger">
|
||||
<span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
|
||||
What role should the heading element containing the accordion button have?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -49,7 +49,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q3">
|
||||
<div ngAccordionPanel #panel3="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
The element containing the button should typically have <code>role="heading"</code> with an
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
<div ngAccordionGroup class="material-accordion">
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
<span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
Which attribute tells assistive tech whether the panel is open or closed?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -11,7 +11,7 @@
|
||||
>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q1">
|
||||
<div ngAccordionPanel #panel1="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled>
|
||||
<span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
|
||||
How do you link the button to the content it controls?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -33,7 +33,7 @@
|
||||
>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q2">
|
||||
<div ngAccordionPanel #panel2="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use the <code>aria-controls</code> attribute on the button, and set its value to match the
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger">
|
||||
<span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
|
||||
What role should the heading element containing the accordion button have?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -55,7 +55,7 @@
|
||||
>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q3">
|
||||
<div ngAccordionPanel #panel3="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
The element containing the button should typically have <code>role="heading"</code> with an
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<div ngAccordionGroup class="retro-accordion">
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger">
|
||||
<span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger">
|
||||
Unlock Treasure Box
|
||||
<span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
|
||||
trigger1.expanded() ? 'lock_open_right' : 'lock'
|
||||
}}</span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q1">
|
||||
<div ngAccordionPanel #panel1="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<div class="treasure-box">👻</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled>
|
||||
<span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
|
||||
Unlock Treasure Box
|
||||
<span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
|
||||
trigger2.expanded() ? 'lock_open_right' : 'lock'
|
||||
}}</span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q2">
|
||||
<div ngAccordionPanel #panel2="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<div class="treasure-box">💎💎💎</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger" [expanded]="true">
|
||||
<span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true">
|
||||
Unlock Treasure Box
|
||||
<span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
|
||||
trigger3.expanded() ? 'lock_open_right' : 'lock'
|
||||
}}</span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q3">
|
||||
<div ngAccordionPanel #panel3="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<div class="treasure-box">🐸 ribbit...ribbit...</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ngAccordionGroup class="basic-accordion">
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
<span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
|
||||
Which attribute tells assistive tech whether the panel is open or closed?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -9,7 +9,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q1">
|
||||
<div ngAccordionPanel #panel1="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger">
|
||||
<span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
|
||||
How do you link the button to the content it controls?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -29,7 +29,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q2">
|
||||
<div ngAccordionPanel #panel2="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
Use the <code>aria-controls</code> attribute on the button, and set its value to match the
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger" [expanded]="true">
|
||||
<span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true">
|
||||
What role should the heading element containing the accordion button have?
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -49,7 +49,7 @@
|
||||
></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div ngAccordionPanel panelId="q3">
|
||||
<div ngAccordionPanel #panel3="ngAccordionPanel">
|
||||
<ng-template ngAccordionContent>
|
||||
<p>
|
||||
The element containing the button should typically have <code>role="heading"</code> with an
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user