mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
342 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6aafd87038 | |||
| d4c8a9a887 | |||
| 057cc6d09d | |||
| dea76c7a6e | |||
| a07d64b406 | |||
| a6d6842467 | |||
| 33a30e0e03 | |||
| a385743983 | |||
| c9f8f3afb6 | |||
| 910dcb6d6a | |||
| de12bc7e02 | |||
| a4f312060c | |||
| de533fe491 | |||
| ea1e34c4dd | |||
| 304222014a | |||
| 82192deda9 | |||
| e8265f4593 | |||
| c0496c1f39 | |||
| 2d2ba938da | |||
| ff8911fe52 | |||
| 72d7a47dfd | |||
| 2c6781071f | |||
| e40d378f3e | |||
| d715c366d9 | |||
| c91bd074c0 | |||
| 2ec9ff087f | |||
| 8102331f82 | |||
| 44fc79b7f7 | |||
| 39827a08fe | |||
| 04f5841eed | |||
| 958fc6ab8a | |||
| cbfd1ee571 | |||
| b0dc2fbfcb | |||
| ef204bdf7a | |||
| 4441a95119 | |||
| d150e2672e | |||
| 59f9912dc1 | |||
| bce82bdc34 | |||
| 2ae0912eea | |||
| e583f4c808 | |||
| 05bcc601ac | |||
| 9f61910923 | |||
| 7797671257 | |||
| 0b08e29f26 | |||
| 50761c8be4 | |||
| 8d56b17b95 | |||
| 4ad6d582d2 | |||
| f363ab41f1 | |||
| 0960592d3d | |||
| d04ddd73df | |||
| b682c62873 | |||
| fea25d1a60 | |||
| f916531ee1 | |||
| 8fd896e99a | |||
| 895c576d49 | |||
| 5fd4f0f854 | |||
| 429513bed0 | |||
| f22d9a7098 | |||
| cf7846bd62 | |||
| 9ea8cb6eea | |||
| 3c41e74fdd | |||
| efbba945bc | |||
| 7d54952e85 | |||
| 94b1b530dc | |||
| 0c9c270552 | |||
| 9b48cb2989 | |||
| bba5ed8e64 | |||
| 56c872dacc | |||
| c8902deb00 | |||
| 4b2008d0f2 | |||
| b453c0ce67 | |||
| a9ca90fc11 | |||
| 807cabd9e1 | |||
| 13f050d220 | |||
| d0cf2995a2 | |||
| a35d572b22 | |||
| 80fd08bcc8 | |||
| d8239c7836 | |||
| c1579163b7 | |||
| dbb15ebab2 | |||
| a6c49e0ea9 | |||
| 2c12339ab8 | |||
| a3048f231c | |||
| ae2f2497d3 | |||
| 2c7d3b5704 | |||
| 6bd2121817 | |||
| e8c047bd8d | |||
| a0ca5a50b2 | |||
| 1cdb322600 | |||
| c9585b7159 | |||
| 238b07fe90 | |||
| 3b3ab8b626 | |||
| e731bbdd14 | |||
| c7a18924a6 | |||
| 244e0b0b59 | |||
| 9bfe1a6065 | |||
| b658accf1f | |||
| 9be5e2c211 | |||
| 8818095a82 | |||
| 80c85ec8a5 | |||
| fb7a10fb18 | |||
| 1af0db5742 | |||
| a821e06e33 | |||
| a21be36e15 | |||
| 7aaa8b9b39 | |||
| d0481860bf | |||
| 880a57d4b3 | |||
| 1ba89d1128 | |||
| 831746eff1 | |||
| 9182b62b96 | |||
| 4d2a9394c3 | |||
| ad0156e056 | |||
| aa3220a4b5 | |||
| b4ab6ba2e8 | |||
| 73d6b01b47 | |||
| 3838554503 | |||
| 1148c93834 | |||
| 9f92312cde | |||
| 04b16dee8f | |||
| 57ce2b64ef | |||
| 5393d8ea2c | |||
| 675d4fb489 | |||
| 851ef77318 | |||
| 5800f17e19 | |||
| e19150d2b5 | |||
| 0e496c1d85 | |||
| 737777ef72 | |||
| fa26e7271f | |||
| 076d41c3f6 | |||
| a8f80c1f99 | |||
| bf948be4c2 | |||
| 23ea431c4e | |||
| 2fb767b139 | |||
| b10e305ccc | |||
| f0d19b88b1 | |||
| 67e0ba7e03 | |||
| ca328eab8f | |||
| faeeef678a | |||
| 4d786517d3 | |||
| 26c43d14ba | |||
| 1890c3008b | |||
| cbb2266984 | |||
| ea4847aae5 | |||
| 5fa3052ed1 | |||
| 86718fbbe1 | |||
| de5fbb7deb | |||
| f39896da99 | |||
| 21cab20dae | |||
| 2d28e9b392 | |||
| 5b46a63c08 | |||
| 03fb3cd463 | |||
| 67a73192bf | |||
| e5ed276f27 | |||
| 334ae10168 | |||
| 1f2dc72e8a | |||
| e3b0e5a0b3 | |||
| 10b0dd94e9 | |||
| 6f41efc689 | |||
| bfb8b177ac | |||
| 2feced366a | |||
| 5d755be01c | |||
| 8017617fd9 | |||
| ed2d324f9c | |||
| d1dcd16c5b | |||
| abbd8797bb | |||
| c06e3a0bb4 | |||
| 8ba2844da5 | |||
| 74698d58ec | |||
| a7e3d764e5 | |||
| 7907e982ee | |||
| adda6c5c10 | |||
| e7593653be | |||
| 21b1c3b2ee | |||
| 6c73aaca30 | |||
| c98eab794c | |||
| d45fced21b | |||
| ed48c41f65 | |||
| 75135586d6 | |||
| ff697707ba | |||
| 7d76103999 | |||
| 575f3023c1 | |||
| 4c86ed382d | |||
| 224e60ecb1 | |||
| 492e7560e6 | |||
| 09638ec7c3 | |||
| ab35383285 | |||
| 934e2732b3 | |||
| c20dabda2a | |||
| 62a97f7e4b | |||
| 106cf4ee90 | |||
| 024e307ab0 | |||
| 77b035eca6 | |||
| 4a9b715b3a | |||
| 420aa725ae | |||
| c499c578c9 | |||
| 0588133210 | |||
| 6416a31c1b | |||
| b2b903957a | |||
| bd72571583 | |||
| 628f3f99db | |||
| 79ac570c26 | |||
| 276c12a349 | |||
| 104ac117ea | |||
| c2a74548d9 | |||
| e7d1d8def3 | |||
| 4a390b6c73 | |||
| 5a486a65cf | |||
| c822bf8e76 | |||
| 31e2c99007 | |||
| f8b6bd0cbe | |||
| 5349b027b8 | |||
| 8f8d67dc57 | |||
| 23153a06dd | |||
| fd3b4413f3 | |||
| 670d1660c4 | |||
| 8ca0237d99 | |||
| 3576019c0f | |||
| 05d022d5e6 | |||
| 1df1697c6e | |||
| 05476ea21b | |||
| f38169d0a7 | |||
| 6d7446bc6c | |||
| c25c84d7dc | |||
| f4160c2073 | |||
| 93c6dc6395 | |||
| 76431ed3e3 | |||
| b4ec3cc4e4 | |||
| 84e79f5add | |||
| 2eecf0d9a2 | |||
| da57d1af73 | |||
| 58ea4d2ee8 | |||
| 277ade97ac | |||
| aeb9b819d2 | |||
| 657d532ad1 | |||
| 8dafd2413a | |||
| 796b9a714a | |||
| dd551b1ff3 | |||
| 84adb2fb3b | |||
| 98343ea35e | |||
| c4226a10d0 | |||
| 5e545069ac | |||
| ecf0bb4668 | |||
| 0d54c5f0e2 | |||
| 8f6ca1d9f9 | |||
| 1f51c61372 | |||
| f5e9a862c2 | |||
| 0c8a2858b9 | |||
| a79e8afbad | |||
| 7b7faef739 | |||
| 95afc09206 | |||
| 23cb9a4291 | |||
| 0a863ba03f | |||
| e923d88398 | |||
| 150f0b3c30 | |||
| fc5f9d03cf | |||
| 498d9cdda4 | |||
| fd8d29a157 | |||
| e2e9a9a531 | |||
| 35fd8b567a | |||
| 4e9648d184 | |||
| 54a0541013 | |||
| 9c0587f755 | |||
| ac06941bcf | |||
| 907415c801 | |||
| 225a56677e | |||
| 944181869d | |||
| 3d3e6cbf20 | |||
| 2158bf8bb8 | |||
| 1b46b6db18 | |||
| f23f3e7f5f | |||
| 9414da5b7e | |||
| 8078abe893 | |||
| 661e800302 | |||
| c960a6e492 | |||
| 2eeb4b5666 | |||
| 8f19cf8d84 | |||
| 50a9ed190d | |||
| e437980659 | |||
| d3ab1ea5f2 | |||
| fe7b26e403 | |||
| 70e4c7fbab | |||
| a25f74238e | |||
| 61ad0f8b90 | |||
| a86c3214c2 | |||
| 706cfecaee | |||
| 63cb3f4fa8 | |||
| 787cd875b0 | |||
| 1a151f2dae | |||
| 6b112e6c6c | |||
| 7d58b798c6 | |||
| ab1c84eed9 | |||
| 103a44f690 | |||
| de9092eb23 | |||
| 0b0b34565a | |||
| b17a7e062f | |||
| 7c976c3453 | |||
| eb6bce0ff5 | |||
| 30e486a41c | |||
| a73d5435d8 | |||
| b9d3fe3b44 | |||
| c9ecf9e40b | |||
| 2f213de593 | |||
| ec32591dd7 | |||
| 02b7f5e4f7 | |||
| 31bf694599 | |||
| bdfb60f3e3 | |||
| e99b92a1f0 | |||
| 5c24de5ee8 | |||
| fad5def327 | |||
| de2eab6820 | |||
| ea1aeb3d45 | |||
| 35f0663ce8 | |||
| 2eeeabb760 | |||
| 87372893ea | |||
| 631f60d1f9 | |||
| 390efd51e7 | |||
| c2cedd1954 | |||
| 3c4deaa52b | |||
| b5eea233df | |||
| c0cb6040f8 | |||
| fb901f7eda | |||
| 17da2c392e | |||
| 6cf0100fe4 | |||
| d2137928e8 | |||
| 1d4783c01c | |||
| 9ee99c7cc0 | |||
| 5b8a403220 | |||
| 9c8f2dab7d | |||
| 9f1a78ca74 | |||
| d45440eda4 | |||
| eb7c593fda | |||
| 48802fd7ed | |||
| 5cea3576cf | |||
| 913bb6ad14 | |||
| 79f1cd462e | |||
| 4ea3cafdc8 | |||
| 94b9a3ef70 | |||
| c5afb88d9a | |||
| f58f9c892e | |||
| 7f33cbeb6c | |||
| dd630ef955 | |||
| 13fd2b1aa3 |
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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`.
|
||||
description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. Use when authoring or reviewing content in adev/src/content.
|
||||
---
|
||||
|
||||
# Angular Documentation (adev) Writing Guide
|
||||
|
||||
@@ -13,8 +13,7 @@ When reviewing a pull request for the `angular` repository, follow these essenti
|
||||
|
||||
2. **Key Focus Areas**:
|
||||
- **Comprehensive Reviews**: You **MUST always** perform a deep, comprehensive review of the _entire_ pull request. If the user asks you to look into a specific issue, file, or area of concern, you must investigate that specific area _in addition to_ reviewing the rest of the PR's substantive changes. Do not terminate your review after addressing only the user's focal point.
|
||||
- **Package-Specific & Topic Guidelines**: Check if there are specific guidelines for the package or topic being modified in the `reference/` directory (e.g., `reference/router.md` or `reference/object_create_null.md`). Always prioritize these rules for their respective areas.
|
||||
- **Prototype Collision & `Object.create(null)` PRs**: When reviewing PRs that swap `{}` for `Object.create(null)`, consult `reference/object_create_null.md` for technical evaluation criteria and rules.
|
||||
- **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.
|
||||
@@ -51,7 +50,6 @@ When reviewing a pull request for the `angular` repository, follow these essenti
|
||||
- **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
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Rules for `Object.create(null)` and Prototype Collision Prevention
|
||||
|
||||
This guide outlines the technical rules and evaluation criteria for using `Object.create(null)` versus standard object literals (`{}`) or `Map` in the Angular codebase.
|
||||
|
||||
---
|
||||
|
||||
## 1. When `Object.create(null)` is Appropriate
|
||||
|
||||
Using `Object.create(null)` (or `Map`) is appropriate when **all** of the following conditions are met:
|
||||
|
||||
1. The object is used as an **internal key-value lookup map or set**.
|
||||
2. The keys are **arbitrary or untrusted dynamic strings** (e.g., URL query parameters in `$locationShim`, HTML sanitizer tag sets, or `jsaction` DOM event-type resolvers).
|
||||
3. Property existence is checked via direct indexing or key checks (e.g., `map[key] !== undefined` or `key in map`), where a key matching an `Object.prototype` member (such as `'toString'`, `'constructor'`, or `'hasOwnProperty'`) causes false positive matches or incorrect behavior.
|
||||
|
||||
---
|
||||
|
||||
## 2. Handling Public API and Boundary Objects
|
||||
|
||||
If an object receives untrusted dynamic keys **and** is exposed to public consumers or third-party code (e.g., `SimpleChanges` in `ngOnChanges`):
|
||||
|
||||
- **Do NOT blindly change the object to `Object.create(null)`**: Stripping `Object.prototype` from public objects is a breaking API change. Consumer code calling `.hasOwnProperty()`, `.toString()`, `.valueOf()`, or using string interpolation (`` `${obj}` ``) will fail at runtime (`TypeError: obj.hasOwnProperty is not a function`).
|
||||
- **Safe Alternatives for Public Objects:**
|
||||
- **`Object.hasOwn(obj, key)`**: Use `Object.hasOwn` for internal framework property lookups instead of direct index or `in` checks. This prevents prototype collision during internal reads without breaking the object's prototype for consumers.
|
||||
- **Input Key Sanitization**: Filter or delete dangerous key names (`__proto__`, `constructor`, `prototype`) when populating the object.
|
||||
- **`Map` or Custom Classes**: For new public APIs requiring key-value stores with dynamic keys, prefer `Map<K, V>` or dedicated classes with explicit `.get()` and `.has()` methods.
|
||||
- **Deprecation / Breaking Change Process**: If changing a public object's prototype to `null` is unavoidable, it must follow Angular's formal deprecation and major version breaking change process.
|
||||
|
||||
---
|
||||
|
||||
## 3. When `Object.create(null)` Should NOT Be Used
|
||||
|
||||
Do not replace `{}` with `Object.create(null)` in the following scenarios:
|
||||
|
||||
1. **Fixed-Shape Structs and DTOs:** Objects with hardcoded static property names (e.g., `let sortedBreakpoints: {breakpoints?: number[]} = {}`). `Object.assign({}, ...)` only copies _own_ enumerable properties, so prototype properties on sources are never copied.
|
||||
2. **Numeric-Key Maps:** Objects indexed by numbers (e.g., `tasksByHandleId: {[id: number]: Task}`). Numeric keys do not collide with `Object.prototype` string members.
|
||||
3. **Reference Sentinels:** Objects used purely for reference identity checks (e.g., `const EMPTY_OBJECT = {}` or `const IN_PROGRESS_RESOLUTION = {}`).
|
||||
4. **Internal Compiler AST and Visitor State:** Temporary objects with internally generated keys where untrusted user input cannot poison key names.
|
||||
5. **Hot Performance Paths and Size-Critical Bundles:** Standard `{}` literals use V8 fast hidden classes and monomorphic inline caching. `Object.create(null)` forces V8 dictionary mode and increases minified bundle size (e.g., in inline polyfills like `event-dispatch-contract` or SSR hydration bundles).
|
||||
@@ -5,7 +5,7 @@ description: Explains the mental model and architecture of the code under `packa
|
||||
|
||||
# Signal Forms Architecture
|
||||
|
||||
The `packages/forms/signals` directory contains the signal-based forms API for Angular.
|
||||
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
|
||||
@@ -36,27 +36,27 @@ The central internal class representing a single field in the form graph. It agg
|
||||
|
||||
- `structure`: Manages parent/child relationships and signal slicing.
|
||||
- `validationState`: Computes `valid`, `invalid`, `errors` signals.
|
||||
- `nodeState`: Tracks `touched`, `dirty`, and derived logical state.
|
||||
- `nodeState`: Tracks `touched`, `dirty`, `pristine`.
|
||||
- `metadataState`: Stores metadata like `min`, `max`, `required`.
|
||||
- `submitState`: Tracks submission status and server errors.
|
||||
|
||||
### 2. `FieldValidationState` (`src/field/validation.ts`)
|
||||
### 2. `ValidationState` (`src/field/validation.ts`)
|
||||
|
||||
Implements `ValidationState` and manages the complexity of validation:
|
||||
Manages the complexity of validation:
|
||||
|
||||
- **Synchronous Errors**: Derived from schema rules.
|
||||
- **Asynchronous Errors**: Handled via signals, including 'pending' states.
|
||||
- **Tree Errors**: Errors that bubble up or are targeted at specific fields.
|
||||
- **Submission Errors**: Server-side errors injected imperatively via `submit()`.
|
||||
|
||||
### 3. `FormField` Directive (`src/directive/form_field.ts`)
|
||||
### 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 `FormValueControl` or `FormCheckboxControl`.
|
||||
- **Custom Controls**: Components implementing `FormUiControl` or `FormValueControl`.
|
||||
- **Legacy Interop**: Components implementing `ControlValueAccessor` (via `InteropNgControl`).
|
||||
|
||||
### 4. `Schema` (`src/api/structure.ts` & `src/api/rules`)
|
||||
@@ -69,7 +69,7 @@ Defines the behavior.
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. **Read**: `form.field().value()` reads directly from the underlying signal (projected to the specific path).
|
||||
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.
|
||||
|
||||
@@ -97,7 +97,7 @@ const userForm = form(user, userRules); // OR apply(userForm, userRules)
|
||||
- `packages/forms/signals/src/api/structure.ts`: Public API entry points (`form`, `apply`).
|
||||
- `packages/forms/signals/src/api/control.ts`: Interfaces for custom controls (`FormUiControl`).
|
||||
- `packages/forms/signals/src/field/node.ts`: The `FieldNode` implementation.
|
||||
- `packages/forms/signals/src/directive/form_field.ts`: The `[formField]` directive.
|
||||
- `packages/forms/signals/src/directive/form_field_directive.ts`: The `[formField]` directive.
|
||||
|
||||
## Supplemental Information
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
This document explains how the Signal Forms system hooks into the Angular compiler and runtime to provide seamless type-checking and efficient updates.
|
||||
|
||||
## 1. Compiler Integration (Type Checking)
|
||||
## 1. Compiler-CLI Integration (Type Checking)
|
||||
|
||||
The `packages/compiler` package contains specific logic to support `[formField]`. This is primarily handled in `src/typecheck/ops/signal_forms.ts`.
|
||||
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:
|
||||
|
||||
@@ -22,7 +22,7 @@ The `packages/compiler` package contains specific logic to support `[formField]`
|
||||
|
||||
### Relevant Files:
|
||||
|
||||
- `packages/compiler/src/typecheck/ops/signal_forms.ts`: The core Signal Forms type-checking logic, including `TcbNativeFieldOp`.
|
||||
- `packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts`: The core logic for `TcbNativeFieldOp` and `SignalFormFieldOp`.
|
||||
|
||||
## 2. Core Runtime Integration
|
||||
|
||||
@@ -30,8 +30,8 @@ The `packages/core` package provides the low-level instructions that power the `
|
||||
|
||||
### Key Mechanisms:
|
||||
|
||||
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`, which causes the compiler to install `ɵɵControlFeature`.
|
||||
- **`ɵɵcontrol` Instructions**: A `formField` binding causes the compiler to emit:
|
||||
- **`ɵ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:
|
||||
@@ -43,4 +43,4 @@ The `packages/core` package provides the low-level instructions that power the `
|
||||
### Relevant Files:
|
||||
|
||||
- `packages/core/src/render3/instructions/control.ts`: Implementation of `ɵɵcontrol` instructions.
|
||||
- `packages/forms/signals/src/directive/form_field.ts`: The directive that implements the hook.
|
||||
- `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.
|
||||
@@ -159,6 +159,16 @@ common --incompatible_allow_tags_propagation
|
||||
# )
|
||||
build --nosandbox_default_allow_network
|
||||
|
||||
##################################
|
||||
# Saucelabs tests settings #
|
||||
# Turn on these settings with #
|
||||
# --config=saucelabs #
|
||||
##################################
|
||||
|
||||
# For saucelabs tests we don't want to enable flaky test attempts. Karma has its own integrated
|
||||
# retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times.
|
||||
test:saucelabs --flaky_test_attempts=1
|
||||
|
||||
################
|
||||
# Flag Aliases #
|
||||
################
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
8.8.0
|
||||
8.6.0
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Language: JavaScript
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 100
|
||||
@@ -43,5 +43,3 @@ c702e8af0b2144d97b93171dc2806ed1a0346762
|
||||
6d3f5752f204a5a30f3c09fbf6d4a510a4522bcb
|
||||
3fa7b2b136696464e0a91b5ec25c6adf272d4d6b
|
||||
ad65f44877f22caadbc8b977b5b720c3c86fcc45
|
||||
# commit that changed formatting
|
||||
698b0288bee60b8c5926148b79b5b93f454098db
|
||||
@@ -11,6 +11,7 @@ esbuild_checked_in(
|
||||
config = "esbuild.conf.js",
|
||||
entry_point = ":lib/main.mts",
|
||||
external = [
|
||||
"undici",
|
||||
"pnpapi",
|
||||
],
|
||||
metafile = False,
|
||||
@@ -35,5 +36,7 @@ ts_project(
|
||||
"//:node_modules/@actions/github",
|
||||
"//:node_modules/@angular/ng-dev",
|
||||
"//:node_modules/@types/node",
|
||||
"//:node_modules/@types/tmp",
|
||||
"//:node_modules/tmp",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import {writeFileSync, mkdtempSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {fileSync} from 'tmp';
|
||||
import {writeSync} from 'node:fs';
|
||||
import {getInput, setSecret} from '@actions/core';
|
||||
|
||||
let credentialFilePath: undefined | string;
|
||||
|
||||
export function getCredentialFilePath(): string {
|
||||
if (credentialFilePath === undefined) {
|
||||
const tmpDir = mkdtempSync(join(tmpdir(), 'credential-'));
|
||||
const filePath = join(tmpDir, 'credential.json');
|
||||
writeFileSync(filePath, getInput('serviceKey', {required: true}));
|
||||
setSecret(filePath);
|
||||
credentialFilePath = filePath;
|
||||
const tmpFile = fileSync({postfix: '.json'});
|
||||
writeSync(tmpFile.fd, getInput('serviceKey', {required: true}));
|
||||
setSecret(tmpFile.name);
|
||||
credentialFilePath = tmpFile.name;
|
||||
}
|
||||
return credentialFilePath;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
fetchLongTermSupportBranchesFromNpm,
|
||||
ActiveReleaseTrains,
|
||||
AuthenticatedGitClient,
|
||||
ReleaseConfig,
|
||||
} from '@angular/ng-dev';
|
||||
import {fetchLongTermSupportBranchesFromNpm, ActiveReleaseTrains} from '@angular/ng-dev';
|
||||
import {ReleaseConfig} from '@angular/ng-dev';
|
||||
import {AuthenticatedGitClient} from '@angular/ng-dev';
|
||||
|
||||
export interface Deployment {
|
||||
branch: string;
|
||||
|
||||
+1202
-32948
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@
|
||||
"types": ["node"],
|
||||
"lib": ["es2021"],
|
||||
"experimentalDecorators": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitOverride": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -12,4 +13,4 @@
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
name: 'Saucelabs legacy test job'
|
||||
description: 'Runs tests against Saucelabs (outside of Bazel)'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Starting Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
|
||||
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
|
||||
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
|
||||
# test in all supported browsers on Saucelabs anyway until this job can be removed.
|
||||
- name: Preparing Bazel-generated fixtures required in legacy tests
|
||||
shell: bash
|
||||
run: |
|
||||
# Locale files are needed for i18n tests running within Saucelabs. These are added
|
||||
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
|
||||
# can succeed. Note that the base locale and currencies files are checked-in, so
|
||||
# we do not need to re-generate those through Bazel.
|
||||
mkdir -p packages/common/locales/extra
|
||||
cp dist/bin/packages/common/locales/*.ts packages/common/locales
|
||||
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
|
||||
- name: Build bundle of tests to run on Saucelabs
|
||||
shell: bash
|
||||
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
|
||||
- name: Wait and confirm Saucelabs tunnel has connected
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh ready-wait
|
||||
- name: Running tests on Saucelabs.
|
||||
shell: bash
|
||||
run: KARMA_WEB_TEST_MODE=SL_REQUIRED pnpm karma start ./karma-js.conf.js --single-run
|
||||
- name: Stop Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh stop
|
||||
@@ -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@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
# `snapshot-build` config is used to stamp the exact version with sha in the footer.
|
||||
run: pnpm bazel build //adev:build.production --config=snapshot-build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
name: Deploying adev preview to Firebase
|
||||
|
||||
on:
|
||||
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
|
||||
workflow_run:
|
||||
workflows: ['Build adev for preview deployment']
|
||||
types: [completed]
|
||||
@@ -30,23 +29,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: '${{secrets.GITHUB_TOKEN}}'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Configure Firebase deploy target
|
||||
working-directory: ./
|
||||
run: |
|
||||
# We can use `npx` as the Firebase deploy actions uses it too.
|
||||
# Use stable version release
|
||||
npx -y firebase-tools@15.15.0 target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@15.15.0 target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -2,7 +2,6 @@ name: DevInfra
|
||||
|
||||
on:
|
||||
push:
|
||||
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, ready_for_review, labeled]
|
||||
|
||||
@@ -15,9 +14,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.repository.fork == false
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
|
||||
reactions: 'rocket'
|
||||
|
||||
- uses: alessbell/pull-request-comment-branch@ef3408c9757d05f89cb525036383033a313758a0 # v2.1.0
|
||||
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
|
||||
id: comment-branch
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Specify repository as the PR branch might be from a fork.
|
||||
repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}}
|
||||
@@ -36,23 +36,9 @@ jobs:
|
||||
# that the action was triggered by a team member.
|
||||
ref: ${{steps.comment-branch.outputs.head_sha}}
|
||||
|
||||
# We cannot use `angular/dev-infra/github-actions/npm/checkout-and-setup-node` here
|
||||
# because it does not support checking out from a fork (as it lacks a `repository` input).
|
||||
# Thus, we checkout and setup Node/pnpm manually.
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
@@ -63,10 +49,7 @@ jobs:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: pnpm benchmarks prepare-for-github-action "$COMMENT_BODY"
|
||||
|
||||
- env:
|
||||
COMPARE_SHA: ${{steps.info.outputs.compareSha}}
|
||||
BENCHMARK_TARGET: ${{steps.info.outputs.benchmarkTarget}}
|
||||
run: pnpm benchmarks run-compare "$COMPARE_SHA" "$BENCHMARK_TARGET"
|
||||
- run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} ${{steps.info.outputs.benchmarkTarget}}
|
||||
id: benchmark
|
||||
name: Running benchmark
|
||||
|
||||
|
||||
+38
-28
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Validate angular robot configuration
|
||||
run: pnpm ng-dev ngbot verify
|
||||
- name: Validate agent skills
|
||||
run: pnpm ng-dev misc validate-skills
|
||||
run: pnpm ng-dev ai skills validate
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: pnpm check-tooling-setup
|
||||
|
||||
@@ -41,13 +41,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
|
||||
uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 # v7.1.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -69,11 +69,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -85,11 +85,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -102,11 +102,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -121,11 +121,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -138,11 +138,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -154,11 +154,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -188,21 +188,31 @@ jobs:
|
||||
- run: pnpm -C packages/zone.js jest:test
|
||||
- run: pnpm -C packages/zone.js jest:nodetest
|
||||
- run: pnpm -C packages/zone.js vitest:test
|
||||
- run: pnpm -C packages/zone.js vitest-globals:test
|
||||
- run: pnpm -C packages/zone.js electrontest
|
||||
- run: pnpm -C packages/zone.js/test/typings test
|
||||
|
||||
# saucelabs:
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
# steps:
|
||||
# - name: Initialize environment
|
||||
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
|
||||
# - name: Install node modules
|
||||
# run: pnpm install --frozen-lockfile
|
||||
# - uses: ./.github/actions/saucelabs-legacy
|
||||
|
||||
adev-deploy:
|
||||
needs: [adev]
|
||||
if: needs.adev.result == 'success'
|
||||
runs-on: ubuntu-latest-8core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Setting `persist-credentials: false` prevents the github-action account from being the
|
||||
# account that is attempted to be used for authentication, instead the remote is set to
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
ANGULAR_READONLY_GITHUB_TOKEN: ${{ secrets.READONLY_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create a PR (if necessary)
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
|
||||
push-to-fork: 'angular-robot/angular'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: DevInfra
|
||||
|
||||
on:
|
||||
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
issues:
|
||||
@@ -16,7 +15,8 @@ jobs:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/labeling/pull-request@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/labeling/pull-request@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
labels: '{"requires: TGP": ["packages/core/primitives/**/{*,.*}"]}'
|
||||
@@ -24,14 +24,15 @@ jobs:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
issue_labels:
|
||||
if: github.event_name == 'issues'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/labeling/issue@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- 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 }}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: Google Internal Tests Enforcement
|
||||
|
||||
on:
|
||||
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
@@ -14,8 +13,8 @@ jobs:
|
||||
statuses: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Manual jobs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
|
||||
jobs:
|
||||
# Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but
|
||||
# kept as "runnable" for debugging/stabilization effort purposes.
|
||||
bazel-saucelabs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
- name: Run all saucelabs bazel tests
|
||||
run: |
|
||||
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
|
||||
pnpm bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
|
||||
@@ -1,8 +1,6 @@
|
||||
name: Merge Ready
|
||||
|
||||
on:
|
||||
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
|
||||
pull_request_target:
|
||||
on: pull_request_target
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: {}
|
||||
@@ -11,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -20,7 +21,7 @@ jobs:
|
||||
workflows: ${{ steps.workflows.outputs.workflows }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- id: workflows
|
||||
@@ -30,17 +31,14 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: list
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
strategy:
|
||||
matrix:
|
||||
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
# We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
|
||||
@@ -52,7 +50,4 @@ jobs:
|
||||
project_id: 'internal-200822'
|
||||
workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular'
|
||||
service_account: 'measures-uploader@internal-200822.iam.gserviceaccount.com'
|
||||
- run: pnpm ng-dev perf workflows --name "$WORKFLOW" --commit-sha "$COMMIT_SHA"
|
||||
env:
|
||||
WORKFLOW: ${{ matrix.workflow }}
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
- run: pnpm ng-dev perf workflows --name ${{ matrix.workflow }} --commit-sha ${{github.sha}}
|
||||
|
||||
+34
-24
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -33,13 +33,13 @@ jobs:
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: pnpm check-tooling-setup
|
||||
- name: Validate agent skills
|
||||
run: pnpm ng-dev misc validate-skills
|
||||
run: pnpm ng-dev ai skills validate
|
||||
- name: Check commit message
|
||||
run: pnpm ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
|
||||
- name: Check code format
|
||||
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
|
||||
- name: Check Package Licenses
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
|
||||
|
||||
@@ -47,13 +47,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Test build
|
||||
run: pnpm devtools:build:chrome
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
|
||||
uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 # v7.1.9
|
||||
with:
|
||||
command: pnpm devtools:test:e2e
|
||||
start: pnpm bazel run //devtools/src:devserver
|
||||
@@ -73,11 +73,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run CI tests for framework
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
ASPECT_RULES_JS_FROZEN_PNPM_LOCK: '1'
|
||||
- name: Upload GRPC logs (for debugging of RBE issues)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
path: /tmp/rbe-grpc.log
|
||||
retention-days: 1
|
||||
@@ -97,11 +97,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run integration CI tests for framework
|
||||
@@ -112,11 +112,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -129,11 +129,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -144,11 +144,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -174,6 +174,16 @@ jobs:
|
||||
- run: pnpm -C packages/zone.js jest:test
|
||||
- run: pnpm -C packages/zone.js jest:nodetest
|
||||
- run: pnpm -C packages/zone.js vitest:test
|
||||
- run: pnpm -C packages/zone.js vitest-globals:test
|
||||
- run: pnpm -C packages/zone.js electrontest
|
||||
- run: pnpm -C packages/zone.js/test/typings test
|
||||
|
||||
# saucelabs:
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
# steps:
|
||||
# - name: Initialize environment
|
||||
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
|
||||
# - name: Install node modules
|
||||
# run: pnpm install --frozen-lockfile
|
||||
# - uses: ./.github/actions/saucelabs-legacy
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Publish Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
workflow_dispatch: # Allow manual trigger for verification
|
||||
|
||||
# Ensure only one release runs at a time PER BRANCH to avoid race conditions.
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read # Required to checkout the repository
|
||||
id-token: write # Required for NPM provenance in reusable workflow
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: angular/dev-infra/.github/workflows/reusable-release.yml@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
|
||||
secrets:
|
||||
wombot-token: ${{ secrets.WOMBOT_TOKEN }}
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
@@ -0,0 +1,52 @@
|
||||
name: OpenSSF Scorecard
|
||||
on:
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results
|
||||
id-token: write
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -10,7 +10,6 @@
|
||||
"packages/compiler-cli/private/bazel.ts",
|
||||
"packages/compiler-cli/private/localize.ts",
|
||||
"packages/compiler-cli/private/tooling.ts",
|
||||
"packages/compiler-cli/private/hybrid_analysis.ts",
|
||||
"packages/compiler-cli/private/babel.d.ts",
|
||||
"packages/compiler-cli/src/bin/**",
|
||||
"packages/core/schematics/utils/tsurge/helpers/angular_devkit/**",
|
||||
|
||||
+2
-16
@@ -7,14 +7,7 @@ export const release = {
|
||||
publishRegistry: 'https://wombat-dressing-room.appspot.com',
|
||||
representativeNpmPackage: '@angular/core',
|
||||
npmPackages: [
|
||||
{
|
||||
name: '@angular/animations',
|
||||
deprecated: {
|
||||
version: '>=20.2.0-next.3',
|
||||
message:
|
||||
'@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.',
|
||||
},
|
||||
},
|
||||
{name: '@angular/animations'},
|
||||
{name: '@angular/common'},
|
||||
{name: '@angular/compiler-cli'},
|
||||
{name: '@angular/compiler'},
|
||||
@@ -24,14 +17,7 @@ export const release = {
|
||||
{name: '@angular/language-server'},
|
||||
{name: '@angular/language-service'},
|
||||
{name: '@angular/localize'},
|
||||
{
|
||||
name: '@angular/platform-browser-dynamic',
|
||||
deprecated: {
|
||||
version: '>=20.1.0-next.0',
|
||||
message:
|
||||
'@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.',
|
||||
},
|
||||
},
|
||||
{name: '@angular/platform-browser-dynamic'},
|
||||
{name: '@angular/platform-browser'},
|
||||
{name: '@angular/platform-server'},
|
||||
{name: '@angular/router'},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es2020",
|
||||
"module": "Node16",
|
||||
"noEmit": true,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Yarn Berry doesn't check engines at all, so pnpm shouldn't either.
|
||||
engine-strict = false
|
||||
|
||||
# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on
|
||||
# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what
|
||||
# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules)
|
||||
hoist=false
|
||||
|
||||
# Avoid pnpm auto-installing peer dependencies. We want to be explicit about our versions used
|
||||
# for peer dependencies, avoiding potential mismatches. In addition, it ensures we can continue
|
||||
# to rely on peer dependency placeholders substituted via Bazel.
|
||||
auto-install-peers=false
|
||||
+1
-1
@@ -53,7 +53,7 @@ function readPackage(pkg, context) {
|
||||
if (pkg.version === '0.0.0-PLACEHOLDER' && localAngularPackages.has(key)) {
|
||||
pkg.dependencies = {
|
||||
...pkg.dependencies,
|
||||
[key]: 'workspace:*',
|
||||
[key]: 'workspace: *',
|
||||
};
|
||||
|
||||
delete pkg.peerDependencies[key];
|
||||
|
||||
@@ -38,10 +38,6 @@ adev/src/content/aria/**/*.json
|
||||
adev/src/content/cli/**/*.json
|
||||
adev/src/content/cdk/**/*.json
|
||||
|
||||
# Example package.json containing docregion (`// #docregion`) markers, which are
|
||||
# not valid JSON and cannot be processed by Prettier's json-stringify parser.
|
||||
adev/src/content/examples/schematics-for-libraries/projects/my-lib/package.json
|
||||
|
||||
# Antigravity rules
|
||||
.agent/rules/agents.md
|
||||
|
||||
|
||||
+43
-32
@@ -36,9 +36,8 @@
|
||||
|
||||
version: 3
|
||||
|
||||
availability:
|
||||
users_unavailable:
|
||||
- devversion
|
||||
#availability:
|
||||
# users_unavailable: []
|
||||
|
||||
# Meta field that goes unused by PullApprove to allow for defining aliases to be
|
||||
# used throughout the config.
|
||||
@@ -100,11 +99,11 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- kirjs
|
||||
- JoostK
|
||||
- ~amishne
|
||||
@@ -120,7 +119,6 @@ groups:
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/core/primitives/*'), [
|
||||
'.agent/skills/**/{*,.*}',
|
||||
'contributing-docs/public-api-surface.md',
|
||||
'dev-app/**/{*,.*}',
|
||||
'integration/**/{*,.*}',
|
||||
@@ -138,7 +136,6 @@ groups:
|
||||
'packages/platform-browser/**/{*,.*}',
|
||||
'packages/platform-browser-dynamic/**/{*,.*}',
|
||||
'packages/platform-server/**/{*,.*}',
|
||||
'packages/private/**/{*,.*}',
|
||||
'packages/ssr/**/{*,.*}',
|
||||
'packages/router/**/{*,.*}',
|
||||
'packages/service-worker/**/{*,.*}',
|
||||
@@ -148,12 +145,12 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- kirjs
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- JeanMeche
|
||||
- ~amishne
|
||||
@@ -203,13 +200,12 @@ groups:
|
||||
users:
|
||||
- ~JiaLiPassion
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- crisbeto
|
||||
- devversion
|
||||
- kirjs
|
||||
- alan-agius4
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
@@ -254,13 +250,13 @@ groups:
|
||||
users:
|
||||
- alan-agius4
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- bencodezen
|
||||
- crisbeto
|
||||
- kirjs
|
||||
- JeanMeche
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- devversion
|
||||
- josephperrott
|
||||
- ~pkozlowski-opensource
|
||||
@@ -286,14 +282,13 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- AleksanderBodurri
|
||||
- csmick
|
||||
- ~devversion
|
||||
- dgp1130
|
||||
- eduhmc
|
||||
- hawkgs
|
||||
- JeanMeche
|
||||
- hybrist
|
||||
- ~josephperrott
|
||||
- ~milomg
|
||||
- JeanMeche
|
||||
- milomg
|
||||
|
||||
# =========================================================
|
||||
# Dev-infra
|
||||
@@ -303,10 +298,9 @@ groups:
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files
|
||||
.exclude('.pullapprove.yml')
|
||||
.exclude('.pullapprove.yml')
|
||||
.exclude('tools/manual_api_docs/blocks/*.md')
|
||||
.exclude('tools/manual_api_docs/elements/*.md')
|
||||
.exclude('.agent/**/*.md'),
|
||||
.exclude('tools/manual_api_docs/elements/*.md'),
|
||||
[
|
||||
'{*,.*}',
|
||||
'.agent/**/{*,.*}',
|
||||
@@ -323,14 +317,18 @@ groups:
|
||||
'modules/{*,.*}',
|
||||
'packages/{*,.*}',
|
||||
'packages/examples/test-utils/**/{*,.*}',
|
||||
'packages/private/**/{*,.*}',
|
||||
'packages/examples/{*,.*}',
|
||||
'scripts/**/{*,.*}',
|
||||
'third_party/**/{*,.*}',
|
||||
'tools/contributing-stats/**/{*,.*}',
|
||||
'tools/gulp-tasks/**/{*,.*}',
|
||||
'tools/legacy-saucelabs/**/{*,.*}',
|
||||
'tools/manual_api_docs/**/{*,.*}',
|
||||
'tools/pnpm-patches/**/{*,.*}',
|
||||
'tools/rxjs/**/{*,.*}',
|
||||
'tools/saucelabs-daemon/**/{*,.*}',
|
||||
'tools/saucelabs/**/{*,.*}',
|
||||
'tools/symbol-extractor/**/{*,.*}',
|
||||
'tools/testing/**/{*,.*}',
|
||||
'tools/tslint/**/{*,.*}',
|
||||
@@ -374,10 +372,10 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- ~alxhub
|
||||
- atscott
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- kirjs
|
||||
- crisbeto
|
||||
@@ -410,11 +408,10 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- kirjs
|
||||
- JeanMeche
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- ~pkozlowski-opensource
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
@@ -459,10 +456,10 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- ~alxhub
|
||||
- ~AndrewKushnir
|
||||
- AndrewKushnir
|
||||
- andrewseguin
|
||||
- dgp1130
|
||||
- ~thePunderWoman
|
||||
- thePunderWoman
|
||||
- josephperrott
|
||||
|
||||
# =========================================================
|
||||
@@ -481,12 +478,26 @@ groups:
|
||||
users:
|
||||
- ~pkozlowski-opensource # Pawel Kozlowski
|
||||
- ~alxhub # Alex Rickabaugh
|
||||
- ~thePunderWoman # Jessica Janiuk
|
||||
- ~AndrewKushnir # Andrew Kushnir
|
||||
- thePunderWoman # Jessica Janiuk
|
||||
- AndrewKushnir # Andrew Kushnir
|
||||
- atscott # Andrew Scott
|
||||
|
||||
# External team required reviews
|
||||
primitives-shared:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/core/primitives/**/*spec.ts'), [
|
||||
'packages/core/primitives/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- csmick # Cameron Smick
|
||||
- mturco # Matt Turco
|
||||
- iteriani # Thomas Nguyen
|
||||
- tbondwilkinson # Tom Wilkinson
|
||||
- rahatarmanahmed # Rahat Ahmed
|
||||
- e-cline # Ethan Cline
|
||||
- rockymeza # Rocky Meza
|
||||
- ENAML # Ethan Cline
|
||||
|
||||
####################################################################################
|
||||
# Override managed result groups
|
||||
|
||||
+21
-1
@@ -1,3 +1,4 @@
|
||||
load("@aspect_rules_js//js:defs.bzl", "js_library")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@devinfra//bazel/validation:defs.bzl", "validate_ts_version_matching")
|
||||
load("@npm//:defs.bzl", "npm_link_all_packages")
|
||||
@@ -7,6 +8,8 @@ package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files([
|
||||
"LICENSE",
|
||||
"karma-js.conf.js",
|
||||
"browser-providers.conf.js",
|
||||
"package.json",
|
||||
])
|
||||
|
||||
@@ -22,7 +25,7 @@ filegroup(
|
||||
)
|
||||
|
||||
validate_ts_version_matching(
|
||||
module_bazel = "MODULE.bazel",
|
||||
module_lock_file = "MODULE.bazel.lock",
|
||||
package_json = "package.json",
|
||||
)
|
||||
|
||||
@@ -31,6 +34,14 @@ alias(
|
||||
actual = "//packages:tsconfig-build.json",
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = "browser-providers",
|
||||
srcs = [
|
||||
"browser-providers.conf.d.ts",
|
||||
"browser-providers.conf.js",
|
||||
],
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "angularjs_scripts",
|
||||
srcs = [
|
||||
@@ -53,6 +64,15 @@ config_setting(
|
||||
values = {"stamp": "true"},
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "sauce_connect",
|
||||
actual = select({
|
||||
"@devinfra//bazel/constraints:linux_x64": "@sauce_connect_linux_amd64//:bin/sc",
|
||||
"@devinfra//bazel/constraints:macos_x64": "@sauce_connect_mac//:bin/sc",
|
||||
"@devinfra//bazel/constraints:macos_arm64": "@sauce_connect_mac//:bin/sc",
|
||||
}),
|
||||
)
|
||||
|
||||
# When enabled, this flag substitutes dependency versions with snapshot repositories
|
||||
# for all packages in this repository. Note that this does not apply to peer
|
||||
# dependencies, as they must be installed directly.
|
||||
|
||||
+9
-1660
File diff suppressed because it is too large
Load Diff
@@ -4967,9 +4967,9 @@ To learn about the release highlights and our CLI-powered automated update workf
|
||||
- **ivy:** i18n - render legacy message ids in `$localize` if requested ([#32937](https://github.com/angular/angular/issues/32937)) ([bcbf3e4](https://github.com/angular/angular/commit/bcbf3e4))
|
||||
- **language-service:** module definitions on directive hover ([#32763](https://github.com/angular/angular/issues/32763)) ([0d186dd](https://github.com/angular/angular/commit/0d186dd)), closes [#32565](https://github.com/angular/angular/issues/32565)
|
||||
- **ngcc:** expose `--create-ivy-entry-points` option on ivy-ngcc ([#33049](https://github.com/angular/angular/issues/33049)) ([b2b917d](https://github.com/angular/angular/commit/b2b917d)), closes [/github.com/angular/angular/pull/32999#issuecomment-539937368](https://github.com/angular/angular/pull/32999/issues/issuecomment-539937368)
|
||||
- update rxjs peerDependencies minimum requirement to 6.5.3 ([#32812](https://github.com/angular/angular/issues/32812)) ([66658c4](https://github.com/angular/angular/commit/66658c4))
|
||||
- update rxjs peerDependencies minimum requirment to 6.5.3 ([#32812](https://github.com/angular/angular/issues/32812)) ([66658c4](https://github.com/angular/angular/commit/66658c4))
|
||||
- **ivy:** support ng-add in localize package ([#32791](https://github.com/angular/angular/issues/32791)) ([e41cbfb](https://github.com/angular/angular/commit/e41cbfb))
|
||||
- **language-service:** allow retrieving synchronized analyzed NgModules ([#32779](https://github.com/angular/angular/issues/32779)) ([98feee7](https://github.com/angular/angular/commit/98feee7))
|
||||
- **language-service:** allow retreiving synchronized analyzed NgModules ([#32779](https://github.com/angular/angular/issues/32779)) ([98feee7](https://github.com/angular/angular/commit/98feee7))
|
||||
- **service-worker:** remove deprecated `versionedFiles` option ([#32862](https://github.com/angular/angular/issues/32862)) ([5d5c94d](https://github.com/angular/angular/commit/5d5c94d))
|
||||
- **language-service:** expose determining the NgModule of a Directive ([#32710](https://github.com/angular/angular/issues/32710)) ([2846505](https://github.com/angular/angular/commit/2846505)), closes [#32565](https://github.com/angular/angular/issues/32565)
|
||||
- **bazel:** support ts_library targets as entry-points for ng_package ([#32610](https://github.com/angular/angular/issues/32610)) ([217db9b](https://github.com/angular/angular/commit/217db9b))
|
||||
@@ -5114,7 +5114,7 @@ To learn about the release highlights and our CLI-powered automated update workf
|
||||
We assume you will fetch rules_nodejs in your WORKSPACE file, and no other dependencies remain here.
|
||||
Simply remove any calls to this function and the corresponding load statement.
|
||||
- typescript 3.4 and 3.5 are no longer supported, please update to typescript 3.6
|
||||
- We no longer directly have a direct dependency on `tslib`. Instead it is now listed a `peerDependency`.
|
||||
- We no longer directly have a direct depedency on `tslib`. Instead it is now listed a `peerDependency`.
|
||||
|
||||
Users not using the CLI will need to manually install `tslib` via;
|
||||
|
||||
@@ -6617,7 +6617,7 @@ For example:
|
||||
- **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||
- **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
|
||||
- **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||
- **platform-server:** don't reflect innerHTML property to attribute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
- **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
- **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
@@ -9193,7 +9193,7 @@ Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 re
|
||||
- **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
|
||||
- **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
|
||||
- **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
|
||||
- **core:** properly destroy embedded Views attached to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
|
||||
- **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
|
||||
- **core:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
|
||||
- **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
|
||||
- **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
|
||||
|
||||
+53
-37
@@ -4,42 +4,42 @@ module(
|
||||
name = "angular",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_pkg", version = "1.3.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.5")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.10.1")
|
||||
bazel_dep(name = "aspect_rules_js", version = "3.4.1")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.27.0")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.3")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.8.8")
|
||||
bazel_dep(name = "aspect_rules_js", version = "3.0.3")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.1")
|
||||
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4")
|
||||
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.2")
|
||||
bazel_dep(name = "bazel_lib", version = "3.7.2")
|
||||
bazel_dep(name = "tar.bzl", version = "0.10.8")
|
||||
bazel_dep(name = "yq.bzl", version = "0.4.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "bazel_lib", version = "3.2.2")
|
||||
bazel_dep(name = "tar.bzl", version = "0.10.1")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.5")
|
||||
bazel_dep(name = "rules_angular")
|
||||
git_override(
|
||||
module_name = "rules_angular",
|
||||
commit = "5fa856469c642490a6e381a05d7f58f60fad1913",
|
||||
commit = "6c36180c2efebc6526ef0e6a55a6d738c7de6909",
|
||||
remote = "https://github.com/angular/rules_angular.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "devinfra")
|
||||
git_override(
|
||||
module_name = "devinfra",
|
||||
commit = "4a09f9588769980eacb41fcc76e2c76d048eac42",
|
||||
commit = "ba726e7bca0b08b125ccc6f93c233749e1213c17",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_sass")
|
||||
git_override(
|
||||
module_name = "rules_sass",
|
||||
commit = "bf9d6564f57b4df94b4915086ad0e7ad49a95f37",
|
||||
commit = "b5ddaa8e77509bcce35158ad20009636d3da4fbc",
|
||||
remote = "https://github.com/angular/rules_sass.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_browsers")
|
||||
git_override(
|
||||
module_name = "rules_browsers",
|
||||
commit = "c37398d63f5e990d618d02f4c6fb2fefe40f1a79",
|
||||
commit = "652b57c41218be318f33fc92032696f53d3aa0ef",
|
||||
remote = "https://github.com/angular/rules_browsers.git",
|
||||
)
|
||||
|
||||
@@ -49,15 +49,15 @@ use_repo(yq, "yq_toolchains")
|
||||
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
|
||||
node.toolchain(
|
||||
node_repositories = {
|
||||
"24.21.0-darwin_arm64": ("node-v24.21.0-darwin-arm64.tar.gz", "node-v24.21.0-darwin-arm64", "bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057"),
|
||||
"24.21.0-darwin_amd64": ("node-v24.21.0-darwin-x64.tar.gz", "node-v24.21.0-darwin-x64", "1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097"),
|
||||
"24.21.0-linux_arm64": ("node-v24.21.0-linux-arm64.tar.xz", "node-v24.21.0-linux-arm64", "6ad1325edbdb5649c379b75a237147a666c95d4f9ae8d340fef2d1575d289ad2"),
|
||||
"24.21.0-linux_ppc64le": ("node-v24.21.0-linux-ppc64le.tar.xz", "node-v24.21.0-linux-ppc64le", "1936fd64623a2f98d1fb31b456686d10c30e92639899cfefdefa8835d22adccd"),
|
||||
"24.21.0-linux_s390x": ("node-v24.21.0-linux-s390x.tar.xz", "node-v24.21.0-linux-s390x", "2ef7e2ecbf7a6c2f3d08d106f6b2f279419c6dac89fe91b8cad193af7890082c"),
|
||||
"24.21.0-linux_amd64": ("node-v24.21.0-linux-x64.tar.xz", "node-v24.21.0-linux-x64", "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6"),
|
||||
"24.21.0-windows_amd64": ("node-v24.21.0-win-x64.zip", "node-v24.21.0-win-x64", "158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541"),
|
||||
"22.22.2-darwin_arm64": ("node-v22.22.2-darwin-arm64.tar.gz", "node-v22.22.2-darwin-arm64", "db4b275b83736df67533529a18cc55de2549a8329ace6c7bcc68f8d22d3c9000"),
|
||||
"22.22.2-darwin_amd64": ("node-v22.22.2-darwin-x64.tar.gz", "node-v22.22.2-darwin-x64", "12a6abb9c2902cf48a21120da13f87fde1ed1b71a13330712949e8db818708ba"),
|
||||
"22.22.2-linux_arm64": ("node-v22.22.2-linux-arm64.tar.xz", "node-v22.22.2-linux-arm64", "e9e1930fd321a470e29bb68f30318bf58e3ecb4acb4f1533fb19c58328a091fe"),
|
||||
"22.22.2-linux_ppc64le": ("node-v22.22.2-linux-ppc64le.tar.xz", "node-v22.22.2-linux-ppc64le", "14045b5a5030d35ca0030fb7e870bd11a651eb9b57323ebc0021e8d78ac6bac9"),
|
||||
"22.22.2-linux_s390x": ("node-v22.22.2-linux-s390x.tar.xz", "node-v22.22.2-linux-s390x", "9e4a07c291b8949289c6ea8ee61b1d14666a4810feae776a8d1eb1f57e03a2fb"),
|
||||
"22.22.2-linux_amd64": ("node-v22.22.2-linux-x64.tar.xz", "node-v22.22.2-linux-x64", "88fd1ce767091fd8d4a99fdb2356e98c819f93f3b1f8663853a2dee9b438068a"),
|
||||
"22.22.2-windows_amd64": ("node-v22.22.2-win-x64.zip", "node-v22.22.2-win-x64", "7c93e9d92bf68c07182b471aa187e35ee6cd08ef0f24ab060dfff605fcc1c57c"),
|
||||
},
|
||||
node_version = "24.21.0",
|
||||
node_version = "22.22.2",
|
||||
)
|
||||
use_repo(node, "nodejs_toolchains")
|
||||
use_repo(node, "nodejs_darwin_amd64")
|
||||
@@ -71,8 +71,8 @@ use_repo(node, "nodejs_windows_amd64")
|
||||
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
|
||||
pnpm.pnpm(
|
||||
name = "pnpm",
|
||||
pnpm_version = "11.24.0",
|
||||
pnpm_version_integrity = "sha512-vSfjRel23LC+C3oSKCF7BJqBfiGx81XJDb59xGZxiVqLwebQbCRVRQXqk+oLRfSJon7Bv7yN5qlln8oPFvoAAA==",
|
||||
pnpm_version = "10.33.0",
|
||||
pnpm_version_integrity = "sha512-EFaLtKavtYyes2MNqQzJUWQXq+vT+rvmc58K55VyjaFJHp21pUTHatjrdXD1xLs9bGN7LLQb/c20f6gjyGSTGQ==",
|
||||
)
|
||||
use_repo(pnpm, "pnpm")
|
||||
|
||||
@@ -86,7 +86,6 @@ npm.npm_translate_lock(
|
||||
"//adev:package.json",
|
||||
"//adev/shared-docs:package.json",
|
||||
"//adev/shared-docs/pipeline/api-gen:package.json",
|
||||
"//devtools:package.json",
|
||||
"//integration:package.json",
|
||||
"//modules:package.json",
|
||||
"//packages/animations:package.json",
|
||||
@@ -110,11 +109,7 @@ npm.npm_translate_lock(
|
||||
"//tools/bazel/rules_angular_store:package.json",
|
||||
"//vscode-ng-language-service/integration/project:package.json",
|
||||
],
|
||||
lifecycle_hooks = {
|
||||
# sleep requires node-gyp rebuild but the native module is not needed in Bazel;
|
||||
# disable the install hook to avoid failing builds when node-gyp is unavailable.
|
||||
"sleep": [],
|
||||
},
|
||||
npmrc = "//:.npmrc",
|
||||
pnpm_lock = "//:pnpm-lock.yaml",
|
||||
)
|
||||
use_repo(npm, "npm")
|
||||
@@ -122,20 +117,22 @@ use_repo(npm, "npm")
|
||||
rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext")
|
||||
rules_ts_ext.deps(
|
||||
name = "angular_npm_typescript",
|
||||
# Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.2 | jq -r '.dist.integrity'
|
||||
ts_integrity = "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
ts_version = "6.0.3",
|
||||
# Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.1-rc | jq -r '.dist.integrity'
|
||||
ts_integrity = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
ts_version = "5.9.3",
|
||||
)
|
||||
use_repo(rules_ts_ext, **{"npm_typescript": "angular_npm_typescript"})
|
||||
|
||||
# TODO: Figure out how to make ng_project update whenever the packages/core::pkg target changes.
|
||||
rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular")
|
||||
rules_angular.setup(
|
||||
name = "angular_rules_angular_configurable_deps",
|
||||
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
|
||||
typescript = "//:node_modules/typescript",
|
||||
|
||||
use_repo_rule("@rules_angular//setup:repositories.bzl", "configurable_deps_repo")(
|
||||
name = "rules_angular_configurable_deps",
|
||||
angular_compiler_cli = "@angular//:node_modules/@angular/compiler-cli",
|
||||
typescript = "@angular//:node_modules/typescript",
|
||||
)
|
||||
use_repo(rules_angular, rules_angular_configurable_deps = "angular_rules_angular_configurable_deps")
|
||||
|
||||
override_repo(rules_angular, "rules_angular_configurable_deps")
|
||||
|
||||
register_toolchains(
|
||||
"@devinfra//bazel/git-toolchain:git_linux_toolchain",
|
||||
@@ -163,3 +160,22 @@ cldr_xml_data(
|
||||
"https://github.com/unicode-org/cldr/releases/download/release-%s/core.zip" % CLDR_VERSION: "d5ee2abac64158c04884a722f8ef4830ea22b6c74aac20185be2838db8eda788",
|
||||
},
|
||||
)
|
||||
|
||||
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
# Fetch sauce connect (tool to open Saucelabs tunnel for Saucelabs browser tests)
|
||||
http_archive(
|
||||
name = "sauce_connect_linux_amd64",
|
||||
build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""",
|
||||
sha256 = "26b9c3630f441b47854b6032f7eca6f1d88d3f62e50ee44c27015d71a5155c36",
|
||||
strip_prefix = "sc-4.8.2-linux",
|
||||
url = "https://saucelabs.com/downloads/sc-4.8.2-linux.tar.gz",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "sauce_connect_mac",
|
||||
build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""",
|
||||
sha256 = "28277ce81ef9ab84f5b87b526258920a8ead44789a5034346e872629bbf38089",
|
||||
strip_prefix = "sc-4.8.2-osx",
|
||||
url = "https://saucelabs.com/downloads/sc-4.8.2-osx.zip",
|
||||
)
|
||||
|
||||
Generated
+3466
-339
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]
|
||||
|
||||
+1
-3
@@ -16,7 +16,6 @@ exports_files([
|
||||
APPLICATION_FILES = [
|
||||
"//adev/src/assets/images",
|
||||
"//adev/src/assets/images/v21-event:v21-event-images",
|
||||
"//adev/src/assets/images/v22-event:v22-event-images",
|
||||
"//adev/src/assets/others",
|
||||
"//adev/src/assets/previews",
|
||||
"//adev/src/assets:tutorials",
|
||||
@@ -71,6 +70,7 @@ APPLICATION_DEPS = [
|
||||
":node_modules/fflate",
|
||||
":node_modules/marked",
|
||||
":node_modules/ngx-progressbar",
|
||||
":node_modules/open-in-idx",
|
||||
":node_modules/tailwindcss",
|
||||
":node_modules/typescript",
|
||||
":node_modules/@types/dom-navigation",
|
||||
@@ -84,7 +84,6 @@ APPLICATION_DEPS = [
|
||||
":node_modules/@algolia/client-common",
|
||||
":node_modules/@algolia/requester-browser-xhr",
|
||||
":node_modules/@algolia/requester-node-http",
|
||||
":node_modules/beasties",
|
||||
":node_modules/@lezer/css",
|
||||
":node_modules/@lezer/html",
|
||||
":node_modules/@lezer/lr",
|
||||
@@ -98,7 +97,6 @@ APPLICATION_DEPS = [
|
||||
":node_modules/@shikijs/langs",
|
||||
":node_modules/@shikijs/engine-oniguruma",
|
||||
":node_modules/@shikijs/core",
|
||||
":node_modules/@shikijs/primitive",
|
||||
":node_modules/@shikijs/engine-javascript",
|
||||
":node_modules/@shikijs/types",
|
||||
":node_modules/@shikijs/vscode-textmate",
|
||||
|
||||
+9
-6
@@ -7,7 +7,9 @@
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
"style": "scss",
|
||||
"standalone": true,
|
||||
"changeDetection": "OnPush"
|
||||
}
|
||||
},
|
||||
"root": ".",
|
||||
@@ -31,8 +33,7 @@
|
||||
"src/robots.txt",
|
||||
"src/llms.txt",
|
||||
"src/assets",
|
||||
"src/assets/images/v21-event",
|
||||
"src/assets/images/v22-event"
|
||||
"src/assets/images/v21-event"
|
||||
],
|
||||
"styles": ["@angular/docs/styles/global-styles.scss", "./src/local-styles.scss"],
|
||||
"scripts": [],
|
||||
@@ -70,16 +71,18 @@
|
||||
"buildTarget": "web-ui:build",
|
||||
"headers": {
|
||||
"Cross-Origin-Opener-Policy": "same-origin",
|
||||
"Cross-Origin-Embedder-Policy": "credentialless"
|
||||
"Cross-Origin-Embedder-Policy": "require-corp"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:unit-test",
|
||||
"options": {
|
||||
"options": {
|
||||
"runner": "karma",
|
||||
"browsers": ["ChromeHeadlessNoSandbox"],
|
||||
"include": ["src/app/**/*.spec.ts"]
|
||||
"include": [
|
||||
"src/app/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cross-Origin-Embedder-Policy",
|
||||
"value": "credentialless"
|
||||
"value": "require-corp"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
{
|
||||
"key": "Cross-Origin-Embedder-Policy",
|
||||
"value": "credentialless"
|
||||
"value": "require-corp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+54
-55
@@ -1,105 +1,104 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@algolia/client-common": "5.57.0",
|
||||
"@algolia/client-search": "5.57.0",
|
||||
"@algolia/requester-browser-xhr": "5.57.0",
|
||||
"@algolia/requester-node-http": "5.57.0",
|
||||
"@algolia/client-common": "5.48.0",
|
||||
"@algolia/client-search": "5.48.0",
|
||||
"@algolia/requester-browser-xhr": "5.48.0",
|
||||
"@algolia/requester-node-http": "5.48.0",
|
||||
"@angular/animations": "workspace:*",
|
||||
"@angular/aria": "22.2.0-next.5",
|
||||
"@angular/build": "22.2.0-next.7",
|
||||
"@angular/cdk": "22.2.0-next.5",
|
||||
"@angular/cli": "22.2.0-next.7",
|
||||
"@angular/aria": "21.2.5",
|
||||
"@angular/build": "21.2.6",
|
||||
"@angular/cdk": "21.2.5",
|
||||
"@angular/cli": "21.2.6",
|
||||
"@angular/common": "workspace:*",
|
||||
"@angular/compiler": "workspace:*",
|
||||
"@angular/compiler-cli": "workspace:*",
|
||||
"@angular/core": "workspace:*",
|
||||
"@angular/docs": "workspace:*",
|
||||
"@angular/forms": "workspace:*",
|
||||
"@angular/material": "22.2.0-next.5",
|
||||
"@angular/material": "21.2.5",
|
||||
"@angular/platform-browser": "workspace:*",
|
||||
"@angular/platform-server": "workspace:*",
|
||||
"@angular/router": "workspace:*",
|
||||
"@angular/ssr": "22.2.0-next.7",
|
||||
"@codemirror/autocomplete": "6.20.3",
|
||||
"@codemirror/commands": "6.11.0",
|
||||
"@angular/ssr": "21.2.6",
|
||||
"@codemirror/autocomplete": "6.20.0",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/lang-angular": "0.1.4",
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-html": "6.4.12",
|
||||
"@codemirror/lang-javascript": "6.2.5",
|
||||
"@codemirror/lang-html": "6.4.11",
|
||||
"@codemirror/lang-javascript": "6.2.4",
|
||||
"@codemirror/lang-sass": "6.0.2",
|
||||
"@codemirror/language": "6.12.4",
|
||||
"@codemirror/lint": "6.9.7",
|
||||
"@codemirror/search": "6.7.1",
|
||||
"@codemirror/state": "6.7.1",
|
||||
"@codemirror/view": "6.43.9",
|
||||
"@lezer/common": "1.5.2",
|
||||
"@lezer/css": "1.3.6",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/lint": "6.9.3",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/state": "6.5.4",
|
||||
"@codemirror/view": "6.39.12",
|
||||
"@lezer/common": "1.5.1",
|
||||
"@lezer/css": "1.3.0",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lezer/html": "1.3.13",
|
||||
"@lezer/javascript": "1.5.4",
|
||||
"@lezer/lr": "1.4.10",
|
||||
"@lezer/lr": "1.4.8",
|
||||
"@lezer/sass": "1.1.0",
|
||||
"@marijn/find-cluster-break": "1.0.4",
|
||||
"@shikijs/core": "^4.0.0",
|
||||
"@shikijs/engine-javascript": "^4.0.0",
|
||||
"@shikijs/engine-oniguruma": "^4.0.0",
|
||||
"@shikijs/langs": "^4.0.0",
|
||||
"@shikijs/primitive": "^4.0.0",
|
||||
"@shikijs/themes": "^4.0.0",
|
||||
"@shikijs/types": "^4.0.0",
|
||||
"@marijn/find-cluster-break": "1.0.2",
|
||||
"@shikijs/core": "^3.21.0",
|
||||
"@shikijs/engine-javascript": "^3.21.0",
|
||||
"@shikijs/engine-oniguruma": "^3.21.0",
|
||||
"@shikijs/langs": "^3.21.0",
|
||||
"@shikijs/themes": "^3.21.0",
|
||||
"@shikijs/types": "^3.21.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@stackblitz/sdk": "1.11.1",
|
||||
"@types/dom-navigation": "1.0.7",
|
||||
"@stackblitz/sdk": "1.11.0",
|
||||
"@types/dom-navigation": "1.0.6",
|
||||
"@types/jasmine": "6.0.0",
|
||||
"@types/jsdom": "30.0.0",
|
||||
"@types/node": "24.13.3",
|
||||
"@typescript/vfs": "1.6.4",
|
||||
"@webcontainer/api": "1.6.4",
|
||||
"@types/jsdom": "27.0.0",
|
||||
"@types/node": "24.10.11",
|
||||
"@typescript/vfs": "1.6.2",
|
||||
"@webcontainer/api": "1.6.1",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/xterm": "6.0.0",
|
||||
"algoliasearch": "5.57.0",
|
||||
"algoliasearch": "5.48.0",
|
||||
"angular-split": "20.0.0",
|
||||
"beasties": "0.5.4",
|
||||
"ccount": "^2.0.1",
|
||||
"character-entities-html4": "^2.1.0",
|
||||
"character-entities-legacy": "^3.0.0",
|
||||
"comma-separated-tokens": "^2.0.3",
|
||||
"crelt": "1.0.7",
|
||||
"diff": "9.0.0",
|
||||
"crelt": "1.0.6",
|
||||
"diff": "8.0.3",
|
||||
"emoji-regex": "10.6.0",
|
||||
"fflate": "0.8.3",
|
||||
"fflate": "0.8.2",
|
||||
"hast-util-to-html": "^9.0.5",
|
||||
"hast-util-whitespace": "^3.0.0",
|
||||
"html-void-elements": "^3.0.0",
|
||||
"jasmine-core": "6.3.0",
|
||||
"jsdom": "30.0.1",
|
||||
"jsdom": "28.0.0",
|
||||
"jasmine-core": "6.1.0",
|
||||
"karma-chrome-launcher": "3.2.0",
|
||||
"karma-coverage": "2.2.1",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-jasmine-html-reporter": "2.3.0",
|
||||
"marked": "18.0.11",
|
||||
"mermaid": "11.17.2",
|
||||
"karma-jasmine-html-reporter": "2.2.0",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"ngx-progressbar": "14.0.0",
|
||||
"playwright-core": "1.62.1",
|
||||
"preact": "10.29.8",
|
||||
"preact-render-to-string": "6.7.0",
|
||||
"prettier": "3.9.6",
|
||||
"open-in-idx": "0.1.1",
|
||||
"playwright-core": "1.58.1",
|
||||
"preact": "10.28.3",
|
||||
"preact-render-to-string": "6.6.6",
|
||||
"prettier": "3.8.1",
|
||||
"property-information": "^7.1.0",
|
||||
"rxjs": "7.8.2",
|
||||
"shiki": "4.4.3",
|
||||
"shiki": "3.22.0",
|
||||
"space-separated-tokens": "^2.0.2",
|
||||
"stringify-entities": "^4.0.4",
|
||||
"style-mod": "4.1.3",
|
||||
"tinyglobby": "0.2.17",
|
||||
"tinyglobby": "0.2.15",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "6.0.3",
|
||||
"typescript": "5.9.3",
|
||||
"w3c-keyname": "2.2.8",
|
||||
"zwitch": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.5.4",
|
||||
"autoprefixer": "10.4.24",
|
||||
"karma": "~6.4.4",
|
||||
"postcss": "8.5.26",
|
||||
"postcss": "8.5.6",
|
||||
"tailwindcss": "3.4.19"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +51,4 @@ js_run_binary(
|
||||
outs = ["defined-routes.json"],
|
||||
chdir = package_name(),
|
||||
tool = ":generate_route",
|
||||
# Public so the api-gen rendering pipeline (used from //packages/...) can validate guide
|
||||
# links against the same set of routes defined for the live site.
|
||||
visibility = [
|
||||
"//adev:__subpackages__",
|
||||
"//packages:__subpackages__",
|
||||
"//tools:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries/index.js';
|
||||
import {extractHeadingIds, findDuplicateIds} from '../../shared-docs/pipeline/shared/heading.mjs';
|
||||
import {getIdFromHeading} from '../../shared-docs/pipeline/shared/heading.mjs';
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {writeFileSync, readFileSync} from 'fs';
|
||||
import {join, resolve} from 'path';
|
||||
@@ -20,7 +20,7 @@ const contentRoot = resolve(process.cwd(), '../../src/content');
|
||||
* in a JSON file. This file then used by other bazel targets to know which routes are valid.
|
||||
*/
|
||||
|
||||
function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, string[]>): string[] {
|
||||
function extractRoutes(items: NavigationItem[]): string[] {
|
||||
const routes: string[] = [];
|
||||
for (const item of items) {
|
||||
if (item.path && !item.path.startsWith('http')) {
|
||||
@@ -29,11 +29,7 @@ function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, st
|
||||
const content = readFileSync(join(contentRoot, `${item.contentPath}.md`), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const headings = extractHeadingIds(content);
|
||||
const duplicates = findDuplicateIds(headings);
|
||||
if (duplicates.length > 0) {
|
||||
duplicatesByPage.set(`${item.contentPath}.md`, duplicates);
|
||||
}
|
||||
const headings = extractHeadings(content);
|
||||
routes.push(
|
||||
...headings.map(
|
||||
(heading) => `${item.path}#${heading.toLowerCase().replace(/\s+/g, '-')}`,
|
||||
@@ -42,28 +38,33 @@ function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, st
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
routes.push(...extractRoutes(item.children, duplicatesByPage));
|
||||
routes.push(...extractRoutes(item.children));
|
||||
}
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
function extractHeadings(content: string): string[] {
|
||||
const headings = content
|
||||
.split('\n')
|
||||
// Top level heading (H1) are used for the page title only
|
||||
// and yes, headings can have leading spaces
|
||||
.filter((line) => line.trim().startsWith('##'))
|
||||
.map((line) => line.replace(/^#+\s*/, '').trim());
|
||||
|
||||
const stepRegex = /<docs-step[^>]*title="([^"]*)"/g;
|
||||
let match;
|
||||
while ((match = stepRegex.exec(content)) !== null) {
|
||||
headings.push(match[1]);
|
||||
}
|
||||
|
||||
return headings.map((heading: string) => getIdFromHeading(heading));
|
||||
}
|
||||
|
||||
function main() {
|
||||
const allRoutes: string[] = [];
|
||||
const duplicatesByPage = new Map<string, string[]>();
|
||||
|
||||
allRoutes.push(...extractRoutes(ALL_ITEMS, duplicatesByPage));
|
||||
|
||||
if (duplicatesByPage.size > 0) {
|
||||
const details = Array.from(duplicatesByPage)
|
||||
.map(([page, ids]) => ` ${page}: ${ids.map((id) => `#${id}`).join(', ')}`)
|
||||
.join('\n');
|
||||
throw new Error(
|
||||
`Headings must produce a unique anchor id within a page, otherwise every link to the ` +
|
||||
`anchor resolves to the first heading that claims it. Give the later heading its own ` +
|
||||
`id with the \`{#custom-id}\` syntax.\n${details}`,
|
||||
);
|
||||
}
|
||||
allRoutes.push(...extractRoutes(ALL_ITEMS));
|
||||
|
||||
const uniqueRoutes = Array.from(new Set(allRoutes.filter((r) => !!r)));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
|
||||
@@ -10,8 +10,6 @@ import {get} from 'node:https';
|
||||
import {posix} from 'node:path';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/repos/';
|
||||
const SHA_REGEX = /^[0-9a-f]{40}$/i;
|
||||
const BRANCH_REGEX = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
|
||||
|
||||
export class GithubClient {
|
||||
#token;
|
||||
@@ -32,12 +30,6 @@ export class GithubClient {
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async getAffectedFiles(baseSha, headSha) {
|
||||
if (!SHA_REGEX.test(baseSha)) {
|
||||
throw new Error(`Invalid base SHA: ${baseSha}`);
|
||||
}
|
||||
if (!SHA_REGEX.test(headSha)) {
|
||||
throw new Error(`Invalid head SHA: ${headSha}`);
|
||||
}
|
||||
const {files} = JSON.parse(await this.#httpGet(`${this.#api}/compare/${baseSha}...${headSha}`));
|
||||
return files.map((f) => f.filename);
|
||||
}
|
||||
@@ -49,9 +41,6 @@ export class GithubClient {
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
async getShaForBranch(branch) {
|
||||
if (!BRANCH_REGEX.test(branch)) {
|
||||
throw new Error(`Invalid branch name: ${branch}`);
|
||||
}
|
||||
const sha = await this.#httpGet(`${this.#api}/commits/${branch}`, {
|
||||
headers: {Accept: 'application/vnd.github.VERSION.sha'},
|
||||
});
|
||||
@@ -60,7 +49,7 @@ export class GithubClient {
|
||||
throw new Error(`Unable to extract the SHA for '${branch}'.`);
|
||||
}
|
||||
|
||||
return sha.trim();
|
||||
return sha;
|
||||
}
|
||||
|
||||
#httpGet(url, options = {}) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
//tslint:disable:no-console
|
||||
import assert from 'node:assert';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {execSync} from 'node:child_process';
|
||||
import {existsSync, constants as fsConstants} from 'node:fs';
|
||||
import {
|
||||
copyFile,
|
||||
@@ -41,16 +41,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
await readFile(buildInfoPath, 'utf-8'),
|
||||
);
|
||||
|
||||
const shaRegex = /^[0-9a-f]{40}$/i;
|
||||
const branchRegex = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
|
||||
|
||||
if (!shaRegex.test(storedSha)) {
|
||||
throw new Error(`Invalid SHA in build info: ${storedSha}`);
|
||||
}
|
||||
if (!branchRegex.test(storedBranch)) {
|
||||
throw new Error(`Invalid branch name in build info: ${storedBranch}`);
|
||||
}
|
||||
|
||||
assert(process.env.ANGULAR_READONLY_GITHUB_TOKEN);
|
||||
const githubApi = new GithubClient(
|
||||
repo,
|
||||
@@ -73,10 +63,6 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
downstreamBranch = storedBranch;
|
||||
}
|
||||
|
||||
if (!shaRegex.test(latestSha)) {
|
||||
throw new Error(`Invalid SHA resolved: ${latestSha}`);
|
||||
}
|
||||
|
||||
console.log(`Comparing ${storedSha}...${latestSha}.`);
|
||||
const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
|
||||
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
|
||||
@@ -92,18 +78,14 @@ export async function updateAssets({repo, assetsPath, destPath}) {
|
||||
|
||||
try {
|
||||
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
|
||||
execFileSync('git', ['init'], execOptions);
|
||||
execFileSync(
|
||||
'git',
|
||||
['remote', 'add', 'origin', `https://github.com/${repo}.git`],
|
||||
execOptions,
|
||||
);
|
||||
execSync('git init', execOptions);
|
||||
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
|
||||
// fetch a commit
|
||||
execFileSync('git', ['fetch', 'origin', latestSha], execOptions);
|
||||
execSync(`git fetch origin ${latestSha}`, execOptions);
|
||||
// reset this repository's main branch to the commit of interest
|
||||
execFileSync('git', ['reset', '--hard', 'FETCH_HEAD'], execOptions);
|
||||
execSync('git reset --hard FETCH_HEAD', execOptions);
|
||||
// get sha when files where changed
|
||||
shaWhenFilesChanged = execFileSync('git', ['rev-list', '-1', latestSha, `${assetsPath}/`], {
|
||||
shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
|
||||
encoding: 'utf8',
|
||||
cwd: temporaryDir,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-algolia-icon',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './algolia-icon.component.html',
|
||||
})
|
||||
export class AlgoliaIcon {}
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component, computed, inject} from '@angular/core';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {ChangeDetectionStrategy, Component, inject, computed} from '@angular/core';
|
||||
import {NavigationState} from '../../services/index';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {RouterLink} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-breadcrumb',
|
||||
imports: [RouterLink],
|
||||
templateUrl: './breadcrumb.component.html',
|
||||
styleUrls: ['./breadcrumb.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class Breadcrumb {
|
||||
private readonly navigationState = inject(NavigationState);
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
@if (!hasAccepted()) {
|
||||
<div class="docs-cookies-popup docs-invert-mode">
|
||||
<div class="docs-cookies-popup-header">
|
||||
<img
|
||||
class="docs-cookies-popup-angie"
|
||||
src="assets/images/angie/greeting.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>This site uses cookies from Google to deliver its services and to analyze traffic.</p>
|
||||
</div>
|
||||
<p>This site uses cookies from Google to deliver its services and to analyze traffic.</p>
|
||||
|
||||
<div class="docs-cookies-popup-actions">
|
||||
<div>
|
||||
<a
|
||||
href="https://policies.google.com/technologies/cookies"
|
||||
target="_blank"
|
||||
|
||||
@@ -15,34 +15,15 @@
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
max-width: 265px;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
color 0.3s ease;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
.docs-cookies-popup-header {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.docs-cookies-popup-angie {
|
||||
flex-shrink: 0;
|
||||
width: 52px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.docs-cookies-popup-actions {
|
||||
> div {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-block-start: 1rem;
|
||||
|
||||
.docs-primary-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
|
||||
import {LOCAL_STORAGE} from '../../providers/index';
|
||||
import {setCookieConsent} from '../../utils';
|
||||
|
||||
@@ -21,6 +21,7 @@ export const STORAGE_KEY = 'docs-accepts-cookies';
|
||||
selector: 'docs-cookie-popup',
|
||||
templateUrl: './cookie-popup.component.html',
|
||||
styleUrls: ['./cookie-popup.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CookiePopup {
|
||||
private readonly localStorage = inject(LOCAL_STORAGE);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, inject, input, signal} from '@angular/core';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {Component, inject, input, signal} from '@angular/core';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@@ -15,7 +15,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
|
||||
|
||||
@Component({
|
||||
selector: 'docs-copy-link-button',
|
||||
template: `<docs-icon></docs-icon>`,
|
||||
template: `<docs-icon>{{ showCopySuccess() ? 'check' : 'link' }}</docs-icon>`,
|
||||
styles: `
|
||||
:host {
|
||||
cursor: pointer;
|
||||
@@ -31,13 +31,6 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
|
||||
:host(.docs-copy-link-success) {
|
||||
color: var(--bright-blue);
|
||||
}
|
||||
docs-icon::before {
|
||||
content: '\\e250'; /* codepoint for "link" */
|
||||
font-family: 'Material Symbols Outlined';
|
||||
}
|
||||
:host(.docs-copy-link-success) docs-icon::before {
|
||||
content: '\\e668'; /* codepoint for "check" */
|
||||
}
|
||||
`,
|
||||
hostDirectives: [
|
||||
{
|
||||
@@ -52,6 +45,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000;
|
||||
'[class.docs-copy-link-success]': 'showCopySuccess()',
|
||||
},
|
||||
imports: [IconComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CopyLinkButton {
|
||||
readonly href = input.required<string>();
|
||||
|
||||
+4
-3
@@ -8,13 +8,13 @@
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {Component, signal} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {
|
||||
CONFIRMATION_DISPLAY_TIME_MS,
|
||||
CopySourceCodeButton,
|
||||
} from './copy-source-code-button.component';
|
||||
import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
|
||||
const SUCCESSFULLY_COPY_CLASS_NAME = 'docs-copy-source-code-button-success';
|
||||
const FAILED_COPY_CLASS_NAME = 'docs-copy-source-code-button-failed';
|
||||
@@ -99,6 +99,7 @@ describe('CopySourceCodeButton', () => {
|
||||
<button docs-copy-source-code></button>
|
||||
`,
|
||||
imports: [CopySourceCodeButton],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class CodeSnippetWrapper {
|
||||
code = signal('');
|
||||
|
||||
+3
-1
@@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
export const REMOVED_LINE_CLASS_NAME = '.line.remove';
|
||||
@@ -32,6 +33,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 2000;
|
||||
'[class.docs-copy-source-code-button-success]': 'showCopySuccess()',
|
||||
'[class.docs-copy-source-code-button-failed]': 'showCopyFailure()',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CopySourceCodeButton {
|
||||
private readonly changeDetector = inject(ChangeDetectorRef);
|
||||
|
||||
@@ -7,10 +7,18 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Component, afterNextRender, computed, inject, signal} from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
afterNextRender,
|
||||
computed,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-icon',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'class': 'material-symbols-outlined',
|
||||
'[style.font-size.px]': 'fontSize()',
|
||||
|
||||
@@ -24,7 +24,6 @@ ng_project(
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/pipes",
|
||||
"//adev/shared-docs/services",
|
||||
"//adev/shared-docs/utils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -9,17 +9,11 @@
|
||||
>
|
||||
@for (itemGroup of groupItems(navigationItems, preserveOtherCategoryOrder); track $index) {
|
||||
@let groupLabel = itemGroup[0];
|
||||
@let group = itemGroup[1];
|
||||
@let items = group.items;
|
||||
@let items = itemGroup[1];
|
||||
@let firstItem = items[0];
|
||||
|
||||
@if (groupLabel && collapsableLevel() !== firstItem.level && items.length > 1) {
|
||||
<li class="docs-navigation-group">
|
||||
<span>{{ groupLabel }}</span>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="itemStatus; context: {$implicit: {status: group.status}}"
|
||||
/>
|
||||
</li>
|
||||
<li class="docs-navigation-group">{{ groupLabel }}</li>
|
||||
}
|
||||
@for (item of items; track $index) {
|
||||
<li
|
||||
@@ -33,7 +27,7 @@
|
||||
[href]="item.path"
|
||||
target="_blank"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -61,7 +55,7 @@
|
||||
}"
|
||||
(click)="emitClickOnLink(item)"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -82,7 +76,7 @@
|
||||
<div
|
||||
class="docs-secondary-nav-header"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
@@ -112,7 +106,7 @@
|
||||
item.children && item.level === expandableLevel() && !item.isExpanded
|
||||
"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="isMobile || itemLabel.length < labelTruncationThreshold"
|
||||
[matTooltipDisabled]="itemLabel.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100dvh;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-block: 1.5rem;
|
||||
@@ -162,12 +162,11 @@ a,
|
||||
}
|
||||
}
|
||||
|
||||
.docs-external-link.docs-faceted-list-item-text {
|
||||
.docs-external-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: 0.5rem;
|
||||
&::after {
|
||||
content: 'open_in_new';
|
||||
@@ -175,11 +174,7 @@ a,
|
||||
font-size: 1.1rem;
|
||||
color: var(--quinary-contrast);
|
||||
transition: color 0.3s ease;
|
||||
margin-inline-end: 0.2rem;
|
||||
}
|
||||
|
||||
a:hover &::after {
|
||||
color: var(--primary-contrast);
|
||||
margin-inline-end: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,12 +188,8 @@ a,
|
||||
}
|
||||
|
||||
.docs-navigation-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
background: var(--septenary-contrast);
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -31,32 +31,6 @@ const navigationItems: NavigationItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItemsWithCategoryStatus: NavigationItem[] = [
|
||||
{
|
||||
label: 'Forms',
|
||||
level: 1,
|
||||
categoriesStatus: [{'Signal Forms': 'new'}],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
navigationItemsWithCategoryStatus[0].children = [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/forms',
|
||||
level: 2,
|
||||
category: 'Signal Forms',
|
||||
parent: navigationItemsWithCategoryStatus[0],
|
||||
},
|
||||
{
|
||||
label: 'Signals',
|
||||
path: 'guide/forms/signals',
|
||||
level: 2,
|
||||
category: 'Signal Forms',
|
||||
parent: navigationItemsWithCategoryStatus[0],
|
||||
},
|
||||
];
|
||||
|
||||
describe('NavigationList', () => {
|
||||
let component: NavigationList;
|
||||
let fixture: ComponentFixture<NavigationList>;
|
||||
@@ -84,20 +58,6 @@ describe('NavigationList', () => {
|
||||
expect(nonClickableItem.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should show category status on grouped navigation headers', async () => {
|
||||
fixture.componentRef.setInput('navigationItems', [...navigationItemsWithCategoryStatus]);
|
||||
await fixture.whenStable();
|
||||
|
||||
const categoryGroup = fixture.debugElement.query(By.css('.docs-navigation-group'));
|
||||
const categoryStatus = fixture.debugElement.query(
|
||||
By.css('.docs-navigation-group .docs-new-item'),
|
||||
);
|
||||
|
||||
expect(categoryGroup.nativeElement.innerText).toContain('Signal Forms');
|
||||
expect(categoryStatus).toBeTruthy();
|
||||
expect(categoryStatus.nativeElement.innerText).toBe('New');
|
||||
});
|
||||
|
||||
it('should append `docs-navigation-list-dropdown` when isDropdownView is true', async () => {
|
||||
fixture.componentRef.setInput('isDropdownView', true);
|
||||
await fixture.whenStable();
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
*/
|
||||
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {Component, inject, input, output} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, inject, input, output} from '@angular/core';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {RouterLink, RouterLinkActive} from '@angular/router';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {IsActiveNavigationItem} from '../../pipes';
|
||||
import {NavigationState} from '../../services/index';
|
||||
import {isMobile} from '../../utils';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@Component({
|
||||
@@ -28,6 +27,7 @@ import {IconComponent} from '../icon/icon.component';
|
||||
],
|
||||
templateUrl: './navigation-list.component.html',
|
||||
styleUrls: ['./navigation-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NavigationList {
|
||||
readonly navigationItems = input.required<NavigationItem[]>();
|
||||
@@ -39,15 +39,11 @@ export class NavigationList {
|
||||
|
||||
readonly linkClicked = output<void>();
|
||||
|
||||
protected readonly labelTruncationThreshold = 27;
|
||||
|
||||
private readonly navigationState = inject(NavigationState);
|
||||
private readonly crossCategoryOrigin = this.navigationState.crossCategoryOrigin;
|
||||
|
||||
readonly activeItem = this.navigationState.activeNavigationItem;
|
||||
|
||||
protected readonly isMobile = isMobile;
|
||||
|
||||
toggle(item: NavigationItem): void {
|
||||
if (
|
||||
item.level === 1 &&
|
||||
@@ -76,27 +72,14 @@ export class NavigationList {
|
||||
return items.some((item) => !!item.category);
|
||||
}
|
||||
|
||||
private getCategoryStatus(item: NavigationItem, category: string): 'new' | 'updated' | undefined {
|
||||
const categoriesStatus = item.parent?.categoriesStatus;
|
||||
|
||||
if (!categoriesStatus) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return categoriesStatus.find((status) => status[category])?.[category];
|
||||
}
|
||||
|
||||
protected groupItems(
|
||||
items: NavigationItem[],
|
||||
preserveOtherCategoryOrder: boolean,
|
||||
): Map<string, {items: NavigationItem[]; status: 'new' | 'updated' | undefined}> {
|
||||
): Map<string, NavigationItem[]> {
|
||||
const hasCategories = this.hasCategories(items);
|
||||
if (hasCategories) {
|
||||
const others: NavigationItem[] = [];
|
||||
const categorizedItems = new Map<
|
||||
string,
|
||||
{items: NavigationItem[]; status: 'new' | 'updated' | undefined}
|
||||
>();
|
||||
const categorizedItems = new Map<string, NavigationItem[]>();
|
||||
for (const item of items) {
|
||||
const category = item.category || 'Other';
|
||||
if (!preserveOtherCategoryOrder && category === 'Other') {
|
||||
@@ -104,20 +87,16 @@ export class NavigationList {
|
||||
continue;
|
||||
}
|
||||
if (!categorizedItems.has(category)) {
|
||||
categorizedItems.set(category, {
|
||||
items: [],
|
||||
status: this.getCategoryStatus(item, category),
|
||||
});
|
||||
categorizedItems.set(category, []);
|
||||
}
|
||||
categorizedItems.get(category)!.items.push(item);
|
||||
categorizedItems.get(category)!.push(item);
|
||||
}
|
||||
if (others.length) {
|
||||
categorizedItems.set('Other', {items: others, status: undefined});
|
||||
categorizedItems.set('Other', others);
|
||||
}
|
||||
|
||||
return categorizedItems;
|
||||
} else {
|
||||
return new Map([['', {items, status: undefined}]]);
|
||||
return new Map([['', items]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ ng_project(
|
||||
"//adev/shared-docs/pipes",
|
||||
"//adev/shared-docs/providers",
|
||||
"//adev/shared-docs/services",
|
||||
"//adev/shared-docs/utils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<dialog #searchDialog id="docsSearchDialog">
|
||||
<dialog #searchDialog>
|
||||
<div class="docs-search-container" (docsClickOutside)="closeSearchDialog()">
|
||||
<docs-text-field
|
||||
[autofocus]="true"
|
||||
@@ -65,22 +65,12 @@
|
||||
<docs-search-history />
|
||||
} @else {
|
||||
<div class="docs-search-results docs-mini-scroll-track">
|
||||
@if (emptyState() === 'start') {
|
||||
@if (!resultsResource.hasValue()) {
|
||||
<div class="docs-search-results__start-typing">
|
||||
<img
|
||||
src="assets/images/angie/magnifying-glass.svg"
|
||||
class="docs-search-angie"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Start typing to see results</span>
|
||||
</div>
|
||||
} @else {
|
||||
} @else if (searchResults().length === 0) {
|
||||
<div class="docs-search-results__no-results">
|
||||
<img
|
||||
src="assets/images/angie/question.svg"
|
||||
class="docs-search-angie"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>No results found</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -150,20 +150,10 @@ dialog {
|
||||
|
||||
.docs-search-results__start-typing,
|
||||
.docs-search-results__no-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
color: var(--quaternary-contrast);
|
||||
}
|
||||
|
||||
.docs-search-angie {
|
||||
width: 210px;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
margin-block: -1rem -0.5rem;
|
||||
}
|
||||
|
||||
.docs-search-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {Router, provideRouter} from '@angular/router';
|
||||
import {SearchDialog} from './search-dialog.component';
|
||||
import {ENVIRONMENT, WINDOW} from '../../providers';
|
||||
import {ALGOLIA_CLIENT, Search} from '../../services';
|
||||
import {FakeEventTarget, timeout, useAutoTick} from '../../testing/index';
|
||||
import {FakeEventTarget} from '../../testing/index';
|
||||
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
import {SearchResult} from '../../interfaces';
|
||||
|
||||
@@ -27,10 +27,8 @@ describe('SearchDialog', () => {
|
||||
|
||||
let search: Search;
|
||||
|
||||
useAutoTick();
|
||||
|
||||
beforeEach(async () => {
|
||||
searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]}));
|
||||
searchResults.and.returnValue([]);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SearchDialog],
|
||||
@@ -57,9 +55,6 @@ describe('SearchDialog', () => {
|
||||
// Fire the request
|
||||
TestBed.inject(ApplicationRef).tick();
|
||||
|
||||
// The delay from debounced (200ms)
|
||||
await timeout(300);
|
||||
|
||||
// Wait for the resource to resolve
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
|
||||
@@ -90,9 +85,6 @@ describe('SearchDialog', () => {
|
||||
// Fire the request
|
||||
TestBed.inject(ApplicationRef).tick();
|
||||
|
||||
// The delay from debounced (200ms)
|
||||
await timeout(300);
|
||||
|
||||
// Wait for the resource to resolve
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
|
||||
@@ -104,7 +96,7 @@ describe('SearchDialog', () => {
|
||||
});
|
||||
|
||||
it('should display `Start typing to see results` message when there are no provided query', () => {
|
||||
searchResults.and.returnValue(Promise.resolve(undefined));
|
||||
searchResults.and.returnValue(undefined);
|
||||
|
||||
const startTypingContainer = fixture.debugElement.query(
|
||||
By.css('.docs-search-results__start-typing'),
|
||||
@@ -113,35 +105,6 @@ describe('SearchDialog', () => {
|
||||
expect(startTypingContainer).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should keep the `No results found` message while re-querying instead of flickering back to `Start typing`', async () => {
|
||||
const appRef = TestBed.inject(ApplicationRef);
|
||||
|
||||
// Settle on an empty result set so `No results found` is shown.
|
||||
search.searchQuery.set('foobarbaz');
|
||||
searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]}));
|
||||
appRef.tick();
|
||||
await timeout(300);
|
||||
await appRef.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('.docs-search-results__no-results'))).toBeTruthy();
|
||||
|
||||
// Edit the query so a new search goes in flight and stays pending.
|
||||
let resolveReload!: (value: unknown) => void;
|
||||
searchResults.and.returnValue(new Promise((resolve) => (resolveReload = resolve)));
|
||||
search.searchQuery.set('foobarba');
|
||||
appRef.tick();
|
||||
|
||||
// The delay from debounced (200ms), after which the pending request is loading.
|
||||
await timeout(300);
|
||||
|
||||
// While the re-query is loading it must not revert to the `Start typing` state.
|
||||
expect(fixture.debugElement.query(By.css('.docs-search-results__start-typing'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('.docs-search-results__no-results'))).toBeTruthy();
|
||||
|
||||
resolveReload({results: [{hits: []}]});
|
||||
await appRef.whenStable();
|
||||
});
|
||||
|
||||
it('should display list of the search results when results exist', async () => {
|
||||
search.searchQuery.set('fakeQuery');
|
||||
searchResults.and.returnValue(Promise.resolve({results: [{hits: fakeSearchResults}]}));
|
||||
@@ -149,9 +112,6 @@ describe('SearchDialog', () => {
|
||||
// Fire the request
|
||||
TestBed.inject(ApplicationRef).tick();
|
||||
|
||||
// The delay from debounced (200ms)
|
||||
await timeout(300);
|
||||
|
||||
// Wait for the resource to resolve
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
effect,
|
||||
@@ -32,10 +33,10 @@ import {RelativeLink} from '../../pipes';
|
||||
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
import {SearchHistoryComponent} from '../search-history/search-history.component';
|
||||
import {TextField} from '../text-field/text-field.component';
|
||||
import {getRelativeUrl} from '../../utils';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-search-dialog',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
ClickOutside,
|
||||
TextField,
|
||||
@@ -56,7 +57,7 @@ export class SearchDialog {
|
||||
|
||||
readonly history = inject(SearchHistory);
|
||||
private readonly search = inject(Search);
|
||||
|
||||
private readonly relativeLink = new RelativeLink();
|
||||
private readonly router = inject(Router);
|
||||
private readonly window = inject(WINDOW);
|
||||
private readonly injector = inject(Injector);
|
||||
@@ -67,7 +68,6 @@ export class SearchDialog {
|
||||
|
||||
readonly resultsResource = this.search.resultsResource;
|
||||
readonly searchResults = this.search.searchResults;
|
||||
readonly emptyState = this.search.emptyState;
|
||||
|
||||
searchForm = form(this.search.searchQuery);
|
||||
|
||||
@@ -122,7 +122,7 @@ export class SearchDialog {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigateByUrl(getRelativeUrl(activeItemLink));
|
||||
this.router.navigateByUrl(this.relativeLink.transform(activeItemLink));
|
||||
this.onClose.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ ng_project(
|
||||
"//adev/shared-docs/directives",
|
||||
"//adev/shared-docs/pipes",
|
||||
"//adev/shared-docs/services",
|
||||
"//adev/shared-docs/utils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
effect,
|
||||
@@ -17,13 +16,14 @@ import {
|
||||
Injector,
|
||||
viewChildren,
|
||||
} from '@angular/core';
|
||||
import {toSignal} from '@angular/core/rxjs-interop';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {toSignal} from '@angular/core/rxjs-interop';
|
||||
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
|
||||
import {SearchItem} from '../../directives';
|
||||
import {RelativeLink} from '../../pipes';
|
||||
import {SearchHistory} from '../../services';
|
||||
import {getRelativeUrl} from '../../utils';
|
||||
import {RelativeLink} from '../../pipes';
|
||||
import {SearchItem} from '../../directives';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-search-history',
|
||||
@@ -34,6 +34,7 @@ import {getRelativeUrl} from '../../utils';
|
||||
'(document:keydown)': 'onKeydown($event)',
|
||||
'(document:mousemove)': 'onMouseMove($event)',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchHistoryComponent {
|
||||
protected readonly items = viewChildren(SearchItem);
|
||||
@@ -42,6 +43,7 @@ export class SearchHistoryComponent {
|
||||
private readonly injector = inject(Injector);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
private readonly relativeLink = new RelativeLink();
|
||||
private readonly keyManager = new ActiveDescendantKeyManager(
|
||||
this.items,
|
||||
this.injector,
|
||||
@@ -99,7 +101,7 @@ export class SearchHistoryComponent {
|
||||
const activeItemLink = this.keyManager.activeItem?.item()?.url;
|
||||
|
||||
if (activeItemLink) {
|
||||
const url = getRelativeUrl(activeItemLink);
|
||||
const url = this.relativeLink.transform(activeItemLink);
|
||||
this.router.navigateByUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ ng_project(
|
||||
],
|
||||
deps = [
|
||||
"//adev:node_modules/@angular/aria",
|
||||
"//adev:node_modules/@angular/cdk",
|
||||
"//adev:node_modules/@angular/common",
|
||||
"//adev:node_modules/@angular/core",
|
||||
"//adev:node_modules/@angular/forms",
|
||||
|
||||
@@ -1,94 +1,61 @@
|
||||
<div ngCombobox #combobox="ngCombobox" class="docs-combobox-container" [(expanded)]="popupExpanded">
|
||||
<div #origin class="docs-select-input-container">
|
||||
<div ngCombobox #outerCombobox="ngCombobox" [readonly]="true" [disabled]="disabled()">
|
||||
<div class="docs-select-input-container">
|
||||
<input
|
||||
ngComboboxInput
|
||||
[attr.id]="id()"
|
||||
[attr.name]="name()"
|
||||
[value]="displayValue()"
|
||||
placeholder="Select an option"
|
||||
placeholder="Select an option..."
|
||||
readonly
|
||||
[tabindex]="-1"
|
||||
/>
|
||||
<span class="material-symbols-outlined docs-select-arrow" translate="no" aria-hidden="true"
|
||||
>arrow_drop_down</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<ng-template
|
||||
[cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
|
||||
[cdkConnectedOverlayOpen]="popupExpanded()"
|
||||
[cdkConnectedOverlayDisableClose]="false"
|
||||
(overlayOutsideClick)="popupExpanded.set(false)"
|
||||
>
|
||||
<ng-template ngComboboxPopup [combobox]="combobox" popupType="dialog">
|
||||
<div class="docs-select-popover">
|
||||
<div class="docs-select-dialog" ngComboboxWidget>
|
||||
<div class="example-combobox-container">
|
||||
<div class="docs-select-search-container">
|
||||
<span
|
||||
class="material-symbols-outlined docs-select-search-icon"
|
||||
translate="no"
|
||||
aria-hidden="true"
|
||||
>search</span
|
||||
>
|
||||
<input
|
||||
ngCombobox
|
||||
#innerCombobox="ngCombobox"
|
||||
#searchInput
|
||||
class="docs-select-search-input"
|
||||
placeholder="Search..."
|
||||
[(value)]="searchString"
|
||||
[alwaysExpanded]="true"
|
||||
(keydown.escape)="onSearchEscape($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" class="cdk-visually-hidden">
|
||||
{{ filteredOptions().length === 0 ? 'No results found for ' + searchString() : '' }}
|
||||
</div>
|
||||
|
||||
<ng-template ngComboboxPopup [combobox]="innerCombobox">
|
||||
<div class="example-popup example-popup-no-margin">
|
||||
@if (filteredOptions().length === 0) {
|
||||
<div class="docs-select-no-results">
|
||||
<img
|
||||
src="assets/images/angie/question.svg"
|
||||
class="docs-select-no-results-angie"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>No results found</span>
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
#listbox="ngListbox"
|
||||
ngListbox
|
||||
[(value)]="selectedValues"
|
||||
[multi]="false"
|
||||
ngComboboxWidget
|
||||
class="docs-select-listbox"
|
||||
focusMode="activedescendant"
|
||||
tabindex="-1"
|
||||
selectionMode="explicit"
|
||||
(click)="onCommit()"
|
||||
(keydown.enter)="onCommit()"
|
||||
[activeDescendant]="listbox.activeDescendant()"
|
||||
[class.example-empty]="filteredOptions().length === 0"
|
||||
>
|
||||
@for (option of filteredOptions(); track option.value) {
|
||||
<div
|
||||
ngOption
|
||||
[value]="option.value"
|
||||
[label]="option.label"
|
||||
class="docs-select-option"
|
||||
>
|
||||
<span class="docs-select-option-label">{{ option.label }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ng-template ngComboboxPopupContainer>
|
||||
<dialog ngComboboxDialog class="docs-select-dialog">
|
||||
<div ngCombobox #innerCombobox="ngCombobox" filterMode="manual" [alwaysExpanded]="true">
|
||||
<div class="docs-select-search-container">
|
||||
<span
|
||||
class="material-symbols-outlined docs-select-search-icon"
|
||||
translate="no"
|
||||
aria-hidden="true"
|
||||
>search</span
|
||||
>
|
||||
<input
|
||||
ngComboboxInput
|
||||
class="docs-select-search-input"
|
||||
placeholder="Search..."
|
||||
[(value)]="searchString"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ng-template ngComboboxPopupContainer>
|
||||
@if (filteredOptions().length === 0) {
|
||||
<div class="docs-select-no-results">No results found</div>
|
||||
}
|
||||
|
||||
<div ngListbox [(values)]="selectedValues" class="docs-select-listbox">
|
||||
@for (option of filteredOptions(); track option.value) {
|
||||
<div
|
||||
ngOption
|
||||
[value]="option.value"
|
||||
[label]="option.label"
|
||||
class="docs-select-option"
|
||||
>
|
||||
<span class="docs-select-option-label">{{ option.label }}</span>
|
||||
<span
|
||||
class="material-symbols-outlined docs-select-check-icon"
|
||||
translate="no"
|
||||
aria-hidden="true"
|
||||
>check</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
</dialog>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
:host {
|
||||
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
|
||||
}
|
||||
|
||||
[ngCombobox] {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.docs-combobox-container {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.docs-select-input-container {
|
||||
@@ -18,25 +16,15 @@
|
||||
position: relative;
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
padding: 0.7rem 1rem 0.7rem;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: var(--primary-contrast);
|
||||
[ngComboboxInput] {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
cursor: pointer;
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
&[aria-expanded='true'] + .docs-select-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
[ngComboboxInput][readonly='true'] {
|
||||
cursor: pointer;
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
[ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) {
|
||||
@@ -44,10 +32,6 @@
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent);
|
||||
}
|
||||
|
||||
.docs-select-popover {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.docs-select-arrow {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@@ -62,19 +46,22 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
[ngComboboxInput] [ngCombobox] {
|
||||
[ngComboboxInput][aria-expanded='true'] + .docs-select-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[ngComboboxInput] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
padding: 0.7rem 1rem 0.7rem;
|
||||
padding: 0.7rem 1rem 0.7rem 2.5rem;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
.docs-select-dialog {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
left: auto;
|
||||
right: auto;
|
||||
top: auto;
|
||||
@@ -82,7 +69,6 @@
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
background-color: var(--septenary-contrast);
|
||||
color: inherit;
|
||||
|
||||
@@ -149,10 +135,6 @@
|
||||
}
|
||||
|
||||
.docs-select-no-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
@@ -160,18 +142,11 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.docs-select-no-results-angie {
|
||||
width: 88px;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.docs-select-listbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 12rem;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
@@ -196,8 +171,16 @@
|
||||
color: var(--vivid-pink);
|
||||
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
|
||||
}
|
||||
|
||||
&:not([aria-selected='true']) .docs-select-check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-select-option-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.docs-select-check-icon {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
@@ -6,21 +6,28 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxDialog,
|
||||
ComboboxInput,
|
||||
ComboboxPopupContainer,
|
||||
} from '@angular/aria/combobox';
|
||||
import {Listbox, Option} from '@angular/aria/listbox';
|
||||
import {OverlayModule} from '@angular/cdk/overlay';
|
||||
import {
|
||||
afterRenderEffect,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
model,
|
||||
signal,
|
||||
untracked,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import {FormValueControl} from '@angular/forms/signals';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
type SelectOptionValue = string;
|
||||
type SelectOptionValue = string | number | boolean;
|
||||
|
||||
export interface SelectOption {
|
||||
label: string;
|
||||
@@ -29,9 +36,18 @@ export interface SelectOption {
|
||||
|
||||
@Component({
|
||||
selector: 'docs-select',
|
||||
templateUrl: 'select.component.html',
|
||||
styleUrl: 'select.component.css',
|
||||
imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
Combobox,
|
||||
ComboboxDialog,
|
||||
ComboboxInput,
|
||||
ComboboxPopupContainer,
|
||||
FormsModule,
|
||||
Listbox,
|
||||
Option,
|
||||
],
|
||||
templateUrl: './select.component.html',
|
||||
styleUrls: ['./select.component.scss'],
|
||||
})
|
||||
export class Select implements FormValueControl<string | null> {
|
||||
readonly value = model<string | null>(null);
|
||||
@@ -39,13 +55,14 @@ export class Select implements FormValueControl<string | null> {
|
||||
readonly id = input.required<string>({alias: 'selectId'});
|
||||
readonly name = input.required<string>();
|
||||
readonly options = input.required<SelectOption[]>();
|
||||
readonly disabled = input(false);
|
||||
|
||||
readonly listbox = viewChild(Listbox);
|
||||
readonly combobox = viewChild(Combobox);
|
||||
readonly dialog = viewChild(ComboboxDialog);
|
||||
readonly listbox = viewChild<Listbox<SelectOptionValue>>(Listbox);
|
||||
readonly combobox = viewChild<Combobox<SelectOptionValue>>(Combobox);
|
||||
|
||||
readonly searchString = signal('');
|
||||
|
||||
readonly popupExpanded = signal(false);
|
||||
readonly filteredOptions = computed(() => {
|
||||
const search = this.searchString().toLowerCase();
|
||||
if (!search) {
|
||||
@@ -67,36 +84,39 @@ export class Select implements FormValueControl<string | null> {
|
||||
|
||||
constructor() {
|
||||
afterRenderEffect(() => {
|
||||
this.listbox()?.scrollActiveItemIntoView();
|
||||
if (this.dialog() && this.combobox()?.expanded()) {
|
||||
untracked(() => this.listbox()?.gotoFirst());
|
||||
this.positionDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCommit() {
|
||||
const values = this.selectedValues();
|
||||
if (values.length) {
|
||||
this.value.set(values[0]);
|
||||
this.popupExpanded.set(false);
|
||||
afterRenderEffect(() => {
|
||||
const selected = this.selectedValues();
|
||||
if (selected.length > 0) {
|
||||
untracked(() => this.dialog()?.close());
|
||||
this.value.set(selected[0] as string);
|
||||
this.searchString.set('');
|
||||
}
|
||||
});
|
||||
|
||||
afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView());
|
||||
}
|
||||
// TODO: Improve once CDK overlay is fixed https://github.com/angular/components/issues/32504
|
||||
private positionDialog(): void {
|
||||
const dialog = this.dialog();
|
||||
const combobox = this.combobox();
|
||||
|
||||
if (!dialog || !combobox) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** Dismisses the dialog overlay on Escape key. */
|
||||
onSearchEscape(event: Event) {
|
||||
this.popupExpanded.set(false);
|
||||
this.combobox()?.element.focus();
|
||||
}
|
||||
const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
/** Handles keydown events on the clear button. */
|
||||
onKeydown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Enter') {
|
||||
this.clear();
|
||||
this.popupExpanded.set(false);
|
||||
event.stopPropagation();
|
||||
if (comboboxRect) {
|
||||
dialog.element.style.width = `${comboboxRect.width}px`;
|
||||
dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`;
|
||||
dialog.element.style.left = `${comboboxRect.left}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears the search query and all selected options. */
|
||||
clear(): void {
|
||||
this.searchString.set('');
|
||||
this.value.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component, input, model} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, input, model} from '@angular/core';
|
||||
import {FormCheckboxControl} from '@angular/forms/signals';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-slide-toggle',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './slide-toggle.component.html',
|
||||
styleUrls: ['./slide-toggle.component.scss'],
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import {
|
||||
afterRenderEffect,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
ElementRef,
|
||||
@@ -32,6 +33,7 @@ let idCounter = 0;
|
||||
host: {
|
||||
class: 'docs-tab-group',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TabGroup {
|
||||
private readonly _renderer = inject(Renderer2);
|
||||
|
||||
@@ -6,14 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
input,
|
||||
inject,
|
||||
afterNextRender,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {Location, ViewportScroller} from '@angular/common';
|
||||
import {afterNextRender, Component, DestroyRef, inject, input, signal} from '@angular/core';
|
||||
import {TableOfContentsLevel} from '../../interfaces/index';
|
||||
import {TableOfContentsLoader} from '../../services';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-table-of-contents',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './table-of-contents.component.html',
|
||||
styleUrls: ['./table-of-contents.component.scss'],
|
||||
imports: [IconComponent],
|
||||
|
||||
@@ -6,12 +6,21 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {afterNextRender, Component, ElementRef, input, model, viewChild} from '@angular/core';
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
input,
|
||||
model,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import {FormValueControl} from '@angular/forms/signals';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-text-field',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [IconComponent],
|
||||
templateUrl: './text-field.component.html',
|
||||
styleUrls: ['./text-field.component.scss'],
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component, inject, input, linkedSignal} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, inject, input, linkedSignal} from '@angular/core';
|
||||
import {ExternalLink} from '../../directives';
|
||||
import {LOCAL_STORAGE} from '../../providers';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
@@ -18,6 +18,7 @@ export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-';
|
||||
imports: [ExternalLink, IconComponent],
|
||||
templateUrl: './top-level-banner.component.html',
|
||||
styleUrl: './top-level-banner.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TopLevelBannerComponent {
|
||||
private readonly localStorage = inject(LOCAL_STORAGE);
|
||||
|
||||
@@ -71,7 +71,6 @@ ts_project(
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/providers",
|
||||
"//adev/shared-docs/services",
|
||||
"//adev/shared-docs/utils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -132,7 +132,6 @@
|
||||
.docs-page-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
|
||||
h1 {
|
||||
margin-block: 0;
|
||||
|
||||
@@ -20,7 +20,6 @@ import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-co
|
||||
import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component';
|
||||
import {TableOfContents} from '../../table-of-contents/table-of-contents.component';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {isFirefox} from '../../../utils';
|
||||
|
||||
describe('DocViewer', () => {
|
||||
let exampleContentSpy: jasmine.SpyObj<ExampleViewerContentLoader>;
|
||||
@@ -66,23 +65,6 @@ describe('DocViewer', () => {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const exampleContentWithVideoFacade = `
|
||||
<div class="docs-video-container">
|
||||
<a
|
||||
class="docs-video-facade"
|
||||
href="https://www.youtube.com/watch?v=abc123&autoplay=1"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="Play video: Test video"
|
||||
data-video-src="https://www.youtube.com/embed/abc123"
|
||||
data-video-title="Test video"
|
||||
>
|
||||
<img class="docs-video-thumbnail" src="https://i.ytimg.com/vi/abc123/maxresdefault.jpg" alt="" loading="lazy" />
|
||||
<span class="docs-video-play-button" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const exampleContentWithHeadings = `
|
||||
<h2>Heading h2</h2>
|
||||
<h3>Heading h3</h3>
|
||||
@@ -238,16 +220,6 @@ describe('DocViewer', () => {
|
||||
expect(copyButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not leak any icon text content (so it stays out of the search index)', () => {
|
||||
const fixture = TestBed.createComponent(CopyLinkButton);
|
||||
fixture.componentRef.setInput('href', '#test-section');
|
||||
fixture.componentRef.setInput('label', 'Test Section');
|
||||
fixture.componentRef.setInput('matTooltip', 'Copy link to Test Section');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should copy link to clipboard when copy button is clicked', async () => {
|
||||
const clipboard = TestBed.inject(Clipboard);
|
||||
const clipboardSpy = spyOn(clipboard, 'copy').and.returnValue(true);
|
||||
@@ -265,25 +237,4 @@ describe('DocViewer', () => {
|
||||
// necessary to undo this location change by going back in the history.
|
||||
window.history.back();
|
||||
});
|
||||
|
||||
it('should upgrade a video facade to an iframe in browsers that can embed it', async () => {
|
||||
const fixture = TestBed.createComponent(DocViewer);
|
||||
fixture.componentRef.setInput('docContent', exampleContentWithVideoFacade);
|
||||
|
||||
await fixture.whenStable();
|
||||
|
||||
const iframe = fixture.nativeElement.querySelector('iframe.docs-video');
|
||||
const facade = fixture.nativeElement.querySelector('a.docs-video-facade');
|
||||
|
||||
if (isFirefox) {
|
||||
// Firefox can't load the cross-origin embed under COEP, so the facade stays a link.
|
||||
expect(facade).toBeTruthy();
|
||||
expect(iframe).toBeNull();
|
||||
} else {
|
||||
expect(iframe).toBeTruthy();
|
||||
expect(iframe.getAttribute('src')).toContain('youtube.com/embed/abc123');
|
||||
expect(iframe.hasAttribute('credentialless')).toBeTrue();
|
||||
expect(facade).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import {DOCUMENT, isPlatformBrowser, Location} from '@angular/common';
|
||||
import {
|
||||
ApplicationRef,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ComponentRef,
|
||||
createComponent,
|
||||
@@ -31,16 +32,16 @@ import {Router} from '@angular/router';
|
||||
import {fromEvent} from 'rxjs';
|
||||
import {Snippet} from '../../../interfaces';
|
||||
import {NavigationState, TOC_SKIP_CONTENT_MARKER} from '../../../services';
|
||||
import {handleHrefClickEventWithRouter, isFirefox} from '../../../utils';
|
||||
import {handleHrefClickEventWithRouter} from '../../../utils';
|
||||
import {IconComponent} from '../../icon/icon.component';
|
||||
import {TableOfContents} from '../../table-of-contents/table-of-contents.component';
|
||||
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
import {Breadcrumb} from '../../breadcrumb/breadcrumb.component';
|
||||
import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component';
|
||||
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
|
||||
import {TabGroup} from '../../tab-group/tab-group.component';
|
||||
import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component';
|
||||
import {ExampleViewer} from '../example-viewer/example-viewer.component';
|
||||
import {TabGroup} from '../../tab-group/tab-group.component';
|
||||
|
||||
const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents';
|
||||
export const ASSETS_EXAMPLES_PATH = 'assets/content/examples';
|
||||
@@ -55,6 +56,7 @@ const GITHUB_CONTENT_URL = 'https://github.com/angular/angular/blob/{{BUILD_SCM_
|
||||
selector: DOCS_VIEWER_SELECTOR,
|
||||
template: '',
|
||||
styleUrls: ['docs-viewer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
'[class.docs-animate-content]': 'animateContent',
|
||||
@@ -124,7 +126,6 @@ export class DocViewer {
|
||||
// In case when content contains tabs, create tabs component and move
|
||||
// content in a tab into tab panel.
|
||||
this.constructTabs(contentContainer);
|
||||
this.setupVideoFacades(contentContainer);
|
||||
}
|
||||
|
||||
// Display Breadcrumb component if the `<docs-breadcrumb>` element exists
|
||||
@@ -413,36 +414,4 @@ export class DocViewer {
|
||||
tabGroup.parentElement!.replaceChild(tabGroupRef.location.nativeElement, tabGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private setupVideoFacades(element: HTMLElement): void {
|
||||
const facades = element.querySelectorAll<HTMLAnchorElement>('a.docs-video-facade');
|
||||
|
||||
for (const facade of Array.from(facades)) {
|
||||
const src = facade.getAttribute('data-video-src');
|
||||
if (!src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFirefox) {
|
||||
const thumbnail = facade.querySelector<HTMLImageElement>('img.docs-video-thumbnail');
|
||||
thumbnail?.addEventListener(
|
||||
'error',
|
||||
() => {
|
||||
thumbnail.src = thumbnail.src.replace('maxresdefault', 'hqdefault');
|
||||
},
|
||||
{once: true},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const iframe = this.document.createElement('iframe');
|
||||
iframe.className = 'docs-video';
|
||||
iframe.src = src;
|
||||
iframe.title = facade.getAttribute('data-video-title') ?? 'Video player';
|
||||
iframe.setAttribute('allow', 'accelerometer; encrypted-media; gyroscope; picture-in-picture');
|
||||
iframe.setAttribute('allowfullscreen', '');
|
||||
iframe.setAttribute('credentialless', '');
|
||||
facade.replaceWith(iframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#codeTabs
|
||||
animationDuration="0ms"
|
||||
mat-stretch-tabs="false"
|
||||
[selectedIndex]="selectedTabIndex()"
|
||||
(selectedTabChange)="onTabIndexChange($event.index)"
|
||||
>
|
||||
@for (tab of tabs(); track tab) {
|
||||
@@ -22,7 +21,7 @@
|
||||
[class.docs-example-code-toggle__show]="!showCode()"
|
||||
[attr.aria-checked]="showCode()"
|
||||
[attr.aria-label]="showCode() ? 'Hide code' : 'Show code'"
|
||||
(click)="toggleCodeVisibility()"
|
||||
(click)="showCode.set(!showCode())"
|
||||
[matTooltip]="showCode() ? 'Hide code' : 'Show code'"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
|
||||
@@ -166,147 +166,6 @@ describe('ExampleViewer', () => {
|
||||
expect(hiddenLine).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the expand control after switching tabs while expanded', async () => {
|
||||
const collapsible = {
|
||||
name: 'example.ts',
|
||||
sanitizedContent:
|
||||
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
|
||||
visibleLinesRange: '1',
|
||||
};
|
||||
|
||||
componentRef.setInput(
|
||||
'metadata',
|
||||
getMetadata({files: [collapsible, {name: 'example.css', sanitizedContent: ''}]}),
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.expandable()).toBeTrue();
|
||||
|
||||
component.toggleExampleVisibility();
|
||||
await fixture.whenStable();
|
||||
|
||||
const tabGroup = await loader.getHarness(MatTabGroupHarness);
|
||||
await (await tabGroup.getTabs())[1].select();
|
||||
await fixture.whenStable();
|
||||
await (await tabGroup.getTabs())[0].select();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.expandable()).toBeTrue();
|
||||
});
|
||||
|
||||
it('hides only the lines outside the visible range, and adds no gap within one range', async () => {
|
||||
componentRef.setInput(
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
sanitizedContent:
|
||||
'<pre><code><div class="line">one</div><div class="line">two</div><div class="line">three</div></code></pre>',
|
||||
visibleLinesRange: '2,3',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
await fixture.whenStable();
|
||||
|
||||
const lines = fixture.debugElement
|
||||
.queryAll(By.css('.line'))
|
||||
.map((line) => (line.nativeElement as HTMLElement).className);
|
||||
|
||||
expect(lines).toEqual(['line hidden', 'line', 'line']);
|
||||
});
|
||||
|
||||
it('hides the line numbers that pair with the hidden lines', async () => {
|
||||
const numbered = (n: number, text: string) =>
|
||||
`<span class="shiki-ln-number">${n}</span><div class="line">${text}</div>`;
|
||||
|
||||
componentRef.setInput(
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
sanitizedContent: `<pre><code>${numbered(1, 'one')}${numbered(2, 'two')}${numbered(3, 'three')}</code></pre>`,
|
||||
visibleLinesRange: '2,3',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
await fixture.whenStable();
|
||||
|
||||
const visibleNumbers = fixture.debugElement
|
||||
.queryAll(By.css('.shiki-ln-number'))
|
||||
.filter((el) => !(el.nativeElement as HTMLElement).classList.contains('hidden'))
|
||||
.map((el) => (el.nativeElement as HTMLElement).textContent);
|
||||
|
||||
expect(visibleNumbers).toEqual(['2', '3']);
|
||||
});
|
||||
|
||||
it('restores the selected tab and its collapsed lines after hiding and reshowing the code', async () => {
|
||||
componentRef.setInput(
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{name: 'example.ts', sanitizedContent: ''},
|
||||
{
|
||||
name: 'example.css',
|
||||
sanitizedContent:
|
||||
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
|
||||
visibleLinesRange: '2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
await fixture.whenStable();
|
||||
|
||||
const tabGroup = await loader.getHarness(MatTabGroupHarness);
|
||||
await (await tabGroup.getTabs())[1].select();
|
||||
await fixture.whenStable();
|
||||
|
||||
component.toggleCodeVisibility();
|
||||
await fixture.whenStable();
|
||||
component.toggleCodeVisibility();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(
|
||||
await (await (await loader.getHarness(MatTabGroupHarness)).getSelectedTab()).getLabel(),
|
||||
).toBe('CSS');
|
||||
const lines = fixture.debugElement
|
||||
.queryAll(By.css('.line'))
|
||||
.map((line) => (line.nativeElement as HTMLElement).className);
|
||||
expect(lines).toEqual(['line hidden', 'line']);
|
||||
});
|
||||
|
||||
it('offers no expand control when the range covers the whole file', async () => {
|
||||
componentRef.setInput(
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
sanitizedContent:
|
||||
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
|
||||
visibleLinesRange: '1,2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.expandable()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should set exampleComponent when metadata contains path and preview is true', async () => {
|
||||
exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent);
|
||||
componentRef.setInput(
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {DOCUMENT, NgComponentOutlet, NgTemplateOutlet} from '@angular/common';
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
ElementRef,
|
||||
@@ -19,12 +18,14 @@ import {
|
||||
signal,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
import {DOCUMENT, NgComponentOutlet, NgTemplateOutlet} from '@angular/common';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
|
||||
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
|
||||
import {IconComponent} from '../../icon/icon.component';
|
||||
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
|
||||
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
|
||||
export const CODE_LINE_NUMBER_CLASS_NAME = 'shiki-ln-number';
|
||||
export const CODE_LINE_CLASS_NAME = 'line';
|
||||
@@ -44,6 +45,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
|
||||
],
|
||||
templateUrl: './example-viewer.component.html',
|
||||
styleUrls: ['./example-viewer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ExampleViewer {
|
||||
readonly exampleMetadata = input<ExampleMetadata | null>(null, {alias: 'metadata'});
|
||||
@@ -69,7 +71,6 @@ export class ExampleViewer {
|
||||
readonly expandable = signal<boolean>(false);
|
||||
readonly expanded = signal<boolean>(false);
|
||||
readonly snippetCode = signal<Snippet | undefined>(undefined);
|
||||
readonly selectedTabIndex = signal<number>(0);
|
||||
readonly showCode = signal<boolean>(true);
|
||||
readonly tabs = computed(() =>
|
||||
this.exampleMetadata()?.files.map((file) => ({
|
||||
@@ -102,7 +103,10 @@ export class ExampleViewer {
|
||||
`example-${this.exampleMetadata()?.id.toString()!}`,
|
||||
);
|
||||
|
||||
this.updateExpandable();
|
||||
const lines = this.getHiddenCodeLines();
|
||||
const lineNumbers = this.getHiddenCodeLineNumbers();
|
||||
|
||||
this.expandable.set(lines.length > 0 || lineNumbers.length > 0);
|
||||
},
|
||||
{injector: this.injector},
|
||||
);
|
||||
@@ -114,15 +118,6 @@ export class ExampleViewer {
|
||||
this.setCodeLinesVisibility();
|
||||
}
|
||||
|
||||
toggleCodeVisibility(): void {
|
||||
const showCode = !this.showCode();
|
||||
this.showCode.set(showCode);
|
||||
|
||||
if (showCode) {
|
||||
afterNextRender(() => this.setCodeLinesVisibility(), {injector: this.injector});
|
||||
}
|
||||
}
|
||||
|
||||
copyLink(): void {
|
||||
// Reconstruct the URL using `origin + pathname` so we drop any pre-existing hash.
|
||||
const fullUrl =
|
||||
@@ -135,25 +130,8 @@ export class ExampleViewer {
|
||||
}
|
||||
|
||||
protected onTabIndexChange(index: number): void {
|
||||
this.selectedTabIndex.set(index);
|
||||
this.snippetCode.set(this.exampleMetadata()?.files[index]);
|
||||
|
||||
afterNextRender(
|
||||
() => {
|
||||
this.setCodeLinesVisibility();
|
||||
this.updateExpandable();
|
||||
},
|
||||
{injector: this.injector},
|
||||
);
|
||||
}
|
||||
|
||||
// A file is expandable when its range hides lines. While the block is expanded nothing is
|
||||
// hidden, so trust the presence of a range instead of the rendered state.
|
||||
private updateExpandable(): void {
|
||||
this.expandable.set(
|
||||
!!this.snippetCode()?.visibleLinesRange &&
|
||||
(this.expanded() || this.getHiddenCodeLines().length > 0),
|
||||
);
|
||||
this.setCodeLinesVisibility();
|
||||
}
|
||||
|
||||
private getFileExtension(name: string): string {
|
||||
@@ -212,13 +190,13 @@ export class ExampleViewer {
|
||||
for (const [index, line] of lines.entries()) {
|
||||
if (!linesToDisplay.includes(index + 1)) {
|
||||
line.classList.add(HIDDEN_CLASS_NAME);
|
||||
} else if (!linesToDisplay.includes(index)) {
|
||||
} else if (!linesToDisplay.includes(index - 1)) {
|
||||
appendGapBefore.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [index, lineNumber] of lineNumbers.entries()) {
|
||||
if (!linesToDisplay.includes(index + 1)) {
|
||||
if (!linesToDisplay.includes(index)) {
|
||||
lineNumber.classList.add(HIDDEN_CLASS_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {isExternalLink} from '../../utils/index';
|
||||
},
|
||||
})
|
||||
export class ExternalLink {
|
||||
private readonly anchor = inject<ElementRef<HTMLAnchorElement>>(ElementRef);
|
||||
private readonly anchor: ElementRef<HTMLAnchorElement> = inject(ElementRef);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
target?: '_blank' | '_self' | '_parent' | '_top' | '';
|
||||
|
||||
@@ -23,7 +23,7 @@ export class SearchItem implements Highlightable {
|
||||
|
||||
readonly item = input<SearchResultItem | undefined>();
|
||||
|
||||
private readonly elementRef = inject<ElementRef<HTMLLIElement>>(ElementRef);
|
||||
private readonly elementRef = inject(ElementRef<HTMLLIElement>);
|
||||
|
||||
private _isActive = signal(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.04145 0.04432l6.56351 8.77603L0 15.95564h1.48651l5.78263-6.24705 4.6722 6.24705h5.05865l-6.9328-9.26967L16.21504.04432h-1.48651l-5.32552 5.75341L5.1001.04432H.04145Zm2.18602 1.09497h2.32396l10.26221 13.72122h-2.32396L2.22747 1.13928Z" fill="#A39FA9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 367 B |
@@ -17,7 +17,6 @@ export interface NavigationItem {
|
||||
contentPath?: string;
|
||||
status?: 'new' | 'updated';
|
||||
category?: string;
|
||||
categoriesStatus?: Record<string, 'new' | 'updated'>[];
|
||||
isCrossReferenced?: boolean;
|
||||
preserveOtherCategoryOrder?: boolean; // true by default
|
||||
}
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
"name": "@angular/docs",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": "^22.2.0-next",
|
||||
"@angular/common": "^22.2.0-next",
|
||||
"@angular/core": "^22.2.0-next",
|
||||
"@angular/forms": "^22.2.0-next",
|
||||
"@angular/material": "^22.2.0-next",
|
||||
"@angular/platform-browser": "^22.2.0-next",
|
||||
"@angular/router": "^22.2.0-next",
|
||||
"@angular/ssr": "^22.2.0-next",
|
||||
"@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",
|
||||
"algoliasearch": "^5.0.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@webcontainer/api": "^1.1.8",
|
||||
"diff": "~9.0.0",
|
||||
"diff": "~8.0.0",
|
||||
"emoji-regex": "~10.6.0",
|
||||
"fflate": "^0.8.2",
|
||||
"jsdom": "~30.0.0",
|
||||
"marked": "~18.0.0",
|
||||
"jsdom": "~28.0.0",
|
||||
"marked": "~17.0.0",
|
||||
"mermaid": "^11.0.0",
|
||||
"shiki": "^4.0.0",
|
||||
"shiki": "^3.0.0",
|
||||
"tinyglobby": "^0.2.12"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
load("@aspect_rules_js//js:providers.bzl", "js_info")
|
||||
|
||||
def _generate_nav_items(ctx):
|
||||
"""Implementation of the navigation items data generator rule"""
|
||||
|
||||
@@ -35,18 +33,9 @@ def _generate_nav_items(ctx):
|
||||
},
|
||||
)
|
||||
|
||||
# The return value describes what the rule is producing. We expose the generated JSON via
|
||||
# both DefaultInfo and JsInfo so it can be consumed as a `ts_project` dependency (the new
|
||||
# rules_angular `ts_project` requires deps to provide JsInfo).
|
||||
outputs = depset([json_output])
|
||||
return [
|
||||
DefaultInfo(files = outputs),
|
||||
js_info(
|
||||
target = ctx.label,
|
||||
sources = outputs,
|
||||
transitive_sources = outputs,
|
||||
),
|
||||
]
|
||||
# The return value describes what the rule is producing. In this case we need to specify
|
||||
# the "DefaultInfo" with the output json file.
|
||||
return [DefaultInfo(files = depset([json_output]))]
|
||||
|
||||
generate_nav_items = rule(
|
||||
# Point to the starlark function that will execute for this rule.
|
||||
|
||||
@@ -66,7 +66,7 @@ function getTagSinceVersion(
|
||||
}
|
||||
|
||||
// In case of deprecated tag we need to separate the version from the deprecation message.
|
||||
const version = tag.comment.match(/^\s*(?:(?:since|from|as of)\s+)?v?(\d+(?:\.\d+)?)/i)?.[1];
|
||||
const version = tag.comment.match(/\d+(\.\d+)?/)?.[0];
|
||||
|
||||
return {version};
|
||||
}
|
||||
|
||||
@@ -1,54 +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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Flat set of all routes (and `route#anchor` pairs) defined by the application's navigation
|
||||
* tree. Populated once per pipeline invocation from `defined-routes.json`, which is produced
|
||||
* by `//adev/scripts/routes:run_generate_route` from a TypeScript import of the canonical
|
||||
* `ALL_ITEMS` navigation entries.
|
||||
*
|
||||
* This is the same source of truth used by the markdown -> HTML guide rendering pipeline
|
||||
* (see `AdevDocsRenderer#isKnownRoute`), so api-gen and guide-gen agree on what counts as a
|
||||
* valid link target.
|
||||
*
|
||||
* Used by the `{@link /guide/...}` and `[label](/guide/...)` validators to catch broken or
|
||||
* stale guide links at build time.
|
||||
*/
|
||||
let definedRoutes = new Set<string>();
|
||||
|
||||
export function setDefinedRoutes(routes: string[]): void {
|
||||
definedRoutes = new Set(routes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given route (e.g. `guide/signals` or `guide/signals#what-are-signals`) is one
|
||||
* of the known navigation routes.
|
||||
*/
|
||||
export function isKnownRoute(route: string): boolean {
|
||||
return definedRoutes.has(route);
|
||||
}
|
||||
|
||||
/** Whether a defined-routes manifest has been loaded for this pipeline invocation. */
|
||||
export function hasDefinedRoutes(): boolean {
|
||||
return definedRoutes.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the heading anchor IDs defined for the given route path. Used to suggest a
|
||||
* corrected anchor when a `#fragment` doesn't match (case-insensitive lookup).
|
||||
*/
|
||||
export function getAnchorsForRoute(route: string): string[] {
|
||||
const prefix = `${route}#`;
|
||||
const anchors: string[] = [];
|
||||
for (const currentRoute of definedRoutes) {
|
||||
if (currentRoute.startsWith(prefix)) {
|
||||
anchors.push(currentRoute.slice(prefix.length));
|
||||
}
|
||||
}
|
||||
return anchors;
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export function getTagSinceVersion<T extends HasJsDocTags>(
|
||||
}
|
||||
|
||||
// In case of deprecated tag we need to separate the version from the deprecation message.
|
||||
const version = tag.comment.match(/^\s*(?:(?:since|from|as of)\s+)?v?(\d+(?:\.\d+)?)/i)?.[1];
|
||||
const version = tag.comment.match(/\d+(\.\d+)?/)?.[0];
|
||||
return {version};
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
@@ -149,7 +149,6 @@ export interface LinkEntryRenderable {
|
||||
label: string;
|
||||
url: string;
|
||||
title?: string;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
export type CliOptionRenderable = CliOption & {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user