Compare commits

..

394 Commits

Author SHA1 Message Date
Andrew Kushnir 710a24a7d0 release: cut the v17.0.4 release 2023-11-20 11:43:40 -08:00
Jessica Janiuk 0fad36eff2 fix(migrations): tweaks to formatting in control flow migration (#53058)
This addresses a few minor formatting issues with the control flow migration.

fixes: #53017

PR Close #53058
2023-11-20 10:46:43 -08:00
Kristiyan Kostadinov 291deac663 fix(compiler): generate i18n instructions for blocks (#52958)
Adds support for generating i18n instructions inside of blocks.

Fixes #52540.
Fixes #52767.

PR Close #52958
2023-11-20 08:59:26 -08:00
Kristiyan Kostadinov f01b7183d2 fix(compiler): produce placeholder for blocks in i18n bundles (#52958)
When blocks were initially implemented, they were represented as containers in the i18n AST. This is problematic, because block affect the structure of the message.

These changes introduce a new `BlockPlaceholder` AST node and integrate it into the i18n pipeline. With the new node blocks are represented with the `START_BLOCK_<name>` and `CLOSE_BLOCK_<name>` placeholders.

PR Close #52958
2023-11-20 08:59:25 -08:00
anthonyfr75 ac9cd6108f fix(migrations): control flow migration fails for async pipe with unboxing of observable (#52756) (#52972)
Update control flow syntax to use 'as' for proper async pipe handling, accounting for variable whitespace before 'let'.

Fixes #52756

PR Close #52972
2023-11-20 08:58:45 -08:00
arturovt 7f1c55755d fix(common): remove load on image once it fails to load (#52990)
This commit adds an `error` listener to image elements and removes both
`load` and `error` listeners once the image loads or fails to load. The `load`
listener would never have been removed if the image failed to load.

PR Close #52990
2023-11-20 08:58:07 -08:00
arturovt fafcb0d23f fix(common): scan images once page is loaded (#52991)
This commit updates the implementation of the `ImagePerformanceWarning` and
runs the image scan even if the page has already been loaded. The `window.load`
event would never fire if the page has already been loaded; that's why we're
checking for the document's ready state.

PR Close #52991
2023-11-20 08:57:31 -08:00
Jessica Janiuk 492ad4698a fix(migrations): fixes migrations of nested switches in control flow (#53010)
This separates out the NgSwitch migration pass from the NgSwitchCase / Default pass, which makes nested switch migrations work.

fixes: #53009

PR Close #53010
2023-11-20 08:56:53 -08:00
Jessica Janiuk 13bf5b7007 fix(migrations): Fixes control flow migration if then else case (#53006)
With if then else use cases, we now properly account for the length
of the original element's contents when tracking new offsets.

fixes: #52927

PR Close #53006
2023-11-20 08:56:18 -08:00
Nicolas Frizzarin ef974647b0 docs: example code afterNextRender phase (#53025)
Currently in the angular.dev documentation, afterNextRender function
take directly the phase as a second parameter which is not correct.
According to the api of this hook, the second parameter is an object
which contains the phase. This PR update the part of the documentation

PR Close #53025
2023-11-20 08:55:38 -08:00
arturovt db1a8ebdb4 fix(core): cleanup loading promise when no dependencies are defined (#53031)
This commit cleans up the `loadingPromise` when no `dependenciesFn` is defined,
as it's already cleaned up after the resolution of `Promise.allSettled`. This
occurs with `prefetch on` triggers, such as when `triggerResourceLoading` is called
from `ɵɵdeferPrefetchOnImmediate`, where there are no dependencies to load. The
`loadingPromise` should still be cleaned up because it typically involves the
`ZoneAwarePromise`, which isn't properly garbage collected when referenced elsewhere
(in this case, it would be referenced from the `tView` data).

PR Close #53031
2023-11-20 08:54:41 -08:00
Tim Deschryver 6a13fd573d docs: update defer testing snippet (#53056)
PR Close #53056
2023-11-20 08:54:01 -08:00
Tim Deschryver 6cb3ade035 docs: update defer test snippet (#53056)
PR Close #53056
2023-11-20 08:54:01 -08:00
Sasidharan SD 6c68f4ca71 docs: fix broken link for view encapsulation (#52995)
PR Close #52995
2023-11-17 12:18:27 -08:00
Luke Schlangen 4b81784317 docs: fix AppComponent class name (#52976)
PR Close #52976
2023-11-17 12:16:39 -08:00
Kristiyan Kostadinov 6df5f0e0f5 Revert "fix(compiler-cli): add diagnostic for control flow that prevents content projection (#52726)" (#53012)
This reverts commit b4d022e230.

PR Close #53012
2023-11-17 11:52:03 -08:00
Kristiyan Kostadinov f6a9292943 Revert "test(core): add tests for control flow content projection with ng-container (#52726)" (#53012)
This reverts commit 181f8e4b6c.

PR Close #53012
2023-11-17 11:52:03 -08:00
Kristiyan Kostadinov cfcce8c880 Revert "refactor(compiler-cli): expose ng-content selectors and preserveWhitespaces during template type checking (#52726)" (#53012)
This reverts commit 4550a81bdc.

PR Close #53012
2023-11-17 11:52:03 -08:00
Kristiyan Kostadinov cf0a0f3fab Revert "refactor(compiler): expose utility for creating CSS selectors from AST nodes (#52726)" (#53012)
This reverts commit adbea7befb.

PR Close #53012
2023-11-17 11:52:02 -08:00
Enea Jahollari 31a047f186 docs(core): replace RefreshTransplantedView with RefreshView (#52982)
PR Close #52982
2023-11-17 08:10:14 -08:00
Kristiyan Kostadinov f671f86ac2 fix(compiler-cli): add diagnostic for control flow that prevents content projection (#52726)
This is a follow-up to the fix from #52414. It adds a diagnostic that will tell users when a control flow is preventing its direct descendants from being projected into a specific component slot.

PR Close #52726
2023-11-17 08:08:42 -08:00
Kristiyan Kostadinov b07b39de78 test(core): add tests for control flow content projection with ng-container (#52726)
The control flow projection diagnostic will mention `ng-container` as a workaround for projection multiple nodes. These changes add a couple of tests to ensure that the approach works.

PR Close #52726
2023-11-17 08:08:42 -08:00
Kristiyan Kostadinov 221226e0a1 refactor(compiler-cli): expose ng-content selectors and preserveWhitespaces during template type checking (#52726)
These changes expose the `ngContentSelectors` and `preserveWhitespaces` metadata to the TCB so they can be used in the next commit to implement a new diagnostic.

PR Close #52726
2023-11-17 08:08:41 -08:00
Kristiyan Kostadinov 6bcdaec268 refactor(compiler): expose utility for creating CSS selectors from AST nodes (#52726)
When doing directive matching in the compiler, we need to be able to create a selector from an AST node. We already have the utility, but these changes simplify the public API and expose it so it can be used in `compiler-cli`.

PR Close #52726
2023-11-17 08:08:41 -08:00
Andrew Scott f3b8a35933 test(core): Remove manual ComponentFixture construction (#52983)
Tests still pass when using TestBed.createComponent directly

PR Close #52983
2023-11-16 15:12:33 -08:00
arturovt e480a9fd63 refactor(common): remove load event listener once it is fired (#52512)
This commit removes the `load` event listener once it has fired within the
`ImagePerformanceWarning`. The `load` event listener prevents the zone stuff from
being garbage collected in development mode when debugging microfrontend applications
that may be destroyed multiple times.

PR Close #52512
2023-11-16 15:11:59 -08:00
Andrew Kushnir 31a1575334 fix(core): handle local refs when getDeferBlocks is invoked in tests (#52973)
This commit fixes an issue where having elements with local refs in some cases causes JS exception.

PR Close #52973
2023-11-16 12:20:55 -08:00
Jessica Janiuk baeb671334 refactor(migrations): Switch control flow migration reformat default to true (#52971)
This switches the default behavior of the control flow migration template reformatting from opt-in to opt-out.

PR Close #52971
2023-11-16 11:16:30 -08:00
Jessica Janiuk f763d2666f refactor(migrations): Add optional template reformatting after migration (#52939)
This adds the ability to reformat the template with proper indents after the migraion finishes. It is an optional parameter.

PR Close #52939
2023-11-16 09:30:35 -08:00
Kristiyan Kostadinov 49dca36880 fix(compiler): nested for loops incorrectly calculating computed variables (#52931)
The `$first`, `$last`, `$even` and `$odd` variables in `@for` loops aren't defined on the template context of the loop, but are computed based on `$index` and `$count` (e.g. `$first` is defined as `$index === 0`). We do this calculation by looking up `$index` and `$count` when one of the variables is used.

The problem is that all `@for` loop variables are available implicitly which means that when a nested loop tries to rewrite a reference to an outer loop computed variable, it finds its own `$index` and `$count` first and it doesn't look up the ones on the parent at all. This means that the calculated values will be incorrect at runtime.

These changes work around the issue by defining nested-level-specific variable names that can be used for lookups (e.g. `$index` at level `2` will also be available as `ɵ$index_2`). This isn't the most elegant solution, however the `TemplatDefitinionBuilder` wasn't set up to handle shadowed variables like this and it doesn't make sense to refactor it given the upcoming template pipeline.

Fixes #52917.

PR Close #52931
2023-11-16 09:29:47 -08:00
ahmadhakeem18 2580eccc2f docs: change class field external link to point to the right page (#52919)
PR Close #52919
2023-11-16 09:29:05 -08:00
Kristiyan Kostadinov 98376f2c09 fix(compiler): changed after checked error in for loops (#52935)
Reworks the `repeater` instruction to go through `advance`, instead of passing in the index directly. This ensures that lifecycle hooks run at the right time and that we don't throw "changed after checked" errors when we shouldn't be.

Fixes #52885.

PR Close #52935
2023-11-15 21:13:37 +00:00
Jessica Janiuk f5307bf119 release: cut the v17.0.3 release 2023-11-15 12:23:29 -08:00
Doug Parker 151a544277 release: bump DevTools version to 1.0.9 (#52938)
PR Close #52938
2023-11-15 20:18:32 +00:00
Jessica Janiuk 4e200bf13b fix(migrations): Add missing support for ngForOf (#52903)
This adds support to migrate ngForOf and ngForTrackBy when migrating control flow.

PR Close #52903
2023-11-15 14:06:18 +00:00
Andrew Kushnir 5de7575be8 fix(core): reset cached scope for components that were overridden using TestBed (#52916)
Currently, when a component is overriden using `TestBed.overrideComponent`, Angular retains calculated scope for that component (a set of components and directives used within a component). This may cause stale information to be used in tests in some cases. This commit updates the logic to reset overridden component scope, so it gets re-computed during the next invocation.

Resolves #52817.

PR Close #52916
2023-11-15 14:03:55 +00:00
Alejandro a523801b25 docs: fixed unexpected closing tags for Receipt (#52914)
PR Close #52914
2023-11-14 22:02:04 +00:00
Andrew Scott 290e26a2ab refactor(core): Remove detectChanges private export (#52875)
detectChanges private export is not needed.

PR Close #52875
2023-11-14 21:05:42 +00:00
tomastrajan 95e4806ce3 docs: fix and improve @defer testing docs for adev (#52874)
PR Close #52874
2023-11-14 19:16:41 +00:00
Tomas Trajan cc8dbb61c4 docs: fix and improve @defer testing docs (#52874)
- the example when taken from docs into projects didn't work
- getDeferBlocks has to be used with async
- improved description of default state (placeholder)
- improved assertion of <large-component /> as when generated by schematics

PR Close #52874
2023-11-14 19:16:41 +00:00
Sai Kumar Kola 64d9e658a4 docs: fix grammar in guide/defer (#52899)
docs: fix grammar in aio/guide/defer and adev/guide/defer

PR Close #52899
2023-11-14 18:32:51 +00:00
Andrew Scott b393e3727e refactor(core): Simplify detectChangesInternal call signature (#52866)
The call signature of detectChangesInternal requires parameters that can all be
found directly on lView. This commit removes those paramters and instead
grabs them in the function implementation.

PR Close #52866
2023-11-14 15:11:41 +00:00
Sasidharan SD 78f817c358 docs: fix gramatical mistake in angular roadmap (#52850)
PR Close #52850
2023-11-14 15:11:10 +00:00
Swami 60f979e01f docs: fix typos in angular.dev (#52803)
PR Close #52803
2023-11-14 15:10:41 +00:00
Christian Svensson eb44643bdf docs: correct a few broken links (#52688)
PR Close #52688
2023-11-14 15:10:07 +00:00
Sasidharan SD 561cb1d8da docs: fix provideRouter typo (#52798)
PR Close #52798
2023-11-13 23:25:57 +00:00
Dmitriy Mishchenko 031a1a8c8e docs(docs-infra): a11y improvement suggestion Property Binding tutorial (#52807)
#52639 issue fix suggestion

PR Close #52807
2023-11-13 23:25:23 +00:00
Sasidharan SD 24c6b0eafb docs: fix ngComponentOutlet api reference link (#52829)
PR Close #52829
2023-11-13 23:24:56 +00:00
oliv37 a2a5646253 docs: improve filter results example (#52838)
PR Close #52838
2023-11-13 23:24:28 +00:00
Jessica Janiuk d033540d0f fix(migrations): Add support for bound versions of NgIfElse and NgIfThenElse (#52869)
This ensures the bound version of NgIfElse and NgIfThenElse work properly with the migration.

fixes: #52842

PR Close #52869
2023-11-13 22:33:33 +00:00
marktechson 8b6fd8dd18 docs: remove live event from the home page (#52878)
PR Close #52878
2023-11-13 22:29:47 +00:00
Joey Perrott c8ff0be38f ci: correct label for bazel-saucelabs job (#52873)
Update the label to the correct schema

PR Close #52873
2023-11-13 22:08:45 +00:00
Etienne FRANK a00beec2e0 docs(forms): replace fb with formBuilder (#52795)
Shorten variable names isn't a good practice. To avoid spreading it, we removed it from Angular's documentation.

PR Close #52795
2023-11-13 18:59:04 +00:00
Andrew Scott 76aba9ef0e refactor(core): Move booleans in LContainer to flags slot (#52338)
There are now 2 booleans in the LContainer so this commit moves them to a shared
FLAGS slot like the LView.

PR Close #52338
2023-11-13 18:25:17 +00:00
Andrew Scott a55c8bb0e0 test(core): Ensure signals can be read after view creation during change detection (#52495)
These tests ensure signals can be read in a template after embedded
views are created in the middle of template execution of an update pass.
The embedded view templates are executed in create mode in the middle of
the component template being executed in update mode. This behavior was
found to not work correctly in past implementations of the reactive
template consumers.

PR Close #52495
2023-11-13 18:24:41 +00:00
Jessica Janiuk 3831942771 fix(migrations): Fixes issue with multiple if elses with same template (#52863)
This should fix the issue where if the same ng-template is used with multiple if / else statements, it replaces all usages properly.
fixes: #52854

PR Close #52863
2023-11-13 18:23:55 +00:00
Matthieu Riegler 30757bd4d5 refactor(animations): EnvironmentProviders for provideAnimationsAsync (#52862)
This will prevent devs from using these providers in at a component level.

PR Close #52862
2023-11-13 17:27:31 +00:00
Matthieu Riegler f5872c9921 fix(animations): prevent the AsyncAnimationRenderer from calling the delegate when there is no element. (#52570)
This happens when `issueAnimationCommand` is invoked

fixes #52538

PR Close #52570
2023-11-13 16:29:18 +00:00
Matthieu Riegler 7c066a4af4 fix(http): Use the response content-type to set the blob type. (#52840)
When downloading a PDF with the fetch client, the blob had no content. It couldn't be displayed in an iframe. This commit fixes this.

Relate to: https://stackoverflow.com/questions/77470626/possible-bug-in-httpclient-when-using-the-blob-data-type

PR Close #52840
2023-11-13 16:22:21 +00:00
AleksanderBodurri 6a1d4ed667 fix(core): handle non-container environment injector cases (#52774)
Previously we had logic for a special case where a root injector in standalone apps would skip the import paths calculation step for the `getEnvironmentInjectorProviders` function.

This commit intends to fix this for two other cases, namely:
- When an injector is created by a route (via the `providers` field and lazy loading).
- When an injector is manually created and attached to the injector tree

It does this by assuming that any environment injector it cannot find a provider imports container for was created without one, and simply returns the raw provider records without the import paths calculation.

PR Close #52774
2023-11-13 16:21:03 +00:00
Sylvain DEDIEU e46162d3e4 docs: update readme links to target angular.dev documentation (#52772)
Change the angular.io links to target the new angular.dev website
Changed the angular logo

PR Close #52772
2023-11-13 15:56:20 +00:00
Sam Edwards 0fbdb63bbc docs: correct component name on Managing Dynamic Data on angular.dev website (#52792)
PR Close #52792
2023-11-13 15:55:51 +00:00
Jessica Janiuk e1f84a31dc fix(migrations): passed in paths will be respected in nx workspaces (#52796)
This fixes a bug where if you have multiple tsconfig files, the migration would not find anything to migrate at the passed in path.
fixes: #52787

PR Close #52796
2023-11-13 15:21:36 +00:00
Jessica Janiuk aa2d815648 fix(migrations): Add support for removing imports post migration (#52763)
This update removes imports from component decorators and at the top of the files. It only removes standalone imports though. It does not remove CommonModule if that is the only import.

PR Close #52763
2023-11-13 15:21:07 +00:00
AleksanderBodurri 54a7b33245 fix(devtools): check for all new DI debug APIs before trying to determine resolution path providers (#52791)
Previously, some versions of Angular 16.1.x that had 3/4 of the new DI debug APIs would enter a code path that required them to have access to the 4th.

Now DevTools checks for the existence of all 4 explicitly before going down this code path.

PR Close #52791
2023-11-10 18:37:05 +00:00
Miles Malerba 38aab6aa9d refactor(compiler): Handle trailing spaces in ICU placeholders (#52698)
In some cases ICU expression placeholders may have trailing spaces that
need to be trimmed when matching the placeholder to its corresponding
text binding.

PR Close #52698
2023-11-10 17:01:07 +00:00
Miles Malerba 12ffb25e94 refactor(compiler): Fix some issues with i18n expressions in ICUs (#52698)
We were previously counting the i18n expression index and deciding when
to apply i18n expressions based on the i18n context. These should be
done based on the i18n block instead.

PR Close #52698
2023-11-10 17:01:07 +00:00
Miles Malerba cb5d9a480e refactor(compiler): Change how ICUs are ingested (#52698)
The previous commit added support for interpolated text in ICUs, but it
made the assumption that the interpolation would be a single variable
read expression.

To properly support all kinds of interpolation expressions, this commit
refactors how ICUs are ingested to allow us to re-use the same logic we
use for bound text outside of ICUs.

To accomplish this, the `IcuOp` creation op has been removed in favor of
a pair of ops: `IcuStartOp` and `IcuEndOp`, that mark the beginning and
end of the ICU. Now, instead of inserting an `IcuUpdateOp` in the update
IR, we call `ingestBoundText` and use the presence of the surrounding
`IcuStartOp` and `IcuEndOp` to match the interpolation with the ICU.

PR Close #52698
2023-11-10 17:01:07 +00:00
Miles Malerba 825718f2b1 refactor(compiler): Support expressions inside ICUs (#52698)
Previously ICUs were assumed to only generate a single i18n expression
per ICU. However, it is possible for ICUs to contain text interpolations
which requires additional expressions. This commit adds support for
multiple expressions per ICU.

PR Close #52698
2023-11-10 17:01:07 +00:00
Miles Malerba 0b7e7fd3a5 refactor(compiler): Support element tags inside ICUs (#52698)
ICUs that contain element tags need extra parameters for the i18n
message. These are in addition to the element slot params that are
already added to the parent i18n block's params. In this commit we add a
new phase to fill in these placeholders.

PR Close #52698
2023-11-10 17:01:07 +00:00
Miles Malerba 22ff9cacce refactor(compiler): More consistent sorting of i18n params (#52698)
Previously the template pipeline sorted i18n message params before
adding the sub-message placeholders. Now its sorts after all
placeholders are added.

Both the template pipeline and TemplateDefinitionBuilder previously
failed to sort the post-processing params. They both now sort these as
well. This is safe to change in TemplateDefinitionBuilder, as it does
not change anything about the functionality, it simply ensures that
params map in the output has the keys ordered in a way that can be
easily reproduced in the template pipeline.

PR Close #52698
2023-11-10 17:01:07 +00:00
Doug Parker 6b164dc83a release: bump DevTools version to 1.0.8 (#52759)
PR Close #52759
2023-11-10 17:00:20 +00:00
Jessica Janiuk c01af4302f refactor(migrations): code clean up and add comments on exported functions (#52755)
This cleans up a bit of code to make maintenance easier. It also adds comments for all the exported methods so they are clear to anyone in the future.

PR Close #52755
2023-11-09 22:55:50 +00:00
Jessica Janiuk 01efb1b807 release: cut the v17.0.2 release 2023-11-09 12:08:18 -08:00
Pawel Kozlowski b16fc2610a fix(core): limit rate of markers invocations (#52742)
This PR assures that the performance markers are invoked
only once for a given feature.

Closes #52524

PR Close #52742
2023-11-09 19:58:26 +00:00
Sasidharan SD cfb42ee2c8 docs: add extended diagnostic NG8109 (#52721)
PR Close #52721
2023-11-09 19:39:24 +00:00
Mikita Himpel 47756d81e5 docs: fix programmatic-rendering example component name (#52731)
PR Close #52731
2023-11-09 19:38:19 +00:00
Nelson Gutierrez 90018f9327 docs: add missing backtick on title (#52711)
PR Close #52711
2023-11-09 19:32:24 +00:00
Patrick Hyatt 7b60b5196f docs: correct typescript type inference link (#52714)
PR Close #52714
2023-11-09 19:30:39 +00:00
Balaji 35e998effb docs: updating incorrect link in Class Binding page (#52715)
PR Close #52715
2023-11-09 19:28:06 +00:00
Mikhail Filin 158b1984bf docs: remove broken link pill in dynamic component page (#52744)
PR Close #52744
2023-11-09 19:18:30 +00:00
Jeevan Mahesha 27bf371e32 docs: removed the broken link (#52743)
PR Close #52743
2023-11-09 19:06:13 +00:00
Tom Wilkinson 56ec37dcbc refactor(common): Update packages/common/src/navigation/platform_navigation.ts (#52363)
Co-authored-by: Andrew Scott <atscott01@gmail.com>

PR Close #52363
2023-11-09 18:01:33 +00:00
Thomas Wilkinson d5fd614836 refactor(common): Add fake implementation of PlatformNavigation. (#52363)
This implementation does most, but not all, of the things the native
Navigation API does. Also adds a spec that tests all the currently
supported behaviors.

PR Close #52363
2023-11-09 18:01:33 +00:00
Thomas Wilkinson e2f3ae51df refactor(common): Add a new platform_navigation that provides the Navigation API. (#52363)
This allows using the Navigation API in Angular packages like Router.

PR Close #52363
2023-11-09 18:01:33 +00:00
Andrew Kushnir f61519ab90 refactor(core): delay applying component metadata until it's needed in tests (#52708)
When a component contains `@defer` blocks, Angular compiler generates the code to apply component metadata (from the `@Component` decorator) after resolving all dynamic dependencies. Currently, this function is invoked eagerly at runtime, which causes dynamic imports to be kicked off earlier than expected. With the change in this commit, Angular will start resolving async metadata when it becomes necessary during testing.

PR Close #52708
2023-11-09 16:58:06 +00:00
Vadim Makeev b651648db6 docs: make homepade example accessible (#52578)
PR Close #52578
2023-11-09 15:58:10 +00:00
Kristiyan Kostadinov a548c0333e fix(compiler-cli): incorrect inferred type of for loop implicit variables (#52732)
Fixes that all implicit variables in `@for` loops were inferred to be numbers, even though most are actually boolean.

Note that I also had to work around a weird TypeScript behavior in `tsDeclareVariable` where usually we declare variables in the following format:

```
var _t1: <type> = null!;
```

This works in most cases, but if the type is a `boolean`, TypeScript infers the variable as `never`, instead of `boolean`. I've worked around it by adding an `as boolean` to the initializer.

Fixes #52730.

PR Close #52732
2023-11-09 15:47:56 +00:00
cexbrayat 7a95cccf50 fix(compiler-cli): add interpolatedSignalNotInvoked to diagnostics (#52687)
This template diagnostic has been introduced in 8eef694def but was not enabled,
as it was not added to `ALL_DIAGNOSTIC_FACTORIES`.

PR Close #52687
2023-11-09 15:46:16 +00:00
Pawel Kozlowski 44c48a4835 fix(core): properly update collection with repeated keys in @for (#52697)
This change fixes a bug in the new list reconcilation algorithm
that could lead to an infinite loop in certain situations.

More specifically, it adjusts the internal MultiMap implementation
such that an entry returned from the .get call is the same entry
(for an identical key) removed by the .delete call.

The existing logic of the MultiMap was leading to a situation where
one view was requested and attached to LContainer, but a very different
view was removed from the MultiMap. This was leaving an attached LView
in a collection that was supposed to hold only detached views.

Closes #52524

PR Close #52697
2023-11-09 15:44:53 +00:00
cexbrayat 2cea80c6e2 fix(core): error code in image performance warning (#52727)
The warnings link to https://angular.io/errors/NG2965 which is a 404
The proper error page is https://angular.io/errors/NG0913

PR Close #52727
2023-11-09 15:44:24 +00:00
Sylvain DEDIEU 7d6752a806 docs(docs-infra): improve decorator deprecated property display in documentation (#52041)
Add the deprecated-api-item class on decorator-overview option code if deprecated. Add deprecated label on decorator-overview short description if deprecated. Add Add deprecated label on decorator option table header if option is deprecated.

PR Close #52041
2023-11-09 15:36:32 +00:00
Andrew Scott 354340d643 docs: fix broken http link (#52541)
fixes #52542

PR Close #52541
2023-11-09 15:35:59 +00:00
Andrew Scott e3950b4e1f docs: fix broken DI link on roadmap (#52541)
fixes #52539

PR Close #52541
2023-11-09 15:35:59 +00:00
Sam Verschueren 881bb4d719 docs: fix LCP element link (#52580)
PR Close #52580
2023-11-09 15:34:07 +00:00
Sasidharan SD 6c7d2b6fe3 docs: fix angular compatability version (#52585)
PR Close #52585
2023-11-09 15:33:38 +00:00
Sasidharan SD 96d554d8e7 docs: fix incorrect tag reference (#52586)
PR Close #52586
2023-11-09 15:01:03 +00:00
Sasidharan SD e994305d70 docs: fix repetetive text (#52587)
PR Close #52587
2023-11-09 15:00:27 +00:00
Sasidharan SD a87d50639b docs: fix cli command redirection (#52589)
PR Close #52589
2023-11-09 14:59:55 +00:00
Sasidharan SD 7e5d71de3e docs: fix prefetching typo issue (#52590)
PR Close #52590
2023-11-09 14:59:00 +00:00
Sasidharan SD bd8ac477c3 docs: add missing backtick (#52607)
PR Close #52607
2023-11-09 14:58:00 +00:00
Ryth-cs 1ce0f7b07a docs: correct reactive-forms typo (#52608)
PR Close #52608
2023-11-09 14:57:28 +00:00
Raghav Kanwal a08232793d docs: update typo in Guide/Testing (#52619)
PR Close #52619
2023-11-09 14:56:36 +00:00
technbuzz 8c3d41b5b3 docs: fix typo in component output guide (#52673)
We should use @Output rathar than @Input decorator for output events.
This comments fixes the typo by replacing Input with Output

PR Close #52673
2023-11-09 14:56:02 +00:00
Marianna Maglio 387051b078 docs: fix typos in tutorials/learn-angular (#52677)
PR Close #52677
2023-11-09 14:55:32 +00:00
Sasidharan SD d6fec2fc75 docs: fix developer preview link (#52693)
PR Close #52693
2023-11-09 14:54:55 +00:00
Jessica Janiuk 4626691198 release: cut the v17.0.1 release 2023-11-08 11:24:30 -08:00
Matthieu Riegler 5c6f3f8ec0 fix(http): Don't override the backend when using the InMemoryWebAPI (#52425)
When using `withFetch`,  the `PRIMARY_HTTP_BACKEND` token is set.

The InMemory Backend services will also set that token.

This means that providers order will matter and the latest on the list will be the one instantiated

PR Close #52425
2023-11-08 18:42:21 +00:00
Jessica Janiuk 70d30c28e0 fix(migrations): Add support for ng-templates with i18n attributes (#52597)
This makes sure that i18n attributes are preserved on ng-templates being removed during the migration.

fixes: #52517

PR Close #52597
2023-11-08 10:26:20 -08:00
Andrew Scott 6240613477 refactor(language-service): Add flag to allow disabling block syntax parsing (#52691)
This commit adds a flag to the language service config options to disable block
parsing in the compiler.

PR Close #52691
2023-11-08 10:25:22 -08:00
Kristiyan Kostadinov 227de98201 refactor(compiler): add flag to disable block syntax in language service (#52683)
Adds the private `_enableBlockSyntax` flag that can be used by the language service to disable blocks on apps that aren't on Angular v17.

PR Close #52683
2023-11-08 09:34:10 -08:00
Kristiyan Kostadinov a46c8cbf4e fix(docs-infra): fix menu icon size (#52689)
Fixes that the hamburger icon was too small, because the top nav is using the wrong kind of button. Switching it to the right one would require more refactoring so I went with the simpler approach for now.

PR Close #52689
2023-11-08 09:23:43 -08:00
cexbrayat 1d31a0c129 refactor(core): sanitize reactivity tests (#52632)
It was intriguing to see a double `fixture.detectChanges()` introduced by 38c9f08c8d
It turns out this is not needed.

PR Close #52632
2023-11-08 08:51:50 -08:00
Swami bd0f811405 docs: typo fix (#52511)
PR Close #52511
2023-11-08 08:51:28 -08:00
Andrew Scott f729215f0a refactor(core): Remove RootViewRef<T> because it is the same as ViewRef<T> (#52430)
`RootViewRef<T>` extends `ViewRef<T>` and overrides 3 methods with behavior
that is identical to `ViewRef<T>`. This commit removes `RootViewRef<T>`
because it is not needed.

PR Close #52430
2023-11-08 08:50:50 -08:00
Matthieu Riegler bd275b1b26 docs: add v17 versions support (#51443)
* Node 16 support is dropped per angular/angular-cli#25675
* TS 5.2 is supported per #51334
* TS 5.1 and lower support is dropped by #51792

PR Close #51443
2023-11-08 08:50:20 -08:00
Lucas 181d5755ee docs: updating information about polyfills (#52519)
PR Close #52519
2023-11-08 08:49:28 -08:00
Andrew Scott e1c0493135 test(core): Add test to ensure writing to signals in afterRender hooks throws error (#52475)
We do not yet handle running change detection again if `afterRender`
hooks write to signals.

PR Close #52475
2023-11-08 08:48:44 -08:00
Jessica Janiuk 4f125c5f9a fix(migrations): Switches to multiple passes to fix several reported bugs (#52592)
Rather than migrate all in one pass, this now migrates in a separate pass per control flow item plus one for templates at the end. This resolves issues with multiple control flow items on a single element as well as making sure ng-templates are fully migrated before being moved to new locations.

fixes: #52518
fixes: #52516
fixes: #52513

PR Close #52592
2023-11-08 08:47:10 -08:00
Alex Rickabaugh c30dde257b build: bump in-memory-web-api to v17 (#52676)
This commit updates the in-memory-web-api package versions from v16 -> v17.

PR Close #52676
2023-11-08 17:05:45 +01:00
Pawel Kozlowski 1cc87b26cb build: update aio to 17.0.0 (#52635) (#52674)
This commit updates the Angular version for AIO and its examples to 17.0.0

PR Close #52674
2023-11-08 17:04:51 +01:00
Alex Rickabaugh da429c59b6 Revert "fix(http): Don't override the backend when using the InMemoryWebAPI (#52425)" (#52630)
This reverts commit 49b037f811. Reason: it
breaks tests in aio-local.

PR Close #52630
2023-11-08 16:28:00 +01:00
Pawel Kozlowski fc6b2f2669 docs: add the v17 blog post link to the changelog (#52675)
This commit adds the short link 'http://goo.gle/angular-v17' to the project changelog.

PR Close #52675
2023-11-08 16:23:01 +01:00
Pawel Kozlowski fecd141dfa release: cut the v17.0.0 release 2023-11-08 13:23:45 +01:00
Andrew Scott 6b83328577 release: cut the v17.0.0-rc.3 release 2023-11-06 16:07:18 -08:00
Andrew Scott d4c20aca71 refactor(core): Add warning when signal equality is false for object.is (#52532)
This commit adds a warning when a signal equality function returns
`false` but `Object.is` returns `true`.

PR Close #52532
2023-11-06 15:53:10 -08:00
AleksanderBodurri a86661ff24 refactor(devtools): update Angular logo for extension and loading animation (#52546)
Previously these were using the pre v17 Angular logo.

Now these have been updated to use the new Angular logo for V17+.

PR Close #52546
2023-11-06 12:34:21 -08:00
Dylan Hunn 590c61889f refactor(compiler): Fix two-way binding source maps (#52479)
Now that two-way bindings work correctly with implicit receivers, we can fix the corresponing source map tests. The main issue was that we were not properly mapping `elementEnd` for elements with no closing tag (self-closing elements).

PR Close #52479
2023-11-06 11:43:00 -08:00
Dylan Hunn b44f2f9e47 refactor(compiler): Fix two-way bindings in template pipeline (#52479)
Some two-way bindings tests were not working properly, because we could not ingest the implicit receiver required to write to the `ngModelChanges` property. Now, we properly resolve that implicit receiver to the root component context.

Also, add some tests, both for the simple case, and the case where the listener is inside a nested view.

PR Close #52479
2023-11-06 11:43:00 -08:00
Dylan Hunn a48d65cdf4 refactor(compiler): Support extracting deps functions for defer in template pipeline (#52479)
Some `defer` blocks have external dependencies on other components or directives. These dependencies need to be extracted into deps functions, which either return local deps, or use a dynamic import for non-local deps. Template Pipeline can now generate these functions.

PR Close #52479
2023-11-06 11:43:00 -08:00
Dylan Hunn 42899c5aa0 refactor(compiler): Emit a template ref extractor on ng-templates with local refs (#52479)
When an `ng-template` has local refs, such as `<ng-template #foo>`, we must emit a `ɵɵtemplateRefExtractor` argument to the template creation functino. The template pipeline now supports this.

PR Close #52479
2023-11-06 11:42:59 -08:00
Dylan Hunn 2367b038fb refactor(compiler): Add template pipeline golden for differing consts (#52479)
The consts for this test are emitted in a different order, but the generated code is valid.

PR Close #52479
2023-11-06 11:42:59 -08:00
Dylan Hunn d8eab6f807 refactor(compiler): Add template pipeline goldens for some tests with const attributes (#52479)
`TemplateDefinitionBuilder` is somewhat unreliable about extracting constant attributes (e.g. `[attr.foo]="'one'"`). It never extracts const non-string expressions, and usually, but not always, extracts const string expressions. Template pipeline consistently extracts const strings, and we add new goldens for a couple such cases.

PR Close #52479
2023-11-06 11:42:59 -08:00
Dylan Hunn dab522f011 refactor(compiler): Support defer triggers with no arguments in template pipeline (#52479)
Some defer triggers, such as `hover`, expect a local reference as an argument. For example, `@defer (on hover(target))` waits until the user hovers over the target.

However, these defer conditions also have a nullary form, in which the trigger is implicitly the first element in the placeholder block. We now support that case in template pipeline.

PR Close #52479
2023-11-06 11:42:59 -08:00
Dylan Hunn 8400533555 refactor(compiler): Support deferWhen instructions in template pipeline (#52479)
We already supported `defer on` conditions, which become instructions in the create mode block.

Now, we also support `defer when` conditions, where are very similar, with the notable difference that they go in the update block (because a user-supplied condition must be re-evaluated on each update.)

PR Close #52479
2023-11-06 11:42:59 -08:00
Andrew Scott 43acd44cf8 refactor(core): Remove change detection flag used in g3 (#52530)
This has now been cleaned up internally and is no longer needed

PR Close #52530
2023-11-06 11:42:16 -08:00
twerske cab7e003b3 docs: add angular.dev (#52534)
PR Close #52534
2023-11-06 11:09:41 -08:00
Emma Twersky 2c3daebc5d docs: add angular.dev banner (#52535)
PR Close #52535
2023-11-06 11:04:13 -08:00
Miles Malerba 91dbcb5eab refactor(compiler): Add more comments for i18n template pipeline phases (#52503)
Adds some additional comments about the i18n termplate pipeline phases
to more clearly document how they work.

PR Close #52503
2023-11-06 09:49:48 -08:00
Miles Malerba fa9bca23f7 refactor(compiler): Add support for ICUs as part of another i18n message (#52503)
Previously we supported ICUs where the ICU itself represetned the entire
translated message. This change allows ICUs to act as a sub-message
inside other translated messages.

PR Close #52503
2023-11-06 09:49:47 -08:00
Miles Malerba 51d73ed957 refactor(compiler): Introduce i18n context objects (#52503)
Previously we assumed that all i18n messages that are eventually
extracted into the consts array would be generated based on an i18n
block. However, it is also possible to have messages generated directly
from ICUs. This change introduces an i18n context op, so that we can
consistently extract i18n messages from the context op in all cases.

PR Close #52503
2023-11-06 09:49:47 -08:00
Jessica Janiuk 9c2be715a3 fix(migrations): Fixes a bug in the ngFor pre-v5 alias translation (#52531)
The logic that transformed the value to get the alias name was incorrect.

fixes: #52522

PR Close #52531
2023-11-06 09:47:37 -08:00
Jessica Janiuk c267f54bc3 fix(migrations): Update regex to better match ng-templates (#52529)
This addresses an edge case where an ng-template name matches an opening element name, preventing the template from being removed.

fixes #52523

PR Close #52529
2023-11-06 09:05:55 -08:00
Kristiyan Kostadinov 83067b3ef2 fix(compiler): ng-template directive invoke twice at the root of control flow (#52515)
Discovered this while validating #52414 against Angular Material. We were projecting `<ng-template>` nodes at the root of `@if` and `@for` with the `ng-template` tag name which enables directive matching and applies the directive to the control flow node.

These changes fix the issue by never passing along the `ng-template` tag name.

PR Close #52515
2023-11-06 09:03:45 -08:00
Kristiyan Kostadinov 09e905ad67 fix(migrations): account for separator characters inside strings (#52525)
Fixes that the control flow migrations wasn't accounting for separator characters used inside string literals.

PR Close #52525
2023-11-06 09:00:23 -08:00
Kristiyan Kostadinov 57404d4723 fix(migrations): handle comma-separated syntax in ngFor (#52525)
This is something that came up when running the script against the Components repo. The `ngFor` syntax can be delimited either by semicolons or by commas, but the migration only accounted for commas.

PR Close #52525
2023-11-06 09:00:23 -08:00
AleksanderBodurri 1c92609719 test(devtools): fix transformInjectorResolutionPathsIntoTree tests (#52489)
Previously transformInjectorResolutionPathsIntoTree returned an array, now it returns a tree node so we update the test cases to reflect that.

PR Close #52489
2023-11-03 17:37:56 -07:00
AleksanderBodurri e5bd6e9ccc feat(devtools): implement zoom and pan in injector graph visualizer (#52489)
Previously only the trackpad could be used to navigate this view.

Now we can zoom and pan around using our mouse wheel and scroll.

Additionally, this commit fixes many issues related to the visualization of the injector graph visualization, allowing it to be more compact without impacting legibility and minimizing edge collisions in larger graphs.

PR Close #52489
2023-11-03 17:37:55 -07:00
AleksanderBodurri 5a816b8739 perf(devtools): optimize injector graph discovery (#52489)
Our algorithm for discovering the injector graph of an application involves calculating resolution paths for each angular node on a page, and then using those paths to construct the underlying tree.

Along the we way we perform many expensive computations that are candidates for optimization through caching. This commit implements this caching, resulting in a substantial increase in performance for large applications.

PR Close #52489
2023-11-03 17:37:55 -07:00
AleksanderBodurri c0d7bac111 feat(devtools): squash multi providers into 1 and allow them to be logged to the console (#52489)
Implements a feature allowing users to visualize multiproviders as one row in the providers table.

Also enables the user to log to console any provider in the table. This log includes information about the provider selected, the injector it was configured in, and also calls `Injector.get(Token)` to determine the value the provider evaluates to in it's injector.

PR Close #52489
2023-11-03 17:37:55 -07:00
AleksanderBodurri 3afc9d0cdb refactor(devtools): stop inspector from changing tab to components (#52489)
Previously this was the only page that used the inspector, so we automatically changed the tab out of convenience.

Now, the injector tree tab also does uses the inspector for some functionality, so we disable this behaviour.

PR Close #52489
2023-11-03 17:37:55 -07:00
Alan Agius de136105aa release: cut the zone.js-0.14.2 release (#52508)
PR Close #52508
2023-11-03 10:54:12 -07:00
Alan Agius 95accb07b4 docs: update ssr docs to add information about CommonEngine (#52506)
This commit add contructor information about `CommonEngine`

PR Close #52506
2023-11-03 09:50:13 -07:00
AleksanderBodurri a2ba5482c3 fix(core): use TNode instead of LView for mapping injector providers (#52436)
Previously, LViews were used here to be consistent with other debug APIs. Using LViews for tracking injector providers does not work because providers only get configured once per TNode type.

Now we use the TNode as the key to track element injector providers, allowing the injector for each item rendered in a list (`ngFor` or `@for`) to be targeted with debug APIs for inspecting providers

PR Close #52436
2023-11-03 09:36:00 -07:00
JiaLiPassion 842b2cceb6 fix(zone.js): disable wrapping unhandled promise error by default (#52492)
Before this commit, zone.js wraps the uncaught promise rejection error
to a new Error object includes more information such as Zone stack
traces. This feature is provided from the very beginning of Zone.js,
but this feature becomes very annoying and make the user difficult to
debug.

So this commit disable this wrapping behavior by default, and user can
enable this feature back by setting
`DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION` to `false`.

PR Close #52492
2023-11-03 09:33:20 -07:00
Kristiyan Kostadinov 6988a0070e fix(migrations): handle ngIf else condition with no whitespaces (#52504)
Fixes that the control flow migration wasn't migrating an `*ngIf` with an `else` condition that doesn't have spaces before the `else`.

Fixes #52502.

PR Close #52504
2023-11-03 09:32:47 -07:00
Aanchal Agarwal 236b448364 docs: omit repetitions (#52413)
PR Close #52413
2023-11-03 07:46:55 -07:00
Angular Robot 1f9ff84e28 build: update dependency multimatch to v7 (#52426)
See associated pull request for more information.

PR Close #52426
2023-11-03 07:45:31 -07:00
Alan Agius 7ff5152547 build: configure tslint to ban performance.mark usages (#52505)
While `performance.mark` is available on all supported browsers and node.js version this API is not available in JSDOM which is used by Jest and Cloudflare worker.

PR Close #52505
2023-11-03 07:43:37 -07:00
Alan Agius 80e7a0f8fa fix(core): guard usages of performance.mark (#52505)
While `performance.mark` is available on all supported browsers and node.js version this API is not available in JSDOM which is used by Jest and Cloudflare worker.

This commit, updates the usage to a safer variant.

PR Close #52505
2023-11-03 07:43:36 -07:00
Alex Rickabaugh 739c0d27ec refactor(core): mark effect as developer preview again (#52490)
Re-add the `@developerPreview` flags to `effect()`. While we don't expect
the `effect` API itself to change, we may change how other FW APIs interact
with `effect`s. Also, add a missing export for one of those effect APIs.

PR Close #52490
2023-11-02 15:29:45 -07:00
Alex Rickabaugh a8e57a1fab release: cut the v17.0.0-rc.2 release 2023-11-02 15:03:38 -07:00
Andrew Scott 32f417f77f refactor(core): Do not refresh view if producers did not actually change (#52476)
Producers represent values which can deliver change notifications.
When a producer value is changed, a change notification is propagated through the graph,
notifying live consumers which depend on the producer of the potential update.
Note here that this is a _potential_ update.

A producer may not have actually "changed" based on its equality function. With
this commit, before refreshing a view that is only marked for refresh
because its consumer is dirty, we poll producers for change to see if
they really have. If not, we can skip the refresh. The example test in this commit
shows that a `computed` which depends on a `signal` that is updated but
produces a value that is the same as before will _not_ cause the
component's template to refresh.

fixes #51797

PR Close #52476
2023-11-02 13:23:49 -07:00
Andrew Scott c99e5cf168 refactor(core): Update LView consumer to use only 1 consumer for a component (#52476)
This commit updates the reactive consumer used for `LView`s to be shared
between a component and its embedded views. This allows us to use the
consumer flag directly for a dirty indicator rather than needing to
find a component view for updating its flags.

In the future, this will also allow us to effectively poll producers to see if
they really changed before refreshing a view.

PR Close #52476
2023-11-02 13:23:49 -07:00
Andrew Scott 2ce2ad1c42 refactor(core): Remove warning about signal set during template execution (#52476)
The significance of the combination of #51854 and #52302 went mostly
unnoticed. The first removed a unidirectional data flow constraint for
transplanted views and the second updated the signal implementation to
share transplanted view logic. The result is that we automatically get behavior
that (mostly) removes `ExpressionChangedAfterItWasCheckedError` when signals are
used to drive application state to DOM synchronization.

fixes #50320

PR Close #52476
2023-11-02 13:23:49 -07:00
Charles Lyding 0d88e25311 docs: additional application builder SSR conversion information (#52485)
This change updates the new build system migration guide with additional
information regarding the steps needed to convert an existing SSR project
to use the new integrated SSR capabilities.

PR Close #52485
2023-11-02 11:06:21 -07:00
arturovt 79b21662fd refactor(core): do not run CDs when setting up image perf warnings (#52482)
This commit wraps the `load` event listener with `runOutsideAngular` to avoid
running unnecessary change detections on asynchronous tasks spawned within the
scope of the `ImagePerformanceWarning` functionality. This was previously causing
extra change detection cycles in development mode and interfering with debugging.

PR Close #52482
2023-11-02 11:03:42 -07:00
Jeremy Elbourn f9b226f9dc refactor(compiler): escape decorators in API JsDoc extraction (#52481)
TypeScript JsDoc parsing, by default, treats occurences of Angular decorators (e.g. `@Component`) in JsDoc comments as JsDoc tags. This commit escapes these decorator strings by copying the raw JS doc onto a dummy symbol in a new SourceFile to make TypeScript re-parse the comment.

PR Close #52481
2023-11-02 11:03:09 -07:00
Jeremy Elbourn 6d39551f53 build: add rules for generating block/element API data (#52480)
Adds build rules for "artificially" generating `DocEntry` collections for block and element APIs. The two rules are very similar, but _just_ different enough that it's worth having two separate implementations.

PR Close #52480
2023-11-02 11:02:08 -07:00
Jeremy Elbourn 5dd4bd6cf5 build: add target to generate api manifest (#52472)
This adds a target to generate a manifest of all public api symbols. The majority of inputs are generated from the extraction rules, but API entries that don't have a TypeScript source symbol (elements and blocks) are defined in hand-written json collections.

PR Close #52472
2023-11-02 11:00:59 -07:00
Pawel Kozlowski 6d4271c340 refactor(core): short-circuits invocations of signals equality (#52465)
This change skips signal equality calls on set / update when the
two values (current and the new one) are referentially identical.
The assumption is that equality function implementation should
never return false for 2 values that are the same (according to
the Object.is logic).

PR Close #52465
2023-11-02 10:59:59 -07:00
Dylan Hunn 0717c08b7d refactor(compiler): Delete some unused imports in template pipeline (#52464)
Remove a variety of unused imports.

PR Close #52464
2023-11-02 10:58:48 -07:00
Dylan Hunn 2837342852 refactor(compiler): Rename slot -> handle in slot consumers (#52464)
This name is a bit more precise, and avoids the `foo.slot.slot` "stuttering access" on consumers.

PR Close #52464
2023-11-02 10:58:48 -07:00
Dylan Hunn 1841d7903b refactor(compiler): Move an enum to enums.ts for Template Pipeline (#52464)
This enum belongs in the main enums file.

PR Close #52464
2023-11-02 10:58:48 -07:00
Dylan Hunn 90af00a07c refactor(compiler): Delete an unused OpKind in template pipeline. (#52464)
`DeferSecondaryBlock` is no longer used.

PR Close #52464
2023-11-02 10:58:48 -07:00
Dylan Hunn e06a25b6c3 refactor(compiler): Replace cpl with job in template pipeline (#52464)
Eliminate all the remaining `cpl` names, and use `job` instead, which is the predominant convention.

Also, replace `.views.values()` with `.unit` in a few places, and perform the corresponding rename.

PR Close #52464
2023-11-02 10:58:48 -07:00
Dylan Hunn 8358dcd7e3 refactor(compiler): Rename and comment template pipeline phases (#52464)
In this cleanup commit:
1. Add explanatory comments to all phases that were previously missing them.
2. Rename all phases, to eliminate the "phase" prefix, and directly describe their functions.

PR Close #52464
2023-11-02 10:58:48 -07:00
Angular Robot cc13d6f1cd build: update peter-evans/create-or-update-comment digest to 23ff157 (#52279)
See associated pull request for more information.

PR Close #52279
2023-11-02 10:43:24 -07:00
Angular Robot 0754122232 build: update actions/checkout digest to b4ffde6 (#52252)
See associated pull request for more information.

PR Close #52252
2023-11-02 10:32:11 -07:00
arturovt bf3281a6a5 fix(animations): remove finish listener once player is destroyed (#51136)
This commit removes the `finish` listener from the Animation object once
the animation is finished, effectively resolving a memory leak. Previously,
the `finish` listener captured `this`, which prevented `this` from being garbage collected.

PR Close #51136
2023-11-02 10:29:51 -07:00
Alex Rickabaugh 356da9c9f6 refactor(core): add a flag for whether we skip logic in checkNoChanges (#52488)
This can be used to patch different behavior in g3.

PR Close #52488
2023-11-01 21:43:21 -07:00
Doug Parker 4e4c678180 docs: update examples to use buildTarget instead of browserTarget (#52361)
`browserTarget` is deprecated and `buildTarget` is the newly preferred name.

PR Close #52361
2023-11-01 16:24:43 -07:00
Doug Parker 0f37b12a17 docs: update examples to use application builder (#52361)
`application` builder is preferred over `browser` builder. This updates all the examples I could find to use `application` builder. A couple notes of fixes that needed to happen:
1.  The testing example was broken because it relied on `~` imports in CSS files, which is a Webpack-specific features. However I don't think we need those custom styles at all, so I just removed the import altogether.
2.  A few tests seemed to be affected and needed to be cleaned up.

PR Close #52361
2023-11-01 16:24:43 -07:00
Alex Rickabaugh 7c950794ea Revert "refactor(core): Remove RootViewRef<T> because it is the same as ViewRef<T> (#52430)" (#52484)
This reverts commit a3028e2340 as it breaks
tests in g3.

PR Close #52484
2023-11-01 10:13:13 -07:00
Alex Rickabaugh c9a3708e55 Revert "refactor(core): rename ViewRef<T> to InternalViewRef<T> and remove existing InternalViewRef (#52430)" (#52484)
This reverts commit a568bc5d97 as it breaks
tests in g3.

PR Close #52484
2023-11-01 10:13:12 -07:00
Alex Rickabaugh ddef3ac9a4 fix(core): effects wait for ngOnInit for their first run (#52473)
When an effect is created in a component constructor, it might read signals
which are derived from component inputs. These signals may be unreliable or
(in the case of the proposed input signals) may throw if accessed before the
component is first change detected (which is what makes required inputs
available).

Depending on the scenario involved, the effect may or may not run before
this initialization takes place, which isn't a great developer experience.
In particular, effects created during CD (e.g. via control flow) work fine,
as do effects created in bootstrap thanks to the sync CD it performs. When
an effect is created through dynamic component creation outside of CD though
(such as on router navigations), it runs before the component is first CD'd,
causing the issue.

In fact, in the signal components RFC we described how effects would wait
until ngOnInit for their first execution for exactly this reason, but this
behavior was never implemented as it was thought our effect scheduling
design made it unnecessary. This is true of the regular execution of effects
but the above scenario shows that *creation* of the effect is still
vulnerable. Thus, this logic is needed.

This commit makes effects sensitive to their creation context, by injecting
`ChangeDetectorRef` optionally. An effect created with an injector that's
tied to a component will wait until that component is initialized before
initially being scheduled. TestBed effect flushing is also adjusted to
account for the additional interaction with change detection.

PR Close #52473
2023-11-01 08:07:35 -07:00
Alex Rickabaugh 935c1816fd fix(core): add rejectErrors option to toSignal (#52474)
By default, `toSignal` transforms an `Observable` into a `Signal`, including
the error channel of the Observable. When an error is received, the signal
begins throwing the error.

`toSignal` is intended to serve the same purpose as the `async` pipe, but
the async pipe has a different behavior with errors: it rejects them
outright, throwing them back into RxJS. Rx then propagates the error into
the browser's uncaught error handling logic. In the case of Angular, the
error is then caught by zone.js and reported via the application's
`ErrorHandler`.

This commit introduces a new option for `toSignal` called `rejectErrors`.
With that flag set, `toSignal` copies the async pipe's behavior, allowing
for easier migrations.

Fixes #51949

PR Close #52474
2023-10-31 14:59:26 -07:00
Jeremy Elbourn 2e12e277c5 refactor(compiler): rename decorator extracted "options" to "members" (#52462)
The property name `members` makes it easier to use the same rendering
code as other constructs.

PR Close #52462
2023-10-31 14:58:03 -07:00
Alex Castle 3bd85fb7b0 fix(common): apply fixed_srcset_width value only to fixed srcsets (#52459)
add logic to NgOptimizedImage to keep fixed_srcset_width from applying to large responsive images, which is incorrect behavior

PR Close #52459
2023-10-31 14:57:19 -07:00
Jessica Janiuk af46256043 docs: update defer guide to use Deferrable View language (#52457)
This updates the defer guide to use the recommended language of
Deferrable Views.

PR Close #52457
2023-10-31 14:55:48 -07:00
AleksanderBodurri 34a59967d7 refactor(core): ignore ENVIRONMENT_INTIIALIZER and INJECTOR_DEF_TYPES provides in getInjectorProviders (#52458)
These special providers are configured when `walkProviderTree` is called. Because of this, they do not maintain any equality between subsequent runs of `walkProviderTree`. This prevents us from being able to compare the provider objects for equality between runs.

This commit changes the behaviour of getInjectorProviders to ignore these providers. In the future we will consider another approach for differentiating these providers from ones provided by users rather than the framework.

PR Close #52458
2023-10-31 14:55:04 -07:00
Matthieu Riegler 792026bc7d build: add missing entries for the api extraction (#52452)
Were missing :

* `@angular/common/http/testing`
* `@angular/platform-browser/animations/async`

PR Close #52452
2023-10-31 14:53:34 -07:00
Kristiyan Kostadinov d2fca84bc4 refactor(compiler): implement control flow content projection fix in template pipeline (#52414)
Recreates the fix for content projection in control flow in the new template pipeline. I also had to make the following adjustments to the pipeline:
1. The `TemplateOp.tag` property was being used to generate the name of the template function, rather than the actual tag name being passed into `ɵɵtemplate`. Since the content projection fix requires the tag name to be passed in, I've introduced a new `functionNameSuffix` property instead.
2. `TemplateOp.block` was being used to determine whether to pass `TemplateOp.tag` into the `ɵɵtemplate` instruction. Now that we're always passing in the tag name after the refactor in point 1, we no longer need this flag.

In addition to the refactors above, I also made some minor cleanups where I saw the opportunity to do so.

PR Close #52414
2023-10-31 14:52:31 -07:00
Kristiyan Kostadinov 1f5039bbd6 fix(compiler): project control flow root elements into correct slot (#52414)
With the directive-based control flow users were able to conditionally project content using the `*` syntax. E.g. `<div *ngIf="expr" projectMe></div>` will be projected into `<ng-content select="[projectMe]"/>`, because the attributes and tag name from the `div` are copied to the template via the template creation instruction. With `@if` and `@for` that is not the case, because the conditional is placed *around* elements, rather than *on* them. The result is that content projection won't work in the same way if a user converts from `*ngIf` to `@if`.

These changes aim to cover the most common case by doing the same copying when a control flow node has *one and only one* root element or template node.

This approach comes with some caveats:
1. As soon as any other node is added to the root, the copying behavior won't work anymore. A diagnostic will be added to flag cases like this and to explain how to work around it.
2. If `preserveWhitespaces` is enabled, it's very likely that indentation will break this workaround, because it'll include an additional text node as the first child. We can work around it here, but in a discussion it was decided not to, because the user explicitly opted into preserving the whitespace and we would have to drop it from the generated code. The diagnostic mentioned point #1 will flag such cases to users.

Fixes #52277.

PR Close #52414
2023-10-31 14:52:31 -07:00
AleksanderBodurri 696f003553 fix(core): mutation bug in getDependenciesFromInjectable (#52450)
Previously, we would modified `dep.flags` directly to convert injection flags to booleans. This caused a mutation bug where subsequent calls to `getDependenciesFromInjectable` would result in the flags object containing false for every injection flag.

Now, we stop modifying `dep.flags` directly and instead assign the converted flags to a new object.

PR Close #52450
2023-10-31 14:21:36 -07:00
Alan Agius e5c3b25496 build: update node.js engines version to be more explicate about v20 support (#52448)
This commit adds Node.js 20 as explicitly supported version to match the Angular CLI engines.

See: https://github.com/angular/angular-cli/pull/26173

PR Close #52448
2023-10-31 14:18:36 -07:00
cexbrayat b9ea2d6900 fix(core): handle aliased index with no space in control flow migration (#52444)
The current regexp supposes that there is at least one space between the `=` and the aliased variable.
As it is possible to write `let myIndex=index`, this commit updates the regexp to handle such a case.

PR Close #52444
2023-10-31 13:45:00 -07:00
Mayuresh Dharwadkar abb4a9bad3 docs: Fix typos (#52442)
PR Close #52442
2023-10-31 13:44:14 -07:00
JoostK d517a75c9b fix(compiler-cli): use originally used module specifier for transform functions (#52437)
Prior to this change, the transform function would be referenced with a potentially
relative import into an external declaration file. Such imports are not portable
and should not be created in this context. This commit addresses the issue by threading
though the originally used module specifier by means of the `Reference` type.

Fixes #52324

PR Close #52437
2023-10-31 13:42:53 -07:00
JoostK 96fd73dc5d fix(compiler-cli): properly emit literal types in input coercion function arguments (#52437)
This commit fixes an issue where using literal types in the arguments of an input coercion
function could result in emitting invalid output, due to an assumption that TypeScript makes
when emitting literal types. Specifically, it takes the literal's text from its containing
source file, but this breaks when the literal type node has been transplanted into a
different source file. This issue has surfaced in the type-check code generator and is
already being addressed there, so this commit moves the relevant `TypeEmitter` class
from the `typecheck` module to the `translator` module, such that it can be reused for
emitting types in the type translator.

Fixes #51672

PR Close #52437
2023-10-31 13:42:53 -07:00
Swami fdd82857b1 docs(core): remove documentation of mutate function (#52435)
PR Close #52435
2023-10-31 13:42:15 -07:00
Kristiyan Kostadinov ddd9df68bb fix(compiler): allow decimals in defer block time values (#52433)
Fixes that our regex for parsing time values in defer blocks didn't allow for decimals. This isn't relevant for times in milliseconds, but it can be convenient to write something like `on timer(1.5s)`.

PR Close #52433
2023-10-31 13:39:27 -07:00
Andrew Scott 59b6ec6be8 docs(core): Deprecate ChangeDetectorRef.checkNoChanges (#52431)
The `checkNoChanges` method does not belong in the API of production interface. `checkNoChanges` is
limited to testing and should not be used in any application code. Test
code should use `ComponentFixture` instead of `ChangeDetectorRef`.
Additionally, it is not desirable to have the `checkNoChanges` API
available in a context where `detectChanges` is not run first.

DEPRECATED: `ChangeDetectorRef.checkNoChanges` is deprecated.

Test code should use `ComponentFixture` instead of `ChangeDetectorRef`.
Application code should not call `ChangeDetectorRef.checkNoChanges` directly.

PR Close #52431
2023-10-31 13:38:44 -07:00
Kristiyan Kostadinov 3f456821b5 refactor(compiler): account for comments when resolving implicit deferred triggers (#52449)
Adds some logic to skip over comments when resolving implicit `@defer` block triggers. This currently isn't a problem since we don't capture comments by default, but it may come up if we start capturing comments.

PR Close #52449
2023-10-31 13:35:00 -07:00
Kristiyan Kostadinov 2aaddd3f64 fix(compiler): allow comments between switch cases (#52449)
Fixes that the template parser was throwing an error if a comment is used directly inside an `@switch` block.

Fixes #52421.

PR Close #52449
2023-10-31 13:35:00 -07:00
Kristiyan Kostadinov 6c58252521 fix(compiler): compilation error when for loop block expression contains new line (#52447)
Fixes that the regex which captures the expression of a `@for` loop block wasn't accounting for line breaks.

Fixes #52446.

PR Close #52447
2023-10-31 13:30:42 -07:00
Andrew Scott d8280ace7d refactor(core): Remove RootViewRef<T> because it is the same as ViewRef<T> (#52430)
`RootViewRef<T>` extends `ViewRef<T>` and overrides 3 methods with behavior
that is identical to `ViewRef<T>`. This commit removes `RootViewRef<T>`
because it is not needed.

PR Close #52430
2023-10-31 13:22:56 -07:00
Andrew Scott b2c38399f9 refactor(core): rename ViewRef<T> to InternalViewRef<T> and remove existing InternalViewRef (#52430)
The `ViewRef<T>` interface extends `InternalViewRef` and is already not
part of the public API. There is no need for the extra `InternalViewRef`
interface. This confusing setup is likely leftover from the types
necessary to support both Ivy and ViewEngine.

PR Close #52430
2023-10-31 13:22:56 -07:00
Jeremy Elbourn 65ccaa4169 docs: minor revisions to control flow guide (#52411)
This commit makes some minor writing changes to the control flow guide for brevity, consistency, and active voice.

PR Close #52411
2023-10-31 13:20:35 -07:00
Matthieu Riegler 8156b3d4ec fix(http): Don't override the backend when using the InMemoryWebAPI (#52425)
When using `withFetch`,  the `PRIMARY_HTTP_BACKEND` token is set.

The InMemory Backend services will also set that token.

This means that providers order will matter and the latest on the list will be the one instantiated

PR Close #52425
2023-10-31 13:17:19 -07:00
Alex Rickabaugh 59a3475d77 refactor(core): global epoch to optimize non-live signal reads (#52420)
This commit adds a global epoch to the reactive graph, which can optimize
non-live reads.

When a non-live read occurs, a computed must poll its dependencies to check
if they've changed, and this operation is transitive and not cacheable.
Since non-live computeds don't receive dirty notifications, they're forced
to assume potential dirtiness on each and every read.

Using a global epoch, we can add an important optimization: if *no* signals
have been set globally since the last time it polled its dependencies, then
we *can* assume a clean state. This significantly improves performance of
large unwatched graphs when repeatedly reading values.

PR Close #52420
2023-10-31 13:12:18 -07:00
Charles Lyding 6e82ae5871 docs: update esbuild guide for v17 (#52407)
Update new build system guide to reflect stable status and the use of the `application` builder in existing projects.

PR Close #52407
2023-10-31 13:10:43 -07:00
Andrew Scott de3454a507 refactor(language-service): Improve autocomplete snippet of for block (#52405)
The `for` block has several parts which we know are required. This
commit improves the autocomplete snippet of the `for` block by adding
those required parts and providing placeholders.

PR Close #52405
2023-10-31 13:08:11 -07:00
Francesco Donzello 0451e7eabc docs: added missing imports for toSignal and toObservable (#52393)
PR Close #52393
2023-10-31 13:07:31 -07:00
Andrew Scott e355f05b9a refactor(language-service): Add quick info for built in control flow/blocks (#52386)
Adds hover info for:

* Defer blocks
* Triggers and trigger behavior keywords
* For loop empty block
* Track keyword in for loop block

resolves https://github.com/angular/vscode-ng-language-service/issues/1946

PR Close #52386
2023-10-31 12:56:45 -07:00
Dylan Hunn 595009ecf4 refactor(compiler): Implement the remaining defer on triggers (#52387)
The previous commits provided the scaffolding for `defer on`. In this commit, we build on that work, adding triggers for `immediate`, `timer`, `hover`, and `viewport`.

PR Close #52387
2023-10-31 12:45:18 -07:00
Dylan Hunn 1d7d53caa3 refactor(compiler): Introduce ConstCollectedExpr, and use it in defer on expressions (#52387)
Previously, we supported a `HasConst` trait, allowing an op to be const collected automatically. However, that approach had the shortcoming that each op could only collect a single constant.

Instead, we now provide a `ConstCollectedExpr`, which collects constants at the expression level, allowing ops to have multiple collectible consts.

Then, we use this new abstraction to support the `defer on` conditions.

PR Close #52387
2023-10-31 12:45:18 -07:00
Dylan Hunn 6a11e9e8b4 refactor(compiler): Implement defer conditions, and change the way slots are linked (#52387)
Previously, we had an "empty shell" implementation of defer conditions, and we used separate ops to represent secondary defer blocks.

Now, we have a real scaffolding for supporting the various defer conditions, and the secondary defer block information has been refactored onto the main defer op.

Additionally, to enable this, we refactor the way that using slot indices works. Instead of having a trait that causes users of slot indices to be linked to the allocated slot, we share a single `SlotHandle` object by reference. This allows an op to use slot information for more than one Xref at a time, and eliminates a layer of indirection.

Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com>

PR Close #52387
2023-10-31 12:45:18 -07:00
Pawel Kozlowski 877ba3e3c1 refactor(core): use performance API for control flow and standalone (#52370)
This commit adds a standard performance marker that can be viewed in Chrome dev tools and other tooling.
See more info at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #52370
2023-10-31 12:43:51 -07:00
Andrew Scott 3861a73135 perf(core): Update LView consumer to only mark component for check (#52302)
This commit updates the reactive template and host binding consumers to
only mark their declaration components for refresh, but not parents/ancestors.

This also updates the `AfterViewChecked` hook to run when a component is
refreshed during change detection but its host is not. It is reasonable
to expect that the `ngAfterViewChecked` lifecycle hook will run when a
signal updates and the component is refreshed. The hooks are typically
run when the host is refreshed so without this change, the update to
not mark ancestors dirty would have caused `ngAfterViewChecked` to not
run.

resolves #14628
resolves #22646

resolves #34347 - this is not the direct request of the issue but
generally forcing change detection to run is necessary only because a
value was updated that needs to be synced to the DOM. Values that use
signals will mark the component for check automatically so accessing the
`ChangeDetectorRef` of a child is not necessary. The other part of this
request was to avoid the need to "mark all views for checking since
it wouldn't affect anything but itself". This is directly addressed by
this commit - updating a signal that's read in the view's template
will not cause ancestors/"all views" to be refreshed.

PR Close #52302
2023-10-31 12:37:56 -07:00
marktechson e74b578bd9 docs: update home page for live stream event (#52456)
PR Close #52456
2023-10-31 11:58:18 -07:00
Emma Twersky b14f0dc5d6 docs: make banner persist until 11/8 (#52432)
PR Close #52432
2023-10-31 11:57:08 -07:00
cexbrayat 4461cefa4f fix(core): handle trackBy and aliased index in control flow migration (#52423)
Currently, the migration always use `$index` in the migrated trackBy function, whereas this variable might be aliased.
The compiler then errors with:

```
error TS2339: Property '$index' does not exist on type 'UsersComponent'.

110 @for (user of users; track byId($index, user); let i = $index) {
```

This commit updates the migration to use the aliased index if there is one.

PR Close #52423
2023-10-27 16:16:46 -07:00
Miles Malerba ce54ead2b5 refactor(compiler): Split the i18n placeholder mega-phase (#52390)
The i18n placeholder resolution phase has accumulated too much logic,
making it difficult to understand. This commit refactors it into several
smaller phases to make it easier to manage.

I suspect this will undergo further refactoring in the near future as I
work through the ICU logic. In particular `ExtractedMessageOp` feels
like a bit of a grab bag of properties, and the i18n const collection
phase is also starting to get quite heavy. This refactor at least feels
like a good start.

PR Close #52390
2023-10-27 16:16:04 -07:00
Miles Malerba f6c2b39812 test(compiler): Enable passing i18n tests (#52390)
Enables a handful of i18n tests that are currently skipped, but pass if
enabled. Some of them require alternate golden files because of
inconsequential differences in the cost array order.

PR Close #52390
2023-10-27 16:16:04 -07:00
Matthieu Riegler 8e17dd719d refactor(localize): mark TargetMessage and MessageId as public. (#52418)
Both entries are exported in the `index.ts`

PR Close #52418
2023-10-27 16:06:11 -07:00
Andrew Kushnir 57b9798cf6 docs: update SSR docs (#52408)
This commit updates the `ssr.md` file to restructure (and simplify) SSR docs.

PR Close #52408
2023-10-27 15:16:14 -07:00
Jessica Janiuk 2579c7121e refactor(migrations): Add optional path param for control flow migration (#52403)
This adds the option to pass in a path to the control flow migration in order to run the migration against one single file.

PR Close #52403
2023-10-27 15:15:22 -07:00
Jessica Janiuk d7397fb29b fix(migrations): Ensure control flow migration ignores new block syntax (#52402)
This fix ensures that the control flow migration does not encounter any problems when new block sytax already exists in a template.

PR Close #52402
2023-10-27 13:02:03 -07:00
Jessica Janiuk e40e55d902 fix(migrations): Remove unhelpful parsing errors from the log (#52401)
When running the control flow migration, unhelpful ICU parsing errors were being logged and creating a bunch of noise for users.

PR Close #52401
2023-10-27 13:00:24 -07:00
Pawel Kozlowski ffd0b4c30b docs: control flow API reference (#52397)
Adds API reference for the new built-in control flow.

PR Close #52397
2023-10-27 12:56:51 -07:00
Jeremy Elbourn 5dd2822523 refactor(compiler): extract api docs for inherited members (#52389)
This commit expands docs extraction for classes and interfaces to include inherited members. This relies on the type checker to get the _resolved_ members of the type so that the extractor doesn't need to reason about inheritance rules, which can get tricky (especially with regards to method overloads).

PR Close #52389
2023-10-27 12:54:14 -07:00
Jeremy Elbourn 7e8a214474 refactor(compiler): extract decorator API docs (#52389)
This commit adds decorators to the extracted API docs. It makes some
very hard-coded assumptions about the pattern used to declare decorators
that's extremely specific to what the framework does today.

PR Close #52389
2023-10-27 12:54:14 -07:00
Jessica Janiuk e92eda9a31 docs: Add defer guide (#52380)
This adds a user guide for how to use defer.

PR Close #52380
2023-10-27 12:50:24 -07:00
Ivad 499c22251d docs(docs-infra): fix typo in tap into the observable tutorial example (#52359)
PR Close #52359
2023-10-27 12:49:39 -07:00
Evan Kleiner becbcdcff0 docs: add warning for localhost proxy (#51948)
PR Close #51948
2023-10-27 12:32:22 -07:00
Alex Rickabaugh 384d7aacd0 fix(core): replace assertion with more intentional error (#52427)
Issue #50320 shows that in some cases, updating a signal that's a dependency
of a template during change detection of that template can have several
adverse effects. This can happen, for example, if the signal is set during
the lifecycle hook of a directive within the same template that reads the
signal.

This can cause a few things to happen:

* Straightforwardly, it can cause `ExpressionChanged` errors.
* Surprisingly, it can cause an assertion within the `ReactiveLViewConsumer`
  to fail.
* Very surprisingly, it can cause change detection for an `OnPush` component
  to stop working.

The root cause of these later behaviors is subtle, and is ultimately a
desync between the reactive graph and the view tree's notion of "dirty" for
a given view. This will be fixed with further work planned for change
detection to handle such updates directly. Until then, this commit improves
the DX through two changes:

1. The mechanism of "committing" `ReactiveLViewConsumer`s to a view is
   changed to use the `consumerOnSignalRead` hook from the reactive graph.
   This prevents the situation which required the assertion in the first
   place.

2. A `console.warn` warning is added when a view is marked dirty via a
   signal while it's still executing.

The warning informs users that they're pushing data against the direction of
change detection, risking `ExpressionChanged` or other issues. It's a
warning and not an error because the check is overly broad and captures
situations where the application would not actually break as a result, such
as if a `computed` marked the template dirty but still returned the same
value.

PR Close #52427
2023-10-27 12:30:17 -07:00
Angular Robot 612986bcd3 build: update cross-repo angular dependencies (#52191)
See associated pull request for more information.

PR Close #52191
2023-10-26 14:12:34 -07:00
Alan Agius da1b71e0a5 docs: update SSR doc to reflect version 17 (#52398)
Changes to SSR doc

PR Close #52398
2023-10-26 12:30:59 -07:00
Jessica Janiuk 6a01d62b9d fix(migrations): fix broken migration when no control flow is present (#52399)
This addresses a bug that caused the control flow migration to crash when no control flow was present in the template.

PR Close #52399
2023-10-26 09:40:11 -07:00
Pawel Kozlowski befd197933 docs: guide for the built-in control flow (#52369)
This change adds a dedicated developer guide for the
built-in control flow.

PR Close #52369
2023-10-26 09:38:56 -07:00
Alan Agius 9376e7f05e refactor(common): avoid repetitive globalThis in global locales (#52394)
This changes reduced slightly the bundle size as `global` can be minified unlike direct usage of `globalThis`.

PR Close #52394
2023-10-26 09:37:41 -07:00
Andrew Kushnir 576338a9f5 build: update zone.js version to 0.14.1 (#52385)
This commit updates the `package.json` of the zone.js package.

PR Close #52385
2023-10-26 09:28:32 -07:00
Andrea Canciani 8580542153 refactor: fix a number of typos throughout the codebase (#52249)
Fix some typos such as `boostrap`, `propery` and more, both in
documentation and in code (comments, identifiers).

PR Close #52249
2023-10-25 16:51:24 -07:00
Susheel Thapa 57630c3769 docs: fixed typos (#52297)
PR Close #52297
2023-10-25 16:39:09 -07:00
Alan Agius f1d42cef87 docs: update to standalone and routing by default (#52293)
This commit covers several changes:
 - routing enabled by default
 - standalone by default

PR Close #52293
2023-10-25 16:37:52 -07:00
Pawel Kozlowski 48816e8c22 docs: remove the dev preview warning from the signals guide (#52375)
Signals are out of dev preview now.

PR Close #52375
2023-10-25 16:37:23 -07:00
Jessica Janiuk d3cc0e998f ci: Add members to fw-migrations pullapprove group (#52378)
This adds jessicajaniuk and AndrewKushnir to the fw-migrations reviewers list.

PR Close #52378
2023-10-25 16:36:56 -07:00
Jessica Janiuk 13b16b905f docs: Adjust defer api summary for prefetch clarity (#52379)
This updates language to clarify exactly how prefetch works in a defer block configuration.

PR Close #52379
2023-10-25 16:36:29 -07:00
Alan Agius 40f75364d4 release: cut the zone.js-0.14.1 release (#52374)
PR Close #52374
2023-10-25 14:39:44 -07:00
Dylan Hunn 2b0698def0 release: cut the v17.0.0-rc.1 release 2023-10-25 14:35:56 -07:00
Andrew Scott 6858149eb1 fix(router): RouterTestingHarness should throw if a component is expected but navigation fails (#52357)
The `RouterTestingHarness` should throw an error if the call to `navigateByUrl`
expects a component to be activated but the navigation failed.

fixes #52344

PR Close #52357
2023-10-25 11:04:26 -07:00
AleksanderBodurri a803c89a21 fix(core): load global utils before creating platform injector in the standalone case (#52365)
This is needed so that the providers configured on platform creation are picked up by the injector profiler.

PR Close #52365
2023-10-25 10:51:24 -07:00
AleksanderBodurri 957effa404 fix(core): get root and platform injector providers in special cases (#52365)
Previously, because the platform injector does not have a provider container, this API would fail. Now, we account for this case specifically by returning the found providers immediately, without trying to calculate their importpaths.

Also previously, in the case where a boostrapped standalone component did not import any feature modules, the environment injector connected to that bootstrapped component would be the root injector configured by `bootstrapApplication`. This injector is configured through a `providers` array instead of an `imports` array, and also does not have a provider container. Similarly to the platform case, we account this for this by returning the found providers immediately if there is no provider container for our standalone component.

PR Close #52365
2023-10-25 10:51:23 -07:00
AleksanderBodurri ea6d75291f fix(core): emit provider configured event when a service is configured with providedIn (#52365)
Previously this case was missed by the default framework injector profiler. Now in ngDevMode this event emits correctly when a service is configured with `providedIn`. This includes the case where injection tokens are configured with a `providedIn`.

This commit also includes unit tests for this new case in the injector profiler.

PR Close #52365
2023-10-25 10:51:23 -07:00
Osama Heykal 403d217a59 docs: added missing 'next steps' sections (#52229)
PR Close #52229
2023-10-25 10:50:46 -07:00
Jessica Janiuk 1acff00468 docs: Add @defer API reference documentation (#52336)
This adds the initial API reference documentation for the new defer blocks.

PR Close #52336
2023-10-25 10:47:49 -07:00
Jessica Janiuk 4c878f90d2 fix(migrations): Add support for nested structures inside a switch statement (#52358)
This updates the code to handle switches more elegantly in line with how the other blocks are handled. This allows nesting to be handled just like other blocks.

PR Close #52358
2023-10-25 09:39:47 -07:00
Alvaro Junqueira 938007df9d docs: the Component import is not being used (#52323)
PR Close #52323
2023-10-25 09:35:24 -07:00
Naaajii cd903cc9f4 docs(docs-infra): fix codeblock snippet for APP_INITIALIZER (#52354)
fixes broken code snippet for standalone application using APP_INITIALIZER

PR Close #52354
2023-10-25 09:34:48 -07:00
Matthieu Riegler 9b32d9148e docs(core): fix the language code (#52352)
PR Close #52352
2023-10-25 09:32:19 -07:00
mgechev 0536b61bb8 docs: update the roadmap to reflect the progress as of v17 (#52356)
PR Close #52356
2023-10-25 09:31:46 -07:00
Alan Agius df68ad4731 fix(zone.js): use globalThis instead of global and window (#52367)
`globalThis` global property contains the global `this` value, which is usually akin to the global object. This is needed for better compatibility with CloudFlare workers were global nor window are defined as globals.

PR Close #52367
2023-10-25 09:31:18 -07:00
Alan Agius 6d0362cb44 refactor(core): modernize $localize checks (#52368)
Use nullish coalescing.

PR Close #52368
2023-10-25 09:30:51 -07:00
Alan Agius 1660f667e0 refactor(common): modernize code in locale file (#52368)
Use `globalThis` global property contains the global this value, which is usually akin to the global object.

PR Close #52368
2023-10-25 09:30:51 -07:00
Andrew Scott 1dd8558f82 fix(core): Ensure backwards-referenced transplanted views are refreshed (#51854)
This commit runs change detection in a loop while there are still dirty
views to be refreshed in the tree. At the moment, this only applies to
transplanted views but will also apply to views with changed signals.

fixes angular#49801

PR Close #51854
2023-10-24 14:50:19 -07:00
Kristiyan Kostadinov c9b1ddff4d fix(migrations): handle nested classes in control flow migration (#52309)
Fixes that the control flow migration was only processing top-level classes. Nested classes could come up during unit tests.

PR Close #52309
2023-10-24 14:34:16 -07:00
Kristiyan Kostadinov 54bc384661 fix(migrations): handle nested classes in block entities migration (#52309)
Fixes that the block entities migration was only processing top-level classes. Nested classes could come up during unit tests.

PR Close #52309
2023-10-24 14:34:16 -07:00
Alan Agius bd9e91ecf7 perf(http): reduce data transfer when using HTTP caching (#52347)
This commit reduces the property size in the http transfer cache to reduce the page payload.

Before
```html
<script id="ng-state" type="application/json">
{
  "4155228514": {
    "body": "....",
    "headers": {},
    "status": 200,
    "statusText": "OK",
    "url": "http://foo.com/assets/media.json",
    "responseType": "json"
  },
}
</script>
```

Now
```html
<script id="ng-state" type="application/json">
{
  "4155228514": {
    "b": "....",
    "h": {},
    "s": 200,
    "st": "OK",
    "u": "http://foo.com/assets/media.json",
    "rt": "json"
  },
}
</script>
```

PR Close #52347
2023-10-24 14:33:26 -07:00
Joey Perrott 4e00851c04 build: lock file maintenance (#52353)
See associated pull request for more information.

PR Close #52353
2023-10-24 14:29:17 -07:00
Jessica Janiuk 54fed68bbf fix(migrations): Fixes the root level template offset in control flow migration (#52355)
When migrating an ng-template later on in a file, the migrationResult was not being reset to zero and causing offsets to be double applied due to ng-template nodes being included in the migration loop.

PR Close #52355
2023-10-24 14:22:32 -07:00
Dylan Hunn 114a2f8153 refactor(compiler): Don't double-create pipes in switch cases (#52289)
Previously, we would emit *two* pipe creation instructions for each pipe in a switch case. This is because we were visiting both the transformed and raw versions of the pipe bindings.

Now, we clear the raw case expressions array after generating the transformed test expression.

Also, we introduce some new goldens, because our pipe creation order is harmlessly different.

PR Close #52289
2023-10-24 11:07:52 -07:00
Dylan Hunn 7002cfc811 refactor(compiler): Support content projection source maps (#52289)
The `projection` op should map onto the entire corresponding `ng-content`.

PR Close #52289
2023-10-24 11:07:52 -07:00
Dylan Hunn 0d23baa740 refactor(compiler): Update pipe test golden for alternative create order (#52289)
We roughly attempt to match TemplateDefinitionBuilder's pipe creation order, by placing pipe creation instructions after their target elements. However, we cannot fully emulate the "inside-out" ordering TemplateDefinitionBuilder uses when multiple pipes apply to one element, because TemplateDefinitionBuilder creates the pipes as expressions are visited, from the leaves up. Our order is perfectly adequate though.

We also add a non-compatibility-mode ordering, which just appends them to the end of the create block. This is better because it allows for more chaining opportunities.

PR Close #52289
2023-10-24 11:07:52 -07:00
Dylan Hunn 3c08f91a01 refactor(compiler): Fix a special case involving var counting for singleton propertyInterpolate (#52289)
Singleton property interpolation instructions consume only one variable, but are still emitted as an interpolation instruction (they cannot be collapsed because `propertyInterpolate` implicitly stringifies its argument.)

PR Close #52289
2023-10-24 11:07:52 -07:00
Dylan Hunn 20993f7a87 refactor(compiler): The projection instruction takes an array literal (#52289)
We were incorrectly emiting a extracted constant pool index for the final argument of the projection instruction. It actually takes an array literal.

(N.B.: This means we re-create the array every time! We should probably modify the runtime to use a const index for this.)

Additionally, we alter the projection op to not extend the element op base type.

PR Close #52289
2023-10-24 11:07:52 -07:00
Dylan Hunn 6ade8c9c8c refactor(compiler): Improve the ordering of update ops (#52289)
The correct order of attributes and properties is:

1. Interpolated properties
2. Interpolated attributes
3. Non-interpolated properties
4. Non-interpolated attributes

This includes an additional nuance: singleton attribute interpolations, such as `[attr.foo]="{{bar}}"`, will be "collaped" into a simple `attribute` instruction. However, this is *not* the case for singleton property interpolations! The ordering phase must take this nuance into account to match the TemplateDefinitionBuilder order.

After the project lands, it might be nice to also collapse singleton property interpolations.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn e4df686ec6 refactor(compiler): Order elements before other phases (#52289)
Previously, we ran the ordering phase near the end of the compilation. However, this meant that phases like slot assignment and variable offset assignment would happen first, and then the nice, monotonically-increasing orders would be scrambled by the reordering.

It's much more intelligible to order first, and then perform these assignments. However, to make this happen, some modifications to the ordering phase are required. In particular, we can no longer rely on `advance` instructions to break up orderable groups.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn 1fa5020739 refactor(compiler): Imitate TemplateDefinitionBuilder's variable offset assignment order (#52289)
Many instructions consume variable slots, which are used to persist data between update runs. For top-level instructions, the offset into the variable data array is implicitly advanced, because those instructions always run.

However, instructions in non-top-level expressions cannot be assumed to run every time, because they might be conditionally executed. Therefore, they cannot implicitly advance the offset into the variable data, and must be given an explicitly assigned variable offset.

TemplateDefinitionBuilder assigned offsets top-to-bottom for all instructions *except* pure functions. Pure functions would be assigned offsets lazily, on a second pass.

Template Pipeline can now imitate this behavior, when in compatibility mode: pure functions are assigned offsets on a second pass.

This also makes the "variadic var offsets" phase unnecessary -- the new approach is more general and correct.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn d2df9fb59b refactor(compiler): Use already available context in closures, instead of saving it (#52289)
Previously, inside an event listener, template pipeline would always save the context from restoring a view, e.g.

```
const restored_ctx = r0.ɵɵrestoreView(s);
```

This is usually correct! However, consider the case of a listener in the template's root view. The appropriate context will already be available via closure capture, and we can just use it (as `ctx`).

Now, the context resolution phase understands that we don't need to use the restored view's saved context if we would have access to it by closure.

Note: we also create a new golden, because the const array is in a harmlessly different order.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn fe82292583 refactor(compiler): Empty context variable reads are $implicit (#52289)
Previously, the template pipeline did not handle "empty" reads gracefully: it would emit syntactically invalid reads of empty properties. Now we read `$implicit`.

This allows us to enable a test that relies on `$implicit`. However, we also have to create another golden, because our variable inlining is more aggressive.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn 439e83a974 refactor(compiler): Update golden for pipe binding collapsing (#52289)
We currently allow elements to be collapsed around pipe creation instructions. TemplateDefinitionBuilder disallows this, but only sometimes. Collapsing in this case is actually less generated code, and it's OK to allow it.

PR Close #52289
2023-10-24 11:07:51 -07:00
Dylan Hunn 4ade911e66 refactor(compiler): Enable already-passing tests (#52289)
Some tests are already passing, but were not enabled at the time they were previously fixed.

PR Close #52289
2023-10-24 11:07:50 -07:00
Alan Agius d2f8412322 refactor(platform-server): replace global usages with globalThis (#52342)
This commit updates a number of `global` usages to `globalThis` to better support other platforms like CF workers

PR Close #52342
2023-10-24 09:26:50 -07:00
Alan Agius 00dee8384c refactor(platform-server): remove dependency between @angular/platform-server and @angular/platform-server/init (#52342)
Previously, the cross entry-point dependency was created to share the `setDomTypes` function. This however, causes extra transformations "linking" during the application build since other Angular code in `@angular/platform-server` is pulled in.

With this commit we remove the cross dependency and thus remove the need for extra transformations in the server polyfill bundle with the result of having a slightly faster build.

See: https://github.com/angular/angular-cli/pull/26113

PR Close #52342
2023-10-24 09:26:50 -07:00
Alex Castle ceb7f1eaf0 docs(common): add preconnect generation to NgOptimizedImage docs (#52248)
add a note on automated preconnect generation to the NgOptimizedImage docs' preconnect section, and add a related FAQ entry.

PR Close #52248
2023-10-24 09:26:06 -07:00
Andrew Kushnir f069b3a71a refactor(core): ignore after and minimum when transition between states in tests (#52314)
This commit updates the logic to ignore `after` and `minimum` conditions when `DeferBlockFixture.render` method is used in tests.

Resolves #52313.

PR Close #52314
2023-10-24 09:25:14 -07:00
Andrew Kushnir ec1e652f8a refactor(core): report @defer errors using ErrorHandler (#52320)
This commit updates the code to report errors via `ErrorHandler` instance.
For dependency loading problems, errors are reported only when `@error` block is not provided.

PR Close #52320
2023-10-24 09:24:43 -07:00
cexbrayat a87586538a docs: remove test.ts file in aio (#52327)
Same as 7ba5dc87ed, but for aio
This file is not necessary since CLI v15+

PR Close #52327
2023-10-24 09:23:02 -07:00
Jessica Janiuk 6a3a7b0d13 refactor(migrations): Fixes offset calculations for nesting (#52332)
This updates offset to handle pre and post offset properly for nested situations, rather than relying on solely nestCount. This should properly apply offset calculations at the right time to handle any nested situation.

PR Close #52332
2023-10-24 09:22:26 -07:00
Andrew Scott 0c1cf7ac6b test(core): fix test observing broken OnPush checkNoChanges (#52337)
The test is actually observing that OnPush results in a view not being checked
when checkNoChanges runs.

PR Close #52337
2023-10-24 09:21:55 -07:00
Doug Parker 394c0fb719 docs: fix typo (#52339)
PR Close #52339
2023-10-24 09:21:15 -07:00
Kristiyan Kostadinov 6795cccbbb fix(compiler): account for type-only imports in defer blocks (#52343)
Fixes that `@defer` blocks didn't account for type-only imports which could cause the import to be considered as not deferrable.

PR Close #52343
2023-10-24 09:20:49 -07:00
Angular Robot 96ad3bf065 docs: update Angular CLI help [17.0.x] (#52331)
Updated Angular CLI help contents.

Closes #52280

PR Close #52331
2023-10-23 13:45:59 -07:00
Dylan Hunn 3f7d081112 refactor(compiler): Support track functions and context variables in @for loops for template pipeline (#52001)
The template pipeline can now generate track functions, and extract them into the constant pool (or optimize them if needed). Additionally, context variables such as `$index` can be used inside track functions and for loop bodies.

PR Close #52001
2023-10-23 13:44:30 -07:00
Dylan Hunn 2560115793 refactor(compiler): Support @for blocks in template pipeline (#52001)
Add support for `repeaterCreate` and `repeater` instructions. Correctly count decls and vars, and support primary and empty blocks.

`track` functions are not yet extracted.

PR Close #52001
2023-10-23 13:44:30 -07:00
Dylan Hunn 9060cebc02 refactor(compiler): Create an alias concept on views (#52001)
View compilations previously had context variables, which were variables available in the view that would result in a property read on the context object.

We now also support the notion of aliases. An alias is a variable available in the view compilation, which might be derived from a context variable, which it may reference by name. It is always inlined at all usage sites, and therefore is not allowed to depend on the current context.

Under the hood, aliases rely on the new `AlwaysInline` mode.

Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com>

PR Close #52001
2023-10-23 13:44:30 -07:00
Dylan Hunn db81193ce6 refactor(compiler): Create an AlwaysInline variable mode (#52001)
The template pipeline now supports lexical variables that are always inlined into their call sites, even if multiple call sites exist.

An `AlwaysInline` variable may not rely on the current context, because it will potentially be inlined at several different locations.

Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com>

PR Close #52001
2023-10-23 13:44:29 -07:00
Emma Twersky af6a75a837 docs: add event banner (#52334)
PR Close #52334
2023-10-23 13:40:02 -07:00
Gerald Monaco f206d1041f refactor(core): use performance API for afterRender feature logging (#52235)
Add a standard performance marker that can be viewed in Chrome Dev Tools and other tooling. See more info at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #52235
2023-10-23 12:03:42 -07:00
Alan Agius f5aed23924 docs: update app-shell docs to reflect v17 (#52294)
Update app-shell docs to reflect v17 changes

PR Close #52294
2023-10-23 12:03:15 -07:00
Andrew Scott 8473675db2 refactor(core): Remove duplicate null check in helper function (#52312)
This commit is a small refactor to remove the top-level null check on the
variable that's already checked for null in the main loop

PR Close #52312
2023-10-23 12:02:48 -07:00
Alan Agius 0c66e2424c fix(platform-server): resolve relative requests URL (#52326)
Prior to this commit relative HTTP requests were not being resolved to absolute even thought the behaviour is documented in https://angular.io/guide/universal#using-absolute-urls-for-http-data-requests-on-the-server.

This caused relative HTTP requests to fail when done on the server because of missing request context. This change is also required to eventually support HTTP requests handled during prerendering (SSG).

Closes #51626

PR Close #52326
2023-10-23 12:02:21 -07:00
Dylan Hunn 9d565cd6d6 fix(language-service): Autocomplete block keywords in more cases (#52198)
Previously, autocompletions were not available in two main cases. We correct them.

1. Autocompletions immediately after `@` were usually not working, for example `foo @|`. We fix this by causing the lexer to not consider the `@` part of the text node.
2. Autocompletions such as `@\nfoo`, where a newline follows a bare `@`, were not working because the language service visitor considered us inside the subsequent text node. We fix this by adding a block name span for the block keyword, and special-case whether we are completing inside the name span. If we are, we don't continue to the following text node.

PR Close #52198
2023-10-23 12:01:52 -07:00
Andrew Kushnir 182d574fa3 refactor(core): defer triggers cleanup (#52291)
This commit adds the logic to cleanup all triggers once defer block is triggered.

When a trigger is created, its cleanup function is stored alongside other defer block info. Prefetch and regular triggers are store in different slots, since we need to invoke them at different time.

PR Close #52291
2023-10-23 12:00:19 -07:00
Joey Perrott 569f57daa9 build: update lockfile (#52270)
Update lockfiles throughout

PR Close #52270
2023-10-23 09:30:39 -07:00
Andrew Kushnir dfc77e5f58 refactor(core): use performance API for defer feature (#52288)
This commit adds a standard performance marker that can be viewed in Chrome dev tools and other tooling. See more info at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #52288
2023-10-23 09:29:37 -07:00
Andrew Kushnir 6005fe2d70 refactor(core): use performance API for hydration-related features (#52288)
This commit adds a standard performance marker that can be viewed in Chrome dev tools and other tooling. See more info at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #52288
2023-10-23 09:29:37 -07:00
Kristiyan Kostadinov 1dc14d9853 perf(core): avoid unnecessary callbacks in after render hooks (#52292)
A few performance improvements and code cleanups in the after render hooks:
1. We were wrapping each `destroy` callback in another callback, because it was typed as `|undefined`. This is unnecessary, because the callback is guaranteed to exist. These changes pass the `destroy` function around directly and avoid the additional callback.
2. In server platforms we were recreating a noop `AfterRenderRef` on each invocation. We can save some memory by returning the same one.
3. Reworks the `AfterRenderCallback` so that it injects `NgZone` and `ErrorHandler` itself, instead of expecting them to be passed in. This reduces the amount of repetition in the code.

PR Close #52292
2023-10-23 09:29:10 -07:00
Alan Agius 129b5d8bde docs: update prerendering guide (#52300)
This commits update the prerendering docs with the version 17 changes.

PR Close #52300
2023-10-23 09:28:42 -07:00
Andrew Kushnir 7a064e372a refactor(core): avoid invoking IntersectionObserver in defer triggers on the server (#52306)
This commit updates the logic to make sure that DOM-specific triggers do not produce errors on the server.

Resolves #52304.

PR Close #52306
2023-10-23 09:28:15 -07:00
Kristiyan Kostadinov 81c315ec6e fix(compiler): template type checking not reporting diagnostics for incompatible type comparisons (#52322)
In #52110 the compiler was changed to produce `if` statements when type checking `@switch` in order to avoid a bug in the TypeScript compiler. In order to avoid duplicate diagnostics, the main `@switch` expression was ignored in each of the `@case` comparisons. This appears to have caused a regression where comparing incompatible types wasn't being reported anymore.

These changes resolve the issue by wrapping the expression in parentheses which allows the compiler to report comparison diagnostics while ignoring diagnostics in the expression itself.

Fixes #52315.

PR Close #52322
2023-10-23 09:27:24 -07:00
Andrew Scott 4dce8766f8 fix(router): Ensure newly resolved data is inherited by child routes (#52167)
The current way of computing a route's params and data recomputes
inherited data from the inheritance root every time. When the
inheritance strategy is "emptyOnly", this isn't necessarily the root of
the tree, but some point along the way (it stops once it reaches an
ancestor route with a component).

Instead, this commit updates parameter inheritance to only inherit data
directly from the parent route (again, instead of recomputing all
inherited data back to the inheritance root). The only requirement for
making this work is that the parent route data has already calculated
and updated its own inherited data. This was really already a
requirement -- parents need to be processed before children.

In addition, the update to the inheritance algorithm in this commit
requires more of an understanding that a resolver running higher up in
the tree has to propagate inherited data downwards. The previous
algorithm hid this knowledge because resolvers would recompute inherited
data from the root when run. However, routes that did not have resolvers
rerun or never had resolvers at all would not get the updated resolved data.

fixes #51934

PR Close #52167
2023-10-19 10:26:28 -07:00
Alex Castle 0b4c013aac refactor(common): use performance API for NgOptimizedImage feature logging (#52205)
add a standard performance marker that can be viewed in Chrome dev tools and other tooling. See more info
at https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark

PR Close #52205
2023-10-19 09:39:06 -07:00
Sylvain DEDIEU 958346107f refactor(core): no need for signals property in component interface since inherited from directive (#52039)
Removed the signals property definition from the Component interface since it already exists in the Directive interface and Component inherits from Directive

PR Close #52039
2023-10-19 09:37:57 -07:00
Andrew Kushnir a207421ada refactor(core): check "skip hydration" condition only when hydration info is present (#52221)
This commit refactors a couple places to improve performance:

* avoid checking parent tree if a current node has "skip hydration" flag
* avoid calling `isInSkipHydrationBlock` if there is no hydration info present

PR Close #52221
2023-10-19 09:27:55 -07:00
Alan Agius 7cc9f193fe refactor(platform-server): replace Node.js URL API usage (#52231)
This commit replace the usage of URL node API to WHATWG URL API.

See: https://nodejs.org/api/url.html#the-whatwg-url-api

PR Close #52231
2023-10-19 09:26:45 -07:00
Kristiyan Kostadinov 30f993a356 test(core): clean up unnecessary nesting in old tests (#52239)
A lot of our tests are wrapped in `{}` which serves no purpose, aside from increasing the nesting level and, in some cases, causing confusion. The braces appear to be a leftover from a time when all tests were wrapped in a `function main() {}`. The function declaration was removed in #21053, but the braces remained, presumably because it was easier to search&replace for `function main()`, but not to remove the braces at the same time.

PR Close #52239
2023-10-19 09:26:15 -07:00
Etienne FRANK aaf00092c8 docs(forms): replace fb with formBuilder (#52261)
Shorten variable names isn't a good practice. To avoid spreading it, we removed it from Angular's documentation.

PR Close #52261
2023-10-19 09:24:40 -07:00
Andrew Scott 44f08baf63 docs: Update control flow diagnostic to direct devs to built-ins (#52268)
The commit adds messaging to the control flow template diagnostic to direct developers to the new
built-in control flow syntax in Angular.

PR Close #52268
2023-10-19 09:24:08 -07:00
Matthieu Riegler b4c1ffbfa1 refactor(forms): Log a warning when FormGroup keys include a dot. (#50642)
Due to the dotted synthax to resolve controls, keys in FormGroups cannot include dots.

fixes #50608

PR Close #50642
2023-10-18 12:11:25 -07:00
Andrew Kushnir 4567a090c9 test(core): add benchmark for hydration runtime logic (#52206)
This commit adds a benchmark for hydration runtime logic, which contains 2 parts: a baseline (create DOM nodes from scratch) and a main scenario (DOM matching instead of re-creating nodes).

PR Close #52206
2023-10-18 12:07:13 -07:00
Andrew Kushnir fc3cde6cff test(core): add benchmark for defer runtime logic (#52222)
This commit adds a benchmark for `@defer` runtime logic and uses `@if` as a baseline.

PR Close #52222
2023-10-18 12:06:45 -07:00
Kristiyan Kostadinov 1a8a5cf68f refactor(core): avoid duplicate work when checking if hydration is disabled (#52253)
Minor refactor to avoid lowercasing the `ngSkipHydration` attribute on each call. Since it's static, we can do it once ahead of time.

PR Close #52253
2023-10-18 12:06:03 -07:00
cexbrayat 866d0f422d refactor(core): remove test.ts files in integration tests (#52109)
These files are not necessary since CLI v15+

PR Close #52109
2023-10-18 11:52:55 -07:00
Quentin b00dd23846 docs: fix typo (#52233)
Fix word: standalone

PR Close #52233
2023-10-18 11:51:49 -07:00
Jeevan Mahesha 6fbe371b2c docs: updated the export default router to satisfies instead of typecast (#52269)
PR Close #52269
2023-10-18 11:51:20 -07:00
Pawel Kozlowski edd060efbf release: cut the v17.0.0-rc.0 release 2023-10-18 18:59:22 +02:00
Andrew Kushnir 1091239d2d refactor(core): remove developer preview note from hydration stats note (#52197)
This commit updates the message that we output in the console (once hydration is completed) to drop the reference to the developer preview.

PR Close #52197
2023-10-18 18:21:49 +02:00
Andrew Kushnir dc6ca34d4c docs: remove developer preview note from hydration feature in docs (#52197)
PR Close #52197
2023-10-18 18:21:49 +02:00
Andrew Kushnir 33bd523be3 docs: promote provideClientHydration and related symbols to stable (#52197)
This commit removes the `@developerPreview` annotation from the `provideClientHydration` function and related symbols, promoting them to stable.

PR Close #52197
2023-10-18 18:21:49 +02:00
Andrew Kushnir 08553046bf docs: promote renderApplication to stable (#52197)
This commit removes the `@developerPreview` annotation from the `renderApplication`, promoting it to stable.

PR Close #52197
2023-10-18 18:21:49 +02:00
Andrew Kushnir 603f30cab0 docs: promote withFetch and FetchBackend to stable (#52197)
This commit removes the `@developerPreview` annotation from the `withFetch` and `FetchBackend` APIs, promoting them to stable.

PR Close #52197
2023-10-18 18:21:49 +02:00
vivekkoya eccbf36dbd docs: Update BAZEL.md with correct bazelrc documentation link (#52090)
PR Close #52090
2023-10-18 18:15:04 +02:00
Quentin 419855629d docs: fix typo (#52258)
Fix typo in aio/content/errors/NG05104.md file

PR Close #52258
2023-10-18 18:13:44 +02:00
Angular Robot 37768da011 build: update all non-major dependencies (#51897)
See associated pull request for more information.

PR Close #51897
2023-10-18 17:49:46 +02:00
Angular Robot 1e941f6f84 build: update scorecard action dependencies (#52126)
See associated pull request for more information.

PR Close #52126
2023-10-18 17:45:31 +02:00
Angular Robot 920f072824 build: update io_bazel_rules_sass digest to 425ecaf (#52173)
See associated pull request for more information.

PR Close #52173
2023-10-18 17:37:09 +02:00
Pawel Kozlowski 37d627dbd4 perf(core): minimze trackBy calculations (#52227)
Re-organize code to minimize number of calls to the
trackBy function.

PR Close #52227
2023-10-18 14:04:24 +02:00
Pawel Kozlowski 1032c1e1a5 perf(core): cache LiveCollectionLContainerImpl (#52227)
This change avoid re-creation of the LiveCollectionLContainerImpl instance
every time the repeater runs (on every change detection).

PR Close #52227
2023-10-18 14:04:24 +02:00
Pawel Kozlowski 8e4a7ab52b perf(core): avoid repeated access to LContainer and trackBy calculation (#52227)
Assuming that the trackBy function is a pure derivation from the collection
object and its index, we can skip trackBy calculation if items in the live
and new colelction have the same identity and index. Additionally this change
minimizes access to the LContainer array.

PR Close #52227
2023-10-18 14:04:24 +02:00
Pawel Kozlowski 3fec271fa3 refactor(core): move key calculation in list reconciler (#52227)
We can speedup items comparision by having access to raw values
and delay key calculation in certain conditions.

PR Close #52227
2023-10-18 14:04:24 +02:00
Pawel Kozlowski f9ef762bc7 refactor(core): remove the at method from the LiveCollection type (#52227)
The at operation is private and doesn't have to be part of the public interface.

PR Close #52227
2023-10-18 14:04:24 +02:00
Miles Malerba e87ac245e0 docs(compiler): Clarify the purpose of i18n param resolution time (#52250)
Adds some additional doc comments clarifying when the `Creation`
resolution time is used vs the `Postprocessing` resolution time.

PR Close #52250
2023-10-18 14:00:04 +02:00
Miles Malerba 39a6f62551 refactor(compiler): Wrap bare ICUs in an i18n block (#52250)
ICUs can be used outside of an i18n block. In this case the ICU should
be automatically wrapped in a new i18n block. This commit adds a new
phase to handle wrapping these bare ICUs.

PR Close #52250
2023-10-18 14:00:04 +02:00
Miles Malerba 96267922fe refactor(compiler): Resolve ICU params during i18n post-processing (#52250)
ICU params in i18n messages are now resolved in the post-processing call
rather than in the initial message creation. This matches the output
generated by TemplateDefinitionBuilder.

PR Close #52250
2023-10-18 14:00:04 +02:00
Miles Malerba ba37440cbf refactor(compiler): Add support for ingesting ICUs (#52250)
ICUs are now ingested by adding ops to both the creation and update IR.
Both of these ops are ultimately removed before reification, but they
are needed to coordinate and link data between the creation and update
ops. This is done in a new ICU extraction phase that removes both ICU
ops and adds an i18nExpr op to the update IR.

PR Close #52250
2023-10-18 14:00:04 +02:00
Alan Agius 5d3f66d1a6 Revert "perf(platform-browser): disable styles of removed components instead of removing (#51808)" (#52238)
This reverts commit 65786b2b96 due to an oberved perf regression in Pantheon.

See: http://b/303667704

PR Close #52238
2023-10-18 11:34:21 +02:00
Quentin 36993f3459 docs: fix typo (#52247)
Fix word: corresponding

PR Close #52247
2023-10-18 11:29:41 +02:00
Angular Robot d811d82a12 build: update babel dependencies to v7.23.2 (#52236)
See associated pull request for more information.

PR Close #52236
2023-10-17 18:11:09 +02:00
Quentin c6e4cf12f0 docs: fix typo (#52232)
Fix word: privileges

PR Close #52232
2023-10-17 18:10:05 +02:00
Jeremy Elbourn a318e6ac7c refactor(compiler): extract generic info for api reference (#52204)
This commit extracts the API reference info for generic parameters for
classes, methods, interfaces, and functions. It includes any constraints
and the default type if present.

PR Close #52204
2023-10-17 12:29:24 +02:00
Alan Agius c9cde3ab10 perf(platform-browser): only append style element on creation (#52237)
Prior to this change the style element was appended to host element multiple times. Whilst the actual element was not added multiple to the DOM multiple times. This causes a performance regression and it caused repainting.

This can be observed in the below benchmark.

```js
(() => {
  const time = (name, fn) => {
    const t = performance.now();
    fn();
    console.log(name, performance.now() - t);
  }

  const s = document.createElement("style");
  s.textContent = "@layer x{} @font-face { font-family: foo; }";

  time("append and enable", () => {
    document.head.append(s);
    s.disabled = false;
  });
  time("compute body color", () => {
    getComputedStyle(document.body).color;
  });
  time("compute body layout", () => {
    document.body.offsetTop;
  });
  time("append and disable", () => {
    document.head.append(s);
    s.disabled = false;
  });
  time("compute body color", () => {
    getComputedStyle(document.body).color;
  });
  time("compute body layout", () => {
    document.body.offsetTop;
  });
})();
```

Output
```
append and enable 0.20000000298023224
compute body color 0.7999999970197678
compute body layout 2.899999998509884
append and disable 0.10000000149011612
compute body color 0.7000000029802322
compute body layout 2.2999999970197678
```

When commenting the 2nd `document.head.append(s);`, the results are slightly different and we can see that calling `getComputedStyle` does not incur any performance impact this is a result of no repainting.

```
append and enable 0.10000000149011612
compute body color 0.7999999970197678
compute body layout 3.1999999955296516
append and disable 0.10000000149011612
compute body color 0
compute body layout 0
```

Pantheon benchmarks: http://docs/spreadsheets/d/1iLRLGCmVYZHuVRdI7dO_WM7wnQ1DvkS-tJzi-0-u1KY?resourcekey=0-kwtrf0nbAhcPqAGdqbdz4g#gid=0

PR Close #52237
2023-10-17 11:18:02 +02:00
Miles Malerba 35fd10a9fd test(compiler): Update golden partial file (#52202)
Updates the golden partial file to account for the newly added test

PR Close #52202
2023-10-17 10:13:23 +02:00
Miles Malerba e760fd7865 refactor(compiler): Fix handling of structural directive on i18n element (#52202)
Placing a structural directive on an element with an `i18n` attribute
was generating too many i18n blocks. This was due to both the element
and the template generating their own i18n block. To fix the issue, we
no longer generate top-level i18n blocks for structural directive
templates.

PR Close #52202
2023-10-17 10:13:23 +02:00
Miles Malerba 4e135947e6 refactor(compiler): Fix handling of sturctural directive on ng-template (#52202)
Structural directives on an ng-template (e.g. <ng-template *ngIf>) were
being assigned the wrong tag name ('ng-template' instead of null).

PR Close #52202
2023-10-17 10:13:23 +02:00
Miles Malerba 6fe4f44b1d refactor(compiler): Fix i18n placeholders for slef-closing elements (#52195)
Fixes handling of placeholders for self-closing tags. Self-closing tags
set a combined value for the start tag placeholder, rather than separate
values for the start and close placeholders.

This commit also enables a number of now passing tests. For some of
these tests I had create a separate golden file due to the different
ordering of the const array. In the template pipeline, i18n and
attribute const collection happen in different pahses and we therefore
get a different order than TemplateDefinitionBuilder, which collected
everything in one pass. The order should not affect the overall behavior.

PR Close #52195
2023-10-16 19:25:05 +02:00
Miles Malerba 920820a1a8 refactor(compiler): Add support for i18n post-processing (#52195)
Runs post-processing on any i18n messages that have multiple values
assigned to a single placeholder.

PR Close #52195
2023-10-16 19:25:05 +02:00
Miles Malerba 6735f5f634 refactor(compiler): Fix propagation of child i18n params (#52195)
The way we were propagating params up to parent i18n ops didn't account
for the fact that a parent and child could both have a value for the
same placeholder. In order to properly merge the value for these cases,
we need to propagate the params up *before* serialization. Therefore I
removed the standalone param propagation phase and folded the logic into
the placeholder resolution phase.

PR Close #52195
2023-10-16 19:25:05 +02:00
Miles Malerba 2d197f0c65 refactor(compiler): Remove unnecessary checks for empty container types (#52195)
I added these in an earlier PR when we were considering moving the empty
elements phase earlier. Since we decided not to do that, this commit
cleans up unnecessary references to the empty versions of the element to
simplify the code and types.

PR Close #52195
2023-10-16 19:25:05 +02:00
cexbrayat e5720edb46 fix(core): handle if alias in control flow migration (#52181)
This adds the support of `if ` conditions with `as` clause when migrating to the control flow syntax.
It now adds the required semicolon before the `as` when migrating the template.

Before: `@if (user$ | async as user) {`
After: `@if (user$ | async; as user) {`

PR Close #52181
2023-10-16 18:35:19 +02:00
Quentin eaf735da58 docs: fix typo (#52218)
Fix word: adjustments

PR Close #52218
2023-10-16 18:33:22 +02:00
Quentin bd1e78d746 docs: fix typo (#52219)
Fix word: incompatibilities

PR Close #52219
2023-10-16 18:30:46 +02:00
Kristiyan Kostadinov 81a287a79a fix(compiler): avoid error in template parser for tag names that can occur in object prototype (#52225)
Fixes that the compiler was throwing an error if an element tag name is the same as a built-in prototype property (e.g. `constructor` or `toString`). The problem was that we were storing the tag names in an object literal with the `Object` prototype. These changes resolve the issue by creating an object without a prototype.

Fixes #52224.

PR Close #52225
2023-10-16 18:22:15 +02:00
Andrew Miller 2fa5e702de docs(language-service): add neovim setup to angular language service docs (#52212)
PR Close #52212
2023-10-16 18:19:12 +02:00
Matthieu Riegler 65c2f02ccb refactor(core): remove hack on render3 (#51238)
Once useful, the hack to prevent circular dependency serves no purpose
We can safely remove it.

PR Close #51238
2023-10-16 17:43:36 +02:00
Osama Heykal a6be2e221f docs: add missing next steps section (#52220)
Added missing 'next steps' section in the tutorial.

PR Close #52220
2023-10-16 16:27:15 +02:00
Gerald Monaco 6ae68f39b9 refactor(core): run internal work outside of public afterRender phases (#52145)
Public afterRender phases have specific API guarantees which can be invalidated if the internal framework is implemented using them. Instead, the framework should use dedicated internal functions.

PR Close #52145
2023-10-16 12:24:00 +02:00
Andrew Kushnir 450f360bd7 refactor(core): simplify hydration annotation key (#52207)
This commit drops the `ɵ` symbol from hydration annotation key: `__ɵnghData__` -> `__nghData__`. This helps ensure that there are no UTF8 symbols that might be damaged in case a web server is misconfigured.

Noticed while working on https://github.com/angular/angular/pull/52206.

PR Close #52207
2023-10-16 11:25:57 +02:00
Kristiyan Kostadinov c1dc86d186 refactor(core): tree shake depepdency interceptor token (#52199)
We have the `DEFER_BLOCK_DEPENDENCY_INTERCEPTOR` DI token that we use in tests to intercept the dependency loading function from deferred blocks, however we were referencing it in a way that caused it to be retained in production bundles as well.

These changes guard the call site with `ngDevMode` since the token is only used for testing.

PR Close #52199
2023-10-16 11:17:24 +02:00
Kristiyan Kostadinov a58301f980 refactor(core): minor fixes for the block entities migration (#52209)
Contains the following minor improvements to the block entities migration:
* The migration won't be stopped anymore if it can't read a template file.
* The migration will exit early if a template doesn't contain the two characters we need to migrate.
* Reduced the amount of code that is wrapped by a try/catch to avoid suppressing errors.

PR Close #52209
2023-10-16 11:15:31 +02:00
thomas b2ba678e73 docs: add thomas laforge to GDE resources and add angularchallenges to resources (#52075)
PR Close #52075
2023-10-16 11:13:01 +02:00
Paul Gschwendtner 2d98726ec9 refactor(core): remove unused helper for setting LView for reactive consumer (#52192)
This code path is never hit because the assignment of the lview happens
in `commitLViewConsumerIfHasProducers`.

PR Close #52192
2023-10-13 14:08:35 +02:00
Thomas Wilkinson 030a68b3df refactor(router): Introduce StateManager interface (#52171)
Move existing logic into `HistoryStateManager`

PR Close #52171
2023-10-13 13:48:17 +02:00
Matthieu Riegler 6fd863d6e1 refactor(core): cleanup DebugElement (#50896)
* `style` is never `null`.
* adding some typing
* Add 2 todos per #49777

PR Close #50896
2023-10-13 12:24:17 +02:00
Matthieu Riegler ba9fc2419e refactor(core): deprecate the NgProbeToken (#51396)
DEPRECATED: NgProbeToken

The `NgProbeToken` is not used internally since the transition from View Engine to Ivy. The token has no utility and can be removed from applications and libraries.

PR Close #51396
2023-10-13 12:19:05 +02:00
Ward Bell e90f5e5f22 docs: Phase 2 Sample Code Conversions to Standalone (#51918)
Phase 2 sample code conversions per instructions in doc: https://docs.google.com/document/d/1QqnVK8Mc0O1gU9ctPrs-JJ42V9_dHCuEttD_01pXh34/edit

**Included**

1. ajs-quick-reference

Minor changes to remove references to NgModule. Converted sample to standalone.

2. architecture

Migrated the code sample. Made minor changes to remove references to NgModule. I explicitly deprecated the `architecture-module.md` page and removed it from the navigation.

4. deprecations / deprecations-guide

Migrated the code sample which required pulling NgModule and Reactive Forms material into a new `deprecated` folder. Made minor changes to clarify when advice was specific to NgModule (see `loadChildren` in particular).

5. displaying-data

Deleted this `displaying-data` sample entirely as there is no need for a sample app just to display a single, vanilla data binding in the deprecated `zone.md` page. Replaced reference in `zone.md` with an inline code snippet.

10. ngcontainer

Converted the code sample. There is no guide page for this sample. It can be displayed in Stackblitz

**Excluded**

3. cli-builder

Code sample is not an app; nothing to convert.

6-9 - related to getting-started and setup.

Waiting until Angular team is ready to proceed.

11. providers

This is in its own commit and PR 51918 because it may not make sense to convert. See that PR for details.

12. routing-with-urlmatcher

No work needed. Already Standalone.

13 schematics-for-libraries

Non-trivial (see doc notes) and Angular may decide to leave as is. Should have own PR.

14 setup

Non-trivial (see doc notes) and Angular may decide to leave as is. Should have own PR.

PR Close #51918
2023-10-13 11:20:09 +02:00
Kristiyan Kostadinov 8e5d54a3d5 refactor(core): clean up viewport trigger event tracking (#52156)
When the `viewport` triggers were first introduced, we ended up having to use a service to keep track of them, because using the same global event handling as the other events led to some inconsistent test failures. It looks like the failures were caused by the same bug fixed #52115 so now we can switch back to the previous approach which is a bit more compact.

PR Close #52156
2023-10-13 11:16:26 +02:00
Miroslav Foltýn 54f7578d55 docs(router): add clarification for behavior of paramsInheritanceStrategy: 'emptyOnly' (#52170)
Follows up to the https://github.com/angular/angular/issues/52108 which was opened due to the confusion surrounding paramsInheritanceStrategy: 'emptyOnly'.

PR Close #52170
2023-10-13 11:14:23 +02:00
Andrew Kushnir a6fcf75423 test(core): verify directive order inside and outside of @defer blocks (#52194)
This test verifies that the order does not change when a particular part of code is wrapped by the `@defer` block.

PR Close #52194
2023-10-13 11:10:20 +02:00
cexbrayat 99e7629159 fix(core): do not remove used ng-template nodes in control flow migration (#52186)
This fixes an issue where `ng-template` nodes were removed even when used in other places than control flow directives.

Template to migrate:
```html
<ng-template #blockUsedElsewhere><div>Block</div></ng-template>
<ng-container *ngTemplateOutlet="blockUsedElsewhere"></ng-container>
```

Before:
```html
<ng-container *ngTemplateOutlet="blockUsedElsewhere"></ng-container>
```

After:
```html
<ng-template #blockUsedElsewhere><div>Block</div></ng-template>
<ng-container *ngTemplateOutlet="blockUsedElsewhere"></ng-container>
```

PR Close #52186
2023-10-12 19:28:23 +02:00
Jessica Janiuk 6f19117a6a refactor(core): Add better error handling to control flow migration (#52189)
This adds some clear error handling and messaging for when errors occur during the control flow migration.

PR Close #52189
2023-10-12 19:27:00 +02:00
cexbrayat ffe9b1fcc2 fix(core): handle for alias with as in control flow migration (#52183)
This adds the support of `let index as myIndex` in `*ngFor` for the control flow migration.

Before: `@for (itm of items; track itm)`
After: `@for (itm of items; track itm; let myIndex = $index)`

PR Close #52183
2023-10-12 19:15:20 +02:00
Matthieu Riegler 0792424df2 refactor(animations): make AnimationBuilder tree-shakable (#52097)
This commit allows also to use the `AnimationBuilder` when using `provideAnimationsAsync()`

fixes #52096

PR Close #52097
2023-10-12 18:13:22 +02:00
Nicolas Frizzarin 5c70148d20 docs: add Nicolas Frizzarin to GDE resources (#52003)
This PR add Nicolas Frizzarin to the GDE resources

docs: fix typo on description

PR Close #52003
2023-10-12 17:44:49 +02:00
cexbrayat 65b460448e fix(common): missing space in ngSwitch equality warning (#52180)
This adds a missing space between the sentences of the `===` vs `==` ngSwitch warning.

PR Close #52180
2023-10-12 15:44:47 +02:00
Andrew Kushnir 4cd1b3d38b refactor(core): better organization of @defer runtime code (#52152)
This commit splits the `render3/instructions/defer.ts` file (that contained most of the runtime code) into smalle
r files that are easier to maintain.

There are no functional changes in this PR, just organizing code.

PR Close #52152
2023-10-12 12:43:54 +02:00
Angular Robot 4e9d6878a5 build: update cross-repo angular dependencies (#52159)
See associated pull request for more information.

PR Close #52159
2023-10-12 12:42:24 +02:00
Pawel Kozlowski 2bf088e7bb Revert "refactor(compiler-cli): remove MethodIdentifier type (#49611)" (#52174)
This reverts commit c2b1a242e8.

PR Close #52174
2023-10-12 12:35:50 +02:00
Matthieu Riegler 2e5ee1c198 refactor(compiler-cli): remove MethodIdentifier type (#49611)
`MethodIdentifier` is unused as is `IdentifierKind.Method`. They both can be removed.

PR Close #49611
2023-10-11 12:34:51 -07:00
Angular Robot 371e26f6b3 docs: update Angular CLI help [main] (#52168)
Updated Angular CLI help contents.

PR Close #52168
2023-10-11 12:19:22 -07:00
Angular Robot d50d3b85bd docs: update events (#52053)
Generated `events.json` with the latest events retrieved from the Firebase DB.

PR Close #52053
2023-10-11 12:18:06 -07:00
Matthieu Riegler 073ebfe09e fix(compiler): apply style on :host attributes in prod builds. (#49118)
In prod builds, selectors are optimized and spaces a removed. #48558 introduced a regression on selectors without spaces. This commit fixes tihs.

Fixes #49100

PR Close #49118
2023-10-11 11:32:19 -07:00
Andrew Scott 926db6dd36 release: cut the v17.0.0-next.8 release 2023-10-11 10:12:33 -07:00
6631 changed files with 407553 additions and 366091 deletions
+4 -1
View File
@@ -16,7 +16,6 @@ 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
@@ -39,3 +38,7 @@ 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/
+18 -4
View File
@@ -1,5 +1,5 @@
# Enable debugging tests with --config=debug
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --spawn_strategy=local
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
# Do not attempt to de-flake locally.
# On CI we might set this to `3` to run with deflaking.
@@ -56,6 +56,11 @@ build --enable_runfiles
build:release --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=release"
build:release --stamp
# Building AIO against local Angular deps requires stamping
# versions in Angular packages due to CLI version checks.
build:aio_local_deps --stamp
build:aio_local_deps --workspace_status_command="yarn -s --cwd aio local-workspace-status"
# Snapshots should also be stamped with version control information.
build:snapshot-build --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=snapshot"
build:snapshot-build --stamp
@@ -69,6 +74,14 @@ build:snapshot-build --stamp
##########################################################
build --flag_alias=aio_build_config=//aio:flag_aio_build_config
####################################
# AIO first party dep substitution #
# Turn on with #
# --config=aio_local_deps #
####################################
build:aio_local_deps --//aio:flag_aio_local_deps
###############################
# Output #
###############################
@@ -105,6 +118,10 @@ common:remote --jobs=200
build:remote --google_default_credentials
# Limit the number of test jobs for on an AIO local deps build. The example tests running
# concurrently pushes the circleci executor RAM usage to its limits.
test:aio_local_deps --jobs=24
# Force remote exeuctions to consider the entire run as linux
build:remote --cpu=k8
build:remote --host_cpu=k8
@@ -160,9 +177,6 @@ test:saucelabs --flaky_test_attempts=1
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
# --adev_fast will run adev build/serve in a faster mode, skipping things like prerendering
# for local development.
build --flag_alias=fast_adev=//adev:fast_build_mode
####################################################
# User bazel configuration
+1 -1
View File
@@ -1,7 +1,7 @@
# Image metadata and config.
# Ideally, the Node.js version should match what we use on CI.
# See `executors > default-executor` in `.circleci/config.yml`.
FROM cimg/node:18.13.0@sha256:45d0ee279f522c4562a464df71108104cc9b2187205333ffd0707d4f05589731
FROM cimg/node:16.13-browsers@sha256:6c7a82701665dda03e92d682360fa2e4358e07dab8d1b6a7e48f7d36e3d98117
LABEL name="Angular dev environment" \
@@ -8,6 +8,7 @@
"devondcarew.bazel-code",
"gkalpak.aio-docs-utils",
"ms-vscode.vscode-typescript-tslint-plugin",
"xaver.clang-format",
// The following extensions are useful when working on angular.io (i.e. inside the `aio/` directory).
//"angular.ng-template",
//"dbaeumer.vscode-eslint",
+1 -1
View File
@@ -1,4 +1,4 @@
name: 'Docs or angular.dev Bug Report'
name: 'Docs or angular.io Bug Report'
description: Report an issue in Angular's documentation or angular.io application
body:
+1 -1
View File
@@ -4,7 +4,7 @@ description: 'Installs the dependencies using Yarn'
runs:
using: 'composite'
steps:
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
path: |
./node_modules/
-38
View File
@@ -1,38 +0,0 @@
# 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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build //adev:build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
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
@@ -1,50 +0,0 @@
# 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
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}}'
+42
View File
@@ -0,0 +1,42 @@
# This workflow builds the AIO 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 AIO app for preview deployment
on:
pull_request:
types: [synchronize, labeled]
permissions: read-all
jobs:
aio-build:
runs-on: ubuntu-latest
# We only want to build and deploy the AIO app if the `aio: preview` label has been
# added, or if the label is already applied and new changes have been made in the PR.
if: |
(github.event.action == 'labeled' && github.event.label.name == 'aio: preview') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'aio: preview'))
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
bazelrc: ./.bazelrc.user
# Build the web package. Note: We run Bazel from a low-resource Github action container,
# so we manually need to instruct Bazel to run more actions concurrently as by default
# 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
with:
workflow-artifact-name: 'aio'
pull-number: '${{github.event.pull_request.number}}'
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
deploy-directory: './dist/bin/aio/build'
+44
View File
@@ -0,0 +1,44 @@
# This workflow runs whenever the AIO 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 AIO preview to Firebase
on:
workflow_run:
workflows: ['Build AIO app for preview deployment']
types: [completed]
permissions:
# Needed in order to be able to comment on the pull request.
pull-requests: write
env:
PREVIEW_PROJECT: ng-dev-previews
PREVIEW_SITE: ng-dev-previews-fw
jobs:
aio-deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Configure Firebase deploy target
working-directory: aio/
run: |
# We can use `npx` as the Firebase deploy actions uses it too.
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
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'aio'
firebase-config-dir: './aio/'
firebase-public-dir: './aio/dist'
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
@@ -13,9 +13,9 @@ jobs:
assistant_to_the_branch_manager:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: angular/dev-infra/github-actions/branch-manager@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -19,7 +19,7 @@ jobs:
exit: true
# Indicate that the benchmark command was received.
- uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
- uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
with:
comment-id: ${{github.event.comment.id}}
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
@@ -28,7 +28,7 @@ jobs:
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
id: comment-branch
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
# Specify repository as the PR branch might be from a fork.
repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}}
@@ -38,7 +38,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
bazelrc: ./.bazelrc.user
@@ -53,7 +53,7 @@ jobs:
id: benchmark
name: Running benchmark
- uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
- uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
with:
issue-number: ${{github.event.issue.number}}
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
+64
View File
@@ -0,0 +1,64 @@
name: CI
on:
pull_request_target:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
permissions: {}
defaults:
run:
shell: bash
jobs:
saucelabs:
runs-on: ubuntu-latest-4core
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
ref: ${{ github.event.pull_request.head.sha }}
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- name: Preparing Bazel-generated fixtures required in legacy tests
run: |
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
mkdir -p dist/legacy-test-out/core/test/reflection/
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
# Locale files are needed for i18n tests running within Saucelabs. These are added
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
# can succeed. Note that the base locale and currencies files are checked-in, so
# we do not need to re-generate those through Bazel.
mkdir -p packages/common/locales/extra
cp dist/bin/packages/common/locales/*.ts packages/common/locales
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
- name: Build bundle of tests to run on Saucelabs
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
- name: Wait and confirm Saucelabs tunnel has connected
run: ./tools/saucelabs/sauce-service.sh ready-wait
timeout-minutes: 3
- name: Running tests on Saucelabs.
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
- name: Stop Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh stop
+103 -109
View File
@@ -5,7 +5,6 @@ on:
branches:
- main
- '[0-9]+.[0-9]+.x'
- 'remove-aio-stuff'
pull_request:
types: [opened, synchronize, reopened]
@@ -24,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
@@ -36,6 +35,8 @@ jobs:
run: yarn install --frozen-lockfile --cwd aio
- name: Check code lint
run: yarn -s tslint
- name: Check code lint in aio
run: yarn -s --cwd aio lint
- name: Check for circular dependencies
run: yarn -s ts-circular-deps:check
- name: Validate pull approve configuration
@@ -59,13 +60,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -77,34 +78,103 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
run: yarn test:ci
adev:
aio:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with upstream packages
run: yarn --cwd aio test:ci
- name: Check generated bundle sizes
run: yarn --cwd aio payload-size
aio-local:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev in fast mode to ensure it continues to work
run: yarn bazel build //adev:build --fast_adev
- name: Run tests
run: yarn bazel test //adev:test
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with local packages
run: yarn --cwd aio test-local:ci
- name: Check generated bundle sizes
run: yarn --cwd aio payload-size aio-local
aio-deploy:
needs: [aio]
if: needs.aio.result == 'success' && github.event_name == 'push'
runs-on:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Set the stable branch environment variable
id: latest_version
run: |
echo "CI_STABLE_BRANCH=$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')" >> $GITHUB_OUTPUT
echo "CI_BRANCH=$(echo ${{ github.event.ref }} | cut -d '/' -f 3)" >> $GITHUB_OUTPUT
- name: Deploy aio to production
env:
CI_AIO_MIN_PWA_SCORE: '95'
CI_COMMIT: ${{ github.event.after }}
CI_REPO_NAME: 'angular'
CI_PULL_REQUEST: 'false'
CI_REPO_OWNER: 'angular'
CI_BUILD_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
CI_STABLE_BRANCH: ${{ steps.latest_version.outputs.CI_STABLE_BRANCH }}
CI_BRANCH: ${{ steps.latest_version.outputs.CI_BRANCH }}
CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN: ${{ secrets.AIO_DEPLOY_TOKEN }}
CI_SECRET_PAYLOAD_FIREBASE_TOKEN: ${{ secrets.AIO_PAYLOAD_TOKEN }}
run: yarn --cwd aio deploy-production
- name: Notify about failed deployment
if: ${{ failure() }}
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v=v1.24.0
with:
channel-id: 'C07DT5M6V,CKV1F72BG'
slack-message: 'Deploy to aio job failed for ${{ github.base_ref }} branch failed on build ${{ github.event.after }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
env:
SLACK_BOT_TOKEN: ${{ secrets.ANGULAR_ROBOT_SLACK_TOKEN }}
publish-snapshots:
if: github.event_name == 'push'
@@ -112,13 +182,13 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules
run: yarn install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -130,7 +200,7 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
@@ -138,9 +208,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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -148,26 +218,28 @@ 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: |
rm -Rf packages/zone.js/build
rm -Rf packages/zone.js/test/extra/*.umd.js
mkdir -p packages/zone.js/build/
mkdir -p packages/zone.js/build/test/
mkdir -p packages/zone.js/test/
cp dist/bin/packages/zone.js/bundles/zone.umd.js packages/zone.js/build/zone.umd.js
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
@@ -179,7 +251,7 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -187,94 +259,16 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
- name: Run all saucelabs bazel tests
run: |
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
saucelabs:
runs-on: ubuntu-latest-4core
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
with:
cache-node-modules: true
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- 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
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- name: Preparing Bazel-generated fixtures required in legacy tests
run: |
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
mkdir -p dist/legacy-test-out/core/test/reflection/
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
# Locale files are needed for i18n tests running within Saucelabs. These are added
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
# can succeed. Note that the base locale and currencies files are checked-in, so
# we do not need to re-generate those through Bazel.
mkdir -p packages/common/locales/extra
cp dist/bin/packages/common/locales/*.ts packages/common/locales
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
- name: Build bundle of tests to run on Saucelabs
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
- name: Wait and confirm Saucelabs tunnel has connected
run: ./tools/saucelabs/sauce-service.sh ready-wait
timeout-minutes: 3
- name: Running tests on Saucelabs.
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
- name: Stop Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh stop
adev-deploy:
needs: [adev]
if: needs.adev.result == 'success' && github.event_name == 'push' && github.ref_name == 'main'
env:
DEPLOY_PROJECT: angular-dev-site
DEPLOY_SITE: angular-dev-site
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build //adev:build
- name: Move generated project files into deployment directory
run: cp -r dist/bin/adev/build adev/build
- name: Setup firebase context
run: |
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{ env.DEPLOY_PROJECT }} hosting angular-docs
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{ env.DEPLOY_PROJECT }} hosting angular-docs ${{ env.DEPLOY_SITE }}
- name: Deploy to firebase
uses: FirebaseExtended/action-hosting-deploy@120e124148ab7016bec2374e5050f15051255ba2 # v0.7.1
with:
entryPoint: './adev'
firebaseServiceAccount: ${{ secrets.ANGULAR_DEV_SITE_DEPLOY }}
projectId: ${{ env.DEPLOY_PROJECT }}
channelId: live
+4 -4
View File
@@ -12,14 +12,14 @@ jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- uses: angular/dev-infra/github-actions/commit-message-based-labels@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/commit-message-based-labels@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- uses: angular/dev-infra/github-actions/post-approval-changes@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/post-approval-changes@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+19
View File
@@ -0,0 +1,19 @@
name: Feature request triage bot
on:
schedule:
# Run at 14:00 every day
- cron: '0 14 * * *'
# Declare default permissions as read only.
permissions:
contents: read
jobs:
feature_triage:
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/feature-request@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+2 -2
View File
@@ -13,8 +13,8 @@ jobs:
statuses: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- uses: angular/dev-infra/github-actions/google-internal-tests@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/google-internal-tests@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
- uses: angular/dev-infra/github-actions/unified-status-check@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/setup@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/bazel/configure-remote@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
- 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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
cache-node-modules: true
node-module-directories: |
+4 -4
View File
@@ -25,12 +25,12 @@ jobs:
steps:
- name: 'Checkout code'
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
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@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
with:
sarif_file: results.sarif
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
# Setting `persist-credentials: false` prevents the github-action account from being the
# account that is attempted to be used for authentication, instead the remote is set to
@@ -28,11 +28,11 @@ jobs:
# This is needed as otherwise the PR creation will fail with `shallow update not allowed` when the forked branch is not in sync.
fetch-depth: 0
- name: Generate CLI help
run: node adev/scripts/update-cli-help/index.mjs
run: node aio/scripts/update-cli-help/index.mjs
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@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
uses: angular/dev-infra/github-actions/create-pr-for-changes@1173ab9b7174e4ec6ff3a3455226ca75594edaa0
with:
branch-prefix: update-cli-help
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
+48
View File
@@ -0,0 +1,48 @@
# Update the `events.json` file that powers the [AIO events page](https://angular.io/events) (if
# necessary) and create a pull request.
#
# For more details on the overall process, see
# [aio/scripts/generate-events/README.md](../../aio/scripts/generate-events/README.md).
name: Update AIO events
on:
workflow_dispatch:
inputs: {}
schedule:
# Run every day at 15:00.
- cron: '0 15 * * *'
# Declare default permissions as read only.
permissions:
contents: read
jobs:
update_events:
name: Update `events.json` (if necessary)
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
# Setting `persist-credentials: false` prevents the github-action account from being the
# account that is attempted to be used for authentication, instead the remote is set to
# an authenticated URL.
persist-credentials: false
# This is needed as otherwise the PR creation will fail with `shallow update not allowed` when the forked branch is not in sync.
fetch-depth: 0
- 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
with:
branch-prefix: docs-update-events
pr-title: 'docs: update events'
pr-description: |
Generated `events.json` with the latest events retrieved from the Firebase DB.
pr-labels: |
action: review
area: docs
target: patch
angular-robot-token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
-4
View File
@@ -57,7 +57,3 @@ baseline.json
# Husky
.husky/_
aio/content/examples/.DS_Store
# Ignore cache created with the Angular CLI.
.angular/
+3
View File
@@ -1,3 +1,6 @@
#!/bin/sh
. "$(dirname $0)/_/husky.sh"
set +e
yarn -s ng-dev commit-message pre-commit-validate --file $1 2>/dev/null
+4 -1
View File
@@ -1,8 +1,11 @@
set +e
#!/bin/sh
. "$(dirname $0)/_/husky.sh"
set +e
yarn -s ng-dev format staged 2>/dev/null
if [ $? -ne 0 ]; then
echo "WARNING: failed to run file formatting (ng-dev format staged)"
fi
exit 0;
+3
View File
@@ -1,3 +1,6 @@
#!/bin/sh
. "$(dirname $0)/_/husky.sh"
set +e
yarn -s ng-dev commit-message restore-commit-message-draft $1 $2 2>/dev/null
+15 -8
View File
@@ -5,25 +5,32 @@ import {FormatConfig} from '@angular/ng-dev';
*/
export const format: FormatConfig = {
'prettier': {
'matchers': ['**/*.{yaml,yml}'],
},
'clang-format': {
'matchers': [
'**/*.{yaml,yml}',
'**/*.{js,ts}',
// Do not format d.ts files as they are generated
'!**/*.d.ts',
// TODO: burn down format failures and remove aio and integration exceptions.
'!aio/**',
'!integration/**',
// Both third_party and .yarn are directories containing copied code which should
// not be modified.
'!third_party/**',
'!.yarn/**',
// Do not format d.ts files as they are generated
'!**/*.d.ts',
// Do not format generated ng-dev script
'!dev-infra/ng-dev.js',
'!dev-infra/build-worker.js',
// Do not format compliance test-cases since they must match generated code
'!packages/compiler-cli/test/compliance/test_cases/**/*.js',
// Do not format the locale files which are checked-in for Google3, but generated using
// the `generate-locales-tool` from `packages/common/locales`.
'!packages/core/src/i18n/locale_en.ts',
'!packages/common/locales/closure-locale.ts',
'!packages/common/src/i18n/currencies.ts',
// Test cases contain non valid code.
'!packages/compiler-cli/test/compliance/test_cases/**/*.{js,ts}',
// Ignore AIO as its pending removal.
'!aio/**/*.{js,ts}',
// Temporarily disable formatting for adev
'!adev/**',
],
},
'buildifier': true,
+4 -8
View File
@@ -1,12 +1,5 @@
{
"syncedFilePatterns": [
"LICENSE",
"modules/benchmarks/**",
"packages/**"
],
"separateFilePatterns": [
"packages/core/primitives/**"
],
"syncedFilePatterns": ["LICENSE", "modules/benchmarks/**", "packages/**"],
"alwaysExternalFilePatterns": [
"packages/*",
"packages/bazel/*",
@@ -29,6 +22,7 @@
"packages/docs/**",
"packages/elements/schematics/**",
"packages/examples/**",
"packages/language-service/**",
"packages/localize/**",
"packages/private/**",
"packages/service-worker/**",
@@ -37,6 +31,8 @@
"packages/http/**",
"**/.gitignore",
"**/.gitkeep",
"**/yarn.lock",
"**/package.json",
"**/third_party/**",
"**/tsconfig-build.json",
"**/tsconfig-tsec.json",
-5
View File
@@ -17,9 +17,4 @@ export const pullRequest: PullRequestConfig = {
// the `bazel` package is not considered part of the public API so that features
// can land in patch branches.
targetLabelExemptScopes: ['dev-infra', 'docs-infra', 'bazel'],
// enables specific validations during the pull request merge process
validators: {
assertEnforceTested: true,
assertIsolatedSeparateFiles: true,
},
};
+1 -1
View File
@@ -1 +1 @@
18.20.0
18.13.0
-1
View File
@@ -2,7 +2,6 @@
"printWidth": 100,
"tabWidth": 2,
"tabs": false,
"embeddedLanguageFormatting": "off",
"singleQuote": true,
"semicolon": true,
"quoteProps": "preserve",
+46 -102
View File
@@ -140,7 +140,7 @@ groups:
- alxhub
- crisbeto
- ~jelbourn
- thePunderWoman
- jessicajaniuk
# =========================================================
# Framework: Compiler
@@ -166,9 +166,8 @@ groups:
- AndrewKushnir
- atscott
- crisbeto
- devversion
- dylhunn
- JoostK
- dylhunn
# =========================================================
# Framework: Migrations
@@ -183,7 +182,7 @@ groups:
- crisbeto
- devversion
- dylhunn
- thePunderWoman
- jessicajaniuk
- AndrewKushnir
# =========================================================
@@ -193,7 +192,7 @@ groups:
<<: *defaults
conditions:
- >
contains_any_globs(files.exclude("packages/core/schematics/*").exclude('packages/core/primitives/*'), [
contains_any_globs(files.exclude("packages/core/schematics/*"), [
'packages/core/**/{*,.*}',
'packages/examples/core/**/{*,.*}',
'packages/platform-browser/**/{*,.*}',
@@ -322,9 +321,7 @@ groups:
'aio/content/guide/providers.md',
'aio/content/examples/providers/**/{*,.*}',
'aio/content/images/guide/providers/**/{*,.*}',
'aio/content/guide/signal-queries.md',
'aio/content/guide/singleton-services.md',
'aio/content/guide/signal-inputs.md',
'aio/content/guide/sharing-ngmodules.md',
'aio/content/guide/standalone-components.md',
'aio/content/guide/standalone-migration.md',
@@ -347,7 +344,6 @@ groups:
'aio/content/guide/defer.md',
'aio/content/guide/signals.md',
'aio/content/guide/control_flow.md',
'aio/content/guide/model-inputs.md',
'aio/content/examples/injection-token/**/{*,.*}',
])
reviewers:
@@ -356,9 +352,8 @@ groups:
- AndrewKushnir
- atscott
- crisbeto
- devversion
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -378,7 +373,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -415,7 +410,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -438,7 +433,7 @@ groups:
- andrewseguin
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -536,7 +531,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -615,7 +610,7 @@ groups:
reviewers:
users:
- alxhub
- thePunderWoman
- jessicajaniuk
# =========================================================
# Framework: Testing
@@ -644,7 +639,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -663,7 +658,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -682,7 +677,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -778,7 +773,7 @@ groups:
- atscott
- crisbeto
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -813,7 +808,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- josephperrott
- pkozlowski-opensource
@@ -867,7 +862,7 @@ groups:
reviewers:
users:
- AndrewKushnir
- thePunderWoman
- jessicajaniuk
# =========================================================
# Docs: Marketing
@@ -939,14 +934,13 @@ groups:
'aio/content/guide/update-to-version-14.md',
'aio/content/guide/update-to-version-15.md',
'aio/content/guide/update-to-version-16.md',
'aio/content/guide/update-to-version-17.md',
])
reviewers:
users:
- alxhub
- AndrewKushnir
- atscott
- thePunderWoman
- jessicajaniuk
- jelbourn
# =========================================================
@@ -1075,18 +1069,9 @@ groups:
])
reviewers:
users:
- twerske
- josephperrott
- jelbourn
- bencodezen
- thePunderWoman
- dylhunn
- AndrewKushnir
- alxhub
- crisbeto
- devversion
- atscott
- pkozlowski-opensource
- ~JeanMeche
# =========================================================
# Docs-infra
@@ -1144,7 +1129,7 @@ groups:
reviewers:
users:
- AndrewKushnir
- thePunderWoman
- jessicajaniuk
- josephperrott
# =========================================================
@@ -1165,7 +1150,6 @@ groups:
- josephperrott
- mgechev
- twerske
- ~JeanMeche
# =========================================================
# Dev-infra
@@ -1243,7 +1227,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
reviews:
request: 3 # Request reviews from 3 people
@@ -1269,7 +1253,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
reviews:
request: 2 # Request reviews from 2 people
@@ -1295,7 +1279,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
####################################################################################
@@ -1319,69 +1303,9 @@ groups:
- andrewseguin
- dgp1130
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- josephperrott
# =========================================================
# Primitives
# =========================================================
# Angular team required reviews
primitives:
<<: *defaults
conditions:
- >
contains_any_globs(files, [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- pkozlowski-opensource # Pawel Kozlowski
- alxhub # Alex Rickabaugh
- thePunderWoman # Jessica Janiuk
- dylhunn # Dylan Hunn
- AndrewKushnir # Andrew Kushnir
- atscott # Andrew Scott
reviews:
required: 1
reviewed_for: optional
labels:
pending: 'requires: TGP'
approved: 'requires: TGP'
rejected: 'requires: TGP'
# External team required reviews
primitives-shared:
<<: *defaults
conditions:
- >
contains_any_globs(files, [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- csmick # Cameron Smick
- lannka # Hongfei Ding
- eduhmc # Eduardo Huerta-Mercado
- ehlemur # Edward Lesmes
- ellenyuan # Ellen Yuan
- jatraman # Jatin Ramanathan
- jesse-good # Jesse Costello-Good
- mturco # Matt Turco
- neonstalwart # Ben Hockey
- nicholasyu-google # Nicholas Yu
- emspishak # Eric Spishak-Thomas
- iteriani # Thomas Nguyen
- tbondwilkinson # Tom Wilkinson
- rahatarmanahmed # Rahat Ahmed
reviews:
required: 1
reviewed_for: optional
labels:
pending: 'requires: TGP'
approved: 'requires: TGP'
rejected: 'requires: TGP'
####################################################################################
# Override managed result groups
#
@@ -1458,6 +1382,26 @@ groups:
author_value: 0 # The author of the PR cannot provide an approval for themself
reviewed_for: ignored # All reviews apply to this group whether noted via Reviewed-for or not
reviewers:
teams:
# Any member of the team can provide a review to perform the minimum review required
- team
users:
- alan-agius4 # Alan Agius
- AleksanderBodurri # Aleksander Bodurri
- alxhub # Alex Rickabaugh
- AndrewKushnir # Andrew Kushnir
- andrewseguin # Andrew Seguin
- atscott # Andrew Scott
- clydin # Charles Lyding
- crisbeto # Kristiyan Kostadinov
- devversion # Paul Gschwendtner
- dgp1130 # Doug Parker
- dylhunn # Dylan Hunn
- jelbourn # Jeremy Elbourn
- jessicajaniuk # Jessica Janiuk
- JiaLiPassion # Jia Li
- JoostK # Joost Koehoorn
- josephperrott # Joey Perrott
- MarkTechson # Mark Thompson (Techson)
- mgechev # Minko Gechev
- mmalerba # Miles Malerba
- pkozlowski-opensource # Pawel Kozlowski
- twerske # Emma Twersky
- zarend # Zach Arend
+6
View File
@@ -5,6 +5,12 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"BazelBuild.vscode-bazel",
"gkalpak.aio-docs-utils",
"ms-vscode.vscode-typescript-tslint-plugin",
"xaver.clang-format",
// The following extensions are useful when working on angular.io (i.e. inside the `aio/` directory).
//"angular.ng-template",
//"dbaeumer.vscode-eslint",
//"errata-ai.vale-server",
],
}
+6
View File
@@ -1,10 +1,16 @@
{
// Format js and ts files on save with `clang-format.executable`
// If `clang-format.executable` is not being used, these two settings should be removed otherwise it will break existing formatting.
// You can instead run `yarn gulp format` to manually format your code.
"[javascript]": {
"editor.formatOnSave": true,
},
"[typescript]": {
"editor.formatOnSave": true,
},
// Please install https://marketplace.visualstudio.com/items?itemName=xaver.clang-format to take advantage of `clang-format` in VSCode.
// (See https://clang.llvm.org/docs/ClangFormat.html for more info `clang-format`.)
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
// Exclude third party modules and build artifacts from the editor watchers/searches.
"files.watcherExclude": {
"**/.git/objects/**": true,
-1
View File
@@ -13,7 +13,6 @@ exports_files([
"scripts/ci/payload-size.sh",
"scripts/ci/payload-size.js",
"package.json",
"angular.json",
])
filegroup(
+23 -980
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -207,7 +207,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
* All public API methods **must be documented**.
* We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at **100 characters**.
An automated formatter is available, see [DEVELOPER.md](docs/DEVELOPER.md#formatting-your-source-code).
An automated formatter is available, see [DEVELOPER.md](docs/DEVELOPER.md#clang-format).
## <a name="commit"></a> Commit Message Format
@@ -389,6 +389,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
[angular-group]: https://groups.google.com/forum/#!forum/angular
[coc]: https://github.com/angular/code-of-conduct/blob/main/CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
@@ -397,3 +398,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
[discord]: https://discord.gg/angular
[individual-cla]: https://cla.developers.google.com/about/google-individual
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
[jsfiddle]: https://jsfiddle.net/
[plunker]: https://plnkr.co/edit
[runnable]: https://runnable.com/
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
Copyright (c) 2010-2023 Google LLC. https://angular.io/license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+3 -7
View File
@@ -1,7 +1,7 @@
<h1 align="center">Angular - The modern web developer's platform</h1>
<p align="center">
<img src="adev/src/assets/images/press-kit/angular_icon_gradient.gif" alt="angular-logo" width="120px" height="120px"/>
<img src="aio/src/assets/images/logos/angular/angular_renaissance.png" alt="angular-logo" width="120px" height="120px"/>
<br>
<em>Angular is a development platform for building mobile and desktop web applications
<br> using TypeScript/JavaScript and other languages.</em>
@@ -61,10 +61,6 @@ Get started with Angular, learn the fundamentals and explore advanced topics on
- [Lazy Loading][lazyloading]
- [Animations][animations]
### Local Development
To contribute to the Angular Docs, check out the [Angular.dev README](adev/README.md)
## Development Setup
### Prerequisites
@@ -152,8 +148,8 @@ Join the conversation and help the community.
[angularmaterial]: https://material.angular.io/
[cli]: https://angular.dev/tools/cli
[architecture]: https://angular.dev/essentials
[componentstemplates]: https://angular.dev/tutorials/learn-angular/1-components-in-angular
[forms]: https://angular.dev/tutorials/learn-angular/15-forms
[componentstemplates]: https://angular.dev/tutorials/learn-angular/components-in-angular
[forms]: https://angular.dev/tutorials/learn-angular/forms
[api]: https://angular.dev/api
[angularelements]: https://angular.dev/guide/elements
[ssr]: https://angular.dev/guide/ssr
+44 -15
View File
@@ -2,12 +2,27 @@ workspace(
name = "angular",
managed_directories = {
"@npm": ["node_modules"],
"@aio_npm": ["aio/node_modules"],
},
)
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("//:yarn.bzl", "YARN_LABEL")
# Add a patch fix for rules_webtesting v0.3.5 required for enabling runfiles on Windows.
# TODO: Remove the http_archive for this transitive dependency when a release is cut
# for https://github.com/bazelbuild/rules_webtesting/commit/581b1557e382f93419da6a03b91a45c2ac9a9ec8
# and the version is updated in rules_nodejs.
http_archive(
name = "io_bazel_rules_webtesting",
patch_args = ["-p1"],
patches = [
"//:tools/bazel-repo-patches/rules_webtesting__windows_runfiles_fix.patch",
],
sha256 = "e9abb7658b6a129740c0b3ef6f5a2370864e102a5ba5ffca2cea565829ed825a",
urls = ["https://github.com/bazelbuild/rules_webtesting/releases/download/0.3.5/rules_webtesting.tar.gz"],
)
http_archive(
name = "build_bazel_rules_nodejs",
patches = [
@@ -49,17 +64,7 @@ load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")
nodejs_register_toolchains(
name = "nodejs",
node_repositories = {
"18.20.0-darwin_arm64": ("node-v18.20.0-darwin-arm64.tar.gz", "node-v18.20.0-darwin-arm64", "10066ad4dd9e03ea5c4c45ef8775420ff37b860de09bbdf87b97e0c07b1ea036"),
"18.20.0-darwin_amd64": ("node-v18.20.0-darwin-x64.tar.gz", "node-v18.20.0-darwin-x64", "062ba71618e88e06321de5caa038843c350aababa2d315f3ca7b8551f8e66c1c"),
"18.20.0-linux_arm64": ("node-v18.20.0-linux-arm64.tar.xz", "node-v18.20.0-linux-arm64", "afe51da9ffb38ac1e3a20d9a06efd403ced4bf8831ab554a02a088dd8392fd79"),
"18.20.0-linux_ppc64le": ("node-v18.20.0-linux-ppc64le.tar.xz", "node-v18.20.0-linux-ppc64le", "9e652bbf53a3e37285b11dfb9d6a9bb8b02010c3b50e5c8229d4cc10e72681da"),
"18.20.0-linux_s390x": ("node-v18.20.0-linux-s390x.tar.xz", "node-v18.20.0-linux-s390x", "a6c2a5796b9d9e9bf21da62ec89ff30b41a8108880b9eaa3c912f1ce795a7743"),
"18.20.0-linux_amd64": ("node-v18.20.0-linux-x64.tar.xz", "node-v18.20.0-linux-x64", "03eea148e56785babb27930b05ed6bf311aaa3bc573c0399dd63cad2fe5713c7"),
"18.20.0-windows_amd64": ("node-v18.20.0-win-x64.zip", "node-v18.20.0-win-x64", "1c0aab05cc6836a8f5148cca345b92ebc948a4a2013f18d117b7ade6ff05aca6"),
},
# We need at least Node 18.17 due to some transitive dependencies.
node_version = "18.20.0",
node_version = "18.13.0",
)
# Download npm dependencies.
@@ -75,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-56d0b1f597b9e4b5d99f289f6904a220802164c1.patch",
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-b2cec2dcba358f9dc7111800a3d65738f57abe8f.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",
@@ -94,6 +99,30 @@ yarn_install(
yarn_lock = "//:yarn.lock",
)
yarn_install(
name = "aio_npm",
# Note that we add the postinstall scripts here so that the dependencies are re-installed
# when the postinstall patches are modified.
data = [
YARN_LABEL,
"//:.yarnrc",
"//:tools/npm-patches/@bazel+jasmine+5.8.1.patch",
"//aio:tools/cli-patches/bazel-architect-output.patch",
"//aio:tools/cli-patches/patch.js",
],
# Currently disabled due to:
# 1. Missing Windows support currently.
# 2. Incompatibilites with the `ts_library` rule.
exports_directories_only = False,
manual_build_file_contents = npm_package_archives(),
package_json = "//aio:package.json",
# We prefer to symlink the `node_modules` to only maintain a single install.
# See https://github.com/angular/dev-infra/pull/446#issuecomment-1059820287 for details.
symlink_node_modules = True,
yarn = YARN_LABEL,
yarn_lock = "//aio:yarn.lock",
)
yarn_install(
name = "aio_example_deps",
# Rename the default js_library target from "node_modules" as this obscures the
@@ -167,10 +196,10 @@ cldr_xml_data_repository(
# sass rules
http_archive(
name = "io_bazel_rules_sass",
sha256 = "845061fad5cb42a353ac394fe6450ec9f21d337acf5b98011ef29ddcb33e3671",
strip_prefix = "rules_sass-9f8be59f5175d0dd3438d7da994b2782299e0c2c",
sha256 = "7a90567717c3417a23ff4b21963a698a6e9aa669ac9e682121ea33f218a2816a",
strip_prefix = "rules_sass-425ecafa8268bc013d684216363fe75bc802323e",
urls = [
"https://github.com/bazelbuild/rules_sass/archive/9f8be59f5175d0dd3438d7da994b2782299e0c2c.zip",
"https://github.com/bazelbuild/rules_sass/archive/425ecafa8268bc013d684216363fe75bc802323e.zip",
],
)
+67
View File
@@ -0,0 +1,67 @@
# 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
@@ -0,0 +1,42 @@
{
// 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
@@ -0,0 +1 @@
node_modules/
+2
View File
@@ -0,0 +1,2 @@
.yarn/releases/*.cjs
src/content/examples/**/*.*
+5
View File
@@ -0,0 +1,5 @@
# Ignoring documentation style examples
/src/content/api-examples
/src/content/examples
/src/content/tutorials
node_modules/
+130
View File
@@ -0,0 +1,130 @@
{
"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"]
}
]
}
}
]
}
-189
View File
@@ -1,189 +0,0 @@
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
load("@bazel_skylib//lib:collections.bzl", "collections")
load("//adev/tools/local_deps:index.bzl", "ensure_local_package_deps", "link_local_packages")
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_ASSETS = [
"//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",
]
APPLICATION_DEPS = [
"@npm//@angular/build",
"@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",
"@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/xterm",
"@npm//algoliasearch",
"@npm//xterm",
"@npm//xterm-addon-fit",
"@npm//angular-split",
"@npm//zone.js",
]
TEST_DEPS = APPLICATION_DEPS + [
"@npm//@angular/platform-browser-dynamic",
"@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",
]
# Create `npm_link` targets for all dependencies that correspond to a
# first-party Angular package that can be built from `HEAD`.
link_local_packages(
all_deps = collections.uniq(APPLICATION_DEPS + TEST_DEPS),
)
copy_to_bin(
name = "application_files_bin",
srcs = APPLICATION_FILES,
)
bool_flag(
name = "fast_build_mode",
build_setting_default = False,
)
config_setting(
name = "fast",
flag_values = {
":fast_build_mode": "true",
},
)
config_setting(
name = "full",
flag_values = {
":fast_build_mode": "false",
},
)
config_based_architect_flags = select({
":fast": ["--no-prerender"],
":full": ["--prerender"],
})
architect(
name = "build",
args = [
"angular-dev:build",
"--output-path=build",
] + config_based_architect_flags,
chdir = "$(RULEDIR)",
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
":application_files_bin",
],
# Network is required to inline fonts.
exec_properties = ENABLE_NETWORK,
output_dir = True,
tags = [
"no-remote-exec",
],
)
architect(
name = "serve",
args = [
"angular-dev:serve",
"--poll=1000",
"--live-reload",
"--watch",
],
chdir = package_name(),
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
":application_files_bin",
],
tags = [
"no-remote-exec",
],
)
architect_test(
name = "test",
args = [
"angular-dev:test",
"--no-watch",
],
chdir = package_name(),
data = ensure_local_package_deps(TEST_DEPS) + TEST_FILES + APPLICATION_ASSETS + [
"//aio/tools:windows-chromium-path",
"@npm//@angular/build-tooling/bazel/browsers/chromium",
],
env = {
"CHROME_BIN": "../$(CHROMIUM)",
},
toolchains = [
"@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias",
],
)
+2 -36
View File
@@ -1,37 +1,3 @@
# [Angular.dev](https://www.angular.dev)
# Angular.dev: The new home for Angular developers (https://angular.dev)
This site is built with Angular.
The content is written primarly in Markdown format located in `src/content`. For simple edits, you can directly edit the file on GitHub and generate a Pull Request.
## Local Development
For local development, [yarn](https://yarnpkg.com/) is the preferred package manager. 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
```
## Contributing
Want to report a bug, contribute some code, or improve the documentation? Excellent!
Read through our [contributing guidelines](CONTRIBUTING.md) to learn about our submission process, coding rules, and more.
And if you're new, check out one of our issues labeled as <kbd>[help wanted](https://github.com/angular/angular/labels/help%20wanted)</kbd> or <kbd>[good first issue](https://github.com/angular/angular/labels/good%20first%20issue)</kbd>.
### Code of Conduct
Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
Read more about our initial launch on the [Angular blog](https://goo.gle/angular-dot-dev).
+64 -24
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": ".",
"newProjectRoot": "projects",
"projects": {
"angular-dev": {
"projectType": "application",
@@ -12,40 +12,56 @@
"changeDetection": "OnPush"
}
},
"root": ".",
"root": "projects/angular-dev",
"sourceRoot": "src",
"prefix": "adev",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"builder": "@angular-devkit/build-angular:application",
"options": {
"externalDependencies": ["path"],
"outputPath": "dist/angular-dev",
"index": "src/index.html",
"browser": "src/main.ts",
"server": "src/main.server.ts",
"prerender": true,
"polyfills": ["src/polyfills.ts", "zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/robots.txt", "src/assets"],
"styles": ["@angular/docs/styles/global-styles.scss"],
"assets": [
"src/favicon.ico",
"src/robots.txt",
"src/assets"
],
"styles": ["projects/shared/src/lib/styles/global-styles.scss"],
"scripts": [],
"webWorkerTsConfig": "tsconfig.worker.json",
"optimization": {
"fonts": {
// TODO(josephperrott): enabled inline scripts
"inline": false
}
}
"webWorkerTsConfig": "tsconfig.worker.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "5kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
@@ -81,23 +97,47 @@
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"include": ["src/app"],
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["@angular/docs/styles/global-styles.scss"],
"styles": ["projects/shared/src/lib/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": {
"analytics": "dca119a9-da31-47f7-a6cb-b60541037021",
"cache": {
// Disable build caching as the cache folder will just be dropped
// when run under Bazel sandboxed execution.
"enabled": false
}
"analytics": "dca119a9-da31-47f7-a6cb-b60541037021"
}
}
-73
View File
@@ -1,73 +0,0 @@
{
"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"
}
]
}
}
-78
View File
@@ -1,78 +0,0 @@
// 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
@@ -0,0 +1,122 @@
{
"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": "16.2.1",
"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": "^22.1.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
@@ -0,0 +1,116 @@
/*!
* @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'}),
}));
}
+135
View File
@@ -0,0 +1,135 @@
/*!
* @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 {input, confirm} from '@inquirer/prompts';
import {existsSync} from 'fs';
import {copyFile, mkdir, writeFile} from 'fs/promises';
import {dirname, join} from 'path';
import {fileURLToPath} from 'url';
import {copyFolder} from './utils/fs.mjs';
const EXAMPLES_BASE = join(
dirname(fileURLToPath(import.meta.url)),
'../../src/content/examples',
);
const STACKBLITZ_TEMPLATE = join(
dirname(fileURLToPath(import.meta.url)),
'../../src/content/stackblitz-template',
);
const MAIN_TS_FILENAME = 'main.ts';
const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
createExample();
async function createExample(): Promise<void> {
console.info('Create example');
const name = await input({message: 'What is the example name?'});
const description = await input({message: 'Could you provide the description of the example?'});
const tags: string[] = [];
let addMoreTags = true;
while (addMoreTags) {
const tag = await input({message: 'Could you provide the tag of the example?'});
if (!!tag) {
tags.push(tag);
}
console.info();
addMoreTags = await confirm({message: 'Would you like to add another tag?', default: true});
}
const shouldCopySrcAppFolderFromTemplate = await confirm({
message: 'Would you like to copy /src/app/**.* from template?',
default: true,
});
const shouldCopyMainTsFromTemplate = await confirm({
message: 'Would you like to copy /src/main.ts from template?',
default: false,
});
const examplePath = join(EXAMPLES_BASE, name);
await createEmptyExample(examplePath, name, description, tags);
await copyFilesFromTemplate(
examplePath,
shouldCopySrcAppFolderFromTemplate,
shouldCopyMainTsFromTemplate,
);
}
/**
* Create the directory and marker files for the new example.
*/
async function createEmptyExample(
examplePath: string,
exampleName: string,
description: string,
tags: string[],
): Promise<void> {
validateExampleName(exampleName);
await ensureExamplePath(examplePath);
await writeStackBlitzFile(examplePath, description, tags);
}
function validateExampleName(exampleName: string): void {
if (/\s/.test(exampleName)) {
throw new Error(`Unable to create example. The example name contains spaces: '${exampleName}'`);
}
}
/**
* Ensure that the new example directory exists.
*/
async function ensureExamplePath(examplePath: string): Promise<void> {
if (existsSync(examplePath)) {
throw new Error(
`Unable to create example. The path to the new example already exists: ${examplePath}`,
);
}
await mkdir(examplePath);
}
/**
* Write the `stackblitz.json` file into the new example.
*/
async function writeStackBlitzFile(
examplePath: string,
description: string,
tags: string[],
): Promise<void> {
const config = {
description,
ignore: ['**/*.d.ts', '**/*.js', '**/*.[1,2].*'],
tags,
};
await writeFile(
join(examplePath, STACKBLITZ_CONFIG_FILENAME),
JSON.stringify(config, null, 2) + '\n',
);
}
async function copyFilesFromTemplate(
examplePath: string,
shouldCopySrcAppFolderFromTemplate: boolean,
shouldCopyMainTsFromTemplate: boolean,
): Promise<void> {
if (shouldCopySrcAppFolderFromTemplate) {
const appTemplateFolderPath = join(STACKBLITZ_TEMPLATE, 'src/app');
const exampleAppFolderPath = join(examplePath, 'src/app');
await copyFolder(appTemplateFolderPath, exampleAppFolderPath);
}
if (shouldCopyMainTsFromTemplate) {
const mainTsTemplatePath = join(STACKBLITZ_TEMPLATE, 'src', MAIN_TS_FILENAME);
const exampleMainTsPath = join(examplePath, 'src', MAIN_TS_FILENAME);
await copyFile(mainTsTemplatePath, exampleMainTsPath);
}
}
@@ -0,0 +1,158 @@
/*!
* @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 ts from 'typescript';
import * as fs from 'fs';
import {dirname, join, relative} from 'path';
import {fileURLToPath} from 'url';
import {readFile, writeFile} from 'fs/promises';
const TYPESCRIPT_EXTENSION = '.ts';
const SKIP_FILES_WITH_EXTENSIONS = ['.e2e-spec.ts', '.spec.ts', '.po.ts'];
const EXAMPLES_PATH = `../../content/examples`;
interface File {
path: string;
content: string;
}
interface AnalyzedFiles {
path: string;
componentNames: string[];
}
/** Absolute disk path to the project directory. */
const examplesDir = join(
dirname(fileURLToPath(import.meta.url)),
'../../src/content/examples',
);
const projectDir = join(dirname(fileURLToPath(import.meta.url)), '../../src');
const previewsComponentMapFileName = 'previews.ts';
main();
async function main() {
const files = await retrieveAllTypescriptFiles(
examplesDir,
(path) => !SKIP_FILES_WITH_EXTENSIONS.some((extensionToSkip) => path.endsWith(extensionToSkip)),
);
const filesWithComponent = files
.map((file) => ({
componentNames: analyzeFile(file),
path: file.path,
}))
.filter((result) => result.componentNames.length > 0);
const previewsComponentMap = generatePreviewsComponentMap(filesWithComponent);
fs.mkdirSync(join(projectDir, 'assets/previews'), {recursive: true});
await writeFile(
join(projectDir, 'assets/previews', previewsComponentMapFileName),
previewsComponentMap,
);
}
/** Recursively search the provided directory for all typescript files and asynchronously load them. */
function retrieveAllTypescriptFiles(
baseDir: string,
predicateFn: (path: string) => boolean,
): Promise<File[]> {
const typescriptFiles: Promise<File>[] = [];
const checkFilesInDirectory = (dir: string) => {
const files = fs.readdirSync(dir, {withFileTypes: true});
for (const file of files) {
const fullPathToFile = join(dir, file.name);
const relativeFilePath = relative(baseDir, fullPathToFile);
if (
file.isFile() &&
file.name.endsWith(TYPESCRIPT_EXTENSION) &&
predicateFn(relativeFilePath)
) {
typescriptFiles.push(
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
return {
path: relativeFilePath,
content: fileContent,
};
}),
);
} else if (file.isDirectory()) {
checkFilesInDirectory(fullPathToFile);
}
}
};
checkFilesInDirectory(baseDir);
return Promise.all(typescriptFiles);
}
/** Returns list of the `Standalone` @Component class names for given file */
function analyzeFile(file: File): string[] {
const componentClassNames: string[] = [];
const sourceFile = ts.createSourceFile(file.path, file.content, ts.ScriptTarget.Latest, false);
const visitNode = (node: ts.Node): void => {
if (ts.isClassDeclaration(node)) {
const decorators = ts.getDecorators(node);
const componentName = node.name ? node.name.text : null;
if (decorators && decorators.length) {
for (const decorator of decorators) {
const call = decorator.expression;
if (
ts.isCallExpression(call) &&
ts.isIdentifier(call.expression) &&
call.expression.text === 'Component' &&
call.arguments.length > 0 &&
ts.isObjectLiteralExpression(call.arguments[0])
) {
const hasStandaloneProperty = call.arguments[0].properties.some(
(property) =>
property.name &&
ts.isIdentifier(property.name) &&
property.name.text === 'standalone',
);
if (hasStandaloneProperty && componentName) {
componentClassNames.push(componentName);
}
}
}
}
}
ts.forEachChild(node, visitNode);
};
visitNode(sourceFile);
return componentClassNames;
}
function generatePreviewsComponentMap(data: AnalyzedFiles[]): string {
let result = '';
for (const fileData of data) {
for (const componentName of fileData.componentNames) {
const key = `${fileData.path}${
fileData.componentNames.length > 1 ? '_' + componentName : ''
}`.replace(/\\/g, '/');
result += `['${key}']: () => import('${EXAMPLES_PATH}/${fileData.path
.replace(/\\/g, '/')
.replace('.ts', '')}').then(c => c.${componentName}),\n`;
}
}
return fs
.readFileSync('scripts/examples/previews-components.template', 'utf8')
.replace(/\${previewsComponents}/g, result);
}
@@ -0,0 +1,23 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
******************************************************************************
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
******************************************************************************
*/
import {Type} from '@angular/core';
/**
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
* Keys has to be equal to paths written down in the docs markdown files.
*/
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
${previewsComponents}
};
@@ -0,0 +1,221 @@
/*!
* @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 {readFile, writeFile} from 'fs/promises';
import {
ASSETS_EXAMPLE_PATH,
CSS_TS_COPYRIGHT,
EXAMPLES_PATH,
HTML_COPYRIGHT,
STACKBLITZ_TEMPLATE_PATH,
STACKBLITZ_CONFIG_FILENAME,
TEMPORARY_EXAMPLES_PATH,
EXCLUDE_FILES_FOR_STACKBLITZ,
} from './utils/examples-constants.mjs';
import {copyFolder, createFolder, removeFolder} from './utils/fs.mjs';
import jsdom from 'jsdom';
import {glob} from 'glob';
import {regionParser} from './../../prerender/markdown-pipeline/regions/region-parser.mjs';
interface StackblitzConfig {
ignore: string[];
file: string;
tags: string[];
description: string;
}
export async function generateStackblitzExample(
exampleFolderName: string,
primaryFilePath: string,
title: string,
) {
const exampleDir = join(EXAMPLES_PATH, exampleFolderName);
const temporaryExampleDir = join(TEMPORARY_EXAMPLES_PATH, exampleFolderName);
const config = await readFile(join(exampleDir, STACKBLITZ_CONFIG_FILENAME), 'utf-8');
const stackblitzConfig: StackblitzConfig = JSON.parse(config);
primaryFilePath = join(...primaryFilePath.split('/'));
await createFolder(temporaryExampleDir);
await combineTemplateWithExample(exampleDir, temporaryExampleDir);
await generateStackblitzHtml(
temporaryExampleDir,
stackblitzConfig,
exampleFolderName,
primaryFilePath,
title,
);
await removeFolder(temporaryExampleDir);
}
async function combineTemplateWithExample(
exampleDir: string,
temporaryExampleDir: string,
): Promise<void> {
// Copy template files to TEMP folder
await copyFolder(STACKBLITZ_TEMPLATE_PATH, temporaryExampleDir);
// Copy example files to TEMP folder
await copyFolder(exampleDir, temporaryExampleDir);
}
async function generateStackblitzHtml(
temporaryExampleDir: string,
stackBlitzConfig: StackblitzConfig,
exampleFolderName: string,
primaryFilePath: string,
title: string,
): Promise<void> {
const defaultIncludes = [
'**/*.ts',
'**/*.js',
'**/*.css',
'**/*.html',
'**/*.md',
'**/*.json',
'**/*.svg',
];
const exampleFilePaths = await glob(defaultIncludes, {
cwd: temporaryExampleDir,
nodir: true,
dot: true,
ignore: stackBlitzConfig.ignore,
});
const postData = await createPostData(
temporaryExampleDir,
stackBlitzConfig,
exampleFilePaths,
title,
);
const primaryFile = getPrimaryFile(primaryFilePath ?? stackBlitzConfig.file, exampleFilePaths);
const html = createStackblitzHtml(postData, primaryFile);
const stackblitzHtmlPath = join(
join(ASSETS_EXAMPLE_PATH, exampleFolderName),
`${primaryFile}.html`,
);
await createFolder(dirname(stackblitzHtmlPath));
await writeFile(stackblitzHtmlPath, html, 'utf-8');
}
function getPrimaryFile(primaryFilePath: string, exampleFilePaths: string[]): string {
if (primaryFilePath) {
if (!exampleFilePaths.some((filePath) => filePath === primaryFilePath)) {
throw new Error(`The specified primary file (${primaryFilePath}) does not exist!`);
}
return primaryFilePath;
} else {
const defaultPrimaryFilePaths = [
'src/app/app.component.html',
'src/app/app.component.ts',
'src/app/main.ts',
];
const primaryFile = defaultPrimaryFilePaths.find((path) =>
exampleFilePaths.some((filePath) => filePath === path),
);
if (!primaryFile) {
throw new Error(
`None of the default primary files (${defaultPrimaryFilePaths.join(', ')}) exists.`,
);
}
return primaryFile;
}
}
async function createPostData(
exampleDir: string,
config: StackblitzConfig,
exampleFilePaths: string[],
title: string,
): Promise<Record<string, string>> {
const postData: Record<string, string> = {};
for (const filePath of exampleFilePaths) {
if (EXCLUDE_FILES_FOR_STACKBLITZ.some((excludedFile) => filePath.endsWith(excludedFile))) {
continue;
}
let content = await readFile(join(exampleDir, filePath), 'utf-8');
content = appendCopyright(filePath, content);
content = extractRegions(filePath, content);
postData[`project[files][${filePath}]`] = content;
}
const tags = ['angular', 'example', ...(config.tags || [])];
tags.forEach((tag, index) => (postData[`project[tags][${index}]`] = tag));
postData['project[description]'] = `Angular Example - ${config.description}`;
postData['project[template]'] = 'node';
postData['project[title]'] = title ?? 'Angular Example';
return postData;
}
function createStackblitzHtml(postData: Record<string, string>, primaryFile: string): string {
const baseHtml = createBaseStackblitzHtml(primaryFile);
const doc = new jsdom.JSDOM(baseHtml).window.document;
const form = doc.querySelector('form');
for (const [key, value] of Object.entries(postData)) {
const element = htmlToElement(doc, `<input type="hidden" name="${key}">`);
if (element && form) {
element.setAttribute('value', value as string);
form.appendChild(element);
}
}
return doc.documentElement.outerHTML;
}
function createBaseStackblitzHtml(primaryFile: string) {
const file = `?file=${primaryFile}`;
const action = `https://stackblitz.com/run${file}`;
return `
<!DOCTYPE html><html lang="en"><body>
<form id="mainForm" method="post" action="${action}" target="_self"></form>
<script>
var embedded = 'ctl=1';
var isEmbedded = window.location.search.indexOf(embedded) > -1;
if (isEmbedded) {
var form = document.getElementById('mainForm');
var action = form.action;
var actionHasParams = action.indexOf('?') > -1;
var symbol = actionHasParams ? '&' : '?'
form.action = form.action + symbol + embedded;
}
document.getElementById("mainForm").submit();
</script>
</body></html>
`.trim();
}
function appendCopyright(filename: string, content: string): string {
if (filename.endsWith('.html')) {
return `${HTML_COPYRIGHT}${content}`;
} else if (filename.endsWith('.ts') || filename.endsWith('.css')) {
return `${CSS_TS_COPYRIGHT}${content}`;
}
return content;
}
function htmlToElement(document: Document, html: string) {
const div = document.createElement('div');
div.innerHTML = html;
return div.firstElementChild;
}
function extractRegions(path: string, contents: string): string {
const regionParserResult = regionParser(contents, path);
return regionParserResult.contents;
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"ts-node": {
"compilerOptions": {
"module": "es2020"
}
}
}
@@ -0,0 +1,55 @@
/*!
* @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';
const BASE = dirname(fileURLToPath(import.meta.url));
// Directory paths
export const ASSETS_EXAMPLE_PATH = join(
BASE,
'../../../src/assets/content/examples',
);
export const EXAMPLES_PATH = join(BASE, '../../../src/content/examples');
export const STACKBLITZ_TEMPLATE_PATH = join(
BASE,
'../../../src/content/stackblitz-template',
);
export const TEMPORARY_EXAMPLES_PATH = join(fileURLToPath(import.meta.url), '../../../../TEMP');
// Extensions
export const TEST_FILES_EXTENSION_SUFFIX = '.spec.ts';
export const TEST_FILES_E2E_EXTENSION_SUFFIX = '.e2e-spec.ts';
// Filename
// TODO: BUILD.bazel and example-config will be deprecated when migration to standalone examples will be done.
export const BUILD_BAZEL_FILENAME = 'BUILD.bazel';
export const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
export const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
// Copyright
const PAD = '\n\n';
const COPYRIGHT =
'@license\n' +
'Copyright Google LLC All Rights Reserved.\n' +
'\n' +
'Use of this source code is governed by an MIT-style license that can be\n' +
'found in the LICENSE file at https://angular.dev/license\n';
export const CSS_TS_COPYRIGHT = `/*\n${COPYRIGHT}\n*/${PAD}`;
export const HTML_COPYRIGHT = `<!-- \n${COPYRIGHT}\n-->${PAD}`;
// Exclude
export const EXCLUDE_FILES_FOR_STACKBLITZ = [
STACKBLITZ_CONFIG_FILENAME,
BUILD_BAZEL_FILENAME,
EXAMPLE_CONFIG_FILENAME,
TEST_FILES_EXTENSION_SUFFIX,
TEST_FILES_E2E_EXTENSION_SUFFIX,
];
+77
View File
@@ -0,0 +1,77 @@
/*!
* @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 {Dirent, existsSync, readdirSync} from 'fs';
import {copyFile, mkdir, readFile, readdir, rm, stat} from 'fs/promises';
import {join, relative} from 'path';
export async function copyFolder(source: string, destination: string) {
if (!existsSync(destination)) {
await mkdir(destination, {recursive: true});
}
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);
const isDirectory = await stats.isDirectory();
if (isDirectory) {
await copyFolder(sourcePath, destPath);
} else {
await copyFile(sourcePath, destPath);
}
}
}
/** Recursively search the provided directory for all files which satisfy predicate and asynchronously load them. */
export function retrieveFiles(
baseDir: string,
predicate?: (file: Dirent) => boolean,
): Promise<{path: string; content: string}[]> {
const filesWithContent: Promise<{path: string; content: string}>[] = [];
const checkFilesInDirectory = (dir: string) => {
const files = readdirSync(dir, {withFileTypes: true});
for (const file of files) {
const fullPathToFile = join(dir, file.name);
if (file.isFile() && (predicate ? predicate(file) : true)) {
filesWithContent.push(
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
return {
path: relative(baseDir, fullPathToFile),
content: fileContent,
};
}),
);
} else if (file.isDirectory()) {
checkFilesInDirectory(fullPathToFile);
}
}
};
checkFilesInDirectory(baseDir);
return Promise.all(filesWithContent);
}
export async function createFolder(path: string): Promise<void> {
if (!existsSync(path)) {
await mkdir(path, {recursive: true});
}
}
export async function removeFolder(path: string): Promise<void> {
if (existsSync(path)) {
await rm(path, {recursive: true});
}
}
@@ -0,0 +1,35 @@
/*!
* @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';
@@ -0,0 +1,146 @@
/*!
* @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');
}
@@ -0,0 +1,22 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
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
@@ -0,0 +1,189 @@
/*!
* @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);
}
}
@@ -0,0 +1,3 @@
# 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>.
@@ -0,0 +1,13 @@
/*!
* @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} */`;
@@ -0,0 +1,14 @@
/*!
* @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} -->`;
@@ -0,0 +1,13 @@
/*!
* @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}`;
@@ -0,0 +1,13 @@
/*!
* @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} */`;
@@ -0,0 +1,13 @@
/*!
* @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}`;
@@ -0,0 +1,196 @@
/*!
* @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);
}
@@ -0,0 +1,58 @@
/*!
* @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,
};
@@ -0,0 +1,61 @@
/*!
* @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);
},
};
@@ -0,0 +1,58 @@
/*!
* @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();
};
@@ -0,0 +1,120 @@
/*!
* @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);
}
@@ -0,0 +1,66 @@
/*!
* @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>
`;
},
};
@@ -0,0 +1,70 @@
/*!
* @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>
`;
},
};
@@ -0,0 +1,169 @@
/*!
* @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'});
}
@@ -0,0 +1,445 @@
/*!
* @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;
}
}
@@ -0,0 +1,84 @@
/*!
* @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'});
}
@@ -0,0 +1,103 @@
/*!
* @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>
`;
},
};
@@ -0,0 +1,69 @@
/*!
* @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>
`;
},
};
@@ -0,0 +1,99 @@
/*!
* @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>
`;
},
};
@@ -0,0 +1,19 @@
/*!
* @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;
}
@@ -0,0 +1,26 @@
/*!
* @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',
}
@@ -0,0 +1,39 @@
/*!
* @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>`;
}
@@ -0,0 +1,26 @@
/*!
* @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}>`;
}
@@ -0,0 +1,13 @@
/*!
* @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"` : '';
@@ -0,0 +1,17 @@
/*!
* @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">
`;
}
@@ -0,0 +1,15 @@
/*!
* @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>`;
}
@@ -0,0 +1,22 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export function transformDocsList(body: string, ordered: boolean) {
if (ordered) {
return `
<ol class="docs-ordered-list">
${body}
</ol>
`;
}
return `
<ul class="docs-list">
${body}
</ul>
`;
}
@@ -0,0 +1,22 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
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>
`;
}
@@ -0,0 +1,31 @@
/*!
* @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
@@ -0,0 +1,19 @@
# 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
```
@@ -0,0 +1,84 @@
/*!
* @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),
);
}
@@ -0,0 +1,204 @@
/*!
* @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;
}
@@ -0,0 +1,56 @@
/*!
* @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
@@ -0,0 +1,203 @@
/*!
* @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();
}
@@ -0,0 +1,62 @@
/*!
* @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)),
),
);
}
@@ -0,0 +1,146 @@
/*!
* @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, '');
}

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