Compare commits

..

424 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
Alex Rickabaugh be7becae9a release: cut the v17.1.0-next.4 release 2023-12-13 17:13:48 -08:00
Alex Rickabaugh bb93f6c791 docs: release notes for the v17.0.7 release 2023-12-13 16:41:41 -08:00
Paul Gschwendtner 4866fce320 refactor(compiler-cli): capture signal inputs in semantic graph for directives (#53521)
Whenever an input of a directive changes, the semantic symbol should
reflect this change for the type check API. This is important because
signal inputs require special output in the type checking blocks- hence
we need to ensure that such type checking blocks are re-generated
properly.

Test verify that incremental type-checking builds work as expected now.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner e6db288ffd refactor(compiler-cli): ensure proper TS incremental program re-use w/ signal inputs (#53521)
Whenever a signal input is captured in a type check block, we will
insert an import. This will change the import graph so that the full
TypeScript program cannot be structurally re-used.

We can fix this trivially by ensuring the import graph remains stable,
by always generating an import to e.g. `@angular/core`. This fixes the
issue nicely for type-check block files. A test verifies this.

For inline code, such as TCB inline or the type constructors inline,
this fix is not applicable because we would change user-input source files,
adding new edges that would not exist for subsequent builds- causing the
program to be not re-used completely. One idea was to rely on the
existing edge that can be assumed to exist for directive code files.
This is true technically, but in practice TS does not deduplicate
imports- so our new namespace import when referencing our symbols will
invalidate the re-use. We will address this in a follow-up. There are a
couple of options, such as working with the TS team, updating the
existing edge, or inlining our helpers as well.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner e1acc6086d test(compiler-cli): set up fake_core implementation of input (#53521)
This will allow us to write unit tests for the new input function,
allowing us eventually to test type checking, emitted code etc.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner abdc7e4578 feat(compiler-cli): support type-checking for generic signal inputs (#53521)
This commit adds the last remaining piece for signal input
type-checking. Bound values to signal inputs are already checked
properly at this point, but inference of generic directive/component
types through their inputs is not implemented.

This commit fixes this. To achieve this, there are a couple of potential
solutions. The generics of a directive are inferred based on input
value expressions using a so-called type constructor. The constructor
looks something like this:

```
const _ctor = <T>(v: Pick<Dir<T>, 'input1', 'input2'>) => Dir<T>;

_ctor({input1: expr1, input2: expr2});
```

This works very well for non-signal inputs where the class member is
directly holding the input values. For signal inputs, this does NOT
work because the class member will actually hold the `InputSignal`
instance. There are a couple of solutions to this:

1. Calling `_ctor` with an `InputSignal<typeof value>`
2. Converting the `_ctor` input signal fields to their write types
   (unwrapping the input signals).

We've decided to go with the second option as TypeScript is very
sensitive with assignments and its checks. i.e. co-variance,
contravariance or bivariance. Semantically it makes more sense to unwrap
the input signal "write type" directly and "assign to it". This is safer
and conceptually also easier to follow. A type constructor continues to
only receive the "expresison values". This simplifies code as well.

It's worth noting that the unwrapping as per option 2 also comes at a
cost. We need to be able to generate imports in type constructors. This
was not possible until the previous commit because inline type constructors
did not have an associated type-check block `Environment` and we were
missing access to expression translation and correct import generation.

Overall, solution 2 is now implemented as works as expected. This commit
adds additional unit tests to ensure this.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner b18122730d refactor(compiler-cli): support emitting references in type constructor code (#53521)
For signal inputs we are looking at generating additional code inside
type constructors. This code is planned to reference an external type
from `@angular/core` to unwrap `InputSignal`'s class fields.

The existing `Environment` class contains helpers for emitting such
references / and translating them from the output AST. We extract
this logic into a superclass for only emitting references. A similar
type already existed to avoid circular dependencies- but now we have
actual use-cases to populate this as a base class.

This allows us to create more-suitable minimal emit environments
when we e.g. generate type constructors inline- which are not
part of any type check block. The existing `Environment` class is scoped
to type check blocks and therefore was not suitable.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner 5f7db018eb refactor(compiler-cli): finalize transform support for signal input transforms (#53521)
Signal inputs do not need coercion members for their transforms. That is
because the `InputSignal` type- which is accessible in the class member-
already holds the type of potential "write values". This eliminates the
need for coercion members which were simply used to somehow capture this
write type (especially when libraries are consumed and only `.d.ts` is
available).

We can simplify this, and also significantlky loosen restrictions
of transform functions- given that we can fully rely on TypeScript for
inferring the type. There is no requirement in being able to
"transplant" the type into different places- hence also allowing
supporting transform functions with generics, or overloads.

In a follow-up commit, once more parts are place, there will be some
compliance tests to ensure these new "loosend restrictions".

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner 0ef03a532b test(compiler-cli): initial type-check tests for signal inputs and infrastructure (#53521)
This commit ensures that the type-check diagnostic testing
infrastructure is prepared to validate signal inputs. i.e. providing the
necessary "mocks" in the fake "d.ts" of `@angular/core`.

The commit then sets up a Golang-style table driven testing environment
that allows us to validate/verify signal input type-checking in a
readable way.

With this infrastructure set up, this commit defines an initial set
of unit tests for type checking of input signals.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner 8908fcd809 refactor(compiler-cli): initial type-checking for signal inputs (#53521)
This commit introduces the initial type-checking for signal inputs.
To enable type-checking od signal inputs, there are a couple of tricks
needed. It's not trivial as it would look like at first glance.

Initial attempts could have been to generate additional statements in
type-checking blocks for signal inputs to simply call a method like
`InputSignal#applyNewValue`. This would seem natural, as it would match
what will happen at runtime, but this would break the language-service
auto completion in a highly subtle way. Consider the case where multiple
directives match the same input. Consider the directives have some
overlap in accepted input values, but they also have distinct diverging
values, like:

```ts
class DirA {
  value = input<'apple'|'shared'>();
}

class DirB {
  value = input<'orange'|'shared'>();
}
```

In such cases, auto completion for the binding expression should suggest
the following values: `apple`, `shared`, `orange` and `undefined`.

The language service achieves this by getting completions in the
type-check block where the user expression would live. This BREAKS if
we'd have multiple places where the expression from the user is used.

Two different places, or more, surface additional problems with
diagnostic collection. Previously diagnostics would surface the union
type of allowed values, but with multiple places, we'd have to work with
potentially 1+ diagnostics. This is non-ideal.

Another important consideration is test coverage. It might sound
problematic to consider the existing test infrastructure as relevant,
but in practice, we have thousands of diagnostic type check block tests
that would greatly benefit if the general emit structure would still
match conceptually. This is another bonus argument on why changing the
way inputs are applied is probably an option we should consider as a
last resort.

Ultimately, there is a good solution where we unwrap directive signal
inputs, based on metadata, and access a brand type field on the
`InputSignal`. This ensures auto-completion continues to work as is, and
also the structure of type check blocks doesn't change conceptually. In
future commits we also need to handle type-inference for generic signal
inputs.

Note: Another alternative considered, in terms of using metadata or not.
We could have type helpers to unwrap signal inputs using type helpers
like: `T extends InputSignal<any, WriteT> ? WriteT : T`. This would
allow us to drop the input signal metadata dependency, but in reality,
this has a few issues:

- users might have `@Input`'s passing around `InputSignal`'s. This is
  unlikely, but shows that the solution would not be fully correct.
- we need the metadata regardless, as we plan on accessing it at runtime
  as well, to distinguish between signal inputs and normal inputs when
  applying new values. This was not clear when this option was
  considered initially.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner 3e0e0b42fa refactor(compiler): emit signal input info in d.ts and generate partial compilation output (#53521)
This commit captures the metadata on whether an input is signal based or
not, in the `.d.ts` of directives and components. This exposes this
information to consumers of the directives. This is needed because
libraries may use signal inputs, and we need to know whether bound
inputs to this library are signal-based or not- so that we can generate
proper type-checking code (account for `InputSignal` or not).

Additionally, this commit introduces a new structure for the partial
compilation output of directive inputs. With the current emit, inputs
are captured in a data structure that is equivalent to the internal data
structure passed to `defineDirective` (the full compilation output).
This worked fine as we only captured a few strings, but in ends up
being a bad practice because partial compilation output should NOT
capture internal data structures that might be specific to a certian
Angular core version. Instead, we introduce a new "future proof"
structure that:

- can hold additional metadata in backwards-compatible ways, like
  `isSignal` or `isRequired`.
- can be parsed trivially using the `AstHost` for the linker, instead of
  having to unwrap/parse an array structure.

The new structure is only emitted when we discover that some inputs are
signal based (or ultimately end up configuring input flags). This is
done for backwards compatibility, so that libraries without signal
inputs remain compatible with older linker versions. In the future,
this might be the only emit.

Compliance tests for this follow in future commits, when the linker
portion is also in place. This commit specialices on the code
generation. With the linker, and compliance test infrastructure fixed
(that is broken right now), we can test the full integration.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner d0ff9b2ceb refactor(core): clean-up duplicate interface for input metadata in facade (#53521)
When working on integrating a new metadata field for inputs, I realized
there are quite a lot of duplications of interfaces. Turns out, the
facade input map type can be replaced in favor of just
`R3DirectiveInput`- even improving type safety-ness of e.g. the wrapped
node expressions of transform functions.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner 6b67800465 refactor(compiler-cli): foundation for propagating signal input metadata (#53521)
This commit defines the initial metadata for inputs passed around in
the compiler-cli. Inputs will now capture additional metadata on whether
they are signal-based or not. This is stored on a per-input basis as
a Zone component may contain both, signal inputs or `@Input` inputs.

The metadata is later used for type-checking, for partial output
generation, or full compilation output generation.

PR Close #53521
2023-12-13 15:44:00 -08:00
Paul Gschwendtner bbd918acd3 refactor(core): introduce signal input() function and compiler detection (#53521)
This commit introduces a function for declaring inputs in
components. The function is called `input`. It comes in two flavors:

- `input` for optional inputs with initial values
- `input.required` for required inputs

Inputs are declared as class members, like with `@Input`- except that
the class field will no longer hold the input value directly. Angular
takes control over the input field and exposes the input value as a
signal. The runtime implementation will follow in future commits.

This commit simply introduces:

- initial compiler detection to recognize such inputs in classes
- the initial signature of `input` and `input.required`.

Note: the defer size test is flawed and there is no minification- hence
this commit also needs to incorporate the new dependency graph changes.

PR Close #53521
2023-12-13 15:44:00 -08:00
Andrew Scott 629343f247 fix(core): Multiple subscribers to ApplicationRef.isStable should all see values (#53541)
The behavior of `ApplicationRef.isStable` changed in 16.1 due to
https://github.com/angular/angular/commit/28c68f709cdc930e12bac51a266e7bf790656034.
This change added a `share` to the `isStable` observable, which prevents
additional subscribers from getting a value until a new one emits. One
solution to the problem would be `shareReplay(1)`. However, that would
increase the bundle size since we do not use `shareReplay` elsewhere.
Instead, we don't even really need to share the observable.

The `Observable` available in `ApplicationRef.isStable` before the above commit
was the zone stable observable, without a `share`. The new behavior adds
only an additional observable to the stream, `hasPendingTasks` (a `BehaviorSubject`).
The observables in this stream are not expensive to subscribe to. The
only one with side effects is the `isStable` (because it subscribes to
onStable), but that one already has the `share` operator on it.
Omitting the `share` in `ApplicationRef` also means that applications on `zoneless` will not
have to pay the cost of the operator when we make zones optional because
the zone stable observable is the only place we use it.

PR Close #53541
2023-12-13 11:26:51 -08:00
Matthieu Riegler 48c471c805 build: remove seek-bzip dependency (#53536)
`seek-bzip` isn't used anymore, let's remove it.

PR Close #53536
2023-12-13 11:26:13 -08:00
Jessica Janiuk 5fa76c372e refactor(migrations): CF migration - add support for not reformatting i18n tags (#53538)
Internationalization is whitespace sensitive. This change updates the formatting code to process for i18n attributes and prevent reformatting those sections of the template.

PR Close #53538
2023-12-13 09:34:54 -08:00
Vincent fdb9cb7a5b fix(platform-browser): Get correct base path when using "." as base href when serving from the file:// protocol. (#53547)
Using http://a as the base URL returns / instead of the actual base path when using the file:// protocol. Using document.baseURI addresses this.

Fixes #53546

PR Close #53547
2023-12-13 09:26:56 -08:00
Kara Erickson f35adcb9b2 fix(core): update feature usage marker (#53542)
This commit updates the name of the 'performance.mark'
counter used to track feature usage. It now matches
the name agreed upon by W3C for this use case:
https://github.com/w3c/user-timing/pull/108

PR Close #53542
2023-12-13 09:24:09 -08:00
Jessica Janiuk ce1076785c fix(migrations): cf migration validate structure of ngswitch before migrating (#53530)
This fix handles the common case where an ngswitch might have invalid syntax post migration. This is likely due to using elements other than case or default underneath the ngswitchcase. This will fail out of the migration for that file when these cases are detected with a useful console message.

fixes: #53234

PR Close #53530
2023-12-13 09:23:15 -08:00
Jessica Janiuk 6aa1bb78e8 fix(migrations): cf migration - undo changes when html fails to parse post migration (#53530)
In the case that a template has some sort of structural issue prior to migrating, like a tag that is not properly closed resulting in invalid HTML post migration, this will attempt to parse the html after migrating and revert to the original structure. An error during migration will be reported out instead.

PR Close #53530
2023-12-13 09:23:14 -08:00
cexbrayat 160363a626 refactor(core): remove no longer needed FlushableEffectRunner private export (#53525)
It is no longer needed after https://github.com/angular/angular/commit/70a442eb023096b329ea2c0c7010df5339993fe7

PR Close #53525
2023-12-13 09:22:42 -08:00
Dylan Hunn 84f143a80f refactor(compiler): Support o.WrappedNodeExpr inside expression conversion (#53478)
`o.WrappedNodeExpr` can show up in some cases, when a host binding's value is inside a TS expression.

It's an open question whether we will need to support all of the TS expression types as a result.

PR Close #53478
2023-12-13 09:21:53 -08:00
Dylan Hunn 6d1d55d2a2 refactor(compiler): Fix a bug involving listeners with targets (#53478)
For some reason, the parser reuses the same field to store the animation phase and the event target. We were incorrectly interpreting the presence of any value on that field as an animation phase, leading us to incorrectly emit synthetic listener instructions for listeners on events with targets. This bug is now fixes.

PR Close #53478
2023-12-13 09:21:53 -08:00
Dylan Hunn 8f2c824a70 refactor(compiler): Support $any in host bindings (#53478)
`$any` should be interpreted as a cast, not as a context read of a variable called `$any`. This already worked in template compilations, but the relevant phase was not enabled for host bindings.

PR Close #53478
2023-12-13 09:21:53 -08:00
Angular Robot 6b20561e1d build: update cross-repo angular dependencies (#52422)
See associated pull request for more information.

PR Close #52422
2023-12-13 09:15:42 -08:00
Kristiyan Kostadinov b98d8f79ed fix(compiler): handle ambient types in input transform function (#51474)
Fixes that the compiler was throwing an error if an ambient type is used inside of an input `transform` function. The problem was that the reference emitter was trying to write a reference to the ambient type's source file which isn't necessary.

Fixes #51424.

PR Close #51474
2023-12-13 09:15:17 -08:00
Joey Perrott d9348be79f feat(docs-infra): put the editor back in angular/angular (#53540)
Puts the editor back in angular/angular until we are able to set up a package containing a worker as needed for this project

PR Close #53540
2023-12-12 16:48:45 -07:00
mgechev 63fd649358 docs: add state of js survey link (#53537)
PR Close #53537
2023-12-12 14:32:12 -08:00
Alan Agius 48c5041687 fix(router): provide more actionable error message when route is not matched in production mode (#53523)
Prior to this commit when a route is not matched and the application was running in production mode an `[Error]: NG04002` was logged in the console. This however, is not actionable when the application is running on the server where there can be multiple pages being rendered at the same time.

Now we change this to also log the route example: `[Error]: NG04002: 'products/Jeep'`.

Closes #53522

PR Close #53523
2023-12-12 14:31:25 -08:00
Kristiyan Kostadinov 9e5456912a fix(compiler-cli): generate less type checking code in for loops (#53515)
The ops for the implicit variables in `@for` loops (e.g. `$index`) are marked as being mandatory which means that they're generated even if they aren't used. These changes make them optional so they're only added when necessary.

PR Close #53515
2023-12-12 14:30:35 -08:00
Miles Malerba 00a1b3bd81 refactor(compiler): Add sanitization support for host bindings (#53513)
Adds support for sanitizing host bindings. Since the tag name of the
element the host binding is being set on isn't always known, we have to
consider multiple possible security contexts.

This commit also adds additional tests to help verify correct behavior
of the sanitization logic for different edge cases.

PR Close #53513
2023-12-12 14:30:11 -08:00
Andrew Scott 44f9f0108e refactor(core): Update flaky test with wait condition (#53437)
The tests in this commit have been flaky commonly due to the elements
not being found. Hopefully using `ExpectedConditions` helps.

PR Close #53437
2023-12-12 09:09:09 -08:00
JiaLiPassion 7a28f50711 feat(zone.js): implement Promise.withResolvers() (#53514)
Implement `Promise.withResolvers()`

```
const {promise, resolve, reject} = Promise.withResolvers();
```

PR Close #53514
2023-12-12 09:04:44 -08:00
Jessica Janiuk cc02852ac4 fix(migrations): CF migration only remove newlines of changed template content (#53508)
The formatting logic would eliminate all newlines in updated template code. This adds start and end markers for tracking when the formatter is in a block of template code that changed or not. It should leave behind any newlines that are outside of a migrated section.

fixes: #53494

PR Close #53508
2023-12-12 09:03:15 -08:00
Jessica Janiuk 79f791543b fix(migrations): fix cf migration import removal when errors occur (#53502)
When migrating a component and the associated external template, if errors occur, the component should not remove the common module imports. This fix should allow the application to still build in that instance.

PR Close #53502
2023-12-12 09:02:32 -08:00
Aditya Kumar e1d01c7be8 docs: Smart quote that breaks code block (#53488)
Signed-off-by: Aditya Kumar <aditya.kumar60@infosys.com>

PR Close #53488
2023-12-12 09:02:02 -08:00
Charles Lyding 58d2df8856 test: replace nodejs-websocket package with ws package (#53482)
The `nodejs-websocket` package has been replace with the `ws` package.
Both provide `WebSocket` server support and both of zero transitive
dependencies. However, the `ws` package has ~78 million weekly downloads
and was last updated this week (as of the writing of this commit) while
the `nodejs-websocket` package has ~7,600 weekly downloads and was last
update 5 years ago. The `ws` package is also already a transitive dependency
of the repository which allows for a reduction in the total dependency count
for the repository.

PR Close #53482
2023-12-12 09:01:03 -08:00
Miles Malerba 4f7d8f3bac refactor(compiler): Simplify how sanitizers are generated (#53473)
Previously we generated an intermediate expression which was later
converted into a symbol import expression for the sanitizer function.
This commit simplifies the behavior by just generating the symbol import
from the beginning

PR Close #53473
2023-12-12 09:00:36 -08:00
Miles Malerba 44bf6d6dec refactor(compiler): Generate trusted const values for extracted attrs (#53473)
Use the DomElementSchemaRegistry to determine the correct security
context for static attributes, and pass it along during ingestion. Then
during the resolve sanitizers phase, use the security context to
determine if a trusted value function is needed

PR Close #53473
2023-12-12 09:00:36 -08:00
Pawel Kozlowski 1fc5442947 perf(core): avoid changes Observable creation on QueryList (#53498)
The changes Observable (impl: EventEmitter) on the QueryList is initalized
lazy - it is created only if someone calls a geter to get a hand on its
instance. But the destroy method was calling this getter thus creating
a new Observable even if no one subscribed to it.

This commit changes the destroy logic to skip creation of an EventEmitter
if it wasn't initialized.

PR Close #53498
2023-12-12 09:00:08 -08:00
Charles Lyding f9731eec93 build: remove unneeded graceful-fs package and resolution workaround (#53487)
The `graceful-fs` package itself is unused within the repository code
and can be removed. The resolution workaround is no longer required
for any direct or transitive dependency based on the issue referenced
via #28213.

PR Close #53487
2023-12-11 14:05:52 -08:00
Charles Lyding fd05ab9767 build: remove unused TypeScript type definition packages (#53475)
Two TypeScript type definition packages are currently unused within the
repository and can be removed. These were most likely leftover from previous
usage of the related packages.

Packages removed:
* `@types/uuid`
* `@types/cli-progress`

PR Close #53475
2023-12-11 14:05:27 -08:00
Matthieu Riegler aa7b3ab85c build: remove incremental-dom dependency (#53474)
The usage of this dependency has been removed in #50108

PR Close #53474
2023-12-11 14:04:53 -08:00
Charles Lyding 89d17dc972 build: replace base64-js package with Node.js Buffer usage (#53464)
The `base64-js` package was only used in tests that were run only on
Node.js. On Node.js, `Buffer` is available which can natively perform
base64 conversion. By using `Buffer in these Node.js only tests, the
`base64-js` package can be removed from the repository.

PR Close #53464
2023-12-11 14:04:06 -08:00
Dylan Hunn 921964220d refactor(compiler): Make some fixups to template binidng ingestion (#53457)
Responding to comments on #53457 about the previous sequence of template binding PRs.

PR Close #53457
2023-12-11 14:03:43 -08:00
Dylan Hunn 41955b89b8 refactor(compiler): Fix animation bindings on structural elements (#53457)
Consider the case:

```
<button *ngIf="true" [@anim]="field"></button>
```

Only the inner `button` should recieve a `property` instruction for the animation binding. We were previously emitting one for the implicit `ng-template` as well, and collecting it into the consts for the `ng-template`. Both of these issues are now fixed.

PR Close #53457
2023-12-11 14:03:43 -08:00
Dylan Hunn c4db46824d refactor(compiler): Fix some behavior with bindings on explicit ng-template elements (#53457)
The behavior of explicit bindings on `ng-template`s was untested, and we differed from `TemplateDefinitionBuilder` significantly. We now have much more similar behavior, although not 100% identical.

For example, consider this templarte:
```
<ng-template l="l1" [p]="p1" [attr.a]="a1" [class.c]="c1"></ng-template>
```

It's not clear what a class binding on an `ng-template` would actually do. Nonetheless, it's well-defined behavior in TemplateDefinitionBuilder, which emits `property` instructions for all three bindings, and people actually do this in google3.

Note that some of these bindings don't really make much sense, but we have to support them for compatibility purposes.

See comments for an in-depth explanation of all the logic.

Also, add a test to exercise the problematic case.

PR Close #53457
2023-12-11 14:03:43 -08:00
Dylan Hunn 0874ef5ecb refactor(compiler): Eliminate BindingFlags and reorder parameters of createTemplateBinding (#53457)
It turns out that `BindingFlags.BindingTargetsTemplate` is actally a redundant property! It will be true in either of the following cases:
1. The template is a normal non-structural `ng-template`. We already know this from `TemplateKind`.
2. The binding came from `templateAttrs` (instead of `attrs`). We have this information in `BindingFlags.IsStructuralTemplateAttribute`.

Therefore, I can just eliminate `BindingFlags.BindingTargetsTemplate`. There's no reason to keep `BindingFlags` around for a single value, so I convert `BindingFlags.IsStructuralTemplateAttribute` to a boolean parameter (with the eventual goal of eliminating it entirely).

Additionally, because element binding ingestion now calls `ir.createBindingOp` inline, it was difficult to compare it to template binding ingestion, which uses the  `createTemplateBinding` helper. I have changed the parameter order of `createTemplateBinding` to closely mimic `ir.createBindingOp`. This will both make the code easier to read, and allow me to easily replace one with the other in the future.

Lastly: the template binding ingestion function is the site of much of the binding ingestion complexity. Add an explanatory function comment.

PR Close #53457
2023-12-11 14:03:42 -08:00
Dylan Hunn 95fecddfe3 refactor(compiler): Split up binding ingestion for elements and templates (#53457)
Previously, we had `ingestBindings` and `ingestBinding`, which required tons of cases to support both elements and templates.

Now, we have two separate functions, `ingestElementBindings` and `ingestTemplateBindings`.

Thanks to the previous refactoring work, `ingestBinding` is now extremely compact. In fact, it's so compact that, in the elements case, it can just be inlined! Therefore, element binding ingestion is now quite easy to read.

The template case continues to be pretty gnarly, although I have already removed some code. In subsequent commits, we will simplify it even further.

PR Close #53457
2023-12-11 14:03:42 -08:00
Dylan Hunn a2e4505bd2 refactor(compiler): Simplify some code in ingest (#53457)
Currently Template Pipeline's ingest phase is very complex, especially when it comes to ingesting bindings.

In this commit, we make some superficial simplifications, in preparation for a larger refactoring. For example, we pull out common code such as `convertAstWithInterpolation` and the `i18n.Message` checks. This enormously shrinks the main binding ingestion functions.

In addition, we reorder the binding kind and flags code above `ingestBindings`, so that `ingestBindings` and `ingestBinding` can be viewed together.

PR Close #53457
2023-12-11 14:03:42 -08:00
Dylan Hunn b44b115785 refactor(compiler): Don't emit class and style bindings on structural template views (#53457)
The Template Pipeline has had a number of tricky bugs involving bindings on structural elements.

Consider this template:

```
<div *ngIf="true" [class.bar]="field"></div>
```

We were incorrectly emitting `ɵɵclassProp` on *both* the template's view, and the inner view. The solution is to just emit an extracted attribute on the enclosing template, so it still shows up in the const array, but does not affect the update block.

We will refactor binding ingestion soon, but this commit improves our correctness before any big refactor.

PR Close #53457
2023-12-11 14:03:42 -08:00
Joey Perrott cc75fab661 build: drop rxjs_umd_modules as it is unused (#53506)
Drop rxjs_umd_modules bazel target as its not longer used and will become a blocker to rxjs>6

PR Close #53506
2023-12-11 14:35:48 -07:00
Joey Perrott 0ea8864e1e refactor(docs-infra): move versions information into the application (#53501)
Move the versions information into the application for now

PR Close #53501
2023-12-11 19:21:34 +00:00
Joey Perrott cebe2db5fa refactor(docs-infra): refactor adev to build using architect (#53497)
Refactor adev as needed to build using architect.

PR Close #53497
2023-12-11 19:21:07 +00:00
Charles Lyding 7c8f026778 build: replace multimatch dependency with glob ignore option (#53463)
The `multimatch` package was only used in the saucelabs test bundling
script to filter out spec files that should be ignored during saucelabs
testing. This functionality can be replaced with `fast-glob` package's
`ignore` option. This removes the need for the `multimatch` package within
the repository.

PR Close #53463
2023-12-11 10:56:03 -08:00
JiaLiPassion 08b0c87a94 fix(zone.js): Promise.resolve(subPromise) should return subPromise (#53423)
In the original `Promise` impelmentation, zone.js follow the spec from
https://promisesaplus.com/#point-51.

```
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(p1);

p1 === p2; // false
```
in this case, `p2` should be the same status with `p1` but they are
still different instances.

And for some edge case.

```
class MyPromise extends Promise {
  constructor(sub) {
    super((res) => res(null));
    this.sub = sub;
  }
  then(onFufilled, onRejected) {
    this.sub.then(onFufilled, onRejected);
  }
}

const p1 = new Promise(setTimeout(res), 100);
const myP = new MyPromise(p1);
const r = await myP;
r === 1; // false
```

So in the above code, `myP` is not the same instance with `p1`,
and since `myP` is resolved in constructor, so `await myP` will
just pass without waiting for `p1`.

And in the current `tc39` spec here https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise-resolve
`Promise.resolve(subP)` should return `subP`.

```
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(p1);

p1 === p2; // true
```

So the above `MyPromise` can wait for the `p1` correctly.

PR Close #53423
2023-12-11 10:55:12 -08:00
Pawel Kozlowski e3a6bf9b6c perf(core): optimize memory allocation when reconcilling lists (#52245)
This change replaces the implementation of the multi-map used to store
detached views while reconciling lists. The new implementation optimizes
memory allocation for such map and avoid arrays allocation when there are
no duplicated keys.

PR Close #52245
2023-12-08 15:00:13 -08:00
Charles Lyding f2245d1fd5 build: convert build/test scripts to use fast-glob (#53397)
The repository currently has two globbing packages. To minimize the number of packages in
the framework repository, the uses of the `glob` package are being converted
to `fast-glob` which is used by the tooling repository.  The change is mostly mechanical
and in this change the build and test scripts are converted.

PR Close #53397
2023-12-08 14:37:07 -08:00
Charles Lyding 636b0d0cd0 build: remove unused babel dependencies (#53441)
Several unused babel related dependencies can be removed:

* `@babel/preset-env` was previously used for ngcc tests but ngcc has been removed. It used here: https://github.com/angular/angular/blob/3569fdf45195c36713e43a91a4feba4fa3c9facf/packages/compiler-cli/ngcc/test/BUILD.bazel#L84
* `@babel/helper-replace-supers` was added during a manual lock file update to workaround issues with since removed babel packages

PR Close #53441
2023-12-08 14:33:59 -08:00
Charles Lyding 119a94ef4b build: remove unneeded babel types postinstall patching (#53441)
These patches are no longer necessary with the current state of the
type packages and the code within the repository. The types are now
included in the already required babel.d.ts file for the relevant
babel packages (currently: `@babel/core` and `@babel/generator`).

PR Close #53441
2023-12-08 14:33:59 -08:00
Charles Lyding ad52eeb164 refactor: reduce direct babel dependencies (#53441)
The `@babel/core` package provides the functionality of multiple other babel packages
without the need to directly depend or import the other babel packages. Since the
`@babel/core` package is already used and imported in the locations that previously
used the other babel packages, an overall reduction in both imports and dependencies
is possible. Six babel related packages were able to be removed from the root `package.json`
and one (also present in the aforementioned six) was removed as a dependency from the
`@angular/localize` package. Unfortunately, the functionality used from the `@babel/generator`
package is not provided by `@babel/core` and is still present. Further refactoring may
allow its removal as well in the future.

The following packages were removed:
* @babel/parser
* @babel/template
* @babel/traverse
* @babel/types
* @types/babel__template
* @types/babel__traverse

PR Close #53441
2023-12-08 14:33:59 -08:00
Joey Perrott 11b06fb92c build: migrate adev generation to @angular/docs package (#53458)
Use the new rule from @angular/docs for generating guides

PR Close #53458
2023-12-08 14:33:33 -08:00
Joey Perrott 9d806c3e2a build: remove already migrated adev build tooling (#53454)
Remove the tooling as its now in dev-infra

PR Close #53454
2023-12-08 14:32:55 -08:00
Miles Malerba 3544f86775 refactor(compiler): Add i18n support for @defer blocks (#53440)
Pass through the i18n placeholders for the various parts of the defer
block during ingestion so its i18n message can be constructed

PR Close #53440
2023-12-08 14:32:31 -08:00
Miles Malerba d35b457820 refactor(compiler): Ensure consistent handling of @empty template (#53440)
Phases that walk through the views by following template and repeater
ops need to remember to check the empty view as well for repeaters. This
commit adds fixes for phases that were missing it, or comments
explaining why its not handled.

PR Close #53440
2023-12-08 14:32:31 -08:00
Miles Malerba 36942d9b72 refactor(compiler): Add i18n support for @for blocks (#53440)
@for does not use actual TemplateOps, but instead has a similar
RepeaterCreateOp. This commit adds support for this op to the relevant
i18n phases.

PR Close #53440
2023-12-08 14:32:31 -08:00
Jessica Janiuk a02767956a fix(migrations): CF Migration - ensure bound ngIfElse cases ignore line breaks (#53435)
When using ternaries or other expressions in bound if / else cases, it is possible that line breaks could end up affecting template replacement.

fixes: #53428

PR Close #53435
2023-12-08 14:31:25 -08:00
Angular Robot 8a46a33e9c docs: update Angular CLI help [main] (#53427)
Updated Angular CLI help contents.

PR Close #53427
2023-12-08 14:30:43 -08:00
Andrew Scott 8cccc7c5be refactor(core): Clean up subscription handling in ComponentFixture (#53426)
The subscriptions can be managed by a single subscription and unsubscribe on destroy

PR Close #53426
2023-12-08 14:30:14 -08:00
Andrew Scott 51ed3b510d refactor(core): Separate application and platform files even more (#53371)
This separates application and platform code into even more files. This now removes
the ciruclar dependency between scheduling and application ref.

PR Close #53371
2023-12-08 14:29:46 -08:00
Andrew Scott 0663708a75 refactor(core): Move code out of application_ref (#53371)
The application_ref is somewhat of a dumping ground for code. This commit
moves things around into more appropriately separate files.

PR Close #53371
2023-12-08 14:29:46 -08:00
Charles Lyding f0377d33a1 refactor(compiler): provide a private API to perform direct style encapsulation (#53363)
To support the development of component specific HMR capabilities, the build/serve
tooling may need to directly process styles to match the view encapsulation
expectations of individual components. To allow for this scenario and to avoid tooling
to need to re-implement the emulated encapsulation logic, an private API is now
available in the `@angular/compiler` package named `encapsulateStyle` that converts
a stylesheet content string to an encapsulated form. This function is not considered
part of the public API nor does it have any of its respective support or versioning guarantees.

PR Close #53363
2023-12-08 14:29:18 -08:00
Miles Malerba 2798aad9ae test(compiler): fix legacy message id test (#53459)
This test actually passes, template pipeline just orders the translated
messages and consts array differently. Since the order isn't important,
we just fork off an alternate golden file for template pipeline.

PR Close #53459
2023-12-08 14:28:27 -08:00
Dylan Hunn 44c2c26235 refactor(compiler): Fix out-of-order i18n issue (#53405)
Fix a bug in the i18n retargeting and reordering phase.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn 8ab7e8474a refactor(compiler): Fix extra attribute on ng-template (#53405)
We no longer emit extra attribute instructions on certain `ng-template` elements with attributes.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn 90e090864f refactor(compiler): Add an additional failing test about i18n ordering (#53405)
I discovered this failure while looking at presubmit results. We appear to still have ordering issues, when more update ops are present.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn db3b05a68d refactor(compiler): Add failing tests about structural attr bindings (#53405)
While running a g3 presubmit, I discovered two related novel failure modes:

1. Simple case: this new test uses an `ngFor` structural directive, which binds a context variable. That variable is immediately used in an attribute binding. It looks like we generate an extra attribute instruction, which might result in an invalid property read at runtime.
2. Complex case: this is another attribute binding, this time on a structural element, inside of an `ng-template`. Not sure what's going on here.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn 671e4f0109 refactor(compiler): Enable meaning_description i18n test in template pipeline (#53405)
The meaning decription test has different i18n message orders, as well as a different const order, but it is in fact passing.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn 3ac6e3ef5b refactor(compiler): Keep a TemplateKind on various binding ops (#53405)
Previously, binding ops only knew whether they applied to a structural template (and even this was actually very misleading!).

Now, binding ops have full information about what kind of template they apply to, if any (e.g. plain template, structural template, etc). Additionally, each binding knows whether it `IsStructuralTemplateAttribute`, which is a property of the binding rather than the template target.

In the future, we should refactor this to unify the various flags that can describe binding types, as well as the flags that describe template targets, into a single and comprehensive field on binding ops.

PR Close #53405
2023-12-08 09:37:56 -08:00
Dylan Hunn df25f462c5 refactor(compiler): Move the creation of i18n attribute contexts into a phase (#53405)
Previously, we created i18n contexts for i18n attributes in ingest. This turned out to be the wrong approach, because we don't always want to produce i18n messages for all i18n attributes! In fact, several kinds of i18n attributes on elements with structural directives should not produce their own messages.

This commit also contains related refactors to fix one such structural directives test.

PR Close #53405
2023-12-08 09:37:55 -08:00
Dylan Hunn 3bc5bd6be4 refactor(compiler): Don't create i18n context ops for attribute bindings on structural templates (#53405)
When a binding is present on an element with a structural directive, that binding is parsed onto *both* the synthetic `ng-template`, as well as the inner element. However, we do not want to create different i18n messages for both bindings; we only want to generate a new i18n message for the inner, "real" element.

PR Close #53405
2023-12-08 09:37:55 -08:00
Dylan Hunn fe8cd6adf7 refactor(compiler): Listeners should be ingested before i18nStart (#53405)
Listener instructions should not be inside the i18n block. In order to avoid this, we ingest bindings on an element before starting the i18n block.

We previously missed this case because almost all bindings result in *update* instructions, which don't need to be ordered relative to i18nStart/i18nEnd create instructions. However, listeners are the only kind of binding that gets ingested into the create block.

PR Close #53405
2023-12-08 09:37:55 -08:00
Dylan Hunn 4950c8d19b refactor(compiler): Fix i18nExp moving phase in Template Pipeline (#53405)
Previously, our i18n slot moving process was buggy. Specifically, it was not resilient to cases in which a create op consumed a slot, but no update ops depended on that slot.

The new algorithm fixes this issue, and is also easier to understand.

PR Close #53405
2023-12-08 09:37:55 -08:00
Pawel Kozlowski 42f4f70e97 fix(core): remove signal equality check short-circuit (#53446)
The PR https://github.com/angular/angular/pull/52465 introduced short-circuit for
the signal equality invocation - with the reasoning that the equality function
should never return false for arguments with the same references. In practice it
turned out that it is rather surprising and the subsequent PR
https://github.com/angular/angular/pull/52532 added a warning when the short-circuit
was taking priority over the equality function.

Still, the presence of the short-circuit prevents people from mutating objects in
place and based on https://github.com/angular/angular/issues/52735 this is a common
and desired scenario. This change removes the short-circuit altogether and thus
fixes the mentioned issue.

We do recognize that removing short-circuit exposes developers to the potentially
surprising logic where mutated in-place change won't be propagated throug the
reactivity graph (due to the deault equality function). But we assume that this might
be less surprising / more desirable as compared to the short-circuit logic.

Fixes #52735

PR Close #53446
2023-12-08 07:05:34 -08:00
Alex Rickabaugh 299eae44a9 Revert "refactor: reduce direct babel dependencies (#53374)" (#53432)
This reverts commit 3b12c59696.

PR Close #53432
2023-12-07 13:21:15 -08:00
Alex Rickabaugh fcc15ce24c Revert "build: remove unneeded babel types postinstall patching (#53374)" (#53432)
This reverts commit 4cdd5158ce.

PR Close #53432
2023-12-07 13:21:14 -08:00
Andrew Scott 2565121851 fix(core): Avoid refreshing a host view twice when using transplanted views (#53021)
This change fixes and issue where the expectation was that change
detection always goes through `detectChangesInView`. In reality,
`detectChangesInternal` directly calls `refreshView`
and refreshes a view directly without checking if it was dirty (to my discontent).

This update changes the implementation of `detectChangesInternal` to
actually be "detect changes" not "force refresh of root view and detect
changes". In addition, it adds the refresh flag to APIs that were
previously calling `detectChangesInternal` so we get the same behavior
as before (host view is forced to refresh).

Note that the use of `RefreshView` instead of `Dirty` is _intentional_
here. The `RefreshView` flag is cleared before refreshing the view while
the `Dirty` flag is cleared at the very end. Using the `Dirty` flag
could have consequences because it is a more long-lasting change to the
view flags. Because `detectChangesInView` will immediately clear the
`RefreshView` flag, this change is much more limited and does not
result in a different set of flags during the view refresh.

PR Close #53021
2023-12-07 12:05:23 -08:00
Jessica Janiuk 1f5c8bf116 fix(migrations): ensure we do not overwrite prior template replacements in migration (#53393)
This fixes a bug where only the last replacement of a template placeholder would apply.

fixes: #53383

PR Close #53393
2023-12-07 09:36:01 -08:00
Andrew Scott 70a442eb02 refactor(core): Run ComponentFixture constructor in injection context (#53400)
The component fixture dependencies have to be passed in manually. This
is a bit annoying to manage as we expand which dependencies are needed.
Instead, we can run the constructor in the TestBed injection context and
move the dependencies into the component fixture code, as is done with
other constructors in Angular.

PR Close #53400
2023-12-07 09:35:24 -08:00
Charles Lyding 4cdd5158ce build: remove unneeded babel types postinstall patching (#53374)
These patches are no longer necessary with the current state of the
type packages and the code within the repository. The types are now
included in the already required babel.d.ts file for the relevant
babel packages (currently: `@babel/core` and `@babel/generator`).

PR Close #53374
2023-12-07 09:34:22 -08:00
Charles Lyding 3b12c59696 refactor: reduce direct babel dependencies (#53374)
The `@babel/core` package provides the functionality of multiple other babel packages
without the need to directly depend or import the other babel packages. Since the
`@babel/core` package is already used and imported in the locations that previously
used the other babel packages, an overall reduction in both imports and dependencies
is possible. Six babel related packages were able to be removed from the root `package.json`
and one (also present in the aforementioned six) was removed as a dependency from the
`@angular/localize` package. Unfortunately, the functionality used from the `@babel/generator`
package is not provided by `@babel/core` and is still present. Further refactoring may
allow its removal as well in the future.

The following packages were removed:
* @babel/parser
* @babel/template
* @babel/traverse
* @babel/types
* @types/babel__template
* @types/babel__traverse

PR Close #53374
2023-12-07 09:34:22 -08:00
Dylan Hunn 5f736089db release: cut the v17.1.0-next.3 release 2023-12-06 10:22:32 -08:00
Dylan Hunn 423692acb4 docs: release notes for the v17.0.6 release 2023-12-06 10:18:10 -08:00
Jessica Janiuk a1e348516e refactor(migrations): Update comments to be more accurate (#53396)
This comment change better clarifies what these regexes do which should alleviate future confusion.

PR Close #53396
2023-12-06 09:45:44 -08:00
Andrew Scott 5c1d441029 feat(router): Add info property to NavigationExtras (#53303)
This commit adds a property to the navigation options to allow
developers to provide transient navigation info that is available for
the duration of the navigation. This information can be retrieved at any
time with `Router.getCurrentNavigation()!.extras.info`. Previously,
developers were forced to either create a service to hold information
like this or put it on the `state` object, which gets persisted to the
session history.

This feature was partially motivated by the [Navigation API](https://github.com/WICG/navigation-api#example-using-info)
and would be something we would want/need to have feature parity if/when the
Router supports managing navigations with that instead of `History`.

PR Close #53303
2023-12-06 09:44:43 -08:00
Andrew Scott a9872ccbb9 Revert "Revert "fix(router): Ensure canMatch guards run on wildcard routes (#53239)" (#53339)" (#53342)
This reverts commit ac8eb5b943.

PR Close #53342
2023-12-06 09:44:19 -08:00
Dylan Hunn dc51f6ee3b refactor(compiler): Support unary ops in template pipeline (#53376)
Template Pipeline can now ingest and emit unary ops, such as `+` and `-`.

PR Close #53376
2023-12-06 09:43:35 -08:00
Dylan Hunn 29469ffb9b refactor(compiler): template pipeline support for i18n blocks (#53376)
Blocks can contain i18n expressions. We already have most of the logic to make them work; we were just missing some ingestion code.

PR Close #53376
2023-12-06 09:43:35 -08:00
Dylan Hunn 91eafa89cc refactor(compiler): Separate ownership and target for i18n expressions, and various refactors (#53376)
I18n expressions logically have both a target and an owner:
- For i18n text expressions, the owner is the i18nStart instruction. The target is initially the same, but later moves to be the last slot consumer in the i18n block.
- For i18n attribute expressions, the owner is the I18nAttributes config instruction, whereas the target is the ElementCreate that hosts the attribute.

This refactor makes the code clearer in quite a few plases.

Additionally, we now perform a lot of the i18n processing earlier. For example, re-targeting and re-ordering of i18n expressions happens *before* apply instructions are generated. As a result, the re-ordering logic is a lot simpler.

These changes also have consequences on i18n const collection, along with a couple other minor changes.

PR Close #53376
2023-12-06 09:43:35 -08:00
Jessica Janiuk 2a5a8f6f05 fix(migrations): Change CF Migration ng-template placeholder generation and handling (#53394)
Using more unique characters makes it easier to parse placeholders that may contain JS logic, making it more flexible.

fixes: #53386
fixes: #53385
fixes: #53384

PR Close #53394
2023-12-06 09:42:43 -08:00
Alex Rickabaugh 580af8e5f8 Revert "refactor(core): output an error guide link in prod mode (#53324)" (#53392)
This reverts commit f245aba782.

Reason: breaks g3

PR Close #53392
2023-12-06 07:23:23 -08:00
Kristiyan Kostadinov e620b3a724 fix(compiler-cli): add compiler option to disable control flow content projection diagnostic (#53311)
These changes add an option to the `extendedDiagnostics` field that allows the check from #53190 to be disabled. This is a follow-up based on a recent discussion.

PR Close #53311
2023-12-05 17:21:16 -08:00
Matthieu Riegler 8195be1e09 refactor(devtools): run control flow migration. (#53353)
Let's update the devtools with the latest improvements !

PR Close #53353
2023-12-05 17:20:09 -08:00
Jessica Janiuk 01b18a4248 fix(migrations): handle templates outside of component in cf migration (#53368)
If a template is passed in as an input, the ng-template will not exist in the same component template. This will leave a template placeholder behind. This fix ensures that template placeholder gets turned into a template outlet.

fixes: #53361

PR Close #53368
2023-12-05 17:19:37 -08:00
Jessica Janiuk 5a0ed28c9d fix(migrations): handle nested ng-template replacement safely in CF migration (#53368)
When there are ng-templates nested inside other ng-templates, the replacement and removal of the templates gets disrupted. Re-processing the templates in the file along the way resolves this issue.

fixes: #53362

PR Close #53368
2023-12-05 17:19:36 -08:00
Dylan Hunn 05f9c7bd31 refactor(compiler): Initial support for i18n attributes (#53341)
Add support for i18n attributes:
- Generate i18n contexts from i18n attributes, and extract the eventual messages into the constant pool.
- Emit I18nAttributes config instructions when needed.
- Use the generated i18n variable in the appropriate places, including extracted attribute instructions, as well as I18nAttributes config arrays.

PR Close #53341
2023-12-05 17:13:58 -08:00
Matthieu Riegler 6cd91c675c refactor(devtools): enables typescript strict option (#53340)
Enabling `strict` is part of an effort to improve the quality of the devtools code base.
One of the direct side effect is to enable `noImplicitAny`, `strictPropertyInitialization` and `strictBindCallApply`.

This commit also replaces `fullTemplateTypeCheck` with `stringTemplates`.

PR Close #53340
2023-12-05 12:42:56 -08:00
Srinath Vayalpati 1e3bcfecba docs: fixed wrong template string placeholder (#53286)
PR Close #53286
2023-12-04 21:50:35 -08:00
Andrew Scott 50d7916278 feat(router): Add router configuration to resolve navigation promise on error (#48910)
With the deprecation of the configurable errorHandler in the Router, there is a missing
use-case to prevent the navigation promise from rejecting on an error. This rejection
results in unhandled promise rejections. This commit allows developers to instruct
the router to instead resolve the navigation promise with 'false', which matches
the behavior of other failed navigations.

Resolving the Promise would be the ideal default behavior. It is rare
that any code handles the navigation Promise at all and even more rare
that the Promise rejection is caught. Updating the default value for
this option should be considered for an upcoming major version.

fixes #48902

PR Close #48910
2023-12-04 21:49:35 -08:00
Joey Perrott 65cc8befbb build: set up markdown pipeline for the reference section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:10 -08:00
Joey Perrott 631da1dbb0 build: set up markdown pipeline for the tools section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:10 -08:00
Joey Perrott df13433035 build: set up markdown pipeline for service-workers section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:10 -08:00
Joey Perrott 97e6e04611 build: set up markdown pipeline for runtime-performance part of the best-practices section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:10 -08:00
Joey Perrott 3c9b6cba9f build: set up markdown pipeline for best-practices section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:10 -08:00
Joey Perrott ae5fe6cc16 build: set up markdown pipeline for introduction section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott c5afa37310 build: set up markdown pipeline for essentials of the introduction section (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott 551af0770f build: set up markdown pipeline for testing directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott ecd44b1e10 build: set up markdown pipeline for templates directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott 0c60dd39cb build: set up markdown pipeline for signals directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott d8deb4080b build: set up markdown pipeline for pipes directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott 37de3f10fc build: set up markdown pipeline for routing directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:09 -08:00
Joey Perrott 2b9eb9d6e5 build: set up markdown pipeline for ngmodules directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott f6691069b1 build: set up markdown pipeline for i18n directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott f35871f6c6 build: set up markdown pipeline for http directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott e582d60f36 build: set up markdown pipeline for forms directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott 289f76f569 build: set up markdown pipeline for directives directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott f5e4fe3f62 build: set up markdown pipeline for di directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:08 -08:00
Joey Perrott f01ff416ce build: set up markdown pipeline for components directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:07 -08:00
Joey Perrott 6594b4311f build: fix name for animations markdown target (#53290)
Use the animations name as the target name instead of test

PR Close #53290
2023-12-04 21:47:07 -08:00
Joey Perrott 4e5c56eda0 build: set up markdown pipeline for root directory of the content guide (#53290)
Set up the markdown pipeline to include the required source files.

PR Close #53290
2023-12-04 21:47:07 -08:00
Kristiyan Kostadinov d7a83f9521 fix(compiler-cli): avoid conflicts with built-in global variables in for loop blocks (#53319)
Currently we generate the following TCB for a `@for` loop:

```ts
// @for (item of items; track item) {...}

for (const item of this.items) {
  var _t1 = item;
  // Do things with `_t1`
}
```

This is problematic if the item name is the same as a global variable (e.g. `document`), because when the TCB has references to that variable (e.g. `document.createElement`), it'll find the loop initializer instead of the global variable.

These changes fix the issue by generating the following instead:

```ts
for (const _t1 of this.items) {
  // Do things with `_t1`
}
```

Fixes #53293.

PR Close #53319
2023-12-04 21:45:17 -08:00
Andrew Kushnir 596b43d631 refactor(core): skip disconnected nodes while computing node path for hydration (#53317)
In certain cases Angular hydration logic can not rely on the order in which elements are present in a template (for example, in content-projection use-cases) and there is a need to serialize a path from one node to another, so that hydration can locate an element on a page. The logic attempts to use an immediate parent element as an anchor and compute the path from it. If it fails - the path is computed starting from the <body> (this is a fallback).

This commit updates the logic to walk up the parents tree if an immediate parent (from a template) is disconnected from the DOM. This helps to shorten the lookup path and make it more stable.

PR Close #53317
2023-12-04 21:44:08 -08:00
Tomasz Ducin ba8dc2843a refactor(devtools): filtering providers by token within "injector tree" tab (#53313)
In some injectors, there are LOTS of providers, making it slightly inconvenient to search for a certain one. This commit introduces a search-by-token for providers of a specific injector.

PR Close #53313
2023-12-04 21:43:34 -08:00
Jessica Janiuk 6f75471307 fix(migrations): CF Migration fix missing alias for bound ngifs (#53296)
Empty aliases are considered the item in an ngFor, and ngIf was skipping that value.

fixes: #53291

PR Close #53296
2023-12-04 21:42:57 -08:00
Andrew Kushnir f245aba782 refactor(core): output an error guide link in prod mode (#53324)
Currently, the link to an error guide is only included into an error message in dev mode. This change makes the `Find more at https://angular.io/errors/NG0XYZ` appear in the error message even in prod mode. Note: the rest of the error message is still tree-shaken away in prod mode (as it happens today).

PR Close #53324
2023-12-04 21:42:28 -08:00
Jessica Janiuk 9834fd2738 fix(migrations): remove setting that removes comments in CF migration (#53350)
This setting was added to prevent comment duplication, since the TS AST printer includes prior line comments as part of a given line with no way to really avoid that.
However in component imports, it is not safe to remove comments as they could be load bearing for some.

PR Close #53350
2023-12-04 21:41:41 -08:00
JoostK 77939a3bd3 fix(core): cleanup signal consumers for all views (#53351)
This commit fixes a memory leak where signal consumers would not be cleaned up for
descendant views when a view is destroyed, because the cleanup logic was only invoked
for the view that is itself being destroyed.

PR Close #53351
2023-12-04 21:41:12 -08:00
Miles Malerba 12dfa9ba13 test(compiler): Update golden partial file (#53327)
Update the golden partial file to include the newly added tests in this
PR.

PR Close #53327
2023-12-04 14:24:21 -08:00
Miles Malerba 4099bd0de6 refactor(compiler): Fix structural directive i18n placeholders (#53327)
Previously we recorded separate param values for a strucural directive
and the element tag it goes on. We then later attempted to combine those
into a single value. However in some cases this merging logic matched
the directive with the wrong tag.

This change implements an alternate approach where we match the
directive to its element tag from the start, while we're traversing the
ops. This should be a more robust solution.

PR Close #53327
2023-12-04 14:24:21 -08:00
Miles Malerba 9a0bf516c3 refactor(compiler): Add missing projection arguments (#53327)
We previously failed to populate the attributes property on projection
ops, this commit populates it and later strips out the "select"
attribute.

PR Close #53327
2023-12-04 14:24:21 -08:00
Miles Malerba c555120d48 refactor(compiler): Add support for ng-content in i18n blocks (#53327)
Adds support for handling i18n paceholders on ng-content and adds a new
test to verify behavior.

PR Close #53327
2023-12-04 14:24:20 -08:00
Miles Malerba 0a1611d4cc refactor(compiler): Fix sub-template index for sibling i18n blocks (#53327)
Previously we failed to reset the sub-template index counter when we
exited a root block. This caused following sibling blocks to start
counting at the wrong index.

PR Close #53327
2023-12-04 14:24:20 -08:00
Miles Malerba 1baf26f589 refactor(compiler): Fix not expressions (#53327)
Adds `PrefixNot` to the handled expressions in the template pipeline

PR Close #53327
2023-12-04 14:24:20 -08:00
Dylan Hunn d4b4236902 Revert "fix(router): Ensure canMatch guards run on wildcard routes (#53239)" (#53339)
This reverts commit 1940280d27.

PR Close #53339
2023-12-03 14:57:54 -08:00
Miles Malerba acd6100960 docs(compiler): Improve code comments (#53300)
Adds better comments describing the case where we have multiple ICU
sub-messages that share the same plaeholder.

PR Close #53300
2023-12-01 14:51:29 -08:00
Miles Malerba 9bd9065410 test(compiler): Enable newly passing tests (#53300)
Enables additional tests that pass after implementing the ICU logic.

PR Close #53300
2023-12-01 14:51:29 -08:00
Miles Malerba 8c34ad5d86 refactor(compiler): Fix ingestion of nested ICUs (#53300)
It is possible for ICUs to be nested inside other ICUs. This change
adjusts our ingestion logic to create extra interpolation ops for the
nested ICUs during ingestion.

PR Close #53300
2023-12-01 14:51:29 -08:00
Miles Malerba 2b3f06a8db refactor(compiler): Remove invalid assertion about i18n params (#53300)
We previously had an assertion that every placeholder in the i18n AST
had a corresponding param in the output. However, there are some cases
such as interpolations nested inside ICUs where this assertion is not
true. This change simply removes the asserion.

PR Close #53300
2023-12-01 14:51:29 -08:00
Miles Malerba e23752c184 refactor(compiler): Add support for ICUs that share a placeholder (#53300)
ICUs may share a placeholder, and in that case they need special
post-processing. This change adds logic to cover this possibility. In
particular, we set the param to a special placeholder value and then
pass an array containing the sub-message variables as a post-processing
param.

PR Close #53300
2023-12-01 14:51:28 -08:00
Miles Malerba 4afa5ac2f9 refactor(compiler): Handle i18n placeholders with spaces (#53300)
I18n placeholders may contain spaces, this change updates the formatting
logic to replace them with underscores in the output.

PR Close #53300
2023-12-01 14:51:28 -08:00
Miles Malerba a9fb5fa458 refactor(compiler): Put i18n expression ops in the correct order (#53300)
When we re-assign the slot dependencies for the i18nExprs, we should
move them down below the other ops that target their same slot. This
keeps the behavior consistent with TDB

PR Close #53300
2023-12-01 14:51:28 -08:00
pBouillon 9423c1901e docs: fix the colspan and colSpan link in the property binding page (#53180)
PR Close #53180
2023-12-01 10:36:53 -08:00
Matthieu Riegler 9f3e64adc9 docs: Error codes should start with a 0. (#53271)
Also adding NG0602 to adev.

PR Close #53271
2023-12-01 10:36:20 -08:00
Andrew Kushnir 82609d471c fix(core): support swapping hydrated views in @for loops (#53274)
This commit fixes an issue where swapping hydrated views was not possible in the new control flow repeater. The problem was caused by the fact that an internal representation of a view had no indication that hydration is completed and further detaching/attaching should work in a regular (non-hydration) mode. This commit adds a logic that resets a pointer to a dehydrated content and we use this as an indication that the view is swtiched to a regular mode.

Resolves #53163.

PR Close #53274
2023-12-01 10:35:36 -08:00
Charles Lyding 5613051a8b fix(compiler): allow TS jsDocParsingMode host option to be programmatically set again (#53292)
When the AOT compiler creates a delegated host for a provided TypeScript CompilerHost,
it delegates functionality back to the original via a series of internal method delegations.
However, unlike other members of the CompilerHost, `jsDocParsingMode` is not a method
and cannot be delegated in this way. Attempting to call bind on the property will result
in a runtime error. Instead, `jsDocParsingMode` is now delegated via get/set accessors.
Additionally, the override of `getSourceFile` now has an updated type signature to reflect
the additional of the `jsDocParsingMode` option for the method.

This is a followup to #53126 which updates the other DelegatingCompilerHost.

PR Close #53292
2023-12-01 10:35:07 -08:00
Jessica Janiuk aad5e5bd0e fix(migrations): CF Migration add support for ngIf with just a then (#53297)
Prior to this fix, the expectation that anytime then was used, else would always be present. That is not a valid assumption.

fixes: #53287

PR Close #53297
2023-12-01 10:33:53 -08:00
Jessica Janiuk 1c1e8c477b fix(migrations): CF migration - ensure NgIfElse attributes are properly removed (#53298)
the attribute in question was assumed to be at the start of the replaced content, but it could be later, too.

fixes: #53288

PR Close #53298
2023-12-01 10:32:35 -08:00
Jessica Janiuk 2998d482dd fix(migrations): CF Migration - Fix case of aliases on i18n ng-templates preventing removal (#53299)
i18n template removal expected no other attributes to be present, but if a bound ngIf is present with aliases and i18n, that is more than what was expected. Now it should safely remove them appropriately.

fixes: #53289

PR Close #53299
2023-12-01 09:18:31 -08:00
Andrew Kushnir 4b23221b4e fix(core): support hydration for cases when content is re-projected using ng-template (#53304)
This commit fixes an issue with hydration, which happens when a content is projected in a certain way, leaving host elements non-projected, but the child content projected.

The fix is to detect such situations and add extra annotations to help runtime logic locate those elements at the right locations.

Resolves #53276.

PR Close #53304
2023-12-01 09:12:54 -08:00
Andrew Kushnir 77ac4cd324 fix(compiler): generate proper code for nullish coalescing in styling host bindings (#53305)
This commit fixes an issue where having an expression with nullish coalescing in styling host bindings leads to JS errors due to the fact that a declaration for a temporary variable was not included into the generated code.

Resolves #53295.

PR Close #53305
2023-12-01 09:12:20 -08:00
Andrew Scott 1940280d27 fix(router): Ensure canMatch guards run on wildcard routes (#53239)
This commit makes sure that wildcard routes still run the `canMatch`
guards.

Fixes #49949

PR Close #53239
2023-11-30 09:45:40 -08:00
Tomasz Ducin cb500c7ee5 refactor(devtools): devtools support for maps and symbol description (#53167)
Added 2 tiny improvements:
- instead of "Symbol()", "Symbol(DESCRIPTION)" is displayed
- ECMAScript Maps are distinguished
Additionally:
- PropTypes has been moved to a separate file
- Simple unit tests covering each PropType except for PropType.Unknown

PR Close #53167
2023-11-30 09:45:13 -08:00
Matthieu Riegler a429167994 fix(devtools): use a shared angular detection code (#51569)
This fixes an issue where an angular app exposes a global `ng` object that is not our `ng`.

fixes #51565

PR Close #51569
2023-11-30 09:40:11 -08:00
AleksanderBodurri 7252ee4162 docs(devtools): write feature descriptions for new DI debugging features in Angular Devtools (#52290)
This commit adds new sections to the devtools aio page for the new DI debugging features in Angular DevTools:

1. Directive dependency inspection
2. Injector hierarchy visualization

PR Close #52290
2023-11-30 09:38:27 -08:00
Alexander Melde 1d6d895cca docs: fix typo in control flow track description (#52849)
as -> are
PR Close #52849
2023-11-30 09:37:38 -08:00
Sylvain DEDIEU f307b505e2 docs: update http providers example to show the app.config.ts instead of the main.ts (#52848)
The examples of how to provide the http fetch, cookie/header name and disabling XSRF protection now show the app.config.ts file instead of the main.ts file since the cli boostrap application with this architecture.

PR Close #52848
2023-11-30 09:37:05 -08:00
Sasidharan SD 26bdf05622 docs: fix variable name in code comment (#52851)
PR Close #52851
2023-11-30 09:36:36 -08:00
Sasidharan SD e2265b938b docs: remove repetetive line (#52864)
PR Close #52864
2023-11-30 09:36:10 -08:00
Sai Kumar Kola 7671879ecf docs: fix typo in learn-angular/forms-validation (#52901)
PR Close #52901
2023-11-30 09:35:42 -08:00
Sasidharan SD 982dc5d9d8 docs: fix typo on form tutorial step 18 (#52908)
PR Close #52908
2023-11-30 09:35:14 -08:00
Pridon Tetradze 9c7e2797a8 docs: add missing --no-standalone flag (#52910)
PR Close #52910
2023-11-30 09:34:41 -08:00
Sylvain DEDIEU 3dfd21d8d2 docs: wrong web docs urls for host and host-context (#52966)
Wrong MDN Web Docs URLs for host / host-context on angular.dev

PR Close #52966
2023-11-30 09:34:13 -08:00
Sasidharan SD 702e7905c1 docs: fix minor typos in tutorials (#52970)
PR Close #52970
2023-11-30 09:33:46 -08:00
Isaac Aggrey dcf14ccc01 docs: fix typo (#53039)
PR Close #53039
2023-11-30 09:33:16 -08:00
Matthieu Riegler 5ad9f216da docs: fix image path for directive overview (#53048)
fixes #53047

PR Close #53048
2023-11-30 09:32:35 -08:00
Sasidharan SD cdca9ea95a docs: fix angular press kit logo title (#53055)
PR Close #53055
2023-11-30 09:30:04 -08:00
Matthieu Riegler 9640f2bceb docs: fix section title on binding.md (#53072)
fixes #53070

PR Close #53072
2023-11-30 09:29:31 -08:00
Sasidharan SD d89491d195 docs: fix directives header (#53080)
PR Close #53080
2023-11-30 09:29:06 -08:00
Sasidharan SD cefeadefbe docs: fix inconsistent example in binding page (#53081)
PR Close #53081
2023-11-30 09:28:34 -08:00
Sasidharan SD d779578aff docs: update template ref variable with correct link (#53084)
PR Close #53084
2023-11-30 09:28:09 -08:00
Alejandro ba3007ecef docs: remove extra parenthesis in multiple examples and extra backtick (#53098)
PR Close #53098
2023-11-30 09:27:39 -08:00
oliv37 0bbec5280a docs: fix schematics links (#53121)
PR Close #53121
2023-11-30 09:27:13 -08:00
Tamás Baranyi cf507d5cc8 docs: tutorial url on tutorials/first-app/http page fixed (#53125)
fixes: #53122

PR Close #53125
2023-11-30 09:25:58 -08:00
Balaji 6e73f53362 docs: added more context to the description of standalone components card (#53186)
PR Close #53186
2023-11-30 09:23:30 -08:00
Balaji b12c751c41 docs: updated the description of typed forms card (#53186)
PR Close #53186
2023-11-30 09:23:29 -08:00
Balaji 639012c521 docs: updated the description of module with providers card (#53186)
PR Close #53186
2023-11-30 09:23:29 -08:00
Balaji db9498e5cb docs: updated the description of standalone components card (#53186)
PR Close #53186
2023-11-30 09:23:29 -08:00
Balaji 719223d19f docs: updated the headline of migration overview section (#53186)
PR Close #53186
2023-11-30 09:23:29 -08:00
prasantht96 05843f4eb1 docs: fix typos in adev (#53189)
PR Close #53189
2023-11-30 09:23:02 -08:00
Abhishek Upadhyay 7b61ff9cb9 docs: corrected gramatical mistake in ssr.md (#53264)
PR Close #53264
2023-11-30 09:21:59 -08:00
Jan-Niklas W f62dfb2abb docs: remove unnecessary word (#53265)
PR Close #53265
2023-11-30 09:21:33 -08:00
Jessica Janiuk 876065407c docs: fix changelog to remove at for user reference (#53266)
This fixes the common issue of a commit referencing our @ syntax and pointing to a user with a matching name on github.

PR Close #53266
2023-11-30 09:21:08 -08:00
Fabian Schmidt 461264a99a docs: fix typo in word "intrinsic" for adev and aio (#53250)
This is a fix for two typos in both angular.dev and angular.io docs

PR Close #53250
2023-11-30 09:20:09 -08:00
Jessica Janiuk 03e2f1bb25 fix(migrations): fix regexp for else and then in cf migration (#53257)
The regexp for then and else did not ignore alphanumeric characters prior to the then and else. So if a string contained then, for example Authentication, it would incorrectly match as a then clause.

fixes: #53252

PR Close #53257
2023-11-30 09:19:43 -08:00
Jessica Janiuk f4a96a9160 fix(migrations): handle aliases on bound ngIf migrations (#53261)
This fixes a reported issue where ngIf is used on an ng-template with let aliases.

fixes: #53251

PR Close #53261
2023-11-30 09:19:15 -08:00
Andrew Scott 9325687b8d docs: Update deprecation guide example (#53262)
fixes #53242

PR Close #53262
2023-11-30 09:18:29 -08:00
Andrew Kushnir 899f6c4a12 fix(core): handle hydration of multiple nodes projected in a single slot (#53270)
This commit updates the logic to handle hydration of multiple nodes projected in a single slot. Currently, in case component nodes are content-projected and their order is changed during the projection, hydration can not find the correct element. With this fix, extra annotation info would be included for such nodes and hydration logic at runtime will use it to locate the right element.

Resolves #53246.

PR Close #53270
2023-11-30 09:16:23 -08:00
Joey Perrott 6462ddd178 build: set up initial adev guide (#53208)
Set up the pipeline for guides in adev with first exampele, animations

PR Close #53208
2023-11-29 20:37:44 +00:00
Joey Perrott fc3e41cc9d refactor(docs-infra): remove shared code from adev in favor of loading from dev-infra common package (#53214)
Remove shared code as part of its migration to the dev-infra package.

PR Close #53214
2023-11-29 20:37:21 +00:00
1918 changed files with 70906 additions and 66380 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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 -29
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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,17 +243,14 @@ 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
# Run zone.js tools tests
- run: yarn --cwd packages/zone.js promisetest
- run: yarn --cwd packages/zone.js promisefinallytest
- 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
@@ -251,7 +262,7 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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.
@@ -259,9 +270,9 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
uses: angular/dev-infra/github-actions/create-pr-for-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
with:
branch-prefix: docs-update-events
pr-title: 'docs: update events'
+4
View File
@@ -57,3 +57,7 @@ baseline.json
# Husky
.husky/_
aio/content/examples/.DS_Store
# Ignore cache created with the Angular CLI.
.angular/
+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
+1
View File
@@ -13,6 +13,7 @@ exports_files([
"scripts/ci/payload-size.sh",
"scripts/ci/payload-size.js",
"package.json",
"angular.json",
])
filegroup(
+161 -72
View File
@@ -1,37 +1,177 @@
<a name="17.1.0-next.2"></a>
# 17.1.0-next.2 (2023-11-29)
<a name="17.1.0"></a>
# 17.1.0 (2024-01-17)
### 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)) |
| [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 |
| -- | -- | -- |
| [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 |
| -- | -- | -- |
| [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)) |
| [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 |
| -- | -- | -- |
| [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)) |
| [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 -->
<a name="17.0.7"></a>
# 17.0.7 (2023-12-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [4fd5409090](https://github.com/angular/angular/commit/4fd5409090b5556ecde3b4e2e1f09955e71443f4) | fix | handle ambient types in input transform function ([#51474](https://github.com/angular/angular/pull/51474)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [a603338fe8](https://github.com/angular/angular/commit/a603338fe800a133c64b1573f2f805978c947725) | fix | generate less type checking code in for loops ([#53515](https://github.com/angular/angular/pull/53515)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [58ed76be93](https://github.com/angular/angular/commit/58ed76be937c23713e05609da3195ebafeb49794) | fix | Avoid refreshing a host view twice when using transplanted views ([#53021](https://github.com/angular/angular/pull/53021)) |
| [c16b5e8290](https://github.com/angular/angular/commit/c16b5e82901b28b8ca0b72009c56aaeca68c58b0) | fix | Multiple subscribers to ApplicationRef.isStable should all see values ([#53541](https://github.com/angular/angular/pull/53541)) |
| [17dbf8b8e2](https://github.com/angular/angular/commit/17dbf8b8e2a0979515d07341b4a869a78fab8583) | fix | remove signal equality check short-circuit ([#53446](https://github.com/angular/angular/pull/53446)) |
| [5b4add27b6](https://github.com/angular/angular/commit/5b4add27b6d9d3ba55740818dc00659b6d462b87) | fix | update feature usage marker ([#53542](https://github.com/angular/angular/pull/53542)) |
| [68d111c841](https://github.com/angular/angular/commit/68d111c8416dff43a83bb9de1241b4847d80eae7) | perf | avoid changes Observable creation on QueryList ([#53498](https://github.com/angular/angular/pull/53498)) |
| [044cb553b4](https://github.com/angular/angular/commit/044cb553b4b8ed2f5f9a80131c5da84b3c964d9f) | perf | optimize memory allocation when reconcilling lists ([#52245](https://github.com/angular/angular/pull/52245)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [96ab999698](https://github.com/angular/angular/commit/96ab999698ca186b8e2b95177ea6f2c84e43628a) | fix | CF Migration - ensure bound ngIfElse cases ignore line breaks ([#53435](https://github.com/angular/angular/pull/53435)) |
| [c9a1c6f1c7](https://github.com/angular/angular/commit/c9a1c6f1c7455431755dc31e00d72864931bf819) | fix | cf migration - undo changes when html fails to parse post migration ([#53530](https://github.com/angular/angular/pull/53530)) |
| [b75aca1d74](https://github.com/angular/angular/commit/b75aca1d747e1bb55d4f3f6996bb2cb363e34f5b) | fix | CF migration only remove newlines of changed template content ([#53508](https://github.com/angular/angular/pull/53508)) |
| [e88a12d5b3](https://github.com/angular/angular/commit/e88a12d5b3cf0e7c55c15c6820362d4d1e25ecaa) | fix | cf migration validate structure of ngswitch before migrating ([#53530](https://github.com/angular/angular/pull/53530)) |
| [543df3dca5](https://github.com/angular/angular/commit/543df3dca52597aae298a01271337949d891dc65) | fix | ensure we do not overwrite prior template replacements in migration ([#53393](https://github.com/angular/angular/pull/53393)) |
| [d232ea143f](https://github.com/angular/angular/commit/d232ea143f99fffc190c126d01920e965ffde3a3) | fix | fix cf migration import removal when errors occur ([#53502](https://github.com/angular/angular/pull/53502)) |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [d5c631bf36](https://github.com/angular/angular/commit/d5c631bf3663b414b6d7734352804e594bfa2772) | fix | Get correct base path when using "." as base href when serving from the file:// protocol. ([#53547](https://github.com/angular/angular/pull/53547)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [e750e4edcf](https://github.com/angular/angular/commit/e750e4edcfe812bcee82d4a2f53fd8acd8792f08) | fix | provide more actionable error message when route is not matched in production mode ([#53523](https://github.com/angular/angular/pull/53523)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.0.6"></a>
# 17.0.6 (2023-12-06)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [a2e5f483f5](https://github.com/angular/angular/commit/a2e5f483f5a869c0cca205f092049e252a02b710) | fix | generate proper code for nullish coalescing in styling host bindings ([#53305](https://github.com/angular/angular/pull/53305)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [66ecf4c274](https://github.com/angular/angular/commit/66ecf4c2748b43b7e53af00499b153bbe70dd684) | fix | add compiler option to disable control flow content projection diagnostic ([#53387](https://github.com/angular/angular/pull/53387)) |
| [74e6ce5d23](https://github.com/angular/angular/commit/74e6ce5d233c8763b6c437fab5d81d7b89ae6cd4) | fix | add diagnostic for control flow that prevents content projection ([#53387](https://github.com/angular/angular/pull/53387)) |
| [6ec7a42b95](https://github.com/angular/angular/commit/6ec7a42b9578aa34a66bb7c81ac491bb18f98941) | fix | avoid conflicts with built-in global variables in for loop blocks ([#53319](https://github.com/angular/angular/pull/53319)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [0a53f96094](https://github.com/angular/angular/commit/0a53f9609462fc59cf20c7fe1436d153c23e0412) | fix | cleanup signal consumers for all views ([#53351](https://github.com/angular/angular/pull/53351)) |
| [4fc1581bbc](https://github.com/angular/angular/commit/4fc1581bbcd98e607eb2bbd9976c64c92a70d827) | fix | handle hydration of multiple nodes projected in a single slot ([#53270](https://github.com/angular/angular/pull/53270)) |
| [14e66533ec](https://github.com/angular/angular/commit/14e66533ec49184723a66652253e9ae863a972e0) | fix | support hydration for cases when content is re-projected using ng-template ([#53304](https://github.com/angular/angular/pull/53304)) |
| [8e366e8911](https://github.com/angular/angular/commit/8e366e8911434d5b91e83e215320caae72f6adf8) | fix | support swapping hydrated views in `@for` loops ([#53274](https://github.com/angular/angular/pull/53274)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [45064f1ae1](https://github.com/angular/angular/commit/45064f1ae1ceb9c5a1f1053077f77370e3e9fdb2) | fix | CF migration - ensure NgIfElse attributes are properly removed ([#53298](https://github.com/angular/angular/pull/53298)) |
| [a6275cfa54](https://github.com/angular/angular/commit/a6275cfa54d680d6612a1c91e5d3f6b86b828fbc) | fix | CF Migration - Fix case of aliases on i18n ng-templates preventing removal ([#53299](https://github.com/angular/angular/pull/53299)) |
| [58a96e0f50](https://github.com/angular/angular/commit/58a96e0f50f2d59376aa357188ad0792b39e4e70) | fix | CF Migration add support for ngIf with just a then ([#53297](https://github.com/angular/angular/pull/53297)) |
| [26e40c7f89](https://github.com/angular/angular/commit/26e40c7f8916612034b03dae4f74e53b61d39d86) | fix | CF Migration fix missing alias for bound ngifs ([#53296](https://github.com/angular/angular/pull/53296)) |
| [836aeba01d](https://github.com/angular/angular/commit/836aeba01db526676610802daff4e8ebca8cef1e) | fix | Change CF Migration ng-template placeholder generation and handling ([#53394](https://github.com/angular/angular/pull/53394)) |
| [72d22ba7ee](https://github.com/angular/angular/commit/72d22ba7eea87e2873a765aa69b3d923b9d6cc6d) | fix | fix regexp for else and then in cf migration ([#53257](https://github.com/angular/angular/pull/53257)) |
| [7a2facae8a](https://github.com/angular/angular/commit/7a2facae8af3240b21fc17857a770dad793b7b6d) | fix | handle aliases on bound ngIf migrations ([#53261](https://github.com/angular/angular/pull/53261)) |
| [5104a89b30](https://github.com/angular/angular/commit/5104a89b3035fb07ce23e09974dc9998ef6932ca) | fix | handle nested ng-template replacement safely in CF migration ([#53368](https://github.com/angular/angular/pull/53368)) |
| [2a4e3f5373](https://github.com/angular/angular/commit/2a4e3f5373dfbc2a1634d67e646c30d8bbe4fea8) | fix | handle templates outside of component in cf migration ([#53368](https://github.com/angular/angular/pull/53368)) |
| [0db75ab5b1](https://github.com/angular/angular/commit/0db75ab5b1c8c79a0e7ca1d6094092f0cb3e3939) | fix | remove setting that removes comments in CF migration ([#53350](https://github.com/angular/angular/pull/53350)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [13ade13a15](https://github.com/angular/angular/commit/13ade13a15f0c5b5f782d2fda0f7a96b3c606198) | fix | Ensure canMatch guards run on wildcard routes ([#53239](https://github.com/angular/angular/pull/53239)) |
<!-- CHANGELOG SPLIT MARKER -->
@@ -49,7 +189,7 @@
| [aab7fb8654](https://github.com/angular/angular/commit/aab7fb865486b94d6aabbe548894f8083f4e6cfe) | fix | Add ngForTemplate support to control flow migration ([#53076](https://github.com/angular/angular/pull/53076)) |
| [dbd6f386ea](https://github.com/angular/angular/commit/dbd6f386ea81e7f29bc1cb34fb8efbba8c290ea8) | fix | allows colons in ngIf else cases to migrate ([#53076](https://github.com/angular/angular/pull/53076)) |
| [5b9f896009](https://github.com/angular/angular/commit/5b9f896009d9c57c91d44e1cb7adf80c29fbfb4c) | fix | cf migration fix migrating empty switch default ([#53237](https://github.com/angular/angular/pull/53237)) |
| [2b3d3b0fe1](https://github.com/angular/angular/commit/2b3d3b0fe10134739ec9982a776837a273ec8e1d) | fix | CF migration log warning when collection aliasing detected in @for ([#53238](https://github.com/angular/angular/pull/53238)) |
| [2b3d3b0fe1](https://github.com/angular/angular/commit/2b3d3b0fe10134739ec9982a776837a273ec8e1d) | fix | CF migration log warning when collection aliasing detected in `@for` ([#53238](https://github.com/angular/angular/pull/53238)) |
| [dffeac8386](https://github.com/angular/angular/commit/dffeac8386bed73ef0b2337c33f122af9884bb82) | fix | cf migration removes unnecessary bound ngifelse attribute ([#53236](https://github.com/angular/angular/pull/53236)) |
| [00cb3339ba](https://github.com/angular/angular/commit/00cb3339bab738100e4164339420f2492be91fd1) | fix | control flow migration formatting fixes ([#53076](https://github.com/angular/angular/pull/53076)) |
| [c22af72f75](https://github.com/angular/angular/commit/c22af72f753b9ad3886561228a0cfd938a2a6de9) | fix | fix off by one issue with template removal in CF migration ([#53255](https://github.com/angular/angular/pull/53255)) |
@@ -64,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
@@ -130,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",
-67
View File
@@ -1,67 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
/src/assets/tutorials
/src/assets/previews
/TEMP
# Node
node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# Ignore ng-dev logs and user-config
.ng-dev.err*
.ng-dev.log
.ng-dev.user*
# System files
.DS_Store
Thumbs.db
# Yarn v2+. Zero installs disabled since we are not using PnP.
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
!.yarn/yarn.js
# Firebase
.firebase
*-debug.log
.runtimeconfig.json
-42
View File
@@ -1,42 +0,0 @@
{
// Default state for all rules
"default": true,
// MD013/line-length - Line length
"MD013": {
// Number of characters
"line_length": 750,
// Include code blocks
"code_blocks": false,
// Include tables
"tables": false
},
// MD034/bare-URL-used - Do not lint bare url - we display links in config code that does not match linter styles
"MD034": false,
// MD038 - Spaces inside code span elements - We display inline templates in codeblocks
"MD038": false,
// MD046/code-block-style - Do not lint inline code blocks - we display formatted CLI output in code that does not match linter styles
"MD046": false,
// MD049/emphasis-style - Emphasis style should be consistent
"MD049": false,
// TODO: Enable the following rules as MD improvements:
// MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content
"MD024": false,
// MD033/no-inline-html - Inline HTML
"MD033": false,
// MD051/link-fragments - Link fragments should be valid
"MD051": false,
// MD052/reference-links-images - Reference links and images should use a label that is defined
"MD052": false,
// MD037/no-space-in-emphasis - Inline lists (such as in tables) incorrectly trigger this warning.
"MD037": false
}
-1
View File
@@ -1 +0,0 @@
node_modules/
-2
View File
@@ -1,2 +0,0 @@
.yarn/releases/*.cjs
src/content/examples/**/*.*
-5
View File
@@ -1,5 +0,0 @@
# Ignoring documentation style examples
/src/content/api-examples
/src/content/examples
/src/content/tutorials
node_modules/
-130
View File
@@ -1,130 +0,0 @@
{
"customSyntax": "postcss-scss",
"defaultSeverity": "error",
"reportNeedlessDisables": true,
"reportInvalidScopeDisables": true,
"plugins": [
"./tools/stylelint/loader-rule.js",
"./tools/stylelint/no-concrete-rules.ts",
"./tools/stylelint/no-import.ts",
"./tools/stylelint/no-unused-import.ts",
"./tools/stylelint/selector-no-deep.ts",
"./tools/stylelint/single-line-comment-only.ts"
],
"rules": {
"@angular/selector-no-deep": true,
"@angular/no-unused-import": true,
"@angular/single-line-comment-only": true,
"@angular/no-import": true,
"color-hex-case": "lower",
"color-no-invalid-hex": true,
"function-calc-no-unspaced-operator": true,
"function-comma-space-after": "always-single-line",
"function-comma-space-before": "never",
"function-name-case": "lower",
"function-url-quotes": "always",
"function-url-scheme-disallowed-list": ["data"],
"function-whitespace-after": "always",
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"length-zero-no-unit": true,
"string-no-newline": true,
"string-quotes": "single",
"unit-case": "lower",
"unit-no-unknown": true,
"unit-allowed-list": [
"px",
"%",
"deg",
"s",
"ms",
"em",
"rem",
"vh",
"vw",
"vmin",
"fr",
"ch",
"lvh",
"svh",
"dvh"
],
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"shorthand-property-no-redundant-values": true,
"property-case": "lower",
"no-duplicate-at-import-rules": true,
"declaration-block-no-duplicate-properties": [
true,
{
"ignore": ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-trailing-semicolon": "always",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-semicolon-space-before": "never",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-newline-after": "always-multi-line",
"declaration-colon-space-after": "always-single-line",
"declaration-property-value-disallowed-list": [
{"/.*/": ["initial"]},
{"message": "The `initial` value is not supported in IE."}
],
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always-multi-line",
"block-opening-brace-newline-after": "always-multi-line",
"block-opening-brace-space-before": "always-multi-line",
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-max-id": 0,
"no-missing-end-of-source-newline": true,
"no-eol-whitespace": true,
"max-line-length": [
100,
{
"ignorePattern": "/https?://.*/"
}
],
"linebreaks": "unix",
"selector-class-pattern": [
"^_?(docs-|adev-|cm-|mat-|mdc-|xterm|ͼ3|ͼ2u|ͼ1|ng-|hljs-|as-|cdk-)",
{
"resolveNestedSelectors": true
}
]
},
"overrides": [
{
"files": ["projects/shared/src/lib/styles/_split.scss"],
"rules": {
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": ["ng-deep"]
}
]
}
}
]
}
+140
View File
@@ -0,0 +1,140 @@
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",
"tsconfig.app.json",
"tsconfig.json",
"tsconfig.worker.json",
] + glob(
["src/**/*"],
exclude = [
"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.
"src/generated/**/*",
],
)
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",
"@npm//@angular/common",
"@npm//@angular/compiler",
"@npm//@angular/compiler-cli",
"@npm//@angular/core",
"@npm//@angular/docs",
"@npm//@angular/forms",
"@npm//@angular/material",
"@npm//@angular/platform-browser",
"@npm//@angular/platform-server",
"@npm//@angular/router",
"@npm//gsap",
"@npm//marked",
"@npm//ngx-progressbar",
"@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",
srcs = APPLICATION_FILES,
)
architect(
name = "build",
args = [
"angular-dev:build",
"--output-path=build",
],
chdir = "$(RULEDIR)",
data = APPLICATION_DEPS + [
":application_files_bin",
],
output_dir = True,
)
architect(
name = "serve",
args = [
"angular-dev:serve",
"--poll=1000",
"--live-reload",
"--watch",
],
chdir = package_name(),
data = APPLICATION_DEPS + [
":application_files_bin",
],
)
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",
],
)
-3
View File
@@ -1,3 +0,0 @@
# Angular.dev: The new home for Angular developers (https://angular.dev)
Read more about our initial launch on the [Angular blog](https://goo.gle/angular-dot-dev).
+18 -56
View File
@@ -1,7 +1,7 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"$schema": "../node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"newProjectRoot": ".",
"projects": {
"angular-dev": {
"projectType": "application",
@@ -12,13 +12,16 @@
"changeDetection": "OnPush"
}
},
"root": "projects/angular-dev",
"root": ".",
"sourceRoot": "src",
"prefix": "app",
"prefix": "adev",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"externalDependencies": [
"path"
],
"outputPath": "dist/angular-dev",
"index": "src/index.html",
"browser": "src/main.ts",
@@ -32,36 +35,24 @@
"src/robots.txt",
"src/assets"
],
"styles": ["projects/shared/src/lib/styles/global-styles.scss"],
"styles": ["@angular/docs/styles/global-styles.scss"],
"scripts": [],
"webWorkerTsConfig": "tsconfig.worker.json"
"webWorkerTsConfig": "tsconfig.worker.json",
"optimization": {
"fonts": {
// TODO(josephperrott): enabled inline scripts
"inline": false
}
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "5kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
"sourceMap": true
}
},
"defaultConfiguration": "production"
@@ -97,44 +88,15 @@
"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": ["projects/shared/src/lib/styles/global-styles.scss"],
"styles": ["@angular/docs/styles/global-styles.scss"],
"scripts": [],
"webWorkerTsConfig": "tsconfig.worker.json"
}
}
}
},
"shared": {
"projectType": "library",
"root": "projects/shared",
"sourceRoot": "projects/shared/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "projects/shared/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/shared/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "projects/shared/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "projects/shared/tsconfig.spec.json",
"polyfills": ["zone.js", "zone.js/testing"]
}
}
}
}
},
"cli": {
+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;
}
-122
View File
@@ -1,122 +0,0 @@
{
"name": "blossom",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "yarn generate-all && ng serve",
"build": "ng build angular-dev",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"ng-dev": "cross-env TS_NODE_PROJECT=$PWD/.ng-dev/tsconfig.json TS_NODE_TRANSPILE_ONLY=1 node --no-warnings --loader ts-node/esm node_modules/@angular/ng-dev/bundles/cli.mjs",
"lint": "tslint -c tslint.json --project tsconfig.json && stylelint \"projects/**/*.+(css|scss)\" --config .stylelintrc.json && markdownlint \"projects/**/*.md\"",
"lint-fix": "markdownlint --fix '**/*.md'",
"format": "yarn ng-dev format changed && tslint -c tslint.json --fix --project tsconfig.json",
"serve:prerender": "http-server -c-l dist/angular-dev/browser",
"postinstall": "yarn generate-all",
"generate-all": "yarn generate-tutorials && yarn generate-docs && yarn generate-previews && yarn set-version && yarn generate-api && yarn generate-cli",
"generate-api": "ts-node-esm ./prerender/api-gen/index.ts all ./src/assets/content/api",
"generate-cli": "ts-node-esm ./prerender/api-gen/cli-index.ts all ./src/assets/content/cli",
"generate-docs": "ts-node-esm ./prerender/markdown-pipeline/index.mts",
"generate-previews": "ts-node-esm scripts/examples/generate-previews-components-map.mts",
"generate-tutorials": "ts-node scripts/tutorials/generate-tutorials.ts",
"check-links": "ts-node scripts/checks/check-links.ts",
"new-example": "ts-node-esm scripts/examples/create-example.mts",
"new-tutorial": "ts-node scripts/tutorials/new-tutorial.ts",
"new-playground-template": "ts-node scripts/tutorials/new-playground-template.ts",
"update-tutorials-package-lock": "ts-node scripts/tutorials/update-package-lock.ts",
"set-version": "ts-node-esm scripts/versions/set-current-version.mts",
"prepare": "husky install"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0-rc.1",
"@angular/cdk": "17.0.0-rc.1",
"@angular/common": "^17.0.0-rc.1",
"@angular/compiler": "^17.0.0-rc.1",
"@angular/core": "^17.0.0-rc.1",
"@angular/elements": "^17.0.0-rc.1",
"@angular/forms": "^17.0.0-rc.1",
"@angular/material": "17.0.0-rc.1",
"@angular/platform-browser": "^17.0.0-rc.1",
"@angular/platform-browser-dynamic": "^17.0.0-rc.1",
"@angular/platform-server": "^17.0.0-rc.1",
"@angular/router": "^17.0.0-rc.1",
"@angular/service-worker": "^17.0.0-rc.1",
"@codemirror/autocomplete": "^6.9.0",
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-angular": "^0.1.2",
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.3",
"@codemirror/lang-javascript": "^6.1.8",
"@codemirror/lang-sass": "^6.0.1",
"@codemirror/language": "^6.7.0",
"@codemirror/lint": "^6.4.0",
"@codemirror/search": "^6.4.0",
"@codemirror/state": "^6.2.1",
"@codemirror/tooltip": "^0.19.16",
"@codemirror/view": "^6.12.0",
"@lezer/highlight": "^1.1.6",
"@typescript/vfs": "^1.5.0",
"@webcontainer/api": "^1.1.5",
"algoliasearch": "^4.20.0",
"angular-split": "17.1.0",
"diff": "^5.1.0",
"express": "^4.15.2",
"gsap": "^3.12.2",
"highlight.js": "^11.8.0",
"jszip": "^3.10.1",
"ngx-progressbar": "^11.1.0",
"ogl": "^1.0.1",
"preact": "^10.17.1",
"preact-render-to-string": "^6.2.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.0.0-rc.3",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#bb8a1cf74bda7e7a193cc449ca9558bc3ce55ad0",
"@angular/cli": "17.0.0-rc.3",
"@angular/compiler-cli": "^17.0.0-rc.1",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#09c58eaf121b8e71edbe863c373854c0a221e290",
"@inquirer/prompts": "^3.0.2",
"@mermaid-js/mermaid-cli": "^10.3.1",
"@types/algoliasearch": "^4.0.0",
"@types/diff": "^5.0.3",
"@types/dom-navigation": "~1.0.0",
"@types/dom-view-transitions": "~1.0.1",
"@types/express": "^4.17.0",
"@types/jasmine": "~4.3.0",
"@types/jsdom": "^21.1.2",
"@types/marked": "^5.0.1",
"@types/node": "^18.11.9",
"cross-env": "^7.0.3",
"emoji-regex": "^10.2.1",
"firebase-tools": "latest",
"glob": "^10.2.7",
"html-entities": "^2.4.0",
"http-server": "^14.1.1",
"husky": "^8.0.0",
"jasmine-core": "~4.5.0",
"jsdom": "^23.0.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"markdownlint-cli": "^0.36.0",
"marked": "^5.1.1",
"ng-packagr": "17.0.0-next.1",
"postcss-scss": "^4.0.4",
"prettier": "^2.8.4",
"stylelint": "^14.14.0",
"ts-node": "^10.8.1",
"tslint": "^6.1.3",
"tslint-eslint-rules": "5.4.0",
"typescript": "~5.2.0",
"uuid": "^9.0.0",
"vrsource-tslint-rules": "6.0.0"
}
}
-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-shared';
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'}),
}));
}
@@ -1,35 +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 {dirname, join} from 'path';
import {fileURLToPath} from 'url';
// FOLDER NAMES
export const ASSETS_FOLDER_NAME = 'assets';
export const CONTENT_FOLDER_NAME = 'content';
export const EXAMPLES_FOLDER_NAME = 'examples';
export const TUTORIALS_FOLDER_NAME = 'tutorials';
// PATHS
export const BASE_PATH = dirname(fileURLToPath(import.meta.url));
export const ASSETS_EXAMPLES_PATH = 'assets/content/examples';
export const PROJECT_FOLDER_PATH = join(BASE_PATH, '../../src');
export const ASSETS_CONTENT_FOLDER_PATH = join(
PROJECT_FOLDER_PATH,
ASSETS_FOLDER_NAME,
CONTENT_FOLDER_NAME,
);
export const CONTENT_FOLDER_PATH = join(PROJECT_FOLDER_PATH, CONTENT_FOLDER_NAME);
export const EXAMPLES_FOLDER_PATH = join(CONTENT_FOLDER_PATH, EXAMPLES_FOLDER_NAME);
export const TUTORIALS_FOLDER_PATH = join(CONTENT_FOLDER_PATH, TUTORIALS_FOLDER_NAME);
// TODO: Update the branch/sha
// URLS
export const GITHUB_CONTENT_URL = 'https://github.com/angular/angular/blob/main/adev/src/content';
export const GITHUB_EDIT_CONTENT_URL =
'https://github.com/angular/angular/edit/main/adev/src/content';
@@ -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 {run} from '@mermaid-js/mermaid-cli';
import {readFile, writeFile} from 'fs/promises';
import {marked} from 'marked';
import {v4 as uuidv4} from 'uuid';
import {
DocsCodeMultifileToken,
DocsCodeToken,
DocsCodeTripleTickBlockToken,
} from './tranformations/docs-code.mjs';
import {generateStackblitzExample} from './../../scripts/examples/stackblitz-builder.mjs';
export const MERMAID_TEMP_FOLDER = 'TEMP';
const MERMAID_INPUT = `${MERMAID_TEMP_FOLDER}/mermaid-input-#{id}.mmd`;
const MERMAID_OUTPUT_SVG = `${MERMAID_TEMP_FOLDER}/mermaid-output-#{id}.svg`;
// If token type is 'docs-code' or 'docs-code-triple-tick' and language is 'mermaid' then we can try to render Mermaid svg.
export async function docsWalkTokens(walkToken: marked.Tokens.Generic): Promise<void> {
const token = walkToken as DocsCodeToken | DocsCodeTripleTickBlockToken | DocsCodeMultifileToken;
await handleMermaid(token);
await handleStackblitzExamples(token);
}
async function handleMermaid(
token: DocsCodeToken | DocsCodeMultifileToken | DocsCodeTripleTickBlockToken,
) {
if (
token.language === 'mermaid' &&
(token.type === 'docs-code' || token.type === 'docs-code-triple-tick')
) {
token.code = await renderMermaidToSvg(token.code);
}
}
async function handleStackblitzExamples(
token: DocsCodeToken | DocsCodeTripleTickBlockToken | DocsCodeMultifileToken,
) {
if (
(token.type !== 'docs-code' && token.type !== 'docs-code-multifile') ||
!token.preview ||
!token.path
) {
return;
}
const defaultCodeToken: marked.Tokens.Generic =
token.type === 'docs-code-multifile' ? token.paneTokens[0] : token;
const path: string = defaultCodeToken['diff'] ?? defaultCodeToken['path'];
const pathToExampleRule = /([^\/]+)\/(.+)/s;
const pathToExampleSegments = path.match(pathToExampleRule);
if (!pathToExampleSegments) {
return;
}
const exampleFolder = pathToExampleSegments[1];
const primaryFilePath = pathToExampleSegments[2];
await generateStackblitzExample(exampleFolder, primaryFilePath, defaultCodeToken['header']);
}
export async function renderMermaidToSvg(content: string): Promise<string> {
// Generate input and output path
const mermaidTempFileId = uuidv4();
const input = MERMAID_INPUT.replace('#{id}', mermaidTempFileId);
const output = MERMAID_OUTPUT_SVG.replace('#{id}', mermaidTempFileId) as `${string}.svg`;
// Create *.mmd file with provided content.
await writeFile(input, content);
// Execute rendering. The generated SVG file will be written in the output path.
await run(input, output, {
quiet: true,
puppeteerConfig: {headless: 1},
parseMMDOptions: {
mermaidConfig: {
themeCSS: `
background-color: var(--page-background) !important; // svg background color
g {
rect {
stroke: black !important; // border around the rectangles, same for dark/light theme
filter: drop-shadow(5px 5px 0px var(--vivid-pink));
}
}
.messageText, .pieTitleText {
fill: var(--primary-contrast) !important; // pie chart title text and line labels
}
.pieOuterCircle {
stroke-width: 1px;
}
.pieCircle {
stroke-width: 1.5px;
}
.legend {
rect {
filter: none;
opacity: 0.7;
}
text {
fill: var(--primary-contrast) !important; // legend label text color
}
}
.slice { // e.g. text on the pie charts
fill: var(--primary-contrast) !important;
}
.flowchart-link, line { // lines
stroke: var(--primary-contrast) !important;
}
.marker,
#statediagram-barbEnd,
.transition,
#arrowhead path { // arrows
stroke: var(--primary-contrast) !important;
fill: var(--primary-contrast) !important;
}
.cluster rect {
stroke: var(--primary-contrast) !important;
fill: var(--page-background) !important;
}
`,
theme: 'base',
themeVariables: {
fontFamily: 'sans-serif',
primaryColor: '#fff',
primaryBorderColor: '#000',
pie1: '#0546ff',
pie2: '#f637e3',
pie3: '#f11653',
pie4: '#8001c6',
pie5: '#00c572',
pie6: '#fe3700',
},
},
},
});
// Read the content of generated SVG file.
return await readFile(output, 'utf8');
}
@@ -1,22 +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 {resetHeaderIdsOfCurrentDocument} from './state.mjs';
/**
* Custom hooks for marked that will be used to post-transform markdown files with parent styles for Angular docs.
*/
export const hooks = {
preprocess(html: string): string {
resetHeaderIdsOfCurrentDocument();
return html;
},
postprocess(html: string): string {
return html;
},
};
-189
View File
@@ -1,189 +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 {existsSync} from 'fs';
import {copyFile, mkdir, readdir, rm, stat, writeFile} from 'fs/promises';
import {marked} from 'marked';
import {dirname, join} from 'path';
import {PROJECT_FOLDER_PATH} from './constants.mjs';
import {MERMAID_TEMP_FOLDER, docsWalkTokens} from './docs-walk-tokens.mjs';
import {hooks} from './hooks.mjs';
import {renderer} from './renderer.mjs';
import {setCurrentParsedFilePath} from './state.mjs';
import {docsAlertExtension} from './tranformations/docs-alert.mjs';
import {docsCalloutExtension} from './tranformations/docs-callout.mjs';
import {docsCardContainerExtension, docsCardExtension} from './tranformations/docs-card.mjs';
import {
docsCodeExtension,
docsCodeMultifileExtension,
docsTripleTickMarkdownCodeExtension,
} from './tranformations/docs-code.mjs';
import {docsDecorativeHeaderExtension} from './tranformations/docs-decorative-header.mjs';
import {docsPillExtension, docsPillRowExtension} from './tranformations/docs-pill.mjs';
import {docsVideoExtension} from './tranformations/docs-video.mjs';
import {docsStepExtension, docsWorkflowExtension} from './tranformations/docs-workflow.mjs';
import {retrieveAllMarkdownFiles} from './utils.mjs';
declare var logs: string[];
(globalThis as any).logs = [];
/**
* Fully configured processor instance.
*
* Marked was chosen as our processor as it is a popular and well supported by the community. Additionally,
* its processing of custom HTML allows for a more direct interactions rather than being done within a custom
* AST representation as other solutions provide.
*/
marked.use({
mangle: false,
headerIds: false,
renderer,
hooks,
extensions: [
/** Custom Extensions are @type marked.TokenizerAndRendererExtension but the this.renderer uses a custom Token that extends marked.Tokens.Generic which is not exported by @types/marked */
// @ts-ignore @types/marked
docsCardExtension,
// @ts-ignore @types/marked
docsCardContainerExtension,
// @ts-ignore @types/marked
docsCalloutExtension,
// @ts-ignore @types/marked
docsDecorativeHeaderExtension,
// @ts-ignore @types/marked
docsPillRowExtension,
// @ts-ignore @types/marked
docsPillExtension,
// @ts-ignore @types/marked
docsWorkflowExtension,
// @ts-ignore @types/marked
docsStepExtension,
// @ts-ignore @types/marked
docsCodeExtension,
// @ts-ignore @types/marked
docsCodeMultifileExtension,
// @ts-ignore @types/marked
docsTripleTickMarkdownCodeExtension,
// @ts-ignore @types/marked
docsAlertExtension,
// @ts-ignore @types/marked
docsVideoExtension,
],
// Marked will return a promise if the async option is true.
// The async option will tell marked to await any walkTokens functions before parsing the tokens and returning an HTML string.
// More details: https://marked.js.org/using_pro#async
async: true,
walkTokens: docsWalkTokens,
});
main();
async function main() {
/** Create temporary folder which helps to generate mermaid diagrams in the svg format */
await createTempMermaidFolder();
/** Generate Docs Content */
await generateContent(
join(PROJECT_FOLDER_PATH, 'content'),
join(PROJECT_FOLDER_PATH, 'assets', 'content'),
);
/** Copy the images from source content folder to assets */
await copyFolder(
join(PROJECT_FOLDER_PATH, 'content', 'images'),
join(PROJECT_FOLDER_PATH, 'assets', 'content', 'images'),
);
/** Remove temp folder */
await cleanUp();
}
async function generateContent(baseDir: string, distDir: string) {
const consoleTimeLabel = '\nMarkdown files processed in';
console.info(`Processing markdown files...`);
console.time(consoleTimeLabel);
/** List of all markdown files in the content directory. */
const files = await retrieveAllMarkdownFiles(baseDir);
/** These directories have no markdown destined to be in the docs */
const skipDirectories = ['examples'];
const filteredFiles = files.filter((f) => !skipDirectories.some((dir) => f.path.startsWith(dir)));
await Promise.all(
filteredFiles.map((file) =>
mkdir(join(distDir, dirname(file.path)), {recursive: true}).catch(console.error),
),
);
let hadWarnings = false;
for (let index = 0; index < filteredFiles.length; index++) {
const file = filteredFiles[index];
// Basic progress report
process.stdout.write('\r\x1b[K');
process.stdout.write(`${index}/${filteredFiles.length} - Processing ${file.path}`);
// keep in mind write doesn't write a newline like console.log does
// Logging is handcrafted because markedjs provides no solution
// Moreover it has no context of filename as it is only string based
// So we're using a global `logs` array
if (logs.length) {
console.warn('\n');
logs.forEach((log) => {
console.warn(`${file.path}: ${log}`);
});
console.warn('\n');
logs.length = 0;
hadWarnings = true;
}
setCurrentParsedFilePath(file.path);
const processedFile = await marked.parse(file.content, {async: true});
const htmlFilePath = `${file.path}.html`;
await writeFile(join(distDir, htmlFilePath), processedFile.toString());
}
if (hadWarnings) {
console.warn(
`\n\nYou can resolve the ambiguities by editing the mentioned file(s) and adding a specific link using the Markdown syntax.`,
);
}
console.timeEnd(consoleTimeLabel);
}
async function createTempMermaidFolder() {
if (!existsSync(MERMAID_TEMP_FOLDER)) {
await mkdir(MERMAID_TEMP_FOLDER);
}
}
async function cleanUp() {
if (existsSync(MERMAID_TEMP_FOLDER)) {
await rm(MERMAID_TEMP_FOLDER, {recursive: true});
}
}
async function copyFolder(source: string, destination: string) {
if (!existsSync(destination)) {
await mkdir(destination);
}
const files = await readdir(source);
for (const file of files) {
const sourcePath = join(source, file);
const destPath = join(destination, file);
const stats = await stat(sourcePath);
(await stats.isDirectory()) ? copyFolder(sourcePath, destPath) : copyFile(sourcePath, destPath);
}
}
@@ -1,3 +0,0 @@
# Code Regions
All the files in the regions folder were implemented based on angular.io: <https://github.com/angular/angular/tree/main/aio/tools/transforms/examples-package/services>.
@@ -1,13 +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
*/
// These kind of comments are used CSS and other languages that do not support inline comments
export const regionStartMatcher = /^\s*\/\*\s*#docregion\s*(.*)\s*\*\/\s*$/;
export const regionEndMatcher = /^\s*\/\*\s*#enddocregion\s*(.*)\s*\*\/\s*$/;
export const plasterMatcher = /^\s*\/\*\s*#docplaster\s*(.*)\s*\*\/\s*$/;
export const createPlasterComment = (plaster: string) => `/* ${plaster} */`;
@@ -1,14 +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
*/
// These kind of comments are used in HTML
export const regionStartMatcher = /^\s*<!--\s*#docregion\s*(.*?)\s*(?:-->)?\s*$/;
export const regionEndMatcher = /^\s*<!--\s*#enddocregion\s*(.*?)\s*-->\s*$/;
export const plasterMatcher = /^\s*<!--\s*#docplaster\s*(.*?)\s*-->\s*$/;
export const createPlasterComment = (plaster: string) => `<!-- ${plaster} -->`;
@@ -1,13 +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
*/
// These kind of comments are used in languages that do not support block comments, such as Jade
export const regionStartMatcher = /^\s*\/\/\s*#docregion\s*(.*)\s*$/;
export const regionEndMatcher = /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/;
export const plasterMatcher = /^\s*\/\/\s*#docplaster\s*(.*)\s*$/;
export const createPlasterComment = (plaster: string) => `// ${plaster}`;
@@ -1,13 +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
*/
// This comment type is used in C like languages such as JS, TS, etc
export const regionStartMatcher = /^\s*\/\/\s*#docregion\s*(.*)\s*$/;
export const regionEndMatcher = /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/;
export const plasterMatcher = /^\s*\/\/\s*#docplaster\s*(.*)\s*$/;
export const createPlasterComment = (plaster: string) => `/* ${plaster} */`;
@@ -1,13 +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
*/
// These type of comments are used in hash comment based languages such as bash and Yaml
export const regionStartMatcher = /^\s*#\s*#docregion\s*(.*)\s*$/;
export const regionEndMatcher = /^\s*#\s*#enddocregion\s*(.*)\s*$/;
export const plasterMatcher = /^\s*#\s*#docplaster\s*(.*)\s*$/;
export const createPlasterComment = (plaster: string) => `# ${plaster}`;
@@ -1,196 +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 * as blockC from './region-matchers/block-c.mjs';
import * as html from './region-matchers/html.mjs';
import * as inlineC from './region-matchers/inline-c.mjs';
import * as inlineCOnly from './region-matchers/inline-c-only.mjs';
import * as inlineHash from './region-matchers/inline-hash.mjs';
import {FileType, removeEslintComments} from './removeEslintComments.mjs';
const WHOLE_FILE_REGION_NAME = '';
const DEFAULT_PLASTER = '. . .';
const REGION_MATCHERS = {
ts: inlineC,
js: inlineC,
mjs: inlineCOnly,
es6: inlineC,
html: html,
svg: html,
css: blockC,
conf: inlineHash,
yaml: inlineHash,
yml: inlineHash,
sh: inlineHash,
jade: inlineCOnly,
pug: inlineCOnly,
json: inlineCOnly,
'json.annotated': inlineCOnly,
};
interface Region {
lines: string[];
open?: boolean;
ranges: {from: number; to?: number}[];
}
/**
* NOTE: We assume that the tag defining the beginning and end of the region will be in different lines in each case.
* For example, in HTML, we don't expect to have the following code on one line: <!-- docregion name -->content<!--enddocregion name-->
*/
export const regionParser = (contents: string, filePath: string) => {
const fileType: FileType | undefined = filePath?.split('.').pop() as FileType;
if (!fileType) {
throw new Error(`Incorrect file type for region parser: ${filePath}!`);
}
const regionMatcher = REGION_MATCHERS[fileType];
const openRegions: string[] = [];
const regionMap: Record<string, Region> = {};
let countOfRegionLines = 0;
if (regionMatcher) {
let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
const lines = removeEslintComments(contents, fileType)
.split(/\r?\n/)
.filter((line, index) => {
const startRegion = line.match(regionMatcher.regionStartMatcher);
const endRegion = line.match(regionMatcher.regionEndMatcher);
const updatePlaster = line.match(regionMatcher.plasterMatcher);
// start region processing
if (startRegion) {
// open up the specified region
handleStartRegion(
startRegion,
regionMap,
index,
countOfRegionLines,
plaster,
openRegions,
);
} else if (endRegion) {
// end region processing
handleEndRegion(openRegions, endRegion, regionMap, index, countOfRegionLines);
} else if (updatePlaster) {
// doc plaster processing
const plasterString = updatePlaster[1].trim();
plaster = plasterString ? regionMatcher.createPlasterComment(plasterString) : '';
} else {
// simple line of content processing
openRegions.forEach((regionName) => regionMap[regionName].lines.push(line));
// do not filter out this line from the content
return true;
}
// this line contained an annotation so let's filter it out
countOfRegionLines++;
return false;
});
if (!regionMap[WHOLE_FILE_REGION_NAME]) {
regionMap[WHOLE_FILE_REGION_NAME] = {lines, ranges: [{from: 1, to: lines.length + 1}]};
}
return {
contents: lines.join('\n'),
regionMap,
totalLinesCount: lines.length,
};
} else {
return {contents, regionMap, totalLinesCount: 0};
}
};
function handleStartRegion(
startRegion: RegExpMatchArray,
regionMap: Record<string, Region>,
index: number,
countOfRegionLines: number,
plaster: string,
openRegions: string[],
) {
const regionNames = getRegionNames(startRegion[1]);
if (regionNames.length === 0) {
regionNames.push(WHOLE_FILE_REGION_NAME);
}
for (const regionName of regionNames) {
const region = regionMap[regionName];
if (region) {
if (region.open) {
throw new Error(`Tried to open a region, named "${regionName}", that is already open`);
}
// Region is opened, set from range value.
region.open = true;
region.ranges.push({from: getFromRangeValue(index, countOfRegionLines)});
if (plaster) {
// Use the same indent as the docregion marker
const indent = startRegion[0].split(/[^ ]/, 1);
region.lines.push(indent + plaster);
}
} else {
regionMap[regionName] = {
lines: [],
open: true,
ranges: [
{
from: getFromRangeValue(index, countOfRegionLines),
},
],
};
}
openRegions.push(regionName);
}
}
function handleEndRegion(
openRegions: string[],
endRegion: RegExpMatchArray,
regionMap: Record<string, Region>,
index: number,
countOfRegionLines: number,
) {
if (openRegions.length === 0) {
throw new Error('Tried to close a region when none are open');
}
// close down the specified region (or most recent if no name is given)
const regionNames = getRegionNames(endRegion[1]);
if (regionNames.length === 0) {
regionNames.push(openRegions[openRegions.length - 1]);
}
for (const regionName of regionNames) {
const region = regionMap[regionName];
if (!region || !region.open) {
throw new Error(`Tried to close a region, named "${regionName}", that is not open`);
}
// Region is closed, we can define the last line number of the region
region.open = false;
region.ranges[region.ranges.length - 1].to = index - countOfRegionLines;
removeLast(openRegions, regionName);
}
}
function getFromRangeValue(index: number, countOfRegionLines: number): number {
return index - countOfRegionLines + 1;
}
function getRegionNames(input: string): string[] {
return input.trim() === '' ? [] : input.split(',').map((name) => name.trim());
}
function removeLast(array: string[], item: string): void {
const index = array.lastIndexOf(item);
array.splice(index, 1);
}
@@ -1,58 +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 type FileType =
| 'ts'
| 'js'
| 'mjs'
| 'es6'
| 'html'
| 'svg'
| 'css'
| 'conf'
| 'yaml'
| 'yml'
| 'sh'
| 'jade'
| 'pug'
| 'json'
| 'json.annotated';
export function removeEslintComments(input: string, fileType: FileType) {
if (!input || (fileType !== 'ts' && fileType !== 'js' && fileType !== 'html')) {
return input;
}
const regexForFileType = regexesForFileTypes[fileType];
return input.replace(regexForFileType, '');
}
const jsRegexes = [
/\/\/ *eslint-disable(?:-next-line)?(?: .*)?(?:\n *|$)/,
/\n? *\/\/ *eslint-(?:disable-line|enable)(?: .*)?(?=\n|$)/,
/\/\*\s*eslint-disable(?:-next-line)?(?: [\s\S]*?)?\*\/ *(?:\n *)?/,
/\n? *\/\*\s*eslint-(?:disable-line|enable)(?: [\s\S]*?)?\*\//,
];
const htmlRegexes = [
/<!--\s*eslint-disable(?:-next-line)?(?: [\s\S]*?)?--> *(?:\n *)?/,
/\n? *<!--\s*eslint-(?:disable-line|enable)(?: [\s\S]*?)?-->/,
];
const joinRegexes = (regexes: any) =>
new RegExp(regexes.map((regex: any) => `(?:${regex.source})`).join('|'), 'g');
const htmlRegex = joinRegexes(htmlRegexes);
// Note: the js regex needs to also include the html ones to account for inline templates in @Components
const jsRegex = joinRegexes([...jsRegexes, ...htmlRegexes]);
const regexesForFileTypes = {
js: jsRegex,
ts: jsRegex,
html: htmlRegex,
};
@@ -1,61 +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 {Renderer as MarkedRenderer, marked} from 'marked';
import {handleCode} from './tranformations/code.mjs';
import {handleEmoji} from './tranformations/emoji.mjs';
import {transformH1} from './tranformations/h1.mjs';
import {transformHeader} from './tranformations/header.mjs';
import {transformImage} from './tranformations/image.mjs';
import {transformLink} from './tranformations/link.mjs';
import {transformDocsList} from './tranformations/lists.mjs';
import {transformDocsTable} from './tranformations/table.mjs';
/**
* Custom renderer for marked that will be used to transform markdown files to HTML
* files that can be used in the Angular docs.
*/
export const renderer: Partial<MarkedRenderer> = {
// All headers have tabindex="-1"
// so that we can focus relevant sections on
// navigation for accessibility purposes.
heading(label: string, level: number) {
switch (level) {
// Custom H1 header element with page breadcrumb and docs edit button link to GitHub
case 1:
return transformH1(label);
// Custom headers include an anchor link and href
default:
return transformHeader(label, level);
}
},
// Custom table styling
table(header: string, body: string) {
return transformDocsTable(header, body);
},
// Custom list styling
list(body: string, ordered: boolean) {
return transformDocsList(body, ordered);
},
// Custom list styling
image(href: string | null, title: string | null, text: string) {
return transformImage(href, title, text);
},
// Handles special behavior for external links
link(href: string | null, title: string | null, text: string) {
return transformLink(href, title, text);
},
// Custom code span styling
codespan(this: marked.RendererThis, code: string): string {
return handleCode(code);
},
// Custom text styling
text(this: marked.RendererThis, code: string): string {
return handleEmoji(code);
},
};
@@ -1,58 +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_FOLDER_NAME} from './constants.mjs';
export const enum DocumentType {
DOCS,
TUTORIAL,
}
let currentParsedPath: string | null = null;
let currentParsedDocumentType: DocumentType = DocumentType.DOCS;
const headerIds = new Map<string, number>();
export const setCurrentParsedFilePath = (path: string) => {
currentParsedPath = path;
setCurrentParsedDocumentType(
path.startsWith(TUTORIALS_FOLDER_NAME) ? DocumentType.TUTORIAL : DocumentType.DOCS,
);
};
export const getCurrentParsedFilePath = (): string | null => currentParsedPath;
export const setCurrentParsedDocumentType = (type: DocumentType) => {
currentParsedDocumentType = type;
};
export const getCurrentParsedDocumentType = (): DocumentType => currentParsedDocumentType;
export const getHeaderId = (id: string): string => {
const numberOfHeaderOccurrencesInTheDocument = headerIds.get(id) ?? 0;
headerIds.set(id, numberOfHeaderOccurrencesInTheDocument + 1);
const cleanedUpId = id
.toLowerCase()
.replaceAll(/<code>(.*?)<\/code>/g, '$1') // remove <code>
.replaceAll(/<strong>(.*?)<\/strong>/g, '$1') // remove <strong>
.replaceAll(/<em>(.*?)<\/em>/g, '$1') // remove <em>
.replace(/\s|\//g, '-') // remove spaces and slashes
.replace(/gt;|lt;/g, '') // remove escaped < and >
.replace(/&#\d+;/g, '') // remove HTML entities
.replace(/[^0-9a-zA-Z\-]/g, ''); // only keep letters, digits & dashes
const headerId = numberOfHeaderOccurrencesInTheDocument
? `${cleanedUpId}-${numberOfHeaderOccurrencesInTheDocument}`
: cleanedUpId;
return headerId;
};
export const resetHeaderIdsOfCurrentDocument = (): void => {
headerIds.clear();
};
@@ -1,120 +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 {readFileSync, readdirSync} from 'fs';
import {join} from 'path';
import {ClassEntry, DocEntry, EntryType} from '../../api-gen/entities';
import {handleEmoji} from './emoji.mjs';
// global defined in the index
declare const logs: string[];
// API
interface EntryCollection {
moduleName: string;
entries: DocEntry[];
}
function parseEntryData(srcs: string[]): EntryCollection[] {
return srcs.map((jsonDataFilePath) => {
const fileContent = readFileSync(jsonDataFilePath, {encoding: 'utf8'});
return JSON.parse(fileContent) as EntryCollection;
});
}
interface ParsedEntry {
module: string;
members: string[];
type: EntryType;
isDeprecated: boolean;
}
let apiEntries: undefined | Map<string, Array<ParsedEntry>>;
function getApiEntries(): Map<string, Array<ParsedEntry>> {
const DEV_DATA_PATH = join('prerender', 'api-gen', 'dev-data', 'api');
const filePaths = readdirSync(DEV_DATA_PATH).map((fileName) => join(DEV_DATA_PATH, fileName));
const entryCollections: EntryCollection[] = parseEntryData(filePaths);
const entries = new Map<string, Array<ParsedEntry>>();
entryCollections.forEach((module) => {
const moduleName = module.moduleName.slice(9); // removing "@angular/"" from the name
module.entries.forEach((entry) => {
const members = (entry as ClassEntry).members?.map((m) => m.name) ?? [];
const parsedEntry = {
module: moduleName,
members,
type: entry.entryType,
isDeprecated: entry.jsdocTags.some((tag) => tag.name === 'deprecated'),
};
if (!entries.has(entry.name)) {
entries.set(entry.name, [parsedEntry]);
} else {
entries.get(entry.name)?.push(parsedEntry);
}
});
});
return entries;
}
function defaultCodeBlock(text: string) {
return `<code>${handleEmoji(text)}</code>`;
}
export function handleCode(text: string): string {
if (!apiEntries) {
apiEntries = getApiEntries();
}
// strip trailing the braces for functions,
// remove leading @ for decorators
let entity = text.replace(/^@|(\(\))$/g, '');
let method: string | undefined;
// We're parsing a class.method
if (entity.includes('.')) {
[entity, method] = entity.split('.');
}
if (apiEntries.has(entity)) {
const isDecorator = text.startsWith('@');
const entries = apiEntries.get(entity);
const nonDeprecated = entries?.find((e) => e.isDeprecated);
let entry: ParsedEntry | undefined;
if (entries?.length === 1) {
entry = entries[0];
} else if (entries?.length === 2 && nonDeprecated) {
// Sometime we move an entry from one package to another and deprecate the original entry.
// In this case we have 2 entries with one deprecated, we take the non-deprecated one
entry = nonDeprecated;
}
if (entry) {
if (isDecorator && entry.type !== 'decorator') {
return defaultCodeBlock(text);
}
if (entity && method && entry.members.includes(method)) {
return `<a href="api/${entry.module}/${entity}#${method}"><code>${text}</code></a>`;
}
return `<a href="api/${entry.module}/${entity}"><code>${text}</code></a>`;
} else {
logs.push(`Cannot resolve the ambiguity for: ${entity}`);
}
}
return defaultCodeBlock(text);
}
@@ -1,66 +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 {marked} from 'marked';
import {AlertSeverityLevel} from './enums/levels.mjs';
interface DocsAlertToken extends marked.Tokens.Generic {
type: 'docs-alert';
body: string;
severityLevel: string;
tokens: marked.Token[];
}
interface DocsAlert {
alert: RegExpExecArray | null;
severityLevel: string;
}
export const docsAlertExtension = {
name: 'docs-alert',
level: 'block',
tokenizer(this: marked.TokenizerThis, src: string): DocsAlertToken | undefined {
const match: DocsAlert = {
alert: null,
severityLevel: AlertSeverityLevel.HELPFUL,
};
for (let level in AlertSeverityLevel) {
// Capture group 1: all alert text content after the severity level
const rule = new RegExp('^s*' + level + ': (.*?)\n', 's');
const possibleMatch = rule.exec(src);
if (possibleMatch?.[1]) {
match.alert = possibleMatch;
match.severityLevel = level;
}
}
if (match?.alert) {
const token: DocsAlertToken = {
type: 'docs-alert',
raw: match.alert[0],
body: match.alert[1].trim(),
severityLevel: match.severityLevel,
tokens: [],
};
token.body = `**${
token.severityLevel === AlertSeverityLevel.TLDR ? 'TL;DR' : token.severityLevel
}:** ${token.body}`;
this.lexer.blockTokens(token.body, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsAlertToken) {
return `
<div class="docs-alert docs-alert-${token.severityLevel.toLowerCase()}">
${this.parser.parse(token.tokens)}
</div>
`;
},
};
@@ -1,70 +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 {marked} from 'marked';
import {CalloutSeverityLevel} from './enums/levels.mjs';
interface DocsCalloutToken extends marked.Tokens.Generic {
type: 'docs-callout';
title: string;
titleTokens: marked.Token[];
severityLevel: CalloutSeverityLevel;
body: string;
bodyTokens: marked.Token[];
}
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const calloutRule = /^<docs-callout([^>]*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s;
const titleRule = /title="([^"]*)"/;
const isImportantRule = /important/;
const isCriticalRule = /critical/;
export const docsCalloutExtension = {
name: 'docs-callout',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-callout/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCalloutToken | undefined {
const match = calloutRule.exec(src);
if (match) {
const attr = match[1].trim();
const title = titleRule.exec(attr);
let severityLevel = CalloutSeverityLevel.HELPFUL;
if (isImportantRule.exec(attr)) severityLevel = CalloutSeverityLevel.IMPORTANT;
if (isCriticalRule.exec(attr)) severityLevel = CalloutSeverityLevel.CRITICAL;
const body = match[2].trim();
const token: DocsCalloutToken = {
type: 'docs-callout',
raw: match[0],
severityLevel: severityLevel,
title: title ? title[1] : '',
titleTokens: [],
body: body ?? '',
bodyTokens: [],
};
this.lexer.inlineTokens(token.title, token.titleTokens);
this.lexer.blockTokens(token.body, token.bodyTokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCalloutToken) {
return `
<div class="docs-callout docs-callout-${token.severityLevel.toLowerCase()}">
<h3>${this.parser.parseInline(token.titleTokens)}</h3>
<p>${this.parser.parse(token.bodyTokens)}</p>
</div>
`;
},
};
@@ -1,169 +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 {marked} from 'marked';
import {targetForExternalHref} from './helpers.mjs';
import {PROJECT_FOLDER_PATH} from '../constants.mjs';
import {join} from 'path';
import {readFileSync} from 'fs';
interface DocsCardContainerToken extends marked.Tokens.Generic {
type: 'docs-card-container';
cards: string;
tokens: marked.Token[];
}
const cardContainerRule = /^<docs-card-container>(.*?)<\/docs-card-container>/s;
export const docsCardContainerExtension = {
name: 'docs-card-container',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-card-container/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCardContainerToken | undefined {
const match = cardContainerRule.exec(src);
if (match) {
const body = match[1];
const token: DocsCardContainerToken = {
type: 'docs-card-container',
raw: match[0],
cards: body ?? '',
tokens: [],
};
this.lexer.blockTokens(token.cards, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCardContainerToken) {
return `
<div class="docs-card-grid">
${this.parser.parse(token.tokens)}
</div>
`;
},
};
interface DocsCardToken extends marked.Tokens.Generic {
type: 'docs-card';
title: string;
body: string;
link?: string;
href?: string;
imgSrc?: string;
tokens: marked.Token[];
}
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const cardRule = /^[^<]*<docs-card\s([^>]*)>((?:.(?!\/docs-card))*)<\/docs-card>/s;
const titleRule = /title="([^"]*)"/;
const linkRule = /link="([^"]*)"/;
const hrefRule = /href="([^"]*)"/;
const imgSrcRule = /imgSrc="([^"]*)"/;
export const docsCardExtension = {
name: 'docs-card',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-card\s*/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCardToken | undefined {
const match = cardRule.exec(src);
if (match) {
const attr = match[1].trim();
const title = titleRule.exec(attr);
const link = linkRule.exec(attr);
const href = hrefRule.exec(attr);
const imgSrc = imgSrcRule.exec(attr);
const body = match[2].trim();
const token: DocsCardToken = {
type: 'docs-card',
raw: match[0],
title: title ? title[1] : '',
body: body ?? '',
href: href ? href[1] : undefined,
link: link ? link[1] : undefined,
imgSrc: imgSrc ? imgSrc[1] : undefined,
tokens: [],
};
this.lexer.blockTokens(token.body, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCardToken) {
return token.imgSrc ? getCardWithSvgIllustration(this, token) : getStandardCard(this, token);
},
};
function getStandardCard(renderer: marked.RendererThis, token: DocsCardToken) {
if (token.href) {
return `
<a href="${token.href}" ${targetForExternalHref(token.href)} class="docs-card">
<div>
<h3>${token.title}</h3>
<p>${renderer.parser.parse(token.tokens)}</p>
</div>
<span>${token.link ? token.link : 'Learn more'}</span>
</a>
`;
}
return `
<div class="docs-card">
<div>
<h3>${token.title}</h3>
<p>${renderer.parser.parse(token.tokens)}</p>
</div>
${token.link ? `<span>${token.link}</span>` : ''}
</div>
`;
}
function getCardWithSvgIllustration(renderer: marked.RendererThis, token: DocsCardToken) {
// We can assume that all illustrations are svg files
// We need to read svg content, instead of renering svg with `img`,
// cause we would like to use CSS variables to support dark and light mode.
const illustration = getSvgIllustration(token.imgSrc!);
if (token.href) {
return `
<a href="${token.href}" ${targetForExternalHref(
token.href,
)} class="docs-card docs-card-with-svg">
${illustration}
<div class="docs-card-text-content">
<div>
<h3>${token.title}</h3>
${renderer.parser.parse(token.tokens)}
</div>
<span>${token.link ? token.link : 'Learn more'}</span>
</div>
</a>
`;
}
return `
<div class="docs-card docs-card-with-svg">
${illustration}
<div class="docs-card-text-content">
<h3>${token.title}</h3>
${renderer.parser.parse(token.tokens)}
</div>
</div>
`;
}
function getSvgIllustration(path: string): string {
return readFileSync(join(PROJECT_FOLDER_PATH, path), {encoding: 'utf-8'});
}
@@ -1,445 +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 {marked} from 'marked';
import hljs from 'highlight.js';
import * as diff from 'diff';
import {join} from 'path';
import {readFileSync} from 'fs';
import {decode} from 'html-entities';
import {regionParser} from '../regions/region-parser.mjs';
import {EXAMPLES_FOLDER_PATH, TUTORIALS_FOLDER_PATH} from '../constants.mjs';
import {DocumentType, getCurrentParsedDocumentType} from '../state.mjs';
/** Marked token for a multifile custom docs element. */
export interface DocsCodeMultifileToken extends marked.Tokens.Generic {
type: 'docs-code-multifile';
// The example path used for linking to Stackblitz or rendering a preview
path: string | undefined;
// The raw nested Markdown of <docs-code> examples in the multifile example
panes: string;
// The DocsCodeToken of the nested <docs-code> examples
paneTokens: marked.Token[];
// True if we should display preview
preview: boolean;
}
/** Marked token for a custom docs element. */
export interface DocsCodeToken extends marked.Tokens.Generic {
type: 'docs-code';
// Nested code OR the code from the optional file path
code: string;
// The example file path
path: string | undefined;
// The example display header
header: string | undefined;
// Code language
language: string | undefined;
// True if styled with line numbers
linenums: boolean | undefined;
// The lines to display highlighting on
highlight: string | undefined;
// The example path to determine diff (lines added/removed)
diff: string | undefined;
// The lines viewable in collapsed view
visibleLines: string | undefined;
// The name of the viewable region in the collapsed view
visibleRegion: string | undefined;
// True if we should display preview
preview: boolean;
}
export interface DocsCodeTripleTickBlockToken extends marked.Tokens.Generic {
type: 'docs-code-triple-tick';
// Nested code
code: string;
// Code language
language: string | undefined;
}
interface DiffMetadata {
code: string;
linesAdded: number[];
linesRemoved: number[];
}
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const multiFileCodeRule = /^\s*<docs-code-multifile(.*?)>(.*?)<\/docs-code-multifile>/s;
const singleFileSelfClosingCodeRule = /^\s*<docs-code\s([^>]*)((?:.(?!\/>))*)\/>/s;
const singleFileCodeRule = /^\s*<docs-code\s([^>]*)>((?:.(?!\/docs-code))*)<\/docs-code>/s;
// TODO: use regex for code implemented in the marked package: https://github.com/markedjs/marked/blob/4e6acc8b8517eafe0036a914f58b6f53d4b12ca6/src/rules.ts#L72C1-L73C1
const tripleTickCodeRule = /^\s*`{3}(\w+)[\r\n]+(.*?)[\r\n]+`{3}/s;
const pathRule = /path="([^"]*)"/;
const headerRule = /header="([^"]*)"/;
const linenumsRule = /linenums/;
const highlightRule = /highlight="([^"]*)"/;
const diffRule = /diff="([^"]*)"/;
const languageRule = /language="([^"]*)"/;
const visibleLinesRule = /visibleLines="([^"]*)"/;
const visibleRegionRule = /visibleRegion="([^"]*)"/;
const previewRule = /preview/;
const lineNumberClassName: string = 'hljs-ln-number';
const lineMultifileClassName: string = 'hljs-ln-line';
const lineAddedClassName: string = 'add';
const lineRemovedClassName: string = 'remove';
const lineHighlightedClassName: string = 'highlighted';
const mermaidClassName = 'mermaid';
export const docsCodeMultifileExtension = {
name: 'docs-code-multifile',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-code-multifile/)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCodeMultifileToken | undefined {
const match = multiFileCodeRule.exec(src);
if (match) {
const attr = match[1].trim();
const path = pathRule.exec(attr);
const preview = previewRule.exec(attr) ? true : false;
const token: DocsCodeMultifileToken = {
type: 'docs-code-multifile',
raw: match[0],
path: path?.[1],
panes: match[2].trim(),
paneTokens: [],
preview: preview,
};
this.lexer.blockTokens(token.panes, token.paneTokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCodeMultifileToken) {
const attributes = getAttribute('path', token.path) + getAttribute('preview', token.preview);
return `
<div class="docs-code-multifile"${attributes}>
${this.parser.parse(token.paneTokens)}
</div>
`;
},
};
export const docsCodeExtension = {
name: 'docs-code',
level: 'block',
start(src: string) {
return src.match(/^<docs-code\s/)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCodeToken | undefined {
const code = singleFileCodeRule.exec(src);
const selfClosingCode = singleFileSelfClosingCodeRule.exec(src);
const match = selfClosingCode ?? code;
if (match) {
const attr = match[1].trim();
const path = pathRule.exec(attr);
const header = headerRule.exec(attr);
const linenums = linenumsRule.exec(attr);
const highlight = highlightRule.exec(attr);
const diff = diffRule.exec(attr);
const language = languageRule.exec(attr);
const visibleLines = visibleLinesRule.exec(attr);
const visibleRegion = visibleRegionRule.exec(attr);
const preview = previewRule.exec(attr) ? true : false;
let code = match[2].trim();
if (path && path[1]) {
code = getCodeFromPath(path[1]);
}
const token: DocsCodeToken = {
type: 'docs-code',
raw: match[0],
code: code,
path: path?.[1],
header: header?.[1],
linenums: !!linenums,
highlight: highlight?.[1],
diff: diff?.[1],
language: language?.[1],
visibleLines: visibleLines?.[1],
visibleRegion: visibleRegion?.[1],
preview: preview,
};
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCodeToken) {
return formatDocsCode(token);
},
};
export const docsTripleTickMarkdownCodeExtension = {
name: 'docs-code-triple-tick',
level: 'block',
start(src: string) {
return src.match(/^(```)\s/)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsCodeTripleTickBlockToken | undefined {
const match = tripleTickCodeRule.exec(src);
if (match) {
const token: DocsCodeTripleTickBlockToken = {
raw: match[0],
type: 'docs-code-triple-tick',
code: match[2],
language: match[1],
};
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsCodeTripleTickBlockToken) {
return formatDocsCode({code: token.code, language: token.language});
},
};
function formatDocsCode(token: Partial<DocsCodeToken>): string {
let diffData: DiffMetadata | null = null;
if (!token.code) {
throw new Error('Undefined code in DocsCodeToken!');
}
let code = token.code;
if (token.diff) {
diffData = getDiffData(token.diff, code);
code = diffData.code;
}
const highlightedLines = token.highlight ? getRangeValues(token.highlight) : [];
const extractRegionsResult = extractRegions(token, code);
code = extractRegionsResult.code;
// Add highlighting based on the code language
code = getHighlightedCode(token.language, code, diffData, highlightedLines, !!token.linenums);
const attributes =
getAttribute(
'visibleLines',
getRangeValues(extractRegionsResult.visibleLines ?? token.visibleLines)?.toString(),
) +
getAttribute('path', token.diff ?? token.path) +
getAttribute('preview', token.preview) +
getAttribute('header', token.header) +
getAttribute('mermaid', token.language === mermaidClassName);
const classes = getClass('shell', token.language == 'shell');
const header = token.header ? `<div class="docs-code-header"><h3>${token.header}</h3></div>` : '';
return `
<div class="docs-code${classes}"${attributes}>
${header}
<pre class="adev-mini-scroll-track">
<code>${code}</code>
</pre>
</div>
`;
}
function getCodeFromPath(path: string): string {
const rootFolder =
getCurrentParsedDocumentType() === DocumentType.DOCS
? EXAMPLES_FOLDER_PATH
: TUTORIALS_FOLDER_PATH;
return readFileSync(join(rootFolder, path), {encoding: 'utf-8'});
}
function getHighlightedCode(
language: string | undefined,
code: string,
diffData: DiffMetadata | null,
highlightedLines: number[] | null,
displayLineNums: boolean,
): string {
if (language == 'none' || language == 'file' || language == mermaidClassName || !highlightedLines)
return code;
// Decode the code content to replace HTML entities to characters
const decodedCode = decode(code);
const highlightResult = language
? hljs.highlight(decodedCode, {language})
: hljs.highlightAuto(decodedCode);
return appendModifierClassesToCodeLinesElements(
highlightResult.value,
diffData,
highlightedLines,
displayLineNums,
);
}
function appendModifierClassesToCodeLinesElements(
htmlString: string,
diffData: DiffMetadata | null,
highlightedLines: number[],
displayLineNums: boolean,
): string {
const lines = getLines(htmlString);
let finalHtml = '';
let lineIndex = 0;
let resultFileLineIndex = 1;
const linesCount = lines.length;
if (linesCount === 0) {
return htmlString;
}
do {
const isRemovedLine = diffData?.linesRemoved.includes(lineIndex);
const isAddedLine = diffData?.linesAdded.includes(lineIndex);
const isHighlighted = highlightedLines.includes(lineIndex);
const statusClasses = `${getClass(lineAddedClassName, isAddedLine)}${getClass(
lineRemovedClassName,
isRemovedLine,
)}${getClass(lineHighlightedClassName, isHighlighted)}`;
if (displayLineNums) {
if (isRemovedLine) {
finalHtml += `<span role="presentation" class="${lineNumberClassName}${statusClasses}">-</span>`;
} else {
finalHtml += `<span role="presentation" class="${lineNumberClassName}${statusClasses}">${
isAddedLine ? '+' : resultFileLineIndex
}</span>`;
resultFileLineIndex++;
}
}
finalHtml += `<div class="${lineMultifileClassName}${statusClasses}">${lines[lineIndex]}</div>`;
lineIndex++;
} while (lineIndex < linesCount);
return finalHtml;
}
function getDiffData(diffPath: string, code: string): DiffMetadata {
const changedCode = getCodeFromPath(diffPath);
const change = diff.diffLines(code, changedCode);
const getLinesRange = (start: number, count: number): number[] =>
Array.from(Array(count).keys()).map((i) => i + start);
let processedLines = 0;
return change.reduce(
(prev: DiffMetadata, part: diff.Change) => {
const diff: DiffMetadata = {
code: `${prev.code}${part.value}`,
linesAdded: part.added
? [...prev.linesAdded, ...getLinesRange(processedLines, part.count ?? 0)]
: prev.linesAdded,
linesRemoved: part.removed
? [...prev.linesRemoved, ...getLinesRange(processedLines, part.count ?? 0)]
: prev.linesRemoved,
};
processedLines += part.count ?? 0;
return diff;
},
{
code: '',
linesAdded: [],
linesRemoved: [],
},
);
}
function extractRegions(
token: Partial<DocsCodeToken>,
code: string,
): {code: string; visibleLines?: string} {
if (!token.path) return {code};
const result = regionParser(code, token.path);
if (token.visibleRegion) {
const region = result.regionMap[token.visibleRegion];
if (!region) throw new Error(`Cannot find ${token.visibleRegion} in ${token.path}!`);
return {
code: result.contents,
visibleLines: `[${region.ranges.map(
(range) => `[${range.from}, ${range.to ?? result.totalLinesCount + 1}]`,
)}]`,
};
}
return {
code: result.contents,
};
}
function getLines(text: string): string[] {
if (text.length === 0) {
return [];
}
return text.split(/\r\n|\r|\n/g);
}
function getAttribute(name: string, value: boolean | number | string | undefined): string {
return value ? ` ${name}="${value}"` : '';
}
function getClass(name: string, value: boolean | number | string | undefined): string {
return value ? ` ${name}` : ``;
}
/**
* The function used to generate ranges of highlighted or visible lines in code blocks
*/
function getRangeValues(token?: string): number[] | null {
const getAllValuesFromRange = (range: any[]) => {
const [start, end] = range;
for (let i = start; i <= end; i++) {
result.push(i - 1);
}
};
if (!token) {
return null;
}
let result: number[] = [];
try {
const boundaryValueArray = JSON.parse(token);
if (!Array.isArray(boundaryValueArray)) {
throw new Error('Provided token has wrong format!\n', boundaryValueArray);
}
// Flat Array
if (
boundaryValueArray.length === 2 &&
!Array.isArray(boundaryValueArray[0]) &&
!Array.isArray(boundaryValueArray[1])
) {
getAllValuesFromRange(boundaryValueArray);
} else {
for (const range of boundaryValueArray) {
if (Array.isArray(range) && range.length === 2) {
getAllValuesFromRange(range);
} else if (!Number.isNaN(range)) {
result.push(Number(range - 1));
} else {
throw new Error('Input has wrong format!\n', range);
}
}
}
return result;
} catch {
return null;
}
}
@@ -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.io/license
*/
import {marked} from 'marked';
import {getPageTitle} from './h1.mjs';
import {readFileSync} from 'fs';
import {join} from 'path';
import {PROJECT_FOLDER_PATH} from '../constants.mjs';
interface DocsDecorativeHeaderToken extends marked.Tokens.Generic {
type: 'docs-decorative-header';
title: string;
imgSrc: string;
body: string;
}
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const decorativeHeaderRule =
/^[^<]*<docs-decorative-header\s([^>]*)>((?:.(?!\/docs-decorative-header))*)<\/docs-decorative-header>/s;
const imgSrcRule = /imgSrc="([^"]*)"/;
const titleRule = /title="([^"]*)"/;
export const docsDecorativeHeaderExtension = {
name: 'docs-decorative-header',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-decorative-header\s*/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsDecorativeHeaderToken | undefined {
const match = decorativeHeaderRule.exec(src);
if (match) {
const attr = match[1].trim();
const body = match[2].trim();
const imgSrc = imgSrcRule.exec(attr);
const title = titleRule.exec(attr);
const token: DocsDecorativeHeaderToken = {
type: 'docs-decorative-header',
raw: match[0],
title: title ? title[1] : '',
imgSrc: imgSrc ? imgSrc[1] : '../assets/images/globe.svg',
body: body ?? '',
};
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsDecorativeHeaderToken) {
// We can assume that all illustrations are svg files
// We need to read svg content, instead of renering svg with `img`,
// cause we would like to use CSS variables to support dark and light mode.
const illustration = getSvgIllustration(token.imgSrc);
return `
<div class="docs-decorative-header-container">
<div class="docs-decorative-header">
<div class="docs-header-content">
<docs-breadcrumb></docs-breadcrumb>
${getPageTitle(token.title)}
<p>${token.body}</p>
</div>
<!-- illustration -->
${illustration}
</div>
</div>
`;
},
};
function getSvgIllustration(path: string): string {
return readFileSync(join(PROJECT_FOLDER_PATH, path), {encoding: 'utf-8'});
}
@@ -1,103 +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 {marked} from 'marked';
import {isExternalLink, targetForExternalHref} from './helpers.mjs';
interface DocsPillRowToken extends marked.Tokens.Generic {
type: 'docs-pill-row';
pills: string;
tokens: marked.Token[];
}
// Capture group 1: all content between the open and close tags
const pillRowRule = /^\s*<docs-pill-row>((?:.(?!docs-pill-row))*)<\/docs-pill-row>/s;
export const docsPillRowExtension = {
name: 'docs-pill-row',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-pill-row/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsPillRowToken | undefined {
const match = pillRowRule.exec(src);
if (match) {
const body = match[1];
const token: DocsPillRowToken = {
type: 'docs-pill-row',
raw: match[0],
pills: body ?? '',
tokens: [],
};
this.lexer.inlineTokens(token.pills, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsPillRowToken) {
return `
<nav class="docs-pill-row">
${this.parser.parseInline(token.tokens)}
</nav>
`;
},
};
interface DocsPillToken extends marked.Tokens.Generic {
type: 'docs-pill';
title: string;
href: string;
tokens: marked.Token[];
}
// Capture group 1: all attributes on the docs-pill tag
const pillRule = /^\s*<docs-pill\s((?:.(?!\n))*)\/>/s;
const titleRule = /title="([^"]*)"/;
const hrefRule = /href="([^"]*)"/;
export const docsPillExtension = {
name: 'docs-pill',
level: 'inline',
start(src: string) {
return src.indexOf('<docs-pill ');
},
tokenizer(this: marked.TokenizerThis, src: string): DocsPillToken | undefined {
const match = pillRule.exec(src);
if (match) {
const attr = match[1].trim();
const title = titleRule.exec(attr);
const href = hrefRule.exec(attr);
const token: DocsPillToken = {
type: 'docs-pill',
raw: match[0],
title: title ? title[1] : '',
href: href ? href[1] : '',
tokens: [],
};
this.lexer.inlineTokens(token.title, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsPillToken) {
return `
<a class="docs-pill" href="${token.href}" ${targetForExternalHref(token.href)}>
${this.parser.parseInline(token.tokens)}
${
isExternalLink(token.href)
? '<docs-icon class="docs-icon-small">open_in_new</docs-icon>'
: ''
}
</a>
`;
},
};
@@ -1,69 +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 {marked} from 'marked';
interface DocsVideoToken extends marked.Tokens.Generic {
type: 'docs-video';
src: string;
title: string | undefined;
}
// Capture group 1: all attributes on the opening tag
const videoRule = /^<docs-video([^>]*)\/>/s;
const srcRule = /src="([^"]*)"/;
const titleRule = /title="([^"]*)"/;
const validYTUrlPrefix = 'https://www.youtube.com/embed/';
export const docsVideoExtension = {
name: 'docs-video',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-video/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsVideoToken | undefined {
const match = videoRule.exec(src);
if (match) {
const attr = match[1].trim();
const src = srcRule.exec(attr);
const title = titleRule.exec(attr);
if (src) {
return {
type: 'docs-video',
raw: match[0],
src: src?.[1],
title: title?.[1],
};
}
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsVideoToken) {
if (!token.src.startsWith(validYTUrlPrefix)) {
process.stdout.write(
`\n<docs-video> cannot load: ${token.src}. YouTube Player API expects src to begin with ${validYTUrlPrefix}.\n`,
);
}
return `
<div class="docs-video-container">
<iframe
class="docs-video"
src="${token.src}"
${token.title ? `title="${token.title}"` : ''}
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
credentialless
title="Video player"
></iframe>
</div>
`;
},
};
@@ -1,99 +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 {marked} from 'marked';
import {transformHeader} from './header.mjs';
interface DocsWorkflowToken extends marked.Tokens.Generic {
type: 'docs-workflow';
steps: string;
tokens: marked.Token[];
}
// Capture group 1: all content between the open and close tags
const workflowRule = /^<docs-workflow>(.*?)<\/docs-workflow>/s;
export const docsWorkflowExtension = {
name: 'docs-workflow',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-workflow/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsWorkflowToken | undefined {
const match = workflowRule.exec(src);
if (match) {
const steps = match[1];
const token: DocsWorkflowToken = {
type: 'docs-workflow',
raw: match[0],
steps: steps ?? '',
tokens: [],
};
this.lexer.blockTokens(token.steps, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsWorkflowToken) {
return `
<ol class="docs-steps">
${this.parser.parse(token.tokens)}
</ol>
`;
},
};
interface DocsStepToken extends marked.Tokens.Generic {
type: 'docs-step';
title: string;
body: string;
tokens: marked.Token[];
}
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const stepRule = /^\s*<docs-step([^>]*)>((?:.(?!\/docs-step))*)<\/docs-step>/s;
const titleRule = /title="([^"]*)"/;
export const docsStepExtension = {
name: 'docs-step',
level: 'block',
start(src: string) {
return src.match(/^\s*<docs-step/m)?.index;
},
tokenizer(this: marked.TokenizerThis, src: string): DocsStepToken | undefined {
const match = stepRule.exec(src);
if (match) {
const attr = match[1].trim();
const title = titleRule.exec(attr);
const body = match[2].trim();
const token: DocsStepToken = {
type: 'docs-step',
raw: match[0],
title: title ? title[1] : '',
body: body,
tokens: [],
};
this.lexer.blockTokens(token.body, token.tokens);
return token;
}
return undefined;
},
renderer(this: marked.RendererThis, token: DocsStepToken) {
return `
<li>
<span class="docs-step-number" aria-hidden="true"></span>
${transformHeader(token.title, 3)}
${this.parser.parse(token.tokens)}
</li>
`;
},
};
@@ -1,19 +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 emojiRegex from 'emoji-regex';
const UNICODE_EMOJI_REGEX = /&#x[\dA-Fa-f]+;/g;
const regex = emojiRegex();
export function handleEmoji(text: string): string {
return text.match(regex) || text.match(UNICODE_EMOJI_REGEX)
? `<span class="docs-emoji">${text}</span>`
: text;
}
@@ -1,26 +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
*/
// Enum with the severity levels for alerts and callouts
export enum CalloutSeverityLevel {
HELPFUL = 'HELPFUL',
IMPORTANT = 'IMPORTANT',
CRITICAL = 'CRITICAL',
}
export enum AlertSeverityLevel {
Note = 'Note',
Tip = 'Tip',
TODO = 'TODO',
QUESTION = 'QUESTION',
Summary = 'Summary',
TLDR = 'TLDR',
CRITICAL = 'CRITICAL',
IMPORTANT = 'IMPORTANT',
HELPFUL = 'HELPFUL',
}
@@ -1,39 +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 {GITHUB_EDIT_CONTENT_URL} from '../constants.mjs';
import {DocumentType, getCurrentParsedDocumentType, getCurrentParsedFilePath} from '../state.mjs';
export function transformH1(text: string) {
// For tutorials pages, we don't have to display docs-breadcrumb
return `
<!-- Document Header -->
<header class="docs-header">
${
getCurrentParsedDocumentType() === DocumentType.TUTORIAL
? ''
: '<docs-breadcrumb></docs-breadcrumb>'
}
${getPageTitle(text)}
</header>
`;
}
// TODO: Add back pencil icon once code is open sourced
export function getPageTitle(text: string): string {
return `
<!-- Page title -->
<div class="docs-page-title">
<h1 tabindex="-1">${text}</h1>
<a class="docs-github-links" target="_blank" href="${GITHUB_EDIT_CONTENT_URL}/${getCurrentParsedFilePath()}" title="Edit this page" aria-label="Edit this page">
<!-- Pencil -->
<!-- <docs-icon role="presentation">edit</docs-icon> -->
</a>
</div>`;
}
@@ -1,26 +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 {getHeaderId} from '../state.mjs';
import {mapBackticksToCodeElement} from '../utils.mjs';
export function transformHeader(text: string, level: number) {
// Nested anchor elements are invalid in HTML
// They might happen when we have a code block in a heading
// regex aren't perfect for that but this one should be "good enough"
const regex = /<a\s+(?:[^>]*?\s+)?href.*?>(.*?)<\/a>/gi;
const anchorLessText = text.replace(regex, '$1');
const link = getHeaderId(anchorLessText);
const label = mapBackticksToCodeElement(anchorLessText);
return `
<h${level} id="${link}">
<a href="#${link}" class="docs-anchor" tabindex="-1">${label}</a>
</h${level}>`;
}
@@ -1,13 +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 isExternalLink = (href: string | undefined | null) =>
href?.startsWith('http') ?? false;
export const targetForExternalHref = (href: string | undefined | null) =>
isExternalLink(href) ? `target="_blank"` : '';
@@ -1,17 +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 {normalize} from 'path';
// TODO: Make sure we're setting up correct image optimization
export function transformImage(href: string | null, title: string | null, text: string) {
const src = href?.startsWith('./') ? `assets/content/${normalize(href)}` : href;
return `
<img src="${src}" alt="${text}" title="${title}" class="docs-image">
`;
}
@@ -1,15 +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 {targetForExternalHref} from './helpers.mjs';
export function transformLink(href: string | null, title: string | null, text: string) {
return `<a href="${href}" ${title ? `title=${title}` : ''} ${targetForExternalHref(
href,
)}>${text}</a>`;
}
@@ -1,22 +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 function transformDocsList(body: string, ordered: boolean) {
if (ordered) {
return `
<ol class="docs-ordered-list">
${body}
</ol>
`;
}
return `
<ul class="docs-list">
${body}
</ul>
`;
}
@@ -1,22 +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 function transformDocsTable(header: string, body: string) {
return `
<div class="docs-table adev-scroll-track-transparent">
<table>
<thead>
${header}
</thead>
<tbody>
${body}
</tbody>
</table>
</div>
`;
}
@@ -1,31 +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 {readFileSync} from 'fs';
import {glob} from 'glob';
import {join} from 'path';
export function mapBackticksToCodeElement(text: string): string {
return text.replaceAll(/`(.*?)`/g, '<code>$1</code>');
}
/** Recursively search the provided directory for all markdown files and asyncronously load them. */
export 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((filePath) => ({
path: filePath,
content: readFileSync(join(baseDir, filePath), {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-shared';
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-shared';
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,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}/`)
);
}
@@ -1,57 +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 {readFile, writeFile} from 'fs/promises';
import {dirname, join} from 'path';
import {argv} from 'process';
import {fileURLToPath} from 'url';
import {VersionsConfig, VersionMode} from './types.mjs';
// CONSTANTS
const MODE_ARG = 'v=';
const VERSIONS_CONFIG = 'versions.json';
// PATHS
const ASSETS = join(
dirname(fileURLToPath(import.meta.url)),
'../../src/assets',
);
const VERSIONS_CONFIG_PATH = join(ASSETS, VERSIONS_CONFIG);
main();
async function main() {
console.info('Updating version...');
const versionsConfig: VersionsConfig = JSON.parse(await readFile(VERSIONS_CONFIG_PATH, 'utf-8'));
const currentVersion = (await getCurrentVersion()) ?? 'stable';
if (
currentVersion !== 'next' &&
currentVersion !== 'rc' &&
currentVersion !== 'stable' &&
Number.isNaN(currentVersion)
) {
throw new Error('Invalid mode value provided!');
}
versionsConfig.currentVersion = currentVersion;
await updateConfig(versionsConfig);
console.info('Updating version successfully done!');
}
async function updateConfig(config: VersionsConfig): Promise<void> {
await writeFile(VERSIONS_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`);
}
async function getCurrentVersion(): Promise<VersionMode | undefined> {
return argv.find((arg) => arg.startsWith(MODE_ARG))?.replaceAll(MODE_ARG, '') as VersionMode;
}
-8
View File
@@ -1,8 +0,0 @@
{
"extends": "../../tsconfig.json",
"ts-node": {
"compilerOptions": {
"module": "es2020"
}
}
}
-20
View File
@@ -1,20 +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 interface VersionUrl {
version: VersionMode;
url: string;
}
export type VersionMode = 'stable' | 'rc' | 'next' | number;
export interface VersionsConfig {
currentVersion: VersionMode;
currentVersions: VersionUrl[];
historicalVersions: VersionUrl[];
}
-7
View File
@@ -1,7 +0,0 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/shared",
"lib": {
"entryFile": "src/public-api.ts"
}
}
-12
View File
@@ -1,12 +0,0 @@
{
"name": "shared",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^16.0.1",
"@angular/core": "^16.0.1"
},
"dependencies": {
"tslib": "^2.3.0"
},
"sideEffects": false
}
@@ -1,52 +0,0 @@
<!-- Algolia logo -->
<svg
id="Layer_1"
class="adev-algolia-logo"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2196.2 500"
>
<defs>
<style>
.cls-1,
.cls-2 {
fill: #003dff;
}
.cls-2 {
fill-rule: evenodd;
}
</style>
</defs>
<path
class="cls-2"
d="M1070.38,275.3V5.91c0-3.63-3.24-6.39-6.82-5.83l-50.46,7.94c-2.87,.45-4.99,2.93-4.99,5.84l.17,273.22c0,12.92,0,92.7,95.97,95.49,3.33,.1,6.09-2.58,6.09-5.91v-40.78c0-2.96-2.19-5.51-5.12-5.84-34.85-4.01-34.85-47.57-34.85-54.72Z"
/>
<rect class="cls-1" x="1845.88" y="104.73" width="62.58" height="277.9" rx="5.9" ry="5.9" />
<path
class="cls-2"
d="M1851.78,71.38h50.77c3.26,0,5.9-2.64,5.9-5.9V5.9c0-3.62-3.24-6.39-6.82-5.83l-50.77,7.95c-2.87,.45-4.99,2.92-4.99,5.83v51.62c0,3.26,2.64,5.9,5.9,5.9Z"
/>
<path
class="cls-2"
d="M1764.03,275.3V5.91c0-3.63-3.24-6.39-6.82-5.83l-50.46,7.94c-2.87,.45-4.99,2.93-4.99,5.84l.17,273.22c0,12.92,0,92.7,95.97,95.49,3.33,.1,6.09-2.58,6.09-5.91v-40.78c0-2.96-2.19-5.51-5.12-5.84-34.85-4.01-34.85-47.57-34.85-54.72Z"
/>
<path
class="cls-2"
d="M1631.95,142.72c-11.14-12.25-24.83-21.65-40.78-28.31-15.92-6.53-33.26-9.85-52.07-9.85-18.78,0-36.15,3.17-51.92,9.85-15.59,6.66-29.29,16.05-40.76,28.31-11.47,12.23-20.38,26.87-26.76,44.03-6.38,17.17-9.24,37.37-9.24,58.36,0,20.99,3.19,36.87,9.55,54.21,6.38,17.32,15.14,32.11,26.45,44.36,11.29,12.23,24.83,21.62,40.6,28.46,15.77,6.83,40.12,10.33,52.4,10.48,12.25,0,36.78-3.82,52.7-10.48,15.92-6.68,29.46-16.23,40.78-28.46,11.29-12.25,20.05-27.04,26.25-44.36,6.22-17.34,9.24-33.22,9.24-54.21,0-20.99-3.34-41.19-10.03-58.36-6.38-17.17-15.14-31.8-26.43-44.03Zm-44.43,163.75c-11.47,15.75-27.56,23.7-48.09,23.7-20.55,0-36.63-7.8-48.1-23.7-11.47-15.75-17.21-34.01-17.21-61.2,0-26.89,5.59-49.14,17.06-64.87,11.45-15.75,27.54-23.52,48.07-23.52,20.55,0,36.63,7.78,48.09,23.52,11.47,15.57,17.36,37.98,17.36,64.87,0,27.19-5.72,45.3-17.19,61.2Z"
/>
<path
class="cls-2"
d="M894.42,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-14.52,22.58-22.99,49.63-22.99,78.73,0,44.89,20.13,84.92,51.59,111.1,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47,1.23,0,2.46-.03,3.68-.09,.36-.02,.71-.05,1.07-.07,.87-.05,1.75-.11,2.62-.2,.34-.03,.68-.08,1.02-.12,.91-.1,1.82-.21,2.73-.34,.21-.03,.42-.07,.63-.1,32.89-5.07,61.56-30.82,70.9-62.81v57.83c0,3.26,2.64,5.9,5.9,5.9h50.42c3.26,0,5.9-2.64,5.9-5.9V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,206.92c-12.2,10.16-27.97,13.98-44.84,15.12-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-42.24,0-77.12-35.89-77.12-79.37,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33v142.83Z"
/>
<path
class="cls-2"
d="M2133.97,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-14.52,22.58-22.99,49.63-22.99,78.73,0,44.89,20.13,84.92,51.59,111.1,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47,1.23,0,2.46-.03,3.68-.09,.36-.02,.71-.05,1.07-.07,.87-.05,1.75-.11,2.62-.2,.34-.03,.68-.08,1.02-.12,.91-.1,1.82-.21,2.73-.34,.21-.03,.42-.07,.63-.1,32.89-5.07,61.56-30.82,70.9-62.81v57.83c0,3.26,2.64,5.9,5.9,5.9h50.42c3.26,0,5.9-2.64,5.9-5.9V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,206.92c-12.2,10.16-27.97,13.98-44.84,15.12-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-42.24,0-77.12-35.89-77.12-79.37,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33v142.83Z"
/>
<path
class="cls-2"
d="M1314.05,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-11.79,18.34-19.6,39.64-22.11,62.59-.58,5.3-.88,10.68-.88,16.14s.31,11.15,.93,16.59c4.28,38.09,23.14,71.61,50.66,94.52,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47h0c17.99,0,34.61-5.93,48.16-15.97,16.29-11.58,28.88-28.54,34.48-47.75v50.26h-.11v11.08c0,21.84-5.71,38.27-17.34,49.36-11.61,11.08-31.04,16.63-58.25,16.63-11.12,0-28.79-.59-46.6-2.41-2.83-.29-5.46,1.5-6.27,4.22l-12.78,43.11c-1.02,3.46,1.27,7.02,4.83,7.53,21.52,3.08,42.52,4.68,54.65,4.68,48.91,0,85.16-10.75,108.89-32.21,21.48-19.41,33.15-48.89,35.2-88.52V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,64.1s.65,139.13,0,143.36c-12.08,9.77-27.11,13.59-43.49,14.7-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-1.32,0-2.63-.03-3.94-.1-40.41-2.11-74.52-37.26-74.52-79.38,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33Z"
/>
<path
class="cls-1"
d="M249.83,0C113.3,0,2,110.09,.03,246.16c-2,138.19,110.12,252.7,248.33,253.5,42.68,.25,83.79-10.19,120.3-30.03,3.56-1.93,4.11-6.83,1.08-9.51l-23.38-20.72c-4.75-4.21-11.51-5.4-17.36-2.92-25.48,10.84-53.17,16.38-81.71,16.03-111.68-1.37-201.91-94.29-200.13-205.96,1.76-110.26,92-199.41,202.67-199.41h202.69V407.41l-115-102.18c-3.72-3.31-9.42-2.66-12.42,1.31-18.46,24.44-48.53,39.64-81.93,37.34-46.33-3.2-83.87-40.5-87.34-86.81-4.15-55.24,39.63-101.52,94-101.52,49.18,0,89.68,37.85,93.91,85.95,.38,4.28,2.31,8.27,5.52,11.12l29.95,26.55c3.4,3.01,8.79,1.17,9.63-3.3,2.16-11.55,2.92-23.58,2.07-35.92-4.82-70.34-61.8-126.93-132.17-131.26-80.68-4.97-148.13,58.14-150.27,137.25-2.09,77.1,61.08,143.56,138.19,145.26,32.19,.71,62.03-9.41,86.14-26.95l150.26,133.2c6.44,5.71,16.61,1.14,16.61-7.47V9.48C499.66,4.25,495.42,0,490.18,0H249.83Z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

@@ -1,17 +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 {Component} from '@angular/core';
@Component({
selector: 'docs-algolia-icon',
standalone: true,
imports: [],
templateUrl: './algolia-icon.component.html',
})
export class AlgoliaIcon {}
@@ -1,13 +0,0 @@
@for (breadcrumb of breadcrumbItems(); track breadcrumb) {
<div class="docs-breadcrumb">
@if (breadcrumb.path) {
@if (breadcrumb.isExternal) {
<a [href]="breadcrumb.path">{{ breadcrumb.label }}</a>
} @else {
<a [routerLink]="'/' + breadcrumb.path">{{ breadcrumb.label }}</a>
}
} @else {
<span>{{ breadcrumb.label }}</span>
}
</div>
}
@@ -1,25 +0,0 @@
:host {
display: flex;
align-items: center;
padding-block-end: 1.5rem;
}
.docs-breadcrumb {
span {
color: var(--quaternary-contrast);
font-size: 0.875rem;
display: flex;
align-items: center;
}
&:not(:last-child) {
span {
&::after {
content: 'chevron_right';
font-family: var(--icons);
margin-inline: 0.5rem;
color: var(--quinary-contrast);
}
}
}
}
@@ -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 {ComponentFixture, TestBed} from '@angular/core/testing';
import {Breadcrumb} from './breadcrumb.component';
describe('Breadcrumb', () => {
let component: Breadcrumb;
let fixture: ComponentFixture<Breadcrumb>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [Breadcrumb],
});
fixture = TestBed.createComponent(Breadcrumb);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,50 +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 {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core';
import {NavigationState} from '../../services';
import {NavigationItem} from '../../interfaces';
import {NgFor, NgIf} from '@angular/common';
import {RouterLink} from '@angular/router';
@Component({
selector: 'docs-breadcrumb',
standalone: true,
imports: [NgIf, NgFor, RouterLink],
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Breadcrumb implements OnInit {
private readonly navigationState = inject(NavigationState);
breadcrumbItems = signal<NavigationItem[]>([]);
ngOnInit(): void {
this.setBreadcrumbItemsBasedOnNavigationStructure();
}
private setBreadcrumbItemsBasedOnNavigationStructure(): void {
let breadcrumbs: NavigationItem[] = [];
const traverse = (node: NavigationItem | null) => {
if (!node) {
return;
}
if (node.parent) {
breadcrumbs = [node.parent, ...breadcrumbs];
traverse(node.parent);
}
};
traverse(this.navigationState.activeNavigationItem());
this.breadcrumbItems.set(breadcrumbs);
}
}
@@ -1,22 +0,0 @@
@if (!hasAccepted()) {
<div class="docs-cookies-popup adev-invert-mode">
<p>This site uses cookies from Google to deliver its services and to analyze traffic.</p>
<div>
<a href="https://policies.google.com/technologies/cookies" target="_blank" rel="noopener">
<button class="adev-primary-btn" [attr.text]="'Learn more'" aria-label="Learn More">
Learn more
</button>
</a>
<button
type="button"
(click)="accept()"
class="adev-primary-btn"
[attr.text]="'Ok, Got it'"
aria-label="Ok, Got it"
>
Ok, Got it
</button>
</div>
</div>
}

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