mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f0d29d92a | |||
| 290b2814e1 | |||
| 9f99b14882 | |||
| 3de87e1eda | |||
| affd2362eb | |||
| 11657fb0b1 | |||
| 7242da24fb | |||
| 31e6f0d7dc | |||
| 6601f06e44 | |||
| fe69342545 | |||
| c57b0355b5 | |||
| 06952b4f0f | |||
| c133a2c1eb | |||
| 3151bc38a0 | |||
| 49e3c25d62 | |||
| b4227fb3f1 | |||
| 89525ec6b7 | |||
| 1234e873d8 | |||
| 359470979b | |||
| 27c7b60d81 | |||
| 72ed76feb3 | |||
| 278cfd3879 | |||
| 5903bf7c98 | |||
| 051ba061a0 | |||
| 01b035ab03 | |||
| 4742c703df | |||
| 21ecdc036a | |||
| 2499238c59 | |||
| e6ea328cf4 | |||
| b203ab0881 | |||
| f7eb996ad5 | |||
| d9e7ab3db6 | |||
| 458e21e0fe | |||
| e4c9ff7a34 | |||
| c9043fb876 | |||
| 61dbdee6fb | |||
| 27eeff9be6 | |||
| f577f8cb5c | |||
| 643f0f4b22 | |||
| ad9e473e73 | |||
| 18842266de | |||
| 223ff74891 | |||
| 0e1f1ed573 | |||
| b9c995e7d3 | |||
| 0d0a86be29 | |||
| 8078739e62 | |||
| 60aceefc75 | |||
| 696e827391 | |||
| 6a5620442f | |||
| 62d847325d | |||
| 3c8d69794c | |||
| 93f3cca5e9 | |||
| ea70b003b0 | |||
| 5d1b950324 | |||
| e6642496b0 | |||
| 26c2fe6db2 | |||
| e828f1c765 | |||
| 01e98ded2e | |||
| 4a1dcb2d82 | |||
| 58720dbc56 | |||
| 9cc0f4d003 | |||
| 82593de5a4 | |||
| 068bb9d3c5 | |||
| eb6af58446 | |||
| 760e2aaa79 | |||
| 85cf7f0daa | |||
| f0bcf876ec | |||
| 6b34e5f444 | |||
| ac50dbc8e1 | |||
| 32ad1e09b4 | |||
| cb75f9ce85 | |||
| ded654dc6b | |||
| 05adfcf8f2 | |||
| aa39f87021 | |||
| 0643f541f9 | |||
| d89a80a970 | |||
| 0f3f346ec4 | |||
| 74017eb355 | |||
| 72877b6c60 | |||
| c0446da35f | |||
| ae4e2e02d0 | |||
| 7f9990710e | |||
| 6027c9128c | |||
| b108ece707 | |||
| 3cc549c723 | |||
| e817f8c830 | |||
| 1ea5c97703 | |||
| d81f8ea0f6 | |||
| 654ed6512b | |||
| 134742fb7b | |||
| 5594a535c1 | |||
| 5097df84b4 | |||
| c4784bd39e | |||
| d576ea577d | |||
| 230b65cc81 | |||
| 6d3b09326a | |||
| 9a764e0f5f | |||
| 9c1a5a5433 | |||
| a649fc8f57 | |||
| 8f16846dd9 | |||
| 114d03f6a6 | |||
| bf5db2b8d3 | |||
| c8b1b15143 | |||
| 559797c19d | |||
| 33ea12a797 | |||
| 1dd06f5e5c | |||
| 76a6984d7b | |||
| f5ed514b41 | |||
| 3396943fd0 | |||
| eecbdc078e | |||
| 40864e85da | |||
| b1c68e66a7 | |||
| 757e0cfd26 | |||
| 532633301b | |||
| 8474277b49 | |||
| 5ad2e1bae0 | |||
| ab86c4dfaf | |||
| 0587bb77ad | |||
| 79f979a673 | |||
| cc066cb31f | |||
| 3932bab633 | |||
| e66a8ef0da | |||
| ed5b62a140 | |||
| 1f47e885fd | |||
| d1f6bcdacd | |||
| ed292e398a | |||
| 3954dc278b | |||
| 8cf3a72d57 | |||
| 481557e69f | |||
| 13a7ee8424 | |||
| fd3253e093 | |||
| addccead9b | |||
| 8f451b5647 | |||
| e4dcd72926 | |||
| 204aacadb4 | |||
| e9cb3cbefb | |||
| 4871131136 | |||
| fb21801827 | |||
| 3de709f41b | |||
| 47d89c8ca2 | |||
| c7d6053797 | |||
| 03e2b36826 | |||
| 29f074a93f | |||
| 62d7533ca6 | |||
| a59f51f0e6 | |||
| a201d33e0c | |||
| 0875deab8d | |||
| dd37fd821e | |||
| 7b396e8bb2 | |||
| 24b0920755 | |||
| f3a7b3ab67 | |||
| 1c055aa268 | |||
| 0c23d02944 | |||
| 83bac5adc9 | |||
| e01dcae4c5 | |||
| 4cdf4d5760 | |||
| f41d721ce0 | |||
| 0fd8477f3f | |||
| 5dd2f2bb98 | |||
| ca57165c7e | |||
| 4391c44801 | |||
| 985bdeefd3 | |||
| 95bc06d6c3 | |||
| 2c3a067bb6 | |||
| 2c74475969 | |||
| 74af7d85e5 | |||
| 6ad2ef6bd8 |
@@ -1 +0,0 @@
|
||||
../../AGENTS.md
|
||||
@@ -1,178 +0,0 @@
|
||||
---
|
||||
name: adev-writing-guide
|
||||
description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. You MUST use this skill any time you plan to create, edit, or review documentation files in `adev/` or `adev/src/content`.
|
||||
---
|
||||
|
||||
# Angular Documentation (adev) Writing Guide
|
||||
|
||||
This skill provides comprehensive guidelines for authoring content in `adev/src/content`. It combines Google's technical writing standards with Angular-specific markdown conventions, components, and best practices.
|
||||
|
||||
## I. Google Technical Writing Guidelines
|
||||
|
||||
### Tone and Content
|
||||
|
||||
- **Be conversational and friendly:** Maintain a helpful yet professional tone. Avoid being overly casual.
|
||||
- **Write accessibly:** Ensure documentation is understandable to a diverse global audience, including non-native English speakers.
|
||||
- **Audience-first:** Focus on what the user needs to do, not just what the system does.
|
||||
- **Avoid pre-announcing:** Do not mention unreleased features or make unsupported claims.
|
||||
- **Use descriptive link text:** Link text should clearly indicate the destination (e.g., avoid "click here").
|
||||
|
||||
### Language and Grammar
|
||||
|
||||
- **Use second person ("you"):** Address the reader directly.
|
||||
- **Prefer active voice:** Clearly state who or what is performing the action (e.g., "The system generates a token" vs "A token is generated").
|
||||
- **Standard American English:** Use standard American spelling and punctuation.
|
||||
- **Conditional clauses first:** Place "if" or "when" clauses before the instruction (e.g., "If you encounter an error, check the logs").
|
||||
- **Define terms:** Introduce new or unfamiliar terms/acronyms upon first use.
|
||||
- **Consistent terminology:** Use the same term for the same concept throughout the document.
|
||||
- **Conciseness:** Aim for one idea per sentence. Keep sentences short.
|
||||
|
||||
### Formatting and Organization
|
||||
|
||||
- **Sentence case for headings:** Capitalize only the first word and proper nouns in titles and headings.
|
||||
- **Lists:**
|
||||
- **Numbered lists:** Use for sequential steps or prioritized items.
|
||||
- **Bulleted lists:** Use for unordered collections of items.
|
||||
- **Description lists:** Use for term-definition pairs.
|
||||
- **Serial commas:** Use the Oxford comma (comma before the last item in a list of three or more).
|
||||
- **Code formatting:** Use code font for code-related text (filenames, variables, commands).
|
||||
- **UI Elements:** formatting user interface elements in **bold**.
|
||||
- **Date formatting:** Use unambiguous formats (e.g., "September 4, 2024" rather than "9/4/2024").
|
||||
- **Structure:** Use logical hierarchy with clear introductions and navigation. Headings should be task-based where possible.
|
||||
|
||||
### Images and Code Samples
|
||||
|
||||
- **Images:** Use simple, clear illustrations to enhance understanding.
|
||||
- **Captions:** Write captions that support the image.
|
||||
- **Code Samples:**
|
||||
- Ensure code is correct and builds without errors.
|
||||
- Follow language-specific conventions.
|
||||
- **Comments:** Focus on _why_, not _what_. Avoid commenting on obvious code.
|
||||
|
||||
### Reference Hierarchy
|
||||
|
||||
1. Project-specific style guidelines (if any exist in `CONTRIBUTING.md` or similar).
|
||||
2. Google Developer Documentation Style Guide.
|
||||
3. Merriam-Webster (spelling).
|
||||
4. Chicago Manual of Style (non-technical).
|
||||
5. Microsoft Writing Style Guide (technical).
|
||||
|
||||
---
|
||||
|
||||
## II. Angular Documentation Specifics
|
||||
|
||||
### Code Blocks
|
||||
|
||||
Use the appropriate language identifier for syntax highlighting:
|
||||
|
||||
- **TypeScript (Angular):** Use `angular-ts` when TypeScript code examples contain inline templates.
|
||||
- **HTML (Angular):** Use `angular-html` for Angular templates.
|
||||
- **TypeScript (Generic):** Use `ts` for plain TypeScript.
|
||||
- **HTML (Generic):** Use `html` for plain HTML.
|
||||
- **Shell/Terminal:** Use `shell` or `bash`.
|
||||
- **Mermaid Diagrams:** Use `mermaid`.
|
||||
|
||||
#### Attributes
|
||||
|
||||
You can enhance code blocks with attributes in curly braces `{}` after the language identifier:
|
||||
|
||||
- `header="Title"`: Adds a title to the code block.
|
||||
- `linenums`: Enables line numbering.
|
||||
- `highlight="[1, 3-5]"`: Highlights specific lines.
|
||||
- `hideCopy`: Hides the copy button.
|
||||
- `prefer`: Marks code as a preferred example (green border/check).
|
||||
- `avoid`: Marks code as an example to avoid (red border/cross).
|
||||
|
||||
**Example:**
|
||||
|
||||
````markdown
|
||||
```angular-ts {header:"My Component", linenums, highlight="[2]"}
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<h1>Hello</h1>',
|
||||
})
|
||||
export class App {}
|
||||
```
|
||||
````
|
||||
|
||||
#### `<docs-code>` Component
|
||||
|
||||
For more advanced code block features, use the `<docs-code>` component:
|
||||
|
||||
- `path`: Path to a source file (e.g., `adev/src/content/examples/...`).
|
||||
- `header`: Custom header text.
|
||||
- `language`: Language identifier (e.g., `angular-ts`).
|
||||
- `linenums`: Boolean attribute.
|
||||
- `highlight`: Array of line numbers/ranges (e.g., `[[3,7], 9]`).
|
||||
- `diff`: Path to diff file.
|
||||
- `visibleLines`: Range of lines to show initially (collapsible).
|
||||
- `region`: Region to extract from source file.
|
||||
- `preview`: Boolean. Renders a live preview (StackBlitz). _Only works with standalone examples._
|
||||
- `hideCode`: Boolean. Collapses code by default.
|
||||
|
||||
**Multifile Example:**
|
||||
|
||||
```html
|
||||
<docs-code-multifile path="..." preview>
|
||||
<docs-code path="..." />
|
||||
<docs-code path="..." />
|
||||
</docs-code-multifile>
|
||||
```
|
||||
|
||||
### Alerts / Admonitions
|
||||
|
||||
Use specific keywords followed by a colon for alerts. These render as styled blocks.
|
||||
|
||||
- `NOTE:` For ancillary information.
|
||||
- `TIP:` For helpful hints or shortcuts.
|
||||
- `IMPORTANT:` For crucial information.
|
||||
- `CRITICAL:` For warnings about potential data loss or severe issues.
|
||||
- `TODO`: For incomplete documentation.
|
||||
- `QUESTION:` To pose a question to the reader.
|
||||
- `SUMMARY:` For section summaries.
|
||||
- `TLDR:` For concise summaries.
|
||||
- `HELPFUL:` For best practices.
|
||||
|
||||
**Example:**
|
||||
|
||||
```markdown
|
||||
TIP: Use `ng serve` to run your application locally.
|
||||
```
|
||||
|
||||
### Custom Components
|
||||
|
||||
- **Cards (`<docs-card>`):**
|
||||
- Must be inside `<docs-card-container>`.
|
||||
- Attributes: `title`, `link`, `href`.
|
||||
- **Callouts (`<docs-callout>`):**
|
||||
- Attributes: `title`, `important`, `critical`.
|
||||
- **Pills (`<docs-pill>`):**
|
||||
- Must be inside `<docs-pill-row>`.
|
||||
- Attributes: `title`, `href`.
|
||||
- **Steps / Workflow (`<docs-step>`):**
|
||||
- Must be inside `<docs-workflow>`.
|
||||
- Attributes: `title`.
|
||||
- **Tabs (`<docs-tab>`):**
|
||||
- Must be inside `<docs-tab-group>`.
|
||||
- Attributes: `label`.
|
||||
- **Videos (`<docs-video>`):**
|
||||
- Attributes: `src` (YouTube embed URL), `alt`.
|
||||
|
||||
### Images
|
||||
|
||||
Use standard markdown syntax with optional attributes for sizing and loading behavior.
|
||||
|
||||
- `#small`, `#medium`: Append to image URL for sizing.
|
||||
- `{loading: 'lazy'}`: Add attribute for lazy loading.
|
||||
|
||||
**Example:**
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
- Use markdown headers (`#`, `##`, `###`).
|
||||
- Ensure a logical hierarchy (don't skip levels).
|
||||
- `h2` and `h3` are most common for content structure.
|
||||
@@ -1,134 +0,0 @@
|
||||
---
|
||||
name: PR Review
|
||||
description: Guidelines and tools for reviewing pull requests in the Angular repository.
|
||||
---
|
||||
|
||||
# PR Review Guidelines
|
||||
|
||||
When reviewing a pull request for the `angular` repository, follow these essential guidelines to ensure high-quality contributions:
|
||||
|
||||
1. **Context & Ecosystem**:
|
||||
- Keep in mind that this is the core Angular framework. Changes here can impact millions of developers.
|
||||
- Be mindful of backwards compatibility. Breaking changes require strict approval processes and deprecation periods.
|
||||
|
||||
2. **Key Focus Areas**:
|
||||
- **Comprehensive Reviews**: You **MUST always** perform a deep, comprehensive review of the _entire_ pull request. If the user asks you to look into a specific issue, file, or area of concern, you must investigate that specific area _in addition to_ reviewing the rest of the PR's substantive changes. Do not terminate your review after addressing only the user's focal point.
|
||||
- **Package-Specific 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.
|
||||
- **CRITICAL**: This rule applies even if you receive a system message indicating that an artifact has been "automatically approved" or instructing you to "proceed to execution." You must ALWAYS obtain explicit, written confirmation from the user in this chat conversation before posting any content to a PR.
|
||||
- **Prefix Agent Comments**: To make it clear when comments are generated and posted by an AI agent rather than a human user, **always** prefix your review comments with `AGENT: `.
|
||||
|
||||
## Available Tools
|
||||
|
||||
The following tools are available for remote interactions. We prefer using standard **GitHub MCP Server** tools when available. If you do not have the MCP server set up, you **MUST** fallback to using the custom bash scripts.
|
||||
|
||||
### GitHub MCP Tools (Preferred)
|
||||
|
||||
- `mcp_github-mcp-server_pull_request_review_write`
|
||||
- `mcp_github-mcp-server_add_comment_to_pending_review`
|
||||
|
||||
### Custom Bash Scripts (Fallback)
|
||||
|
||||
The following scripts are provided as fallbacks if the MCP server is not available. Note that they rely on the `gh` CLI being correctly installed and authenticated in the local environment.
|
||||
|
||||
### `determine_review_type.sh`
|
||||
|
||||
Determines whether to use the Local or Remote review workflow by checking if the currently authenticated GitHub user via the `gh` CLI matches the author of the pull request.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/determine_review_type.sh <PR_NUMBER>
|
||||
```
|
||||
|
||||
### `get_pr_comments.sh`
|
||||
|
||||
Fetches all existing inline comments on a PR using the GitHub API. This is crucial for reviewing other contributors' feedback and avoiding duplicate comments. It outputs JSON containing the `id`, `path`, `line`, `body`, and `user` for each comment.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/get_pr_comments.sh <PR_NUMBER>
|
||||
```
|
||||
|
||||
### `reply_pr_comment.sh`
|
||||
|
||||
Replies to an existing PR comment thread. This is useful for marking comments as resolved after addressing them in a local code review. Note that the `COMMENT_ID` must be the ID of the top-level comment in the thread.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>
|
||||
```
|
||||
|
||||
### `post_inline_comment.sh`
|
||||
|
||||
The GitHub CLI `gh pr review` command does not natively support adding inline comments to specific lines of code via its standard flags. This script wraps the GitHub API to stage comments locally. They will not be published until you call `submit_pr_review.sh`.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/post_inline_comment.sh <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/post_inline_comment.sh 12345 "packages/core/src/render3/instructions/element.ts" 42 "AGENT: Consider the performance implications here."
|
||||
```
|
||||
|
||||
### `submit_pr_review.sh`
|
||||
|
||||
Submits all locally staged inline comments as a single batched review via the GitHub Pull Request Reviews API.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/submit_pr_review.sh <PR_NUMBER> <EVENT_TYPE> [BODY]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `EVENT_TYPE`: Must be `COMMENT`, `APPROVE`, or `REQUEST_CHANGES`. Never use `APPROVE` for external PRs.
|
||||
- `BODY`: (Optional) A general summary comment for the review.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
.agent/skills/pr_review/scripts/submit_pr_review.sh 12345 COMMENT "AGENT: I have left a few inline suggestions for your consideration."
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# Router PR Review Guidelines
|
||||
|
||||
When reviewing pull requests that modify the Angular Router (`packages/router`), pay special attention to the following:
|
||||
|
||||
- **Timing Sensitivity**: The router is extremely sensitive to timing changes. Any changes that alter the asynchronous timing of navigations, resolvers, or guards are almost always breaking changes and must be scrutinized carefully.
|
||||
- **Testing Practices**: Tests should usually use the `RouterTestingHarness`. Many existing tests are older and do not use this harness. Do not blindly follow the shape of existing tests when writing or reviewing new ones; encourage the use of modern testing utilities.
|
||||
- **Feature Justification**: Changes to router core code should be well-justified. Consider whether the change is proven to be a core developer ask, such as resolving a highly upvoted GitHub issue or addressing a critical bug.
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# determine_review_type.sh <PR_NUMBER>
|
||||
# Determines if the PR should be reviewed locally or remotely based on author.
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: determine_review_type.sh <PR_NUMBER>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER=$1
|
||||
|
||||
# Get current authenticated user
|
||||
CURRENT_USER=$(gh api user -q .login 2>/dev/null)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Could not determine current GitHub user. Are you logged in to gh?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get PR author
|
||||
PR_AUTHOR=$(gh pr view "$PR_NUMBER" --json author -q .author.login 2>/dev/null)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Could not retrieve PR information for $PR_NUMBER."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_USER" = "$PR_AUTHOR" ]; then
|
||||
echo "local"
|
||||
else
|
||||
echo "remote"
|
||||
fi
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# get_pr_comments.sh
|
||||
# Fetches existing inline comments on a PR to avoid duplicate reviews.
|
||||
# Usage: ./get_pr_comments.sh <PR_NUMBER>
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: $0 <PR_NUMBER>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER="$1"
|
||||
|
||||
# Ensure gh cli is installed
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "Error: gh CLI could not be found. Please install and authenticate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current repository (e.g., angular/angular)
|
||||
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
||||
|
||||
# Fetch comments
|
||||
gh api \
|
||||
--paginate \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/${REPO}/pulls/${PR_NUMBER}/comments" \
|
||||
--jq '.[] | {id: .id, path: .path, line: .line, body: .body, user: .user.login}'
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# post_inline_comment.sh
|
||||
# Adds an inline comment to a specific line in a PR via the GitHub API.
|
||||
# Usage: ./post_inline_comment.sh <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>
|
||||
|
||||
if [ "$#" -lt 4 ]; then
|
||||
echo "Usage: $0 <PR_NUMBER> <FILE_PATH> <LINE_NUMBER> <COMMENT_BODY>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER="$1"
|
||||
FILE_PATH="$2"
|
||||
LINE="$3"
|
||||
BODY="$4"
|
||||
|
||||
# Ensure gh cli is installed
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "Error: gh CLI could not be found. Please install and authenticate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current repository (e.g., angular/angular)
|
||||
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
||||
|
||||
echo "Staging inline comment for PR #${PR_NUMBER} on ${FILE_PATH}:${LINE}..."
|
||||
|
||||
COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json"
|
||||
if [ ! -f "$COMMENT_FILE" ]; then
|
||||
echo "[]" > "$COMMENT_FILE"
|
||||
fi
|
||||
|
||||
# Append the new comment to the JSON array
|
||||
jq --arg path "${FILE_PATH}" --argjson line "${LINE}" --arg body "${BODY}" \
|
||||
'. += [{"path": $path, "line": $line, "body": $body}]' "$COMMENT_FILE" > "${COMMENT_FILE}.tmp" && mv "${COMMENT_FILE}.tmp" "$COMMENT_FILE"
|
||||
|
||||
echo "Comment successfully staged locally. Remember to call submit_pr_review.sh when finished to publish all comments as a single review!"
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>
|
||||
# Replies to an existing PR comment thread. Note: COMMENT_ID must be the ID of the top-level comment in the thread you are replying to.
|
||||
|
||||
if [ "$#" -lt 3 ]; then
|
||||
echo "Usage: reply_pr_comment.sh <PR_NUMBER> <COMMENT_ID> <REPLY_BODY>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER="$1"
|
||||
COMMENT_ID="$2"
|
||||
BODY="$3"
|
||||
|
||||
# Ensure gh cli is installed
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "Error: gh CLI could not be found. Please install and authenticate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current repository (e.g., angular/angular)
|
||||
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
||||
|
||||
# Reply to the thread using the provided comment ID
|
||||
gh api \
|
||||
--silent \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/${REPO}/pulls/${PR_NUMBER}/comments/${COMMENT_ID}/replies" \
|
||||
-f body="$BODY"
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# submit_pr_review.sh
|
||||
# Submits a batched PR review using comments previously staged by post_inline_comment.sh
|
||||
# Usage: ./submit_pr_review.sh <PR_NUMBER> <EVENT_TYPE> [BODY]
|
||||
# EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "Usage: $0 <PR_NUMBER> <EVENT_TYPE> [BODY]"
|
||||
echo "EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER="$1"
|
||||
EVENT="$2"
|
||||
BODY="${3:-}"
|
||||
COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json"
|
||||
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "Error: gh CLI could not be found. Please install and authenticate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
||||
|
||||
# Check if there are staged comments
|
||||
COMMENTS="[]"
|
||||
if [ -f "$COMMENT_FILE" ]; then
|
||||
COMMENTS=$(cat "$COMMENT_FILE")
|
||||
fi
|
||||
|
||||
echo "Submitting review for PR #${PR_NUMBER}..."
|
||||
|
||||
# Create the payload
|
||||
PAYLOAD_FILE="/tmp/angular_pr_${PR_NUMBER}_payload.json"
|
||||
jq -n --arg event "$EVENT" --arg body "$BODY" --argjson comments "$COMMENTS" \
|
||||
'{event: $event, body: $body, comments: $comments}' > "$PAYLOAD_FILE"
|
||||
|
||||
# Post the review using the GitHub Pull Request Reviews API
|
||||
gh api \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
|
||||
--input "$PAYLOAD_FILE"
|
||||
|
||||
echo "Review submitted successfully!"
|
||||
rm -f "$COMMENT_FILE"
|
||||
rm -f "$PAYLOAD_FILE"
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
name: reference-compiler-cli
|
||||
description: Explains the mental model and architecture of the code under `packages/compiler-cli`. You MUST use this skill any time you plan to work with code in `packages/compiler-cli`
|
||||
---
|
||||
|
||||
# Angular Compiler CLI (`ngtsc`) Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The `packages/compiler-cli` package contains the Angular Compiler (Ivy), often referred to as `ngtsc`. It is a wrapper around the TypeScript compiler (`tsc`) that extends it with Angular-specific capabilities.
|
||||
|
||||
The core goal of `ngtsc` is to compile Angular decorators (like `@Component`, `@Directive`, `@Pipe`) into static properties on the class (Ivy instructions, e.g., `static ɵcmp = ...`). It also performs template type checking and ahead-of-time (AOT) compilation.
|
||||
|
||||
## Mental Model
|
||||
|
||||
The compiler is designed as a **lazy, incremental, and partial** compilation pipeline.
|
||||
|
||||
1. **Wrapper Pattern**: `NgtscProgram` wraps the standard `ts.Program`. It intercepts calls to act as a drop-in replacement for standard tooling.
|
||||
2. **Traits System**: Every class with an Angular decorator is considered a "Trait". The compiler manages the state of these traits through a state machine:
|
||||
- **Pending**: Detected but not processed.
|
||||
- **Analyzed**: Metadata extracted, template parsed (but dependencies not yet linked).
|
||||
- **Resolved**: Dependencies (directives/pipes in template) resolved, import cycles handled.
|
||||
- **Skipped**: Not an Angular class.
|
||||
3. **Lazy Analysis**: Analysis only happens when necessary (e.g., when diagnostics are requested or emit is prepared).
|
||||
4. **Output AST**: The compiler generates an intermediate "Output AST" (`o.Expression`) for the generated code, which is then translated into TypeScript AST nodes during the emit phase.
|
||||
|
||||
## Key Subsystems
|
||||
|
||||
### 1. Core Orchestration (`ngtsc/core`)
|
||||
|
||||
- **`NgtscProgram`**: The public API implementing `api.Program`. It manages the `ts.Program` and the `NgCompiler`.
|
||||
- **`NgCompiler`**: The brain of the compiler. It orchestrates the compilation phases (Analysis, Resolution, Type Checking, Emit). It holds the `TraitCompiler`.
|
||||
|
||||
### 2. Trait Compilation (`ngtsc/transform`)
|
||||
|
||||
- **`TraitCompiler`**: Manages the lifecycle of "Traits". It iterates over source files, identifies decorated classes, and delegates to the appropriate `DecoratorHandler`.
|
||||
- **`Trait`**: A state container for a class, holding its handler, analysis results, and resolution results.
|
||||
|
||||
### 3. Decorator Handlers (`ngtsc/annotations`)
|
||||
|
||||
- **`DecoratorHandler`**: An interface for handling specific decorators.
|
||||
- **`ComponentDecoratorHandler`**: The most complex handler. It:
|
||||
- Extracts metadata (selector, inputs, outputs).
|
||||
- Parses the template.
|
||||
- Resolves used directives and pipes (`R3TargetBinder`).
|
||||
- Generates the `ɵcmp` instruction.
|
||||
- **`DirectiveDecoratorHandler`**, **`PipeDecoratorHandler`**, **`NgModuleDecoratorHandler`**: Handle their respective decorators.
|
||||
|
||||
### 4. Template Type Checking (`ngtsc/typecheck`)
|
||||
|
||||
- **`TemplateTypeChecker`**: Generates "Type Check Blocks" (TCBs). A TCB is a block of TypeScript code that represents the template's logic in a way `tsc` can understand and check for errors.
|
||||
- **`TypeCheckBlock`**: The actual generated code that validates bindings, events, and structural directives.
|
||||
|
||||
### 5. Metadata & Scope (`ngtsc/metadata`, `ngtsc/scope`)
|
||||
|
||||
- **`MetadataReader`**: Reads Angular metadata from source files (using `LocalMetadataRegistry`) and `.d.ts` files (using `DtsMetadataReader`).
|
||||
- **`ScopeRegistry`**: Determines the "compilation scope" of a component (which directives/pipes are available to it), handling `NgModule` transitive exports and Standalone Component imports.
|
||||
|
||||
### 6. Emit & Transformation (`ngtsc/transform`)
|
||||
|
||||
- **`ivyTransformFactory`**: A TypeScript transformer factory.
|
||||
- **`IvyCompilationVisitor`**: Visits classes, triggers compilation via `TraitCompiler`, and collects the Output AST.
|
||||
- **`IvyTransformationVisitor`**: Translates the Output AST into TypeScript AST, injects the `static ɵ...` fields, and removes the original decorators.
|
||||
|
||||
## Compilation Phases
|
||||
|
||||
1. **Construction**: `NgtscProgram` creates `NgCompiler`, which sets up all registries and the `TraitCompiler`.
|
||||
2. **Analysis** (`analyzeSync`):
|
||||
- The `TraitCompiler` scans files.
|
||||
- `DecoratorHandler`s extract metadata and parse templates.
|
||||
- No cross-file resolution happens here (allowing for parallelism and caching).
|
||||
3. **Resolution** (`resolve`):
|
||||
- `TraitCompiler` resolves traits.
|
||||
- Components link their templates to specific Directives and Pipes (found via `ScopeRegistry`).
|
||||
- Import cycles are detected and handled (e.g., via "remote scoping").
|
||||
4. **Type Checking**:
|
||||
- `TemplateTypeChecker` creates TCBs for all components.
|
||||
- TypeScript diagnostics are retrieved for these TCBs.
|
||||
5. **Emit** (`prepareEmit`):
|
||||
- `ivyTransformFactory` is created.
|
||||
- TS `emit` is called.
|
||||
- The transformers run, injecting the compiled Ivy instructions into the JS/DTS output.
|
||||
|
||||
## Important File Locations
|
||||
|
||||
- `packages/compiler-cli/src/ngtsc/program.ts`: Entry point (`NgtscProgram`).
|
||||
- `packages/compiler-cli/src/ngtsc/core/src/compiler.ts`: Core logic (`NgCompiler`).
|
||||
- `packages/compiler-cli/src/ngtsc/transform/src/trait.ts`: Trait state machine.
|
||||
- `packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts`: Component compilation logic.
|
||||
- `packages/compiler-cli/src/ngtsc/typecheck/src/template_type_checker.ts`: Type checking logic.
|
||||
- `packages/compiler-cli/src/ngtsc/transform/src/transform.ts`: AST transformation logic.
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
name: reference-core
|
||||
description: Explains the mental model and architecture of the code under `packages/core`. You MUST use this skill any time you plan to work with code in `packages/core`
|
||||
---
|
||||
|
||||
# Angular Core (`packages/core`) Mental Model
|
||||
|
||||
This document outlines the architecture and mental model for `packages/core`, the heart of the Angular framework.
|
||||
|
||||
## 1. High-Level Architecture
|
||||
|
||||
`packages/core` contains the runtime logic for Angular. Its primary responsibilities are:
|
||||
|
||||
1. **Rendering (Ivy/Render3)**: Transforming templates into DOM updates.
|
||||
2. **Dependency Injection (DI)**: Managing object creation and lifetime.
|
||||
3. **Change Detection**: Synchronizing the model with the view.
|
||||
4. **Reactivity**: Signals and Zone.js integration.
|
||||
|
||||
## 2. Rendering Engine (Ivy / Render3)
|
||||
|
||||
The rendering engine (located in `packages/core/src/render3`) uses an **instruction-based** approach.
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **Instructions**: The Angular compiler transforms templates into a sequence of instruction calls (e.g., `ɵɵelementStart`, `ɵɵtext`, `ɵɵproperty`). These instructions are executed at runtime to create and update the view.
|
||||
- _Location_: `packages/core/src/render3/instructions`
|
||||
|
||||
- **LView (Logical View)**: An array containing the _state_ of a specific view instance. It holds:
|
||||
- DOM nodes (`RElement`, `RText`).
|
||||
- Binding values (for change detection).
|
||||
- Directive/Component instances.
|
||||
- _Context_: `packages/core/src/render3/interfaces/view.ts`
|
||||
|
||||
- **TView (Template View)**: An array containing the _static structure_ of a view. It is shared across all instances (`LView`s) of the same component/template. It holds:
|
||||
- Property names for bindings.
|
||||
- Node relationship information.
|
||||
- Compiled directive definitions.
|
||||
- _Context_: `packages/core/src/render3/interfaces/view.ts`
|
||||
|
||||
- **Memory Layout**: `LView` and `TView` are parallel arrays. Index `i` in `LView` corresponds to metadata at index `i` in `TView`.
|
||||
- `HEADER`: Fixed size, contains context (Parent, Host, etc.).
|
||||
- `DECLS`: Static nodes (elements, text, pipes).
|
||||
- `VARS`: Binding values.
|
||||
- `EXPANDO`: Dynamic data (host bindings, injectors).
|
||||
|
||||
### The Render Cycle
|
||||
|
||||
1. **Creation Mode**: Instructions create DOM nodes and store them in `LView`.
|
||||
2. **Update Mode**: Instructions check current values against previous values stored in `LView`. If changed, they update the DOM.
|
||||
|
||||
## 3. Dependency Injection (DI)
|
||||
|
||||
DI in Angular is hierarchical and split into two systems that interact:
|
||||
|
||||
### Module Injector (`R3Injector`)
|
||||
|
||||
- Configured via `@NgModule.providers` or `providedIn: 'root'`.
|
||||
- Stored in a hierarchy of `R3Injector` instances.
|
||||
- _Location_: `packages/core/src/di/r3_injector.ts`
|
||||
|
||||
### Node Injector
|
||||
|
||||
- Configured via `@Component.providers` or `@Directive.providers`.
|
||||
- **Not a class**, but a data structure embedded in the `LView` ("Expando" section).
|
||||
- Uses **Bloom Filters** (`TView.data`) to quickly check if a token is present at a specific node index before traversing up the tree.
|
||||
- Resolves tokens starting from the current node, walking up the view tree (Element Injector hierarchy), and falling back to the Module Injector if not found.
|
||||
|
||||
## 4. Change Detection
|
||||
|
||||
- **Dirty Checking**: Angular checks if values bound in templates have changed.
|
||||
- **Strategies**:
|
||||
- `Default`: Checks everything.
|
||||
- `OnPush`: Checks only if inputs change, events fire, or signals update.
|
||||
- **Signals**: The new reactivity primitive. Signals notify the scheduler when they change, potentially allowing for fine-grained updates (Zoneless).
|
||||
|
||||
## 5. Key Directories to Know
|
||||
|
||||
- `src/render3`: The Ivy rendering engine.
|
||||
- `instructions`: The runtime instructions called by compiled code.
|
||||
- `interfaces`: `LView`, `TView`, `TNode` definitions.
|
||||
- `src/di`: Dependency injection system.
|
||||
- `src/change_detection`: Change detection logic.
|
||||
- `src/zone`: Zone.js integration.
|
||||
- `src/signal`: Signals implementation (if present in this version, otherwise likely in `primitives`).
|
||||
|
||||
## 6. Conventions & Gotchas
|
||||
|
||||
- **Prefixes**: Private/Internal exports often start with `ɵ`.
|
||||
- **Global State**: Ivy relies heavily on global state (e.g., `getLView()`) during instruction execution to avoid passing context arguments everywhere. This is for performance and code size.
|
||||
- **Performance**: The code is highly optimized for performance and memory. You will see arrays used instead of objects, bitmasks, and manual memory management patterns. **Respect these patterns.**
|
||||
|
||||
## 7. How to Modify Core
|
||||
|
||||
1. **Understand the Instruction**: If modifying runtime behavior, find the corresponding instruction in `src/render3/instructions`.
|
||||
2. **Check `LView`/`TView` Impact**: If adding state, understand where it fits in the `LView` array.
|
||||
3. **Tests**: Core has extensive tests. Run them using Bazel.
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
name: reference-signal-forms
|
||||
description: Explains the mental model and architecture of the code under `packages/forms/signals`. You MUST use this skill any time you plan to work with code in `packages/forms/signals`
|
||||
---
|
||||
|
||||
# Signal Forms Architecture
|
||||
|
||||
The `packages/forms/signals` directory contains an experimental, signal-based forms API for Angular.
|
||||
This system differs significantly from the existing Reactive and Template-driven forms.
|
||||
|
||||
## Mental Model
|
||||
|
||||
1. **Model-Driven**: The system is built around a `WritableSignal<T>` which serves as the **single source of truth**.
|
||||
Unlike Reactive Forms where the `FormControl` holds the value, here the `Signal` holds the value.
|
||||
The form is merely a _view_ or _projection_ of that signal, adding form-specific state (validity, dirty, touched).
|
||||
|
||||
2. **Proxy-Based Traversal**: The form API (`form(signal)`) returns a `FieldTree`. This object is a **Proxy**.
|
||||
It allows accessing nested fields (e.g., `myForm.user.name`) without manually creating control groups.
|
||||
Accessing a property on the proxy lazily resolves or creates the corresponding `FieldNode`.
|
||||
|
||||
3. **Schema-Based Logic**: Validation, disabled state, and other metadata are defined separately via **Schemas**.
|
||||
Schemas are applied to the form structure using functions like `apply`, `applyEach` (for arrays), and `applyWhen`.
|
||||
This separates the _structure_ of the data from the _rules_ governing it.
|
||||
|
||||
4. **Directives as Glue**: The `[formField]` directive binds a DOM element (native input or custom control) to a `FieldNode`.
|
||||
It handles:
|
||||
- Syncing the value between the DOM and the Signal.
|
||||
- Reflecting state (valid, touched, etc.) to the UI.
|
||||
- Handling user interaction events (blur, input).
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. `FieldNode` (`src/field/node.ts`)
|
||||
|
||||
The central internal class representing a single field in the form graph. It aggregates several state managers:
|
||||
|
||||
- `structure`: Manages parent/child relationships and signal slicing.
|
||||
- `validationState`: Computes `valid`, `invalid`, `errors` signals.
|
||||
- `nodeState`: Tracks `touched`, `dirty`, `pristine`.
|
||||
- `metadataState`: Stores metadata like `min`, `max`, `required`.
|
||||
- `submitState`: Tracks submission status and server errors.
|
||||
|
||||
### 2. `ValidationState` (`src/field/validation.ts`)
|
||||
|
||||
Manages the complexity of validation:
|
||||
|
||||
- **Synchronous Errors**: Derived from schema rules.
|
||||
- **Asynchronous Errors**: Handled via signals, including 'pending' states.
|
||||
- **Tree Errors**: Errors that bubble up or are targeted at specific fields.
|
||||
- **Submission Errors**: Server-side errors injected imperatively via `submit()`.
|
||||
|
||||
### 3. `FormField` Directive (`src/directive/form_field_directive.ts`)
|
||||
|
||||
The bridge between the `FieldNode` and the DOM.
|
||||
|
||||
- Selector: `[formField]`
|
||||
- It supports:
|
||||
- **Native Elements**: `<input>`, `<select>`, `<textarea>`.
|
||||
- **Custom Controls**: Components implementing `FormUiControl` or `FormValueControl`.
|
||||
- **Legacy Interop**: Components implementing `ControlValueAccessor` (via `InteropNgControl`).
|
||||
|
||||
### 4. `Schema` (`src/api/structure.ts` & `src/api/rules`)
|
||||
|
||||
Defines the behavior.
|
||||
|
||||
- Created via `schema(fn)`.
|
||||
- Applied via `apply(path, schema)`.
|
||||
- Rules include validators (`required`, `pattern`, `min`, `max`) and state modifiers (`disabled`, `hidden`).
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. **Read**: `form.field.value()` reads directly from the underlying signal (projected to the specific path).
|
||||
2. **Write**: Writing to the form (e.g., via UI) updates the underlying signal.
|
||||
3. **Validation**: A computed effect observes the value signal and runs validators defined in the schema.
|
||||
|
||||
## Usage Example (Conceptual)
|
||||
|
||||
```typescript
|
||||
// 1. Define Model
|
||||
const user = signal({name: '', age: 0});
|
||||
|
||||
// 2. Define Schema
|
||||
const userRules = schema((u) => {
|
||||
required(u.name);
|
||||
min(u.age, 18);
|
||||
});
|
||||
|
||||
// 3. Create Form
|
||||
const userForm = form(user, userRules); // OR apply(userForm, userRules)
|
||||
|
||||
// 4. Bind in Template
|
||||
// <input [formField]="userForm.name">
|
||||
```
|
||||
|
||||
## Important Files
|
||||
|
||||
- `packages/forms/signals/src/api/structure.ts`: Public API entry points (`form`, `apply`).
|
||||
- `packages/forms/signals/src/api/control.ts`: Interfaces for custom controls (`FormUiControl`).
|
||||
- `packages/forms/signals/src/field/node.ts`: The `FieldNode` implementation.
|
||||
- `packages/forms/signals/src/directive/form_field_directive.ts`: The `[formField]` directive.
|
||||
|
||||
## Supplemental Information
|
||||
|
||||
- [Compiler & Core Integration](references/integration.md): Details how `[formField]` hooks into type-checking and the runtime.
|
||||
@@ -1,46 +0,0 @@
|
||||
# Signal Forms Integration: Compiler & Core
|
||||
|
||||
This document explains how the Signal Forms system hooks into the Angular compiler and runtime to provide seamless type-checking and efficient updates.
|
||||
|
||||
## 1. Compiler-CLI Integration (Type Checking)
|
||||
|
||||
The `packages/compiler-cli` package contains specific logic to support `[formField]`. This is primarily handled in `src/ngtsc/typecheck/src/ops/signal_forms.ts`.
|
||||
|
||||
### Key Mechanisms:
|
||||
|
||||
- **Detection**: The compiler identifies a directive as a "Field Directive" if it has the `ɵNgFieldDirective` property or comes from `@angular/forms/signals`.
|
||||
- **Synthetic Binding Expansion**: When you write `<input [formField]="mySignal" />`, the type checker doesn't just check `formField`. It synthetically expands this into a set of bindings for validation:
|
||||
- `[value]="mySignal()"` (or `checked` for checkboxes)
|
||||
- `[disabled]="mySignal.disabled()"`
|
||||
- `[required]="mySignal.required()"`
|
||||
- ...and so on.
|
||||
- This ensures that `mySignal` (the `FieldNode`) has all the necessary properties to drive the form control.
|
||||
- **Conflict Detection**: It actively prevents "double binding". If you bind `[formField]`, you are _banned_ from also binding `[value]`, `[disabled]`, `[required]`, etc., as the signal form should be the single source of truth.
|
||||
- **Element Type Validation**:
|
||||
- **Native Elements**: Checks that the signal's value type matches the element type (e.g., `<input type="checkbox">` requires a `boolean` signal).
|
||||
- **Custom Controls**: Detects if a custom component is a "Form Value Control" (has a `value` input/output) or "Form Checkbox Control" (has a `checked` input/output) and validates against that.
|
||||
|
||||
### Relevant Files:
|
||||
|
||||
- `packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts`: The core logic for `TcbNativeFieldOp` and `SignalFormFieldOp`.
|
||||
|
||||
## 2. Core Runtime Integration
|
||||
|
||||
The `packages/core` package provides the low-level instructions that power the `FormField` directive. This allows it to do things normal directives cannot, like efficiently syncing state without change detection overhead for every property.
|
||||
|
||||
### Key Mechanisms:
|
||||
|
||||
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`.
|
||||
- **`ɵɵcontrol` Instructions**: When the compiler sees `ɵngControlCreate`, it emits:
|
||||
- `ɵɵcontrolCreate`: Called during the creation phase.
|
||||
- `ɵɵcontrol`: Called during the update phase.
|
||||
- **`ControlDirectiveHost`**: These instructions provide the directive with a `ControlDirectiveHost`. This is a privileged interface that allows the `FormField` directive to:
|
||||
- **Access the Element**: Get direct access to the native element or component instance.
|
||||
- **Set Inputs**: Write directly to inputs of _other directives_ on the same node (e.g., setting the `value` input of a custom control).
|
||||
- **Listen to Outputs**: Subscribe to outputs of other directives (e.g., `valueChange`).
|
||||
- **Bypass Templates**: It effectively acts as a "meta-directive" that manages the bindings for you, bypassing the need for explicit template syntax for every property.
|
||||
|
||||
### Relevant Files:
|
||||
|
||||
- `packages/core/src/render3/instructions/control.ts`: Implementation of `ɵɵcontrol` instructions.
|
||||
- `packages/forms/signals/src/directive/form_field_directive.ts`: The directive that implements the hook.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
description: Find and fix flaky tests in the repository
|
||||
---
|
||||
|
||||
Investigate flaky tests in the repo and propose fixes to improve stability.
|
||||
High-level process:
|
||||
|
||||
1. Run tests in the repo to look for flakes.
|
||||
- Consider using Bazel's `--runs_per_test` flag to easily find
|
||||
flakes.
|
||||
- Be cognizant of not exhausting all the resources on the current
|
||||
machine, run a subset of tests at a time such as
|
||||
`bazel test //packages/core/...`.
|
||||
2. Once you find some flakes, focus on one at a time.
|
||||
3. Create a new branch named `flakes/${relevantNameFromTest}`.
|
||||
4. Reproduce the flake to the best of your ability.
|
||||
- Consider using `--test_env JASMINE_RANDOM_SEED=1234` to
|
||||
replicate the broken test ordering.
|
||||
5. Debug the test to understand the failure mode.
|
||||
- Consider temporarily disabling / skipping other tests with `xit`
|
||||
and `fit` to narrow down where the flake might be coming from if
|
||||
multiple tests are influencing each other.
|
||||
- Consider temporarily ignoring Firefox tests with
|
||||
`--test_tag_filters -firefox` if the flake does not appear to be
|
||||
browser specific.
|
||||
- Consider using `--test_sharding_strategy disabled` to run the
|
||||
test in a single shard.
|
||||
- Try to understand why the test was _flaky_, not just why it
|
||||
_failed_. Understanding the inconsistency is important to
|
||||
finding the correct fix.
|
||||
6. Attempt a fix and validate with `--runs_per_test`.
|
||||
- Iterate on the fix until you have something which appears to
|
||||
work.
|
||||
- If you find yourself stuck and not making meaningful progress,
|
||||
note down what you've learned/where you're struggling, commit
|
||||
what you have, look for another flake to fix, and continue. At
|
||||
the end, surface to the user what you failed to fix.
|
||||
- Don't try to make significant changes to Angular's runtime
|
||||
behavior, focus just on making the test pass/fail consistently.
|
||||
7. Commit the change with relevant details in the commit message and
|
||||
move on to the next test.
|
||||
- Be sure to include your theory of why the test was flaky and
|
||||
how this fix eliminates or reduces that flakiness.
|
||||
8. Iterate as many times as the user requests you to (default 5
|
||||
branches if not otherwise specified).
|
||||
9. Once you can't find any flaky tests or have iterated as many times
|
||||
as requested, stop and inform the user what you found and fixed.
|
||||
|
||||
Additional notes:
|
||||
|
||||
- Multiple fixes including the same/related files can go in the same
|
||||
commit or multiple commits on the same branch.
|
||||
- Distinct test fixes should go in different branches, make a new one
|
||||
for each investigation.
|
||||
- You may push these branches to `origin`, but do not create PRs for
|
||||
them.
|
||||
+1
-1
@@ -1 +1 @@
|
||||
8.6.0
|
||||
8.5.1
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
description = "Researches part of the codebase and creates a skill for it"
|
||||
prompt="""
|
||||
## Role
|
||||
|
||||
You are an experienced senior developer specializing in reverse engineering and understanding
|
||||
complex code, prompt engineering, and context engineering.
|
||||
|
||||
Your task is to build a complete understanding of the mental model and architecture of the code,
|
||||
and to create an efficient context file that allows a coding agent to understand and work with the
|
||||
code.
|
||||
|
||||
## Guiding Principles
|
||||
|
||||
1. **Deep Analysis:** Understand the _why_ behind the code. Not just a surface level listing of
|
||||
files and classes.
|
||||
|
||||
2. **Curiosity:** Be curious. If something is unclear, confusing, or unexpected you _MUST_
|
||||
investigate further until you fully understand.
|
||||
|
||||
3. **Efficient Token Use:** The final output will be directly included into the context of the
|
||||
coding assistant, so be sure to make efficient use of tokens. Think deeply, but only capture the
|
||||
most important information for the coding agent in the final output
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Fully explore the code before first, with a focus on understanding the architecture and how all
|
||||
of the pieces fit together.
|
||||
|
||||
2. Develop an understanding of the mental model behind the architecture. _Why_ is the code is
|
||||
architected this way? How should developers think about the system?
|
||||
|
||||
3. Document your findings using the given output format described in the next section.
|
||||
|
||||
## Output Format
|
||||
|
||||
Note the following placeholders below:
|
||||
|
||||
- `[package]`: is a unique identifier for the current code package (for example if you're looking at
|
||||
code under `packages/forms/signals` you might use `signal-forms` as the `[package]`)
|
||||
- `[code-location]`: is the location of the current code within the larger repository
|
||||
- `[context]`: is the context to add to the coding agent when it is working with this code.
|
||||
|
||||
With those placeholders in mind, write a file create a new directory under `.gemini/skills` with the
|
||||
name `reference-[package]/`, and under that directory add a `SKILL.md` file with the following
|
||||
content:
|
||||
|
||||
```md
|
||||
---
|
||||
name: reference-[package]
|
||||
description: Explains the mental model and architecture of the code under `[code-location]`. You MUST use this skill any time you plan to work with code in `[code-location]`
|
||||
---
|
||||
|
||||
[context]
|
||||
```
|
||||
|
||||
## Additional User Instructions
|
||||
|
||||
The user may have additional instructions.
|
||||
|
||||
- The user should indicate what package to research
|
||||
- The user may indicate that they want to update a package that has already been researched. In this
|
||||
case, read the existing skill and make any required updates based on your research
|
||||
- The user may request supplemental information be added to the skill (if so, add this to the
|
||||
existing skill in a new markdown file under a `references` directory within the skill. Add a
|
||||
section to the main `SKILL.md` that mentions what supplemental information is available)
|
||||
|
||||
Here are the user's additional instructions:
|
||||
|
||||
{{args}}
|
||||
"""
|
||||
+1
-2
@@ -7,5 +7,4 @@ code_review:
|
||||
help: false
|
||||
summary: false
|
||||
code_review: false
|
||||
ignore_patterns:
|
||||
- pnpm-lock.yaml
|
||||
ignore_patterns: []
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"context": {
|
||||
"fileName": ["AGENTS.md", "GEMINI.md"]
|
||||
}
|
||||
}
|
||||
+22992
-20451
File diff suppressed because one or more lines are too long
@@ -5,9 +5,9 @@ runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/saucelabs@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- 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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- 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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -29,20 +29,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
with:
|
||||
token: '${{secrets.GITHUB_TOKEN}}'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure Firebase deploy target
|
||||
working-directory: ./
|
||||
run: |
|
||||
# We can use `npx` as the Firebase deploy actions uses it too.
|
||||
# Use stable version release
|
||||
npx -y firebase-tools@15.15.0 target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@15.15.0 target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
|
||||
reactions: 'rocket'
|
||||
|
||||
- uses: alessbell/pull-request-comment-branch@653a7d5ca8bd91d3c5cb83286063314d0b063b8e # v1.4.0
|
||||
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
|
||||
id: comment-branch
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -34,23 +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_sha}}
|
||||
|
||||
# We cannot use `angular/dev-infra/github-actions/npm/checkout-and-setup-node` here
|
||||
# because it does not support checking out from a fork (as it lacks a `repository` input).
|
||||
# Thus, we checkout and setup Node/pnpm manually.
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
ref: ${{steps.comment-branch.outputs.head_ref}}
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
@@ -61,8 +49,7 @@ jobs:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: pnpm benchmarks prepare-for-github-action "$COMMENT_BODY"
|
||||
|
||||
- run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} "${{steps.info.outputs.benchmarkTarget}}"
|
||||
|
||||
- run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} ${{steps.info.outputs.benchmarkTarget}}
|
||||
id: benchmark
|
||||
name: Running benchmark
|
||||
|
||||
|
||||
+26
-28
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -32,8 +32,6 @@ jobs:
|
||||
run: pnpm ng-dev pullapprove verify
|
||||
- name: Validate angular robot configuration
|
||||
run: pnpm ng-dev ngbot verify
|
||||
- name: Validate agent skills
|
||||
run: pnpm ng-dev ai skills validate
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: pnpm check-tooling-setup
|
||||
|
||||
@@ -41,13 +39,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -57,7 +55,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@c495c3ddffba403ba11be95fffb67e25203b3799 # v7.1.10
|
||||
uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -69,11 +67,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -85,11 +83,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -102,11 +100,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -121,11 +119,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -138,11 +136,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -154,11 +152,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -208,11 +206,11 @@ jobs:
|
||||
runs-on: ubuntu-latest-8core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
ANGULAR_READONLY_GITHUB_TOKEN: ${{ secrets.READONLY_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create a PR (if necessary)
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
|
||||
push-to-fork: 'angular-robot/angular'
|
||||
|
||||
@@ -3,36 +3,23 @@ name: DevInfra
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pull_request_labels:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/labeling/pull-request@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/pull-request-labeling@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/saucelabs@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- 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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- 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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
# We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
|
||||
|
||||
+21
-23
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -32,14 +32,12 @@ jobs:
|
||||
run: pnpm ng-dev ngbot verify
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: pnpm check-tooling-setup
|
||||
- name: Validate agent skills
|
||||
run: pnpm ng-dev 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@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
|
||||
|
||||
@@ -47,13 +45,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -61,7 +59,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@c495c3ddffba403ba11be95fffb67e25203b3799 # v7.1.10
|
||||
uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -73,11 +71,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run CI tests for framework
|
||||
@@ -97,11 +95,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run integration CI tests for framework
|
||||
@@ -112,11 +110,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -129,11 +127,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -144,11 +142,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@1d545550f0a93b2ffee3672587314d6f6b3dc7fd
|
||||
- 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@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@ export const github = {
|
||||
name: 'angular',
|
||||
mainBranchName: 'main',
|
||||
mergeMode: 'caretaker-only',
|
||||
requireReleaseModeForRelease: false,
|
||||
requireReleaseModeForRelease: true,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"packages/docs/**",
|
||||
"packages/elements/schematics/**",
|
||||
"packages/examples/**",
|
||||
"packages/forms/signals/src/api/rules/validation/standard_schema.ts",
|
||||
"packages/localize/**",
|
||||
"packages/private/**",
|
||||
"packages/service-worker/**",
|
||||
|
||||
+1
-7
@@ -30,16 +30,10 @@ vscode-ng-language-service/syntaxes/test/data/*.html
|
||||
# Ignore goldens MD files
|
||||
goldens/**/*.api.md
|
||||
|
||||
# Ignore golden symbol json files
|
||||
packages/**/*.golden_symbols.json
|
||||
|
||||
# adev generated files
|
||||
adev/src/content/aria/**/*.json
|
||||
adev/src/content/cli/**/*.json
|
||||
adev/src/content/cdk/**/*.json
|
||||
|
||||
# Antigravity rules
|
||||
.agent/rules/agents.md
|
||||
|
||||
CHANGELOG.md
|
||||
CHANGELOG_ARCHIVE.md
|
||||
CHANGELOG_ARCHIVE.md
|
||||
+42
-48
@@ -36,9 +36,8 @@
|
||||
|
||||
version: 3
|
||||
|
||||
availability:
|
||||
users_unavailable:
|
||||
- devversion
|
||||
#availability:
|
||||
# users_unavailable: []
|
||||
|
||||
# Meta field that goes unused by PullApprove to allow for defining aliases to be
|
||||
# used throughout the config.
|
||||
@@ -100,13 +99,14 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- kirjs
|
||||
- JoostK
|
||||
- mmalerba
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
- ~mattrbeck
|
||||
@@ -146,13 +146,15 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- kirjs
|
||||
- ~thePunderWoman
|
||||
- ~jelbourn
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- mmalerba
|
||||
- JeanMeche
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
@@ -179,9 +181,9 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- jelbourn
|
||||
- josephperrott
|
||||
- ~pkozlowski-opensource
|
||||
- alan-agius4
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
@@ -200,14 +202,16 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~JiaLiPassion
|
||||
- ~jelbourn
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- kirjs
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- mmalerba
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
- ~mattrbeck
|
||||
@@ -244,25 +248,25 @@ groups:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'adev/**/{*,.*}',
|
||||
'tools/manual_api_docs/blocks/*.md',
|
||||
'tools/manual_api_docs/elements/*.md',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alan-agius4
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- bencodezen
|
||||
- crisbeto
|
||||
- kirjs
|
||||
- JeanMeche
|
||||
- ~thePunderWoman
|
||||
- jelbourn
|
||||
- thePunderWoman
|
||||
- devversion
|
||||
- josephperrott
|
||||
- ~pkozlowski-opensource
|
||||
- ~mgechev
|
||||
- MarkTechson
|
||||
- mmalerba
|
||||
- ~hawkgs
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
@@ -298,13 +302,8 @@ groups:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files
|
||||
.exclude('.pullapprove.yml')
|
||||
.exclude('tools/manual_api_docs/blocks/*.md')
|
||||
.exclude('tools/manual_api_docs/elements/*.md'),
|
||||
[
|
||||
contains_any_globs(files.exclude('.pullapprove.yml'), [
|
||||
'{*,.*}',
|
||||
'.agent/**/{*,.*}',
|
||||
'.devcontainer/**/{*,.*}',
|
||||
'.github/**/{*,.*}',
|
||||
'.husky/**/{*,.*}',
|
||||
@@ -341,7 +340,6 @@ groups:
|
||||
- author not in ["angular-robot"]
|
||||
reviewers:
|
||||
users:
|
||||
- alan-agius4
|
||||
- devversion
|
||||
- josephperrott
|
||||
|
||||
@@ -373,12 +371,14 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- ~alxhub
|
||||
- atscott
|
||||
- ~thePunderWoman
|
||||
- ~jelbourn
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- kirjs
|
||||
- mmalerba
|
||||
- crisbeto
|
||||
- devversion
|
||||
- JeanMeche
|
||||
@@ -409,11 +409,13 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- kirjs
|
||||
- ~thePunderWoman
|
||||
- ~jelbourn
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- mmalerba
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
- ~mattrbeck
|
||||
@@ -422,24 +424,6 @@ groups:
|
||||
required: 1 # Require that 1 person approve
|
||||
reviewed_for: required
|
||||
|
||||
# =========================================================
|
||||
# Agent Skills
|
||||
# =========================================================
|
||||
agent-skills:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'skills/{*,.*}',
|
||||
'skills/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- MarkTechson
|
||||
- kirjs
|
||||
- ~JeanMeche
|
||||
- ~dgp1130
|
||||
|
||||
####################################################################################
|
||||
# Special Cases
|
||||
####################################################################################
|
||||
@@ -457,10 +441,11 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- andrewseguin
|
||||
- dgp1130
|
||||
- ~thePunderWoman
|
||||
- ~jelbourn
|
||||
- thePunderWoman
|
||||
- josephperrott
|
||||
|
||||
# =========================================================
|
||||
@@ -479,16 +464,20 @@ groups:
|
||||
users:
|
||||
- ~pkozlowski-opensource # Pawel Kozlowski
|
||||
- ~alxhub # Alex Rickabaugh
|
||||
- ~thePunderWoman # Jessica Janiuk
|
||||
- ~AndrewKushnir # Andrew Kushnir
|
||||
- thePunderWoman # Jessica Janiuk
|
||||
- AndrewKushnir # Andrew Kushnir
|
||||
- atscott # Andrew Scott
|
||||
labels:
|
||||
pending: 'requires: TGP'
|
||||
approved: 'requires: TGP'
|
||||
rejected: 'requires: TGP'
|
||||
|
||||
# External team required reviews
|
||||
primitives-shared:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/core/primitives/**/*spec.ts'), [
|
||||
contains_any_globs(files, [
|
||||
'packages/core/primitives/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
@@ -499,6 +488,10 @@ groups:
|
||||
- tbondwilkinson # Tom Wilkinson
|
||||
- rahatarmanahmed # Rahat Ahmed
|
||||
- ENAML # Ethan Cline
|
||||
labels:
|
||||
pending: 'requires: TGP'
|
||||
approved: 'requires: TGP'
|
||||
rejected: 'requires: TGP'
|
||||
|
||||
####################################################################################
|
||||
# Override managed result groups
|
||||
@@ -521,6 +514,7 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- alxhub
|
||||
- jelbourn
|
||||
- josephperrott
|
||||
reviews:
|
||||
request: 0
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
This is the source code for the Angular framework. This guide outlines standard practices for AI agents working in this repository.
|
||||
|
||||
## Environment
|
||||
|
||||
- Use `pnpm` for package management.
|
||||
- Use `pnpm bazel test //target` to run tests.
|
||||
|
||||
## Key Documentation
|
||||
|
||||
- [Building and Testing](contributing-docs/building-and-testing-angular.md): definitive guide for running targets.
|
||||
- [Coding Standards](contributing-docs/coding-standards.md): style guide for TypeScript and other files.
|
||||
- [Commit Guidelines](contributing-docs/commit-message-guidelines.md): format for commit messages and PR titles.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Zoneless & Async-First:** Assume a zoneless environment where state changes schedule updates asynchronously.
|
||||
- **Do NOT** use `fixture.detectChanges()` to manually trigger updates.
|
||||
- **ALWAYS** use the "Act, Wait, Assert" pattern:
|
||||
1. **Act:** Update state or perform an action.
|
||||
2. **Wait:** `await fixture.whenStable()` to allow the framework to process the scheduled update.
|
||||
3. **Assert:** Verify the output.
|
||||
- To keep tests fast, minimize the need for waiting:
|
||||
- Use `useAutoTick()` (from `packages/private/testing/src/utils.ts`) to fast-forward time via the mock clock.
|
||||
- When waiting is necessary, use real async tests (`it('...', async () => { ... })`) along with:
|
||||
- `await timeout(ms)` (from `packages/private/testing/src/utils.ts`) to wait a specific number of milliseconds.
|
||||
- `await fixture.whenStable()` to wait for framework stability.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
- Use the `gh` CLI (GitHub CLI) for creating and managing pull requests.
|
||||
-475
@@ -1,464 +1,3 @@
|
||||
<a name="21.2.16"></a>
|
||||
# 21.2.16 (2026-06-03)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f6d8e642b0](https://github.com/angular/angular/commit/f6d8e642b0b215d2f9dbf1060abd24348c6cbf66) | fix | only strip a literal /index.html suffix from URLs |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ae1c8a1f7a](https://github.com/angular/angular/commit/ae1c8a1f7a7f1d4832da3b22e3763864fa5ff098) | fix | move projection attributes into constants |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3fd6897a67](https://github.com/angular/angular/commit/3fd6897a67fd6acdc01fcde0452a98c3e0f81e21) | fix | harden inherit definition feature against polluted prototypes |
|
||||
| [7e38336dc7](https://github.com/angular/angular/commit/7e38336dc73e14d98cc6465f54e1b7d6271facb2) | fix | use Object.create(null) for LOCALE_DATA as a hardening measure |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [66821c4ed5](https://github.com/angular/angular/commit/66821c4ed5f580912a1609fc1e06a86f8793c2cf) | fix | throw on suspicious URLs and restrict protocol-relative URLs |
|
||||
| [d3170031b6](https://github.com/angular/angular/commit/d3170031b6f35508f960cba18586843925bb61ec) | fix | update domino to latest version |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.15"></a>
|
||||
# 21.2.15 (2026-05-28)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7f4ac78994](https://github.com/angular/angular/commit/7f4ac78994bff1576ab33f3ce48f95c17f40b4d8) | fix | add upper bounds for digitsInfo |
|
||||
| [300f61feb3](https://github.com/angular/angular/commit/300f61feb3a534bfddf16fcbd240f97b32249699) | fix | sanitize placeholder |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0b07f47bd6](https://github.com/angular/angular/commit/0b07f47bd6598ae6bd5b75a375e2c817a3c0f243) | fix | normalize tag names with custom namespaces in DomElementSchemaRegistry ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
| [eb1cbbf2eb](https://github.com/angular/angular/commit/eb1cbbf2eb5833219a367a61c04eb07aaa36cc29) | fix | prevent namespaced SVG <style> elements from being stripped |
|
||||
| [cc1378d54b](https://github.com/angular/angular/commit/cc1378d54bd93f3882d732261be8e66720eb71b2) | fix | sanitize dynamic href and xlink:href bindings on SVG a elements ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
| [782e01594e](https://github.com/angular/angular/commit/782e01594e2ad9134c7385dcf3b518101b23ccab) | fix | strip namespaced SVG script elements during template compilation ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ff12fe55ac](https://github.com/angular/angular/commit/ff12fe55ace5e861ba261afb4c0480ff3c40a192) | fix | normalize tag names in runtime i18n attribute security context lookup ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
| [e6fe77cc97](https://github.com/angular/angular/commit/e6fe77cc97fd10351687416f938bf754aff4eb9f) | fix | sanitize meta selectors |
|
||||
| [daaf32937f](https://github.com/angular/angular/commit/daaf32937fd5c46e411b26f7c082613716fe9550) | fix | support prefix-insensitive DOM schema lookups and compile-time i18n attribute validation ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
| [dada86e43d](https://github.com/angular/angular/commit/dada86e43d847204f714d1a933084617ab941c0a) | fix | synchronize core sanitization schema with compiler ([#68925](https://github.com/angular/angular/pull/68925)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [582a417bd2](https://github.com/angular/angular/commit/582a417bd27fdaf989e5065dbcdf1ad752faf70c) | fix | exclude withCredentials requests from transfer cache |
|
||||
| [5c6d6df34b](https://github.com/angular/angular/commit/5c6d6df34bbeff3ce98f3b35875444f925cc8f51) | fix | skip TransferCache for cookie-bearing requests by default |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [37e8aadf87](https://github.com/angular/angular/commit/37e8aadf87b4facfcaf002a1557f8c393a362d97) | fix | prevent SSRF bypasses via backslash URLs in HttpClient |
|
||||
| [72696e244e](https://github.com/angular/angular/commit/72696e244ed7646cca9ab9afc7769a2163943bda) | fix | secure location and document initialization against SSRF and path hijack |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b8bd49341d](https://github.com/angular/angular/commit/b8bd49341ddcee10d119a9d4aa8e5736e4e5da53) | fix | Preserves explicit 'credentials: omit' in asset requests |
|
||||
| [ca32fc1000](https://github.com/angular/angular/commit/ca32fc10001301e6174804f9abcfba62252334f4) | fix | Preserves HTTP cache mode in asset group requests |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.14"></a>
|
||||
# 21.2.14 (2026-05-20)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [68282dff9f](https://github.com/angular/angular/commit/68282dff9f9ef46540cca4bd38fc1ab739c8a783) | fix | strip namespaced SVG script elements during template compilation |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c0f52272ed](https://github.com/angular/angular/commit/c0f52272ed337d4776bd4178cbbdc7f32037500f) | fix | do not insert todo when migrating void @Output |
|
||||
| [938a7f3edd](https://github.com/angular/angular/commit/938a7f3eddda97a39edb9edcc8b4dd970858b3a2) | fix | makes resource URL sanitizer lookup case-insensitive |
|
||||
| [0fb2724194](https://github.com/angular/angular/commit/0fb272419407a64a0a47096b03a911f4e7e83d79) | fix | reject script element as a dynamic component host |
|
||||
| [49113ac0ef](https://github.com/angular/angular/commit/49113ac0eff852d987b5acb28a9bbda0242842cd) | fix | visit ICU expressions in signal migration schematics |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [099bf577ee](https://github.com/angular/angular/commit/099bf577ee8f0bab60593a8fd2a1de7d298e3cd6) | fix | skip scroll-to-top on initial navigation when hydrating |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.13"></a>
|
||||
# 21.2.13 (2026-05-13)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c6553e97d](https://github.com/angular/angular/commit/1c6553e97d9655d8c48fbf625987fae86f9cd947) | fix | disallow event attribute bindings in host bindings unconditionally |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [629905d537](https://github.com/angular/angular/commit/629905d537f59dc3c264c49f6347e3599dea0215) | fix | add `allowedHosts` option to `renderModule` and `renderApplication` |
|
||||
| [0b7192f441](https://github.com/angular/angular/commit/0b7192f4410d055191ac9b15bff57d1d0b9a644f) | fix | forward BEFORE_APP_SERIALIZED errors to ErrorHandler |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.12"></a>
|
||||
# 21.2.12 (2026-05-06)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fe13bb669d](https://github.com/angular/angular/commit/fe13bb669d2bfab4713623d17b41c430aa0a61d8) | fix | allow explicit read generic with signal input transforms |
|
||||
| [3430251fef](https://github.com/angular/angular/commit/3430251fef93f6aec1fa9c7867e85df23f67c9a0) | fix | i18n flags leaking on errors |
|
||||
| [1aeebbe304](https://github.com/angular/angular/commit/1aeebbe3048b5aa612dd0a5448de9883ed51e7e8) | fix | respect ngSkipHydration on components with projectable nodes in LContainers |
|
||||
| [9e38ed7d57](https://github.com/angular/angular/commit/9e38ed7d5773a9193ba07afdba3f7a9f2fe02d18) | fix | sanitizer typings |
|
||||
| [7a05a9a71a](https://github.com/angular/angular/commit/7a05a9a71a5ab75042ec5560c01526de6e61e062) | fix | validate security-sensitive attributes in i18n bindings |
|
||||
| [c37f6ca42f](https://github.com/angular/angular/commit/c37f6ca42f263353cb9563fa90d7b31d3c7837ca) | fix | visit ng-let expression value in signal migration schematics |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [03ad53863b](https://github.com/angular/angular/commit/03ad53863bf3c368f0f02a4322d4141e8f70f674) | fix | prohibit concurrent submits in signal forms |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.11"></a>
|
||||
# 21.2.11 (2026-04-29)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [10ad3c0692](https://github.com/angular/angular/commit/10ad3c06923453ae0ec06b06e664ce05900a4ff6) | fix | prevent focus from scrollToAnchor |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4f5d8a2c0b](https://github.com/angular/angular/commit/4f5d8a2c0b5e38d4debc4293945270cea4a9590d) | fix | let declaration span not including end character |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a40e2cebc8](https://github.com/angular/angular/commit/a40e2cebc878965c3e21bfb61658f3f80cbd2ebf) | fix | fix ordering of view queries metadata in JIT mode |
|
||||
| [885a1a1d97](https://github.com/angular/angular/commit/885a1a1d9757adfa8766d9b369c848a277438c31) | fix | guard against non-object events and avoid listener wrapper identity mismatch |
|
||||
| [7a64aff9b5](https://github.com/angular/angular/commit/7a64aff9b59999077ea915486a7fa0b97a286659) | fix | prevent event replay double-invocation when element hydrates before app stability |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [be1f80a253](https://github.com/angular/angular/commit/be1f80a253b8ee27ed7d8de2287d6895c4821909) | fix | ensure origin has a trailing slash when parsing url |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.2.10"></a>
|
||||
# 21.2.10 (2026-04-22)
|
||||
### docs
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0d5ee9ae1b](https://github.com/angular/angular/commit/0d5ee9ae1ba4b7acd8f27a059a778f0b4bd8a5bd) | fix | link formatting in "Animating your Application with CSS" |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5533ab4f56](https://github.com/angular/angular/commit/5533ab4f56f574bc9365cf0573c4a34a3ab5aaf1) | fix | fix NgClass leaving trailing comma after removal |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [580212c995](https://github.com/angular/angular/commit/580212c995751c4bf4ce8a49df4167498743e0ea) | fix | restore internal URL on popstate when `browserUrl` is used |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<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
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [18003a33bb](https://github.com/angular/angular/commit/18003a33bb0d6bb09def8a0e5939fa24069696eb) | feat | add an 'outlet' injector option for ngTemplateOutlet |
|
||||
| [8bbe6dc46c](https://github.com/angular/angular/commit/8bbe6dc46c9dc13bafa81a60c7613b84b5ca3761) | feat | Add Location strategies to manage trailing slash on write |
|
||||
| [51cc914807](https://github.com/angular/angular/commit/51cc91480761b7275c15b5600381207f8ca00ee5) | feat | support height in ImageLoaderConfig and built-in loaders |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [72534e2a34](https://github.com/angular/angular/commit/72534e2a3458df4e1bb097973872f00bbb92be42) | feat | Add support for the `instanceof` binary operator |
|
||||
| [95b3f37d4a](https://github.com/angular/angular/commit/95b3f37d4a7d9a38f616d56df746dfcda3c2139b) | feat | Exhaustive checks for switch blocks |
|
||||
| [04ba09a8d9](https://github.com/angular/angular/commit/04ba09a8d9454013bebdd643eacb737642161952) | feat | support `AstVisitor.visitEmptyExpr()` |
|
||||
| [ce80136e7b](https://github.com/angular/angular/commit/ce80136e7b9f0024d49fce835cffa024c4505855) | fix | optimize away unnecessary restore/reset view calls |
|
||||
| [3242a61bae](https://github.com/angular/angular/commit/3242a61bae02253d13abb510b666376c665e61ac) | fix | variable counter visiting some expressions twice |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [473dd3e1cb](https://github.com/angular/angular/commit/473dd3e1cbd4fe3fa88ae4d5358eee35c11acb1b) | fix | attach source spans to object literal keys in TCB |
|
||||
| [a904d9f77b](https://github.com/angular/angular/commit/a904d9f77b56feab407f75f8d0527fa512d5dafb) | fix | support nested component declaration |
|
||||
| [2ea6dfc6c9](https://github.com/angular/angular/commit/2ea6dfc6c9ca11e96a2654510c980419899f8d04) | fix | update diagnostic to flag no-op arrow functions in listeners |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [8d5210c9fe](https://github.com/angular/angular/commit/8d5210c9fedd8abdd810d7a89ec7ee9a1234f5c1) | feat | add ChangeDetectionStrategy.Eager alias for Default |
|
||||
| [92d2498910](https://github.com/angular/angular/commit/92d2498910caed06c182b67e39726e1441418698) | feat | add host node to DeferBlockData ([#66546](https://github.com/angular/angular/pull/66546)) |
|
||||
| [ea2016a6dc](https://github.com/angular/angular/commit/ea2016a6dce58f95ecab7c773d5dcde274354e1a) | feat | add support for nested animations |
|
||||
| [81cabc1477](https://github.com/angular/angular/commit/81cabc14777a3b4966c29d60e1505aca8c29b71c) | feat | add support for TypeScript 6 |
|
||||
| [1ba9b7ac50](https://github.com/angular/angular/commit/1ba9b7ac5001b315cc9df78c518964dbf479d647) | feat | resource composition via snapshots |
|
||||
| [d9923b72a2](https://github.com/angular/angular/commit/d9923b72a20972ba6bf728d78f1afac6936ade18) | feat | support arrow functions in expressions |
|
||||
| [a7e8abbb7e](https://github.com/angular/angular/commit/a7e8abbb7e738ba338c3f50c76934c99925954e5) | fix | correctly handle SkipSelf when resolving from embedded view injector |
|
||||
| [0806ee3826](https://github.com/angular/angular/commit/0806ee38269b664f535e10d4d501b88370d3b44c) | fix | prevent animated element duplication with dynamic components in zoneless mode |
|
||||
| [ed78fa05c7](https://github.com/angular/angular/commit/ed78fa05c710ebafb355ae00a85b190a118b6cc4) | fix | Remove note to skip arrow functions in best practices |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f56bb07d83](https://github.com/angular/angular/commit/f56bb07d83a015b0ac12e74fdb0cf1550ff36b97) | feat | add field param to submit action and onInvalid |
|
||||
| [ba009b6031](https://github.com/angular/angular/commit/ba009b603119299a03f9d844f93882d42d47d150) | feat | add form directive |
|
||||
| [22afbb2f36](https://github.com/angular/angular/commit/22afbb2f36be89c2ae575df343571a918dec5985) | feat | add parsing support to native inputs ([#66917](https://github.com/angular/angular/pull/66917)) |
|
||||
| [95c386469c](https://github.com/angular/angular/commit/95c386469c7a2f09dd731601c2061bdb10d25717) | feat | Add passing focus options to form field |
|
||||
| [95ecce8334](https://github.com/angular/angular/commit/95ecce8334299defe55fb2b74264e5258ffd137c) | feat | allow setting submit options at form-level |
|
||||
| [ebae211add](https://github.com/angular/angular/commit/ebae211add37700858adeb8fc5d87bf503a59721) | feat | introduce parse errors in signal forms |
|
||||
| [3937afc316](https://github.com/angular/angular/commit/3937afc3167ce409eebb06d91d5fb122eea4e33d) | feat | introduce SignalFormControl for Reactive Forms compatibility |
|
||||
| [30f0914754](https://github.com/angular/angular/commit/30f09147545b67185f93efb9796e37c1db76733a) | feat | support binding null to number input ([#66917](https://github.com/angular/angular/pull/66917)) |
|
||||
| [dd208ca259](https://github.com/angular/angular/commit/dd208ca2595258fcd1e289374f812ce0b56c7011) | feat | update submit function to accept options object |
|
||||
| [27397b3f4f](https://github.com/angular/angular/commit/27397b3f4f3182ce00d6e2f8690285c316e2a274) | fix | clear parse errors when model updates ([#66917](https://github.com/angular/angular/pull/66917)) |
|
||||
| [63d8005703](https://github.com/angular/angular/commit/63d80057039928b3e878b59c1fe6b93ef1c6b701) | fix | preserve custom-control focus context in signal forms |
|
||||
| [631f60d1f9](https://github.com/angular/angular/commit/631f60d1f9be72cb68330308a6ff18cc195babb8) | fix | preserve parse errors when parse returns value |
|
||||
| [adfb83146b](https://github.com/angular/angular/commit/adfb83146b0c149734f43961563b389e00cc1d85) | fix | simplify design of parse errors |
|
||||
| [fb05fc86d0](https://github.com/angular/angular/commit/fb05fc86d0f12ffafd94c7c1420118d8a79f7e59) | fix | sort error summary by DOM order |
|
||||
| [567f292e8e](https://github.com/angular/angular/commit/567f292e8e0f9d2b5ddebadfa1c6d6dd6c456f39) | fix | support custom controls as host directives |
|
||||
| [bdfb60f3e3](https://github.com/angular/angular/commit/bdfb60f3e33065e047183dc1890c36e527e2b304) | fix | use consistent error format returned from parse |
|
||||
| [d75046bc09](https://github.com/angular/angular/commit/d75046bc091699bbadcb5f2032be627e983ee6fa) | fix | warn when showing hidden field state |
|
||||
### language-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ebc90c26f5](https://github.com/angular/angular/commit/ebc90c26f5ff1ba1e0ca9b775a44e301ebfb9473) | feat | Add completions and hover info for inline styles |
|
||||
| [26fd0839c3](https://github.com/angular/angular/commit/26fd0839c32d2ebeaa5e3ecc10ed70ab9ca17749) | feat | Add folding range support for inline styles |
|
||||
| [573aadef7e](https://github.com/angular/angular/commit/573aadef7eb8b6b5e83b82a16f95d2a556f27c01) | feat | Add quick info for inline styles |
|
||||
| [6fb39d9b62](https://github.com/angular/angular/commit/6fb39d9b62cbb634e95ec00fe5ef85d84da3bdbd) | feat | Support client-side file watching via `onDidChangeWatchedFiles` |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [496967e7b1](https://github.com/angular/angular/commit/496967e7b13dfe1ebdde69724cd62880914beb60) | feat | add JSON schema for angularCompilerOptions |
|
||||
| [8c21866f49](https://github.com/angular/angular/commit/8c21866f49ff74344551395ae0a5df1841d54c0d) | feat | add linked editing ranges for HTML tag synchronization |
|
||||
| [d2137928e8](https://github.com/angular/angular/commit/d2137928e8f075527016a3c011dd8efc4d4e1ebd) | perf | use lightweight project warmup for Angular analysis |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b51bab583d](https://github.com/angular/angular/commit/b51bab583d84e38f16dea489e4119edc34e2a491) | feat | Add partial ActivatedRouteSnapshot information to `canMatch` params |
|
||||
| [cf9620f7d0](https://github.com/angular/angular/commit/cf9620f7d072897f13b7f281b7bca6f51f69cfd0) | feat | Make match options optional in isActive |
|
||||
| [907a94dcec](https://github.com/angular/angular/commit/907a94dcec2926a5c7d0c4d36249bd62e31a2ae3) | feat | Update `IsActiveMatchOptions` APIs to accept a Partial |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.1.5"></a>
|
||||
# 21.1.5 (2026-02-18)
|
||||
No user facing changes in this release
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.1.4"></a>
|
||||
# 21.1.4 (2026-02-11)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [caab23dfe6](https://github.com/angular/angular/commit/caab23dfe6acf06c3b859af091f5e078b08f1c4c) | fix | add geolocation element to schema |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2b99eaa019](https://github.com/angular/angular/commit/2b99eaa019b5551a2e2fcf9ff8cd0a796e1e857b) | fix | capture animation dependencies eagerly to avoid destroyed injector |
|
||||
| [d6aeac504c](https://github.com/angular/angular/commit/d6aeac504c6181f15e5d8afdca3d9c3e3b32652c) | fix | Fix flakey test due to document injection |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0d1acd0165](https://github.com/angular/angular/commit/0d1acd0165511b57ce853f29486d9b92d0215959) | feat | support signal-based schemas in validateStandardSchema |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3905015ccc](https://github.com/angular/angular/commit/3905015ccc53399a606dd8e4f3c4d0cce628a08e) | fix | correctly parse ArrayBuffer and Blob in transfer cache |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.1.3"></a>
|
||||
# 21.1.3 (2026-02-04)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2b254bc050](https://github.com/angular/angular/commit/2b254bc0508b73aab8991c3b1a9a703c339cb735) | fix | `linkedSignal.update` should propagate errors |
|
||||
| [e5110b4fa1](https://github.com/angular/angular/commit/e5110b4fa155e4669ed507f3460d2d29026a28ab) | fix | export DirectiveWithBindings |
|
||||
| [2cf4da0ea1](https://github.com/angular/angular/commit/2cf4da0ea11f5746eb7ae4dfd775f757576e4d98) | fix | hold constructors weakly in DepsTracker cache |
|
||||
| [70a5b651be](https://github.com/angular/angular/commit/70a5b651be29f1421eb25150b560bfe154aad6bc) | fix | prevent element duplication with dynamic components |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6f75b6e3f6](https://github.com/angular/angular/commit/6f75b6e3f60dc2a4f33e13562649931dc95eb52b) | fix | Resolves debounce promise on abort in debounceForDuration |
|
||||
### localize
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4c7126d23b](https://github.com/angular/angular/commit/4c7126d23be3e43b1d5bd6f2fb13119d185c3682) | fix | add support for unit-test builder in ng-add schematic |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d6268c0bbb](https://github.com/angular/angular/commit/d6268c0bbbdc92abaaaeb8eebee3bc45decab9c9) | fix | limit UrlParser recursion depth to prevent stack overflow |
|
||||
| [49a36f4cc7](https://github.com/angular/angular/commit/49a36f4cc7254420bc34fff4e0f0242e00970280) | perf | Use .bind to avoid holding other closures in memory |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.1.2"></a>
|
||||
# 21.1.2 (2026-01-28)
|
||||
### forms
|
||||
@@ -544,20 +83,6 @@ No user facing changes in this release
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.0.9"></a>
|
||||
# 21.0.9 (2026-01-14)
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [82d556a8fb](https://github.com/angular/angular/commit/82d556a8fb38cd2024e3d098c55254305ba12b6b) | fix | Ensure the control instruction comes after the other bindings |
|
||||
| [0055f3cc79](https://github.com/angular/angular/commit/0055f3cc79f387b8dec6ce5e1a33fad5486f9341) | fix | Rename signal form [field] to [formField] |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e4bfa5c9e7](https://github.com/angular/angular/commit/e4bfa5c9e7feec48d3c4e9425a21a2ccf6532bdb) | fix | prevent duplicate imports in common-to-standalone migration |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.0.8"></a>
|
||||
# 21.0.8 (2026-01-08)
|
||||
### core
|
||||
|
||||
+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 a minimum set of expectations that are required of any community contribution pull request that is opened.
|
||||
We strongly value open source contribution and pull requests from community contributors. Please note that every pull request is reviewed and merged by an actual person on the team, which does take time and effort. That is time and effort that does take away from other valuable work. With that in mind we have an minimum set of expectations that are required of any community contribution pull request that is opened.
|
||||
|
||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR that relates to your submission.
|
||||
- You don't want to duplicate existing efforts.
|
||||
@@ -238,7 +238,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
- For corporations, we'll need you to
|
||||
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
|
||||
|
||||
If you have more than one GitHub account, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests.
|
||||
If you have more than one GitHub accounts, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests.
|
||||
|
||||
The following documents can help you sort out issues with GitHub accounts and multiple email addresses:
|
||||
|
||||
|
||||
+34
-31
@@ -5,42 +5,42 @@ module(
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.4")
|
||||
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 = "rules_nodejs", version = "6.7.3")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.8.3")
|
||||
bazel_dep(name = "aspect_rules_js", version = "2.9.2")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0")
|
||||
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.2")
|
||||
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "bazel_lib", version = "3.3.1")
|
||||
bazel_dep(name = "tar.bzl", version = "0.10.1")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.6")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
|
||||
bazel_dep(name = "tar.bzl", version = "0.7.0")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.4")
|
||||
bazel_dep(name = "rules_angular")
|
||||
git_override(
|
||||
module_name = "rules_angular",
|
||||
commit = "b1d295334e70335dab7ac9984a989fae0a9c9dc2",
|
||||
remote = "https://github.com/angular/rules_angular.git",
|
||||
commit = "e975faa54a52caa45ff932ec806292ce0988d8d4",
|
||||
remote = "https://github.com/devversion/rules_angular.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "devinfra")
|
||||
git_override(
|
||||
module_name = "devinfra",
|
||||
commit = "48c48fa3848de5bb0ec1c3203558f099765165ab",
|
||||
commit = "1d545550f0a93b2ffee3672587314d6f6b3dc7fd",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_sass")
|
||||
git_override(
|
||||
module_name = "rules_sass",
|
||||
commit = "a65d937fabd87672f7245aeaa5eb5a0c5a2abead",
|
||||
remote = "https://github.com/angular/rules_sass.git",
|
||||
commit = "1184a80751a21af8348f308abc5b38a41f26850e",
|
||||
remote = "https://github.com/devversion/rules_sass.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_browsers")
|
||||
git_override(
|
||||
module_name = "rules_browsers",
|
||||
commit = "b03f09ef28a08f8ae07482851cf5ecbf6ac23a2a",
|
||||
remote = "https://github.com/angular/rules_browsers.git",
|
||||
commit = "5f0e50d17d15c70e6ab5546f1659338ae94c4072",
|
||||
remote = "https://github.com/devversion/rules_browsers.git",
|
||||
)
|
||||
|
||||
yq = use_extension("@yq.bzl//yq:extensions.bzl", "yq")
|
||||
@@ -49,15 +49,15 @@ use_repo(yq, "yq_toolchains")
|
||||
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
|
||||
node.toolchain(
|
||||
node_repositories = {
|
||||
"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"),
|
||||
"22.22.0-darwin_arm64": ("node-v22.22.0-darwin-arm64.tar.gz", "node-v22.22.0-darwin-arm64", "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"),
|
||||
"22.22.0-darwin_amd64": ("node-v22.22.0-darwin-x64.tar.gz", "node-v22.22.0-darwin-x64", "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"),
|
||||
"22.22.0-linux_arm64": ("node-v22.22.0-linux-arm64.tar.xz", "node-v22.22.0-linux-arm64", "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"),
|
||||
"22.22.0-linux_ppc64le": ("node-v22.22.0-linux-ppc64le.tar.xz", "node-v22.22.0-linux-ppc64le", "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"),
|
||||
"22.22.0-linux_s390x": ("node-v22.22.0-linux-s390x.tar.xz", "node-v22.22.0-linux-s390x", "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"),
|
||||
"22.22.0-linux_amd64": ("node-v22.22.0-linux-x64.tar.xz", "node-v22.22.0-linux-x64", "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"),
|
||||
"22.22.0-windows_amd64": ("node-v22.22.0-win-x64.zip", "node-v22.22.0-win-x64", "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"),
|
||||
},
|
||||
node_version = "22.22.2",
|
||||
node_version = "22.22.0",
|
||||
)
|
||||
use_repo(node, "nodejs_toolchains")
|
||||
use_repo(node, "nodejs_darwin_amd64")
|
||||
@@ -71,8 +71,8 @@ use_repo(node, "nodejs_windows_amd64")
|
||||
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
|
||||
pnpm.pnpm(
|
||||
name = "pnpm",
|
||||
pnpm_version = "10.33.2",
|
||||
pnpm_version_integrity = "sha512-qQ+vb+6rca1sblf5Tg/hoS9dzCLNdU20CulZPraj4LaxLjVAIYuzeuCDQEsfLObbKkEh6XmCm0r/lLmfSdoc+A==",
|
||||
pnpm_version = "10.28.2",
|
||||
pnpm_version_integrity = "sha512-QYcvA3rSL3NI47Heu69+hnz9RI8nJtnPdMCPGVB8MdLI56EVJbmD/rwt9kC1Q43uYCPrsfhO1DzC1lTSvDJiZA==",
|
||||
)
|
||||
use_repo(pnpm, "pnpm")
|
||||
|
||||
@@ -83,6 +83,7 @@ npm.npm_translate_lock(
|
||||
"//:.pnpmfile.cjs",
|
||||
"//:package.json",
|
||||
"//:pnpm-workspace.yaml",
|
||||
"//:tools/pnpm-patches/dagre-d3-es+7.0.11.patch",
|
||||
"//adev:package.json",
|
||||
"//adev/shared-docs:package.json",
|
||||
"//adev/shared-docs/pipeline/api-gen:package.json",
|
||||
@@ -117,7 +118,7 @@ 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.1-rc | jq -r '.dist.integrity'
|
||||
# Obtained by: curl --silent https://registry.npmjs.org/typescript/5.9.3 | jq -r '.dist.integrity'
|
||||
ts_integrity = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
ts_version = "5.9.3",
|
||||
)
|
||||
@@ -125,12 +126,14 @@ use_repo(rules_ts_ext, **{"npm_typescript": "angular_npm_typescript"})
|
||||
|
||||
# TODO: Figure out how to make ng_project update whenever the packages/core::pkg target changes.
|
||||
rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular")
|
||||
rules_angular.setup(
|
||||
name = "angular_rules_angular_configurable_deps",
|
||||
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
|
||||
typescript = "//:node_modules/typescript",
|
||||
|
||||
use_repo_rule("@rules_angular//setup:repositories.bzl", "configurable_deps_repo")(
|
||||
name = "rules_angular_configurable_deps",
|
||||
angular_compiler_cli = "@angular//:node_modules/@angular/compiler-cli",
|
||||
typescript = "@angular//:node_modules/typescript",
|
||||
)
|
||||
use_repo(rules_angular, rules_angular_configurable_deps = "angular_rules_angular_configurable_deps")
|
||||
|
||||
override_repo(rules_angular, "rules_angular_configurable_deps")
|
||||
|
||||
register_toolchains(
|
||||
"@devinfra//bazel/git-toolchain:git_linux_toolchain",
|
||||
|
||||
Generated
+496
-353
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@ Install the Angular CLI globally:
|
||||
npm install -g @angular/cli
|
||||
```
|
||||
|
||||
Create a workspace:
|
||||
Create workspace:
|
||||
|
||||
```
|
||||
ng new [PROJECT NAME]
|
||||
|
||||
@@ -60,7 +60,6 @@ APPLICATION_DEPS = [
|
||||
":node_modules/@lezer/highlight",
|
||||
":node_modules/@lezer/javascript",
|
||||
":node_modules/@stackblitz/sdk",
|
||||
":node_modules/shiki",
|
||||
":node_modules/@typescript/vfs",
|
||||
":node_modules/@webcontainer/api",
|
||||
":node_modules/@xterm/addon-fit",
|
||||
@@ -92,30 +91,6 @@ APPLICATION_DEPS = [
|
||||
":node_modules/crelt",
|
||||
":node_modules/style-mod",
|
||||
":node_modules/w3c-keyname",
|
||||
#
|
||||
":node_modules/@shikijs/themes",
|
||||
":node_modules/@shikijs/langs",
|
||||
":node_modules/@shikijs/engine-oniguruma",
|
||||
":node_modules/@shikijs/core",
|
||||
":node_modules/@shikijs/engine-javascript",
|
||||
":node_modules/@shikijs/types",
|
||||
":node_modules/@shikijs/vscode-textmate",
|
||||
":node_modules/hast-util-to-html",
|
||||
# ":node_modules/oniguruma-to-es",
|
||||
":node_modules/ccount",
|
||||
":node_modules/comma-separated-tokens",
|
||||
":node_modules/hast-util-whitespace",
|
||||
":node_modules/html-void-elements",
|
||||
# ":node_modules/oniguruma-parser",
|
||||
":node_modules/property-information",
|
||||
# ":node_modules/regex-recursion",
|
||||
# ":node_modules/regex",
|
||||
":node_modules/space-separated-tokens",
|
||||
":node_modules/stringify-entities",
|
||||
":node_modules/zwitch",
|
||||
":node_modules/character-entities-html4",
|
||||
":node_modules/character-entities-legacy",
|
||||
# ":node_modules/regex-utilities",
|
||||
]
|
||||
|
||||
TEST_FILES = APPLICATION_FILES + glob(["src/app/**/*.spec.ts"])
|
||||
|
||||
+30
-48
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@algolia/client-common": "5.48.0",
|
||||
"@algolia/client-search": "5.48.0",
|
||||
"@algolia/requester-browser-xhr": "5.48.0",
|
||||
"@algolia/requester-node-http": "5.48.0",
|
||||
"@algolia/client-common": "5.46.2",
|
||||
"@algolia/client-search": "5.46.2",
|
||||
"@algolia/requester-browser-xhr": "5.46.2",
|
||||
"@algolia/requester-node-http": "5.46.2",
|
||||
"@angular/animations": "workspace:*",
|
||||
"@angular/aria": "21.2.9",
|
||||
"@angular/build": "21.2.9",
|
||||
"@angular/cdk": "21.2.9",
|
||||
"@angular/cli": "21.2.9",
|
||||
"@angular/aria": "21.1.1",
|
||||
"@angular/build": "21.1.2",
|
||||
"@angular/cdk": "21.1.1",
|
||||
"@angular/cli": "21.1.2",
|
||||
"@angular/common": "workspace:*",
|
||||
"@angular/compiler": "workspace:*",
|
||||
"@angular/compiler-cli": "workspace:*",
|
||||
"@angular/core": "workspace:*",
|
||||
"@angular/docs": "workspace:*",
|
||||
"@angular/forms": "workspace:*",
|
||||
"@angular/material": "21.2.9",
|
||||
"@angular/material": "21.1.1",
|
||||
"@angular/platform-browser": "workspace:*",
|
||||
"@angular/platform-server": "workspace:*",
|
||||
"@angular/router": "workspace:*",
|
||||
"@angular/ssr": "21.2.9",
|
||||
"@angular/ssr": "21.1.2",
|
||||
"@codemirror/autocomplete": "6.20.0",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/lang-angular": "0.1.4",
|
||||
@@ -28,75 +28,57 @@
|
||||
"@codemirror/lang-javascript": "6.2.4",
|
||||
"@codemirror/lang-sass": "6.0.2",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/lint": "6.9.3",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/state": "6.5.4",
|
||||
"@codemirror/view": "6.39.12",
|
||||
"@lezer/common": "1.5.1",
|
||||
"@codemirror/lint": "6.9.2",
|
||||
"@codemirror/search": "6.5.11",
|
||||
"@codemirror/state": "6.5.3",
|
||||
"@codemirror/view": "6.39.9",
|
||||
"@lezer/common": "1.5.0",
|
||||
"@lezer/css": "1.3.0",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lezer/html": "1.3.13",
|
||||
"@lezer/javascript": "1.5.4",
|
||||
"@lezer/lr": "1.4.8",
|
||||
"@lezer/lr": "1.4.7",
|
||||
"@lezer/sass": "1.1.0",
|
||||
"@marijn/find-cluster-break": "1.0.2",
|
||||
"@shikijs/core": "^3.21.0",
|
||||
"@shikijs/engine-javascript": "^3.21.0",
|
||||
"@shikijs/engine-oniguruma": "^3.21.0",
|
||||
"@shikijs/langs": "^3.21.0",
|
||||
"@shikijs/themes": "^3.21.0",
|
||||
"@shikijs/types": "^3.21.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@stackblitz/sdk": "1.11.0",
|
||||
"@types/dom-navigation": "1.0.6",
|
||||
"@types/jasmine": "6.0.0",
|
||||
"@types/jasmine": "5.1.13",
|
||||
"@types/jsdom": "27.0.0",
|
||||
"@types/node": "24.10.11",
|
||||
"@types/node": "24.10.4",
|
||||
"@typescript/vfs": "1.6.2",
|
||||
"@webcontainer/api": "1.6.1",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/xterm": "6.0.0",
|
||||
"algoliasearch": "5.48.0",
|
||||
"algoliasearch": "5.46.2",
|
||||
"angular-split": "20.0.0",
|
||||
"ccount": "^2.0.1",
|
||||
"character-entities-html4": "^2.1.0",
|
||||
"character-entities-legacy": "^3.0.0",
|
||||
"comma-separated-tokens": "^2.0.3",
|
||||
"crelt": "1.0.6",
|
||||
"diff": "8.0.3",
|
||||
"diff": "8.0.2",
|
||||
"emoji-regex": "10.6.0",
|
||||
"fflate": "0.8.2",
|
||||
"hast-util-to-html": "^9.0.5",
|
||||
"hast-util-whitespace": "^3.0.0",
|
||||
"html-void-elements": "^3.0.0",
|
||||
"jsdom": "28.0.0",
|
||||
"jasmine-core": "6.1.0",
|
||||
"jasmine-core": "5.13.0",
|
||||
"jsdom": "27.4.0",
|
||||
"karma-chrome-launcher": "3.2.0",
|
||||
"karma-coverage": "2.2.1",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-jasmine-html-reporter": "2.2.0",
|
||||
"karma-jasmine-html-reporter": "2.1.0",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"ngx-progressbar": "14.0.0",
|
||||
"open-in-idx": "0.1.1",
|
||||
"playwright-core": "1.58.1",
|
||||
"preact": "10.28.3",
|
||||
"preact-render-to-string": "6.6.6",
|
||||
"prettier": "3.8.1",
|
||||
"property-information": "^7.1.0",
|
||||
"playwright-core": "1.57.0",
|
||||
"preact": "10.28.2",
|
||||
"preact-render-to-string": "6.6.5",
|
||||
"prettier": "3.8.0",
|
||||
"rxjs": "7.8.2",
|
||||
"shiki": "3.22.0",
|
||||
"space-separated-tokens": "^2.0.2",
|
||||
"stringify-entities": "^4.0.4",
|
||||
"shiki": "3.21.0",
|
||||
"style-mod": "4.1.3",
|
||||
"tinyglobby": "0.2.15",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.9.3",
|
||||
"w3c-keyname": "2.2.8",
|
||||
"zwitch": "^2.0.4"
|
||||
"w3c-keyname": "2.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.24",
|
||||
"autoprefixer": "10.4.23",
|
||||
"karma": "~6.4.4",
|
||||
"postcss": "8.5.6",
|
||||
"tailwindcss": "3.4.19"
|
||||
|
||||
@@ -19,8 +19,7 @@ ts_project(
|
||||
tsconfig = {
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"moduleResolution": "node",
|
||||
"types": ["node"],
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": True,
|
||||
@@ -29,7 +28,6 @@ ts_project(
|
||||
deps = [
|
||||
"//adev:node_modules/@angular/docs",
|
||||
"//adev:node_modules/@types/node",
|
||||
"//adev/shared-docs/pipeline/shared:heading",
|
||||
"//adev/src/app/routing/navigation-entries",
|
||||
"//adev/src/content/reference/errors:route-nav-items",
|
||||
"//adev/src/content/reference/extended-diagnostics:route-nav-items",
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries/index.js';
|
||||
import {getIdFromHeading} from '../../shared-docs/pipeline/shared/heading.mjs';
|
||||
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries';
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {writeFileSync, readFileSync} from 'fs';
|
||||
import {join, resolve} from 'path';
|
||||
@@ -73,3 +72,11 @@ function main() {
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
// TODO: refactor so this function is shared with the generation pipeline (adev/shared-docs/pipeline/shared/marked/transformations/heading.mts)
|
||||
function getIdFromHeading(heading: string): string {
|
||||
return heading
|
||||
.toLowerCase()
|
||||
.replace(/\s|\//g, '-') // replace spaces and slashes with dashes
|
||||
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import {get} from 'node:https';
|
||||
import {posix} from 'node:path';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/repos/';
|
||||
const SHA_REGEX = /^[0-9a-f]{40}$/i;
|
||||
const BRANCH_REGEX = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
|
||||
|
||||
export class GithubClient {
|
||||
#token;
|
||||
@@ -32,12 +30,6 @@ export class GithubClient {
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async getAffectedFiles(baseSha, headSha) {
|
||||
if (!SHA_REGEX.test(baseSha)) {
|
||||
throw new Error(`Invalid base SHA: ${baseSha}`);
|
||||
}
|
||||
if (!SHA_REGEX.test(headSha)) {
|
||||
throw new Error(`Invalid head SHA: ${headSha}`);
|
||||
}
|
||||
const {files} = JSON.parse(await this.#httpGet(`${this.#api}/compare/${baseSha}...${headSha}`));
|
||||
return files.map((f) => f.filename);
|
||||
}
|
||||
@@ -49,9 +41,6 @@ export class GithubClient {
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
async getShaForBranch(branch) {
|
||||
if (!BRANCH_REGEX.test(branch)) {
|
||||
throw new Error(`Invalid branch name: ${branch}`);
|
||||
}
|
||||
const sha = await this.#httpGet(`${this.#api}/commits/${branch}`, {
|
||||
headers: {Accept: 'application/vnd.github.VERSION.sha'},
|
||||
});
|
||||
@@ -60,7 +49,7 @@ export class GithubClient {
|
||||
throw new Error(`Unable to extract the SHA for '${branch}'.`);
|
||||
}
|
||||
|
||||
return sha.trim();
|
||||
return sha;
|
||||
}
|
||||
|
||||
#httpGet(url, options = {}) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
//tslint:disable:no-console
|
||||
import assert from 'node:assert';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {execSync} from 'node:child_process';
|
||||
import {existsSync, constants as fsConstants} from 'node:fs';
|
||||
import {
|
||||
copyFile,
|
||||
@@ -41,16 +41,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
await readFile(buildInfoPath, 'utf-8'),
|
||||
);
|
||||
|
||||
const shaRegex = /^[0-9a-f]{40}$/i;
|
||||
const branchRegex = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
|
||||
|
||||
if (!shaRegex.test(storedSha)) {
|
||||
throw new Error(`Invalid SHA in build info: ${storedSha}`);
|
||||
}
|
||||
if (!branchRegex.test(storedBranch)) {
|
||||
throw new Error(`Invalid branch name in build info: ${storedBranch}`);
|
||||
}
|
||||
|
||||
assert(process.env.ANGULAR_READONLY_GITHUB_TOKEN);
|
||||
const githubApi = new GithubClient(
|
||||
repo,
|
||||
@@ -73,10 +63,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
downstreamBranch = storedBranch;
|
||||
}
|
||||
|
||||
if (!shaRegex.test(latestSha)) {
|
||||
throw new Error(`Invalid SHA resolved: ${latestSha}`);
|
||||
}
|
||||
|
||||
console.log(`Comparing ${storedSha}...${latestSha}.`);
|
||||
const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
|
||||
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
|
||||
@@ -92,18 +78,14 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
|
||||
try {
|
||||
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
|
||||
execFileSync('git', ['init'], execOptions);
|
||||
execFileSync(
|
||||
'git',
|
||||
['remote', 'add', 'origin', `https://github.com/${repo}.git`],
|
||||
execOptions,
|
||||
);
|
||||
execSync('git init', execOptions);
|
||||
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
|
||||
// fetch a commit
|
||||
execFileSync('git', ['fetch', 'origin', latestSha], execOptions);
|
||||
execSync(`git fetch origin ${latestSha}`, execOptions);
|
||||
// reset this repository's main branch to the commit of interest
|
||||
execFileSync('git', ['reset', '--hard', 'FETCH_HEAD'], execOptions);
|
||||
execSync('git reset --hard FETCH_HEAD', execOptions);
|
||||
// get sha when files where changed
|
||||
shaWhenFilesChanged = execFileSync('git', ['rev-list', '-1', latestSha, `${assetsPath}/`], {
|
||||
shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
|
||||
encoding: 'utf8',
|
||||
cwd: temporaryDir,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
[href]="item.path"
|
||||
target="_blank"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -53,9 +53,9 @@
|
||||
matrixParams: 'ignored',
|
||||
fragment: 'ignored',
|
||||
}"
|
||||
(click)="emitClickOnLink(item)"
|
||||
(click)="emitClickOnLink()"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -76,7 +76,7 @@
|
||||
<div
|
||||
class="docs-secondary-nav-header"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -106,7 +106,7 @@
|
||||
item.children && item.level === expandableLevel() && !item.isExpanded
|
||||
"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100dvh;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-block: 1.5rem;
|
||||
|
||||
@@ -153,6 +153,5 @@ describe('NavigationList', () => {
|
||||
class FakeNavigationListState {
|
||||
isOpened = signal(true);
|
||||
activeNavigationItem = signal(navigationItems.at(1));
|
||||
crossCategoryOrigin = signal<NavigationItem | undefined>(undefined);
|
||||
toggleItem(item: NavigationItem) {}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,7 @@ export class NavigationList {
|
||||
|
||||
readonly linkClicked = output<void>();
|
||||
|
||||
protected readonly labelTruncationThreshold = 27;
|
||||
|
||||
private readonly navigationState = inject(NavigationState);
|
||||
private readonly crossCategoryOrigin = this.navigationState.crossCategoryOrigin;
|
||||
|
||||
readonly activeItem = this.navigationState.activeNavigationItem;
|
||||
|
||||
@@ -54,19 +51,10 @@ export class NavigationList {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const prevParentItem = this.crossCategoryOrigin();
|
||||
if (prevParentItem) {
|
||||
this.crossCategoryOrigin.set(undefined);
|
||||
this.navigationState.toggleItem(prevParentItem);
|
||||
return;
|
||||
}
|
||||
this.navigationState.toggleItem(item);
|
||||
}
|
||||
|
||||
emitClickOnLink(item: NavigationItem): void {
|
||||
if (item.isCrossReferenced) {
|
||||
this.crossCategoryOrigin.set(item.parent);
|
||||
}
|
||||
emitClickOnLink(): void {
|
||||
this.linkClicked.emit();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,16 +27,14 @@
|
||||
</i>
|
||||
</span>
|
||||
|
||||
<!-- Page title and package badge -->
|
||||
<span class="docs-search-result__label__text">
|
||||
<span [innerHtml]="result.labelHtml"></span>
|
||||
@if (result.package) {
|
||||
<span
|
||||
[innerHTML]="result.package"
|
||||
class="docs-search-result__label__package"
|
||||
></span>
|
||||
}
|
||||
</span>
|
||||
<!-- Page title -->
|
||||
<span [innerHtml]="result.labelHtml"></span>
|
||||
@if (result.package) {
|
||||
<span
|
||||
[innerHTML]="result.package"
|
||||
class="docs-search-result__label__package"
|
||||
></span>
|
||||
}
|
||||
</p>
|
||||
|
||||
@if (result.subLabelHtml) {
|
||||
|
||||
@@ -46,7 +46,6 @@ dialog {
|
||||
|
||||
.docs-search-result-icon {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
@@ -92,20 +91,7 @@ dialog {
|
||||
|
||||
&__label {
|
||||
font-weight: 600;
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem 0.5rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__package {
|
||||
font-size: 0.75rem;
|
||||
@@ -151,14 +137,14 @@ dialog {
|
||||
.docs-search-results__start-typing,
|
||||
.docs-search-results__no-results {
|
||||
padding: 0.75rem;
|
||||
color: var(--quaternary-contrast);
|
||||
color: var(--gray-400);
|
||||
}
|
||||
|
||||
.docs-search-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--quaternary-contrast);
|
||||
color: var(--gray-400);
|
||||
padding: 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -95,6 +95,9 @@ export class SearchDialog {
|
||||
if (!this.dialog().nativeElement.open) {
|
||||
this.dialog().nativeElement.showModal?.();
|
||||
}
|
||||
// We want to select the pre-existing text on opening
|
||||
// In order to change the search input with minimal user interaction.
|
||||
this.searchForm().focusBoundControl();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ ng_project(
|
||||
"//adev/shared-docs/components:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//adev:node_modules/@angular/aria",
|
||||
"//adev:node_modules/@angular/common",
|
||||
"//adev:node_modules/@angular/core",
|
||||
"//adev:node_modules/@angular/forms",
|
||||
|
||||
@@ -1,61 +1,10 @@
|
||||
<div ngCombobox #outerCombobox="ngCombobox" [readonly]="true" [disabled]="disabled()">
|
||||
<div class="docs-select-input-container">
|
||||
<input
|
||||
ngComboboxInput
|
||||
[attr.id]="id()"
|
||||
[attr.name]="name()"
|
||||
[value]="displayValue()"
|
||||
placeholder="Select an option..."
|
||||
readonly
|
||||
/>
|
||||
<span class="material-symbols-outlined docs-select-arrow" translate="no" aria-hidden="true"
|
||||
>arrow_drop_down</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<ng-template ngComboboxPopupContainer>
|
||||
<dialog ngComboboxDialog class="docs-select-dialog">
|
||||
<div ngCombobox #innerCombobox="ngCombobox" filterMode="manual" [alwaysExpanded]="true">
|
||||
<div class="docs-select-search-container">
|
||||
<span
|
||||
class="material-symbols-outlined docs-select-search-icon"
|
||||
translate="no"
|
||||
aria-hidden="true"
|
||||
>search</span
|
||||
>
|
||||
<input
|
||||
ngComboboxInput
|
||||
class="docs-select-search-input"
|
||||
placeholder="Search..."
|
||||
[(value)]="searchString"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ng-template ngComboboxPopupContainer>
|
||||
@if (filteredOptions().length === 0) {
|
||||
<div class="docs-select-no-results">No results found</div>
|
||||
}
|
||||
|
||||
<div ngListbox [(values)]="selectedValues" class="docs-select-listbox">
|
||||
@for (option of filteredOptions(); track option.value) {
|
||||
<div
|
||||
ngOption
|
||||
[value]="option.value"
|
||||
[label]="option.label"
|
||||
class="docs-select-option"
|
||||
>
|
||||
<span class="docs-select-option-label">{{ option.label }}</span>
|
||||
<span
|
||||
class="material-symbols-outlined docs-select-check-icon"
|
||||
translate="no"
|
||||
aria-hidden="true"
|
||||
>check</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</dialog>
|
||||
</ng-template>
|
||||
</div>
|
||||
<select
|
||||
[attr.id]="id()"
|
||||
[attr.name]="name()"
|
||||
[value]="value()"
|
||||
(change)="setOption($any($event.target).value)"
|
||||
>
|
||||
@for (item of options(); track item) {
|
||||
<option [value]="item.value" [selected]="value() === item.value">{{ item.label }}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
:host {
|
||||
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
|
||||
}
|
||||
|
||||
[ngCombobox] {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.docs-select-input-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
[ngComboboxInput] {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
[ngComboboxInput][readonly='true'] {
|
||||
cursor: pointer;
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
[ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) {
|
||||
outline: 1.5px solid var(--vivid-pink);
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent);
|
||||
}
|
||||
|
||||
.docs-select-arrow {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
pointer-events: none;
|
||||
padding: 0 0.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
[ngComboboxInput][aria-expanded='true'] + .docs-select-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[ngComboboxInput] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
padding: 0.7rem 1rem 0.7rem 2.5rem;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
.docs-select-dialog {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: auto;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: inherit;
|
||||
|
||||
.docs-select-input-container {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[ngCombobox],
|
||||
.docs-select-input-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
[ngComboboxInput] {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
[ngCombobox]:focus-within [ngComboboxInput] {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.docs-select-search-input:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.docs-select-search-container {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-select-search-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.docs-select-search-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
padding: 0 0.5rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.docs-select-search-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2rem;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
.docs-select-no-results {
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: var(--primary-contrast);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.docs-select-listbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 12rem;
|
||||
overflow: auto;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.docs-select-option {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
|
||||
}
|
||||
|
||||
&[data-active='true'] {
|
||||
outline-offset: -2px;
|
||||
outline: 2px solid var(--vivid-pink);
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
color: var(--vivid-pink);
|
||||
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
|
||||
}
|
||||
|
||||
&:not([aria-selected='true']) .docs-select-check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-select-option-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.docs-select-check-icon {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@@ -6,26 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxDialog,
|
||||
ComboboxInput,
|
||||
ComboboxPopupContainer,
|
||||
} from '@angular/aria/combobox';
|
||||
import {Listbox, Option} from '@angular/aria/listbox';
|
||||
import {
|
||||
afterRenderEffect,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
model,
|
||||
signal,
|
||||
untracked,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, input, model} from '@angular/core';
|
||||
import {FormValueControl} from '@angular/forms/signals';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
type SelectOptionValue = string | number | boolean;
|
||||
|
||||
@@ -37,17 +19,11 @@ export interface SelectOption {
|
||||
@Component({
|
||||
selector: 'docs-select',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
Combobox,
|
||||
ComboboxDialog,
|
||||
ComboboxInput,
|
||||
ComboboxPopupContainer,
|
||||
FormsModule,
|
||||
Listbox,
|
||||
Option,
|
||||
],
|
||||
templateUrl: './select.component.html',
|
||||
styleUrls: ['./select.component.scss'],
|
||||
host: {
|
||||
class: 'docs-form-element',
|
||||
},
|
||||
})
|
||||
export class Select implements FormValueControl<string | null> {
|
||||
readonly value = model<string | null>(null);
|
||||
@@ -57,66 +33,11 @@ export class Select implements FormValueControl<string | null> {
|
||||
readonly options = input.required<SelectOption[]>();
|
||||
readonly disabled = input(false);
|
||||
|
||||
readonly dialog = viewChild(ComboboxDialog);
|
||||
readonly listbox = viewChild<Listbox<SelectOptionValue>>(Listbox);
|
||||
readonly combobox = viewChild<Combobox<SelectOptionValue>>(Combobox);
|
||||
|
||||
readonly searchString = signal('');
|
||||
|
||||
readonly filteredOptions = computed(() => {
|
||||
const search = this.searchString().toLowerCase();
|
||||
if (!search) {
|
||||
return this.options();
|
||||
}
|
||||
return this.options().filter((option) => option.label.toLowerCase().includes(search));
|
||||
});
|
||||
|
||||
readonly displayValue = computed(() => {
|
||||
const currentValue = this.value();
|
||||
if (currentValue === null) {
|
||||
return '';
|
||||
}
|
||||
const option = this.options().find((opt) => opt.value === currentValue);
|
||||
return option ? option.label : '';
|
||||
});
|
||||
|
||||
readonly selectedValues = signal<SelectOptionValue[]>([]);
|
||||
|
||||
constructor() {
|
||||
afterRenderEffect(() => {
|
||||
if (this.dialog() && this.combobox()?.expanded()) {
|
||||
untracked(() => this.listbox()?.gotoFirst());
|
||||
this.positionDialog();
|
||||
}
|
||||
});
|
||||
|
||||
afterRenderEffect(() => {
|
||||
const selected = this.selectedValues();
|
||||
if (selected.length > 0) {
|
||||
untracked(() => this.dialog()?.close());
|
||||
this.value.set(selected[0] as string);
|
||||
this.searchString.set('');
|
||||
}
|
||||
});
|
||||
|
||||
afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView());
|
||||
}
|
||||
// TODO: Improve once CDK overlay is fixed https://github.com/angular/components/issues/32504
|
||||
private positionDialog(): void {
|
||||
const dialog = this.dialog();
|
||||
const combobox = this.combobox();
|
||||
|
||||
if (!dialog || !combobox) {
|
||||
setOption($event: SelectOptionValue): void {
|
||||
if (this.disabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
if (comboboxRect) {
|
||||
dialog.element.style.width = `${comboboxRect.width}px`;
|
||||
dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`;
|
||||
dialog.element.style.left = `${comboboxRect.left}px`;
|
||||
}
|
||||
this.value.set($event as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,11 @@
|
||||
top: 0;
|
||||
height: fit-content;
|
||||
width: 14rem;
|
||||
padding-right: 1rem;
|
||||
padding-right: 2rem;
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include mq.for-large-desktop-up {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
@include mq.for-extra-large-desktop-up {
|
||||
width: 25rem;
|
||||
}
|
||||
|
||||
aside {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ import {IconComponent} from '../icon/icon.component';
|
||||
class: 'docs-form-element',
|
||||
},
|
||||
})
|
||||
export class TextField implements FormValueControl<string> {
|
||||
export class TextField implements FormValueControl<string | null> {
|
||||
readonly input = viewChild.required<ElementRef<HTMLInputElement>>('inputRef');
|
||||
readonly name = input<string>('');
|
||||
readonly value = model<string>('');
|
||||
readonly value = model<string | null>(null);
|
||||
readonly placeholder = input<string | null>(null);
|
||||
readonly disabled = model<boolean>(false);
|
||||
readonly hideIcon = input<boolean>(false);
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
height: fit-content;
|
||||
|
||||
docs-icon {
|
||||
color: var(--quaternary-contrast);
|
||||
color: var(--gray-400);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill="var(--quaternary-contrast)"
|
||||
fill="var(--gray-400)"
|
||||
d="M3.4 22 2 20.6 8.6 14H4v-2h8v8h-2v-4.6L3.4 22ZM12 12V4h2v4.6L20.6 2 22 3.4 15.4 10H20v2h-8Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
.docs-example-viewer-preview {
|
||||
.docs-dark-mode & {
|
||||
color: var(--primary-contrast);
|
||||
background: var(--primary-constrast);
|
||||
background: var(--gray-900);
|
||||
}
|
||||
@media screen and (prefers-color-scheme: dark) {
|
||||
color: var(--primary-contrast);
|
||||
background: var(--primary-constrast);
|
||||
background: var(--gray-900);
|
||||
}
|
||||
.docs-light-mode & {
|
||||
background: var(--page-background);
|
||||
@@ -70,7 +70,7 @@
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
color: var(--quaternary-contrast);
|
||||
color: var(--gray-400);
|
||||
|
||||
path {
|
||||
transition: fill 0.3s ease;
|
||||
|
||||
@@ -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,6 +43,15 @@ 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,6 +17,5 @@ export interface NavigationItem {
|
||||
contentPath?: string;
|
||||
status?: 'new' | 'updated';
|
||||
category?: string;
|
||||
isCrossReferenced?: boolean;
|
||||
preserveOtherCategoryOrder?: boolean; // true by default
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"name": "@angular/docs",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": "^21.2.0-next",
|
||||
"@angular/common": "^21.2.0-next",
|
||||
"@angular/core": "^21.2.0-next",
|
||||
"@angular/forms": "^21.2.0-next",
|
||||
"@angular/material": "^21.2.0-next",
|
||||
"@angular/platform-browser": "^21.2.0-next",
|
||||
"@angular/router": "^21.2.0-next",
|
||||
"@angular/ssr": "^21.2.0-next",
|
||||
"@angular/cdk": "^21.1.0-next",
|
||||
"@angular/common": "^21.1.0-next",
|
||||
"@angular/core": "^21.1.0-next",
|
||||
"@angular/forms": "^21.1.0-next",
|
||||
"@angular/material": "^21.1.0-next",
|
||||
"@angular/platform-browser": "^21.1.0-next",
|
||||
"@angular/router": "^21.1.0-next",
|
||||
"@angular/ssr": "^21.1.0-next",
|
||||
"algoliasearch": "^5.0.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
@@ -18,7 +18,7 @@
|
||||
"diff": "~8.0.0",
|
||||
"emoji-regex": "~10.6.0",
|
||||
"fflate": "^0.8.2",
|
||||
"jsdom": "~28.0.0",
|
||||
"jsdom": "~27.4.0",
|
||||
"marked": "~17.0.0",
|
||||
"mermaid": "^11.0.0",
|
||||
"shiki": "^3.0.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ParameterEntry,
|
||||
PipeEntry,
|
||||
TypeAliasEntry,
|
||||
EntryType,
|
||||
} from '../entities.mjs';
|
||||
|
||||
import {CliCommand, CliOption} from '../cli-entities.mjs';
|
||||
@@ -104,7 +105,6 @@ export type FunctionEntryRenderable = FunctionEntry &
|
||||
export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata &
|
||||
DocEntryRenderable & {
|
||||
params: ParameterEntryRenderable[];
|
||||
htmlReturnDescription?: string;
|
||||
};
|
||||
|
||||
/** Documentation entity for a block augmented with transformed content for rendering. */
|
||||
@@ -167,9 +167,11 @@ export type CliCommandRenderable = CliCommand & {
|
||||
htmlDescription: string;
|
||||
cards: CliCardRenderable[];
|
||||
argumentsLabel: string;
|
||||
optionsLabel: string;
|
||||
hasOptions: boolean;
|
||||
subcommands?: CliCommandRenderable[];
|
||||
};
|
||||
|
||||
export interface InitializerApiFunctionRenderable
|
||||
extends Omit<InitializerApiFunctionEntry, 'jsdocTags'>, DocEntryRenderable, HasRenderableToc {}
|
||||
extends Omit<InitializerApiFunctionEntry, 'jsdocTags'>,
|
||||
DocEntryRenderable,
|
||||
HasRenderableToc {}
|
||||
|
||||
@@ -59,16 +59,10 @@ 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>
|
||||
<a class="docs-anchor" href={'#' + memberName}>
|
||||
{label}
|
||||
</a>
|
||||
</h3>
|
||||
<h3>{displayName ?? memberName}</h3>
|
||||
{isClassMethodEntry(member) && member.signatures.length > 1 ? (
|
||||
<span>{member.signatures.length} overloads</span>
|
||||
) : returnType ? (
|
||||
|
||||
@@ -23,7 +23,6 @@ import {RawHtml} from './raw-html';
|
||||
export function ClassMethodInfo(props: {
|
||||
entry: FunctionSignatureMetadataRenderable;
|
||||
hideUsageNotes?: boolean;
|
||||
hideDescription?: boolean;
|
||||
}) {
|
||||
const entry = props.entry;
|
||||
|
||||
@@ -31,9 +30,7 @@ export function ClassMethodInfo(props: {
|
||||
<div
|
||||
className={`${REFERENCE_MEMBER_CARD_ITEM} ${entry.deprecated ? 'docs-reference-card-item-deprecated' : ''}`}
|
||||
>
|
||||
{!props.hideDescription && (
|
||||
<RawHtml value={entry.htmlDescription} className={'docs-function-definition'} />
|
||||
)}
|
||||
<RawHtml value={entry.htmlDescription} className={'docs-function-definition'} />
|
||||
{/* In case when method is overloaded we need to indicate which overload is deprecated */}
|
||||
{entry.deprecated ? (
|
||||
<div>
|
||||
@@ -48,9 +45,6 @@ export function ClassMethodInfo(props: {
|
||||
<div className={'docs-return-type'}>
|
||||
<span className={PARAM_KEYWORD_CLASS_NAME}>@returns</span>
|
||||
<CodeSymbol code={entry.returnType} />
|
||||
{entry.htmlReturnDescription && (
|
||||
<RawHtml value={entry.htmlReturnDescription} className="docs-parameter-description" />
|
||||
)}
|
||||
</div>
|
||||
{entry.htmlUsageNotes && !props.hideUsageNotes ? (
|
||||
<div className={'docs-usage-notes'}>
|
||||
|
||||
@@ -26,15 +26,19 @@ export function CliCommandReference(entry: CliCommandRenderable) {
|
||||
<code>
|
||||
<div className={'shiki line cli'}>
|
||||
ng {commandName(entry, command)}
|
||||
{entry.argumentsLabel && (
|
||||
<span member-id={'Arguments'} className="shiki-ln-line-argument">
|
||||
{entry.argumentsLabel ? (
|
||||
<button member-id={'Arguments'} className="shiki-ln-line-argument">
|
||||
{entry.argumentsLabel}
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{entry.optionsLabel && (
|
||||
<span member-id={'Options'} className="shiki-ln-line-option">
|
||||
{entry.optionsLabel}
|
||||
</span>
|
||||
{entry.hasOptions ? (
|
||||
<button member-id={'Options'} className="shiki-ln-line-option">
|
||||
[options]
|
||||
</button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</code>
|
||||
|
||||
@@ -32,43 +32,31 @@ import {SectionUsageNotes} from './section-usage-notes';
|
||||
export const signatureCard = (
|
||||
name: string,
|
||||
signature: FunctionSignatureMetadataRenderable,
|
||||
opts: {
|
||||
id: string;
|
||||
printSignaturesAsHeader: boolean;
|
||||
hideUsageNotes?: boolean;
|
||||
hideHeader?: boolean;
|
||||
hideDescription?: boolean;
|
||||
},
|
||||
opts: {id: string; printSignaturesAsHeader: boolean; hideUsageNotes?: boolean},
|
||||
) => {
|
||||
return (
|
||||
<div id={opts.id} class={REFERENCE_MEMBER_CARD}>
|
||||
{!opts.hideHeader && (
|
||||
<header class={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
{opts.printSignaturesAsHeader ? (
|
||||
<HighlightTypeScript
|
||||
code={printInitializerFunctionSignatureLine(
|
||||
name,
|
||||
signature,
|
||||
// Always omit types in signature headers, to keep them short.
|
||||
true,
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<h3>{name}</h3>
|
||||
<div>
|
||||
<CodeSymbol code={signature.returnType} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<header class={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
{opts.printSignaturesAsHeader ? (
|
||||
<HighlightTypeScript
|
||||
code={printInitializerFunctionSignatureLine(
|
||||
name,
|
||||
signature,
|
||||
// Always omit types in signature headers, to keep them short.
|
||||
true,
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<h3>{name}</h3>
|
||||
<div>
|
||||
<CodeSymbol code={signature.returnType} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
<div class={REFERENCE_MEMBER_CARD_BODY}>
|
||||
<ClassMethodInfo
|
||||
entry={signature}
|
||||
hideUsageNotes={opts.hideUsageNotes}
|
||||
hideDescription={opts.hideDescription}
|
||||
/>
|
||||
<ClassMethodInfo entry={signature} hideUsageNotes={opts.hideUsageNotes} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -76,8 +64,8 @@ export const signatureCard = (
|
||||
|
||||
/** Component to render a function API reference document. */
|
||||
export function FunctionReference(entry: FunctionEntryRenderable) {
|
||||
// Use signatures as header if there are multiple signatures.
|
||||
const printSignaturesAsHeader = entry.signatures.length > 1;
|
||||
const hideSignatureCardDescription = !printSignaturesAsHeader;
|
||||
|
||||
return (
|
||||
<div className={API_REFERENCE_CONTAINER}>
|
||||
@@ -85,15 +73,14 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
|
||||
<DeprecationWarning entry={entry} />
|
||||
<SectionApi entry={entry} />
|
||||
<div className={REFERENCE_MEMBERS}>
|
||||
{entry.signatures.map((s, i) =>
|
||||
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
|
||||
id: `${s.name}_${i}`,
|
||||
printSignaturesAsHeader,
|
||||
hideHeader: !printSignaturesAsHeader,
|
||||
hideUsageNotes: hideSignatureCardDescription,
|
||||
hideDescription: hideSignatureCardDescription,
|
||||
}),
|
||||
)}
|
||||
{entry.signatures.length > 1 &&
|
||||
entry.signatures.map((s, i) =>
|
||||
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
|
||||
id: `${s.name}_${i}`,
|
||||
printSignaturesAsHeader,
|
||||
hideUsageNotes: true,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SectionDescription entry={entry} />
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
import {h} from 'preact';
|
||||
import {ParameterEntryRenderable} from '../entities/renderables.mjs';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {PARAM_GROUP_CLASS_NAME} from '../styling/css-classes.mjs';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
import {RawHtml} from './raw-html';
|
||||
|
||||
/** Component to render a function or method parameter reference doc fragment. */
|
||||
export function Parameter(props: {param: ParameterEntryRenderable}) {
|
||||
@@ -21,9 +21,7 @@ export function Parameter(props: {param: ParameterEntryRenderable}) {
|
||||
{/*TODO: isOptional, isRestParam*/}
|
||||
<span class="docs-param-keyword">@param</span>
|
||||
<span class="docs-param-name">{param.name}</span>
|
||||
<span class="docs-param-type">
|
||||
<CodeSymbol code={param.type} />
|
||||
</span>
|
||||
<CodeSymbol code={param.type} />
|
||||
<RawHtml value={param.htmlDescription} className="docs-parameter-description" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -40,20 +40,7 @@ describe('CLI docs to html', () => {
|
||||
const cliTocs = fragment.querySelectorAll('.docs-reference-cli-toc')!;
|
||||
expect(cliTocs.length).toBe(2);
|
||||
|
||||
expect(cliTocs[0].textContent).toContain('ng component [name] [options]');
|
||||
expect(cliTocs[1].textContent).toContain('ng c [name] [options]');
|
||||
});
|
||||
|
||||
it('should rewrite absolute angular.dev hrefs in option descriptions to root-relative', async () => {
|
||||
const renderableJson = await getRenderable(entryJson, '', 'angular/cli');
|
||||
const localFragment = JSDOM.fragment(renderEntry(renderableJson));
|
||||
const hrefs = Array.from(localFragment.querySelectorAll('a')).map((a) =>
|
||||
a.getAttribute('href'),
|
||||
);
|
||||
// Absolute angular.dev URLs are rewritten to root-relative.
|
||||
expect(hrefs).toContain('/cli-test-rewrite');
|
||||
expect(hrefs).not.toContain('https://angular.dev/cli-test-rewrite');
|
||||
// Subdomains are intentionally left as external.
|
||||
expect(hrefs).toContain('https://next.angular.dev/cli-test-preview');
|
||||
expect(cliTocs[0].textContent).toContain('ng component[name][options]');
|
||||
expect(cliTocs[1].textContent).toContain('ng c[name][options]');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"name": "generate",
|
||||
"command": "ng generate <schematic>",
|
||||
"shortDescription": "Generates and/or modifies files based on a schematic.",
|
||||
"aliases": ["g"],
|
||||
"aliases": [
|
||||
"g"
|
||||
],
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
@@ -14,7 +16,9 @@
|
||||
{
|
||||
"name": "dry-run",
|
||||
"type": "boolean",
|
||||
"aliases": ["d"],
|
||||
"aliases": [
|
||||
"d"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Run through and reports activity without writing out results."
|
||||
},
|
||||
@@ -40,12 +44,6 @@
|
||||
"type": "string",
|
||||
"description": "The [collection:schematic] to run.",
|
||||
"positional": 0
|
||||
},
|
||||
{
|
||||
"name": "test-angular-dev-href-rewrite",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "See [angular.dev test](https://angular.dev/cli-test-rewrite) and [preview test](https://next.angular.dev/cli-test-preview)."
|
||||
}
|
||||
],
|
||||
"subcommands": [
|
||||
@@ -77,13 +75,17 @@
|
||||
{
|
||||
"name": "inline-style",
|
||||
"type": "boolean",
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"description": "Include styles inline in the root component.ts file. Only CSS styles can be included inline. Default is false, meaning that an external styles file is created and referenced in the root component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
"type": "boolean",
|
||||
"aliases": ["t"],
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"description": "Include template inline in the root component.ts file. Default is false, meaning that an external template file is created and referenced in the root component.ts file. "
|
||||
},
|
||||
{
|
||||
@@ -101,7 +103,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"default": "app",
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
@@ -131,7 +135,9 @@
|
||||
{
|
||||
"name": "skip-tests",
|
||||
"type": "boolean",
|
||||
"aliases": ["S"],
|
||||
"aliases": [
|
||||
"S"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Do not create \"spec.ts\" test files for the application."
|
||||
},
|
||||
@@ -157,17 +163,29 @@
|
||||
"name": "style",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less"],
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less"
|
||||
],
|
||||
"description": "The file extension or preprocessor to use for style files."
|
||||
},
|
||||
{
|
||||
"name": "view-encapsulation",
|
||||
"type": "string",
|
||||
"enum": ["Emulated", "None", "ShadowDom", "ExperimentalIsolatedShadowDom"],
|
||||
"enum": [
|
||||
"Emulated",
|
||||
"None",
|
||||
"ShadowDom",
|
||||
"ExperimentalIsolatedShadowDom"
|
||||
],
|
||||
"description": "The view encapsulation strategy to use in the new application."
|
||||
}
|
||||
],
|
||||
"aliases": ["app"],
|
||||
"aliases": [
|
||||
"app"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -198,7 +216,9 @@
|
||||
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
|
||||
}
|
||||
],
|
||||
"aliases": ["cl"],
|
||||
"aliases": [
|
||||
"cl"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -209,15 +229,22 @@
|
||||
{
|
||||
"name": "change-detection",
|
||||
"type": "string",
|
||||
"aliases": ["c"],
|
||||
"aliases": [
|
||||
"c"
|
||||
],
|
||||
"default": "Default",
|
||||
"enum": ["Default", "OnPush"],
|
||||
"enum": [
|
||||
"Default",
|
||||
"OnPush"
|
||||
],
|
||||
"description": "The change detection strategy to use in the new component."
|
||||
},
|
||||
{
|
||||
"name": "display-block",
|
||||
"type": "boolean",
|
||||
"aliases": ["b"],
|
||||
"aliases": [
|
||||
"b"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Specifies if the style will contain `:host { display: block; }`."
|
||||
},
|
||||
@@ -242,21 +269,27 @@
|
||||
{
|
||||
"name": "inline-style",
|
||||
"type": "boolean",
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
"type": "boolean",
|
||||
"aliases": ["t"],
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -268,7 +301,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"description": "The prefix to apply to the generated component selector."
|
||||
},
|
||||
{
|
||||
@@ -309,7 +344,13 @@
|
||||
"name": "style",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less", "none"],
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less",
|
||||
"none"
|
||||
],
|
||||
"description": "The file extension or preprocessor to use for style files, or 'none' to skip generating the style file."
|
||||
},
|
||||
{
|
||||
@@ -321,12 +362,21 @@
|
||||
{
|
||||
"name": "view-encapsulation",
|
||||
"type": "string",
|
||||
"aliases": ["v"],
|
||||
"enum": ["Emulated", "None", "ShadowDom", "ExperimentalIsolatedShadowDom"],
|
||||
"aliases": [
|
||||
"v"
|
||||
],
|
||||
"enum": [
|
||||
"Emulated",
|
||||
"None",
|
||||
"ShadowDom",
|
||||
"ExperimentalIsolatedShadowDom"
|
||||
],
|
||||
"description": "The view encapsulation strategy to use in the new component."
|
||||
}
|
||||
],
|
||||
"aliases": ["c"],
|
||||
"aliases": [
|
||||
"c"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -342,7 +392,10 @@
|
||||
{
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"enum": ["karma", "browserslist"],
|
||||
"enum": [
|
||||
"karma",
|
||||
"browserslist"
|
||||
],
|
||||
"description": "Specifies which type of configuration file to create.",
|
||||
"positional": 0
|
||||
}
|
||||
@@ -370,7 +423,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -382,7 +437,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
{
|
||||
@@ -414,7 +471,9 @@
|
||||
"description": "Whether the generated directive is standalone."
|
||||
}
|
||||
],
|
||||
"aliases": ["d"],
|
||||
"aliases": [
|
||||
"d"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -439,7 +498,9 @@
|
||||
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
|
||||
}
|
||||
],
|
||||
"aliases": ["e"],
|
||||
"aliases": [
|
||||
"e"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -476,7 +537,9 @@
|
||||
{
|
||||
"name": "implements",
|
||||
"type": "array",
|
||||
"aliases": ["guardType"],
|
||||
"aliases": [
|
||||
"guardType"
|
||||
],
|
||||
"description": "Specifies which type of guard to create."
|
||||
},
|
||||
{
|
||||
@@ -497,7 +560,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new guard."
|
||||
}
|
||||
],
|
||||
"aliases": ["g"],
|
||||
"aliases": [
|
||||
"g"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -566,7 +631,9 @@
|
||||
"positional": 1
|
||||
}
|
||||
],
|
||||
"aliases": ["i"],
|
||||
"aliases": [
|
||||
"i"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -589,7 +656,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"default": "lib",
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
@@ -623,7 +692,9 @@
|
||||
"description": "Creates a library based upon the standalone API, without NgModules."
|
||||
}
|
||||
],
|
||||
"aliases": ["lib"],
|
||||
"aliases": [
|
||||
"lib"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -640,7 +711,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -669,11 +742,16 @@
|
||||
"name": "routing-scope",
|
||||
"type": "string",
|
||||
"default": "Child",
|
||||
"enum": ["Child", "Root"],
|
||||
"enum": [
|
||||
"Child",
|
||||
"Root"
|
||||
],
|
||||
"description": "The scope for the new routing module."
|
||||
}
|
||||
],
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -696,7 +774,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -729,7 +809,9 @@
|
||||
"description": "Whether the generated pipe is standalone."
|
||||
}
|
||||
],
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -767,7 +849,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new resolver."
|
||||
}
|
||||
],
|
||||
"aliases": ["r"],
|
||||
"aliases": [
|
||||
"r"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -799,7 +883,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new service."
|
||||
}
|
||||
],
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -463,7 +463,6 @@
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnDescription": "A reference that can be used to unregister callbacks registered by this call.",
|
||||
"rawComment": "/**\n * Register callbacks to be invoked the next time the application finishes rendering, during the\n * specified phases. The available phases are:\n * - `earlyRead`\n * Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n * perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n * reading can wait until after the write phase. **Never** write to the DOM in this phase.\n * - `write`\n * Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n * - `mixedReadWrite`\n * Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n * it is possible to divide the work among the other phases instead.\n * - `read`\n * Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n *\n * <div class=\"docs-alert docs-alert-critical\">\n *\n * You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\n * phases when possible, to avoid performance degradation.\n *\n * </div>\n *\n * Note that:\n * - Callbacks run in the following phase order *once, after the next render*:\n * 1. `earlyRead`\n * 2. `write`\n * 3. `mixedReadWrite`\n * 4. `read`\n * - Callbacks in the same phase run in the order they are registered.\n * - Callbacks run on browser platforms only, they will not run on the server.\n *\n * The first phase callback to run as part of this spec will receive no parameters. Each\n * subsequent phase callback in this spec will receive the return value of the previously run\n * phase callback as a parameter. This can be used to coordinate work across multiple phases.\n *\n * Angular is unable to verify or enforce that phases are used correctly, and instead\n * relies on each developer to follow the guidelines documented for each value and\n * carefully choose the appropriate one, refactoring their code if necessary. By doing\n * so, Angular is better able to minimize the performance degradation associated with\n * manual DOM access, ensuring the best experience for the end users of your application\n * or library.\n *\n * <div class=\"docs-alert docs-alert-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param spec The callback functions to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```angular-ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */",
|
||||
"returnType": "AfterRenderRef"
|
||||
},
|
||||
@@ -498,7 +497,6 @@
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnDescription": "A reference that can be used to unregister the callback registered by this call.",
|
||||
"rawComment": "/**\n * Register a callback to be invoked the next time the application finishes rendering, during the\n * `mixedReadWrite` phase.\n *\n * <div class=\"docs-alert docs-alert-critical\">\n *\n * You should prefer specifying an explicit phase for the callback instead, or you risk significant\n * performance degradation.\n *\n * </div>\n *\n * Note that the callback will run\n * - in the order it was registered\n * - on browser platforms only\n * - during the `mixedReadWrite` phase\n *\n * <div class=\"docs-alert docs-alert-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param callback A callback function to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```angular-ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @publicApi 20.0\n */",
|
||||
"returnType": "AfterRenderRef"
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ 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.
|
||||
@@ -62,21 +60,4 @@ 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}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -199,23 +199,6 @@ describe('jsdoc transforms', () => {
|
||||
|
||||
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
|
||||
});
|
||||
|
||||
it('should throw on a miscased absolute @link to a known API symbol', () => {
|
||||
setSymbols({RouterModule: 'router'});
|
||||
|
||||
const entryFn = () =>
|
||||
addHtmlAdditionalLinks({
|
||||
jsdocTags: [
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link /api/router/routerModule#forRoot forRoot}',
|
||||
},
|
||||
],
|
||||
moduleName: 'test',
|
||||
});
|
||||
|
||||
expect(entryFn).toThrowError(/Broken @link.*Did you mean \/api\/router\/RouterModule/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addHtmlDescription', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export function getCliRenderable(command: CliCommand): CliCommandRenderable {
|
||||
}),
|
||||
cards: getCliCardsRenderable(command),
|
||||
argumentsLabel: getArgumentsLabel(command),
|
||||
optionsLabel: getOptionsLabel(command),
|
||||
hasOptions: getOptions(command).length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,14 +53,11 @@ export function getCliCardsRenderable(command: CliCommand): CliCardRenderable[]
|
||||
return cards;
|
||||
}
|
||||
|
||||
// Rewrite absolute angular.dev hrefs to root-relative so links stay in-site.
|
||||
const angularDevHrefRegex = /(href=["'])https?:\/\/angular\.dev\//g;
|
||||
|
||||
function getRenderableOptions(items: CliOption[]): CliOptionRenderable[] {
|
||||
return items.map((option) => ({
|
||||
...option,
|
||||
deprecated: option.deprecated ? {version: undefined} : undefined,
|
||||
description: (marked.parse(option.description) as string).replace(angularDevHrefRegex, '$1/'),
|
||||
description: marked.parse(option.description) as string,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -69,12 +66,7 @@ function getArgumentsLabel(command: CliCommand): string {
|
||||
if (args.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const label = command.command.replace(/^ng\s+/, '').replace(`${command.name} `, '');
|
||||
return ` ${label}`;
|
||||
}
|
||||
|
||||
function getOptionsLabel(command: CliCommand): string {
|
||||
return getOptions(command).length > 0 ? ' [options]' : '';
|
||||
return command.command.replace(`${command.name} `, '');
|
||||
}
|
||||
|
||||
function getArgs(command: CliCommand): CliOption[] {
|
||||
|
||||
@@ -95,7 +95,6 @@ export async function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
|
||||
{
|
||||
language: 'typescript',
|
||||
apiEntries: getSymbolsAsApiEntries(),
|
||||
removeWhitespace: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms.mjs';
|
||||
import {addModuleName} from './module-name.mjs';
|
||||
import {addHtmlReturnDescription, addRenderableFunctionParams} from './params-transforms.mjs';
|
||||
import {addRenderableFunctionParams} from './params-transforms.mjs';
|
||||
import {addRepo} from './repo.mjs';
|
||||
|
||||
/** Given an unprocessed function entry, get the fully renderable function entry. */
|
||||
@@ -50,13 +50,11 @@ export function getFunctionMetadataRenderable(
|
||||
repo: string,
|
||||
): FunctionSignatureMetadataRenderable {
|
||||
return addHtmlAdditionalLinks(
|
||||
addHtmlReturnDescription(
|
||||
addRenderableFunctionParams(
|
||||
addHtmlUsageNotes(
|
||||
setEntryFlags(
|
||||
addHtmlJsDocTagComments(
|
||||
addHtmlDescription(addRepo(addModuleName(entry, moduleName), repo)),
|
||||
),
|
||||
addRenderableFunctionParams(
|
||||
addHtmlUsageNotes(
|
||||
setEntryFlags(
|
||||
addHtmlJsDocTagComments(
|
||||
addHtmlDescription(addRepo(addModuleName(entry, moduleName), repo)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -112,7 +112,7 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
|
||||
}
|
||||
|
||||
/** Given a markdown JsDoc text, gets the rendered HTML. */
|
||||
export function getHtmlForJsDocText(text: string): string {
|
||||
function getHtmlForJsDocText(text: string): string {
|
||||
const mdToParse = convertLinks(wrapExampleHtmlElementsWithCode(text));
|
||||
const parsed = parseMarkdown(mdToParse, {
|
||||
apiEntries: getSymbolsAsApiEntries(),
|
||||
@@ -213,6 +213,10 @@ function convertLinks(text: string) {
|
||||
}
|
||||
|
||||
function parseAtLink(link: string): {label: string; url: string} | undefined {
|
||||
// Because of microsoft/TypeScript/issues/59679
|
||||
// getTextOfJSDocComment introduces an extra space between the symbol and a trailing ()
|
||||
link = link.replace(/ \(\)$/, '');
|
||||
|
||||
let [rawSymbol, description] = link.split(/\s(.+)/);
|
||||
if (rawSymbol.startsWith('#')) {
|
||||
rawSymbol = rawSymbol.substring(1);
|
||||
@@ -223,27 +227,6 @@ function parseAtLink(link: string): {label: string; url: string} | undefined {
|
||||
);
|
||||
}
|
||||
|
||||
// Validate absolute `/api/...` links against the known symbol registry. This catches
|
||||
// miscased symbol names (e.g. `/api/router/routerModule` instead of
|
||||
// `/api/router/RouterModule`) at build time.
|
||||
if (rawSymbol.startsWith('/api/')) {
|
||||
const [pathPart] = rawSymbol.split('#');
|
||||
const segments = pathPart.split('/').filter((s) => s.length > 0);
|
||||
const symbolName = segments[segments.length - 1];
|
||||
// Case-insensitive lookup: find the canonical symbol name in the registry.
|
||||
const knownSymbols = Object.keys(getSymbolsAsApiEntries());
|
||||
const canonicalSymbol = knownSymbols.find(
|
||||
(s) => s.toLowerCase() === symbolName.toLowerCase(),
|
||||
);
|
||||
if (canonicalSymbol && canonicalSymbol !== symbolName) {
|
||||
const expectedUrl = getSymbolUrl(canonicalSymbol);
|
||||
throw Error(
|
||||
`Broken @link: ${link}. Did you mean ${expectedUrl}? ` +
|
||||
`Symbol names in API URLs are case-sensitive.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
url: rawSymbol,
|
||||
label: description ?? rawSymbol.split('/').pop()!,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {HasModuleName, HasParams, HasRenderableParams} from '../entities/traits.mjs';
|
||||
import {addHtmlDescription, getHtmlForJsDocText} from './jsdoc-transforms.mjs';
|
||||
import {addHtmlDescription} from './jsdoc-transforms.mjs';
|
||||
import {addModuleName} from './module-name.mjs';
|
||||
|
||||
export function addRenderableFunctionParams<T extends HasParams & HasModuleName>(
|
||||
@@ -22,13 +22,3 @@ export function addRenderableFunctionParams<T extends HasParams & HasModuleName>
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
/** Converts `returnDescription` to `htmlReturnDescription` for rendering. */
|
||||
export function addHtmlReturnDescription<
|
||||
T extends {returnDescription?: string; moduleName: string},
|
||||
>(entry: T): T & {htmlReturnDescription?: string} {
|
||||
const htmlReturnDescription = entry.returnDescription
|
||||
? getHtmlForJsDocText(entry.returnDescription)
|
||||
: undefined;
|
||||
return {...entry, htmlReturnDescription};
|
||||
}
|
||||
|
||||
@@ -10,15 +10,6 @@ ts_project(
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "heading",
|
||||
srcs = ["heading.mts"],
|
||||
visibility = [
|
||||
"//adev/scripts/routes:__subpackages__",
|
||||
"//adev/shared-docs/pipeline:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "shiki",
|
||||
srcs = ["shiki.mts"],
|
||||
@@ -45,17 +36,3 @@ zoneless_jasmine_test(
|
||||
name = "linking_test",
|
||||
data = [":linking_test_lib"],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "heading_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["test/heading.spec.mts"],
|
||||
deps = [
|
||||
":heading",
|
||||
],
|
||||
)
|
||||
|
||||
zoneless_jasmine_test(
|
||||
name = "heading_test",
|
||||
data = [":heading_test_lib"],
|
||||
)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracts the ID from a heading text.
|
||||
* Supports custom ID syntax: `## My Heading {#custom-id}`
|
||||
*/
|
||||
export function getIdFromHeading(heading: string): string {
|
||||
// extract the extended markdown heading id
|
||||
// ex: ## MyHeading {# myId}
|
||||
// This is recommended in case we end up having duplicate Ids but we still want the same heading text.
|
||||
// We don't want to make Id generation stateful/too complex to handle duplicates automatically.
|
||||
const customIdRegex = /{#\s*([\w-]+)\s*}/g;
|
||||
const customId = customIdRegex.exec(heading)?.[1];
|
||||
|
||||
if (customId) {
|
||||
return customId;
|
||||
}
|
||||
|
||||
return heading
|
||||
.toLowerCase()
|
||||
.replace(/\s|\//g, '-') // replace spaces and slashes with dashes
|
||||
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
|
||||
}
|
||||
@@ -30,15 +30,6 @@ const LINK_EXEMPT = new Set([
|
||||
'Event',
|
||||
'form',
|
||||
'type',
|
||||
'Host',
|
||||
'filter',
|
||||
'required',
|
||||
'pattern',
|
||||
'min',
|
||||
'max',
|
||||
'minLength',
|
||||
'maxLength',
|
||||
'query',
|
||||
]);
|
||||
|
||||
export function shouldLinkSymbol(symbol: string): boolean {
|
||||
|
||||
@@ -18,7 +18,6 @@ ts_project(
|
||||
"//adev:node_modules/mermaid",
|
||||
"//adev:node_modules/playwright-core",
|
||||
"//adev:node_modules/shiki",
|
||||
"//adev/shared-docs/pipeline/shared:heading",
|
||||
"//adev/shared-docs/pipeline/shared:linking",
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
"//adev/shared-docs/pipeline/shared/regions",
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ interface DocsCardContainerToken extends Tokens.Generic {
|
||||
// Capture group 1: all attributes on the opening tag
|
||||
// Capture group 2: all content between the open and close tags
|
||||
const cardContainerRule =
|
||||
/^\s*<docs-card-container(?:\s([^>]*))?>((?:.(?!\/docs-card-container))*)<\/docs-card-container>/s;
|
||||
/^[^<]*<docs-card-container(?:\s([^>]*))?>((?:.(?!\/docs-card-container))*)<\/docs-card-container>/s;
|
||||
const headerTitleRule = /headerTitle="([^"]*)"/;
|
||||
const headerImgSrcRule = /headerImgSrc="([^"]*)"/;
|
||||
|
||||
|
||||
@@ -18,20 +18,18 @@ interface DocsCardToken extends Tokens.Generic {
|
||||
href?: string;
|
||||
imgSrc?: string;
|
||||
iconImgSrc?: string; // Need image since icons are custom
|
||||
titleInline?: boolean;
|
||||
tokens: Token[];
|
||||
}
|
||||
|
||||
// Capture group 1: all attributes on the opening tag
|
||||
// Capture group 2: all content between the open and close tags
|
||||
const cardRule = /^\s*<docs-card(?:\s([^>]*))?>((?:.(?!\/docs-card))*)<\/docs-card>/s;
|
||||
const cardRule = /^[^<]*<docs-card(?:\s([^>]*))?>((?:.(?!\/docs-card))*)<\/docs-card>/s;
|
||||
|
||||
const titleRule = /title="([^"]*)"/;
|
||||
const linkRule = /link="([^"]*)"/;
|
||||
const hrefRule = /href="([^"]*)"/;
|
||||
const imgSrcRule = /imgSrc="([^"]*)"/;
|
||||
const iconImgSrcRule = /iconImgSrc="([^"]*)"/;
|
||||
const titleInlineRule = /(?:^|\s)titleInline(?=\s|$|=)/;
|
||||
|
||||
export const docsCardExtension = {
|
||||
name: 'docs-card',
|
||||
@@ -49,7 +47,6 @@ export const docsCardExtension = {
|
||||
const href = hrefRule.exec(attr);
|
||||
const imgSrc = imgSrcRule.exec(attr);
|
||||
const iconImgSrc = iconImgSrcRule.exec(attr);
|
||||
const titleInline = titleInlineRule.test(attr);
|
||||
|
||||
const body = match[2].trim();
|
||||
|
||||
@@ -62,7 +59,6 @@ export const docsCardExtension = {
|
||||
link: link ? link[1] : undefined,
|
||||
imgSrc: imgSrc ? imgSrc[1] : undefined,
|
||||
iconImgSrc: iconImgSrc ? iconImgSrc[1] : undefined,
|
||||
titleInline,
|
||||
tokens: [],
|
||||
};
|
||||
this.lexer.blockTokens(token.body, token.tokens);
|
||||
@@ -83,14 +79,12 @@ function getStandardCard(renderer: AdevDocsRenderer, token: DocsCardToken) {
|
||||
// We need to read svg content, instead of renering svg with `img`,
|
||||
// cause we would like to use CSS variables to support dark and light mode.
|
||||
const icon = loadWorkspaceRelativeFile(token.iconImgSrc);
|
||||
const header = token.titleInline
|
||||
? `<div class="docs-card-header-inline">${icon}<h3>${token.title}</h3></div>`
|
||||
: `${icon}<h3>${token.title}</h3>`;
|
||||
|
||||
return `
|
||||
<a href="${token.href}" ${anchorTarget(token.href)} class="docs-card">
|
||||
<div>
|
||||
${header}
|
||||
${icon}
|
||||
<h3>${token.title}</h3>
|
||||
${renderer.parser.parse(token.tokens)}
|
||||
</div>
|
||||
<span>${token.link ? token.link : 'Learn more'}</span>
|
||||
|
||||
@@ -41,7 +41,6 @@ export const docsCodeBlockExtension = {
|
||||
const headerRule = /header\s*:\s*(['"`])([^'"`]+)\1/; // The 2nd capture matters here
|
||||
const highlightRule = /highlight\s*:\s*(.*)([^,])/;
|
||||
const hideCopyRule = /hideCopy/;
|
||||
const hideDollarRule = /hideDollar/;
|
||||
const preferRule = /\b(prefer|avoid)\b/;
|
||||
const linenumsRule = /linenums/;
|
||||
|
||||
@@ -53,7 +52,6 @@ export const docsCodeBlockExtension = {
|
||||
header: headerRule.exec(metadataStr)?.[2],
|
||||
highlight: highlightRule.exec(metadataStr)?.[1],
|
||||
hideCopy: hideCopyRule.test(metadataStr),
|
||||
hideDollar: hideDollarRule.test(metadataStr),
|
||||
style: preferRule.exec(metadataStr)?.[1] as 'prefer' | 'avoid' | undefined,
|
||||
linenums: linenumsRule.test(metadataStr),
|
||||
};
|
||||
|
||||
@@ -32,7 +32,6 @@ const visibleLinesRule = /visibleLines="([^"]*)"/;
|
||||
const regionRule = /region="([^"]*)"/;
|
||||
const previewRule = /preview/;
|
||||
const hideCodeRule = /hideCode/;
|
||||
const hideDollarRule = /hideDollar/;
|
||||
const preferRule = /\b(prefer|avoid)\b/;
|
||||
|
||||
export const docsCodeExtension = {
|
||||
@@ -53,9 +52,8 @@ export const docsCodeExtension = {
|
||||
const language = languageRule.exec(attr);
|
||||
const visibleLines = visibleLinesRule.exec(attr);
|
||||
const region = regionRule.exec(attr);
|
||||
const preview = previewRule.test(attr);
|
||||
const hideCode = hideCodeRule.test(attr);
|
||||
const hideDollar = hideDollarRule.test(attr);
|
||||
const preview = previewRule.exec(attr) ? true : false;
|
||||
const hideCode = hideCodeRule.exec(attr) ? true : false;
|
||||
const classes = classRule.exec(attr);
|
||||
const style = preferRule.exec(attr);
|
||||
|
||||
@@ -80,7 +78,6 @@ export const docsCodeExtension = {
|
||||
region: region?.[1],
|
||||
preview: preview,
|
||||
hideCode,
|
||||
hideDollar,
|
||||
style: style?.[1] as 'prefer' | 'avoid' | undefined,
|
||||
classes: classes?.[1]?.split(' '),
|
||||
};
|
||||
|
||||
@@ -39,8 +39,6 @@ export interface CodeToken extends Tokens.Generic {
|
||||
highlight?: string;
|
||||
/** Whether to hide the copy button */
|
||||
hideCopy?: boolean;
|
||||
/** Whether to hide the dollar sign in the shell code */
|
||||
hideDollar?: boolean;
|
||||
|
||||
// additional classes for the element
|
||||
classes?: string[];
|
||||
@@ -134,10 +132,6 @@ function applyContainerAttributesAndClasses(el: Element, token: CodeToken) {
|
||||
if (token.hideCopy) {
|
||||
el.setAttribute('hideCopy', 'true');
|
||||
}
|
||||
if (token.hideDollar) {
|
||||
el.setAttribute('hideDollar', 'true');
|
||||
}
|
||||
|
||||
const language = token.language;
|
||||
|
||||
if (language === 'mermaid') {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Token, Tokens, RendererThis, TokenizerThis} from 'marked';
|
||||
import {anchorTarget} from '../../helpers.mjs';
|
||||
import {anchorTarget, isExternalLink} from '../../helpers.mjs';
|
||||
import {AdevDocsRenderer} from '../../renderer.mjs';
|
||||
|
||||
interface DocsPillToken extends Tokens.Generic {
|
||||
@@ -70,7 +70,11 @@ export const docsPillExtension = {
|
||||
|
||||
return `
|
||||
<a class="docs-pill" href="${token.href}"${targetAttr}${downloadAttr}>
|
||||
${this.parser.parseInline(token.tokens)}
|
||||
${this.parser.parseInline(token.tokens)}${
|
||||
isExternalLink(token.href)
|
||||
? '<docs-icon class="docs-icon-small">open_in_new</docs-icon>'
|
||||
: ''
|
||||
}
|
||||
</a>
|
||||
`;
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import {chromium} from 'playwright-core';
|
||||
import {Mermaid, MermaidConfig} from 'mermaid';
|
||||
import {resolve, join} from 'path';
|
||||
|
||||
const runfilesDir = process.env['JS_BINARY__RUNFILES']!;
|
||||
const runfilesDir = process.env['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.
|
||||
|
||||
+2
-37
@@ -1,39 +1,4 @@
|
||||
Introductory paragraph that should be preserved before any card containers.
|
||||
|
||||
## First Section
|
||||
|
||||
This section contains important information about the feature status.
|
||||
|
||||
### Available features
|
||||
|
||||
- Feature one
|
||||
- Feature two
|
||||
- Feature three
|
||||
|
||||
### In progress
|
||||
|
||||
The following items are currently being developed:
|
||||
|
||||
1. Item A with description
|
||||
2. Item B with description
|
||||
|
||||
<docs-card-container>
|
||||
<docs-card title="Card One"></docs-card>
|
||||
<docs-card title="Card Two"></docs-card>
|
||||
<docs-card></docs-card>
|
||||
<docs-card></docs-card>
|
||||
</docs-card-container>
|
||||
|
||||
### Additional context
|
||||
|
||||
More text after the first card container that should also be preserved.
|
||||
|
||||
## Second Section
|
||||
|
||||
Another paragraph before the second card container.
|
||||
|
||||
<docs-card-container>
|
||||
<docs-card title="Card Three"></docs-card>
|
||||
</docs-card-container>
|
||||
|
||||
## Final Section
|
||||
|
||||
Concluding remarks at the end of the document.
|
||||
|
||||
+4
-54
@@ -20,60 +20,10 @@ describe('markdown to html', () => {
|
||||
markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, rendererContext));
|
||||
});
|
||||
|
||||
it('creates multiple card containers with correct card counts', () => {
|
||||
const containers = markdownDocument.querySelectorAll('.docs-card-grid');
|
||||
it('creates card containers containing multiple cards', () => {
|
||||
const containerEl = markdownDocument.querySelector('.docs-card-grid');
|
||||
|
||||
expect(containers.length).toBe(2);
|
||||
expect(containers[0].children.length).toBe(2);
|
||||
expect(containers[1].children.length).toBe(1);
|
||||
});
|
||||
|
||||
it('preserves all h2 section headings', () => {
|
||||
const h2Elements = markdownDocument.querySelectorAll('h2');
|
||||
|
||||
expect(h2Elements.length).toBe(3);
|
||||
expect(h2Elements[0].textContent).toContain('First Section');
|
||||
expect(h2Elements[1].textContent).toContain('Second Section');
|
||||
expect(h2Elements[2].textContent).toContain('Final Section');
|
||||
});
|
||||
|
||||
it('preserves all h3 subsection headings outside card containers', () => {
|
||||
// Card titles also render as h3, so we filter to only h3s not inside card containers
|
||||
const allH3s = Array.from(markdownDocument.querySelectorAll('h3'));
|
||||
const sectionH3s = allH3s.filter((h3) => !h3.closest('.docs-card-grid'));
|
||||
|
||||
expect(sectionH3s.length).toBe(3);
|
||||
expect(sectionH3s[0].textContent).toContain('Available features');
|
||||
expect(sectionH3s[1].textContent).toContain('In progress');
|
||||
expect(sectionH3s[2].textContent).toContain('Additional context');
|
||||
});
|
||||
|
||||
it('preserves unordered lists before card containers', () => {
|
||||
const ulElements = markdownDocument.querySelectorAll('ul');
|
||||
|
||||
expect(ulElements.length).toBeGreaterThanOrEqual(1);
|
||||
expect(ulElements[0].children.length).toBe(3);
|
||||
expect(ulElements[0].textContent).toContain('Feature one');
|
||||
});
|
||||
|
||||
it('preserves ordered lists before card containers', () => {
|
||||
const olElements = markdownDocument.querySelectorAll('ol');
|
||||
|
||||
expect(olElements.length).toBe(1);
|
||||
expect(olElements[0].children.length).toBe(2);
|
||||
expect(olElements[0].textContent).toContain('Item A');
|
||||
});
|
||||
|
||||
it('preserves paragraphs throughout the document', () => {
|
||||
const paragraphs = markdownDocument.querySelectorAll('p');
|
||||
|
||||
expect(paragraphs.length).toBeGreaterThanOrEqual(5);
|
||||
|
||||
const paragraphTexts = Array.from(paragraphs).map((p) => p.textContent);
|
||||
expect(paragraphTexts.some((t) => t?.includes('Introductory paragraph'))).toBeTrue();
|
||||
expect(paragraphTexts.some((t) => t?.includes('important information'))).toBeTrue();
|
||||
expect(paragraphTexts.some((t) => t?.includes('More text after'))).toBeTrue();
|
||||
expect(paragraphTexts.some((t) => t?.includes('Another paragraph'))).toBeTrue();
|
||||
expect(paragraphTexts.some((t) => t?.includes('Concluding remarks'))).toBeTrue();
|
||||
expect(containerEl!.children.length).toBe(2);
|
||||
expect(containerEl!.classList.contains('docs-card-grid')).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,3 @@ this is a code block
|
||||
```
|
||||
code block without language
|
||||
```
|
||||
|
||||
```shell {hideDollar}
|
||||
echo "hello"
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user