Compare commits

..

224 Commits

Author SHA1 Message Date
Ben Hong 95efcdd019 docs: add documentation and script for local docs dev environment 2024-01-25 09:05:49 -08:00
Alan Agius a3787bdfa6 docs: update docs to reflect changes in Angular CLI version 17.1 (#53949)
This changes include mentioning the experimental application builder migration and changes to the `outputPath` and `optimization` option.

PR Close #53949
2024-01-23 18:56:18 +01:00
Younes Jaaidi 454d5e9c86 test(core): fix test with false-negative risk (#54025)
change assertion to handle the case where the function doesn't throw

PR Close #54025
2024-01-23 16:58:56 +01:00
Dylan Hunn d99bc8aeb6 refactor(compiler): ICUs should roll up to the root i18n block for application (#54026)
Previously, if an ICU was inside a nested i18n root, it would use the nested root to calculate whether it should be applied. Now, we use the root i18n block.

PR Close #54026
2024-01-23 15:09:36 +01:00
Paul Gschwendtner f81a4365fe test: add compliance tests for signal-based queries (#53978)
This commit adds compliance tests to ensure that the generated output of
signal-based queries matches our expectation.

Note: collapsing query advance instructions is not implemented yet.

PR Close #53978
2024-01-23 10:24:37 +01:00
Paul Gschwendtner d4c84a97e8 refactor(compiler-cli): generate partial compilation output for signal-based queries (#53978)
This commit ensures that libraries can use signal-based queries, and the
partial compilation output will capture their metadata.

The linker is updated to support parsing this.

Two notes:

1. Older linker versions are not capable of parsing this, so the minimum
   version for signal-based queries is adjusted when such are used.
2. We only emit `isSignal` metadata for queries when signal queries are
   used. This enables libraries to continue supporting older linker
   versions, if signal-based queries are not used.

PR Close #53978
2024-01-23 10:24:36 +01:00
Paul Gschwendtner df828e9947 test: add compiler integration test for signal-based queries (#53978)
Adds a compiler integration test for recognizing signal-based queries,
and emitting the expected output. Concrete output will be verified via
the compliance tests.

PR Close #53978
2024-01-23 10:24:36 +01:00
Paul Gschwendtner d57c1f50b4 refactor(core): correct types for queries where locator was not split at compile time (#53978)
Currently, Angular tries to recognize string locators/predicates for
queries at compile time, and attempts to split multi-selector predicates
into an array as generated output. This is a a performance optimization.

In practice, this works most of the time because the compiler can detect
string locactors/predicates through static analysis.

Though, there are cases where it's not possible. That is when advanced
constructs are used, identifier references etc. that ultimately evaluate
to a string. Currently this breaks with queries and also surfaces now
with signal-based queries.

PR Close #53978
2024-01-23 10:24:36 +01:00
Paul Gschwendtner 8f936a4111 refactor(compiler-cli): recognize signal-based queries in class initializers (#53978)
This commit uses the initializer API recognition that we built for
signal-based inputs, and teaches the compiler to recognize class members
that refer to `viewChild`, `viewChildren`, `contentChild` or
`contentChildren`. Those will declare signal-based view or content queries.

PR Close #53978
2024-01-23 10:24:36 +01:00
Paul Gschwendtner 35ee10e357 refactor(compiler-cli): support generation of signal-based queries (#53978)
This commit introduces the compiler output generation for signal-based
queries. Signal-based queries will have new creation-mode instructions
and update instructions to advance the current query indices in the
global shared context.

An output like the following is the expected output for signal-based
queries:

```
i0.ɵɵdefineComponent({
  viewQuery: function App_Query(rf, ctx) {
      if (rf & 1) {
          i0.ɵɵviewQuery(ctx.d, _c0, 5);
          i0.ɵɵviewQuerySignal(ctx.ds1, _c0, 5);
          i0.ɵɵviewQuerySignal(ctx.ds2, _c0, 5);
      }
      if (rf & 2) {
          let _t;
          // only change-detected queries need explicit refresh
          i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.d = _t.first);
          // we bump up current query index by 2 positions since there are 2 signal-based queries
          i0.ɵɵqueryAdvance(2);
      }
      …
  },
  …
});

```

Note: For now, the collapsing of multiple advance instructions is not
implemented. This will be a follow-up.

Note 2: A couple of query helpers are now in their own file. This makes
it easier to focus on query-specific compiler code. The new function is
called `createQueryCreateCall`, which is a modified variant of the
existing function that previously only generated query parameters.

PR Close #53978
2024-01-23 10:24:36 +01:00
Paul Gschwendtner 7ed3927082 refactor(compiler-cli): expose helper for recognizing initializer APIs (#53978)
The new `input` API is recognized using class member initializers.
We want to support similar APIs for queries, using e.g. `viewChild`
or `viewChild.required`.

This commit extracts the input recognition API and makes it reusable,
so that the same logic can be used to detect queries on a class member.

Additional changes:
  - replacing `coreModule` with the simpler `isCore` parameter. This is
    more readable.
  - support for detecting a list of API names on a single class member.
    This allows us to detect possible query functions on the same class
    member without having to check X times. We simply check for the
    initializer API pattern and check if one API function name matches.

PR Close #53978
2024-01-23 10:24:36 +01:00
Aristeidis Bampakos 9e22d60763 docs: update Aristeidis Bampakos bio (#51779)
PR Close #51779
2024-01-23 10:22:23 +01:00
Matthieu Riegler 93845373a3 refactor(devtools): migrate to standalone (#53998)
Migrated with the schematics and cleanup by hand.

PR Close #53998
2024-01-23 09:53:24 +01:00
Matthieu Riegler ff5575ad0b refactor(devtools): remove date utility. (#53997)
We can use the `toIsoString()` function instead !

PR Close #53997
2024-01-23 09:52:14 +01:00
Srinath Vayalpati 3e35f7415e docs: fixed req.method in http client testing (#53395)
PR Close #53395
2024-01-23 09:50:59 +01:00
ilir.beqiri 24290bcab7 docs: fix typo at component template metadata property (#54015)
Rename the component metadata property, templateUrl to template in the reactive forms tutorial step

PR Close #54015
2024-01-23 09:47:43 +01:00
Matthieu Riegler 5c1bbf2e50 docs: update version compatibility table for aio & adev (#54008)
PR Close #54008
2024-01-23 09:46:56 +01:00
divyasudagoni 6186e5237f docs: move import statement of ngmodule from standalone component to module code #53952 (#54016)
PR Close #54016
2024-01-22 19:43:37 +01:00
Payam Valadkhan 3e1384048e feat(compiler-cli): support host directives for local compilation mode (#53877)
At the moment local compilation breaks for host directives because the current logic relies on global static analysis. This change creates a local version by cutting the diagnostics and copying the directive identifier as it is to the generated code without attempting to statically resolve it.

PR Close #53877
2024-01-22 14:44:24 +01:00
Payam Valadkhan b774e22d8e feat(compiler-cli): make it configurable to generate alias reexports (#53937)
At the moment when unified host is selected (through option `_useHostForImportGeneration`) the compiler always generates alias reexports. Such reexports are mainly generated to satisfy strict dependency condition for generated files. Such condition is no longer the case for G3. At the same time, these alias reexports make it impossible to mix locally compiled targets with globally compiled targets. More precisely, a globally compiled target may not be able to consume a locally compiled target as its dependency since the former may import from the alias reexports which do not exist in the latter due to local compilation mode. So, to make global-local compilation interop possible, it is required to be able to turn off alias reexport generation.

PR Close #53937
2024-01-22 14:20:26 +01:00
Joey Perrott ec03e462f3 refactor: migrate bazel, benchpress, elements and misc to prettier formatting (#53995)
Migrate formatting to prettier for bazel, benchpress, elements and misc from clang-format

PR Close #53995
2024-01-22 09:07:15 +01:00
Joey Perrott bbbe477f47 refactor: migrate animations to prettier formatting (#53977)
Migrate formatting to prettier for animations from clang-format

PR Close #53977
2024-01-19 20:08:57 +01:00
Joey Perrott 711cb41626 refactor(devtools): migrate devtools to prettier formatting (#53945)
Migrate formatting to prettier for devtools from clang-format

PR Close #53945
2024-01-19 19:09:54 +01:00
Tomasz Ducin 4be253483d refactor(devtools): improving type safety (#53436)
This PR reduces the number of unnecessary `any` occurrences in devtools packages.

PR Close #53436
2024-01-19 17:35:25 +01:00
Paul 30f00d887d docs: update start-data.md (#52577)
PR Close #52577
2024-01-19 17:34:45 +01:00
Matthieu Riegler 3aea2e6872 build: Adding myself for devtools approval (#53989)
This to help Alex with the reviewing of devtools PRs.

PR Close #53989
2024-01-19 17:33:21 +01:00
Paul Gschwendtner c8ee52897e build: update g3 sync config to match internal one (#53987)
There were some changes to the Copybara config that should
be reflected here as well.

PR Close #53987
2024-01-19 17:18:11 +01:00
Andrew Scott 5ae85e4849 refactor(core): node removal notifies scheduler only when animations are enabled (#53857)
Node removal is immediate and does not require change detection to run
when animations are not provided. This refactor makes the animation
engine notify the scheduler rather than doing it on all node removals.

PR Close #53857
2024-01-19 10:28:24 +01:00
Joey Perrott cc34e5fb07 refactor: migrate scripts to prettier formatting (#53976)
Migrate formatting to prettier for scripts from clang-format

PR Close #53976
2024-01-19 10:22:06 +01:00
Jessica Janiuk 1d1c413169 docs: updates defer guide to reflect updated default defer block test behavior (#53975)
This updates the deferrable view guide to specify playthrough as the default with examples of configuring to manual.

PR Close #53975
2024-01-19 10:16:35 +01:00
Angular Robot ee68df9be9 build: update all non-major dependencies (#53979)
See associated pull request for more information.

PR Close #53979
2024-01-19 10:11:29 +01:00
Joey Perrott a057b61d1f ci: provide proper location for artifacts to allow for firebase to find the artifacts for deployment (#53960)
Move the artifacts during deployment into adev/ so that its at the same level as the firebase.json file.

PR Close #53960
2024-01-18 18:08:30 +01:00
Angular Robot c67647b8c2 build: update cross-repo angular dependencies (#53970)
See associated pull request for more information.

PR Close #53970
2024-01-18 17:16:34 +01:00
Ankita Sood 3d9e8b2109 docs: add ankita (#52319)
add Ankita as GDE in contributors.json

PR Close #52319
2024-01-18 14:28:35 +01:00
lilbeqiri 8737544d69 build(devtools): prevent underscores from being added at build time (#53921)
Add the keepNames config property to esbuild in order to prevent prepending of underscores to the class names.

PR Close #53921
2024-01-18 10:22:12 +01:00
Sasidharan SD a566c5ce44 docs: fix broken code example (#53531)
PR Close #53531
2024-01-18 10:16:19 +01:00
Sasidharan SD 65ff0f8246 docs: fix spelling errors in tutorials steps (#53840)
PR Close #53840
2024-01-18 10:14:47 +01:00
Matthieu Riegler e227275087 refactor(devtools): Add support for signals. (#53269)
The devtools now support signals.
Writable signals of primitives are editable.
Object Signal and other non-writable signals (like computed) are not editable.

Co-authored-by: Tomasz Ducin <tomasz.ducin@gmail.com>

PR Close #53269
2024-01-17 16:47:17 -08:00
Matthieu Riegler 7dd56736e8 refactor(compiler): remove mentions of unused compiler options. (#53746)
Those options where removed in #49672.

PR Close #53746
2024-01-17 16:41:35 -08:00
macvag ff51e0933b docs: fix broken hyperlink in tutorials first app search (#53816)
PR Close #53816
2024-01-17 16:40:06 -08:00
Dylan Hunn b4c7167d90 docs: release notes for the v17.1.0 release 2024-01-17 14:47:35 -08:00
Dylan Hunn 4fd78a99d2 revert: no longer depend on forwardRef changes (#53961)
Revert only the part of the signal queries PRs that depends on the forward ref changes, which were reverted.

PR Close #53961
2024-01-17 13:56:07 -08:00
Dylan Hunn d0ce0110e8 Revert "refactor(core): improve forwardRef typings (#53880)" (#53961)
This reverts commit af6f6e6448.

PR Close #53961
2024-01-17 13:56:07 -08:00
cexbrayat a7f8d9dc08 refactor(compiler-cli): interpolatedSignalNotInvoked diagnostic for input signals (#53883)
This updates the extended diagnotic to handle input signals as well.

PR Close #53883
2024-01-17 11:36:43 -08:00
Matthieu Riegler f80fabbf27 refactor(router): Use Nullish coalescing assignment when possible. (#53930)
Inspired by #53923, lets simplify code when possible.

PR Close #53930
2024-01-17 11:36:11 -08:00
Joey Perrott 455b64524f ci: use ng-dev-previews project for previews (#53958)
Rather than use the actual adev site, we should be using the ng-dev-preview site

PR Close #53958
2024-01-17 11:19:27 -08:00
Andrew Kushnir b0e0f0014e refactor(compiler): extra diagnostics for @defer in local compilation mode (#53899)
This commit adds extra logic to produce a diagnostic in case `@Component.deferredImports` contain types from imports that also bring eager symbols. This would result in retaining a regular import and generating a dynamic import, which would not allow to defer-load dependencies.

PR Close #53899
2024-01-17 10:46:15 -08:00
Jessica Janiuk df6c2057f2 fix(core): Change defer block fixture default behavior to playthrough (#53956)
This inverts the default behavior of test bed to use playthrough for defer blocks instead of manual.

fixes: #53686

PR Close #53956
2024-01-17 10:45:42 -08:00
Joey Perrott f9781f9804 refactor: migrate modules to prettier formatting (#53954)
Migrate formatting to prettier for modules directory from clang-format

PR Close #53954
2024-01-17 09:41:59 -08:00
Pawel Kozlowski 616df433c9 test(core): support type tests for different authoring functions (#53829)
The new type testing infrastructure was introduced for the input-as-signals
authoring functions. This commit modifies this infrastructure to make it more
generic and support queries-as-signals.

PR Close #53829
2024-01-17 09:15:14 -08:00
Pawel Kozlowski e1ac52a0b4 refactor(core): introduce query-as-signal authoring functions (#53829)
This commit adds signatures of the quer-as-signal authoring functions
and their respective type tests.

PR Close #53829
2024-01-17 09:15:14 -08:00
Matthieu Riegler 6007af9651 docs: add platform-browser/animations/async entry to AIO (#53914)
fixes #53912

PR Close #53914
2024-01-17 09:14:42 -08:00
Daniel Puckowski 66e940aebf feat(compiler): scope selectors in @starting-style (#53943)
make sure selectors inside @starting-style queries are correctly scoped

PR Close #53943
2024-01-17 09:14:11 -08:00
Joey Perrott 0accc64cc0 ci: use the config from adev for adev previews (#53955)
Properly use the adev firebase config.

PR Close #53955
2024-01-17 09:13:42 -08:00
Andrew Kushnir 08233b8374 refactor(compiler): enable register and resolve phases for local compilation (#53901)
This commit update the logic to enable `register` and `resolve` phases for local compilation. Those phases will be useful for local compilation in certain cases (will be used in followup PRs).

PR Close #53901
2024-01-17 09:03:55 -08:00
Joey Perrott e1b9b662a0 ci: set up previews of adev (#53941)
Set up preview actions to allow for a preview of the adev site to be deployed for a PR with the `adev: preview` label.

PR Close #53941
2024-01-16 19:18:30 -08:00
Joey Perrott d04f98d713 ci: run adev tests on CI (#53941)
Run tests in adev for CI

PR Close #53941
2024-01-16 19:18:30 -08:00
Joey Perrott dc62446ef7 refactor: migrate tools to prettier formatting (#53947)
Migrate formatting to prettier for tools directory from clang-format

PR Close #53947
2024-01-16 19:17:49 -08:00
AleksanderBodurri 4ca81b09e8 docs(devtools): update development docs (#53948)
These docs are out of date with how we currently build devtools in dev mode and as a browser extension. This commit brings these docs up to date.

PR Close #53948
2024-01-16 19:17:18 -08:00
AleksanderBodurri 426bcb999a docs(devtools): remove devtools/CONTRIBUTING.md (#53948)
This is an artifact left over form the repo merge. There is already an existing CONTRIBUTING.md for this repo

PR Close #53948
2024-01-16 19:17:17 -08:00
Pawel Kozlowski 6581ac94cd refactor(core): re-organize queries code (#53922)
This commit splits the query implementation and instructions
into a separate files. This is a pattern frequently used by
other functional areas of the framework and is a preparation for
introducing queries-as-signals where we are going to see more
instructions delegating to the same core functionality.

PR Close #53922
2024-01-16 16:00:34 -08:00
Ilija Leko e9c659a486 docs: fix-build-path-in-http-server-command (#53226)
PR Close #53226
2024-01-16 09:37:10 -08:00
Paul Gschwendtner 760b1f3d0b fix(compiler-cli): do not throw fatal error if extended type check fails (#53896)
Currently when the extended type check fails due to an import reference
that cannot be generated, the fatal diagnostic is not caught and
not properly exposed as a `ts.Diagnostic` that can be gracefully
handled. This is inconsistent to non-extended type checking diagnostics.

This is problematic because Angular CLI applications currently fail in
obscure ways because:

- the CLI does not expect `getDiagnosticsForFile` to actually throw
  runtime errors.
- the CLI does not seem to properly print these errors given the
  parallel workers and build excection, and those errors are
  especially hard to debug because there is no `stack` for
  `FatalDiagnosticError`'s.

Example: `MyDir` is not exported and the type check block cannot reference it.

PR Close #53896
2024-01-16 09:36:36 -08:00
Joey Perrott 02b8e11e1e build(docs-infra): enable prerendering for adev (#53871)
Enable the prerender option for adev's angular.json file.

PR Close #53871
2024-01-16 09:35:44 -08:00
Angular Robot a2087a1978 build: update cross-repo angular dependencies (#53606)
See associated pull request for more information.

PR Close #53606
2024-01-12 10:27:01 -08:00
Sasidharan SD 1af3ae3dee docs: remove repeated sentence (#53853)
PR Close #53853
2024-01-12 10:26:32 -08:00
Pawel Kozlowski af6f6e6448 refactor(core): improve forwardRef typings (#53880)
This commit improves the forwardRef typings for better
type safety and inference.

PR Close #53880
2024-01-12 10:26:01 -08:00
Andrew Scott 1f8c53cd0c fix(core): TestBed should still use the microtask queue to schedule effects (#53843)
Prior to this commit, `TestBed` would require tests call `flushEffects`
or `fixture.detectChanges` in order to execute effects. In general, we
want to discourage authoring tests like this because it makes the timing
of change detection and effects differ from what happens in the
application. Instead, developers should perform actions and `await` (or
`flush`/`tick` when using `fakeAsync`) some `Promise` so that Angular
can react to the changes in the same way that it does in the
application.

Note that this still _allows_ developers to flush effects synchronously
with `flushEffects` and `detectChanges` but also enables the <action>,
`await` pattern described above.

PR Close #53843
2024-01-11 12:05:57 -08:00
Andrew Kushnir 79fbc4eaba refactor(compiler-cli): add tests for defer blocks and local compilation (#53591)
This commit adds tests to cover local compilation support for `@defer` blocks.

PR Close #53591
2024-01-10 15:28:59 -08:00
Andrew Kushnir 8ca0b0515d refactor(compiler): update TemplateDefinitionBuilder to support local compilation (#53591)
This commit updates the logic of the `TemplateDefinitionBuilder` to support local compilation and generate a single dependency function for all explicitly deferred deps within a component.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir df685ed7c5 refactor(compiler-cli): add checks to prevent same deps in imports and deferredImports (#53591)
This commit updates the typechecker logic to prevent same deps in `imports` and `deferredImports`.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir a2aa23d8b5 refactor(compiler): add support for internal deferredImports field (#53591)
This commit updates the logic to add support for internal `deferredImports` field in compiler.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir b780d39612 refactor(compiler-cli): update DeferredSymbolTracker to use explicit deps config (#53591)
This commit updates the `DeferredSymbolTracker` class to take info account the `onlyExplicitDeferDependencyImports` flag. The `DeferredSymbolTracker` class also exposes a new API to register import declarations as explicitly deferred, which will be used in followup commits.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir d28cf00df6 refactor(compiler-cli): add an internal config to enforce explicit deps in @defer for local compilation mode (#53591)
This commit adds an internal config option to enforce explicit deps in `@defer` for local compilation mode.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir 6eb5e37d6f refactor(compiler): add an internal API to check is an element is inside a defer block (#53591)
This commit updates the logic to expose an internal API on the `R3BoundTarget` to check if an element belongs to any defer block.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir 7d1d604916 refactor(compiler-cli): store full Pipe metadata in typechecking code (#53591)
This commit updates typechecker to store full Pipe metadata in internal data strctures, so that this information is available to more places in the code, which will be updated in a followup commit.

PR Close #53591
2024-01-10 15:28:58 -08:00
Andrew Kushnir 869ebc54e9 refactor(compiler-cli): extract various logic into separate functions (#53591)
This commit updates a few places to extract the logic into a separate functions which will be reused in a few places in followup commits.

PR Close #53591
2024-01-10 15:28:58 -08:00
Joey Perrott 4af7e0c96a build: set up adev testing (#53854)
Set up testing for the adev directory

PR Close #53854
2024-01-10 15:22:36 -08:00
Andrew Scott 9bbc415408 docs: release notes for the v17.1.0-rc.0 release 2024-01-10 14:38:55 -08:00
Andrew Scott debe484188 release: bump the next branch to v17.2.0-next.0 2024-01-10 14:38:55 -08:00
Andrew Scott 3877635741 docs: release notes for the v17.0.9 release 2024-01-10 14:13:54 -08:00
Paul Gschwendtner 863be4b698 feat(core): expose new input API for signal-based inputs (#53872)
Enables signal inputs for existing Zone based components.
This is a next step we are taking to bring signal inputs earlier to the Angular community.

The goal is to enable early access for the ecosystem to signal inputs, while we are continuing
development of full signal components as outlined in the RFC. This will allow the ecosystem
to start integrating signals more deeply, prepare for future migrations, and improves code quality
and DX for existing components (especially for OnPush).

Based on our work on full signal components, we've gathered more information and learned
new things. We've improved the API by introducing a way to intuitively declare required inputs,
as well as improved the API around initial values. We even support non-primitive initial values
as the first argument to the `input` function now.

```ts
@Directive({..})
export class MyDir {
  firstName = input<string>();            // string|undefined
  lastName = input.required<string>();    // string
  age = input(0);                         // number
```

PR Close #53872
2024-01-10 12:33:31 -08:00
Paul Gschwendtner b2066d4922 refactor(compiler-cli): detect input functions without partial evaluation (#53872)
This allows us to ensure signal inputs and a potential JIT transform
remain single file compilation compatible. The consequences are that
options need to be statically analyzable more strictly, compared to
loosened restrictions with static interpretation where e.g. `alias`
can be defined through a shared variable.

PR Close #53872
2024-01-10 12:33:31 -08:00
David LJ f6a32c03f4 docs: fix & update apf links to pkg examples (#53866)
PR Close #53866
2024-01-10 10:43:54 -08:00
Paul Gschwendtner 7862686a60 test(core): add acceptance unit tests for signal inputs (#53808)
Adds AOT and JIT unit tests for signal inputs that verify integration of
signal inputs for our users.

PR Close #53808
2024-01-10 12:21:06 +00:00
Paul Gschwendtner 74099a3d4a test: add infrastructure to run signal acceptance tests with JIT and AOT (#53808)
Adds infrastructure to run signal input tests with JIT (using the
transform) and AOT. Acceptance tests for signal inputs will run with
both variants. In the future we can consider expanding this
infrastructure for all of our acceptance tests, but that's a different
story.

PR Close #53808
2024-01-10 12:21:06 +00:00
Paul Gschwendtner 39ea8b356e refactor(compiler-cli): drop dependency on external modules for input recognition (#53808)
Improves the recognition of the `input`/`input.required` functions to
not depend on external module resolution. This is useful for local
compilation and for transforms operating on a single file/ isolated
module.

PR Close #53808
2024-01-10 12:21:06 +00:00
Paul Gschwendtner e934f3a581 refactor(compiler-cli): add diagnostic for inputs declared on static members (#53808)
Currently when someone declares a signal or non-signal input on a static
class member, the compiler will not yield any diagnostic. We can detect
these mistakes and report a diagnostic to help our users.

PR Close #53808
2024-01-10 12:21:06 +00:00
Paul Gschwendtner 40b0467d3e refactor(compiler-cli): diagnostics ensuring signal inputs are declared properly (#53808)
This commit addds two diagnostics for two scenarios where signal inputs
are declared incorrectly:

- a signal input is also annotated with `@Input` in the TypeScript
  sources.
- a signal input is also declared in the `inputs` option of
  `@Directive/`@Component`.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 4906ba7b18 test: add completion tests for language service with signal inputs (#53808)
Given that the TCB output changes with signal inputs, and one of our
important considerations was auto-completion, we need some unit tests
that verify and guarantee proper completion with signal inputs being
bound in templates. This commit adds these.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 81379ef52c refactor(compiler-cli): follow-up typo fixes for typecheck utilities (#53808)
Follow-up for the inital signal inputs PR, fixing a typo that was made.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner eba017f8c7 refactor(compiler-cli): add transform to support signal input in JIT (#53808)
This commit adds a transform for supporting input signals in JIT
environments. The transform will be wired up for Angular CLI
applications automatically. An integration test verifies that this fixes
unit testing with signal inputs.

The transform basically will take the signal input metadata and
transform it into `@Input` decorators that can provide static
information to the Angular JIT runtime when the directive/component
definition is compiled.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner b51f386ac7 refactor(compiler-cli): expose import manager import insertion helper (#53808)
This commit does two things:

- exposes `addImports` so that it can be used by transforms that we are
  adding to the compiler. e.g. the signal input to `@Input` transform.

- `updates `addImports` to support/use the transform context factory.
  This will allow us to write proper transforms using `addImports`. Also
  leverages this in the Ivy JS/DTS transforms.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 4c5366829b refactor(compiler-cli): expose helper for parsing signal input class members (#53808)
Moves the signal input class member extraction logic into the dedicated
input function file. This makes the code for signal inputs more
self-contained.

This commit then re-exposes the function as part of `ngtsc/annotations`
so that it can be used later for a transform that will take the signal
input metadata and translate it into a `@Input` decorator. This allows
us to remove code duplication and guarantees consistency/correctness

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 82a1ce0f32 test(compiler-cli): add additional diagnostic test for generic inline constructors (#53808)
We recently landed a commit to introduce support for generic type
checking of signal inputs. For that we had to implement logic that
will generate imports for inline type constructors. This required
changes to the context logic and `TypeCtorOp` file-level op.

This commit ensures that everything is working as expected, specifically
in cases where an inline type ctor is generated and imports would be
needed to unwrap the class members for `InputSignal`.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 55b202f0c7 test(core): add test playground for zone signal inputs (#53808)
This commit creates a small http server Angular application playground
for playing with signal inputs. This is useful for development and also
validates some of the common input patterns.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 977884a0c8 test(core): add runtime tests for input signal (#53808)
This commit adds some runtime unit tests to ensure that input
signal is behaving properly at runtime.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 1df95cd1c1 refactor(core): improve error message and add guide for required inputs (#53808)
Whenever a required input is accessed too early in a
directive/component, the signal input will throw an error.

This is necessary so that we can support required inputs
with intuitive typings that do not include `undefined` for
the short period of time where Angular is creating the component and
then assigning inputs later (Angular currently has no way of setting
inputs as part of the class creation when `new Dir()` happens)

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 7f5ae52c02 test: add integration test for signal inputs in CLI (#53808)
This commit introduces a new integration test to ensure signal inputs
work as expected for end uses in Angular CLI applications.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 4a016886fa test: add signal input compliance tests for linking, partial output and full compilations (#53808)
Adds signal input compliance tests, ensuring linking works as expected,
partial output is generated properly, types are inferred properly, and
that the full, or linked output matches our expected runtime structure.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner 4b4941046c refactor(compiler-cli): support linking/parsing new partial output for signal inputs (#53808)
As we introduced the new partial output for signal inputs, we also need
to update the linker code to be able to parse this. This commit adds
this functionality.

In the follow-up commit, compliance tests for linking, partial output,
and full compilations are added.

PR Close #53808
2024-01-10 12:21:05 +00:00
Paul Gschwendtner d7147507e4 refactor(core): properly emit input flags using bitwise or (#53808)
As part of testing we did accidentally use `bitwiseAnd` for the input
flags, given we started without an extra flag for `HasTransform`.

This commit teaches the compiler to support emitting bitwise OR
and uses it when combining input flags, fully re-enabling transforms
for signal components after the new flag mechanism was introduced in
previous commits.

PR Close #53808
2024-01-10 12:21:04 +00:00
Paul Gschwendtner cfab5a59d6 refactor(core): detect signal input transforms independently of flag (#53808)
This commit changes the `HasTransform` flag to be only concerned with
decorator inputs. This allows us to automatically detect signal input
transforms without reliance on the flag, resulting in less complexity in
the compiler (as outlined in the design doc) and various  other places,
while it also allows us to simplify JIT support for signal inputs
because there would be no need to capture the "hasTransform" state in
the decorator so that JIT can generate the according input flags.

`isSignal` will still persist as an input flag to allow for monomorphic
and highly efficient distinguishing at runtime, whether an input is
signal based or not. JIT transform will also need to propagate this
information to the runtime somehow.

PR Close #53808
2024-01-10 12:21:04 +00:00
Paul Gschwendtner eee0af0599 refactor(core): add internal signal input support for @Input decorator (#53808)
We are adding internal support for declaring signal inputs via the
`@Input` decorator. This is needed for JIT unit testing, or JIT
applications.

In JIT, Angular is not able to recognize signal inputs due to the
lack of static reflection metadata. Decorators attach their information
on the class- without it needing to be instantiated. This allows Angular
to know inputs when preparing/generating the directive definition. With
signal inputs this is not possible- so we need a way to tell Angular
about inputs for JIT applications. We've decided that this is not
something users should have to deal with, so a transform will be added
in a follow-up that will automatically derive/and add the decorators
for signal inputs when requested in JIT environments.

PR Close #53808
2024-01-10 12:21:04 +00:00
Paul Gschwendtner 6fcfe2b0ce build: fix partial output generation errors swallowed (#53808)
In some cases, the input files for a partial output generation
compliance tests may be invalid and lead to compilation errors.

The golden partial would be silently generated with the remaining
test cases. Instead of hiding errors, we will now print these and
cause the script to fail properly.

Note that the error logging is pretty minimalistic, but it's sufficient.

PR Close #53808
2024-01-10 12:21:04 +00:00
Paul Gschwendtner 05ed60a0f9 refactor(compiler-cli): improve type safety of linker AST (#53808)
The linker AST is abstracted to be agnostic of the underlying
implementation AST. i.e. TS AST or Babel AST.

This abstraction also intends to provide some type-safety-ness to
parsing of various partial declarations. This commit improves type
safety further by fixing that `AstValue'`s were not checked for
assignability of `T`- potentially hiding issues/unaccounted values.

Additionally, we fix that `getObject()` does not properly narrow
union types to actual object literals. This happend because e.g. arrays
are of type `object`. We can improve type safety here. Using `Record`
did not help as an array would still assign to that.

PR Close #53808
2024-01-10 12:21:04 +00:00
Andrew Scott 5978b3d132 refactor(core): Move change detection scheduler implementation to core (#53579)
This commit moves the implementation of the change detection scheduler
used for testing to angular/core along with a (private export) provider function.

Note: Naming of the provider function is absolutely not final (and not
public API). I would prefer one that did not mention "zones"
but the easiest thing for now is to have a "Zone" and "Zoneless" naming
scheme.

PR Close #53579
2024-01-09 16:05:32 -08:00
Andrew Scott 60dfcc209e test(core): Add scheduler in tests to tie into ApplicationRef.isStable (#53579)
This commit updates the test scheduler implementation to contribute to
ApplicationRef stableness.

PR Close #53579
2024-01-09 16:05:31 -08:00
Andrew Scott c2dd703d2f refactor(core): Remove internal-only testability features (#53767)
This commit removes the testability features that are internal only.
This simplifies the implementation of testability which will need
updates to support zoneless. Those updates will be easier to manage if
the Testability implementation is simpler.
While protractor is indeed officially EOL, we will still need to do some
updates to support teams migrating to zoneless that have protractor
tests.

As far as protractor's own use of `whenStable`, it does not read the
internal only methods either:
https://github.com/angular/protractor/blob/master/lib/clientsidescripts.js
Anything else depending on these values are not following the defined public API
contract.

PR Close #53767
2024-01-09 14:46:46 -08:00
Andrew Scott 5996d3fa6b Revert "refactor(router): Update integration tests to cover navigation and history API (#53799)" (#53860)
This reverts commit eb2e879a00.

PR Close #53860
2024-01-09 14:30:50 -08:00
Jan Kuehle acf1793d7b refactor(zone.js): delete zone.js externs (#53445)
Externs were used for Closure Compiler. Users have moved on to other
bundlers like Webpack, esbuild, etc. Externs are no longer needed.

PR Close #53445
2024-01-09 13:20:51 -08:00
Jan Kuehle 0dad149514 refactor(zone.js): change from scripts to modules (#53445)
Make Zone.js compatible with moduleDetection:force by turning files that
are currently incompatible from scripts into modules using an empty
export statement.

PR Close #53445
2024-01-09 13:20:50 -08:00
Andrew Scott eb2e879a00 refactor(router): Update integration tests to cover navigation and history API (#53799)
This commit updates the router integration tests to cover both the
classic History and the new Navigation API. There is more work to be
done here, but this commit works to prove the efficacy of the
`FakeNavigation` implementation.

PR Close #53799
2024-01-09 12:56:47 -08:00
Kristiyan Kostadinov 2dedc4a969 fix(compiler): generate less code for advance instructions (#53845)
We generate `advance` instructions before most update instructions and the majority of `advance` calls are advancing by one. We can save some bytes for the most common case by omitting the parameter for `advance(1)` altogether.

PR Close #53845
2024-01-09 12:27:58 -08:00
Matthieu Riegler 09baed082b refactor(core): remove signal mutate implementation (#52348)
It's not used anymore.

PR Close #52348
2024-01-09 12:23:07 -08:00
Tomasz Ducin 2d7d4e2cf0 refactor(core): type-safe global ng (#53439)
This PR provides strict type definition for the window.ng object used
for both console debugging and devtools. `GlobalDevModeUtils` now
gathers all type information about all methods exposed on window.ng.

PR Close #53439
2024-01-09 12:17:48 -08:00
Joey Perrott e7330560cd ci: add adev to ci (#53833)
Add adev job to CI to ensure it continues to build as expected

PR Close #53833
2024-01-09 12:15:59 -08:00
Nadeem Ur-Rehman 7ff44f9424 docs(docs-infra): wrong URL for Unicode CLDR fixed (#53785)
fixes wrong URLs for Unicodde CLDR

Fixes #53734

PR Close #53785
2024-01-09 12:13:11 -08:00
Jessica Janiuk d0b95d5877 fix(migrations): Fix empty switch case offset bug in cf migration (#53839)
This addresses the offset issue caused when a switch case was empty with no spaces or children being affected by the markers that were added, but not accounted for in offset. The markers are not needed for empty content and can be safely removed in this case.

fixes: #53779

PR Close #53839
2024-01-09 12:09:38 -08:00
JiaLiPassion a468a5e965 release: cut the zone.js-0.14.3 release (#53630)
PR Close #53630
2024-01-09 09:10:02 -08:00
Andrew Scott e2b598852f refactor(core): node removal should notify the scheduler (#53812)
This commit ensures that change detection runs when an `LView` is
removed. Change detection is required because DOM nodes aren't actually
removed until the animation engine flushes and this doesn't happen until
the end of `detectChangesInternal` (`rendererFactory.end`).

PR Close #53812
2024-01-09 08:51:30 -08:00
Joey Perrott 1be6b0a58a fix(common): remove unused parameters from the ngClass constructor (#53831)
Remove unused parameters which were only being kept because of a downstream usage in flex layout which is deprecated and end of life

PR Close #53831
2024-01-08 14:38:50 -08:00
Joey Perrott f7c02e1321 fix(docs-infra): include the homepage playground content (#53832)
Include the homepage playground content in the homepage

PR Close #53832
2024-01-08 11:33:17 -08:00
Angular Robot 4a87a8ac10 build: update cross-repo angular dependencies (#53836)
See associated pull request for more information.

PR Close #53836
2024-01-08 11:31:57 -08:00
Andrew Scott dfcf0d5882 fix(core): afterRender hooks now only run on ApplicationRef.tick (#52455)
The `afterRender` hooks currently run after `ApplicationRef.tick` but
also run after any call to `ChangeDetectorRef.detectChanges`. This is
problematic because code which uses `afterRender` cannot expect the
component it's registered from to be rendered when the callback
executes. If there is a call to `ChangeDetectorRef.detectChanges` before
the global change detection, that will cause the hooks to run earlier
than expected.

This behavior is somewhat of a blocker for the zoneless project. There
is plenty of application code that do things like `setTimeout(() =>
doSomethingThatExpectsComponentToBeRendered())`, `NgZone.onStable(() =>
...)` or `ApplicationRef.onStable...`. `ApplicationRef.onStable` is a
should likely work similarly, but all of these are really wanting an API
that is `afterRender` with the requirement that the hook runs after the
global render, not an individual CDRef instance.

This change updates the `afterRender` hooks to only run when
`ApplicationRef.tick` happens.

fixes #52429
fixes #53232

PR Close #52455
2024-01-08 11:30:27 -08:00
Andrew Scott a5a9b408e2 feat(router): Add transient info to RouterLink input (#53784)
This is a follow up to https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4
which added the `info` property to navigation requests. `RouterLink` now
supports passing that transient navigation info to the navigation
request.

This info object can be anything and doesn't have to be serializable.
One use-case might be for passing the element that was clicked. This
might be useful for something like view transitions. In the "animating
with javascript" example from the blog (https://stackblitz.com/edit/stackblitz-starters-cklnkm)
those links could have done this instead of needing to create a separate
directive that tracks clicks.

PR Close #53784
2024-01-05 11:28:31 -08:00
Jeremy Elbourn 91f250dab7 build: configure cross-pkg resolution for api extraction (#52499)
This commit adds path mapping and source dependencies necessary to fully
resolve types during api doc extraction.

PR Close #52499
2024-01-05 11:27:34 -08:00
Matthieu Riegler 5e84d9c8c3 refactor(router): replace last helper with native Array.at(-1) (#53749)
We now have a native method to return the last item of an array

PR Close #53749
2024-01-04 13:58:17 -08:00
Matthieu Riegler b7a717ed10 refactor(router): replace Object.keys with Object.entries where useful. (#53745)
`Object.entries` wasn't supported when those lines were written.

PR Close #53745
2024-01-04 13:57:22 -08:00
mgechev a27da867f9 docs: remove state of js 2023 banner (#53798)
PR Close #53798
2024-01-04 13:56:20 -08:00
Andrew Scott f4bd5a33d2 fix(platform-server): Do not delete global Event (#53659)
This commit removes a hack that deletes `Event` from the global context
when using domino. Instead, it sets the global event to domino's
implementation of `Event`.

PR Close #53659
2024-01-04 12:51:22 -08:00
Paul Gschwendtner 36318dbd41 refactor(compiler-cli): reference InputFlags enum directly for full compiler output (#53571)
Instead of computing the bit input flags at compile-time and inling
the final bit flag number, we will use the `InputFlags` enum directly.
This is a little more code in the compiler side, but will allow us to
have better debuggable development code, and also prevents problems
where runtime flag bitmasks differ from the compiler flag bitmasks.

This is in practice a noop for optimized applications as the enum values
would be inlined anyway. This matches existing compiler emit for e.g.
change detection strategy, or view encapsulation enums.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 56ff04607c build: re-enable linker compliance tests (#53571)
The linker compliance tests were disabled with a Babel update and
nobody realized for quite a while, via
https://github.com/angular/angular/pull/49914.

As we've came across this lost coverage, which also is quite
impactful as all libraries depend on linked output- I've took initiative
to debug the root cause as there was no follow-up.
https://github.com/angular/angular/issues/51647.

It turned out to be a highly complex issue that is non-trivial to fix,
but at least we should try to resurrect the significant portion of test
coverage by still running the linker tests- avoiding regressions, or
unexpected issues (like with defer being developed). We can work on
re-enabling and fixing source-maps separately.

Tracked via https://github.com/angular/angular/issues/51647.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 56ce8dabc5 test: fix linker compliance tests after not running for a while (#53571)
The linker compliance tests did not run for a while. There were a couple
of new tests that were not passing as this wasn't flagged on CI. This commit fixes this.
Fortunately there was no problematic code that did indicate issues with linking.

In the follow-up commit, we fix the compliance test infrastructure to
re-enable linker testing..

One clear issue is still that the defer blocks are not handled properly
in linked output- hence making defer not actually "lazy" for compiled
libraries. This needs to be handled separately by the framework team.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 659b921ab1 test(compiler-cli): add ngtsc test for new signal input API (#53571)
This commit adds a final test for input signals, integrating all major
parts:

* type-checking
* compiler detection
* compiler emit
* API signature tests

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 4e2f0b7f0d test(core): add type signature test for signal input API (#53571)
Adds tests that allow us to ensure that the `input` API works as
expected and that resulting return types match our expectations- without
silently regressing in the future, or missing potential edge-cases.

Testing signatures is hard because of covariance and contravariance,
especially when it comes to the different semantics of `ReadT` and
`WriteT` of input signals. We enable reliable testing by validating the
`d.ts` of the "fake directive class". This ensures clear results,
compared to relying on e.g. type assertions that might
accidentally/silently pass due to covariance/contravariance or
biavariance in the type system.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner c948128902 refactor(core): type EMPTY_OBJ as never for improved type safety (#53571)
Consider a snippet like:

```
const x = directiveDef.inputs || EMPTY_OBJ
```

this currently results in `x` being inferred as just `{}`- ending up
turning of potential future assignment checks. This surfaced in the
`DirectiveDefinition` -> `DirectiveDef` conversion.

Note: This has the effect that assigning `EMPTY_OBJ` to a field of
anything would _always_ pass. It's questionable if this rather impacts
type-safety in a more negative way. There seem to be trade-offs in both
ways... Maybe worth considering just using `{}` directly as fallbacks in
some places, and treating this as an unique symbol?!

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAHgLhmApgNxQJxgXhgbwF8YBDCZdLAbgCgbRJYAHJfGmDmAGxC6SkwBXFABoahAD6CwAExQAzAJaoZuZIK5dS5EmACeteuGgxBapjAkT4VIA

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 1d95a832e3 refactor(core): detect signal inputs at runtime using input flags (#53571)
This commit introduces a new enum for capturing additional metadata
about inputs. Called `InputFlags`. These will be built up at compile
time and then propagated into the runtime logic, in a way that does
not require additional lookup dictionaries data structures, or
additional memory allocations for "common inputs" that do not have any flags.

The flags will incorporate information on whether an input is signal
based. This can then be used to avoid megamorphic accesses when such
input is set- as we'd not need to check the input field value. This also
avoids cases where an input signal may be used as initial value for an
input (as we'd not incorrectly detect the input as a signal input then).

The new metadata emit will be useful for incorporating additional
metadata for inputs, such as whether they are required etc (although
required inputs are a build-time only construct right now- but this is a
good illustration of why input flags can be useful). An alternative
could have been to have an additional boolean entry for signal inputs,
but allocating a number with more flexible input flags seems more future
proof and more reasonable andreadable.

More information on the megamorphic access when updating an input
signal
https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 32f908ab70 fix(core): do not accidentally inherit input transforms when overridden (#53571)
Currently when a base class defines an input with a transform, derived
classes re-defining the input via `@Input`, or `inputs: [<..>]`, end up
inherting the transform due to a bug in the inherit definitions feature.

This commit fixes this. We verified in the Google codebase that this is
an unlikely occurrence and it's trivial to fix on user side by removing
the re-declaration/override, or explictly adding the necessary
transform.

Conceptually, the behavior was quite inconsistent as everything else of
inputs was overridden as expected. i.e. alias, required state etc. The
exception were input transforms. This commit fixes this.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 560ae24511 refactor(core): initial test code for setInput to work with input signals (#53571)
At this point, we have the following pieces in place:

* the input signature is implemented
* the compiler properly parses and recognizes signal inputs
* the compiler supports type-checking of signal inputs
* input signal metadata is passed to partial output

This commit adds a naive runtime solution to distinguishing between
signal inputs and decorator inputs when the `property` instruction
invokes. This is not ideal and non-performant as we introduce additional
megamorphic reads for every property instruction invocation, or if we'd
use `instanceof`, introducing a hard dependency on `InputSignal` and
risking potentially slower detection.

This code exists purely for testing, to enable playing with input
signals in the playground. In a future commit, we will pass around the
input signal metadata at runtime and can perform highly optimized checks
to distinguish between signal or non-signal inputs- when assigning
values.

More information: https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit#heading=h.oloxympe902x

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner 7fb4a37f7d refactor(core): introduce runtime InputSignal implementation (#53571)
This commit introduces the runtime `InputSignal` implementation.
Input initializers using `input` or `input.required` will result in
an instance of `InputSignal` to be created.

An input signal extends the signal primtive, with a couple of small
differences:

 - it's a readonly signal. There is no public `set` or `update`.
 - equality is non-configurable. As per CD semantics, the value is
   guaranteed to be different when the `property` instruction attempts
   to update an input signal.
 - we support a `transform` function, that allows transforming input
   values. The transform is called whenever the input is set. An
   alternative could have been to follow computed-semantics and call the
   transform upon accessing, if dirty.

In the future, we might change this to extend the computed reactive
node, so that we can support computed inputs that do not rely on
continious bound value assignments. See signal based components RFC.

PR Close #53571
2024-01-04 12:07:13 -08:00
Paul Gschwendtner caf3eec51a refactor(core): expose SIGNAL_NODE to allow for advanced extensions of signals (#53571)
For the implementation of input signals, we want to extend the signal
primitive. The basic methods exposed here are not suitable as we'd like
to store additional metadata on the reactive node, and also have a
custom getter (for required inputs and throwing).

To enable this, one small piece was missing. This commit exposes it and
also improves type safety, now that `SignalNode` is typed properly after
the previous commit.

PR Close #53571
2024-01-04 12:07:12 -08:00
Paul Gschwendtner 69b384c0d1 fix(core): SignalNode reactive node incorrectly exposing unset field (#53571)
The `SignalNode` interface, describing the reactive node for a `Signal`,
seemingly exposes the `SIGNAL` symbol as a class member. This is not
true as the `SIGNAL` reactive node only exists on the getter function,
as a way to retrieve the signal underlying reactive node.

This commit fixes this, enabling improved type-safety later, in a
follow-up commit where `SIGNAL_NODE` can now be typed to match the
`SignalNode` interface (unlike now where it's typed as just `object`).

PR Close #53571
2024-01-04 12:07:12 -08:00
Paul Gschwendtner 3eaa006c6f test(compiler-cli): additional type-check transform tests for signal inputs (#53571)
This commit adds additional type-check transform tests for signal
inputs. These tests verify some of the problems with covariance,
contravariance and bivariance that we were suspecting to be problematic
if we would assign `InputSignal`'s directly to the type constructors.

PR Close #53571
2024-01-04 12:07:12 -08:00
Matthieu Riegler 1c63eddb7c refactor(router): remouve unused (#53750)
minor optimisation, removing unused paramters.

PR Close #53750
2024-01-03 13:29:05 -08:00
Andrew Scott 58f2b74807 refactor(core): Remove LContainerFlags.HasChildViewsToRefresh (#53715)
This flag is not actually read anywhere. It doesn't even have any effect
on the traversal algorithm because embedded views are always refreshed
in `Global` traversal mode during the refresh of their parent views.

PR Close #53715
2024-01-03 13:01:35 -08:00
Andrew Scott c59a4dcd77 refactor(core): Use NoopNgZone in componentFixture (#53670)
The `ComponentFixture` code needlessly dances around the `ngZone` being
`null` when the `ComponentFixtureNoNgZone` option is set. Instead, it
can use the `NoopNgZone` to get the same effect without needing to have
checks all over the place for its presence.

PR Close #53670
2024-01-03 11:28:08 -08:00
Dylan Hunn ec661a3f0b refactor(compiler): Fix ambiguous repeater context variables in template pipeline (#53662)
In #52931, Kristiyan fixed a TemplateDefinitionBuilder bug in which derived alias variables in for loops (`$even`, `$first`, etc) were referring to the wrong level of nested `@for` block. (These variables are unique because they become inlined expressions, and are not "real" context variables.) He fixed this by appending level information to the generated alias name.

Template Pipeline actually suffered from the same bug. We fix it in a very similar way -- in particular, whenever these derived context variables are used, we make them depend on versions of `$index` and `$count` that have been suffixed with the xref of the enclosing repeater.

I have added a few more pipeline goldens, because we are not quite as clever as TDB about only generating the duplicate suffixed index and count variables when inside nested loops. This is fine, since in the long run, we want to refactor it more fundamentally.

I have also added a TODO to fix this more rigorously. In particular, it would be nice if we had proper support for shadowed variables, as well as unlimited levels of variables depending on one another.

PR Close #53662
2024-01-03 11:24:20 -08:00
Miles Malerba 3584a230e4 test(compiler): Fix mangling of camel case CSS vars in host bindings (#53665)
Template pipeline previously mangled CSS property names like
`--camelCase` when used in host style bindings. Note: It still *does*
mangle these names in static style attrs, both in host bindings and on
elements. This is clearly wrong, but is consistent with what TDB does
today.

PR Close #53665
2024-01-03 11:23:37 -08:00
Johnny Gérard fed831fe0a docs: fix incorrect glossary entry (#53609)
PR Close #53609
2024-01-03 10:34:54 -08:00
Payam Valadkhan 1a6eaa0fea fix(compiler-cli): input transform in local compilation mode (#53645)
Currently compiling input transform in local mode breaks, since compiler does static analysis for the transform function, and this cannot be done in local mode if the function is imported from another compilation unit. In this fix the static analysis is ditched in local mode.

PR Close #53645
2024-01-03 10:29:34 -08:00
Kristiyan Kostadinov dd052dc0d6 fix(common): server-side rendering error when using in-memory scrolling (#53683)
Fixes that the `BrowserViewportScroller` was throwing an error during server-side rendering because it was accessing `window` directly. Also removes some assertions that aren't necessary anymore.

Fixes #53682.

PR Close #53683
2024-01-03 10:28:55 -08:00
Matthieu Riegler 4cb4e984fb refactor(core): remove duplicate stringify (#53751)
`throwProviderNotFoundError` already stringifies for error. Also let's restrain the paramter type.

PR Close #53751
2024-01-03 10:27:37 -08:00
Matthieu Riegler ce41575e34 refactor(core): minor improvements (#53751)
minor optimisation, removing unused paramters and improve tree shaking

PR Close #53751
2024-01-03 10:27:37 -08:00
cexbrayat 33b5707ee9 fix(compiler-cli): interpolatedSignalNotInvoked diagnostic (#53585)
The diagnostic was catching the following case:

```ts
name = signal('Angular');
```

but not the following ones:

```ts
name = signal('Angular').asReadonly();
name = computed(() => 'Angular');
name!: Signal<string>
```

This was not catched in the tests because the type of `Signal` is different than the one actually used in core.
It turns out the real type forces the diagnostic to check both the `symbol.tsType.symbol` and the `symbol.tsType.aliasSymbol`.

PR Close #53585
2024-01-03 10:19:54 -08:00
Miles Malerba 330242595e refactor(compiler): Fix handling of namespaced attributes (#53646)
It's possible for attributes to have a namespace, we need to handle this
possiblity for both attribute instructions and attributes extracted to
the consts array.

PR Close #53646
2024-01-03 10:19:02 -08:00
Dylan Hunn 1731988e72 refactor(compiler): Add a test for attribute namespaces (#53646)
We currently do not support the `namespace` argument for the attribute instruction. (Who knew it even existed!)

PR Close #53646
2024-01-03 10:19:02 -08:00
Aanchal Agarwal 163e7ad6b7 docs: remove duplicate words (#53673)
PR Close #53673
2024-01-03 10:17:55 -08:00
Kristiyan Kostadinov 2b9a850789 fix(core): allow effect to be used inside an ErrorHandler (#53713)
`effect` was expecting an `ErrorHandler` in its constructor which can lead to a circular DI error if an effect is used inside a custom `ErrorHandler`. These changes inject the `ErrorHandler` only when reporting errors.

Fixes #52680.

PR Close #53713
2024-01-03 10:17:30 -08:00
Andrew Scott 043b1ff8be refactor(common): Fix fake navigation to work with relative navigations (#53719)
This commit updates the implementation of the fake navigation to support
relative navigation requests.

PR Close #53719
2024-01-03 10:16:57 -08:00
Andrew Scott 6586b99daa refactor(router): use MockPlatformLocation for integration tests (#53640)
Use of the `SpyLocation` is problematic because it prevents location
APIs from reaching the platform level (`PlatformLocation`) and
`PathLocationStrategy`. This makes it difficult to test interactions
with those providers, including the ability to use the `Navigation` API,
which will live at the platform level.

PR Close #53640
2024-01-03 10:15:54 -08:00
Kristiyan Kostadinov e5f02052cb fix(compiler): ignore empty switch blocks (#53776)
Adds some code to the compiler so that it ignores empty `@switch` blocks instead of trying to generate code for them.

Fixes #53773.

PR Close #53776
2024-01-03 10:15:17 -08:00
Andrew Scott d315e2c4fa release: cut the v17.1.0-next.5 release 2023-12-21 06:33:32 -08:00
Andrew Scott 9e2e5d6920 docs: release notes for the v17.0.8 release 2023-12-21 06:29:29 -08:00
Joey Perrott c4de4e1f89 refactor(docs-infra): build adev application using local generated assets (#53511)
Use local generated assets to build adev application.

PR Close #53511
2023-12-20 14:49:31 -08:00
Andrew Scott 8d58595478 refactor(core): Add scheduler abstraction and notify when necessary (#53499)
In order to provide a reasonable experience for Angular without Zones,
we need a mechanism to run change detection when we receive a change
notification. There are several existing APIs today that serve as the
change notification: `ChangeDetectorRef.markForCheck`, signal updates,
event listeners (since they mark the view dirty), and attaching a view to
either the `ApplicationRef` or `ChangeDetectorRef`. These operations
are now paired with a notification to the change detection scheduler.

The concrete implementation for this scheduler is still being designed.
However, this gives us a starting point to partner with teams to
experiment with what that might look like.

PR Close #53499
2023-12-20 07:53:31 -08:00
Miles Malerba cc74ebfdf6 refactor(compiler): Rework how ICU placeholders are handled (#53643)
The way we were handling ICU placeholders was not compatible with using
interpolations on attributes of elements inside the ICU. This change
refactors the handling of ICU placeholders and unifies the way
expression and tag placeholders work inside ICUs.

The new approach modifies the ingest logic to add the placeholder on to
the TextOp rather than the TextInterpolationOp. This is because, in
ICUs, we may need multiple i18n expressions created from the
interpolation expressions to roll up into the same placeholder. ICUs
essentially do the interpolation at compile time, combining the static
strings with special placeholder strings that represent the expression
values.

PR Close #53643
2023-12-20 07:23:54 -08:00
Dylan Hunn cafc3b0081 refactor(compiler): Drop the explicit this. in most explicit receivers (#53594)
Consider a case when an explicit `this` read is inside a template with a context that also provides the variable name being read:

```
<ng-template let-a>{{this.a}}</ng-template>
```

Clearly, `this.a` should refer to the class property `a`. However, in today's Angular, `this.a` will refer to `let-a` on the template context.

Amazingly, both TemplateDefinitionBuilder and the Typecheck block have the same bug, and are consistent with each other! This is because `ImplicitReceiver` extends `ThisReceiver` in the parser AST, which is an insane gotcha.

In this commit, I patch the template pipeline to emulate this behavior as well.

To actually fix this nastiness, we have to:
- Update `ingest.ts` in the Template Pipeline (see the corresponding comment)
- Check `type_check_block.ts` in the Typecheck block code (see the corresponding comment)
- Turn off legacy TemplateDefinitionBuilder
- Fix g3, and release in a major version

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn 3212935f4d refactor(compiler): Tricky implicit context issue in template pipeline (#53594)
Add a failing tests about an implicit context issue; I will debug this shortly.

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn 89dd890046 refactor(compiler): Put projection instructions through the regular element const collection (#53594)
`ng-content` elements, and thus their corresponding projection instructions, can have many attributes on them. Some of these attributes may result in special behavior. For example, `ngProjectAs` and `i18n-foo` both result in special const collection, into the approprate BindingKind slot in the const array. Additionally, `i18n-foo` needs to recieve all the additional i18n attribute processing.

We solve this by subjecting `ng-content` attributes to all the same pipeline logic that applies to attributes on elements, and then allow the element const collection phase to collect them.

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn 9ecf0b31ca refactor(compiler): Host bindings do not const collect listener names (#53594)
For regular templates, any listener will have its name const collected into the bindings section of the element consts.

In contrast, host bindings omit listener names from their hostAttrs. This is a strange and inconsistent behavior, so we hide it behind a compatiblity mode flag.

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn 0b1144563b refactor(compiler): Host bindings have a different order for update instructions (#53594)
Annoyingly, TDB uses a separate code path for host bindings, and they have a different order.

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn be54cc852c refactor(compiler): Don't allow pipe bindings in track functions (#53594)
For block track functions may not contain pipe bindings.

PR Close #53594
2023-12-19 13:46:11 -08:00
Dylan Hunn 3e7195764a refactor(compiler): Fix a naming collision in template pipeline (#53594)
We has some special behavior for naming identifiers in Template Pipline, for the sake of compatibility with TDB's source maps tests. However, this has the potential to cause a variable name collision in a particular special case (when the identifier is `ctx`). We add a special check for this, and also tuck all the backwards-compatible naming code inside a compatibility block.

PR Close #53594
2023-12-19 13:46:10 -08:00
Dylan Hunn f89030b83e refactor(compiler): Add source maps support for i18n instructions (#53594)
i18n start/end instructions now take the source spans of the elements they refer to.

PR Close #53594
2023-12-19 13:46:10 -08:00
Andrew Scott 12181b9914 refactor(core): Use single source of truth for ApplicationRef.isStable (#53576)
This commit updates the `ApplicationRef.isStable` implementation to use
a single `Observable` to manage the state. This simplifies the mental
model quite a bit and removes the need for rx operators like
`distinctUntilChanged` and `combineLatest`.

PR Close #53576
2023-12-19 12:42:45 -08:00
Jessica Janiuk d49333edc3 fix(migrations): cf migration - detect and error when result is invalid i18n nesting (#53638) (#53639)
This will gracefully error on templates when the resulting template would have invalid i18n nested structures.

PR Close #53638

PR Close #53639
2023-12-19 12:22:01 -08:00
Jessica Janiuk fb7c58cda7 fix(migrations): cf migration - detect and error when result is invalid i18n nesting (#53638)
This will gracefully error on templates when the resulting template would have invalid i18n nested structures.

PR Close #53638
2023-12-19 11:01:57 -08:00
Jessica Janiuk 22b95de9bc fix(migrations): cf migration - fix bug in attribute formatting (#53636)
The formatting that would preserve attribute indents completely missed attributes that start on new lines rather than the same line as the opening element.

PR Close #53636
2023-12-19 10:16:29 -08:00
Jessica Janiuk 8e2178792d fix(migrations): cf migration - ensure full check runs for all imports (#53637)
In cases where CommonModule was unsafe to remove but other imports were present, the symbol check would be skipped. This should run for all the possibly removed symbols for safety.

PR Close #53637
2023-12-19 10:11:47 -08:00
Andrew Scott c16e9a7cf0 refactor(core): No default for zone stableness token (#53505)
When an application does not use zones, it does not need a default value
for the zone stableness token. This will allow zoneless applications to
tree-shake a lot of rxjs operators out of `ApplicationRef`.

Note that at the moment, `provideZoneChangeDetection` is included in all
applications as well as the `TestBed` environment. It is not currently
possible to remove the zone stable code as a result. This will be
possible only when we make zones an opt-in rather than opt-out.

PR Close #53505
2023-12-19 09:59:12 -08:00
Andrew Scott 502f300757 fix(router): Should not freeze original object used for route data (#53635)
This was broken in 3278966068 where the
new code fails to copy the data object when not inheriting data.

fixes #53632

PR Close #53635
2023-12-19 09:20:56 -08:00
Jens Kuehlers 00cbdc99fd docs: Add v18 dates (#53549)
Add the v18 dates to a.dev as well and shift v17.1 to a more
conservative date (exact week is tbc)

PR Close #53549
2023-12-19 08:38:05 -08:00
Jens Kuehlers 4d8925b053 docs: fixed the year (#53549)
Set the year of 17.1-18.0 releases to 2024

PR Close #53549
2023-12-19 08:38:05 -08:00
Jens Kuehlers a5543f78c6 docs: remove extra spare line (#53549)
removed accidentially included line

PR Close #53549
2023-12-19 08:38:05 -08:00
Jens Kuehlers 16bfaaf152 docs: add upcoming release dates (#53549)
Adds the tentatively planned release weeks for 17.1-18.0 - please note
that these might change (and 17.3 might or might not be released).

PR Close #53549
2023-12-19 08:38:05 -08:00
Kristiyan Kostadinov 478d622265 fix(compiler): project empty block root node in template pipeline (#53620)
Updates the template pipeline to allow for the root node of an `@empty` block to be content projected.

PR Close #53620
2023-12-19 08:37:33 -08:00
Kristiyan Kostadinov df8a825910 fix(compiler): project empty block root node (#53620)
Expands the workaround from #52414 to allow for the root nodes of `@empty` blocks to be content projected.

Fixes #53570.

PR Close #53620
2023-12-19 08:37:33 -08:00
Andrew Scott 6c8faaa769 refactor(core): Rename InitialRenderPendingTasks and restructure isStable observable (#53534)
The InitialRenderPendingTasks currently attempts to only contribute to
ApplicationRef stableness one time to support SSR. This isn't actually
how the switchMap works in reality. This commit updates
the isStable observable to be more clear that it's always a combination
of the zone stableness and pending tasks.

In addition, this commit renames the service to just be PendingTasks
because it doesn't directly relate to rendering. While the purpose is
to track things that might cause rendering to happen, we don't know if the
tasks will affect rendering at all.

PR Close #53534
2023-12-19 08:36:28 -08:00
Jessica Janiuk 8bf752539f fix(migrations): cf migration - preserve indentation on attribute strings (#53625)
During formatting, attribute indentation is changed, and that can affect internationalized strings. This fix detects if an attribute value string is left open and skips formatting on those lines.

PR Close #53625
2023-12-18 22:38:52 +00:00
Jessica Janiuk b40bb22a66 fix(migrations): cf migration - improve import declaration handling (#53622)
This should make the import declaration symbol removal a bit more robust and handle more than just CommonModule safely.

PR Close #53622
2023-12-18 22:10:58 +00:00
Miles Malerba 8460a4380c test(compiler): Fix handling of deceptively named attributes (#53626)
It's possible for the user to create a host attrbiute binding with a
name that makes it _look_ like a class binding `{['class.foo']: ''}`, we
were previously treating these as actual class property bindings. This
change fixes the logic so that only true property bindings cam be
converted to class property bindings.

Note: A user who added an attribute like the above almost certainly
intended to create an actual class property binding. It would be nice if
we could add a diagnostic to warn them about this.

PR Close #53626
2023-12-18 22:10:30 +00:00
Miles Malerba d485cfef28 test(compiler): Update partial golden files (#53596)
Update partial golden files to reflect newly added tests

PR Close #53596
2023-12-18 22:09:26 +00:00
Miles Malerba 73faf94273 refactor(compiler): Allow duplicate style and class consts (#53596)
Further refine the template pipeline's behavior w.r.t. duplicate values
in the consts array to better align its behavior with TDB. In particular
this means allowing duplicate values for classes and styles.

PR Close #53596
2023-12-18 22:09:26 +00:00
Miles Malerba 2ef1bb960e test(compiler): Add a test for handling of duplicate bindings (#53596)
Adds a test for handling of duplicate bindings. Fow now we replicate the
TDB behavior in template pipeline, which is: For style and class text
attributes, only keep the last one. For all other text attributes, add
all of the values to the consts array.

PR Close #53596
2023-12-18 22:09:26 +00:00
Miles Malerba 4c8e8e3714 refactor(compiler): support multiple statements in host listener (#53596)
Support multiple statements in a host listener, like we do for listeners
in the template. Also adds a test to verify the behavior.

PR Close #53596
2023-12-18 22:09:26 +00:00
Kristiyan Kostadinov 3a689c2050 fix(compiler): correctly intercept index in loop tracking function (#53604)
The for loop tracking function doesn't allow references to local template variables, aside from `$index` and the item which are passed in as parameters. We enforce this by rewriting all variable references to the components scope.

The problem is that the logic that rewrites the references first walks the view tree and then checks if the variable is `$index` or the item. This is problematic in nested for loops, because it'll find the `$index` of the parent.

These changes resolve the issue by checking for `$index` and the item first.

Fixes #53600.

PR Close #53604
2023-12-18 16:27:06 +00:00
Kristiyan Kostadinov 872e7f25fe fix(core): tree shake version class (#53598)
Core bundles were retaining the `Version` class and `VERSION` constant, because we stamp out the current version in the DOM. This shouldn't be necessary, because any usage of `0.0.0-PLACEHOLDER` will be replaced with the current version at build time. These changes remove the reference so it can be tree shaken away.

PR Close #53598
2023-12-18 16:26:36 +00:00
Kristiyan Kostadinov aecb675fa5 fix(core): avoid repeated work when parsing version (#53598)
The `Version` class was splitting the same value 3 times instead of doing it once and reusing the result.

PR Close #53598
2023-12-18 16:26:36 +00:00
Miles Malerba dda656e054 refactor(compiler): Make attribute const collection less aggressive (#53580)
Changes template pipeline to be less aggressive in const collecting
attrs, to match the behavior of template definition builder. There is
nothing wrong with the more aggressive const collection, and in fact it
would be good to re-enable it later, but for now this makes it easier to
transition from TDB to template pipeline.

Also adds a test to verify that sensitive iframe attributes are properly
validated.

PR Close #53580
2023-12-18 16:26:03 +00:00
Charles Lyding e149ebf228 build: update rxjs build version to v7 (#53500)
The version of rxjs used to build the repository has been updated to v7.
This required only minimal changes to the code. Most of which were type
related only due to more strict types in v7. The behavior in those cases
was left intact. The most common type related change was to handle the
possibility of `undefined` with `toPromise` which was always possible with
v6 but the types did not reflect the runtime behavior. The one change that
was not type related was to provide a parameter value to the `defaultIfEmpty`
operator. It no longer defaults to a value of `null` if no default is provided.
To provide the same behavior the value of `null` is now passed to the operator.

PR Close #53500
2023-12-18 16:25:37 +00:00
JiaLiPassion b06b24b504 fix(zone.js): handle fetch with AbortSignal (#49595)
fetch support AbortSignal, zone.js schedules a macroTask when fetch()

```
fetch(..., {signal: abortSignal});
```

we should also be able to cancel fetch with `zoneTask.cancel` call.
So this commit create an internal AbortSignal to handle
`zoneTask.cancel()` call and also delegate the `options.signal` from the
user code.

PR Close #49595
2023-12-18 15:35:10 +00:00
JiaLiPassion d4973ff9b0 fix(zone.js): support addEventListener with signal option. (#49595)
Close #49591

```
const ac = new AbortController();
addEventListener(eventName, handler, {signal: ac.signal);`
ac.abort();
```

Currently `zone.js` doesn't support the `signal` option, this PR allows
the user to use AbortContoller to remove the event listener.

PR Close #49595
2023-12-18 15:35:10 +00:00
Johnny Gérard 1b65931428 docs: clarify Node.js version (#53581)
Resolve #53334

PR Close #53581
2023-12-18 15:33:40 +00:00
Angular Robot e52a6cf949 docs: update Angular CLI help [main] (#53590)
Updated Angular CLI help contents.

PR Close #53590
2023-12-18 15:32:34 +00:00
Angular Robot 13e89bcbd7 build: update scorecard action dependencies (#53607)
See associated pull request for more information.

PR Close #53607
2023-12-18 15:32:03 +00:00
Jessica Janiuk 1892904070 refactor(migrations): update cf migration to handle module imports better (#53575)
This addresses the case where modules are being used and declared in the same file as the component. It is unclear whether its safe to remove the common module in this case, so best to leave it.

PR Close #53575
2023-12-15 19:58:40 +00:00
Jessica Janiuk 7bb312fcf6 fix(migrations): cf migration - stop removing empty newlines from i18n blocks (#53578)
Since i18n sections are whitespace sensitive, we should avoid removing empty lines when inside an i18n block that were present before.

PR Close #53578
2023-12-15 19:49:04 +00:00
Dylan Hunn c9879457f6 refactor(compiler): Not all attribute values beginning with a colon are namespaced (#53574)
TemplateDefinitionBuilder is apparently more careful about when it attempts to split namespaces in attribute values. However, we are doing this on style attributes, which might start with a single `:`. Rather than refactor our logic to only try to split namespaces in some cases, we can just add an option to make namespace splitting fail gracefully. We only use this option for attributes, not elements.

Note also: the compiled code for this, while "correct" is absolutely insane. Maybe we should consider fixing this, as a matter of principle.

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn 7ef5d9deef refactor(compiler): Support class and non-class attributes with the same name (#53574)
Some elements may have multiple bindings with the same name. We should accept and emit them all, as long as they have different kinds.

Co-authored-by: Miles Malerba <mmalerba@users.noreply.github.com>

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn 39767e764f refactor(compiler): Repeaters may consume a variable for their empty block (#53574)
Repeaters consume a variable slot only when they have an empty block.

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn d1e7ce1b2d refactor(compiler): Host attribute bindings should always be extracted into hostAttrs (#53574)
Host attribute literal bindings should not result in an `attribute` update instruction.

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn 06286ce413 refactor(compiler): Defer when consumes a variable (binding) slot (#53574)
The template pipeline was previously not reserving a variable slot for the result of the `deferWhen` instruction, which caused the `defer when` feature to crash at runtime.

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn ea71ff8ef7 refactor(compiler): Fix self-closing element source spans in template pipeline (#53574)
When an element is self-closing, it will cause an `element` instruction to be emitted (instead of `elementStart`/`elementEnd`). In that case, we should use map whole source span for the instruction, not just the starting span.

PR Close #53574
2023-12-15 19:48:28 +00:00
Dylan Hunn a5565d176a refactor(compiler): Update a user visible error to match TDB (#53574)
This error about global event targets is user-facing; update it to match TDB's error verbatim.

PR Close #53574
2023-12-15 19:48:27 +00:00
Dylan Hunn 98277dacab refactor(compiler): Add an escape hatch to use non-unique const pool names (#53574)
The template pipeline was producing slightly different names than TemplateDefinitionBuilder for defer deps functions. I have added a workaround in the name of backwards compatibility, to avoid suffixing the const pool function names.

PR Close #53574
2023-12-15 19:48:27 +00:00
Matthieu Riegler d7e7409e81 refactor(animations): drop convertToMap & copyStyles and use native functions. (#52441)
`copyStyles` was side-effectful and also returned a value : not great.

PR Close #52441
2023-12-14 12:33:24 -08:00
Matthieu Riegler d28ce0a4cf refactor(animations): use a spread operator instead of custom copyObj (#52441)
Using the spread operator is the fastest way to shallow copy an object.

PR Close #52441
2023-12-14 12:33:24 -08:00
Matthieu Riegler 6a347d36eb refactor(animations): use a spread operator to convert an iterable to an array. (#52441)
Using the spread operator is the fastest way to convert an iterable to an array.

PR Close #52441
2023-12-14 12:33:24 -08:00
Matthieu Riegler 635c3b4fa8 refactor(animations): use existing helper methods. (#52441)
Saving a few bytes.

PR Close #52441
2023-12-14 12:33:24 -08:00
Miles Malerba e8f00423fa refactor(compiler): Ensure correct context kind on ICU contexts (#53557)
Previously when we found an ICU that was the only translatable content
in its i18n block, we assigned the block's i18n context to the ICU.
However, we neglected to set the contextKind to inidcate that the
context was associated with an ICU. As of this change we now set the
correct contextKind.

This change also refactors the context creation to explicitly separate
creation of contexts for attributes, root i18n blocks, child i18n
blocks, and ICUs. This allows us to more easily ensure that contexts are
shared appropriately between i18n blocks and ICUs.

Finally, this change also refactors the i18n message extraction pahse to
simplify how contexts are converted to i18n messages. This
simplification should make it easier to merge i18n contexts and i18n
messages into a single op in a future refactor.

PR Close #53557
2023-12-14 09:33:52 -08:00
Dylan Hunn cba1b902da refactor(compiler): Add test for ICU root message with element tags (#53557)
Add test for ICU root message with element tags

PR Close #53557
2023-12-14 09:33:52 -08:00
Jessica Janiuk db6b4a6bc4 fix(migrations): Fix cf migration bug with parsing for loop conditions properly (#53558)
The order of operations for getting for loop parts was inverted resulting in the StringStack to never be reduced.

fixes: #53555

PR Close #53558
2023-12-14 09:33:27 -08:00
Andrew Scott 606de5166e refactor(core): newly created and any dirty views should get refreshed during CD (#53022)
When a view has the `Dirty` flag and is reattached, we should ensure that it is
reached and refreshed during the next change detection run from above.

In addition, when a view is created and attached, we should ensure that it is reached
and refreshed during change detection. This can happen if the view is
created and attached outside a change run or when it is created and
attached after its insertion view was already checked. In both cases, we
should ensure that the view is reached and refreshed during either the
current change detection or the next one (if change detection is not
already running).

We can achieve this by creating all views with the `Dirty` flag set.

However, this does happen to be a breaking change in some scenarios.
The one identified internally was actually depending on change detection
_not_ running immediately because it relied on an input value that was
set using `ngModel`. Because `ngModel` sets its value in a `Promise`, it
is not available until the _next_ change detection cycle. Ensuring
created views run in the current change change detection will result in
different behavior in this case.

Making option the default is the solution to #52928. That will have to
wait for a major version.

PR Close #53022
2023-12-14 09:17:49 -08:00
1301 changed files with 52171 additions and 48118 deletions
+1 -4
View File
@@ -16,6 +16,7 @@ integration/cli-hello-world/node_modules
integration/cli-hello-world-ivy-i18n/node_modules
integration/cli-hello-world-lazy/node_modules
integration/cli-hello-world-mocha/node_modules
integration/cli-signal-inputs/node_modules
integration/defer/node_modules
integration/dynamic-compiler/node_modules
integration/forms/node_modules
@@ -38,7 +39,3 @@ integration/typings_test_ts49/node_modules
integration/typings_test_ts50/node_modules
integration/typings_test_ts51/node_modules
integration/typings_test_ts52/node_modules
# Temporarily ignore all of adev
adev/
+38
View File
@@ -0,0 +1,38 @@
# This workflow builds the previews for pull requests when a certain label is applied.
# The actual deployment happens as part of a dedicated second workflow to avoid security
# issues where the building would otherwise occur in an authorized context where secrets
# could be leaked. More details can be found here:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
name: Build adev for preview deployment
on:
pull_request:
types: [synchronize, labeled]
permissions: read-all
jobs:
adev-build:
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'adev: preview') ||
(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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build --config=aio_local_deps //adev:build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
deploy-directory: './dist/bin/adev/build/browser'
+50
View File
@@ -0,0 +1,50 @@
# This workflow runs whenever the ADEV build workflow has completed. Deployment happens
# as part of a dedicated second workflow to avoid security issues where the building would
# otherwise occur in an authorized context where secrets could be leaked.
#
# More details can be found here:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
name: Deploying adev preview to Firebase
on:
workflow_run:
workflows: ['Build adev for preview deployment']
types: [completed]
permissions:
# Needed in order to be able to comment on the pull request.
pull-requests: write
# Needed in order to checkout the repository
contents: read
# Needed in order to retrieve the artifacts from the previous job
actions: read
env:
PREVIEW_PROJECT: ng-dev-previews
PREVIEW_SITE: ng-dev-previews-fw
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # tag=v3
with:
token: '${{secrets.GITHUB_TOKEN}}'
- name: Configure Firebase deploy target
working-directory: ./
run: |
# We can use `npx` as the Firebase deploy actions uses it too.
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
firebase-config-dir: './adev'
firebase-public-dir: './adev/build/browser'
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
bazelrc: ./.bazelrc.user
@@ -34,7 +34,7 @@ jobs:
# the number of concurrent actions is determined based on the host resources.
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
workflow-artifact-name: 'aio'
pull-number: '${{github.event.pull_request.number}}'
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'aio'
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/branch-manager@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
bazelrc: ./.bazelrc.user
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -31,7 +31,7 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Starting Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh run &
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
+40 -28
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
@@ -60,13 +60,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -78,13 +78,13 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
@@ -94,15 +94,15 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with upstream packages
@@ -110,20 +110,35 @@ jobs:
- name: Check generated bundle sizes
run: yarn --cwd aio payload-size
adev:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel test --config=aio_local_deps //adev:test
aio-local:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with local packages
@@ -138,15 +153,15 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Set the stable branch environment variable
@@ -182,13 +197,13 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -200,7 +215,7 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
@@ -208,9 +223,9 @@ jobs:
./packages/zone.js/node_modules
./packages/zone.js/test/typings/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -218,7 +233,6 @@ jobs:
//packages/zone.js/bundles:zone.umd.js \
//packages/zone.js:npm_package \
//packages/zone.js/test/closure:closure_js \
//packages/zone.js:zone_externs
- run: |
mkdir -p packages/zone.js/build/
@@ -229,7 +243,6 @@ jobs:
cp dist/bin/packages/zone.js/npm_package/bundles/zone-mix.umd.js ./packages/zone.js/test/extra/
cp dist/bin/packages/zone.js/npm_package/bundles/zone-patch-electron.umd.js ./packages/zone.js/test/extra/
cp dist/bin/packages/zone.js/test/closure/zone.closure.mjs ./packages/zone.js/build/test/zone.closure.mjs
cp dist/bin/packages/zone.js/zone_externs.js ./packages/zone.js/build/zone_externs.js
# Install
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
@@ -238,7 +251,6 @@ jobs:
- run: yarn --cwd packages/zone.js jest:test
- run: yarn --cwd packages/zone.js jest:nodetest
- run: yarn --cwd packages/zone.js electrontest
- run: yarn --cwd packages/zone.js closuretest
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
- run: yarn --cwd packages/zone.js/test/typings test
@@ -250,7 +262,7 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -258,9 +270,9 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/commit-message-based-labels@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/commit-message-based-labels@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/post-approval-changes@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/post-approval-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -14,6 +14,6 @@ jobs:
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/feature-request@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/feature-request@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/google-internal-tests@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/google-internal-tests@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@e52eb8237f2ed71195f87ce8046467a176568e58
- uses: angular/dev-infra/github-actions/unified-status-check@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -22,15 +22,15 @@ jobs:
version: ['https://next.angular.io/', 'https://angular.io/']
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Install node modules in aio
run: yarn install --frozen-lockfile --cwd aio
- name: Run basic e2e and deployment config tests.
@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
cache-node-modules: true
node-module-directories: |
+3 -3
View File
@@ -30,7 +30,7 @@ jobs:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
@@ -39,7 +39,7 @@ jobs:
# Upload the results as artifacts.
- name: 'Upload artifact'
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
with:
name: SARIF file
path: results.sarif
@@ -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@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
with:
sarif_file: results.sarif
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
env:
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/create-pr-for-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
branch-prefix: update-cli-help
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- name: Generate `events.json`
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@e52eb8237f2ed71195f87ce8046467a176568e58
uses: angular/dev-infra/github-actions/create-pr-for-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
branch-prefix: docs-update-events
pr-title: 'docs: update events'
+23 -1
View File
@@ -5,7 +5,18 @@ import {FormatConfig} from '@angular/ng-dev';
*/
export const format: FormatConfig = {
'prettier': {
'matchers': ['**/*.{yaml,yml}'],
'matchers': [
'**/*.{yaml,yml}',
'devtools/**/*.{js,ts}',
'tools/**/*.{js,ts}',
'modules/**/*.{js,ts}',
'scripts/**/*.{js,ts}',
'packages/animations/**/*.{js,ts}',
'packages/bazel/**/*.{js,ts}',
'packages/benchpress/**/*.{js,ts}',
'packages/elements/**/*.{js,ts}',
'packages/misc/**/*.{js,ts}',
],
},
'clang-format': {
'matchers': [
@@ -31,6 +42,17 @@ export const format: FormatConfig = {
'!packages/common/src/i18n/currencies.ts',
// Temporarily disable formatting for adev
'!adev/**',
// Migrated to prettier
'!devtools/**/*.{js,ts}',
'!tools/**/*.{js,ts}',
'!modules/**/*.{js,ts}',
'!scripts/**/*.{js,ts}',
'!packages/animations/**/*.{js,ts}',
'!packages/bazel/**/*.{js,ts}',
'!packages/benchpress/**/*.{js,ts}',
'!packages/elements/**/*.{js,ts}',
'!packages/misc/**/*.{js,ts}',
],
},
'buildifier': true,
-2
View File
@@ -31,8 +31,6 @@
"packages/http/**",
"**/.gitignore",
"**/.gitkeep",
"**/yarn.lock",
"**/package.json",
"**/third_party/**",
"**/tsconfig-build.json",
"**/tsconfig-tsec.json",
+2
View File
@@ -1150,6 +1150,7 @@ groups:
- josephperrott
- mgechev
- twerske
- ~jeanmeche
# =========================================================
# Dev-infra
@@ -1394,6 +1395,7 @@ groups:
- devversion # Paul Gschwendtner
- dgp1130 # Doug Parker
- dylhunn # Dylan Hunn
- jeanmeche # Matthieu Riegler
- jelbourn # Jeremy Elbourn
- jessicajaniuk # Jessica Janiuk
- JiaLiPassion # Jia Li
+84 -153
View File
@@ -1,39 +1,99 @@
<a name="17.1.0-next.4"></a>
# 17.1.0-next.4 (2023-12-13)
<a name="17.1.0"></a>
# 17.1.0 (2024-01-17)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [b98d8f79ed](https://github.com/angular/angular/commit/b98d8f79ed4979fb91661dbdfb6266a917b41474) | fix | handle ambient types in input transform function ([#51474](https://github.com/angular/angular/pull/51474)) |
| [79ff91a813](https://github.com/angular/angular/commit/79ff91a813e544929cb5eb5f9aab762a9f3d0435) | fix | allow TS jsDocParsingMode host option to be programmatically set ([#53126](https://github.com/angular/angular/pull/53126)) |
| [5613051a8b](https://github.com/angular/angular/commit/5613051a8bd2626ae347292807b2bf21085c4c02) | fix | allow TS jsDocParsingMode host option to be programmatically set again ([#53292](https://github.com/angular/angular/pull/53292)) |
| [df8a825910](https://github.com/angular/angular/commit/df8a825910951bebf34a4eede42f3ce5cd3e6fb7) | fix | project empty block root node ([#53620](https://github.com/angular/angular/pull/53620)) |
| [478d622265](https://github.com/angular/angular/commit/478d6222650884478314985e3d5132587c4f670c) | fix | project empty block root node in template pipeline ([#53620](https://github.com/angular/angular/pull/53620)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [9e5456912a](https://github.com/angular/angular/commit/9e5456912a699315f1f5e8458d35df946c01dc12) | fix | generate less type checking code in for loops ([#53515](https://github.com/angular/angular/pull/53515)) |
| [abdc7e4578](https://github.com/angular/angular/commit/abdc7e45786667e4283912024a641975f1917d97) | feat | support type-checking for generic signal inputs ([#53521](https://github.com/angular/angular/pull/53521)) |
| [e620b3a724](https://github.com/angular/angular/commit/e620b3a724cb615af24b7779c0ab492d24efb8cc) | fix | add compiler option to disable control flow content projection diagnostic ([#53311](https://github.com/angular/angular/pull/53311)) |
| [4c1d69e288](https://github.com/angular/angular/commit/4c1d69e2880f22745c820eee630d10071e4fa86b) | fix | add diagnostic for control flow that prevents content projection ([#53190](https://github.com/angular/angular/pull/53190)) |
| [76ceebad04](https://github.com/angular/angular/commit/76ceebad047f62972654a8c934c77d8d02d9fa14) | fix | do not throw fatal error if extended type check fails ([#53896](https://github.com/angular/angular/pull/53896)) |
| [1a6eaa0fea](https://github.com/angular/angular/commit/1a6eaa0fea1024b919e17ac9d2e8c07df7916de8) | fix | input transform in local compilation mode ([#53645](https://github.com/angular/angular/pull/53645)) |
| [56a76d73e0](https://github.com/angular/angular/commit/56a76d73e037aeea1975808d5c51608fd23d4fa6) | fix | modify `getConstructorDependencies` helper to work with reflection host after the previous change ([#52215](https://github.com/angular/angular/pull/52215)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2565121851](https://github.com/angular/angular/commit/25651218512becb3a491b1c94d643a5066a6d3cb) | fix | Avoid refreshing a host view twice when using transplanted views ([#53021](https://github.com/angular/angular/pull/53021)) |
| [629343f247](https://github.com/angular/angular/commit/629343f24741760e76d98ea1005debaf4775bfc7) | fix | Multiple subscribers to ApplicationRef.isStable should all see values ([#53541](https://github.com/angular/angular/pull/53541)) |
| [42f4f70e97](https://github.com/angular/angular/commit/42f4f70e97b47f7ffcf15e8af2c1a3fa3f393903) | fix | remove signal equality check short-circuit ([#53446](https://github.com/angular/angular/pull/53446)) |
| [f35adcb9b2](https://github.com/angular/angular/commit/f35adcb9b255741515d54bbce694f4cd34aac249) | fix | update feature usage marker ([#53542](https://github.com/angular/angular/pull/53542)) |
| [1fc5442947](https://github.com/angular/angular/commit/1fc5442947b07be7c11fe7c79b9e69f9458c8108) | perf | avoid changes Observable creation on QueryList ([#53498](https://github.com/angular/angular/pull/53498)) |
| [e3a6bf9b6c](https://github.com/angular/angular/commit/e3a6bf9b6c3bef03df9bfc8f05b817bc875cbad6) | perf | optimize memory allocation when reconcilling lists ([#52245](https://github.com/angular/angular/pull/52245)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [a02767956a](https://github.com/angular/angular/commit/a02767956a00e0b44b690411f01584f6660c83ea) | fix | CF Migration - ensure bound ngIfElse cases ignore line breaks ([#53435](https://github.com/angular/angular/pull/53435)) |
| [6aa1bb78e8](https://github.com/angular/angular/commit/6aa1bb78e8b9ca866b3c2d84f4005a0e67f2b6bc) | fix | cf migration - undo changes when html fails to parse post migration ([#53530](https://github.com/angular/angular/pull/53530)) |
| [cc02852ac4](https://github.com/angular/angular/commit/cc02852ac44bfea13e8b9d5768694a094fc2c4b5) | fix | CF migration only remove newlines of changed template content ([#53508](https://github.com/angular/angular/pull/53508)) |
| [ce1076785c](https://github.com/angular/angular/commit/ce1076785c98ecc9660637cafa4a62a63be8d315) | fix | cf migration validate structure of ngswitch before migrating ([#53530](https://github.com/angular/angular/pull/53530)) |
| [1f5c8bf116](https://github.com/angular/angular/commit/1f5c8bf116783c25234ab7fa301a0be842e58007) | fix | ensure we do not overwrite prior template replacements in migration ([#53393](https://github.com/angular/angular/pull/53393)) |
| [79f791543b](https://github.com/angular/angular/commit/79f791543b607eea1bb6a28b9c744760ba4fd028) | fix | fix cf migration import removal when errors occur ([#53502](https://github.com/angular/angular/pull/53502)) |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [fdb9cb7a5b](https://github.com/angular/angular/commit/fdb9cb7a5be4695abaa60efc89ecd50ddb9e9e6e) | fix | Get correct base path when using "." as base href when serving from the file:// protocol. ([#53547](https://github.com/angular/angular/pull/53547)) |
| [863be4b698](https://github.com/angular/angular/commit/863be4b6981dc60ca0610b0e61d2ba1f5759e2a3) | feat | expose new `input` API for signal-based inputs ([#53872](https://github.com/angular/angular/pull/53872)) |
| [94096c6ede](https://github.com/angular/angular/commit/94096c6ede67436a349ae07901f2bb418bf9f461) | feat | support TypeScript 5.3 ([#52572](https://github.com/angular/angular/pull/52572)) |
| [69b384c0d1](https://github.com/angular/angular/commit/69b384c0d16f631741339d8757c32ef08260cfce) | fix | `SignalNode` reactive node incorrectly exposing unset field ([#53571](https://github.com/angular/angular/pull/53571)) |
| [6f79507ea7](https://github.com/angular/angular/commit/6f79507ea7f272d8d09250e222ca831f407867d8) | fix | Change defer block fixture default behavior to playthrough ([#53956](https://github.com/angular/angular/pull/53956)) |
| [32f908ab70](https://github.com/angular/angular/commit/32f908ab70f1b9ed3f92df1cae05ddde68932404) | fix | do not accidentally inherit input transforms when overridden ([#53571](https://github.com/angular/angular/pull/53571)) |
| [bdd61c768a](https://github.com/angular/angular/commit/bdd61c768a28b56c68634b99c036986499829f45) | fix | replace assertion with more intentional error ([#52234](https://github.com/angular/angular/pull/52234)) |
| [0daca457bb](https://github.com/angular/angular/commit/0daca457bb5bb6ffe14b7037264f8497eb5b3daf) | fix | TestBed should still use the microtask queue to schedule effects ([#53843](https://github.com/angular/angular/pull/53843)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [48c5041687](https://github.com/angular/angular/commit/48c50416872313f23d2759232c990cf8d2de6996) | fix | provide more actionable error message when route is not matched in production mode ([#53523](https://github.com/angular/angular/pull/53523)) |
| [5c1d441029](https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4) | feat | Add info property to `NavigationExtras` ([#53303](https://github.com/angular/angular/pull/53303)) |
| [50d7916278](https://github.com/angular/angular/commit/50d79162785bb8d3e158a7a4a3733f4c75d3b127) | feat | Add router configuration to resolve navigation promise on error ([#48910](https://github.com/angular/angular/pull/48910)) |
| [a5a9b408e2](https://github.com/angular/angular/commit/a5a9b408e2eb64dcf1d3ca16da4897649dd2fc34) | feat | Add transient info to RouterLink input ([#53784](https://github.com/angular/angular/pull/53784)) |
| [726530a9af](https://github.com/angular/angular/commit/726530a9af9c8daf7295cc3548f24e70f380d70e) | feat | Allow `onSameUrlNavigation: 'ignore'` in `navigateByUrl` ([#52265](https://github.com/angular/angular/pull/52265)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.9"></a>
# 17.0.9 (2024-01-10)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [c22b513b3f](https://github.com/angular/angular/commit/c22b513b3f45c49baf4d6e571735aa4aa33b7845) | fix | remove unused parameters from the ngClass constructor ([#53831](https://github.com/angular/angular/pull/53831)) |
| [bd9f89d1c8](https://github.com/angular/angular/commit/bd9f89d1c8e295f00ef3399c6bedca4e2ce0e89e) | fix | server-side rendering error when using in-memory scrolling ([#53683](https://github.com/angular/angular/pull/53683)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [92fd6cc42e](https://github.com/angular/angular/commit/92fd6cc42e0a217f4575404ea8e2af462f14ae18) | fix | generate less code for advance instructions ([#53845](https://github.com/angular/angular/pull/53845)) |
| [6a41961fbd](https://github.com/angular/angular/commit/6a41961fbdf921f7a3ab82e92185eab751c0d153) | fix | ignore empty switch blocks ([#53776](https://github.com/angular/angular/pull/53776)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [7309463697](https://github.com/angular/angular/commit/7309463697110d848781bfe81f04b6070c759928) | fix | interpolatedSignalNotInvoked diagnostic ([#53585](https://github.com/angular/angular/pull/53585)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [441db5123f](https://github.com/angular/angular/commit/441db5123f368c5fe3a505a79b97309e8400250f) | fix | `afterRender` hooks now only run on `ApplicationRef.tick` ([#52455](https://github.com/angular/angular/pull/52455)) |
| [f9120d79cb](https://github.com/angular/angular/commit/f9120d79cb88a9f14c4baa6981f71a5afbd984e1) | fix | allow effect to be used inside an ErrorHandler ([#53713](https://github.com/angular/angular/pull/53713)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [e92c86b77f](https://github.com/angular/angular/commit/e92c86b77ff7d400c034740ee0ad68acb626a22f) | fix | Fix empty switch case offset bug in cf migration ([#53839](https://github.com/angular/angular/pull/53839)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [91cb16fde9](https://github.com/angular/angular/commit/91cb16fde9ff68bcdc760428c47f4ebf3e476bd2) | fix | Do not delete global Event ([#53659](https://github.com/angular/angular/pull/53659)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.8"></a>
# 17.0.8 (2023-12-21)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [de5c9ca8e9](https://github.com/angular/angular/commit/de5c9ca8e9a026ad752aab348bd137f647cc3cc9) | fix | correctly intercept index in loop tracking function ([#53604](https://github.com/angular/angular/pull/53604)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [d79489255a](https://github.com/angular/angular/commit/d79489255a48a55b136746856af0d8be51bbe665) | fix | avoid repeated work when parsing version ([#53598](https://github.com/angular/angular/pull/53598)) |
| [513fee871e](https://github.com/angular/angular/commit/513fee871eb5d1c8a12bfe64878276b1d9c07705) | fix | tree shake version class ([#53598](https://github.com/angular/angular/pull/53598)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [eb7c29c7b6](https://github.com/angular/angular/commit/eb7c29c7b64a64853c7f7691912835240b62dfeb) | fix | cf migration - detect and error when result is invalid i18n nesting ([#53638](https://github.com/angular/angular/pull/53638)) |
| [ed936ba0e9](https://github.com/angular/angular/commit/ed936ba0e9d5323095aa486dc58d8b088b4d64cc) | fix | cf migration - detect and error when result is invalid i18n nesting ([#53638](https://github.com/angular/angular/pull/53638)) ([#53639](https://github.com/angular/angular/pull/53639)) |
| [5c2f2539e2](https://github.com/angular/angular/commit/5c2f2539e27bd18fd586c5977d853cd827f7b004) | fix | cf migration - ensure full check runs for all imports ([#53637](https://github.com/angular/angular/pull/53637)) |
| [817dc1b27f](https://github.com/angular/angular/commit/817dc1b27fcf32db8f8d8417bdd46a1763460f11) | fix | cf migration - fix bug in attribute formatting ([#53636](https://github.com/angular/angular/pull/53636)) |
| [7ac60bab9a](https://github.com/angular/angular/commit/7ac60bab9a4bb7a5af037a2de47339bd7837b157) | fix | cf migration - improve import declaration handling ([#53622](https://github.com/angular/angular/pull/53622)) |
| [c3f85e51a9](https://github.com/angular/angular/commit/c3f85e51a970aa458b920835573fa4e392e6f909) | fix | cf migration - preserve indentation on attribute strings ([#53625](https://github.com/angular/angular/pull/53625)) |
| [e73205ff5a](https://github.com/angular/angular/commit/e73205ff5ae4c382924266520f271b56972a0db6) | fix | cf migration - stop removing empty newlines from i18n blocks ([#53578](https://github.com/angular/angular/pull/53578)) |
| [886aa7b2a9](https://github.com/angular/angular/commit/886aa7b2a99a301eb6e35dc1a59ef918f0bea348) | fix | Fix cf migration bug with parsing for loop conditions properly ([#53558](https://github.com/angular/angular/pull/53558)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [0696ab6a5b](https://github.com/angular/angular/commit/0696ab6a5bea8acd6dafde488151150bf3332b79) | fix | Should not freeze original object used for route data ([#53635](https://github.com/angular/angular/pull/53635)) |
<!-- CHANGELOG SPLIT MARKER -->
@@ -76,47 +136,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.0-next.3"></a>
# 17.1.0-next.3 (2023-12-06)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [5613051a8b](https://github.com/angular/angular/commit/5613051a8bd2626ae347292807b2bf21085c4c02) | fix | allow TS jsDocParsingMode host option to be programmatically set again ([#53292](https://github.com/angular/angular/pull/53292)) |
| [77ac4cd324](https://github.com/angular/angular/commit/77ac4cd32491d0c994cb4ea50372601c955cec3d) | fix | generate proper code for nullish coalescing in styling host bindings ([#53305](https://github.com/angular/angular/pull/53305)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [e620b3a724](https://github.com/angular/angular/commit/e620b3a724cb615af24b7779c0ab492d24efb8cc) | fix | add compiler option to disable control flow content projection diagnostic ([#53311](https://github.com/angular/angular/pull/53311)) |
| [d7a83f9521](https://github.com/angular/angular/commit/d7a83f95213cdd5d3ceefbc95fa1190856b0198c) | fix | avoid conflicts with built-in global variables in for loop blocks ([#53319](https://github.com/angular/angular/pull/53319)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [77939a3bd3](https://github.com/angular/angular/commit/77939a3bd39073d07e24797a4632ec2d2a6b92e0) | fix | cleanup signal consumers for all views ([#53351](https://github.com/angular/angular/pull/53351)) |
| [899f6c4a12](https://github.com/angular/angular/commit/899f6c4a12127f87aeedf47ee128ce949ebe717c) | fix | handle hydration of multiple nodes projected in a single slot ([#53270](https://github.com/angular/angular/pull/53270)) |
| [4b23221b4e](https://github.com/angular/angular/commit/4b23221b4e5f8be7bcffc8ace255143653550d3d) | fix | support hydration for cases when content is re-projected using ng-template ([#53304](https://github.com/angular/angular/pull/53304)) |
| [82609d471c](https://github.com/angular/angular/commit/82609d471c9802b847a5654918eca1ba3ebb29b3) | fix | support swapping hydrated views in `@for` loops ([#53274](https://github.com/angular/angular/pull/53274)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [1c1e8c477b](https://github.com/angular/angular/commit/1c1e8c477b8d5c8deecf05744bd1d5f7c86d2e14) | fix | CF migration - ensure NgIfElse attributes are properly removed ([#53298](https://github.com/angular/angular/pull/53298)) |
| [2998d482dd](https://github.com/angular/angular/commit/2998d482dd3f1f5ff7f08260c3947ded74dac126) | fix | CF Migration - Fix case of aliases on i18n ng-templates preventing removal ([#53299](https://github.com/angular/angular/pull/53299)) |
| [aad5e5bd0e](https://github.com/angular/angular/commit/aad5e5bd0e7f6581f51fc3a23c98a7f11219a8f8) | fix | CF Migration add support for ngIf with just a then ([#53297](https://github.com/angular/angular/pull/53297)) |
| [6f75471307](https://github.com/angular/angular/commit/6f75471307a9458d0cffd0ee2bbf4190640c3c3a) | fix | CF Migration fix missing alias for bound ngifs ([#53296](https://github.com/angular/angular/pull/53296)) |
| [2a5a8f6f05](https://github.com/angular/angular/commit/2a5a8f6f052961c010a68a05d868f50220f2fcf2) | fix | Change CF Migration ng-template placeholder generation and handling ([#53394](https://github.com/angular/angular/pull/53394)) |
| [03e2f1bb25](https://github.com/angular/angular/commit/03e2f1bb25693d1a5f4e53fc4f4cd1937cf6bda1) | fix | fix regexp for else and then in cf migration ([#53257](https://github.com/angular/angular/pull/53257)) |
| [f4a96a9160](https://github.com/angular/angular/commit/f4a96a9160927903c38e172f6375c4bc5f8e0905) | fix | handle aliases on bound ngIf migrations ([#53261](https://github.com/angular/angular/pull/53261)) |
| [5a0ed28c9d](https://github.com/angular/angular/commit/5a0ed28c9d3f1c7507feee482e37c3346d52b033) | fix | handle nested ng-template replacement safely in CF migration ([#53368](https://github.com/angular/angular/pull/53368)) |
| [01b18a4248](https://github.com/angular/angular/commit/01b18a4248f068df33c0ca8a2d62ef2fc69f941c) | fix | handle templates outside of component in cf migration ([#53368](https://github.com/angular/angular/pull/53368)) |
| [9834fd2738](https://github.com/angular/angular/commit/9834fd27387ed5edc65bed56aa003fc45d250420) | fix | remove setting that removes comments in CF migration ([#53350](https://github.com/angular/angular/pull/53350)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [5c1d441029](https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4) | feat | Add info property to `NavigationExtras` ([#53303](https://github.com/angular/angular/pull/53303)) |
| [50d7916278](https://github.com/angular/angular/commit/50d79162785bb8d3e158a7a4a3733f4c75d3b127) | feat | Add router configuration to resolve navigation promise on error ([#48910](https://github.com/angular/angular/pull/48910)) |
| [1940280d27](https://github.com/angular/angular/commit/1940280d27bb3ece585d6345dbd742d208f46912) | fix | Ensure canMatch guards run on wildcard routes ([#53239](https://github.com/angular/angular/pull/53239)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.6"></a>
# 17.0.6 (2023-12-06)
### compiler
@@ -156,43 +175,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.0-next.2"></a>
# 17.1.0-next.2 (2023-11-29)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [79ff91a813](https://github.com/angular/angular/commit/79ff91a813e544929cb5eb5f9aab762a9f3d0435) | fix | allow TS jsDocParsingMode host option to be programmatically set ([#53126](https://github.com/angular/angular/pull/53126)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [4c1d69e288](https://github.com/angular/angular/commit/4c1d69e2880f22745c820eee630d10071e4fa86b) | fix | add diagnostic for control flow that prevents content projection ([#53190](https://github.com/angular/angular/pull/53190)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [58cf389d80](https://github.com/angular/angular/commit/58cf389d8095dd522a998b6b9a7d7b8da1656644) | fix | avoid stale provider info when TestBed.overrideProvider is used ([#52918](https://github.com/angular/angular/pull/52918)) |
| [c7c7ea9813](https://github.com/angular/angular/commit/c7c7ea9813f6dcf91c096bb37d36bfe0c715a04f) | fix | inherit host directives ([#52992](https://github.com/angular/angular/pull/52992)) |
| [b35c6731e5](https://github.com/angular/angular/commit/b35c6731e51de9c33707010fc780cbaa559be6c3) | fix | Reattached views that are dirty from a signal update should refresh ([#53001](https://github.com/angular/angular/pull/53001)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [dbca1c9d61](https://github.com/angular/angular/commit/dbca1c9d618be4d195d779ef80606c6d22f7c977) | fix | Add ngForTemplate support to control flow migration ([#53076](https://github.com/angular/angular/pull/53076)) |
| [53912fdf74](https://github.com/angular/angular/commit/53912fdf74e1224f95edc3dd550c51451e613c44) | fix | allows colons in ngIf else cases to migrate ([#53076](https://github.com/angular/angular/pull/53076)) |
| [fadfee4324](https://github.com/angular/angular/commit/fadfee43247acec7e450d5cc929a0a6780a54bd1) | fix | cf migration fix migrating empty switch default ([#53237](https://github.com/angular/angular/pull/53237)) |
| [b2aeaf5d97](https://github.com/angular/angular/commit/b2aeaf5d97327842a5bb657e9f02bb4f1358304b) | fix | CF migration log warning when collection aliasing detected in `@for` ([#53238](https://github.com/angular/angular/pull/53238)) |
| [c6326289f8](https://github.com/angular/angular/commit/c6326289f87089b3415ba0f3e371206b8a396b66) | fix | cf migration removes unnecessary bound ngifelse attribute ([#53236](https://github.com/angular/angular/pull/53236)) |
| [e6f10e81d2](https://github.com/angular/angular/commit/e6f10e81d2486b5cc1f468777b7e1057d3f09dc5) | fix | control flow migration formatting fixes ([#53076](https://github.com/angular/angular/pull/53076)) |
| [6291c8db09](https://github.com/angular/angular/commit/6291c8db092c0ab67f6789cc03c19dfa0bdc251d) | fix | fix off by one issue with template removal in CF migration ([#53255](https://github.com/angular/angular/pull/53255)) |
| [f1b7d40057](https://github.com/angular/angular/commit/f1b7d400575a6a02a2fc2b642c2b8c15cca9b3c4) | fix | fixes CF migration i18n ng-template offsets ([#53212](https://github.com/angular/angular/pull/53212)) |
| [a738b48717](https://github.com/angular/angular/commit/a738b48717e16613b34c3e50480cd49f2a31858f) | fix | fixes control flow migration common module removal ([#53076](https://github.com/angular/angular/pull/53076)) |
| [a3599515bb](https://github.com/angular/angular/commit/a3599515bb2513fbcb2899284c7f6b4bc021ddaf) | fix | properly handle ngIfThen cases in CF migration ([#53256](https://github.com/angular/angular/pull/53256)) |
| [8a52674faa](https://github.com/angular/angular/commit/8a52674faacefd4042726383fdf0aed59a04fb7e) | fix | Update CF migration to skip templates with duplicate ng-template names ([#53204](https://github.com/angular/angular/pull/53204)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [29e0834c4d](https://github.com/angular/angular/commit/29e0834c4deecfa8bf384b5e4359796c8123afcd) | fix | Resolvers in different parts of the route tree should be able to execute together ([#52934](https://github.com/angular/angular/pull/52934)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.5"></a>
# 17.0.5 (2023-11-29)
### core
@@ -222,39 +204,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.0-next.1"></a>
# 17.1.0-next.1 (2023-11-20)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [29c5416d14](https://github.com/angular/angular/commit/29c5416d14638a05a894269aa5dbe67e98754418) | fix | remove `load` on image once it fails to load ([#52990](https://github.com/angular/angular/pull/52990)) |
| [7affa57754](https://github.com/angular/angular/commit/7affa5775427e92ef6e949c879765b7c8aa172da) | fix | scan images once page is loaded ([#52991](https://github.com/angular/angular/pull/52991)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [ec2d6e7b9c](https://github.com/angular/angular/commit/ec2d6e7b9c2b386247d1320ee89f8e3ac5e5a0dd) | fix | changed after checked error in for loops ([#52935](https://github.com/angular/angular/pull/52935)) |
| [406049b95e](https://github.com/angular/angular/commit/406049b95e5234f17a7a18839ac848640f53fdde) | fix | generate i18n instructions for blocks ([#52958](https://github.com/angular/angular/pull/52958)) |
| [d9d566d315](https://github.com/angular/angular/commit/d9d566d31540582d73201675d0b8ed901261669e) | fix | nested for loops incorrectly calculating computed variables ([#52931](https://github.com/angular/angular/pull/52931)) |
| [5fb707f81a](https://github.com/angular/angular/commit/5fb707f81aee43751e61d2ed0861afc9b85bc85a) | fix | produce placeholder for blocks in i18n bundles ([#52958](https://github.com/angular/angular/pull/52958)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [b4d022e230](https://github.com/angular/angular/commit/b4d022e230ca141b12437949d11dc384bfe5c082) | fix | add diagnostic for control flow that prevents content projection ([#52726](https://github.com/angular/angular/pull/52726)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [ed0fbd4071](https://github.com/angular/angular/commit/ed0fbd4071339b1af22d82bac07d51c6c41790cd) | fix | cleanup loading promise when no dependencies are defined ([#53031](https://github.com/angular/angular/pull/53031)) |
| [1ce31d819b](https://github.com/angular/angular/commit/1ce31d819b2e4f4425a41f07167a6edce98e77e1) | fix | handle local refs when `getDeferBlocks` is invoked in tests ([#52973](https://github.com/angular/angular/pull/52973)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [e33f6e0f1a](https://github.com/angular/angular/commit/e33f6e0f1a483cad908fa6d7376d62332797499c) | fix | control flow migration fails for async pipe with unboxing of observable ([#52756](https://github.com/angular/angular/pull/52756)) ([#52972](https://github.com/angular/angular/pull/52972)) |
| [5564d020cd](https://github.com/angular/angular/commit/5564d020cdcea8273b65cf69c45c3f935195af66) | fix | Fixes control flow migration if then else case ([#53006](https://github.com/angular/angular/pull/53006)) |
| [28f6cbf9c9](https://github.com/angular/angular/commit/28f6cbf9c91f957b4926fe34610387e1f1919d4f) | fix | fixes migrations of nested switches in control flow ([#53010](https://github.com/angular/angular/pull/53010)) |
| [e090b48bf8](https://github.com/angular/angular/commit/e090b48bf8534761d46523be57a7889a325bcdec) | fix | tweaks to formatting in control flow migration ([#53058](https://github.com/angular/angular/pull/53058)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.4"></a>
# 17.0.4 (2023-11-20)
### common
@@ -288,24 +237,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.1.0-next.0"></a>
# 17.1.0-next.0 (2023-11-15)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [56a76d73e0](https://github.com/angular/angular/commit/56a76d73e037aeea1975808d5c51608fd23d4fa6) | fix | modify `getConstructorDependencies` helper to work with reflection host after the previous change ([#52215](https://github.com/angular/angular/pull/52215)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [94096c6ede](https://github.com/angular/angular/commit/94096c6ede67436a349ae07901f2bb418bf9f461) | feat | support TypeScript 5.3 ([#52572](https://github.com/angular/angular/pull/52572)) |
| [bdd61c768a](https://github.com/angular/angular/commit/bdd61c768a28b56c68634b99c036986499829f45) | fix | replace assertion with more intentional error ([#52234](https://github.com/angular/angular/pull/52234)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [726530a9af](https://github.com/angular/angular/commit/726530a9af9c8daf7295cc3548f24e70f380d70e) | feat | Allow `onSameUrlNavigation: 'ignore'` in `navigateByUrl` ([#52265](https://github.com/angular/angular/pull/52265)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.3"></a>
# 17.0.3 (2023-11-15)
### animations
+19
View File
@@ -61,6 +61,25 @@ Get started with Angular, learn the fundamentals and explore advanced topics on
- [Lazy Loading][lazyloading]
- [Animations][animations]
### Local Development
To contribute to Angular docs, you can setup a local environment with the following commands:
```bash
# Clone Angular repo
git clone https://github.com/angular/angular.git
# Navigate to project directory
cd angular
# Install dependencies
yarn
# Build and run local dev server
# Note: Initial build will take some time
yarn docs
```
## Development Setup
### Prerequisites
+1 -1
View File
@@ -80,7 +80,7 @@ yarn_install(
"//:.yarnrc",
"//:tools/npm-patches/@bazel+jasmine+5.8.1.patch",
"//tools:postinstall-patches.js",
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-b2cec2dcba358f9dc7111800a3d65738f57abe8f.patch",
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-e0ec7b60641d7f6369be45d8d02663fd50f320be.patch",
"//tools/esm-interop:patches/npm/@bazel+concatjs+5.8.1.patch",
"//tools/esm-interop:patches/npm/@bazel+esbuild+5.7.1.patch",
"//tools/esm-interop:patches/npm/@bazel+protractor+5.7.1.patch",
-2
View File
@@ -1,2 +0,0 @@
/src/assets/tutorials
/src/assets/previews
+78 -17
View File
@@ -1,24 +1,34 @@
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect")
load("//:packages.bzl", "link_packages")
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
package(default_visibility = ["//visibility:public"])
# All source and configuration files required to build the docs app
APPLICATION_FILES = [
"//:angular.json",
"adev/tsconfig.app.json",
"adev/tsconfig.json",
"adev/tsconfig.worker.json",
"angular.json",
"tsconfig.app.json",
"tsconfig.json",
"tsconfig.worker.json",
] + glob(
["adev/src/**/*"],
["src/**/*"],
exclude = [
"adev/src/**/*.spec.ts",
"src/**/*.spec.ts",
# Temporarily exclude generated sources produced by the non-bazel
# build until the whole project is built by bazel and this directory
# isn't needed.
"adev/src/generated/**/*",
"src/generated/**/*",
],
)
APPLICATION_DEPS = [
TEST_FILES = APPLICATION_FILES + [
"karma.conf.js",
"tsconfig.spec.json",
] + glob(
["**/*.spec.ts"],
)
APPLICATION_DEPS = link_packages([
"@npm//@angular-devkit/build-angular",
"@npm//@angular/animations",
"@npm//@angular/cdk",
@@ -38,7 +48,48 @@ APPLICATION_DEPS = [
"@npm//ogl",
"@npm//rxjs",
"@npm//typescript",
]
"//adev/src/assets/images",
"//adev/src/assets/textures",
"//adev/src/assets/previews",
"//adev/src/assets:tutorials",
"//adev/src/assets/icons",
"//adev/src/assets:api",
"//adev/src/assets:content",
"@npm//@typescript/vfs",
"@npm//@codemirror/state",
"@npm//@codemirror/view",
"@npm//@codemirror/language",
"@npm//@codemirror/commands",
"@npm//@codemirror/search",
"@npm//@codemirror/autocomplete",
"@npm//@codemirror/lint",
"@npm//@codemirror/lang-html",
"@npm//@codemirror/lang-angular",
"@npm//@codemirror/lang-css",
"@npm//@codemirror/lang-sass",
"@npm//@codemirror/lang-javascript",
"@npm//@lezer/highlight",
"@npm//@lezer/javascript",
"@npm//@lezer/common",
"@npm//xterm",
"@npm//xterm-addon-fit",
"@npm//angular-split",
])
TEST_DEPS = APPLICATION_DEPS + link_packages([
"@npm//@angular/platform-browser-dynamic",
"@npm//@angular/build-tooling/bazel/browsers/chromium",
"@npm//@types/jasmine",
"@npm//@types/node",
"@npm//assert",
"@npm//jasmine",
"@npm//jasmine-core",
"@npm//karma-chrome-launcher",
"@npm//karma-coverage",
"@npm//karma-jasmine",
"@npm//karma-jasmine-html-reporter",
"//aio/tools:windows-chromium-path",
])
copy_to_bin(
name = "application_files_bin",
@@ -49,16 +100,13 @@ architect(
name = "build",
args = [
"angular-dev:build",
"--output-path=build-app",
"--output-path=build",
],
chdir = "$(RULEDIR)",
data = APPLICATION_DEPS + [
":application_files_bin",
],
output_dir = True,
# When building with local packages (--config=aio_local_deps), RBE complains about
# the input tree being too large (> 70,000 files).
tags = ["no-remote-exec"],
)
architect(
@@ -73,7 +121,20 @@ architect(
data = APPLICATION_DEPS + [
":application_files_bin",
],
# When building with local packages (--config=aio_local_deps), RBE complains about
# the input tree being too large (> 70,000 files).
tags = ["no-remote-exec"],
)
architect_test(
name = "test",
args = [
"angular-dev:test",
"--no-watch",
],
chdir = package_name(),
data = TEST_DEPS + TEST_FILES,
env = {
"CHROME_BIN": "../$(CHROMIUM)",
},
toolchains = [
"@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias",
],
)
+17 -14
View File
@@ -1,5 +1,5 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"$schema": "../node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": ".",
"projects": {
@@ -12,30 +12,32 @@
"changeDetection": "OnPush"
}
},
"root": "adev",
"sourceRoot": "adev/src",
"root": ".",
"sourceRoot": "src",
"prefix": "adev",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"externalDependencies": [
"path"
],
"outputPath": "dist/angular-dev",
"index": "adev/src/index.html",
"browser": "adev/src/main.ts",
"server": "adev/src/main.server.ts",
// TODO(josephperrott): enabled prerender
"prerender": false,
"polyfills": ["adev/src/polyfills.ts", "zone.js"],
"tsConfig": "adev/tsconfig.app.json",
"index": "src/index.html",
"browser": "src/main.ts",
"server": "src/main.server.ts",
"prerender": true,
"polyfills": ["src/polyfills.ts", "zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"adev/src/favicon.ico",
"adev/src/robots.txt",
"adev/src/assets"
"src/favicon.ico",
"src/robots.txt",
"src/assets"
],
"styles": ["@angular/docs/styles/global-styles.scss"],
"scripts": [],
"webWorkerTsConfig": "adev/tsconfig.worker.json",
"webWorkerTsConfig": "tsconfig.worker.json",
"optimization": {
"fonts": {
// TODO(josephperrott): enabled inline scripts
@@ -86,6 +88,7 @@
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"include": ["src/app"],
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["@angular/docs/styles/global-styles.scss"],
+73
View File
@@ -0,0 +1,73 @@
{
"hosting": {
"target": "angular-docs",
"public": "./build/browser",
"ignore": ["**/.*"],
"headers": [
{
"source": "assets/tutorials/common/*.jpg",
"headers": [
{
"key": "Cross-Origin-Resource-Policy",
"value": "cross-origin"
},
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
},
{
"source": "*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)",
"headers": [
{
"key": "Cache-Control",
"value": "public,max-age=31536000,immutable"
},
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
},
{
"source": "/[0-9a-f][0-9a-f][0-9a-f].[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].js",
"headers": [
{
"key": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
}
]
},
{
"source": "/@(ngsw-worker.js|ngsw.json)",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache"
}
]
},
{
"source": "/**",
"headers": [
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
},
{
"key": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
}
]
}
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
+74
View File
@@ -0,0 +1,74 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const {getAdjustedChromeBinPathForWindows} = require('../aio/tools/windows-chromium-path');
process.env.CHROME_BIN = getAdjustedChromeBinPathForWindows();
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
{'reporter:jasmine-seed': ['type', JasmineSeedReporter]},
],
proxies: {
'/dummy/image': 'src/assets/images/logos/angular/angular.png',
},
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
random: true,
seed: '',
},
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/site'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
],
},
reporters: ['progress', 'kjhtml', 'jasmine-seed'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
// See /integration/README.md#browser-tests for more info on these args
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
browsers: ['ChromeHeadlessNoSandbox'],
browserNoActivityTimeout: 60000,
singleRun: false,
restartOnFileChange: true,
});
};
// Helpers
function JasmineSeedReporter(baseReporterDecorator) {
baseReporterDecorator(this);
this.onBrowserComplete = (browser, result) => {
const seed = result.order && result.order.random && result.order.seed;
if (seed) this.write(`${browser}: Randomized with seed ${seed}.\n`);
};
this.onRunComplete = () => undefined;
}
-116
View File
@@ -1,116 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/* tslint:disable:no-console */
import {NavigationItem} from '@angular/docs';
import {readFileSync} from 'fs';
import {glob} from 'glob';
import {join} from 'path';
import {SUB_NAVIGATION_DATA} from '../../src/app/sub-navigation-data';
// PATHS
const PROJECT_FOLDER_PATH = join(__dirname, '../../src');
// SKIP Files
const SKIPED_ORPHAN = ['kitchen-sink', 'error', 'examples'];
main();
async function main() {
const contentDir = join(PROJECT_FOLDER_PATH, 'content');
const mdFiles = await retrieveAllMarkdownFiles(contentDir);
const allowedPaths = new Set([
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getPaths(item)),
SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getPaths(item)),
SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getPaths(item)),
]);
const contentPaths = new Set([
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getContentPaths(item)),
...SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getContentPaths(item)),
...SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getContentPaths(item)),
]);
// Regular expression to match relative links
const regex = /\]\((?!https?:\/\/)([^)]+)\)/g;
const orphanFiles: string[] = [];
for (const {path} of mdFiles) {
if (
!contentPaths.has(path.slice(0, -3)) &&
!path.startsWith('tutorials') &&
!SKIPED_ORPHAN.some((skip) => path.startsWith(skip))
) {
orphanFiles.push(path.slice(0, -3));
}
}
if (orphanFiles.length > 0) {
console.log('======== ORPHAN FILES =========\n');
orphanFiles.forEach((file) => console.warn(file));
}
const deadLinks: {path: string; links: string[]}[] = [];
for (const {content, path} of mdFiles) {
let match;
const matches = [];
while ((match = regex.exec(content)) !== null) {
const relativeLink = match[1].split('#')[0];
if (!allowedPaths.has(relativeLink) && relativeLink.startsWith('guide')) {
matches.push(relativeLink);
}
}
if (matches.length) {
deadLinks.push({path, links: matches});
}
}
if (deadLinks.length > 0) {
console.warn('\n\n======== DEAD RELATIVE LINKS =========\n');
deadLinks.forEach(({path, links}) => {
console.warn(path);
links.forEach((path) => console.log(`\t ${path}`));
});
}
if (deadLinks.length > 0 || orphanFiles.length > 0) {
throw new Error('Check fails');
}
}
function getPaths(navItem: NavigationItem): string[] {
return [
...(navItem.path ? [navItem.path] : []),
...(navItem.children ?? []).flatMap((item) => getPaths(item)),
];
}
function getContentPaths(navItem: NavigationItem): string[] {
return [
...(navItem.contentPath ? [navItem.contentPath] : []),
...(navItem.children ?? []).flatMap((item) => getContentPaths(item)),
];
}
/**
* Recursively search the provided directory for all markdown files and
* asyncronously load them.
*/
async function retrieveAllMarkdownFiles(
baseDir: string,
): Promise<{path: string; content: string}[]> {
const files = await glob('**/*.md', {
root: baseDir,
cwd: baseDir,
ignore: ['**/node_modules/**'],
});
return files.map((fullPathToFile) => ({
path: fullPathToFile,
content: readFileSync(join(baseDir, fullPathToFile), {encoding: 'utf-8'}),
}));
}
-19
View File
@@ -1,19 +0,0 @@
# Tutorial Scripts
The tutorials scripts are responsible for generating the tutorials source code and metadata by reading the tutorials content from [`src/content/tutorials`](/src/content/tutorials).
_See [the tutorials content README](/src/content/tutorials/README.md) for more information about the tutorials content._
For each tutorial, the following JSON files are generated and stored at `src/assets/tutorials`:
- `source-code/<tutorial-pathname>.json`: contains the tutorial source code in the [WebContainer](https://webcontainers.io) `FileSystemTree` format. It's
used to mount the tutorial files in the WebContainer. See the WebContainer docs for more info: <https://webcontainers.io/guides/working-with-the-file-system>
- `metadata/<tutorial-pathname>.json`: the tutorial metadata has all the information necessary to manage the tutorial in app.
It contains the tutorial `config.json`, the previous and next step, the next tutorial, and the code editor file paths and contents.
- `routes.json`: defines all routes for the tutorials, used at [`src/app/sub-navigation-data.ts`](/src/app/sub-navigation-data.ts) to populate the tutorials nav.
## Usage
```bash
yarn generate-tutorials
```
@@ -1,84 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {NavigationItem} from '@angular/docs';
import {PagePrefix} from '../../src/app/core/enums/pages';
import type {TutorialNavigationData, TutorialNavigationItem, TutorialStep} from './tutorials-types';
import {createDirectoryAndWriteFile} from './utils/filesystem';
import {TUTORIALS_ROUTES_NODE_PATH} from './utils/node-constants';
/** the step is used only in this function to sort the nav items */
export type TutorialNavigationItemWithStep = TutorialNavigationItem & {
tutorialData: TutorialNavigationData & {
step: TutorialStep['step'];
};
};
export async function buildTutorialsNavigationItems(
initialNavigationItems: TutorialNavigationItemWithStep[],
) {
const sortedInitialNavigationItems = initialNavigationItems.sort((a, b) => {
if (!a.tutorialData?.step || !b.tutorialData?.step) return 0;
if (a.tutorialData.step < b.tutorialData.step) return -1;
else if (a.tutorialData.step > b.tutorialData.step) return 1;
return 0;
});
const addTutorialPagePrefixToNavItemPath = (path: string) => `${PagePrefix.TUTORIALS}/${path}`;
const navItemMap = new Map<NavigationItem['path'], TutorialNavigationItem>();
for (const navItem of sortedInitialNavigationItems) {
const pathParts = navItem.path.split('/');
const topLevelPath = pathParts[0];
const topLevelNavItem = navItemMap.get(topLevelPath);
const isNavItemTopLevelPath = pathParts.length === 1;
const newNavItem: TutorialNavigationItem = {
path: addTutorialPagePrefixToNavItemPath(navItem.path),
label: navItem.label,
contentPath: navItem.contentPath,
tutorialData: navItem.tutorialData,
};
if (!topLevelNavItem) {
// create new map entry
navItemMap.set(
topLevelPath,
isNavItemTopLevelPath ? newNavItem : ({children: [newNavItem]} as TutorialNavigationItem),
);
} else if (isNavItemTopLevelPath) {
// add top level path to existing map entry
navItemMap.set(topLevelPath, {
...newNavItem,
...topLevelNavItem,
});
} else if (!topLevelNavItem.children) {
// create children in existing map entry
topLevelNavItem.children = [newNavItem];
navItemMap.set(topLevelPath, topLevelNavItem);
} else {
// add children to existing map entry
topLevelNavItem.children.push(newNavItem);
navItemMap.set(topLevelPath, topLevelNavItem);
}
}
const tutorialNavigationItems = Array.from(navItemMap.values());
await createDirectoryAndWriteFile(
TUTORIALS_ROUTES_NODE_PATH,
JSON.stringify(tutorialNavigationItems),
);
}
@@ -1,204 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {mkdir, rm, writeFile} from 'fs/promises';
import {basename, join, sep} from 'path';
import {
GLOB_OPTIONS,
INTRO_DIRECTORY,
PLAYGROUND_ROUTE_NODE_PATH,
STEPS_DIRECTORY,
TUTORIALS_ASSETS_NODE_PATH,
TUTORIALS_COMMON_NODE_PATH,
TUTORIALS_METADATA_NODE_PATH,
TUTORIALS_PROJECT_PATH,
TUTORIALS_SOURCE_CODE_NODE_PATH,
} from './utils/node-constants';
import {createDirectoryAndWriteFile, getFilesContents, globWithCwdPath} from './utils/filesystem';
import {
TutorialNavigationItemWithStep,
buildTutorialsNavigationItems,
} from './generate-tutorials-routes';
import {copyCommonAssetsToAppAssets, getTutorialCommonFiles} from './tutorial-type/common';
import {getHomepagePlaygroundFiles} from './tutorial-type/homepage';
import {getIntroAndStepsTutorialFiles} from './tutorial-type/intro-and-steps';
import {getTutorialPlaygroundFiles, isPlaygroundRouteData} from './tutorial-type/playground';
import {PlaygroundFiles, TutorialFiles} from './tutorials-types';
import {
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_HOMEPAGE_DIRECTORY,
TUTORIALS_PLAYGROUND_DIRECTORY,
} from './utils/web-constants';
main();
/**
* Builds and stores the tutorials source codes and metadata into JSON files.
* These files are later used to load the tutorials into the WebContainer file system
* and manage the tutorials in the embedded editor.
*
* @see https://webcontainers.io/guides/working-with-the-file-system
*/
export async function main() {
const consoleTimeLabel = 'Tutorials files build done in';
// Informs the developers that this script is running and how much time it took
console.info('Building tutorials files...');
console.time(consoleTimeLabel);
const tutorialsFiles = await getTutorialsFiles();
// Clean up the directory before writing the files
await rm(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}).catch(() => {
// `rm` throws if the directory doesn't exist, meaning this script is
// being executed for the first time
});
await Promise.all([
mkdir(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}),
mkdir(TUTORIALS_SOURCE_CODE_NODE_PATH, {recursive: true}),
mkdir(TUTORIALS_METADATA_NODE_PATH, {recursive: true}),
]);
const writeFilesPromises: Promise<void>[] = [];
const tutorialsRoutes: TutorialNavigationItemWithStep[] = [];
tutorialsFiles.forEach(({sourceCode, metadata, route, sourceCodeZip}, tutorialName) => {
if (sourceCode) {
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.json');
const fileContents = JSON.stringify(sourceCode);
if (tutorialName.includes(sep)) {
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
} else {
writeFilesPromises.push(writeFile(filePath, fileContents));
}
}
if (metadata) {
const filePath = join(TUTORIALS_METADATA_NODE_PATH, tutorialName + '.json');
const fileContents = JSON.stringify(metadata);
if (tutorialName.includes(sep)) {
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
} else {
writeFilesPromises.push(writeFile(filePath, fileContents));
}
}
if (route) {
if (isPlaygroundRouteData(route)) {
writeFilesPromises.push(
createDirectoryAndWriteFile(PLAYGROUND_ROUTE_NODE_PATH, JSON.stringify(route)),
);
} else {
tutorialsRoutes.push({
...route,
path: tutorialName,
});
}
}
if (sourceCodeZip) {
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.zip');
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, sourceCodeZip));
}
});
await Promise.all([
...writeFilesPromises,
copyCommonAssetsToAppAssets(),
buildTutorialsNavigationItems(tutorialsRoutes),
]);
console.timeEnd(consoleTimeLabel);
}
/**
* Retrieve the tutorials source code and generate a Map representing the files
* and contents that will be stored in the filesystem.
*
* Those files are later used by the `/tutorials` pages.
*
* The map key is the project name and the value contain the contents for each
* .json file.
*/
async function getTutorialsFiles(): Promise<Map<string, TutorialFiles | PlaygroundFiles>> {
const [tutorialsProjects, commonTutorialFiles] = await Promise.all([
globWithCwdPath('**', {
...GLOB_OPTIONS,
ignore: [...(GLOB_OPTIONS.ignore as string[]), '*.md', TUTORIALS_COMMON_DIRECTORY],
maxDepth: 1,
cwd: TUTORIALS_PROJECT_PATH,
}),
globWithCwdPath('**', {
...GLOB_OPTIONS,
cwd: TUTORIALS_COMMON_NODE_PATH,
nodir: true,
}),
]);
const tutorialsFiles = new Map<string, TutorialFiles | PlaygroundFiles>();
// tutorials depends on the common project files so here we handle it first
const commonFilesContents = await getFilesContents(commonTutorialFiles);
tutorialsFiles.set(
TUTORIALS_COMMON_DIRECTORY,
await getTutorialCommonFiles(commonTutorialFiles, commonFilesContents),
);
await Promise.all(
tutorialsProjects.map(async (tutorialPath) => {
const tutorialName = basename(tutorialPath);
const [tutorialDirectories, tutorialFiles] = await Promise.all([
globWithCwdPath('**/', {...GLOB_OPTIONS, cwd: tutorialPath}),
globWithCwdPath('**', {...GLOB_OPTIONS, nodir: true, cwd: tutorialPath}),
]);
try {
if (tutorialName === TUTORIALS_PLAYGROUND_DIRECTORY) {
const playgroundFiles = await getTutorialPlaygroundFiles(commonFilesContents);
playgroundFiles.forEach((playgroundFiles, playgroundName) => {
tutorialsFiles.set(playgroundName, playgroundFiles);
});
} else if (tutorialName === TUTORIALS_HOMEPAGE_DIRECTORY) {
const homepageFiles = await getHomepagePlaygroundFiles(commonFilesContents);
tutorialsFiles.set(TUTORIALS_HOMEPAGE_DIRECTORY, homepageFiles);
} else if (
tutorialDirectories.some((directory) => directory.includes(STEPS_DIRECTORY)) &&
tutorialDirectories.some((directory) => directory.includes(INTRO_DIRECTORY))
) {
const tutorialStepsFiles = await getIntroAndStepsTutorialFiles(
tutorialName,
tutorialDirectories,
tutorialFiles,
commonFilesContents,
);
tutorialStepsFiles.forEach((stepFiles, stepName) => {
tutorialsFiles.set(stepName, stepFiles);
});
} else {
throw `Could not find "${INTRO_DIRECTORY}" and/or "${STEPS_DIRECTORY}" directories.`;
}
} catch (error) {
console.error(`\n❌ Failed to parse tutorial at ${TUTORIALS_PROJECT_PATH}/${tutorialName}`);
console.error(`\t${error}\n`);
process.exit(1);
}
}),
);
return tutorialsFiles;
}
@@ -1,56 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {join} from 'path';
import {glob} from 'glob';
import {input} from '@inquirer/prompts';
import {TutorialType} from './utils/web-constants';
import {parseConfig} from './tutorials-config';
import {createDirectoryAndWriteFile} from './utils/filesystem';
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from './utils/node-constants';
import {validatePathAnswer} from './utils/cli';
createTemplate();
async function createTemplate() {
const existingTemplates = await glob('**/', {
...GLOB_OPTIONS,
absolute: false,
cwd: TUTORIALS_PLAYGROUND_NODE_PATH,
maxDepth: 1,
});
const path = await askTemplatePath(existingTemplates);
const title = await input({message: 'What is the title for the template?'});
const templatePath = `${TUTORIALS_PLAYGROUND_NODE_PATH}/${path}`;
const config = await parseConfig({
type: TutorialType.EDITOR_ONLY,
title,
openFiles: ['src/main.ts'],
});
await Promise.all([
createDirectoryAndWriteFile(join(templatePath, CONFIG_FILE), JSON.stringify(config, null, 2)),
createDirectoryAndWriteFile(join(templatePath, 'src', 'main.ts'), ''),
]);
console.info(`\n✅ Template created at ${templatePath}`);
}
function askTemplatePath(existingTemplates: string[]) {
const placeholder = 'path-to-template';
return input({
message: 'What is the path of the template?',
default: placeholder,
validate: (answer) => validatePathAnswer(answer, existingTemplates, placeholder),
});
}
-203
View File
@@ -1,203 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {join} from 'path';
import {select, input, confirm} from '@inquirer/prompts';
import {glob} from 'glob';
import {createDirectoryAndWriteFile} from './utils/filesystem';
import {
CONFIG_FILE,
CONTENT_PLACEHOLDER,
GLOB_OPTIONS,
INTRO_DIRECTORY,
STEPS_DIRECTORY,
TUTORIALS_PROJECT_PATH,
TUTORIAL_CONTENT_FILENAME,
} from './utils/node-constants';
import type {TutorialConfig} from './tutorials-types';
import {parseConfig} from './tutorials-config';
import {
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_PLAYGROUND_DIRECTORY,
TutorialType,
} from './utils/web-constants';
import {validatePathAnswer} from './utils/cli';
type TutorialFields = TutorialConfig & {
path: string;
};
type TutorialToCreate = {
intro: TutorialFields;
steps: Array<TutorialFields & {step: number}>;
};
createTutorial();
async function createTutorial() {
const existingTutorials = await glob('**/', {
ignore: [
...(GLOB_OPTIONS.ignore as string[]),
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_PLAYGROUND_DIRECTORY,
],
cwd: TUTORIALS_PROJECT_PATH,
maxDepth: 1,
});
const introPath = await askTutorialPath(existingTutorials);
const introType = await askTutorialType('What is the type of the tutorial intro?');
const introTitle = await input({message: 'What is the title for the tutorial intro?'});
const introNextTutorial = await askAboutNextTutorial(existingTutorials);
const tutorialToCreate: TutorialToCreate = {
intro: {
type: introType as any, // Note: casting to any here as a workaround for the enum type error
title: introTitle,
path: introPath,
openFiles: [''],
nextTutorial: introNextTutorial,
},
steps: [],
};
let addMoreSteps = true;
let step = 1;
while (addMoreSteps) {
console.info(`\nStep ${step}`);
const path = await askTutorialStepPath(tutorialToCreate.steps);
const type = await askTutorialType('What is the type of the step?');
const title = await input({message: 'What is the title of the step?'});
tutorialToCreate.steps.push({
step,
type: type as any, // Note: casting to any here as a workaround for the enum type error
path,
title,
openFiles: [''],
});
step++;
console.info();
addMoreSteps = await confirm({message: 'Would you like to add another step?', default: true});
}
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${tutorialToCreate.intro.path}`;
await Promise.all([
createIntroFiles(tutorialToCreate.intro),
...createStepsFiles(tutorialPath, tutorialToCreate.steps),
]);
console.info(`\n✅ Tutorial created at ${tutorialPath}`);
}
function askTutorialType(message: string = 'What is the type of the tutorial?') {
return select<TutorialConfig['type']>({
message: message,
choices: [
{
name: 'CLI only: a tutorial for the Angular CLI in-browser unsing the embedded terminal',
value: TutorialType.CLI,
},
{name: 'Local app: a tutorial for building an app locally', value: TutorialType.LOCAL},
{
name: 'Embedded editor: a tutorial for building an app in-browser using the embedded editor',
value: TutorialType.EDITOR,
},
],
});
}
function askAboutNextTutorial(
existingTutorials: string[],
message: string = 'What should be the next tutorial?',
) {
return select({
message,
choices: [
...existingTutorials.map((tutorial) => ({name: tutorial, value: tutorial})),
{name: 'None', value: undefined},
],
});
}
function askTutorialPath(existingTutorials: string[]) {
const placeholder = 'path-to-tutorial';
return input({
message: 'What is the path of the tutorial?',
default: placeholder,
validate: (answer) => validatePathAnswer(answer, existingTutorials, placeholder),
});
}
function askTutorialStepPath(createdSteps: TutorialToCreate['steps']) {
const placeholder = 'path-to-step';
const existingSteps = createdSteps.map((step) => step.path);
return input({
message: 'What is the path of the step?',
default: placeholder,
validate: (answer) => validatePathAnswer(answer, existingSteps, placeholder),
});
}
async function createIntroFiles(intro: TutorialToCreate['intro']): Promise<Promise<void>[]> {
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${intro.path}`;
const config = await parseConfig({
type: intro.type as any, // Note: casting to any here as a workaround for the enum type error
title: intro.title,
openFiles: intro.openFiles,
nextTutorial: intro.nextTutorial,
});
return [
createDirectoryAndWriteFile(
join(tutorialPath, INTRO_DIRECTORY, CONFIG_FILE),
JSON.stringify(config, null, 2),
),
createDirectoryAndWriteFile(
join(tutorialPath, INTRO_DIRECTORY, TUTORIAL_CONTENT_FILENAME),
CONTENT_PLACEHOLDER,
),
];
}
function createStepsFiles(
tutorialPath: string,
steps: TutorialToCreate['steps'],
): Promise<Promise<void>[]>[] {
const tutorialStepsPath = join(tutorialPath, STEPS_DIRECTORY);
return steps
.map(async (step) => {
const stepPath = join(tutorialStepsPath, `${step.step}-${step.path}`);
const config = await parseConfig({
type: step.type as any, // Note: casting to any here as a workaround for the enum type error
title: step.title,
openFiles: step.openFiles,
});
return [
createDirectoryAndWriteFile(`${stepPath}/${CONFIG_FILE}`, JSON.stringify(config, null, 2)),
createDirectoryAndWriteFile(
`${stepPath}/${TUTORIAL_CONTENT_FILENAME}`,
CONTENT_PLACEHOLDER,
),
];
})
.flat();
}
@@ -1,62 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {copyFile, mkdir} from 'fs/promises';
import {glob} from 'glob';
import {filesAndContentsToRecord} from '../utils/filesystem';
import {TUTORIALS_COMMON_DIRECTORY} from '../utils/web-constants';
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
import type {FileAndContent, TutorialFiles} from '../tutorials-types';
import {TUTORIALS_COMMON_ASSETS_DEST, TUTORIALS_COMMON_ASSETS_SRC} from '../utils/node-constants';
const BEFORE_COMMON_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_COMMON_DIRECTORY}\/`);
export async function getTutorialCommonFiles(
files: string[],
filesContents: FileAndContent[],
): Promise<TutorialFiles> {
const filesPath = files.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
const filesContentsWithoutPath = filesContents.map(({path, content}) => ({
path: getCleanFilePath(path),
content,
}));
const fileSystemTree = getFileSystemTree(
filesPath,
filesAndContentsToRecord(filesContentsWithoutPath),
);
return {sourceCode: fileSystemTree};
}
export function getCleanFilePath(path: string) {
return path.replace(BEFORE_COMMON_PATH_REGEX, '');
}
export async function copyCommonAssetsToAppAssets() {
const [commonAssets] = await Promise.all([
// List of files for the assets from the tutorial common directory/app.
glob(['**/*.png', '**/*.jpg'], {
cwd: TUTORIALS_COMMON_ASSETS_SRC,
nodir: true,
absolute: true,
}),
// Ensure the common directory for tutorial assets exists.
mkdir(TUTORIALS_COMMON_ASSETS_DEST, {recursive: true}),
]);
await Promise.all(
commonAssets.map((asset) =>
copyFile(asset, asset.replace(TUTORIALS_COMMON_ASSETS_SRC, TUTORIALS_COMMON_ASSETS_DEST)),
),
);
}
@@ -1,146 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename} from 'path';
import {
filesAndContentsToRecord,
getFilesContents,
getPackageJsonFromFiles,
globWithCwdPath,
} from '../utils/filesystem';
import {
GLOB_OPTIONS,
TUTORIALS_COMMON_NODE_PATH,
TUTORIALS_HOMEPAGE_NODE_PATH,
} from '../utils/node-constants';
import {getCleanFilePath as getCleanCommonFilePath} from './common';
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
import {
FileAndContent,
FileAndContentRecord,
TutorialFiles,
TutorialMetadata,
} from '../tutorials-types';
import {getAllFiles, validatePackageJson} from '../utils/metadata';
import {TUTORIALS_HOMEPAGE_DIRECTORY, TutorialType} from '../utils/web-constants';
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
const BEFORE_HOMEPAGE_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_HOMEPAGE_DIRECTORY}\/`);
export async function getHomepagePlaygroundFiles(
commonFiles: FileAndContent[],
): Promise<TutorialFiles> {
const homepagePlaygroundFiles = await globWithCwdPath('**', {
...GLOB_OPTIONS,
cwd: TUTORIALS_HOMEPAGE_NODE_PATH,
nodir: true,
});
const config = await getTutorialConfig(homepagePlaygroundFiles);
if (config.type !== TutorialType.EDITOR_ONLY)
throw new Error(`Homepage must be of type "${TutorialType.EDITOR_ONLY}"`);
const filesContents = await getFilesContents(homepagePlaygroundFiles);
const tutorialFiles = getTutorialFiles(filesContents, commonFiles);
if (config.openFiles) {
validateOpenFilesConfig(
TUTORIALS_HOMEPAGE_NODE_PATH,
config.openFiles,
Object.keys(tutorialFiles),
);
} else {
config.openFiles = Object.keys(tutorialFiles);
}
const hasPackageJson = homepagePlaygroundFiles.some((file) => basename(file) === 'package.json');
let dependencies: TutorialMetadata['dependencies'];
if (hasPackageJson) {
const packageJson = getPackageJsonFromFiles(filesContents);
dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
validatePackageJson(homepagePlaygroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
}
const allFiles = getAllFiles(
homepagePlaygroundFiles,
commonFiles.map(({path}) => path),
getCleanFilePath,
);
return {
sourceCode: getSourceCode(homepagePlaygroundFiles, filesContents),
metadata: {
type: config.type,
allFiles,
dependencies,
tutorialFiles,
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
hiddenFiles: config.openFiles
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
: [],
},
};
}
function getSourceCode(tutorialFiles: string[], filesContents: FileAndContent[]) {
// TODO: reuse this as it's duplicated in getTutorialFiles
const fileSystemTreeContents = filesContents
.filter(({path}) => shouldUseFileInWebContainer(path))
// remove steps paths from file contents
.map(({path, content}) => ({
path: getCleanFilePath(path),
content,
}));
const fileSystemTreeFiles = tutorialFiles
.filter((filename) => shouldUseFileInWebContainer(filename))
.map(getCleanFilePath);
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
}
function getTutorialFiles(
filesContents: FileAndContent[],
commonFiles: FileAndContent[],
): FileAndContentRecord {
const playgroundFilesForCodeEditor = filesContents
.filter(({path}) => shouldUseFileInWebContainer(getCleanFilePath(path)))
.map(({path, content}) => ({
path: getCleanFilePath(path),
content,
}));
const commonFilesForCodeEditor = commonFiles
.filter(({path}) => shouldUseFileInWebContainer(path))
.map(({path, content}) => ({
path: getCleanCommonFilePath(path),
content,
}));
return {
...filesAndContentsToRecord(commonFilesForCodeEditor),
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
};
}
function getCleanFilePath(path: string) {
if (path.includes(TUTORIALS_COMMON_NODE_PATH)) return getCleanCommonFilePath(path);
return path.replace(BEFORE_HOMEPAGE_PATH_REGEX, '');
}
@@ -1,402 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename, dirname, join} from 'path';
import {
filesAndContentsToRecord,
getFilesContents,
getPackageJsonFromFiles,
getZipBuffer,
globWithCwdPath,
recordToFilesAndContents,
} from '../utils/filesystem';
import {
ANSWER_DIRECTORY,
GLOB_OPTIONS,
INTRO_DIRECTORY,
STEPS_DIRECTORY,
TUTORIALS_PROJECT_PATH,
} from '../utils/node-constants';
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
import {getCleanFilePath as getCleanCommonFilePath} from './common';
import {TutorialNavigationItemWithStep} from '../generate-tutorials-routes';
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
import type {
FileAndContent,
FileAndContentRecord,
TutorialConfig,
TutorialFiles,
TutorialMetadata,
TutorialStep,
} from '../tutorials-types';
import {
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_DIST_PATH,
TUTORIALS_SOURCE_CODE_WEB_PATH,
TutorialType,
} from '../utils/web-constants';
import {getAllFiles, validatePackageJson} from '../utils/metadata';
export const TUTORIALS_DIRECTORY_REGEX = /^[a-z-0-9]*$/;
export const STEPS_DIRECTORY_REGEX = /^([0-9]|[0-9][0-9])-[A-Za-z-0-9]*$/;
const BEFORE_STEP_PATH_REGEX = new RegExp(`.*\/${STEPS_DIRECTORY}\/[^\/]+\/`);
const BEFORE_INTRO_PATH_REGEX = new RegExp(`.*\/${INTRO_DIRECTORY}\/`);
export async function getIntroAndStepsTutorialFiles(
tutorialName: string,
tutorialDirectories: string[],
tutorialFiles: string[],
commonTutorialFiles: FileAndContent[],
): Promise<Map<string, TutorialFiles>> {
const tutorialSteps = getTutorialSteps(tutorialName, tutorialDirectories);
const introAndStepsFiles = new Map<TutorialStep['url'], TutorialFiles>();
const tutorialCommonFiles = tutorialFiles.filter((file) =>
isCommonTutorialFile(tutorialName, file),
);
const introFiles = await getTutorialIntroFiles(
tutorialName,
[
...tutorialCommonFiles,
...tutorialFiles.filter((file) => file.includes(`/${INTRO_DIRECTORY}/`)),
],
tutorialSteps,
commonTutorialFiles,
);
introAndStepsFiles.set(tutorialName, introFiles);
await Promise.all(
tutorialSteps.map(async (step) => {
const stepFiles = tutorialFiles.filter((file) => file.includes(step.path));
let nextTutorial;
if (introFiles.route && 'tutorialData' in introFiles.route) {
nextTutorial = introFiles.route.tutorialData.nextTutorial;
}
const stepSourceCodeAndMetadata = await getStepFiles(
[...tutorialCommonFiles, ...stepFiles],
{
...step,
nextTutorial,
},
commonTutorialFiles,
);
introAndStepsFiles.set(step.url, stepSourceCodeAndMetadata);
}),
);
return introAndStepsFiles;
}
async function getTutorialIntroFiles(
tutorialName: string,
introTutorialFiles: string[],
tutorialSteps: TutorialStep[],
commonFilesContents: FileAndContent[],
) {
return getStepFiles(
introTutorialFiles,
{
step: 0,
name: tutorialName,
path: dirname(introTutorialFiles[0]),
nextStep: tutorialSteps.find((step) => step.step === 0 || step.step === 1)?.url, // assume the first tutorialSteps array item is the first step
url: tutorialName,
},
commonFilesContents,
);
}
async function getStepFiles(
stepFiles: string[],
step: TutorialStep,
commonFilesContents: FileAndContent[],
): Promise<TutorialFiles> {
const config = await getTutorialConfig(stepFiles);
// if src is present in the config, add files to the step files
if (config.src) {
const srcFiles = await getExternalTutorialFiles(step, config.src);
stepFiles.push(...srcFiles);
}
const filesContents = await getFilesContents(stepFiles);
const route: Omit<TutorialNavigationItemWithStep, 'path'> = {
label: config.title,
contentPath: getTutorialContentPath(stepFiles),
tutorialData: {
type: config.type,
title: config.title,
step: step.step,
nextStep: step.nextStep,
previousStep: step.previousStep,
nextTutorial: config.nextTutorial ?? step.nextTutorial,
},
};
if (config.type === TutorialType.LOCAL) {
const hasFilesToDownload =
filesContents.filter(({path}) => shouldUseFileInWebContainer(path)).length > 0;
if (!hasFilesToDownload) {
return {route};
} else {
const filesToDownload = getFilesToDownload(commonFilesContents, filesContents);
route.tutorialData.sourceCodeZipPath = join(
TUTORIALS_SOURCE_CODE_WEB_PATH,
step.url + '.zip',
);
return {
sourceCodeZip: await getZipBuffer(filesToDownload),
route,
};
}
}
const tutorialFiles = getTutorialFiles(filesContents);
if (config.openFiles) {
validateOpenFilesConfig(step.path, config.openFiles, Object.keys(tutorialFiles));
} else if (config.type === TutorialType.EDITOR) {
config.openFiles = Object.keys(tutorialFiles);
}
const hasPackageJson = stepFiles.some((file) => basename(file) === 'package.json');
let dependencies: TutorialMetadata['dependencies'];
if (hasPackageJson) {
const packageJson = getPackageJsonFromFiles(filesContents);
dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
validatePackageJson(stepFiles, packageJson, getPackageJsonFromFiles(commonFilesContents));
}
const fileSystemTreeContents = filesContents
.filter(({path}) => shouldUseFileInWebContainer(path))
.map(({path, content}) => ({path: getCleanFilePath(path), content}));
const fileSystemTreeFiles = stepFiles.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
const allFiles = getAllFiles(
stepFiles,
commonFilesContents.map(({path}) => path),
getCleanFilePath,
);
const metadata: TutorialMetadata = {
type: config.type,
allFiles,
tutorialFiles,
answerFiles: await getAnswerFiles(step, filesContents, config.answerSrc),
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
hiddenFiles: config.openFiles
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
: [],
dependencies,
};
return {
metadata,
sourceCode: getFileSystemTree(
fileSystemTreeFiles,
filesAndContentsToRecord(fileSystemTreeContents),
),
route,
};
}
export function getTutorialSteps(
tutorialName: string,
tutorialDirectories: string[],
): TutorialStep[] {
const stepsDirectories = tutorialDirectories.filter((directory) => {
const isStepDirectory = directory.includes(`${tutorialName}/${STEPS_DIRECTORY}/`);
if (!isStepDirectory) return false;
const stepPathParts = directory.split('/');
// steps directory is always right after STEPS_DIRECTORY in the path, e.g. `learn-angular/steps/1-getting-started`
const stepDirectoryIndex = stepPathParts.indexOf(STEPS_DIRECTORY) + 1;
const step = stepPathParts[stepDirectoryIndex];
return step === stepPathParts.at(-1);
});
const steps: TutorialStep[] = [];
for (const stepDirectoryPath of stepsDirectories) {
const stepDirectory = basename(stepDirectoryPath);
// force the tutorials steps to follow the pattern steps/{step}-{name}
if (!STEPS_DIRECTORY_REGEX.test(stepDirectory))
throw `Invalid step directory '${stepDirectory}'. The step directory must start with a number > 0, followed by a hyphen and a name. For example: '1-intro'`;
const [step, ...nameParts] = stepDirectory.replace(`${STEPS_DIRECTORY}/`, '').split('-');
const name = nameParts.join('-');
steps.push({
step: Number(step),
name,
path: stepDirectoryPath,
url: join(tutorialName, name),
});
}
return steps.map((step) => {
const previousStep = steps.find(({step: previousStep}) => previousStep === step.step - 1);
const nextStep = steps.find(({step: nextStep}) => nextStep === step.step + 1);
return {
...step,
nextStep: nextStep?.url,
previousStep: previousStep?.url ?? tutorialName,
};
});
}
/**
* Filter files that won't be used in the WebContainer.
*/
function getTutorialFiles(
tutorialFilesContents: FileAndContent[],
): Record<FileAndContent['path'], FileAndContent['content']> {
const tutorialFiles = tutorialFilesContents
.filter(({path}) => shouldUseFileInWebContainer(path))
.map(({path, content}) => ({
path: getCleanFilePath(path),
content,
}));
return filesAndContentsToRecord(tutorialFiles);
}
async function getAnswerFiles(
step: TutorialStep,
filesContents: FileAndContent[],
answerSrc: TutorialConfig['answerSrc'],
): Promise<FileAndContentRecord> {
const removeAnswerPathFromFiles = (filesAndContents: FileAndContent[]) =>
filesAndContents.map(({path, content}) => ({
path: path.replace(`${ANSWER_DIRECTORY}/`, ''),
content,
}));
if (answerSrc) {
const answerSrcFiles = await getExternalTutorialFiles(step, answerSrc);
const answerFiles = await getFilesContents(answerSrcFiles);
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
}
const answerFiles = filesContents.filter(({path}) => path.includes(`${ANSWER_DIRECTORY}/`));
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
}
/**
* Get files external to the current tutorial directory.
*/
async function getExternalTutorialFiles(
step: TutorialStep,
relativePath: string,
): Promise<string[]> {
const externalFilesPath = join(step.path, relativePath);
const files = await globWithCwdPath('**', {
...GLOB_OPTIONS,
cwd: externalFilesPath,
nodir: true,
});
return files;
}
function getTutorialContentPath(stepFiles: string[]) {
const tutorialContentPath = stepFiles.find((filename) => filename.endsWith('README.md'));
if (!tutorialContentPath) {
const tutorialPath = dirname(stepFiles[0]);
throw `Tutorial at ${tutorialPath} is missing a README.md for content`;
}
const contentPathWithDistPath = tutorialContentPath.replace(
TUTORIALS_PROJECT_PATH,
TUTORIALS_DIST_PATH,
);
return contentPathWithDistPath.replace(/\.[^.]+$/, '');
}
/**
* Clean the file path by making it relative to the project root.
*
* @example
* ```ts
* getCleanFilePath('path-to-tutorial/intro/src/app/app.component.ts') => 'src/app/app.component.ts'
* getCleanFilePath('path-to-tutorial/steps/1-step/src/app/app.component.ts') => 'src/app/app.component.ts'
* ```
*/
function getCleanFilePath(path: string) {
if (path.includes(TUTORIALS_COMMON_DIRECTORY)) return getCleanCommonFilePath(path);
if (path.includes(STEPS_DIRECTORY)) return path.replace(BEFORE_STEP_PATH_REGEX, '');
return path.replace(BEFORE_INTRO_PATH_REGEX, '');
}
/**
* Merge common project files with the step files to get the final files to download.
*/
function getFilesToDownload(
commonFilesContents: FileAndContent[],
stepFilesWithoutAnswer: FileAndContent[],
) {
const commonFilesWithoutPath = commonFilesContents.map(({path, content}) => ({
path: getCleanCommonFilePath(path),
content,
}));
const stepFilesWithoutPath = stepFilesWithoutAnswer
.filter(({path}) => shouldUseFileInWebContainer(path))
.map(({path, content}) => ({
path: getCleanFilePath(path),
content,
}));
// use object to easily override common files with step files
const filesToDownload = {
...filesAndContentsToRecord(commonFilesWithoutPath),
...filesAndContentsToRecord(stepFilesWithoutPath),
};
return recordToFilesAndContents(filesToDownload);
}
function isCommonTutorialFile(tutorialPath: string, filePath: string) {
return filePath.includes(join(tutorialPath, TUTORIALS_COMMON_DIRECTORY));
}
@@ -1,289 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {glob} from 'glob';
import {basename, join, sep} from 'path';
import {
filesAndContentsToRecord,
getFilesContents,
getPackageJsonFromFiles,
globWithCwdPath,
} from '../utils/filesystem';
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from '../utils/node-constants';
import {
DEFAULT_PLAYGROUND_TEMPLATE,
STARTER_PLAYGROUND_TEMPLATE,
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_PLAYGROUND_DIRECTORY,
TutorialType,
} from '../utils/web-constants';
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
import {
FileAndContent,
FileAndContentRecord,
PlaygroundFiles,
PlaygroundRouteData,
PlaygroundTemplate,
TutorialConfig,
TutorialFiles,
TutorialMetadata,
} from '../tutorials-types';
import {getAllFiles, validatePackageJson} from '../utils/metadata';
import {getCleanFilePath as getCleanCommonFilePath} from './common';
// used to avoid recreating a regex in loops
const beforePlaygroundPathRegexCache = new Map<string, RegExp>();
export async function getTutorialPlaygroundFiles(
commonFiles: FileAndContent[],
): Promise<Map<string, PlaygroundFiles>> {
const cwd = TUTORIALS_PLAYGROUND_NODE_PATH;
const [playgroundProjects, playgroundFiles] = await Promise.all([
glob('*/', {
ignore: [TUTORIALS_COMMON_DIRECTORY],
cwd,
}),
globWithCwdPath('**', {
...GLOB_OPTIONS,
nodir: true,
cwd,
}),
]);
const playgroundCommonFiles = playgroundFiles.filter(isPlaygroundCommonFile);
const playgroundFilesMap = new Map<string, PlaygroundFiles>();
const templates: PlaygroundTemplate[] = [];
await Promise.all(
playgroundProjects.map(async (project) => {
const projectFiles = playgroundFiles.filter((file) =>
file.startsWith(`${cwd}${sep}${project}${sep}`),
);
if (!projectFiles.length) {
throw new Error(
`Project at ${join(TUTORIALS_PLAYGROUND_NODE_PATH, project)} has no files.`,
);
}
const projectFilesAndPlaygroundCommonFiles = [...playgroundCommonFiles, ...projectFiles];
const projectPath = join(TUTORIALS_PLAYGROUND_DIRECTORY, project);
const {sourceCode, metadata, route} = await getPlaygroundFiles(
projectPath,
projectFilesAndPlaygroundCommonFiles,
commonFiles,
);
// store only sourceCode and metadata per playground template
playgroundFilesMap.set(projectPath, {sourceCode, metadata});
// compose playground templates from each project route object
if (route) templates.push(...route!.templates);
}),
);
const defaultTemplate = templates.find((template) =>
template.path.endsWith(DEFAULT_PLAYGROUND_TEMPLATE),
);
if (!defaultTemplate) {
throw new Error(`Invalid default playground template path '${DEFAULT_PLAYGROUND_TEMPLATE}'`);
}
const starterTemplate = templates.find((template) =>
template.path.endsWith(STARTER_PLAYGROUND_TEMPLATE),
);
if (!starterTemplate) {
throw new Error(`Invalid starter playground template path '${STARTER_PLAYGROUND_TEMPLATE}'`);
}
playgroundFilesMap.set(TUTORIALS_PLAYGROUND_DIRECTORY, {
route: {
templates: templates.sort((a, b) => {
const aPath = a.path.toLowerCase();
const bPath = b.path.toLowerCase();
if (aPath < bPath) return -1;
if (aPath > bPath) return 1;
return 0;
}),
defaultTemplate,
starterTemplate,
},
});
return playgroundFilesMap;
}
async function getPlaygroundFiles(
project: string,
playgroundFiles: string[],
commonFiles: FileAndContent[],
): Promise<PlaygroundFiles> {
const config = await getTutorialConfig(playgroundFiles);
if (config.type !== TutorialType.EDITOR_ONLY)
throw new Error(`Playground must be of type "${TutorialType.EDITOR_ONLY}"`);
const filesContents = await getFilesContents(playgroundFiles);
const projectFiles = getProjectFiles(project, config.openFiles, filesContents, commonFiles);
if (config.openFiles) {
validateOpenFilesConfig(
TUTORIALS_PLAYGROUND_NODE_PATH,
config.openFiles,
Object.keys(projectFiles),
);
}
const hasPackageJson = playgroundFiles.some((file) => basename(file) === 'package.json');
let dependencies: TutorialMetadata['dependencies'];
if (hasPackageJson) {
const packageJson = getPackageJsonFromFiles(filesContents);
dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
validatePackageJson(playgroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
}
const allFiles = getAllFiles(
playgroundFiles,
commonFiles.map(({path}) => path),
(path: string) => getCleanFilePath(project, path),
);
return {
sourceCode: getSourceCode(project, playgroundFiles, filesContents),
metadata: {
type: config.type,
allFiles,
dependencies,
tutorialFiles: projectFiles,
openFiles: config.openFiles ?? Object.keys(projectFiles),
hiddenFiles: config.openFiles
? Object.keys(projectFiles).filter((filename) => !config.openFiles!.includes(filename))
: [],
},
route: {
templates: [{path: project, label: config.title}],
},
};
}
function getProjectFiles(
project: string,
openFiles: TutorialConfig['openFiles'],
playgroundFilesContents: FileAndContent[],
commonFiles: FileAndContent[],
): FileAndContentRecord {
const playgroundFilesForCodeEditor = playgroundFilesContents
// remove files that should not be shown in the code editor
.filter(({path}) => shouldUseFileInWebContainer(path))
// remove playground path from file paths
.map(({path, content}) => ({
path: getCleanFilePath(project, path),
content,
}));
if (!openFiles) return filesAndContentsToRecord(playgroundFilesForCodeEditor);
const commonFilesForCodeEditor = commonFiles
// remove files that should not be shown in the code editor
.filter(({path}) => shouldUseFileInWebContainer(getCleanCommonFilePath(path)))
// remove common path from file paths
.map(({path, content}) => ({
path: getCleanCommonFilePath(path),
content,
}));
const commonAndPlaygroundFiles = {
...filesAndContentsToRecord(commonFilesForCodeEditor),
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
};
const openFilesConfigWithContents: FileAndContentRecord = {};
for (const openFile of openFiles) {
if (commonAndPlaygroundFiles[openFile]) {
openFilesConfigWithContents[openFile] = commonAndPlaygroundFiles[openFile];
} else {
throw `At ${TUTORIALS_PLAYGROUND_NODE_PATH}/${CONFIG_FILE}.\n\tCould not find file "${openFile}" in playground or common files`;
}
}
return openFilesConfigWithContents;
}
export function getSourceCode(
project: string,
tutorialFiles: string[],
filesContents: FileAndContent[],
) {
const fileSystemTreeContents = filesContents
// filter out config files
.filter(({path}) => shouldUseFileInWebContainer(path))
// remove steps paths from file contents
.map(({path, content}) => ({
path: getCleanFilePath(project, path),
content,
}));
const fileSystemTreeFiles = tutorialFiles
.filter(shouldUseFileInWebContainer)
.map((filename) => getCleanFilePath(project, filename));
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
}
/**
* Create a regex that matches the string before the project name in a path.
*
* This is used to remove the project name from the path, keeping only the path
* relative to the project root.
*/
function getBeforePlaygroundProjectRegex(project: string): RegExp {
const projectRegex = beforePlaygroundPathRegexCache.get(project);
if (projectRegex) return projectRegex;
const newRegex = new RegExp(`.*\/${project}\/`);
beforePlaygroundPathRegexCache.set(project, newRegex);
return newRegex;
}
function getCleanFilePath(project: string, path: string) {
if (isPlaygroundCommonFile(path)) {
return getCleanCommonFilePath(path);
}
return path.replace(getBeforePlaygroundProjectRegex(project), '');
}
export function isPlaygroundRouteData(
routeData: PlaygroundRouteData | TutorialFiles['route'],
): routeData is PlaygroundRouteData {
return (routeData as PlaygroundRouteData).templates !== undefined;
}
function isPlaygroundCommonFile(path: string) {
return path.includes(TUTORIALS_COMMON_DIRECTORY);
}
@@ -1,145 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename, join} from 'path';
import {readdir} from 'fs/promises';
import {glob} from 'glob';
import {getFileContents} from './utils/filesystem';
import {
CONFIG_FILE,
CONFIG_KEYS,
REQUIRED_CONFIGS,
TUTORIALS_PROJECT_PATH,
} from './utils/node-constants';
import type {TutorialConfig} from './tutorials-types';
import {TutorialType} from './utils/web-constants';
export async function getTutorialConfig(tutorialFiles: string[]): Promise<TutorialConfig> {
const configFilePath = tutorialFiles.find((file) => basename(file) === CONFIG_FILE);
if (!configFilePath) throw `Can't find ${CONFIG_FILE}`;
const configFileContents = await getFileContents(configFilePath);
let config: TutorialConfig;
try {
config = JSON.parse(String(configFileContents));
config = await parseConfig(config);
return config;
} catch (err) {
throw `${configFilePath.replace(`${TUTORIALS_PROJECT_PATH}/`, '')}: ${err}`;
}
}
export async function parseConfig(config: TutorialConfig): Promise<TutorialConfig> {
const tutorialConfigKeys = Object.keys(config);
REQUIRED_CONFIGS.forEach((requiredConfig) => {
if (!tutorialConfigKeys.includes(requiredConfig)) {
throw `missing required config '${requiredConfig}'`;
}
});
for (const key of tutorialConfigKeys) {
// @ts-expect-error
if (!CONFIG_KEYS.includes(key)) throw `invalid config '${key}'`;
}
if (config.nextTutorial) {
if (typeof config.nextTutorial !== 'string') {
throw '"nextTutorial" must be a string';
}
const path = join(TUTORIALS_PROJECT_PATH, config.nextTutorial);
// check if nextTutorial directory exists
try {
await readdir(path);
} catch (error) {
throw `the nextTutorial "${config.nextTutorial}" doesn't exist at ${path}`;
}
}
if (config.src) {
if (typeof config.src !== 'string') {
throw '"src" must be a string';
}
// check if src directory exists
const srcFiles = await glob(`**/${config.src}`, {cwd: TUTORIALS_PROJECT_PATH});
if (!srcFiles.length) throw `could not find "src" "${config.src}"`;
}
if (config.answerSrc) {
if (typeof config.answerSrc !== 'string') {
throw '"answerSrc" must be a string';
}
// check if answerSrc directory exists
const answerSrcFiles = await glob(`**/${config.answerSrc}`, {cwd: TUTORIALS_PROJECT_PATH});
if (!answerSrcFiles.length) throw `could not find "answerSrc" "${config.answerSrc}"`;
}
switch (config.type) {
case TutorialType.EDITOR:
assertOpenFiles(config.openFiles);
return config;
case TutorialType.CLI:
return config;
case TutorialType.LOCAL:
return config;
case TutorialType.EDITOR_ONLY:
assertOpenFiles(config.openFiles);
return config;
default:
throw 'invalid config';
}
}
function assertOpenFiles(
openFiles: TutorialConfig['openFiles'],
): asserts openFiles is string[] | undefined {
if (openFiles === undefined) return;
const errorMessage = '"openFiles" must be an array of strings';
if (!Array.isArray(openFiles)) {
throw errorMessage;
}
for (const openFile of openFiles) {
if (typeof openFile !== 'string') throw errorMessage;
}
}
/**
* Check if openFiles exist in tutorial files.
*/
export function validateOpenFilesConfig(
tutorialPath: string,
openFiles: NonNullable<TutorialConfig['openFiles']>,
tutorialFiles: string[],
) {
for (const openFile of openFiles) {
if (!tutorialFiles.includes(openFile)) {
throw `At: ${tutorialPath}/${CONFIG_FILE}\n\tCould not find "${openFile}" in the tutorial files`;
}
}
}
-152
View File
@@ -1,152 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type {FileSystemTree} from '@webcontainer/api';
import type {TutorialNavigationItemWithStep} from './generate-tutorials-routes';
import {NavigationItem} from '@angular/docs';
import {TutorialType} from './utils/web-constants';
/**
* Represents the contents of the tutorial files to be generated by the build script
*/
export type TutorialFiles = {
sourceCode?: FileSystemTree;
metadata?: TutorialMetadata;
sourceCodeZip?: Buffer;
route?: Omit<TutorialNavigationItemWithStep, 'path'>;
};
export type PlaygroundFiles = {
sourceCode?: FileSystemTree;
metadata?: TutorialMetadata;
sourceCodeZip?: undefined;
route?: PlaygroundRouteData;
};
/** Represents the contents of the tutorial config file */
export type TutorialMetadata = {
type: TutorialConfig['type'];
/** a record of all tutorials filenames and its contents */
tutorialFiles: FileAndContentRecord;
/** a record of filenames and contents for the tutorial answer */
answerFiles?: FileAndContentRecord;
/** files that are part of the project but are not visible in the code editor */
hiddenFiles: string[];
/**
* All files in the project, used to find the difference between new and old projects
* when changing projects
*/
allFiles: string[];
openFiles: NonNullable<TutorialConfig['openFiles']>;
/** whether a package.json exists */
dependencies?: Record<string, string>;
};
export type TutorialStep = {
step: number;
name: string;
path: string;
url: string;
nextStep?: TutorialStep['url'];
previousStep?: TutorialStep['url'];
nextTutorial?: string;
};
export type TutorialConfig =
| EditorTutorialConfig
| LocalTutorialConfig
| CliTutorialConfig
| EditorOnlyTutorialConfig;
export interface TutorialConfigBase {
type: TutorialType;
/** The tutorial title */
title: string;
/** The name of the tutorial folder that will be started after the current one ends. */
nextTutorial?: string;
/** The path to the tutorial src folder when it's external to the tutorial */
src?: string;
/** The path to the tutorial answer folder when it's external to the tutorial */
answerSrc?: string;
/** An array of files to be open in the editor */
openFiles?: string[];
}
/** Represents a tutorial config with all the embedded editor components enabled */
export interface EditorTutorialConfig extends TutorialConfigBase {
type: TutorialType.EDITOR;
}
/** Represents a tutorial config that won't use the embedded editor */
export interface LocalTutorialConfig extends TutorialConfigBase {
type: TutorialType.LOCAL;
// fields that must be undefined for local app tutorials
openFiles?: undefined;
src?: undefined;
answerSrc?: undefined;
}
/** Represents a tutorial config that supports only the interactive terminal for the Angular CLI */
export type CliTutorialConfig = Omit<LocalTutorialConfig, 'type'> & {
type: TutorialType.CLI;
};
export type EditorOnlyTutorialConfig = Omit<EditorTutorialConfig, 'type'> & {
type: TutorialType.EDITOR_ONLY;
};
export type FileAndContent = {
path: string;
content: string | Buffer;
};
export type FileAndContentRecord = Record<FileAndContent['path'], FileAndContent['content']>;
export type TutorialNavigationItem = {
path: NonNullable<NavigationItem['path']>;
label: NonNullable<NavigationItem['label']>;
children?: TutorialNavigationItem[];
parent?: TutorialNavigationItem;
contentPath?: string;
tutorialData: TutorialNavigationData;
};
export type TutorialNavigationData = {
type: TutorialConfig['type'];
title: TutorialConfig['title'];
nextStep?: string;
previousStep?: string;
nextTutorial?: string;
sourceCodeZipPath?: string;
};
export type PlaygroundRouteData = {
templates: PlaygroundTemplate[];
defaultTemplate?: PlaygroundTemplate;
starterTemplate?: PlaygroundTemplate;
};
export type PlaygroundTemplate = Required<Pick<NavigationItem, 'path' | 'label'>>;
// Note: only the fields being used are defined in this type
export interface PackageJson {
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
}
@@ -1,36 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {glob} from 'glob';
import {TUTORIALS_CONTENT_NODE_PATH} from './utils/node-constants';
import {dirname} from 'path';
import {execSync} from 'child_process';
main();
/**
* This script updates the package-lock.json files for all tutorials
* after manually changing the dependencies in the package.json files
*/
async function main() {
const tutorialsPackageJsons = await glob('**/package.json', {
ignore: ['**/node_modules'],
absolute: true,
cwd: TUTORIALS_CONTENT_NODE_PATH,
});
for (const path of tutorialsPackageJsons) {
const directory = dirname(path);
console.info(`\nUpdating lock file at ${directory}\n`);
execSync('npm install --package-lock-only', {
cwd: directory,
stdio: [null, null, 'inherit'],
});
}
}
-29
View File
@@ -1,29 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {TUTORIALS_DIRECTORY_REGEX} from '../tutorial-type/intro-and-steps';
export function validatePathAnswer(
answer: string,
existingPaths: string[],
placeholder: string,
): string | true {
if (answer === placeholder) {
return 'Please define the path';
}
if (!TUTORIALS_DIRECTORY_REGEX.test(answer)) {
return `The path must be in the format of "${placeholder}"`;
}
if (existingPaths.includes(answer)) {
return `'${answer}' already exists`;
}
return true;
}
@@ -1,150 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename, dirname} from 'path';
import {mkdir, readFile, writeFile} from 'fs/promises';
import {GlobOptionsWithFileTypesFalse, glob} from 'glob';
import JSZip from 'jszip';
import type {FileAndContent, FileAndContentRecord, PackageJson} from '../tutorials-types';
const fileContentsCache = new Map<string, string | Buffer>();
export async function getFileContents(path: string): Promise<string | Buffer> {
if (fileContentsCache.get(path)) {
return fileContentsCache.get(path)!;
}
const fileBuffer = await readFile(path);
if (isBufferImage(fileBuffer)) {
fileContentsCache.set(path, fileBuffer);
return fileBuffer;
}
fileContentsCache.set(path, fileBuffer.toString());
return fileBuffer.toString();
}
export async function getFilesContents(files: string[]): Promise<FileAndContent[]> {
const filesContents: FileAndContent[] = [];
await Promise.all(
files.map((path) =>
getFileContents(path).then((fileContents) => {
filesContents.push({path, content: fileContents});
}),
),
);
return filesContents;
}
export async function createDirectoryAndWriteFile(path: string, fileContents: string | Buffer) {
await mkdir(dirname(path), {recursive: true}).catch(() => {
// ignore error if directory already exists
});
await writeFile(path, fileContents);
}
export function filesAndContentsToRecord(filesAndContents: FileAndContent[]): FileAndContentRecord {
return Object.fromEntries(filesAndContents.map(({path, content}) => [path, content]));
}
export function recordToFilesAndContents(filesRecord: FileAndContentRecord): FileAndContent[] {
return Object.entries(filesRecord).map(([path, content]) => ({path, content}));
}
export async function getZipBuffer(files: FileAndContent[]): Promise<Buffer> {
const zip = new JSZip();
for (const file of files) {
zip.file(file.path, file.content, {binary: true});
}
return await zip.generateAsync({type: 'nodebuffer'});
}
export async function globWithCwdPath(
pattern: string | string[],
optionsWithCwd: Omit<GlobOptionsWithFileTypesFalse, 'cwd'> & {cwd: string},
) {
const files = await glob(pattern, optionsWithCwd);
return getFilesWithCwdPath(optionsWithCwd.cwd, files).sort();
}
export function getFilesWithCwdPath(cwdPath: string, files: string[]) {
return files.map((file) => file.substring(file.indexOf(cwdPath)));
}
function isBufferImage(buffer: Buffer) {
return isBufferPng(buffer) || isBufferJpg(buffer) || isBufferGif(buffer);
}
/**
* Check initial bytes of the buffer(aka magic numbers) to see if it's a PNG file.
*
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
*/
function isBufferPng(buffer: Buffer) {
const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
return bufferMatchInitialBytes(buffer, pngSignature);
}
/**
* Check initial bytes of the buffer(aka magic numbers) to see if it's a JPG file.
*
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
*/
function isBufferJpg(buffer: Buffer) {
const jpgSignature = [0xff, 0xd8, 0xff];
return bufferMatchInitialBytes(buffer, jpgSignature);
}
/**
* Check initial bytes of the buffer(aka magic numbers) to see if it's a GIF file.
*
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
*/
function isBufferGif(buffer: Buffer) {
const gifSignature = [0x47, 0x49, 0x46];
return bufferMatchInitialBytes(buffer, gifSignature);
}
/**
* Check if the initial bytes of a buffer matches the expected bytes.
*/
function bufferMatchInitialBytes(buffer: Buffer, expectedInitialBytes: number[]) {
for (const [index, byte] of expectedInitialBytes.entries()) {
if (buffer[index] !== byte) return false;
}
return true;
}
export function getPackageJsonFromFiles(filesAndContents: FileAndContent[]): PackageJson {
const packageJson = filesAndContents.find(({path}) => basename(path) === 'package.json');
if (!packageJson)
throw new Error(`Could not find ${dirname(filesAndContents[0].path)}/package.json`);
let parsedPackageJsonContent: PackageJson;
try {
parsedPackageJsonContent = JSON.parse(packageJson.content as string);
} catch (e) {
throw new Error(`Could not JSON.parse ${dirname(filesAndContents[0].path)}/package.json`);
}
return parsedPackageJsonContent;
}
@@ -1,74 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename, dirname} from 'path';
import {PackageJson} from '../tutorials-types';
import {shouldUseFileInWebContainer} from './webcontainers';
export function validatePackageJson(
files: string[],
projectPackageJson: PackageJson,
commonPackageJson: PackageJson,
): void {
if (!files.some((file) => basename(file) === 'package-lock.json')) {
throw new Error(
`Tutorial at ${dirname(
files[0],
)} has a package.json but is missing a package-lock.json. A lock file is required for faster installs.`,
);
}
const commonDependencies = {
...commonPackageJson.dependencies,
...commonPackageJson.devDependencies,
};
const projectDependencies = {
...projectPackageJson.dependencies,
...projectPackageJson.devDependencies,
};
const projectDependenciesNames = Object.keys(projectDependencies);
for (const dependency of Object.keys(commonDependencies)) {
if (!projectDependenciesNames.includes(dependency)) {
throw new Error(
`Tutorial at ${dirname(files[0])} is missing '${dependency}' as a dependency.`,
);
} else {
const commonDependencyVersion = commonDependencies[dependency];
const projectDependencyVersion = projectDependencies[dependency];
if (commonDependencyVersion !== projectDependencyVersion) {
throw new Error(
`${dirname(
files[0],
)} has a different version of '${dependency}' than the common package.json.
The common version is ${commonDependencyVersion}.
The project version is ${projectDependencyVersion}.
Please update the project package.json to match the common package.json, or update both.`,
);
}
}
}
}
export function getAllFiles(
projectFiles: string[],
commonFiles: string[],
getCleanFilePath: (file: string) => string,
) {
const commonAndProjectPaths = [...commonFiles, ...projectFiles]
.map((file) => getCleanFilePath(file))
.filter(shouldUseFileInWebContainer);
const uniquePaths = Array.from(new Set(commonAndProjectPaths));
return uniquePaths;
}
@@ -1,115 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {join} from 'path';
import type {TutorialConfigBase} from '../tutorials-types';
import {
TUTORIALS_ASSETS_METADATA_DIRECTORY,
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_HOMEPAGE_DIRECTORY,
TUTORIALS_PLAYGROUND_DIRECTORY,
} from './web-constants';
import {GlobOptionsWithFileTypesFalse} from 'glob';
export const CONTENT_PATH = join('projects', 'angular-dev', 'src', 'content');
export const TUTORIALS_PROJECT_PATH = join(CONTENT_PATH, 'tutorials');
export const TUTORIALS_ASSETS_NODE_PATH = join(
'projects',
'angular-dev',
'src',
'assets',
'tutorials',
);
export const TUTORIALS_PLAYGROUND_NODE_PATH = join(
TUTORIALS_PROJECT_PATH,
TUTORIALS_PLAYGROUND_DIRECTORY,
);
export const TUTORIALS_HOMEPAGE_NODE_PATH = join(
TUTORIALS_PROJECT_PATH,
TUTORIALS_HOMEPAGE_DIRECTORY,
);
export const TUTORIALS_COMMON_NODE_PATH = join(TUTORIALS_PROJECT_PATH, TUTORIALS_COMMON_DIRECTORY);
/** The common location where the tutorial assets are stored. */
export const TUTORIALS_COMMON_ASSETS_SRC = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
/** The common location for the tutorial assets to be served from. */
export const TUTORIALS_COMMON_ASSETS_DEST = join(TUTORIALS_ASSETS_NODE_PATH, 'common');
export const TUTORIALS_SOURCE_CODE_NODE_PATH = join(
TUTORIALS_ASSETS_NODE_PATH,
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
);
export const TUTORIALS_METADATA_NODE_PATH = join(
TUTORIALS_ASSETS_NODE_PATH,
TUTORIALS_ASSETS_METADATA_DIRECTORY,
);
export const TUTORIALS_CONTENT_NODE_PATH = join(
'projects',
'angular-dev',
'src',
'content',
'tutorials',
);
export const TUTORIALS_ROUTES_NODE_PATH = join(
TUTORIALS_ASSETS_NODE_PATH,
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
'tutorials.json',
);
export const PLAYGROUND_ROUTE_NODE_PATH = join(
TUTORIALS_ASSETS_NODE_PATH,
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
'playground.json',
);
export const TUTORIALS_COMMON_ASSETS_PATH = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
export const IGNORED_BASENAMES = [
'.DS_Store',
'LICENSE',
'.gitignore',
'.editorconfig',
'.gitkeep',
'favicon.ico',
];
export const IGNORED_EXTENSIONS = ['.map'];
export const IGNORED_DIRECTORIES = ['.git'];
export const TUTORIAL_CONTENT_FILENAME = 'README.md';
export const ANSWER_DIRECTORY = 'answer';
export const INTRO_DIRECTORY = 'intro';
export const STEPS_DIRECTORY = 'steps';
export const CONFIG_FILE = 'config.json';
export const CONFIG_KEYS: Array<keyof TutorialConfigBase> = [
'title',
'type',
'nextTutorial',
'src',
'answerSrc',
'openFiles',
];
export const REQUIRED_CONFIGS: Array<keyof Pick<TutorialConfigBase, 'title' | 'type'>> = [
'title',
'type',
];
export const CONTENT_PLACEHOLDER = 'Insert content here';
export const GLOB_OPTIONS: GlobOptionsWithFileTypesFalse = {
dot: false,
absolute: true,
posix: true,
ignore: [
'.',
'**/node_modules/**',
...IGNORED_BASENAMES.map((ignoredBasename) => `**/${ignoredBasename}`),
...IGNORED_EXTENSIONS.map((ext) => `**/*${ext}`),
...IGNORED_DIRECTORIES.map((dir) => `**/${dir}/**`),
],
};
@@ -1,25 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export const TUTORIALS_ASSETS_WEB_PATH = '/assets/tutorials';
export const TUTORIALS_DIST_PATH = 'tutorials';
export const TUTORIALS_PLAYGROUND_DIRECTORY = 'playground';
/** the playground template that loads by default when entering the /playground page */
export const DEFAULT_PLAYGROUND_TEMPLATE = '0-minigame';
export const STARTER_PLAYGROUND_TEMPLATE = '3-hello-world';
export const TUTORIALS_HOMEPAGE_DIRECTORY = 'homepage';
export const TUTORIALS_COMMON_DIRECTORY = 'common';
export const TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY = 'source-code';
export const TUTORIALS_ASSETS_METADATA_DIRECTORY = 'metadata';
export const TUTORIALS_ASSETS_ROUTES_DIRECTORY = 'routes';
export const TUTORIALS_SOURCE_CODE_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY}`;
export const TUTORIALS_METADATA_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_METADATA_DIRECTORY}`;
@@ -1,91 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {basename, dirname, extname} from 'path';
import {DirectoryNode, FileNode, FileSystemTree} from '@webcontainer/api';
import {ANSWER_DIRECTORY, CONFIG_FILE} from './node-constants';
import {FileAndContent, FileAndContentRecord} from '../tutorials-types';
/**
* Create a WebContainer's FileSystemTree from a list of files and its contents
*/
export function getFileSystemTree(files: string[], filesContents: FileAndContentRecord) {
const fileSystemTree: FileSystemTree = {};
for (let filepath of files) {
const dir = dirname(filepath);
const filename = basename(filepath);
if (dir === '.') {
const fileNode: FileNode = {file: {contents: filesContents[filepath]}};
fileSystemTree[filename] = fileNode;
} else {
const dirParts = dir.split('/');
buildFileSystemTree(fileSystemTree, dirParts, filename, filesContents[filepath]);
}
}
return fileSystemTree;
}
/**
* Builds a WebContainer's file system tree object recursively, mutating the
* `fileSystemTree` parameter.
*
* @see https://webcontainers.io/api#filesystemtree
*/
export function buildFileSystemTree(
fileSystemTree: FileSystemTree,
fileDirectories: string[],
filename: FileAndContent['path'],
fileContents: FileAndContent['content'],
): void {
if (fileDirectories.length === 1) {
const directory = fileDirectories[0];
const fileNode: FileNode = {file: {contents: fileContents}};
fileSystemTree[directory] = {
...fileSystemTree[directory],
directory: {
...(fileSystemTree[directory]
? (fileSystemTree[directory] as DirectoryNode).directory
: undefined),
[filename]: fileNode,
},
};
return;
}
const nextDirectory = fileDirectories.shift();
if (!nextDirectory) return;
if (!fileSystemTree[nextDirectory]) {
fileSystemTree[nextDirectory] = {directory: {}};
}
buildFileSystemTree(
(fileSystemTree[nextDirectory] as DirectoryNode).directory,
fileDirectories,
filename,
fileContents,
);
}
export function shouldUseFileInWebContainer(filename: string) {
return (
basename(filename) !== CONFIG_FILE &&
['.md', '.png', '.jpg'].includes(extname(filename)) === false &&
!filename.includes(`${ANSWER_DIRECTORY}/`)
);
}
@@ -7,7 +7,7 @@
*/
import {Injector} from '@angular/core';
import {WINDOW} from '@angular/docs';
import {ENVIRONMENT, WINDOW} from '@angular/docs';
import {AnalyticsService} from './analytics.service';
describe('AnalyticsService', () => {
@@ -36,6 +36,7 @@ describe('AnalyticsService', () => {
injector = Injector.create({
providers: [
{provide: ENVIRONMENT, useValue: {}},
{provide: AnalyticsService, deps: [WINDOW]},
{provide: WINDOW, useFactory: () => mockWindow, deps: []},
],
@@ -19,7 +19,7 @@ import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
import {CodeEditor, REQUIRED_FILES} from './code-editor.component';
import {CodeMirrorEditor} from './code-mirror-editor.service';
import {FakeChangeDetectorRef} from '../../../../../shared/src/lib/utils/test-utils.spec';
import {FakeChangeDetectorRef} from '@angular/docs/testing';
import {TutorialType} from '@angular/docs';
const files = [
@@ -8,16 +8,9 @@
import {TestBed} from '@angular/core/testing';
import type {FileSystemTree} from '@webcontainer/api';
import type {
TutorialConfig,
TutorialMetadata,
} from '@angular/docs';
import {
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_METADATA_WEB_PATH,
TUTORIALS_SOURCE_CODE_WEB_PATH,
TutorialType,
} from '@angular/docs';
import type {TutorialConfig, TutorialMetadata} from '@angular/docs';
import {TUTORIALS_ASSETS_WEB_PATH} from '../editor/constants';
import {TutorialType} from '@angular/docs';
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
describe('EmbeddedTutorialManager', () => {
@@ -252,13 +245,7 @@ describe('EmbeddedTutorialManager', () => {
},
},
};
const commonSourceCode: FileSystemTree = {
'common.ts': {
file: {
contents: 'code',
},
},
};
const metadata: TutorialMetadata = {
tutorialFiles: {'app.js': ''},
openFiles: ['app.js'],
@@ -271,14 +258,13 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock.and.returnValues(
Promise.resolve(new Response(JSON.stringify(commonSourceCode))),
Promise.resolve(new Response(JSON.stringify(tutorialSourceCode))),
Promise.resolve(new Response(JSON.stringify(metadata))),
);
await service.fetchAndSetTutorialFiles(tutorial);
expect(fetchMock).toHaveBeenCalledTimes(3);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(service.tutorialId()).toEqual(tutorial);
expect(service.tutorialFilesystemTree()).toEqual(tutorialSourceCode);
@@ -289,8 +275,6 @@ describe('EmbeddedTutorialManager', () => {
expect(service.type()).toEqual(metadata.type);
expect(service.commonFilesystemTree()).toEqual(commonSourceCode);
expect(service.hiddenFiles()).toEqual(metadata.hiddenFiles);
expect(service.answerFiles()).toEqual(metadata.answerFiles!);
});
@@ -299,15 +283,11 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
fetchMock
.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`)
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
await expectAsync(service.fetchAndSetTutorialFiles(tutorial)).toBeRejectedWithError(
@@ -315,38 +295,15 @@ describe('EmbeddedTutorialManager', () => {
);
});
it('should throw an error if the tutorial common project cannot be fetched', async () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response(null, {status: 404})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
await expectAsync(service.fetchAndSetTutorialFiles(tutorial)).toBeRejectedWithError(
'Missing source code for tutorial common',
);
});
it('should not set shouldReinstallDependencies if project did not change', async () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -368,15 +325,12 @@ describe('EmbeddedTutorialManager', () => {
it('should trigger shouldReInstallDependencies if new metadata has different dependencies', async () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -404,14 +358,10 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -437,14 +387,10 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -472,16 +418,12 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
const allFiles = ['file1.ts', 'file2.ts'];
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -507,16 +449,12 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
const allFiles = ['file1.ts', 'file2.ts'];
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -539,16 +477,12 @@ describe('EmbeddedTutorialManager', () => {
const fetchMock = spyOn(window, 'fetch');
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${TUTORIALS_COMMON_DIRECTORY}.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
fetchMock
.withArgs(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`)
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
const allFiles = ['file1.ts', 'file2.ts'];
fetchMock.withArgs(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`).and.returnValues(
fetchMock.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`).and.returnValues(
Promise.resolve(
new Response(
JSON.stringify({
@@ -13,8 +13,7 @@ import {BehaviorSubject} from 'rxjs';
import {TutorialMetadata} from '@angular/docs';
import {
TUTORIALS_COMMON_DIRECTORY,
TUTORIALS_METADATA_WEB_PATH,
TUTORIALS_SOURCE_CODE_WEB_PATH,
TUTORIALS_ASSETS_WEB_PATH
} from './constants';
/**
@@ -133,15 +132,15 @@ export class EmbeddedTutorialManager {
async fetchCommonFiles(): Promise<FileSystemTree> {
if (this.commonFilesystemTree() !== null) return this.commonFilesystemTree() as FileSystemTree;
const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
//const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
this.tutorialFilesystemTree.set(commonFiles);
//this.tutorialFilesystemTree.set(commonFiles);
return commonFiles;
return {};
}
private async fetchTutorialSourceCode(tutorial: string): Promise<FileSystemTree> {
const tutorialSourceCode = await fetch(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`);
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`);
if (!tutorialSourceCode.ok) throw new Error(`Missing source code for tutorial ${tutorial}`);
@@ -149,7 +148,7 @@ export class EmbeddedTutorialManager {
}
private async fetchTutorialMetadata(tutorial: string): Promise<TutorialMetadata> {
const tutorialSourceCode = await fetch(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`);
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`);
if (!tutorialSourceCode.ok) throw new Error(`Missing metadata for ${tutorial}`);
@@ -15,7 +15,7 @@ import {TutorialType} from '@angular/docs';
import {
FakeWebContainer,
FakeWebContainerProcess,
} from '../../../../shared/src/lib/utils/test-utils.spec';
} from '@angular/docs/testing';
import {AlertManager} from './alert-manager.service';
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
import {LoadingStep} from './enums/loading-steps';
@@ -11,7 +11,8 @@ import {By} from '@angular/platform-browser';
import {Terminal} from './terminal.component';
import {TerminalHandler, TerminalType} from './terminal-handler.service';
import {FakeEventTarget, WINDOW} from '@angular/docs';
import {WINDOW} from '@angular/docs';
import {FakeEventTarget} from '@angular/docs/testing';
describe('Terminal', () => {
let component: Terminal;
@@ -25,7 +25,7 @@ import {ClickOutside, NavigationList} from '@angular/docs';
import {injectAsync} from '../../core/services/inject-async';
import {EmbeddedTutorialManager} from '../../editor/index';
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/routes/playground.json';
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playground/routes.json';
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
@Component({
@@ -19,7 +19,7 @@ import {
EmbeddedEditor,
EmbeddedTutorialManager,
NodeRuntimeSandbox
} from '@angular/docs';
} from '../../editor';
import {mockAsyncProvider} from '../../core/services/inject-async';
import Tutorial from './tutorial.component';
@@ -214,14 +214,14 @@ export default class Tutorial implements AfterViewInit {
this.shouldRenderContent.set(routeData.type !== TutorialType.EDITOR_ONLY);
this.nextStepPath = routeData.nextStep
? `/${PagePrefix.TUTORIALS}/${routeData.nextStep}`
? `/${routeData.nextStep}`
: undefined;
this.previousStepPath = routeData.previousStep
? `/${PagePrefix.TUTORIALS}/${routeData.previousStep}`
? `/${routeData.previousStep}`
: undefined;
this.nextTutorialPath.set(
routeData.nextTutorial ? `/${PagePrefix.TUTORIALS}/${routeData.nextTutorial}` : null,
routeData.nextTutorial ? `/${routeData.nextTutorial}` : null,
);
}
+4 -2
View File
@@ -8,7 +8,8 @@
import {NavigationItem} from '@angular/docs';
import TUTORIALS_SUB_NAVIGATION_DATA_JSON from '../../src/assets/tutorials/routes/tutorials.json';
import FIRST_APP_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/first-app/routes.json';
import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/learn-angular/routes.json';
import {DefaultPage} from './core/enums/pages';
import {getApiNavigationItems} from './features/references/helpers/manifest.helper';
@@ -937,7 +938,8 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
];
export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
...TUTORIALS_SUB_NAVIGATION_DATA_JSON,
FIRST_APP_TUTORIAL_NAV_DATA,
LEARN_ANGULAR_TUTORIAL_NAV_DATA,
{
path: DefaultPage.TUTORIALS,
contentPath: 'tutorials/home',
+96
View File
@@ -0,0 +1,96 @@
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
package(default_visibility = ["//adev:__subpackages__"])
copy_to_directory(
name = "content",
srcs = [
"//adev/src/content/best-practices",
"//adev/src/content/best-practices/runtime-performance",
"//adev/src/content/ecosystem",
"//adev/src/content/ecosystem/service-workers",
"//adev/src/content/guide",
"//adev/src/content/guide/animations",
"//adev/src/content/guide/components",
"//adev/src/content/guide/di",
"//adev/src/content/guide/directives",
"//adev/src/content/guide/forms",
"//adev/src/content/guide/http",
"//adev/src/content/guide/i18n",
"//adev/src/content/guide/ngmodules",
"//adev/src/content/guide/pipes",
"//adev/src/content/guide/routing",
"//adev/src/content/guide/signals",
"//adev/src/content/guide/templates",
"//adev/src/content/guide/testing",
"//adev/src/content/introduction",
"//adev/src/content/introduction/essentials",
"//adev/src/content/reference",
"//adev/src/content/reference/configs",
"//adev/src/content/reference/errors",
"//adev/src/content/reference/extended-diagnostics",
"//adev/src/content/reference/migrations",
"//adev/src/content/tools",
"//adev/src/content/tools/cli",
"//adev/src/content/tools/libraries",
"//adev/src/content/tutorials",
"//adev/src/content/tutorials/first-app:first-app-guides",
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
"//packages/animations:animations_docs",
"//packages/animations/browser:animations_browser_docs",
"//packages/animations/browser/testing:animations_browser_testing_docs",
"//packages/common:common_docs",
"//packages/common/http:http_docs",
"//packages/common/http/testing:http_testing_docs",
"//packages/common/testing:common_testing_docs",
"//packages/common/upgrade:common_upgrade_docs",
"//packages/core:core_docs",
"//packages/core/rxjs-interop:core_rxjs-interop_docs",
"//packages/core/testing:core_testing_docs",
"//packages/elements:elements_docs",
"//packages/forms:forms_docs",
"//packages/localize:localize_docs",
"//packages/platform-browser:platform-browser_docs",
"//packages/platform-browser-dynamic:platform-browser_dynamic_docs",
"//packages/platform-browser-dynamic/testing:platform-browser_dynamic_testing_docs",
"//packages/platform-browser/animations:platform-browser_animations_docs",
"//packages/platform-browser/animations/async:platform-browser_animations_async_docs",
"//packages/platform-browser/testing:platform-browser_testing_docs",
"//packages/platform-server:platform-server_docs",
"//packages/platform-server/testing:platform-server_testing_docs",
"//packages/router:router_docs",
"//packages/router/testing:router_testing_docs",
"//packages/router/upgrade:router_upgrade_docs",
"//packages/service-worker:service-worker_docs",
"//packages/upgrade:upgrade_docs",
"//packages/upgrade/static:upgrade_static_docs",
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
],
replace_prefixes = {
"adev/src/content": "",
"packages/**/": "api/",
},
)
copy_to_directory(
name = "tutorials",
srcs = [
"//adev/src/content/tutorials/first-app",
"//adev/src/content/tutorials/homepage",
"//adev/src/content/tutorials/learn-angular",
"//adev/src/content/tutorials/playground",
],
replace_prefixes = {
"adev/**/tutorials/*/": "",
},
)
copy_to_directory(
name = "api",
srcs = [
"//packages:docs_api_manifest",
],
replace_prefixes = {
"**/": "",
},
)
-736
View File
@@ -1,736 +0,0 @@
{
"@angular/animations": [
{"name": "AnimationEvent", "type": "interface", "isDeprecated": false},
{"name": "AnimationPlayer", "type": "interface", "isDeprecated": false},
{"name": "AnimateTimings", "type": "type_alias", "isDeprecated": false},
{"name": "AnimationBuilder", "type": "undecorated_class", "isDeprecated": false},
{"name": "AnimationOptions", "type": "interface", "isDeprecated": false},
{"name": "AnimateChildOptions", "type": "interface", "isDeprecated": false},
{"name": "AnimationMetadataType", "type": "enum", "isDeprecated": false},
{"name": "AnimationFactory", "type": "undecorated_class", "isDeprecated": false},
{"name": "NoopAnimationPlayer", "type": "undecorated_class", "isDeprecated": false},
{"name": "AUTO_STYLE", "type": "constant", "isDeprecated": false},
{"name": "AnimationMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationTriggerMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationStateMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationTransitionMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationReferenceMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationQueryMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationKeyframesSequenceMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationStyleMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationAnimateMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationAnimateChildMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationAnimateRefMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationSequenceMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationGroupMetadata", "type": "interface", "isDeprecated": false},
{"name": "AnimationQueryOptions", "type": "interface", "isDeprecated": false},
{"name": "AnimationStaggerMetadata", "type": "interface", "isDeprecated": false},
{"name": "trigger", "type": "function", "isDeprecated": false},
{"name": "animate", "type": "function", "isDeprecated": false},
{"name": "group", "type": "function", "isDeprecated": false},
{"name": "sequence", "type": "function", "isDeprecated": false},
{"name": "style", "type": "function", "isDeprecated": false},
{"name": "state", "type": "function", "isDeprecated": false},
{"name": "keyframes", "type": "function", "isDeprecated": false},
{"name": "transition", "type": "function", "isDeprecated": false},
{"name": "animation", "type": "function", "isDeprecated": false},
{"name": "animateChild", "type": "function", "isDeprecated": false},
{"name": "useAnimation", "type": "function", "isDeprecated": false},
{"name": "query", "type": "function", "isDeprecated": false},
{"name": "stagger", "type": "function", "isDeprecated": false}
],
"@angular/animations/browser": [
{"name": "NoopAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
{"name": "AnimationDriver", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/animations/browser/testing": [
{"name": "MockAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
{"name": "MockAnimationPlayer", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/common": [
{"name": "DatePipeConfig", "type": "interface", "isDeprecated": false},
{"name": "XhrFactory", "type": "undecorated_class", "isDeprecated": false},
{"name": "registerLocaleData", "type": "function", "isDeprecated": false},
{"name": "JsonPipe", "type": "pipe", "isDeprecated": false},
{"name": "ViewportScroller", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgIf", "type": "undecorated_class", "isDeprecated": false},
{"name": "I18nSelectPipe", "type": "pipe", "isDeprecated": false},
{"name": "LowerCasePipe", "type": "pipe", "isDeprecated": false},
{"name": "SlicePipe", "type": "pipe", "isDeprecated": false},
{"name": "NgLocalization", "type": "undecorated_class", "isDeprecated": false},
{"name": "CommonModule", "type": "ng_module", "isDeprecated": false},
{"name": "NgTemplateOutlet", "type": "undecorated_class", "isDeprecated": false},
{"name": "PlatformLocation", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgStyle", "type": "undecorated_class", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "NgComponentOutlet", "type": "undecorated_class", "isDeprecated": false},
{"name": "isPlatformBrowser", "type": "function", "isDeprecated": false},
{"name": "NgPlural", "type": "directive", "isDeprecated": false},
{"name": "NumberFormatStyle", "type": "enum", "isDeprecated": false},
{"name": "ImageLoaderConfig", "type": "interface", "isDeprecated": false},
{"name": "KeyValue", "type": "interface", "isDeprecated": false},
{"name": "I18nPluralPipe", "type": "pipe", "isDeprecated": false},
{"name": "PopStateEvent", "type": "interface", "isDeprecated": false},
{"name": "LocationStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "HashLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgForOfContext", "type": "undecorated_class", "isDeprecated": false},
{"name": "DOCUMENT", "type": "constant", "isDeprecated": false},
{"name": "DecimalPipe", "type": "pipe", "isDeprecated": false},
{"name": "Location", "type": "undecorated_class", "isDeprecated": false},
{"name": "isPlatformServer", "type": "function", "isDeprecated": false},
{"name": "KeyValuePipe", "type": "undecorated_class", "isDeprecated": false},
{"name": "Plural", "type": "enum", "isDeprecated": false},
{"name": "provideCloudflareLoader", "type": "constant", "isDeprecated": false},
{"name": "DATE_PIPE_DEFAULT_TIMEZONE", "type": "constant", "isDeprecated": true},
{"name": "isPlatformWorkerApp", "type": "function", "isDeprecated": true},
{"name": "ImageLoader", "type": "type_alias", "isDeprecated": false},
{"name": "NgFor", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgForOf", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideImgixLoader", "type": "constant", "isDeprecated": false},
{"name": "FormStyle", "type": "enum", "isDeprecated": false},
{"name": "provideImageKitLoader", "type": "constant", "isDeprecated": false},
{"name": "NgSwitch", "type": "directive", "isDeprecated": false},
{"name": "provideCloudinaryLoader", "type": "constant", "isDeprecated": false},
{"name": "isPlatformWorkerUi", "type": "function", "isDeprecated": true},
{"name": "NgLocaleLocalization", "type": "undecorated_class", "isDeprecated": false},
{"name": "PRECONNECT_CHECK_BLOCKLIST", "type": "constant", "isDeprecated": false},
{"name": "NgClass", "type": "undecorated_class", "isDeprecated": false},
{"name": "formatDate", "type": "function", "isDeprecated": false},
{"name": "TranslationWidth", "type": "enum", "isDeprecated": false},
{"name": "IMAGE_LOADER", "type": "constant", "isDeprecated": false},
{"name": "FormatWidth", "type": "enum", "isDeprecated": false},
{"name": "DATE_PIPE_DEFAULT_OPTIONS", "type": "constant", "isDeprecated": false},
{"name": "DatePipe", "type": "pipe", "isDeprecated": false},
{"name": "AsyncPipe", "type": "undecorated_class", "isDeprecated": false},
{"name": "APP_BASE_HREF", "type": "constant", "isDeprecated": false},
{"name": "PathLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgPluralCase", "type": "undecorated_class", "isDeprecated": false},
{"name": "LOCATION_INITIALIZED", "type": "constant", "isDeprecated": false},
{"name": "LocationChangeEvent", "type": "interface", "isDeprecated": false},
{"name": "NumberSymbol", "type": "enum", "isDeprecated": false},
{"name": "LocationChangeListener", "type": "interface", "isDeprecated": false},
{"name": "BrowserPlatformLocation", "type": "undecorated_class", "isDeprecated": false},
{"name": "PercentPipe", "type": "pipe", "isDeprecated": false},
{"name": "NgOptimizedImage", "type": "undecorated_class", "isDeprecated": false},
{"name": "formatCurrency", "type": "function", "isDeprecated": false},
{"name": "WeekDay", "type": "enum", "isDeprecated": false},
{"name": "getLocaleId", "type": "function", "isDeprecated": false},
{"name": "getLocaleDayPeriods", "type": "function", "isDeprecated": false},
{"name": "CurrencyPipe", "type": "pipe", "isDeprecated": false},
{"name": "formatPercent", "type": "function", "isDeprecated": false},
{"name": "NgSwitchCase", "type": "undecorated_class", "isDeprecated": false},
{"name": "getLocaleDayNames", "type": "function", "isDeprecated": false},
{"name": "getLocaleMonthNames", "type": "function", "isDeprecated": false},
{"name": "formatNumber", "type": "function", "isDeprecated": false},
{"name": "getLocaleEraNames", "type": "function", "isDeprecated": false},
{"name": "NgSwitchDefault", "type": "undecorated_class", "isDeprecated": false},
{"name": "getLocaleFirstDayOfWeek", "type": "function", "isDeprecated": false},
{"name": "NgIfContext", "type": "undecorated_class", "isDeprecated": false},
{"name": "getLocaleWeekEndRange", "type": "function", "isDeprecated": false},
{"name": "getLocaleDateFormat", "type": "function", "isDeprecated": false},
{"name": "getLocaleTimeFormat", "type": "function", "isDeprecated": false},
{"name": "TitleCasePipe", "type": "pipe", "isDeprecated": false},
{"name": "getLocaleDateTimeFormat", "type": "function", "isDeprecated": false},
{"name": "getLocaleNumberSymbol", "type": "function", "isDeprecated": false},
{"name": "UpperCasePipe", "type": "pipe", "isDeprecated": false},
{"name": "getLocaleNumberFormat", "type": "function", "isDeprecated": false},
{"name": "getLocaleCurrencySymbol", "type": "function", "isDeprecated": false},
{"name": "getLocaleCurrencyName", "type": "function", "isDeprecated": false},
{"name": "getLocaleCurrencyCode", "type": "function", "isDeprecated": false},
{"name": "getLocalePluralCase", "type": "constant", "isDeprecated": false},
{"name": "getLocaleExtraDayPeriodRules", "type": "function", "isDeprecated": false},
{"name": "getLocaleExtraDayPeriods", "type": "function", "isDeprecated": false},
{"name": "getLocaleDirection", "type": "function", "isDeprecated": false},
{"name": "Time", "type": "type_alias", "isDeprecated": false},
{"name": "getCurrencySymbol", "type": "function", "isDeprecated": false},
{"name": "getNumberOfCurrencyDigits", "type": "function", "isDeprecated": false}
],
"@angular/common/http": [
{"name": "HttpContextToken", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpParameterCodec", "type": "interface", "isDeprecated": false},
{"name": "HttpEventType", "type": "enum", "isDeprecated": false},
{"name": "HttpHeaders", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpHandler", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpContext", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpUrlEncodingCodec", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpClientXsrfModule", "type": "ng_module", "isDeprecated": false},
{"name": "HttpInterceptor", "type": "interface", "isDeprecated": false},
{"name": "HttpTransferCacheOptions", "type": "type_alias", "isDeprecated": false},
{"name": "HttpBackend", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpFeatureKind", "type": "enum", "isDeprecated": false},
{"name": "HttpProgressEvent", "type": "interface", "isDeprecated": false},
{"name": "FetchBackend", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpXsrfTokenExtractor", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpFeature", "type": "interface", "isDeprecated": false},
{"name": "HttpXhrBackend", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpDownloadProgressEvent", "type": "interface", "isDeprecated": false},
{"name": "provideHttpClient", "type": "function", "isDeprecated": false},
{"name": "HttpUploadProgressEvent", "type": "interface", "isDeprecated": false},
{"name": "HttpClient", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpSentEvent", "type": "interface", "isDeprecated": false},
{"name": "HttpRequest", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpClientModule", "type": "ng_module", "isDeprecated": false},
{"name": "HttpUserEvent", "type": "interface", "isDeprecated": false},
{"name": "JsonpClientBackend", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpHandlerFn", "type": "type_alias", "isDeprecated": false},
{"name": "HttpEvent", "type": "type_alias", "isDeprecated": false},
{"name": "HttpClientJsonpModule", "type": "ng_module", "isDeprecated": false},
{"name": "HttpResponseBase", "type": "undecorated_class", "isDeprecated": false},
{"name": "HttpParamsOptions", "type": "interface", "isDeprecated": false},
{"name": "HttpInterceptorFn", "type": "type_alias", "isDeprecated": false},
{"name": "withInterceptors", "type": "function", "isDeprecated": false},
{"name": "HttpParams", "type": "undecorated_class", "isDeprecated": false},
{"name": "withInterceptorsFromDi", "type": "function", "isDeprecated": false},
{"name": "HttpHeaderResponse", "type": "undecorated_class", "isDeprecated": false},
{"name": "withXsrfConfiguration", "type": "function", "isDeprecated": false},
{"name": "HttpResponse", "type": "undecorated_class", "isDeprecated": false},
{"name": "withNoXsrfProtection", "type": "function", "isDeprecated": false},
{"name": "withJsonpSupport", "type": "function", "isDeprecated": false},
{"name": "withRequestsMadeViaParent", "type": "function", "isDeprecated": false},
{"name": "HTTP_INTERCEPTORS", "type": "constant", "isDeprecated": false},
{"name": "HttpErrorResponse", "type": "undecorated_class", "isDeprecated": false},
{"name": "withFetch", "type": "function", "isDeprecated": false},
{"name": "HttpStatusCode", "type": "enum", "isDeprecated": false},
{"name": "JsonpInterceptor", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/common/testing": [
{"name": "MockLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "SpyLocation", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideLocationMocks", "type": "function", "isDeprecated": false},
{"name": "MockPlatformLocationConfig", "type": "interface", "isDeprecated": false},
{"name": "MOCK_PLATFORM_LOCATION_CONFIG", "type": "constant", "isDeprecated": false},
{"name": "MockPlatformLocation", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/common/upgrade": [
{"name": "UrlCodec", "type": "undecorated_class", "isDeprecated": false},
{"name": "LocationUpgradeConfig", "type": "interface", "isDeprecated": false},
{"name": "$locationShim", "type": "undecorated_class", "isDeprecated": false},
{"name": "LOCATION_UPGRADE_CONFIGURATION", "type": "constant", "isDeprecated": false},
{"name": "LocationUpgradeModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "AngularJSUrlCodec", "type": "undecorated_class", "isDeprecated": false},
{"name": "$locationShimProvider", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/core": [
{"name": "SecurityContext", "type": "enum", "isDeprecated": false},
{"name": "SchemaMetadata", "type": "interface", "isDeprecated": false},
{"name": "ViewEncapsulation", "type": "enum", "isDeprecated": false},
{"name": "Version", "type": "undecorated_class", "isDeprecated": false},
{"name": "ChangeDetectionStrategy", "type": "enum", "isDeprecated": false},
{"name": "PipeTransform", "type": "interface", "isDeprecated": false},
{"name": "ErrorHandler", "type": "undecorated_class", "isDeprecated": false},
{"name": "DoBootstrap", "type": "interface", "isDeprecated": false},
{"name": "RendererType2", "type": "interface", "isDeprecated": false},
{"name": "ApplicationConfig", "type": "interface", "isDeprecated": false},
{"name": "Signal", "type": "type_alias", "isDeprecated": false},
{"name": "untracked", "type": "function", "isDeprecated": false},
{"name": "ViewRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "ApplicationModule", "type": "ng_module", "isDeprecated": false},
{"name": "Sanitizer", "type": "undecorated_class", "isDeprecated": false},
{"name": "ProviderToken", "type": "type_alias", "isDeprecated": false},
{"name": "CreateComputedOptions", "type": "interface", "isDeprecated": false},
{"name": "assertNotInReactiveContext", "type": "function", "isDeprecated": false},
{"name": "InjectionToken", "type": "undecorated_class", "isDeprecated": false},
{"name": "ForwardRefFn", "type": "interface", "isDeprecated": false},
{"name": "WritableSignal", "type": "interface", "isDeprecated": false},
{"name": "platformCore", "type": "constant", "isDeprecated": false},
{"name": "ENVIRONMENT_INITIALIZER", "type": "constant", "isDeprecated": false},
{"name": "NgModuleRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "KeyValueDiffer", "type": "interface", "isDeprecated": false},
{"name": "DestroyRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "StateKey", "type": "type_alias", "isDeprecated": false},
{"name": "getModuleFactory", "type": "function", "isDeprecated": true},
{"name": "RendererFactory2", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgIterable", "type": "type_alias", "isDeprecated": false},
{"name": "ComponentRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "QueryList", "type": "undecorated_class", "isDeprecated": false},
{"name": "computed", "type": "function", "isDeprecated": false},
{"name": "createComponent", "type": "function", "isDeprecated": false},
{"name": "mergeApplicationConfig", "type": "function", "isDeprecated": false},
{"name": "InjectableProvider", "type": "type_alias", "isDeprecated": false},
{"name": "isSignal", "type": "function", "isDeprecated": false},
{"name": "INJECTOR", "type": "constant", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "runInInjectionContext", "type": "function", "isDeprecated": false},
{"name": "IterableDiffer", "type": "interface", "isDeprecated": false},
{"name": "ChangeDetectorRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "Injector", "type": "undecorated_class", "isDeprecated": false},
{"name": "AfterRenderPhase", "type": "enum", "isDeprecated": false},
{"name": "forwardRef", "type": "function", "isDeprecated": false},
{"name": "EffectCleanupFn", "type": "type_alias", "isDeprecated": false},
{"name": "makeStateKey", "type": "function", "isDeprecated": false},
{"name": "CUSTOM_ELEMENTS_SCHEMA", "type": "constant", "isDeprecated": false},
{"name": "TemplateRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "ValueEqualityFn", "type": "type_alias", "isDeprecated": false},
{"name": "NgZone", "type": "undecorated_class", "isDeprecated": false},
{"name": "ModuleWithComponentFactories", "type": "undecorated_class", "isDeprecated": true},
{"name": "ComponentFactoryResolver", "type": "undecorated_class", "isDeprecated": true},
{"name": "DefaultIterableDiffer", "type": "undecorated_class", "isDeprecated": true},
{"name": "getNgModuleById", "type": "function", "isDeprecated": false},
{"name": "createNgModule", "type": "function", "isDeprecated": false},
{"name": "ElementRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "CreateSignalOptions", "type": "interface", "isDeprecated": false},
{"name": "EmbeddedViewRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "DebugEventListener", "type": "undecorated_class", "isDeprecated": false},
{"name": "IterableChanges", "type": "interface", "isDeprecated": false},
{"name": "NO_ERRORS_SCHEMA", "type": "constant", "isDeprecated": false},
{"name": "asNativeElements", "type": "function", "isDeprecated": false},
{"name": "APP_ID", "type": "constant", "isDeprecated": false},
{"name": "signal", "type": "function", "isDeprecated": false},
{"name": "KeyValueChanges", "type": "interface", "isDeprecated": false},
{"name": "TransferState", "type": "undecorated_class", "isDeprecated": false},
{"name": "Compiler", "type": "undecorated_class", "isDeprecated": true},
{"name": "RendererStyleFlags2", "type": "enum", "isDeprecated": false},
{"name": "Renderer2", "type": "undecorated_class", "isDeprecated": false},
{"name": "makeEnvironmentProviders", "type": "function", "isDeprecated": false},
{"name": "DebugNode", "type": "undecorated_class", "isDeprecated": false},
{"name": "Attribute", "type": "decorator", "isDeprecated": false},
{"name": "Inject", "type": "decorator", "isDeprecated": false},
{"name": "PLATFORM_INITIALIZER", "type": "constant", "isDeprecated": false},
{"name": "Query", "type": "undecorated_class", "isDeprecated": false},
{"name": "ImportProvidersSource", "type": "type_alias", "isDeprecated": false},
{"name": "PLATFORM_ID", "type": "constant", "isDeprecated": false},
{"name": "createNgModuleRef", "type": "constant", "isDeprecated": true},
{"name": "NgModuleFactory", "type": "undecorated_class", "isDeprecated": true},
{"name": "assertInInjectionContext", "type": "function", "isDeprecated": false},
{"name": "importProvidersFrom", "type": "function", "isDeprecated": false},
{"name": "resolveForwardRef", "type": "function", "isDeprecated": false},
{"name": "PACKAGE_ROOT_URL", "type": "constant", "isDeprecated": true},
{"name": "Testability", "type": "undecorated_class", "isDeprecated": false},
{"name": "KeyValueChangeRecord", "type": "interface", "isDeprecated": false},
{"name": "LOCALE_ID", "type": "constant", "isDeprecated": false},
{"name": "ComponentFactory", "type": "undecorated_class", "isDeprecated": true},
{"name": "ANIMATION_MODULE_TYPE", "type": "constant", "isDeprecated": false},
{"name": "Optional", "type": "decorator", "isDeprecated": false},
{"name": "KeyValueDifferFactory", "type": "interface", "isDeprecated": false},
{"name": "ViewContainerRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "AfterRenderOptions", "type": "interface", "isDeprecated": false},
{"name": "CSP_NONCE", "type": "constant", "isDeprecated": false},
{"name": "Injectable", "type": "decorator", "isDeprecated": false},
{"name": "EnvironmentInjector", "type": "undecorated_class", "isDeprecated": false},
{"name": "KeyValueDiffers", "type": "undecorated_class", "isDeprecated": false},
{"name": "IterableChangeRecord", "type": "interface", "isDeprecated": false},
{"name": "DebugElement", "type": "undecorated_class", "isDeprecated": false},
{"name": "AfterRenderRef", "type": "interface", "isDeprecated": false},
{"name": "ComponentMirror", "type": "interface", "isDeprecated": false},
{"name": "DEFAULT_CURRENCY_CODE", "type": "constant", "isDeprecated": false},
{"name": "APP_INITIALIZER", "type": "constant", "isDeprecated": false},
{"name": "CompilerOptions", "type": "type_alias", "isDeprecated": false},
{"name": "APP_BOOTSTRAP_LISTENER", "type": "constant", "isDeprecated": false},
{"name": "TrackByFunction", "type": "interface", "isDeprecated": false},
{"name": "Self", "type": "decorator", "isDeprecated": false},
{"name": "ApplicationInitStatus", "type": "undecorated_class", "isDeprecated": false},
{"name": "COMPILER_OPTIONS", "type": "constant", "isDeprecated": false},
{"name": "inject", "type": "function", "isDeprecated": false},
{"name": "EventEmitter", "type": "constant", "isDeprecated": false},
{"name": "CompilerFactory", "type": "undecorated_class", "isDeprecated": true},
{"name": "TRANSLATIONS", "type": "constant", "isDeprecated": false},
{"name": "reflectComponentType", "type": "function", "isDeprecated": false},
{"name": "SkipSelf", "type": "decorator", "isDeprecated": false},
{"name": "TRANSLATIONS_FORMAT", "type": "constant", "isDeprecated": false},
{"name": "MissingTranslationStrategy", "type": "enum", "isDeprecated": false},
{"name": "afterRender", "type": "function", "isDeprecated": false},
{"name": "ContentChildren", "type": "decorator", "isDeprecated": false},
{"name": "IterableDifferFactory", "type": "interface", "isDeprecated": false},
{"name": "NgModule", "type": "decorator", "isDeprecated": false},
{"name": "EffectRef", "type": "interface", "isDeprecated": false},
{"name": "Host", "type": "decorator", "isDeprecated": false},
{"name": "createEnvironmentInjector", "type": "function", "isDeprecated": false},
{"name": "IterableDiffers", "type": "undecorated_class", "isDeprecated": false},
{"name": "CreateEffectOptions", "type": "interface", "isDeprecated": false},
{"name": "NgProbeToken", "type": "undecorated_class", "isDeprecated": true},
{"name": "createPlatform", "type": "function", "isDeprecated": false},
{"name": "effect", "type": "function", "isDeprecated": false},
{"name": "afterNextRender", "type": "function", "isDeprecated": false},
{"name": "TestabilityRegistry", "type": "undecorated_class", "isDeprecated": false},
{"name": "ContentChild", "type": "decorator", "isDeprecated": false},
{"name": "GetTestability", "type": "interface", "isDeprecated": false},
{"name": "setTestabilityGetter", "type": "function", "isDeprecated": false},
{"name": "Directive", "type": "decorator", "isDeprecated": false},
{"name": "createPlatformFactory", "type": "function", "isDeprecated": false},
{"name": "ViewChildren", "type": "decorator", "isDeprecated": false},
{"name": "assertPlatform", "type": "function", "isDeprecated": false},
{"name": "destroyPlatform", "type": "function", "isDeprecated": false},
{"name": "getPlatform", "type": "function", "isDeprecated": false},
{"name": "NgZoneOptions", "type": "interface", "isDeprecated": false},
{"name": "ViewChild", "type": "decorator", "isDeprecated": false},
{"name": "isStandalone", "type": "function", "isDeprecated": false},
{"name": "BootstrapOptions", "type": "interface", "isDeprecated": false},
{"name": "PlatformRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "Component", "type": "decorator", "isDeprecated": false},
{"name": "Pipe", "type": "decorator", "isDeprecated": false},
{"name": "getDebugNode", "type": "function", "isDeprecated": false},
{"name": "Predicate", "type": "interface", "isDeprecated": false},
{"name": "Input", "type": "decorator", "isDeprecated": false},
{"name": "ApplicationRef", "type": "undecorated_class", "isDeprecated": false},
{"name": "Output", "type": "decorator", "isDeprecated": false},
{"name": "HostBinding", "type": "decorator", "isDeprecated": false},
{"name": "HostListener", "type": "decorator", "isDeprecated": false},
{"name": "provideZoneChangeDetection", "type": "function", "isDeprecated": false},
{"name": "@if", "type": "block", "isDeprecated": false},
{"name": "@for", "type": "block", "isDeprecated": false},
{"name": "@switch", "type": "block", "isDeprecated": false},
{"name": "@defer", "type": "block", "isDeprecated": false},
{"name": "ng-content", "type": "element", "isDeprecated": false},
{"name": "ng-template", "type": "element", "isDeprecated": false},
{"name": "ng-container", "type": "element", "isDeprecated": false}
],
"@angular/core/rxjs-interop": [
{"name": "ToObservableOptions", "type": "interface", "isDeprecated": false},
{"name": "takeUntilDestroyed", "type": "function", "isDeprecated": false},
{"name": "ToSignalOptions", "type": "interface", "isDeprecated": false},
{"name": "toObservable", "type": "function", "isDeprecated": false},
{"name": "toSignal", "type": "function", "isDeprecated": false}
],
"@angular/core/testing": [
{"name": "waitForAsync", "type": "function", "isDeprecated": false},
{"name": "MetadataOverride", "type": "type_alias", "isDeprecated": false},
{"name": "ComponentFixture", "type": "undecorated_class", "isDeprecated": false},
{"name": "resetFakeAsyncZone", "type": "function", "isDeprecated": false},
{"name": "DeferBlockFixture", "type": "undecorated_class", "isDeprecated": false},
{"name": "TestComponentRenderer", "type": "undecorated_class", "isDeprecated": false},
{"name": "fakeAsync", "type": "function", "isDeprecated": false},
{"name": "ComponentFixtureAutoDetect", "type": "constant", "isDeprecated": false},
{"name": "ComponentFixtureNoNgZone", "type": "constant", "isDeprecated": false},
{"name": "TestModuleMetadata", "type": "interface", "isDeprecated": false},
{"name": "async", "type": "function", "isDeprecated": true},
{"name": "tick", "type": "function", "isDeprecated": false},
{"name": "TestBedStatic", "type": "interface", "isDeprecated": false},
{"name": "TestEnvironmentOptions", "type": "interface", "isDeprecated": false},
{"name": "ModuleTeardownOptions", "type": "interface", "isDeprecated": false},
{"name": "flush", "type": "function", "isDeprecated": false},
{"name": "discardPeriodicTasks", "type": "function", "isDeprecated": false},
{"name": "flushMicrotasks", "type": "function", "isDeprecated": false},
{"name": "getTestBed", "type": "function", "isDeprecated": false},
{"name": "TestBed", "type": "constant", "isDeprecated": false},
{"name": "inject", "type": "function", "isDeprecated": false},
{"name": "InjectSetupWrapper", "type": "undecorated_class", "isDeprecated": false},
{"name": "withModule", "type": "function", "isDeprecated": false}
],
"@angular/elements": [
{"name": "NgElementStrategyEvent", "type": "interface", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "NgElementStrategy", "type": "interface", "isDeprecated": false},
{"name": "NgElementConstructor", "type": "interface", "isDeprecated": false},
{"name": "NgElementStrategyFactory", "type": "interface", "isDeprecated": false},
{"name": "NgElement", "type": "undecorated_class", "isDeprecated": false},
{"name": "WithProperties", "type": "type_alias", "isDeprecated": false},
{"name": "NgElementConfig", "type": "interface", "isDeprecated": false},
{"name": "createCustomElement", "type": "function", "isDeprecated": false}
],
"@angular/forms": [
{"name": "ControlValueAccessor", "type": "interface", "isDeprecated": false},
{"name": "ControlContainer", "type": "undecorated_class", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "NgControl", "type": "undecorated_class", "isDeprecated": false},
{"name": "Form", "type": "interface", "isDeprecated": false},
{"name": "AbstractFormGroupDirective", "type": "directive", "isDeprecated": false},
{"name": "AbstractControlDirective", "type": "undecorated_class", "isDeprecated": false},
{"name": "FormControlState", "type": "interface", "isDeprecated": false},
{"name": "RangeValueAccessor", "type": "directive", "isDeprecated": false},
{"name": "FormsModule", "type": "ng_module", "isDeprecated": false},
{"name": "CheckboxControlValueAccessor", "type": "directive", "isDeprecated": false},
{"name": "NumberValueAccessor", "type": "directive", "isDeprecated": false},
{"name": "FormControlOptions", "type": "interface", "isDeprecated": false},
{"name": "NgModelGroup", "type": "directive", "isDeprecated": false},
{"name": "SelectControlValueAccessor", "type": "directive", "isDeprecated": false},
{"name": "FormGroupName", "type": "directive", "isDeprecated": false},
{"name": "COMPOSITION_BUFFER_MODE", "type": "constant", "isDeprecated": false},
{"name": "DefaultValueAccessor", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgForm", "type": "directive", "isDeprecated": false},
{"name": "ValidationErrors", "type": "type_alias", "isDeprecated": false},
{"name": "FormControlDirective", "type": "directive", "isDeprecated": false},
{"name": "FormArray", "type": "undecorated_class", "isDeprecated": false},
{"name": "SelectMultipleControlValueAccessor", "type": "directive", "isDeprecated": false},
{"name": "FormGroupDirective", "type": "directive", "isDeprecated": false},
{"name": "FormControlName", "type": "directive", "isDeprecated": false},
{"name": "Validator", "type": "interface", "isDeprecated": false},
{"name": "SetDisabledStateOption", "type": "type_alias", "isDeprecated": false},
{"name": "FormControlStatus", "type": "type_alias", "isDeprecated": false},
{"name": "ReactiveFormsModule", "type": "ng_module", "isDeprecated": false},
{"name": "FormGroup", "type": "undecorated_class", "isDeprecated": false},
{"name": "ControlConfig", "type": "type_alias", "isDeprecated": false},
{"name": "NG_VALIDATORS", "type": "constant", "isDeprecated": false},
{"name": "NgModel", "type": "directive", "isDeprecated": false},
{"name": "NgControlStatus", "type": "directive", "isDeprecated": false},
{"name": "NgControlStatusGroup", "type": "directive", "isDeprecated": false},
{"name": "RadioControlValueAccessor", "type": "undecorated_class", "isDeprecated": false},
{"name": "NG_ASYNC_VALIDATORS", "type": "constant", "isDeprecated": false},
{"name": "FormArrayName", "type": "directive", "isDeprecated": false},
{"name": "AbstractControlOptions", "type": "interface", "isDeprecated": false},
{"name": "FormBuilder", "type": "undecorated_class", "isDeprecated": false},
{"name": "Validators", "type": "undecorated_class", "isDeprecated": false},
{"name": "NgSelectOption", "type": "undecorated_class", "isDeprecated": false},
{"name": "MaxValidator", "type": "directive", "isDeprecated": false},
{"name": "NG_VALUE_ACCESSOR", "type": "constant", "isDeprecated": false},
{"name": "MinValidator", "type": "directive", "isDeprecated": false},
{"name": "AsyncValidator", "type": "interface", "isDeprecated": false},
{"name": "RequiredValidator", "type": "directive", "isDeprecated": false},
{"name": "CheckboxRequiredValidator", "type": "directive", "isDeprecated": false},
{"name": "AbstractControl", "type": "undecorated_class", "isDeprecated": false},
{"name": "EmailValidator", "type": "directive", "isDeprecated": false},
{"name": "FormControl", "type": "constant", "isDeprecated": false},
{"name": "ValidatorFn", "type": "interface", "isDeprecated": false},
{"name": "AsyncValidatorFn", "type": "interface", "isDeprecated": false},
{"name": "MinLengthValidator", "type": "directive", "isDeprecated": false},
{"name": "NonNullableFormBuilder", "type": "undecorated_class", "isDeprecated": false},
{"name": "MaxLengthValidator", "type": "directive", "isDeprecated": false},
{"name": "PatternValidator", "type": "directive", "isDeprecated": false},
{"name": "UntypedFormBuilder", "type": "undecorated_class", "isDeprecated": false},
{"name": "UntypedFormControl", "type": "constant", "isDeprecated": false},
{"name": "isFormControl", "type": "constant", "isDeprecated": false},
{"name": "UntypedFormArray", "type": "constant", "isDeprecated": false},
{"name": "isFormArray", "type": "constant", "isDeprecated": false},
{"name": "UntypedFormGroup", "type": "constant", "isDeprecated": false},
{"name": "isFormGroup", "type": "constant", "isDeprecated": false},
{"name": "FormRecord", "type": "undecorated_class", "isDeprecated": false},
{"name": "isFormRecord", "type": "constant", "isDeprecated": false}
],
"@angular/localize": [
{"name": "loadTranslations", "type": "function", "isDeprecated": false},
{"name": "clearTranslations", "type": "function", "isDeprecated": false}
],
"@angular/platform-browser": [
{"name": "By", "type": "undecorated_class", "isDeprecated": false},
{"name": "MetaDefinition", "type": "type_alias", "isDeprecated": false},
{"name": "enableDebugTools", "type": "function", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "Title", "type": "undecorated_class", "isDeprecated": false},
{"name": "EVENT_MANAGER_PLUGINS", "type": "constant", "isDeprecated": false},
{"name": "EventManager", "type": "undecorated_class", "isDeprecated": false},
{"name": "HydrationFeatureKind", "type": "enum", "isDeprecated": false},
{"name": "HydrationFeature", "type": "interface", "isDeprecated": false},
{"name": "makeStateKey", "type": "constant", "isDeprecated": true},
{"name": "disableDebugTools", "type": "function", "isDeprecated": false},
{"name": "SafeValue", "type": "interface", "isDeprecated": false},
{"name": "Meta", "type": "undecorated_class", "isDeprecated": false},
{"name": "SafeHtml", "type": "interface", "isDeprecated": false},
{"name": "SafeStyle", "type": "interface", "isDeprecated": false},
{"name": "withNoHttpTransferCache", "type": "function", "isDeprecated": false},
{"name": "HAMMER_GESTURE_CONFIG", "type": "constant", "isDeprecated": false},
{"name": "SafeScript", "type": "interface", "isDeprecated": false},
{"name": "ApplicationConfig", "type": "type_alias", "isDeprecated": true},
{"name": "HammerLoader", "type": "type_alias", "isDeprecated": false},
{"name": "SafeUrl", "type": "interface", "isDeprecated": false},
{"name": "REMOVE_STYLES_ON_COMPONENT_DESTROY", "type": "constant", "isDeprecated": false},
{"name": "SafeResourceUrl", "type": "interface", "isDeprecated": false},
{"name": "HAMMER_LOADER", "type": "constant", "isDeprecated": false},
{"name": "withHttpTransferCacheOptions", "type": "function", "isDeprecated": false},
{"name": "bootstrapApplication", "type": "function", "isDeprecated": false},
{"name": "DomSanitizer", "type": "undecorated_class", "isDeprecated": false},
{"name": "TransferState", "type": "constant", "isDeprecated": false},
{"name": "HammerGestureConfig", "type": "undecorated_class", "isDeprecated": false},
{"name": "StateKey", "type": "type_alias", "isDeprecated": true},
{"name": "EventManagerPlugin", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideClientHydration", "type": "function", "isDeprecated": false},
{"name": "createApplication", "type": "function", "isDeprecated": false},
{"name": "provideProtractorTestingSupport", "type": "function", "isDeprecated": false},
{"name": "platformBrowser", "type": "constant", "isDeprecated": false},
{"name": "BrowserModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "HammerModule", "type": "ng_module", "isDeprecated": false}
],
"@angular/platform-browser-dynamic": [
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "JitCompilerFactory", "type": "undecorated_class", "isDeprecated": true},
{"name": "RESOURCE_CACHE_PROVIDER", "type": "constant", "isDeprecated": true},
{"name": "platformBrowserDynamic", "type": "constant", "isDeprecated": false}
],
"@angular/platform-browser-dynamic/testing": [
{"name": "platformBrowserDynamicTesting", "type": "constant", "isDeprecated": false},
{"name": "BrowserDynamicTestingModule", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/platform-browser/animations": [
{"name": "BrowserAnimationsModuleConfig", "type": "interface", "isDeprecated": false},
{"name": "BrowserAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideAnimations", "type": "function", "isDeprecated": false},
{"name": "NoopAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideNoopAnimations", "type": "function", "isDeprecated": false}
],
"@angular/platform-browser/testing": [
{"name": "platformBrowserTesting", "type": "constant", "isDeprecated": false},
{"name": "BrowserTestingModule", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/platform-server": [
{"name": "PlatformConfig", "type": "interface", "isDeprecated": false},
{"name": "PlatformState", "type": "undecorated_class", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "provideServerRendering", "type": "function", "isDeprecated": false},
{"name": "ServerTransferStateModule", "type": "ng_module", "isDeprecated": true},
{"name": "INITIAL_CONFIG", "type": "constant", "isDeprecated": false},
{"name": "BEFORE_APP_SERIALIZED", "type": "constant", "isDeprecated": false},
{"name": "ServerModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "platformServer", "type": "constant", "isDeprecated": false},
{"name": "platformDynamicServer", "type": "constant", "isDeprecated": true},
{"name": "renderModule", "type": "function", "isDeprecated": false},
{"name": "renderApplication", "type": "function", "isDeprecated": false}
],
"@angular/platform-server/testing": [
{"name": "platformServerTesting", "type": "constant", "isDeprecated": false},
{"name": "ServerTestingModule", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/router": [
{"name": "UrlHandlingStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "mapToCanMatch", "type": "function", "isDeprecated": false},
{"name": "VERSION", "type": "constant", "isDeprecated": false},
{"name": "PRIMARY_OUTLET", "type": "constant", "isDeprecated": false},
{"name": "OutletContext", "type": "undecorated_class", "isDeprecated": false},
{"name": "DetachedRouteHandle", "type": "type_alias", "isDeprecated": false},
{"name": "TitleStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "IsActiveMatchOptions", "type": "interface", "isDeprecated": false},
{"name": "OnSameUrlNavigation", "type": "type_alias", "isDeprecated": false},
{"name": "createUrlTreeFromSnapshot", "type": "function", "isDeprecated": false},
{"name": "RouterState", "type": "undecorated_class", "isDeprecated": false},
{"name": "PreloadingStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterLinkActive", "type": "undecorated_class", "isDeprecated": false},
{"name": "ChildrenOutletContexts", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterLink", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterLinkWithHref", "type": "undecorated_class", "isDeprecated": false},
{"name": "EventType", "type": "enum", "isDeprecated": false},
{"name": "InitialNavigation", "type": "type_alias", "isDeprecated": false},
{"name": "ViewTransitionsFeatureOptions", "type": "interface", "isDeprecated": false},
{"name": "Params", "type": "type_alias", "isDeprecated": false},
{"name": "mapToCanActivate", "type": "function", "isDeprecated": false},
{"name": "RouterOutletContract", "type": "interface", "isDeprecated": false},
{"name": "RouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "PreloadAllModules", "type": "undecorated_class", "isDeprecated": false},
{"name": "ParamMap", "type": "interface", "isDeprecated": false},
{"name": "ROUTES", "type": "constant", "isDeprecated": false},
{"name": "RouterEvent", "type": "undecorated_class", "isDeprecated": false},
{"name": "mapToCanActivateChild", "type": "function", "isDeprecated": false},
{"name": "NoPreloading", "type": "undecorated_class", "isDeprecated": false},
{"name": "ViewTransitionInfo", "type": "interface", "isDeprecated": false},
{"name": "provideRouter", "type": "function", "isDeprecated": false},
{"name": "RouterPreloader", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterConfigOptions", "type": "interface", "isDeprecated": false},
{"name": "mapToCanDeactivate", "type": "function", "isDeprecated": false},
{"name": "BaseRouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "DeprecatedGuard", "type": "type_alias", "isDeprecated": true},
{"name": "NavigationStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "UrlCreationOptions", "type": "interface", "isDeprecated": false},
{"name": "mapToResolve", "type": "function", "isDeprecated": false},
{"name": "Router", "type": "undecorated_class", "isDeprecated": false},
{"name": "DefaultTitleStrategy", "type": "undecorated_class", "isDeprecated": false},
{"name": "Routes", "type": "type_alias", "isDeprecated": false},
{"name": "convertToParamMap", "type": "function", "isDeprecated": false},
{"name": "RouterModule", "type": "ng_module", "isDeprecated": false},
{"name": "defaultUrlMatcher", "type": "function", "isDeprecated": false},
{"name": "UrlMatchResult", "type": "type_alias", "isDeprecated": false},
{"name": "RouterFeature", "type": "interface", "isDeprecated": false},
{"name": "ActivatedRoute", "type": "undecorated_class", "isDeprecated": false},
{"name": "UrlMatcher", "type": "type_alias", "isDeprecated": false},
{"name": "RouterOutlet", "type": "directive", "isDeprecated": false},
{"name": "NavigationEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideRoutes", "type": "function", "isDeprecated": true},
{"name": "Data", "type": "type_alias", "isDeprecated": false},
{"name": "ResolveData", "type": "type_alias", "isDeprecated": false},
{"name": "NavigationCancellationCode", "type": "enum", "isDeprecated": false},
{"name": "DefaultExport", "type": "interface", "isDeprecated": false},
{"name": "InMemoryScrollingOptions", "type": "interface", "isDeprecated": false},
{"name": "InMemoryScrollingFeature", "type": "type_alias", "isDeprecated": false},
{"name": "LoadChildrenCallback", "type": "type_alias", "isDeprecated": false},
{"name": "withInMemoryScrolling", "type": "function", "isDeprecated": false},
{"name": "NavigationExtras", "type": "interface", "isDeprecated": false},
{"name": "NavigationSkippedCode", "type": "enum", "isDeprecated": false},
{"name": "NavigationCancel", "type": "undecorated_class", "isDeprecated": false},
{"name": "Navigation", "type": "interface", "isDeprecated": false},
{"name": "LoadChildren", "type": "type_alias", "isDeprecated": false},
{"name": "QueryParamsHandling", "type": "type_alias", "isDeprecated": false},
{"name": "UrlTree", "type": "undecorated_class", "isDeprecated": false},
{"name": "ExtraOptions", "type": "interface", "isDeprecated": false},
{"name": "RunGuardsAndResolvers", "type": "type_alias", "isDeprecated": false},
{"name": "NavigationSkipped", "type": "undecorated_class", "isDeprecated": false},
{"name": "Route", "type": "interface", "isDeprecated": false},
{"name": "NavigationError", "type": "undecorated_class", "isDeprecated": false},
{"name": "UrlSegmentGroup", "type": "undecorated_class", "isDeprecated": false},
{
"name": "EnabledBlockingInitialNavigationFeature",
"type": "type_alias",
"isDeprecated": false
},
{"name": "ROUTER_INITIALIZER", "type": "constant", "isDeprecated": false},
{"name": "RoutesRecognized", "type": "undecorated_class", "isDeprecated": false},
{"name": "InitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
{"name": "UrlSegment", "type": "undecorated_class", "isDeprecated": false},
{"name": "GuardsCheckStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "withEnabledBlockingInitialNavigation", "type": "function", "isDeprecated": false},
{"name": "ROUTER_CONFIGURATION", "type": "constant", "isDeprecated": false},
{"name": "ActivatedRouteSnapshot", "type": "undecorated_class", "isDeprecated": false},
{"name": "GuardsCheckEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "ResolveStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "UrlSerializer", "type": "undecorated_class", "isDeprecated": false},
{"name": "DisabledInitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
{"name": "ResolveEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "DefaultUrlSerializer", "type": "undecorated_class", "isDeprecated": false},
{"name": "withDisabledInitialNavigation", "type": "function", "isDeprecated": false},
{"name": "RouteConfigLoadStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouteConfigLoadEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "DebugTracingFeature", "type": "type_alias", "isDeprecated": false},
{"name": "ChildActivationStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "withDebugTracing", "type": "function", "isDeprecated": false},
{"name": "ChildActivationEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterStateSnapshot", "type": "undecorated_class", "isDeprecated": false},
{"name": "ActivationStart", "type": "undecorated_class", "isDeprecated": false},
{"name": "ActivationEnd", "type": "undecorated_class", "isDeprecated": false},
{"name": "PreloadingFeature", "type": "type_alias", "isDeprecated": false},
{"name": "withPreloading", "type": "function", "isDeprecated": false},
{"name": "Scroll", "type": "undecorated_class", "isDeprecated": false},
{"name": "Event", "type": "type_alias", "isDeprecated": false},
{"name": "RouterConfigurationFeature", "type": "type_alias", "isDeprecated": false},
{"name": "withRouterConfig", "type": "function", "isDeprecated": false},
{"name": "RouterHashLocationFeature", "type": "type_alias", "isDeprecated": false},
{"name": "withHashLocation", "type": "function", "isDeprecated": false},
{"name": "NavigationErrorHandlerFeature", "type": "type_alias", "isDeprecated": false},
{"name": "withNavigationErrorHandler", "type": "function", "isDeprecated": false},
{"name": "ViewTransitionsFeature", "type": "type_alias", "isDeprecated": false},
{"name": "withComponentInputBinding", "type": "function", "isDeprecated": false},
{"name": "CanActivate", "type": "interface", "isDeprecated": true},
{"name": "withViewTransitions", "type": "function", "isDeprecated": false},
{"name": "RouterFeatures", "type": "type_alias", "isDeprecated": false},
{"name": "CanActivateFn", "type": "type_alias", "isDeprecated": false},
{"name": "CanActivateChild", "type": "interface", "isDeprecated": true},
{"name": "CanActivateChildFn", "type": "type_alias", "isDeprecated": false},
{"name": "CanDeactivate", "type": "interface", "isDeprecated": true},
{"name": "CanDeactivateFn", "type": "type_alias", "isDeprecated": false},
{"name": "CanMatch", "type": "interface", "isDeprecated": true},
{"name": "CanMatchFn", "type": "type_alias", "isDeprecated": false},
{"name": "Resolve", "type": "interface", "isDeprecated": true},
{"name": "ResolveFn", "type": "type_alias", "isDeprecated": false},
{"name": "CanLoad", "type": "interface", "isDeprecated": true},
{"name": "CanLoadFn", "type": "type_alias", "isDeprecated": true},
{"name": "NavigationBehaviorOptions", "type": "interface", "isDeprecated": false}
],
"@angular/router/testing": [
{"name": "RouterTestingModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "RouterTestingHarness", "type": "undecorated_class", "isDeprecated": false}
],
"@angular/router/upgrade": [
{"name": "RouterUpgradeInitializer", "type": "constant", "isDeprecated": false},
{"name": "locationSyncBootstrapListener", "type": "function", "isDeprecated": false},
{"name": "setUpLocationSync", "type": "function", "isDeprecated": false}
],
"@angular/service-worker": [
{"name": "SwUpdate", "type": "undecorated_class", "isDeprecated": false},
{"name": "ServiceWorkerModule", "type": "ng_module", "isDeprecated": false},
{"name": "SwPush", "type": "undecorated_class", "isDeprecated": false},
{"name": "NoNewVersionDetectedEvent", "type": "interface", "isDeprecated": false},
{"name": "VersionDetectedEvent", "type": "interface", "isDeprecated": false},
{"name": "VersionInstallationFailedEvent", "type": "interface", "isDeprecated": false},
{"name": "VersionReadyEvent", "type": "interface", "isDeprecated": false},
{"name": "VersionEvent", "type": "type_alias", "isDeprecated": false},
{"name": "UnrecoverableStateEvent", "type": "interface", "isDeprecated": false},
{"name": "SwRegistrationOptions", "type": "undecorated_class", "isDeprecated": false},
{"name": "provideServiceWorker", "type": "function", "isDeprecated": false}
],
"@angular/upgrade": [
{"name": "UpgradeAdapter", "type": "undecorated_class", "isDeprecated": true},
{"name": "UpgradeAdapterRef", "type": "undecorated_class", "isDeprecated": true}
],
"@angular/upgrade/static": [
{"name": "UpgradeModule", "type": "undecorated_class", "isDeprecated": false},
{"name": "downgradeModule", "type": "function", "isDeprecated": false},
{"name": "UpgradeComponent", "type": "directive", "isDeprecated": false}
],
"@angular/upgrade/static/testing": [
{"name": "createAngularJSTestingModule", "type": "function", "isDeprecated": false},
{"name": "createAngularTestingModule", "type": "function", "isDeprecated": false}
]
}
-2
View File
@@ -1,2 +0,0 @@
**/*
!.gitignore
+13
View File
@@ -0,0 +1,13 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
exports_files(
glob(["*"]),
)
copy_to_bin(
name = "icons",
srcs = glob(["*"]),
visibility = [
"//visibility:public",
],
)
+13
View File
@@ -0,0 +1,13 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
exports_files(
glob(["*"]),
)
copy_to_bin(
name = "images",
srcs = glob(["*"]),
visibility = [
"//visibility:public",
],
)
+15
View File
@@ -0,0 +1,15 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
# TODO(josephperrott): Generate previews from source instead of using already generated files
exports_files(
glob(["*"]),
)
copy_to_bin(
name = "previews",
srcs = glob(["*"]),
visibility = [
"//visibility:public",
],
)
+22
View File
@@ -0,0 +1,22 @@
/*!
* @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
*/
/**
******************************************************************************
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
******************************************************************************
*/
import {Type} from '@angular/core';
/**
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
* Keys has to be equal to paths written down in the docs markdown files.
*/
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
};
+13
View File
@@ -0,0 +1,13 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
exports_files(
glob(["*"]),
)
copy_to_bin(
name = "textures",
srcs = glob(["*"]),
visibility = [
"//visibility:public",
],
)
-18
View File
@@ -1,18 +0,0 @@
{
"currentVersion": "stable",
"historicalVersionsLinkPattern": "https://v{{version}}.angular.dev",
"mainVersions": [
{
"version": "stable",
"url": "https://angular.dev"
},
{
"version": "rc",
"url": "https://rc.angular.dev"
},
{
"version": "next",
"url": "https://next.angular.dev"
}
]
}
@@ -29,17 +29,14 @@ describe('testability example', () => {
let waitWithResultScript = function (done: any) {
let rootEl = document.querySelector('example-app');
let testability = window.getAngularTestability(rootEl);
testability.whenStable((didWork: boolean, tasks: any) => {
done(tasks);
testability.whenStable(() => {
done();
}, 1000);
};
element(by.css('.start-button')).click();
browser.driver.executeAsyncScript(waitWithResultScript).then((result: any[]) => {
let pendingTask = result[0];
expect(pendingTask.data.delay).toEqual(5000);
expect(pendingTask.source).toEqual('setTimeout');
browser.driver.executeAsyncScript(waitWithResultScript).then(() => {
expect(element(by.css('.status')).getText()).not.toContain('done');
done();
});
@@ -72,4 +72,5 @@ generate_guides(
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts",
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -5,4 +5,5 @@ generate_guides(
srcs = glob([
"*.md",
]),
visibility = ["//adev:__subpackages__"],
)
+1
View File
@@ -5,4 +5,5 @@ generate_guides(
srcs = glob([
"*.md",
]),
visibility = ["//adev:__subpackages__"],
)
@@ -11,4 +11,5 @@ generate_guides(
"//adev/src/content/examples/service-worker-getting-started:src/app/log-update.service.ts",
"//adev/src/content/examples/service-worker-getting-started:src/app/prompt-update.service.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -78,7 +78,7 @@ For example, imagine the following scenario:
1. After some time, a new version of the application is deployed to the server.
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.
IMPORANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
IMPORTANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache.
Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
@@ -110,7 +110,7 @@ Make a change to the application, and watch the service worker install the updat
<docs-code language="shell">
ng build
npx http-server -p 8080 -c-1 dist/
npx http-server -p 8080 -c-1 dist/&lt;project-name&gt;/browser
</docs-code>
+1
View File
@@ -15,4 +15,5 @@ generate_guides(
"//adev/src/content/examples/security:src/app/inner-html-binding.component.ts",
"//adev/src/content/examples/ssr:server.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -31,4 +31,5 @@ generate_guides(
"//adev/src/content/examples/animations:src/app/open-close.component.ts",
"//adev/src/content/examples/animations:src/app/status-slider.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -8,4 +8,5 @@ generate_guides(
data = [
"//adev/src/assets/images:components.svg",
],
visibility = ["//adev:__subpackages__"],
)
+4 -1
View File
@@ -240,10 +240,13 @@ In the example below, the prefetching starts when a browser becomes idle and the
## Testing
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests are in "paused" (`@placeholder`) state, so that you can manually transition between states.
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests will play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to `Manual` in the TestBed configuration.
```typescript
it('should render a defer block in different states', async () => {
// configures the defer block behavior to start in "paused" state for manual control.
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
@Component({
// ...
template: `
+1
View File
@@ -9,4 +9,5 @@ generate_guides(
"//adev/src/assets/images:dependency_injection.svg",
"//adev/src/content/examples/resolution-modifiers:src/app/self/self.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -11,7 +11,7 @@ The injection context is available in these situations:
* In the `factory` function specified for an `InjectionToken`.
* Within a stack frame that is run in a injection context.
Knowing when your are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
Knowing when you are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
## Class constructors
@@ -33,6 +33,7 @@ class MyComponent {
Some APIs are designed to be run in an injection context. This is the case, for example, of the router guards. It allows the use of [`inject`](api/core/inject) to access a service within the guard function.
Here is an example for `CanActivateFn`
<docs-code language="typescript" highlight="[3]">
const canActivateTeam: CanActivateFn =
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
@@ -12,9 +12,6 @@ With hierarchical dependency injection, you can isolate sections of the applicat
## Types of injector hierarchies
Injectors in Angular have rules that you can leverage to achieve the desired visibility of injectables in your applications.
By understanding these rules, you can determine whether to declare a provider at the application level, in a Component, or in a Directive.
Angular has two injector hierarchies:
| Injector hierarchies | Details |
@@ -22,4 +22,5 @@ generate_guides(
"//adev/src/content/examples/built-in-directives:src/app/item-switch.component.ts",
"//adev/src/content/examples/structural-directives:src/app/app.component.html",
],
visibility = ["//adev:__subpackages__"],
)
+1
View File
@@ -49,4 +49,5 @@ generate_guides(
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.html",
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
+1
View File
@@ -5,4 +5,5 @@ generate_guides(
srcs = glob([
"*.md",
]),
visibility = ["//adev:__subpackages__"],
)
+1 -1
View File
@@ -50,7 +50,7 @@ const configPromise = firstValueFrom(config$);
const req = httpTesting.expectOne('/api/config', 'Request to load the configuration');
// We can assert various properties of the request if desired.
expect(req.method).toBe('GET');
expect(req.request.method).toBe('GET');
// Flushing the request causes it to complete, delivering the result.
req.flush(DEFAULT_CONFIG);
+1
View File
@@ -19,4 +19,5 @@ generate_guides(
"//adev/src/content/examples/i18n:src/app/app.component.ts",
"//adev/src/content/examples/i18n:src/main.ts",
],
visibility = ["//adev:__subpackages__"],
)
+3 -3
View File
@@ -5,7 +5,7 @@ Angular uses the Unicode *locale identifier* \(Unicode locale ID\) to find the c
<docs-callout title="Unicode locale ID">
* A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification][UnicodeCldrDevelopmentCoreSpecification].
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798].
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationLocaleIDs].
* CLDR and Angular use [BCP 47 tags][RfcEditorInfoBcp47] as the base for the locale ID
@@ -61,6 +61,6 @@ To change the source locale of your project for the build, complete the followin
[RfcEditorInfoBcp47]: https://www.rfc-editor.org/info/bcp47 "BCP 47 | RFC Editor"
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/development/core-specification "Core Specification | Unicode CLDR Project"
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/index/cldr-spec "Core Specification | Unicode CLDR Project"
[UnicodeCldrDevelopmentCoreSpecificationHVgyyng33o798]: https://cldr.unicode.org/development/core-specification#h.vgyyng33o798 "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
[UnicodeCldrDevelopmentCoreSpecificationLocaleID]: https://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
@@ -7,4 +7,5 @@ generate_guides(
]),
data = [
],
visibility = ["//adev:__subpackages__"],
)
+1
View File
@@ -14,4 +14,5 @@ generate_guides(
"//adev/src/content/examples/pipes:src/app/hero-async-message.component.ts",
"//adev/src/content/examples/pipes:src/app/power-booster.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -18,4 +18,5 @@ generate_guides(
"//adev/src/content/examples/routing-with-urlmatcher:src/app/app.routes.ts",
"//adev/src/content/examples/routing-with-urlmatcher:src/app/profile/profile.component.html",
],
visibility = ["//adev:__subpackages__"],
)
@@ -8,4 +8,5 @@ generate_guides(
data = [
"//adev/src/assets/images:signals.svg",
],
visibility = ["//adev:__subpackages__"],
)
@@ -27,4 +27,5 @@ generate_guides(
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.html",
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -45,4 +45,5 @@ generate_guides(
"//adev/src/content/examples/testing:src/testing/async-observable-helpers.ts",
"//adev/src/content/examples/testing:src/testing/index.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -8,4 +8,5 @@ generate_guides(
data = [
"//adev/src/assets/images:what_is_angular.svg",
],
visibility = ["//adev:__subpackages__"],
)
@@ -15,4 +15,5 @@ generate_guides(
"//adev/src/assets/images:templates.svg",
"//adev/src/assets/images:what_is_angular.svg",
],
visibility = ["//adev:__subpackages__"],
)
+1
View File
@@ -8,4 +8,5 @@ generate_guides(
data = [
"//adev/src/assets/images:roadmap.svg",
],
visibility = ["//adev:__subpackages__"],
)
@@ -9,4 +9,5 @@ generate_guides(
"//adev/src/content/examples/angular-compiler-options:tsconfig.app.json",
"//adev/src/content/examples/angular-compiler-options:tsconfig.json",
],
visibility = ["//adev:__subpackages__"],
)
@@ -224,7 +224,7 @@ For details of those options and their possible values, see the [Angular CLI Ref
## Complex configuration values
The `assets`, `index`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
The `assets`, `index`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
The `sourceMap` and `optimization` options can be set to a simple boolean value. They can also be given a complex value using the configuration file.
The following sections provide more details of how these complex values are used in each case.
@@ -418,6 +418,7 @@ Several options can be used to fine-tune the optimization of an application.
|:--- |:--- |:--- |:--- |
| `minify` | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values. | `boolean` | `true` |
| `inlineCritical` | Extract and inline critical CSS definitions to improve [First Contentful Paint](https://web.dev/first-contentful-paint). | `boolean` | `true` |
| `removeSpecialComments` | Remove comments in global CSS that contains `@license` or `@preserve` or that starts with `//!` or `/*!`. | `boolean` | `true` |
#### Fonts optimization options
@@ -508,3 +509,16 @@ When supplying the value as a string the filename of the specified path will be
|:--- |:--- |:--- |:--- |
| `input` | The path of a file to use for the application's generated HTML index. | `string` | None (required) |
| `output` | The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path. | `string` | `index.html` |
### Output path configuration
The `outputPath` option can be either a String which will be used as the `base` value or an Object for more fine-tune configuration.
Several options can be used to fine-tune the output structure of an application.
| Options | Details | Value type | Default value |
|:--- |:--- |:--- |:--- |
| `base` | Specify the output path relative to workspace root. | `string` | |
| `browser` | The output directory name for your browser build is within the base output path. This can be safely served to users. | `string` | `browser` |
| `server` | The output directory name of your server build within the output path base. | `string` | `server` |
| `media` | The output directory name for your media files located within the output browser directory. These media files are commonly referred to as resources in CSS files. | `string` | `media` |
@@ -9,4 +9,5 @@ generate_guides(
"//adev/src/content/examples/errors:cyclic-imports/child.component.ts",
"//adev/src/content/examples/errors:cyclic-imports/parent.component.ts",
],
visibility = ["//adev:__subpackages__"],
)
@@ -0,0 +1,16 @@
# Required input is accessed before a value is set.
@description
A required input was accessed but no value was bound.
This can happen when a required input is accessed too early in your directive or component.
This is commonly happening when the input is read as part of class construction.
Inputs are guaranteed to be available in the `ngOnInit` lifecycle hook and afterwards.
## Fixing the error
Access the required input in reactive contexts.
For example, in the template itself, inside a `computed`, or inside an effect.
Alternatively, access the input inside the `ngOnInit` lifecycle hook, or later.
@@ -5,4 +5,5 @@ generate_guides(
srcs = glob([
"*.md",
]),
visibility = ["//adev:__subpackages__"],
)

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