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