Compare commits

..

187 Commits

Author SHA1 Message Date
7c7d432085 release: cut the v9.0.0-rc.3 release 2019-11-20 14:18:59 -08:00
213e3c3e49 fix(ivy): move setClassMetadata calls into a pure iife (#33337)
This commit transforms the setClassMetadata calls generated by ngtsc from:

```typescript
/*@__PURE__*/ setClassMetadata(...);
```

to:

```typescript
/*@__PURE__*/ (function() {
  setClassMetadata(...);
})();
```

Without the IIFE, terser won't remove these function calls because the
function calls have arguments that themselves are function calls or other
impure expressions. In order to make the whole block be DCE-ed by terser,
we wrap it into IIFE and mark the IIFE as pure.

It should be noted that this change doesn't have any impact on CLI* with
build-optimizer, which removes the whole setClassMetadata block within
the webpack loader, so terser or webpack itself don't get to see it at
all. This is done to prevent cross-chunk retention issues caused by
webpack's internal module registry.

* actually we do expect a short-term size regression while
https://github.com/angular/angular-cli/pull/16228
is merged and released in the next rc of the CLI. But long term this
change does nothing to CLI + build-optimizer configuration and is done
primarly to correct the seemingly correct but non-function PURE annotation
that builds not using build-optimizer could rely on.

PR Close #33337
2019-11-20 12:55:58 -08:00
5267c92a93 fix(ivy): retain JIT metadata unless JIT mode is explicitly disabled (#33671)
NgModules in Ivy have a definition which contains various different bits
of metadata about the module. In particular, this metadata falls into two
categories:

* metadata required to use the module at runtime (for bootstrapping, etc)
in AOT-only applications.
* metadata required to depend on the module from a JIT-compiled app.

The latter metadata consists of the module's declarations, imports, and
exports. To support JIT usage, this metadata must be included in the
generated code, especially if that code is shipped to NPM. However, because
this metadata preserves the entire NgModule graph (references to all
directives and components in the app), it needs to be removed during
optimization for AOT-only builds.

Previously, this was done with a clever design:

1. The extra metadata was added by a function called `setNgModuleScope`.
A call to this function was generated after each NgModule.
2. This function call was marked as "pure" with a comment and used
`noSideEffects` internally, which causes optimizers to remove it.

The effect was that in dev mode or test mode (which use JIT), no optimizer
runs and the full NgModule metadata was available at runtime. But in
production (presumably AOT) builds, the optimizer runs and removes the JIT-
specific metadata.

However, there are cases where apps that want to use JIT in production, and
still make an optimized build. In this case, the JIT-specific metadata would
be erroneously removed. This commit solves that problem by adding an
`ngJitMode` global variable which guards all `setNgModuleScope` calls. An
optimizer can be configured to statically define this global to be `false`
for AOT-only builds, causing the extra metadata to be stripped.

A configuration for Terser used by the CLI is provided in `tooling.ts` which
sets `ngJitMode` to `false` when building AOT apps.

PR Close #33671
2019-11-20 12:55:43 -08:00
595375f7a3 fix(ivy): don't infer template context types when in full mode (#33537)
The Ivy template type-checker is capable of inferring the type of a
structural directive (such as NgForOf<T>). Previously, this was done with
fullTemplateTypeCheck: true, even if strictTemplates was false. View Engine
previously did not do this inference, and so this causes breakages if the
type of the template context is not what the user expected.

In particular, consider the template:

```html
<div *ngFor="let user of users as all">
  {{user.index}} out of {{all.length}}
</div>
```

As long as `users` is an array, this seems reasonable, because it appears
that `all` is an alias for the `users` array. However, this is misleading.

In reality, `NgForOf` is rendered with a template context that contains
both a `$implicit` value (for the loop variable `user`) as well as a
`ngForOf` value, which is the actual value assigned to `all`. The type of
`NgForOf`'s template context is `NgForContext<T>`, which declares `ngForOf`'s
type to be `NgIterable<T>`, which does not have a `length` property (due to
its incorporation of the `Iterable` type).

This commit stops the template type-checker from inferring template context
types unless strictTemplates is set (and strictInputTypes is not disabled).

Fixes #33527.

PR Close #33537
2019-11-20 11:47:42 -08:00
d92da1335d fix(ivy): report watch mode diagnostics correctly (#33862)
This commit changes the reporting of watch mode diagnostics for ngtsc to use
the same formatting as non-watch mode diagnostics. This prints rich and
contextual errors even in watch mode, which previously was not the case.

Fixes #32213

PR Close #33862
2019-11-20 11:46:02 -08:00
30ddadc4d7 fix(ivy): always re-analyze the program during incremental rebuilds (#33862)
Previously, the ngtsc compiler attempted to reuse analysis work from the
previous program during an incremental build. To do this, it had to prove
that the work was safe to reuse - that no changes made to the new program
would invalidate the previous analysis.

The implementation of this had a significant design flaw: if the previous
program had errors, the previous analysis would be missing significant
information, and the dependency graph extracted from it would not be
sufficient to determine which files should be re-analyzed to fill in the
gaps. This often meant that the build output after an error was resolved
would be wholly incorrect.

This commit switches ngtsc to take a simpler approach to incremental
rebuilds. Instead of attempting to reuse prior analysis work, the entire
program is re-analyzed with each compilation. This is actually not as
expensive as one might imagine - analysis is a fairly small part of overall
compilation time.

Based on the dependency graph extracted during this analysis, the compiler
then can make accurate decisions on whether to emit specific files. A new
suite of tests is added to validate behavior in the presence of source code
level errors.

This new approach is dramatically simpler than the previous algorithm, and
should always produce correct results for a semantically correct program.s

Fixes #32388
Fixes #32214

PR Close #33862
2019-11-20 11:46:02 -08:00
dc8e300029 test(ivy): driveDiagnostics() works incrementally (#33862)
PR Close #33862
2019-11-20 11:46:02 -08:00
a7e2327212 test(docs-infra): more thoroughly clean up after ScrollService tests (#33937)
By clearing `sessionStorage` and unsubscribing from `Location` events,
after each test, we reduce the possibility of potential
[spooky action at a distance][1]-type of failures in the future.

This does not have an impact on the actual app, since `ScrollService` is
currently expected to live throughout the lifetime of the app. Still,
unsubscribing from `Location` events keeps the code consistent with how
other `ScrollService` listeners are handled (e.g. for `window` events).

[1]: https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)

PR Close #33937
2019-11-20 11:04:12 -08:00
a7d6829cde test(docs-infra): destroy all ScrollService instances after each test (#33937)
`ScrollService` subscribes to global `window` events and mutates global
state in the listener (e.g. read/write values from/to `sessionStorage`).
Therefore, we need to always call its `ngOnDestroy()` method to
unsubscribe from these events after each test.

In f69c6e204, a new testcase was introduced that was not destroyed. As a
result, random failures started to randomly happen in other, unrelated
tests ([example CI failure][1]).

This commit fixes this by ensuring all `ScrollService` instances are
destroyed after each tests (provided that they are created with the
`createScrollService()` helper).

[1]: https://circleci.com/gh/angular/angular/533298

PR Close #33937
2019-11-20 11:04:12 -08:00
bc97c25efe build: add ngJitMode to ng_rollup_bundle terser config (#33865)
This adds a `tools/ng_rollup_bundle/terser_config.json` file to override the default terser_minified config provided by the rule. After this change, the layer violation in rules_nodejs can be fixed by removing `"global_defs": {"ngDevMode": false, "ngI18nClosureMode": false},` from `terser_config.default.json` in rules_nodejs.

Change requested by Alex Rickabaugh in https://github.com/bazelbuild/rules_nodejs/pull/1338.

PR Close #33865
2019-11-20 10:29:46 -08:00
49571bf11a fix(core): make QueryList implement Iterable in the type system (#33536)
Originally, QueryList implemented Iterable and provided a Symbol.iterator
on its prototype. This caused issues with tree-shaking, so QueryList was
refactored and the Symbol.iterator added in its constructor instead. As
part of this change, QueryList no longer implemented Iterable directly.

Unfortunately, this meant that QueryList was no longer assignable to
Iterable or, consequently, NgIterable. NgIterable is used for NgFor's input,
so this meant that QueryList was not usable (in a type sense) for NgFor
iteration. View Engine's template type checking would not catch this, but
Ivy's did.

As a fix, this commit adds the declaration (but not the implementation) of
the Symbol.iterator function back to QueryList. This has no runtime effect,
so it doesn't affect tree-shaking of QueryList, but it ensures that
QueryList is assignable to NgIterable and thus usable with NgFor.

Fixes #29842

PR Close #33536
2019-11-19 13:43:53 -08:00
14156bd262 fix(ivy): emit fs-relative paths when rootDir(s) aren't in effect (#33828)
Previously, the compiler assumed that all TS files logically within a
project existed under one or more "root directories". If the TS compiler
option `rootDir` or `rootDirs` was set, they would dictate the root
directories in use, otherwise the current directory was used.

Unfortunately this assumption was unfounded - it's common for projects
without explicit `rootDirs` to import from files outside the current
working directory. In such cases the `LogicalProjectStrategy` would attempt
to generate imports into those files, and fail. This would lead to no
`ReferenceEmitStrategy` being able to generate an import, and end in a
compiler assertion failure.

This commit introduces a new strategy to use when there are no `rootDirs`
explicitly present, the `RelativePathStrategy`. It uses simpler, filesystem-
relative paths to generate imports, even to files above the current working
directory.

Fixes #33659
Fixes #33562

PR Close #33828
2019-11-19 12:41:24 -08:00
e42fa81aa9 test(ivy): support chdir() on the compiler's filesystem abstraction (#33828)
This commit adds the ability to change directories using the compiler's
internal filesystem abstraction. This is a prerequisite for writing tests
which are sensitive to the current working directory.

In addition to supporting the `chdir()` operation, this commit also fixes
`getDefaultLibLocation()` for mock filesystems to not assume `node_modules`
is in the current directory, but to resolve it similarly to how Node does
by progressively looking higher in the directory tree.

PR Close #33828
2019-11-19 12:41:24 -08:00
c7a3694eb9 fix(ivy): properly insert views before ng-container with injected ViewContainerRef (#33853)
PR Close #33853
2019-11-19 11:56:43 -08:00
839ac3c0a7 refactor(ivy): stricter TNode.inputs typing (#33798)
TNode.inputs are initialised during directives resolution now so we know early
if a node has directives with inputs or no. We don't need to use undefined value
as an indicator that inputs were not resolved yet.

PR Close #33798
2019-11-19 11:56:00 -08:00
105616cd2b perf(ivy): don't store public input names in two places (#33798)
Before this change a public name of a directive's input
was stored in 2 places:
- as a key of an object on TNode.index;
- as a value of PropertyAliasValue at the index 1

This PR changes the data structure so the public name is stored
only once as a key on TNode.index. This saves one array entry
for each and every directive input.

PR Close #33798
2019-11-19 11:56:00 -08:00
edd624b254 perf(ivy): add micro-benchmark focused on directive input update (#33798)
PR Close #33798
2019-11-19 11:56:00 -08:00
56801c74b9 fix(docs-infra): do not break when cookies are disabled in the browser (#33829)
Whenever cookies are disabled in the browser, `window.sessionStorage` is not avaialable to the app (i.e. even trying to access `window.sessionStorage` throws an error).

To avoid breaking the app, we use a no-op `Storage` implementation if `window.sessionStorage` is not available.

Fixes #33795

PR Close #33829
2019-11-19 11:42:11 -08:00
01af94ccd7 perf(ivy): extract template's instruction first create pass processing (#33856)
This refactorings clearly separates the first and subsequent creation execution
of the `template` instruction. This approach has the following benefits:
- it is clear what happens during the first vs. subsequent executions;
- we can avoid several memory reads and checks after the first creation pass
(there is measurable performance improvement on various benchmarks);
- the template instructions becomes smaller and should become a candidate
for optimisations / inlining faster;

PR Close #33856
2019-11-19 11:41:49 -08:00
456ea5cce4 refactor(ivy): limit reads from TView.consts (#33856)
PR Close #33856
2019-11-19 11:41:49 -08:00
1e78fafd37 docs: correct lifecycle hooks feature example (#33886)
PR Close #33886
2019-11-19 11:41:00 -08:00
83140f7926 build: ensure package-builder finds bazel (#33904)
Since `scripts/package-builder.js` gets run from the
`aio` folder sometimes, it is important to set the cwd
for the command that finds the bazel bin directory.

PR Close #33904
2019-11-19 11:40:19 -08:00
b08bb411b9 docs: add version requirement for routing option (#33909)
PR Close #33909
2019-11-19 11:40:03 -08:00
1b4d264ccf build: remove rules_nodejs legacy rollup_bundle patch (#33915)
We no longer depend on the legacy rollup_bundle rule so we don't require this patch

PR Close #33915
2019-11-19 11:39:49 -08:00
05d5c4fc76 fix(ngcc): properly detect origin of constructor param types (#33901)
The ReflectionHost supports enumeration of constructor parameters, and one
piece of information it returns describes the origin of the parameter's
type. Parameter types come in two flavors: local (the type is not imported
from anywhere) or non-local (the type comes via an import).

ngcc incorrectly classified all type parameters as 'local', because in the
source files that ngcc processes the type parameter is a real ts.Identifer.
However, that identifier may still have come from an import and thus might
be non-local.

This commit changes ngcc's ReflectionHost(s) to properly recognize and
report these non-local type references.

Fixes #33677

PR Close #33901
2019-11-19 11:38:33 -08:00
c126a6e3fc build: remove rollup-rule temp settings (#33908)
PR Close #33908
2019-11-18 16:02:25 -08:00
97ff04a7bd build: force RBE runs to use linux remote executors (#33902)
PR Close #33902
2019-11-18 16:02:11 -08:00
b744505620 refactor(ivy): separate first creation pass in the elementContainerStart instruction (#33894)
PR Close #33894
2019-11-18 16:01:55 -08:00
b707c48817 refactor(ivy): separate first creation pass in the text instruction (#33894)
PR Close #33894
2019-11-18 16:01:55 -08:00
2d7b015ed7 fix(bazel): add terser as an optional peer dependency (#33891)
`ng_package` rule has an implicitly optional depedency on terser a48573efe8/packages/bazel/src/ng_package/ng_package.bzl (L36)

When using this rule without terser being available we get the below error;
```
ERROR: /home/circleci/ng/modules/express-engine/BUILD.bazel:22:1: every rule of type ng_package implicitly depends upon the target '@npm//terser/bin:terser', but this target could not be found because of: no such package '@npm//terser/bin': BUILD file not found in directory 'terser/bin' of external repository @npm. Add a BUILD file to a directory to mark it as a package.
ERROR: Analysis of target '//modules/express-engine:npm_package' failed; build aborted: no such package '@npm//terser/bin': BUILD file not found in directory 'terser/bin' of external repository @npm. Add a BUILD file to a directory to mark it as a package.
```

PR Close #33891
2019-11-18 16:01:33 -08:00
b53a1ac999 fix(ivy): i18n - support "\", "`" and "${" sequences in i18n messages (#33820)
Since i18n messages are mapped to `$localize` tagged template strings,
the "raw" version must be properly escaped. Otherwise TS will throw an
error such as:

```
Error: Debug Failure. False expression: Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'.
```

This commit ensures that we properly escape these raw strings before creating
TS AST nodes from them.

PR Close #33820
2019-11-18 16:00:23 -08:00
bc28ca7b0c fix(ivy): i18n - ensure that colons in i18n metadata are not rendered (#33820)
The `:` char is used as a metadata marker in `$localize` messages.
If this char appears in the metadata it must be escaped, as `\:`.
Previously, although the `:` char was being escaped, the TS AST
being generated was not correct and so it was being output double
escaped, which meant that it appeared in the rendered message.

As of TS 3.6.2 the "raw" string can be specified when creating tagged
template AST nodes, so it is possible to correct this.

PR Close #33820
2019-11-18 16:00:23 -08:00
b7c1a5e45f build: make ivy-i18n/debug-test.sh executable (#33820)
PR Close #33820
2019-11-18 16:00:22 -08:00
f136ddad0d fix(ivy): properly insert views into ViewContainerRef injected by querying <ng-container> (#33816)
When asking for a ViewContainerRef on <ng-container> we do reuse <ng-container> comment
node as a LContainer's anachor. Before this fix the act of re-using a <ng-container>'s
comment node would result in this comment node being re-appended to the DOM in the wrong
place. With the fix in this PR we make sure that re-using <ng-container>'s comment node
doesn't result in unwanted DOM manipulation (ng-gontainer's comment node is already part
of the DOM and doesn't have to be re-created / re-appended).

PR Close #33816
2019-11-18 16:00:01 -08:00
5be23a309e fix(ivy): shadow all DOM properties in DebugElement.properties (#33781)
Fixes #33695

PR Close #33781
2019-11-18 15:49:22 -08:00
6d06d9d3c6 refactor(compiler): add details while throw error during expression convert (#32760)
Fixes #32759

PR Close #32760
2019-11-18 15:47:59 -08:00
b7c012f91b feat(core): missing-injectable migration should migrate empty object literal providers (#33709)
In View Engine, providers which neither used `useValue`, `useClass`,
`useFactory` or `useExisting`, were interpreted differently.

e.g.

```
{provide: X} -> {provide: X, useValue: undefined}, // this is how it works in View Engine
{provide: X} -> {provide: X, useClass: X}, // this is how it works in Ivy
```

The missing-injectable migration should migrate such providers to the
explicit `useValue` provider. This ensures that there is no unexpected
behavioral change when updating to v9.

PR Close #33709
2019-11-18 15:47:20 -08:00
1b7aa05699 fix: fixes typo of zone.js patch vrdisplaydisconnected property (#33581)
Close #33579

PR Close #33581
2019-11-18 15:46:52 -08:00
1218ce4f5f docs: add xrefs to update app and deprecations policy (#33767)
PR Close #33767
2019-11-18 09:13:16 -08:00
7ffbe54ef0 docs: add html template for sizer component in template syntax guide (#33776)
fixes #33771

PR Close #33776
2019-11-18 09:12:46 -08:00
2f8c9e1990 build: update to fsevents@2.1.2 (#33866)
the previous version caused native compilation errors during postinstall on mac.

PR Close #33866
2019-11-18 09:12:30 -08:00
bde55ecc91 build: update to @bazel/buildifier@0.29.0 (#33866)
the previously used version was incompatible with the bazel vscode extension.

PR Close #33866
2019-11-18 09:12:30 -08:00
aeaf530de6 build: fix for ng_rollup_bundle outside of a sandbox (#33867)
Fixes issue introduced in https://github.com/angular/angular/pull/33808 for ng_rollup_bundle with `-spawn_strategy=standalone`. Without the sandbox (if --spawn_strategy=standalone is set) rollup can resolve to the non-esm .js file generated by ts_library instead of the desired .mjs. This fixes the problem by prioritizing .mjs.

Issue observed is of the flavor:

```
ERROR: modules/benchmarks/src/views/BUILD.bazel:20:1: Bundling JavaScript modules/benchmarks/src/views/bundle.es2015.js
[rollup] failed (Exit 1)
[!] Error: 'enableProdMode' is not exported by bazel-out/darwin-fastbuild/bin/packages/core/index.js
https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module
bazel-out/darwin-fastbuild/bin/modules/benchmarks/src/views/index.mjs (12:9)
10:  * found in the LICENSE file at https://angular.io/license
11:  */
12: import { enableProdMode } from '@angular/core';
             ^
13: import { platformBrowser } from '@angular/platform-browser';
14: import { ViewsBenchmarkModuleNgFactory } from './views-benchmark.ngfactory';
Error: 'enableProdMode' is not exported by bazel-out/darwin-fastbuild/bin/packages/core/index.js
```

PR Close #33867
2019-11-18 09:12:18 -08:00
66ecab6e8c build(docs-infra): upgrade cli command docs sources to fbfcc45a3 (#33870)
Updating [angular#9.0.x](https://github.com/angular/angular/tree/9.0.x) from [cli-builds#9.0.x](https://github.com/angular/cli-builds/tree/9.0.x).

##
Relevant changes in [commit range](4deae92a3...fbfcc45a3):

**Modified**
- help/doc.json
- help/update.json

##

PR Close #33870
2019-11-18 09:12:02 -08:00
f3d8f6adca fix(ngcc): always add exports for ModuleWithProviders references (#33875)
In #32902 a bug was supposedly fixed where internal classes as used
within `ModuleWithProviders` are publicly exported, even when the
typings file already contained the generic type on the
`ModuleWithProviders`. This fix turns out to have been incomplete, as
the `ModuleWithProviders` analysis is not done when not processing the
typings files.

The effect of this bug is that formats that are processed after the
initial format had been processed would not have exports for internal
symbols, resulting in "export '...' was not found in '...'" errors.

This commit fixes the bug by always running the `ModuleWithProviders`
analyzer. An integration test has been added that would fail prior to
this change.

Fixes #33701

PR Close #33875
2019-11-18 09:11:34 -08:00
a05e42ad09 test(ngcc): expand integration tests with APF like package layouts (#33875)
ngcc has a basic integration test infrastructure that downlevels
TypeScript code into bundle formats that need to be processed by ngcc.
Until now, only ES5 bundles were created with a flat structure, however
more complex scenarios require an APF-like layout containing multiple
bundle formats.

PR Close #33875
2019-11-18 09:11:34 -08:00
0854dc8688 fix(ngcc): correctly include internal .d.ts files (#33875)
Some declaration files may not be referenced from an entry-point's
main typings file, as it may declare types that are only used internally.
ngcc has logic to include declaration files based on all source files,
to ensure internal declaration files are available.

For packages following APF layout, however, this logic was insufficient.
Consider an entry-point with base path of `/esm2015/testing` and typings
residing in `/testing`, the file
`/esm2015/testing/src/nested/internal.js` has its typings file at
`/testing/src/nested/internal.d.ts`. Previously, the declaration was
assumed to be located at `/esm2015/testing/testing/internal.d.ts` (by
means of `/esm2015/testing/src/nested/../../testing/internal.d.ts`)
which is not where the declaration file can be found. This commit
resolves the issue by looking in the correct directory.

PR Close #33875
2019-11-18 09:11:34 -08:00
5926b2897f docs(docs-infra): prevent auto-linking of snippet in guide (#33877)
By adding the `bash` language to the code snippet it will no
longer be auto-linked, which was causing a false positive link
to be rendered.

Fixes #33859

PR Close #33877
2019-11-18 09:11:01 -08:00
0405ddb521 fix(docs-infra): do not auto-link code in bash and json snippets (#33877)
Previously any code block, which was not marked with
`no-auto-link` css class would have its contents auto-linked to
API pages. Sometimes this results in false positive links being
generated.

This is problematic for triple backticked blocks, which cannot provide
the `no-auto-link` CSS class to prevent the linking.

This commit fixes the problem by allowing the auto-linker to be
configured not to auto-link code blocks that have been marked with an
"ignored" language. By default these are `bash` and `json`.

Triple backticked blocks are able to specify the language directly after
the first set of triple backticks.

Fixes #33859

PR Close #33877
2019-11-18 09:11:01 -08:00
1a9904d2f1 style(docs-infra): reformat the auto-link-code files (#33877)
PR Close #33877
2019-11-18 09:11:01 -08:00
37243ce680 build(docs-infra): sync examples node engine constraints with aio (#33877)
PR Close #33877
2019-11-18 09:11:01 -08:00
59a4b76b57 fix(ngcc): correctly associate decorators with aliased classes (#33878)
In flat bundle formats, multiple classes that have the same name can be
suffixed to become unique. In ES5-like bundles this results in the outer
declaration from having a different name from the "implementation"
declaration within the class' IIFE, as the implementation declaration
may not have been suffixed.

As an example, the following code would fail to have a `Directive`
decorator as ngcc would search for `__decorate` calls that refer to
`AliasedDirective$1` by name, whereas the `__decorate` call actually
uses the `AliasedDirective` name.

```javascript
var AliasedDirective$1 = /** @class */ (function () {
    function AliasedDirective() {}
    AliasedDirective = tslib_1.__decorate([
        Directive({ selector: '[someDirective]' }),
    ], AliasedDirective);
    return AliasedDirective;
}());
```

This commit fixes the problem by not relying on comparing names, but
instead finding the declaration and matching it with both the outer
and inner declaration.

PR Close #33878
2019-11-18 09:10:36 -08:00
80cd7d7a40 test(ngcc): avoid using spy in Esm2015ReflectionHost test (#33878)
A testcase that was using a spy has shown itself to be brittle, and its
assertions can easily be moved into a related test.

PR Close #33878
2019-11-18 09:10:36 -08:00
e47638ec7c build: replace fs methods with ShellJS' test('-d') (#33895)
This makes the JS script more similar to the original bash script it
replaced (see #33854) and will be easier to follow for people that are
less familiar with Node.js scripting.

Discussed in:
https://github.com/angular/angular/pull/33854#discussion_r346949765

PR Close #33895
2019-11-18 09:10:16 -08:00
7567f2b8c8 Revert "build: add ngJitMode to ng_rollup_bundle terser config (#33773)"
This reverts commit c89eed56b6.

Reason: fails on Windows CI
2019-11-15 16:21:13 -08:00
ebf3afb7f0 docs: updated dynamic forms example (#31510)
Fixes #31446

PR Close #31510
2019-11-15 16:16:45 -08:00
d562625960 build: fix typo in ng_rollup_bundle marco where es5 bundle were being passed to terser instead of es2015 bundle (#33808)
PR Close #33808
2019-11-15 16:15:13 -08:00
e57a8350e1 build: fix es2015 resolution issue in ng_rollup_bundle tool (#33808)
This fixes an issue bundling es2015 with ng_rollup_bundle that was covered up by a typo when refactoring ng_rollup_bundle to not use legacy rollup_bundle internals and to use the new terser_minified rule and npm_package_bin to call brotli.

Note: ng_rollup_bundle is only used in the angular repo and not externally so this issue does not affect users

PR Close #33808
2019-11-15 16:15:13 -08:00
dafdf9a87b docs(platform-webworker): mark all public APIs as deprecated (#33824)
In ccc76f749, `@angular/platform-webworker` and
`@angular/platform-webworker-dynamic` were deprecated (i.e. by
deprecating all their public APIs). However, some public
`@angular/platform-webworker` APIs were accidentally missed,
additionally resulting in the package itself not showing up as
deprecated on angular.io.

This commit fixes it by marking those remaining public APIs as
deprecated.

PR Close #33824
2019-11-15 16:14:37 -08:00
0e20453273 fix(language-service): Provide completions for attribute values (#33839)
This commit fixes a bug whereby completions for attribute values are only
provided for directives that support the micro-syntax format, all other
bindings are ignored.

I'm not sure if this is a regresssion or a bug, because there were no
tests prior to this.

PR Close #33839
2019-11-15 16:13:09 -08:00
5bfcd8231a build: fix build scripts on macOS (#33854)
In #33823, `scripts/package-builds.sh` (which is used by both
`build-packages-dist.sh` and `build-ivy-npm-packages.sh`) was updated to
use `realpath`. It turns out that `realpath` does not exist on macOS, so
the build scripts do not work there.

In order to fix this (and also reduce the likelihood of introducing
similar issues in the future), this commit changes these bash scripts to
Node.js scripts (using [ShellJS](https://github.com/shelljs/shelljs) for
a cross-platform implementation of Unix shell commands where necessary).

PR Close #33854
2019-11-15 16:05:00 -08:00
e641eaaae0 build: add ngJitMode to ng_rollup_bundle terser config (#33773)
This adds a `tools/ng_rollup_bundle/terser_config.json` file to override the default terser_minified config provided by the rule. After this change, the layer violation in rules_nodejs can be fixed by removing `"global_defs": {"ngDevMode": false, "ngI18nClosureMode": false},` from `terser_config.default.json` in rules_nodejs.

Change requested by Alex Rickabaugh in https://github.com/bazelbuild/rules_nodejs/pull/1338.

PR Close #33773
2019-11-15 15:58:00 -08:00
3074cdfea9 Revert "fix(router): make routerLinkActive work with query params which contain arrays (#22666)" (#33861)
This reverts commit b30bb8dd91.

Reason: breaks internal g3 project.

PR Close #33861
2019-11-15 13:14:43 -08:00
c182dea146 build: fix zone.js version stamping after updating to new rollup_bundle (#33329)
PR Close #33329
2019-11-15 10:50:14 -08:00
502fa906cd build: update zone.js to use the new rollup_bundle (#33329)
PR Close #33329
2019-11-15 10:50:14 -08:00
430293abe2 build: fixes for cross-platform RBE (#33708)
The earlier update to nodejs rules 0.40.0 fixes the cross-platform RBE issues with nodejs_binary. This commit adds a work-around for rules_webtesting cross-platform RBE issues.

PR Close #33708
2019-11-15 10:49:55 -08:00
cba6b7dd20 fix(ivy): ExpressionChangedAfterItHasBeenCheckedError for SafeValue (#33749)
Fix #33448

PR Close #33749
2019-11-15 10:44:23 -08:00
09480d3a9d fix(compiler-cli): Refactor getTsTypeFromBuiltinType (#33778)
This commit fixes a few issues with helper method
`getBuiltInTypeFromTsType`.

1. The function is wrongly named. It should be the other way round.
2. The ts.Type returned by the function should not contain any value.
This is because for some data types like Number and String, the
SourceFile (context.node) is not the correct value. Value is never
needed for program correctness in this case.

PR Close #33778
2019-11-15 10:43:41 -08:00
ca633535c5 fix(language-service): Function alias should be callable (#33782)
This commit fixes a long standing bug whereby a template variable that
gets initialized to a class method gets resolved to the Any type, thus
when it is called the language service produces error "Member X is not
callable".

PR closes https://github.com/angular/angular/issues/16643
PR closes https://github.com/angular/vscode-ng-language-service/issues/234

PR Close #33782
2019-11-15 10:43:18 -08:00
9761ebe7bd fix(ivy): support for #id bootstrap selectors (#33784)
Fixes: #33485

PR Close #33784
2019-11-15 10:42:53 -08:00
9882a8215a fix(language-service): Recompute analyzed modules only when source files change (#33806)
This commit fixes a bug brought up by @andrius-pra whereby the language
service host would recompute the analyzed modules even when none of the
source files changes. This is due to a bug in our unit test that
precludes non-TS files from incrementing the project version.
Consequently, when the external template is updated, the program remains
the same.

With the bug fixed, the next step is to figure out if any source files
have been added / changed / removed since the last computation. The
previously analyzed could be safely retained only when none of these
operations happen.

PR Close #33806
2019-11-15 10:42:09 -08:00
0688a28be3 fix(language-service): Remove getTemplateReferences() from LanguageService API (#33807)
The method `getTemplateReferences()` appears in both the LanguageService
interface and LanguageServiceHost interface. It should belong in the
latter and not the former, since the former deals with the semantics of
the language and not the mechanics.

PR Close #33807
2019-11-15 10:41:52 -08:00
af2cca0c7a refactor(ivy): Remove findComponentView since we now store it in LView[DECLARATION_COMPONENT_VIEW] (#33810)
PR Close #33810
2019-11-15 10:41:02 -08:00
4a4888b276 docs(ivy): improve the missing $localize error message (#33826)
If the application is not running directly in the browser, e.g.
universal or app-shell, then the `$localize` import must be
adding to a different file than for normal browser applications.

This commit adds more information about this to avoid any
confusion.

// FW-1557

PR Close #33826
2019-11-15 10:38:36 -08:00
d45bbccb58 build: update to terser 4.4.0 (#33835)
Now that terser_minified supports args as of nodejs rules 0.40.0, ng_rollup_bundle can updated to the pass —comments /a^/ to args can turn off all comments and maintain the current ng_rollup_bundle behavior with the latest version fo terser. //packages/core/test/bundling/todo:bundle.min.js size test passes with this fix.

Tho not strictly necessary to update terser, this will be a rough edge when someone does try it as it is not obvious why the //packages/core/test/bundling/todo:bundle.min.js size test fails. Updating now should save time in the future by not hitting this issue.\

This change also affect ng_package output as the default comments that are preserved by terser are now Comments with @preserve, @license, @cc_on as well as comments starting with /*! and /**! are now preserved by default.. (https://github.com/terser/terser/blob/master/CHANGELOG.md). Example ng_package golden file also updated as there are not some /*! comments preserved that were in older versions of terser.

PR Close #33835
2019-11-15 10:37:45 -08:00
d49c830064 build: fix build scripts on macOS (#33843)
In #33823, `scripts/package-builds.sh` (which is used by both
`build-packages-dist.sh` and `build-ivy-npm-packages.sh`) was updated to
use `realpath`. It turns out that `realpath` does not exist on macOS, so
the build scripts do not work there.

This commit fixes it by switching from `realpath $(pwd)/..` to
`dirname $(pwd)`, which does practically the same thing (in this
situation) and works on macOS as well.

PR Close #33843
2019-11-15 10:36:55 -08:00
12d67e0d31 docs: Hyperlink to HTML & CSS (#33844)
Link to have a reference on the HTML & CSS for basic understanding.

PR Close #33844
2019-11-15 10:36:18 -08:00
d39b643553 docs(changelog): mention CLDR update in breaking changes (#33848)
Angular `9.0.0-rc.2` contains an update of CLDR data to `v36.0.0`. The localized data changed for some locales, and this is breaking tests that are checking the result of i18n applications. For example, the space separator used in numbers changed from `\xa0` to `\u202f`.

This commit adds a mention to this change in the release notes, so developers can more easily understand why some of their tests are failing when updating to v9.

PR Close #33848
2019-11-15 10:34:37 -08:00
84524583e9 fix(ivy): extend assertion in directiveInject function to support IcuContainers (#33832)
Prior to this commit the assert that we have in `directiveInject` (assert introduced recently) didn't include IcuContainer TNode type and as a result, the error is thrown in case pipes with dependencies are used inside ICUs. This commit extends the assert to allow for IcuContainer TNode types.

PR Close #33832
2019-11-14 16:01:30 -08:00
a61fb76d51 fix(ivy): avoid cyclical dependency in imports (#33831)
This commit moves the `setLContainerActiveIndex` and `getLContainerActiveIndex` functions used in a few files to a common `util/view_util.ts` lib to avoid cyclical dependency while importing `instructions/container.ts` where these functions located originally.

PR Close #33831
2019-11-14 12:13:26 -08:00
b1d0a4f84d feat(bazel): update ng-add to use bazel v1.1.0 (#33813)
The ng-add schematic for `@angular/bazel` should set up
the latest Bazel version v1.1.0 that contains fixes for
windows. e.g. 618e5a28f7

PR Close #33813
2019-11-14 10:49:45 -08:00
63c3569e2a build: update bazel to v1.1.0 (#33813)
Updates Bazel to the latest stable version. Bazel 1.1.0
supposedly fixes a permission bug in Windows. Hence we
should try to update and see if that fixes the bug.

It's generally good to be up to date. See potential bug
fix commit:
618e5a28f7.

PR Close #33813
2019-11-14 10:49:44 -08:00
aaa38cf3f3 build: minor improvements to build script output (#33823)
- Move "Building packages" log above actual work (computing targets).
- Print corrent script name (`build-ivy-npm-packages.sh` would claim to
  be `build-packages-dist.sh`).
- Make logs of different operations a little more consistent.

PR Close #33823
2019-11-14 09:29:50 -08:00
a9d834fe69 refactor(compiler-cli): Move diagnostics files to language service (#33809)
The following files are consumed only by the language service and do not
have to be in compiler-cli:

1. expression_diagnostics.ts
2. expression_type.ts
3. typescript_symbols.ts
4. symbols.ts

PR Close #33809
2019-11-14 09:29:07 -08:00
987d2bd181 refactor(ivy): minor improvements / cleanup in the DI code (#33794)
PR Close #33794
2019-11-14 09:28:26 -08:00
a16a57e52a perf(ivy): Improve performance of transplanted views (#33702)
PR Close #33702
2019-11-14 09:27:58 -08:00
1860a9edbc feat(docs-infra): simplify image styles and remove figure references (#33748)
Reference #33259
Removes figures elements as AIO is not typically using captions or image groups where figures would be necessary or appropriate

PR Close #33748
2019-11-14 09:27:02 -08:00
5fd0d15d86 docs(common): fix condition in transformed *ngIf (#33425)
`<div class="hero-list" *ngIf="heroes else loading">` defines `heroes` as condition, but `hero-list` is used as condition in the transformed code `<ng-template [ngIf]="heroes" [ngIfElse]="loading">`.
PR Close #33425
2019-11-14 09:25:37 -08:00
8e5ed203bc fix(router): make routerLinkActive work with query params which contain arrays (#22666)
The url_tree equalQueryParams and containsQueryParam methods did not handle query params that has arrays, which resulted in the routerLinkActive to not behave as expected, change was made to ensure query params with arrays are handled correctly

fixes #22223

PR Close #22666
2019-11-13 13:51:35 -08:00
06e36e5972 fix(ngcc): do not emit ES2015 code in ES5 files (#33514)
Previously, ngcc's `Renderer` would add some constants in the processed
files which were emitted as ES2015 code (e.g. `const` declarations).
This would result in invalid ES5 generated code that would break when
run on browsers that do not support the emitted format.

This commit fixes it by adding a `printStatement()` method to
`RenderingFormatter`, which can convert statements to JavaScript code in
a suitable format for the corresponding `RenderingFormatter`.
Additionally, the `translateExpression()` and `translateStatement()`
ngtsc helper methods are augmented to accept an extra hint to know
whether the code needs to be translated to ES5 format or not.

Fixes #32665

PR Close #33514
2019-11-13 13:49:31 -08:00
21bd8c9a2e fix(ngcc): generate correct metadata for classes with getter/setter properties (#33514)
While processing class metadata, ngtsc generates a `setClassMetadata()`
call which (among other things) contains info about property decorators.
Previously, processing getter/setter pairs with some of ngcc's
`ReflectionHost`s resulted in multiple metadata entries for the same
property, which resulted in duplicate object keys, which in turn causes
an error in ES5 strict mode.

This commit fixes it by ensuring that there are no duplicate property
names in the `setClassMetadata()` calls.

In addition, `generateSetClassMetadataCall()` is updated to treat
`ClassMember#decorators: []` the same as `ClassMember.decorators: null`
(i.e. omitting the `ClassMember` from the generated `setClassMetadata()`
call). Alternatively, ngcc's `ReflectionHost`s could be updated to do
this transformation (`decorators: []` --> `decorators: null`) when
reflecting on class members, but this would require changes in many
places and be less future-proof.

For example, given a class such as:

```ts
class Foo {
  @Input() get bar() { return 'bar'; }
  set bar(value: any) {}
}
```

...previously the generated `setClassMetadata()` call would look like:

```ts
ɵsetClassMetadata(..., {
  bar: [{type: Input}],
  bar: [],
});
```

The same class will now result in a call like:

```ts
ɵsetClassMetadata(..., {
  bar: [{type: Input}],
});
```

Fixes #30569

PR Close #33514
2019-11-13 13:49:31 -08:00
1c1240c21a refactor(compiler-cli): avoid superfluous parenthesis around statements (#33514)
Previously, due to a bug a `Context` with `isStatement: false` could be
returned in places where a `Context` with `isStatement: true` was
requested. As a result, some statements would be unnecessarily wrapped
in parenthesis.

This commit fixes the bug in `Context#withStatementMode` to always
return a `Context` with the correct `isStatement` value. Note that this
does not have any impact on the generated code other than avoiding some
superfluous parenthesis on certain statements.

PR Close #33514
2019-11-13 13:49:31 -08:00
4c706086a7 docs(common): getCurrencySymbol return typo (#33792)
Extra 0 added to end of docs.
PR Close #33792
2019-11-13 13:37:11 -08:00
93ba4c2418 fix(ivy): constant object literals shared across element and component instances (#33705)
Currently if a consumer does something like the following, the object literal will be shared across the two elements and any instances of the component template. The same applies to array literals:

```
<div [someDirective]="{}"></div>
<div [someDirective]="{}"></div>
```

These changes make it so that we generate a pure function even if an object is constant so that each instance gets its own object.

Note that the original design for this fix included moving the pure function factories into the `consts` array. In the process of doing so I realized that pure function are also used inside of directive host bindings which means that we don't have access to the `consts`.

These changes also:
* Fix an issue that meant that the `pureFunction0` instruction could only be run during creation mode.
* Make the `getConstant` utility slightly more convenient to use. This isn't strictly required for these changes to work, but I had made it as a part of a larger refactor that I ended up reverting.

PR Close #33705
2019-11-13 13:36:41 -08:00
3f6d9a9870 build: ts_web_test & ts_web_test_suite deprecated in favor of karma_web_test & karma_web_test_suite (#33802)
This is a breaking change in nodejs rules 0.40.0 as part of the API review & cleanup for the 1.0 release. Their APIs are identical as ts_web_test was just karma_web_test without the config_file attribute.

PR Close #33802
2019-11-13 13:33:39 -08:00
ba4f171fac build: update to rules_nodejs 0.40.0 (#33802)
This release includes nodejs cross-platform RBE fix in https://github.com/bazelbuild/rules_nodejs/pull/1320 and adds `args` to terser_minified in https://github.com/bazelbuild/rules_nodejs/pull/1317. These changes are needed to land a few outstanding PRs.

* build: fixes for cross-platform RBE #33708
* build: update zone.js to use the new rollup_bundle #33329

fix: fix

PR Close #33802
2019-11-13 13:33:38 -08:00
744a3854f3 docs: add info on setting up VSCode remote development using docker containers (#33790)
In f78bda9ff, recommended configuration files were added to be used as
basis for setting up [Remote Development using docker containers][1] in
VSCode. Apparently, I had forgotten to commit the corresponding
`README.md` file.

This commit adds `.devcontainer/README.md` with more info on Remote
Development in VSCode.

[1]: https://code.visualstudio.com/docs/remote/containers

PR Close #33790
2019-11-13 13:33:22 -08:00
ac9fa2f2eb build: delete rxjs d.ts files referencing rxjs-compat (#33786)
In order to speed up bazel build performance delete all rxjs d.ts files
that reference rxjs-compat.

For all ts_library and ng_module rules Bazel generates tsconfig.json file
that explicitly lists all d.ts files found in required npm package.

In case of rxjs, this means that tsconfig contains all d.ts files that
reference rxjs-compat package, which is an interop/backwards compatibility
package not installed in angular/angular repo.

But because tsconfig contains these d.ts files, tsc will try to resolve
them and silently fail. All these lookups are quite expensive and not
cached. This causes significant slowdown of the build under bazel.

This change removes all of these problematic rxjs d.ts files via an npm
postinstall hook. This is not ideal because it solves the problem only
for our repo, but it's a good start.

Build perf improvements per target:
//packages/core/src/reflect:reflect    5sec =>  3 sec
//packages/core:core                  17sec => 12 sec
//packages/router:router              30sec =>  8 sec

PR Close #33786
2019-11-13 13:33:06 -08:00
de8cf75ff5 fix(ivy): reset style property value defined using [style.prop.px] (#33780)
Prior to this change, setting style prop value to undefined or empty string would not result in resetting prop value in case the style prop is defined using [style.prop.px] syntax. The problem is that the check for empty value (and thus reseting the value) considered successful only in case of `null` value. This commit updates the check to use `isStylingValueDefined` function that also checks for undefined and empty string.

PR Close #33780
2019-11-13 13:32:33 -08:00
5ad1e5f5ec release: cut the v9.0.0-rc.2 release 2019-11-13 12:49:34 -08:00
b4d7a684f8 build: set up all packages to publish via wombot proxy (#33747)
PR Close #33747
2019-11-13 11:34:33 -08:00
49e517d049 fix(ngcc): support minified ES5 scenarios (#33777)
The reflection hosts have been updated to support the following
code forms, which were found in some minified library code:

* The class IIFE not being wrapped in parentheses.
* Calls to `__decorate()` being combined with the IIFE return statement.

PR Close #33777
2019-11-13 11:11:49 -08:00
e1df98be17 fix(ngcc): remove __decorator calls even when part of the IIFE return statement (#33777)
Previously we only removed `__decorate()` calls that looked like:

```
SomeClass = __decorate(...);
```

But in some minified scenarios this call gets wrapped up with the
return statement of the IIFE.

```
return SomeClass = __decorate(...);
```

This is now removed also, leaving just the return statement:

```
return SomeClass;
```

PR Close #33777
2019-11-13 11:11:49 -08:00
1b489083bc refactor(ngcc): move stripParentheses to Esm5ReflectionHost for re-use (#33777)
PR Close #33777
2019-11-13 11:11:48 -08:00
9f10aa0d8c refactor(ngcc): remove unused function (#33777)
PR Close #33777
2019-11-13 11:11:48 -08:00
ecf38d48cf fix(ngcc): add default config for ng2-dragula (#33797)
The `dist/` directory has a duplicate `package.json` pointing to the
same files, which (under certain configurations) can causes ngcc to try
to process the files twice and fail.

This commit adds a default ngcc config for `ng2-dragula` to ignore the
`dist/` entry-point.

Fixes #33718

PR Close #33797
2019-11-13 11:09:59 -08:00
5f9a2d1cf8 docs: expand type-check info in update page (#33577)
PR Close #33577
2019-11-13 11:08:51 -08:00
8ed32e5e71 refactor(language-service): Colocate getAnalzyedModules and upToDate (#33779)
These two functions go hand-in-hand, putting them together improves
readability.

PR Close #33779
2019-11-12 21:34:53 -08:00
d67a38b7f5 fix(ivy): ComponentFactory.create should clear host element content (#33487)
Prior to this change, ComponentFactory.create function invocation in Ivy retained the content of the host element (in case host element reference or CSS seelctor is provided as an argument). This behavior is different in View Engine, where the content of the host element was cleared, except for the case when ShadowDom encapsulation is used (to make sure native slot projection works). This commit aligns Ivy and View Engine and makes sure the host element is cleared before component content insertion.

PR Close #33487
2019-11-12 21:34:07 -08:00
f750f187d5 docs: dix typo in DEVELOPER.md (#33743)
PR Close #33743
2019-11-12 16:08:44 -08:00
4e027fe3e4 test(ivy): view insertion before ng-container with a ViewContainerRef (#33755)
Closes #33679

PR Close #33755
2019-11-12 14:07:31 -08:00
1b0255c3b0 docs: remove migration-ngcc guide (#33727)
PR Close #33727
2019-11-12 14:03:49 -08:00
29852960c3 docs: add postinstall opt-in (#33727)
PR Close #33727
2019-11-12 14:03:48 -08:00
508bbfd92f fix(core): remove ngcc postinstall migration (#33727)
Partially address https://github.com/angular/angular-cli/issues/16017

PR Close #33727
2019-11-12 14:03:48 -08:00
e4cb91d373 style: Remove use of String as type and use string instead. (#33763)
PR Close #33763
2019-11-12 13:59:17 -08:00
71238a95d9 fix(ivy): ensure module scope is rebuild on dependent change (#33522)
During incremental compilations, ngtsc needs to know which metadata
from a previous compilation can be reused, versus which metadata has to
be recomputed as some dependency was updated. Changes to
directives/components should cause the NgModule in which they are
declared to be recompiled, as the NgModule's compilation is dependent
on its directives/components.

When a dependent source file of a directive/component is updated,
however, a more subtle dependency should also cause to NgModule's source
file to be invalidated. During the reconciliation of state from a
previous compilation into the new program, the component's source file
is invalidated because one of its dependency has changed, ergo the
NgModule needs to be invalidated as well. Up until now, this implicit
dependency was not imposed on the NgModule. Additionally, any change to
a dependent file may influence the module scope to change, so all
components within the module must be invalidated as well.

This commit fixes the bug by introducing additional file dependencies,
as to ensure a proper rebuild of the module scope and its components.

Fixes #32416

PR Close #33522
2019-11-12 13:56:31 -08:00
c22f15812c docs: expand configuration usage (#33229)
Followup to https://github.com/angular/angular-cli/pull/15819

PR Close #33229
2019-11-12 13:55:59 -08:00
da01dbc459 fix(ivy): recompile component when template changes in ngc watch mode (#33551)
When the Angular compiler is operated through the ngc binary in watch
mode, changing a template in an external file would not cause the
component to be recompiled if Ivy is enabled.

There was a problem with how a cached compiler host was present that was
unaware of the changed resources, therefore failing to trigger a
recompilation of a component whenever its template changes. This commit
fixes the issue by ensuring that information about modified resources is
correctly available to the cached compiler host.

Fixes #32869

PR Close #33551
2019-11-12 13:55:10 -08:00
37ae45e2ec fix(ivy): Run ChangeDetection on transplanted views (#33644)
https://hackmd.io/@mhevery/rJUJsvv9H

Closes #33393

PR Close #33644
2019-11-12 13:53:54 -08:00
4ec079f852 fix(core): support ngInjectableDef on types with inherited ɵprov (#33732)
The `ngInjectableDef` property was renamed to `ɵprov`, but core must
still support both because there are published libraries that use the
older term.

We are only interested in such properties that are defined directly on
the type being injected, not on base classes. So there is a check that
the defintion is specifically for the given type.

Previously if you tried to inject a class that had `ngInjectableDef` but
also inherited `ɵprov` then the check would fail on the `ɵprov` property
and never even try the `ngInjectableDef` property resulting in a failed
injection.

This commit fixes this by attempting to find each of the properties
independently.

Fixes https://github.com/angular/ngcc-validation/pull/526

PR Close #33732
2019-11-12 11:54:15 -08:00
b75a21294f build(common): generate correct "global" locale files (#33662)
PR Close #33662
2019-11-12 11:39:20 -08:00
faaa96c737 build(common): fix bad root reference in global locale files (#33662)
There was a mistake in the generation of the global
locale files, where `root` was being used instead of
the correctl `global`.

PR Close #33662
2019-11-12 11:39:20 -08:00
c315881d04 perf(core): Avoid unnecessary creating provider factory (#33742)
In providerToRecord move creating the factory into a condition which
actually needs it to avoid unnecessary creating it

PR Close #33742
2019-11-12 09:57:05 -08:00
bb9b8030d0 ci: publish tarballs for the zone.js package as CI build artifacts (#33733)
Since #33321, Angular packages have been persisted on each build as
CircleCI build artifacts (`.tgz` files), which can be used to install
dependencies on a project (for the purpose of testing or trying out a
change before a PR being merged and without having to build the packages
from source locally).

Previously, only packages published to npm under the `@angular` scope
were persisted as build artifacts.

This commit adds the `zone.js` package to the list of persisted
packages.

Fixes #33686

PR Close #33733
2019-11-12 09:55:18 -08:00
20e27a86d4 test(ngcc): build zone.js from source in scripts/build-packages-dist.sh (#33733)
In #33046, internal uses of `zone.js` were switched to reference it
directly from source (built with Bazel) instead of npm. As a result, the
necessary scripts were updated to build `zone.js` as necessary. However,
some `integration/**/debug-test.sh` scripts were missed (apparently
because they are not used on CI, but only locally as helpers for
debugging the integration projects).

This commit updates the `scripts/build-packages-dist.sh` script to also
build `zone.js`, so that other scripts (such as the various
`debug-test.sh` scripts) can use it.

PR Close #33733
2019-11-12 09:55:17 -08:00
7c4366dce8 revert: fix(ivy): R3TestBed should clean up registered modules after each test (#32872) (#33663)
This commit reverts 475e36a.

PR Close #33663
2019-11-12 09:53:16 -08:00
f8e9c1e6f1 revert: fix(ivy): Only restore registered modules if user compiles modules with TestBed (#32944) (#33663)
This commit reverts 63256b5.

PR Close #33663
2019-11-12 09:53:16 -08:00
4988094e7d fix(ivy): auto register NgModules with ID (#33663)
PR Close #33663
2019-11-12 09:53:16 -08:00
9ab49def61 docs: updated amexio resource description (#33645)
PR Close #33645
2019-11-12 09:52:49 -08:00
66157436f8 fix(language-service): Resolve template variable in nested ngFor (#33676)
This commit fixes a bug whereby template variables in nested scope are
not resolved properly and instead are simply typed as `any`.

PR closes https://github.com/angular/vscode-ng-language-service/issues/144

PR Close #33676
2019-11-11 16:06:00 -08:00
72796b98b1 fix: generate the new locale files (#33682)
PR Close #33682
2019-11-11 15:55:14 -08:00
ea83125df3 fix: use full cldr data to support all locales (#33682)
switching to cldr-data package resulted in
loss of some locales, since by default only core locales are loaded.
This PR adds a flag to tell cldr-data to use full locale coverage

fixes: #33681

PR Close #33682
2019-11-11 15:55:14 -08:00
83626962cf fix(ivy): ensure that the correct document is available (#33712)
Most of the use of `document` in the framework is within
the DI so they just inject the `DOCUMENT` token and are done.

Ivy is special because it does not rely upon the DI and must
get hold of the document some other way. There are a limited
number of places relevant to ivy that currently consume a global
document object.

The solution is modelled on the `LOCALE_ID` approach, which has
`getLocaleId()` and `setLocaleId()` top-level functions for ivy (see
`core/src/render3/i18n.ts`).  In the rest of Angular (i.e. using DI) the
`LOCALE_ID` token has a provider that also calls setLocaleId() to
ensure that ivy has the same value.

This commit defines `getDocument()` and `setDocument() `top-level
functions for ivy. Wherever ivy needs the global `document`, it calls
`getDocument()` instead.  Each of the platforms (e.g. Browser, Server,
WebWorker) have providers for `DOCUMENT`. In each of those providers
they also call `setDocument()` accordingly.

Fixes #33651

PR Close #33712
2019-11-11 14:01:05 -08:00
5b292bf125 test: rename mispelled sanitization_spec.ts file (#33712)
PR Close #33712
2019-11-11 14:01:04 -08:00
3160193719 test: cleanup document "after" each test (#33712)
It looks like there was a typo when it originally was
written. As it works right now, the `beforeEach` and
`afterEach` cancel each other out. But then
`ensureDocument()` is called anyway in the `withBody()`
function.

With this change there is no need to call `ensureDocument() in the
`withBody() function.

PR Close #33712
2019-11-11 14:01:04 -08:00
9045e3e495 fix: resolve event listeners not correct when registered outside of ngZone (#33711)
Close #33687.

PR Close #33711
2019-11-11 14:00:31 -08:00
c5400616f8 fix(ngcc): ensure that adjacent statements go after helper calls (#33689)
Previously the renderers were fixed so that they inserted extra
"adjacent" statements after the last static property of classes.

In order to help the build-optimizer (in Angular CLI) to be able to
tree-shake classes effectively, these statements should also appear
after any helper calls, such as `__decorate()`.

This commit moves the computation of this positioning into the
`NgccReflectionHost` via the `getEndOfClass()` method, which
returns the last statement that is related to the class.

FW-1668

PR Close #33689
2019-11-11 13:01:15 -08:00
f67802ddc0 refactor(ngcc): allow look up of multiple helpers (#33689)
This change is a precursor to finding the end of a
class, which needs to search for helpers of many
different names.

PR Close #33689
2019-11-11 13:01:15 -08:00
ffb010d3ab docs: fix numbering (#33736)
PR Close #33736
2019-11-11 12:46:34 -08:00
f45d5dc331 fix(ivy): provider override via TestBed should remove old providers from the list (#33706)
While overriding providers in Ivy TestBed (via TestBed.overrideProvider call), the old providers were retained in the list, since the override takes precedence. However, presence of providers in the list might have side-effect: if a provider has the `ngOnDestroy` lifecycle hook, this hook will be registered and invoked later (when component is destroyed). This commit updates TestBed logic to clear provider list by removing the ones which have overrides.

PR Close #33706
2019-11-11 12:46:16 -08:00
9396b19631 docs: update update guide to use ng update instead of npm i (#33726)
For more context see: https://github.com/angular/angular-cli/issues/16021

Closes: https://github.com/angular/angular-cli/issues/16021

PR Close #33726
2019-11-11 09:40:08 -08:00
343483cae0 build: add @angular/localize to ng-update packageGroup (#33721)
PR Close #33721
2019-11-11 09:39:43 -08:00
682eaef690 ci: update material-unit-tests job commit (#33716)
Updates the commit we run the `material-unit-tests` job
against. The latest commit includes 1255139a38

This commit reduces the flakiness of a `MatMenu` test and therefore
improves the stability of the material-unit-tests job.

Example failing build: https://circleci.com/gh/angular/angular/521625

PR Close #33716
2019-11-11 09:39:24 -08:00
011ecdf939 fix(common): rerun cldr to remove � characters (#33699)
PR Close #33699
2019-11-11 09:38:58 -08:00
cdceb60bd0 build: move clang formating out of gulp stream for cldr (#33699)
PR Close #33699
2019-11-11 09:38:58 -08:00
4613639a02 build(docs-infra): update @angular/* to 9.0.0-rc.1 (#33547)
PR Close #33547
2019-11-11 09:38:05 -08:00
f32ce01d93 build(docs-infra): update @angular/* to 9.0.0-rc.0 (#33547)
PR Close #33547
2019-11-11 09:38:05 -08:00
3d13a6d318 build(docs-infra): update @angular/cli to 9.0.0-rc.0 (#33547)
PR Close #33547
2019-11-11 09:38:04 -08:00
46b5e4ae69 build(docs-infra): update aio payload size (#33547)
PR Close #33547
2019-11-11 09:38:04 -08:00
e6f383fe20 test(ivy): get ViewRef.rootNodes should get all root nodes from projectable nodes (#33647)
PR Close #33647
2019-11-11 09:37:39 -08:00
86d656bc67 refactor(ivy): simplify getFirstNativeNode processing of LContainer (#33647)
PR Close #33647
2019-11-11 09:37:39 -08:00
c5737f4a19 fix(ivy): properly insert views in front of empty views (#33647)
PR Close #33647
2019-11-11 09:37:39 -08:00
0b998844c8 fix(ivy): properly insert views in front of views with an empty element container (#33647)
PR Close #33647
2019-11-11 09:37:39 -08:00
fc24a69a6b docs: add AOT mode default to version 9 upgrade doc (#33703)
PR Close #33703
2019-11-08 15:38:41 -08:00
b789c891bb docs: update jiali's info (#33698)
PR Close #33698
2019-11-08 14:41:16 -08:00
eb9ed8cc13 docs: grammar fix (#33170)
PR Close #33170
2019-11-08 14:40:58 -08:00
139b2e4f40 docs: add missing word (#33170)
PR Close #33170
2019-11-08 14:40:58 -08:00
290ae43db3 docs: fix migration-renderer code snippets (#33553)
Fix code snippets for migration-renderer.md found in next.angular.io
PR Close #33553
2019-11-08 12:43:21 -08:00
c7f913d1ec build(docs-infra): upgrade cli command docs sources to 4deae92a3 (#33684)
Updating [angular#9.0.x](https://github.com/angular/angular/tree/9.0.x) from [cli-builds#9.0.x](https://github.com/angular/cli-builds/tree/9.0.x).

##
Relevant changes in [commit range](7184f55eb...4deae92a3):

**Modified**
- help/update.json

##

PR Close #33684
2019-11-08 10:55:37 -08:00
ba68b560bc style: increase timeout to give blaze sufficient time to build (#33678)
PR Close #33678
2019-11-08 10:55:01 -08:00
69fdcbd71c docs: add ivy ssr opt-out (#33616)
PR Close #33616
2019-11-08 10:54:31 -08:00
a6ddda8834 docs: describe your change... (#32914)
Grammatical changes
PR Close #32914
2019-11-08 10:52:17 -08:00
2910d1a65f docs: update ivy compatibility guide with latest changes (#33675)
This commit removes one of the expected Ivy changes because we have
decided to change the behavior to be more backwards-compatible.

It also adds a bug fix that is technically breaking to the list of
expected changes.

PR Close #33675
2019-11-08 10:51:53 -08:00
811275cd15 fix(ivy): properly determine the first native node of a view (#33627)
PR Close #33627
2019-11-07 16:50:30 -08:00
c8c51a9879 test(ivy): tests for view insertion before another view (#33627)
PR Close #33627
2019-11-07 16:50:30 -08:00
33f6cd4799 fix(compiler-cli): Pass SourceFile to getFullText() (#33660)
Similar to https://github.com/angular/angular/pull/33633, this commit is
needed to fix an outage with the Angular Kythe indexer.

Crash logs:

```
TypeError: Cannot read property 'text' of undefined
    at NodeObject.getFullText (typescript/stable/lib/typescript.js:121443:57)
    at FactoryGenerator.generate (angular2/rc/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts:67:34)
    at GeneratedShimsHostWrapper.getSourceFile (angular2/rc/packages/compiler-cli/src/ngtsc/shims/src/host.ts:88:26)
    at findSourceFile (typescript/stable/lib/typescript.js:90654:29)
    at typescript/stable/lib/typescript.js:90553:85
    at getSourceFileFromReferenceWorker (typescript/stable/lib/typescript.js:90520:34)
    at processSourceFile (typescript/stable/lib/typescript.js:90553:13)
    at processRootFile (typescript/stable/lib/typescript.js:90383:13)
    at typescript/stable/lib/typescript.js:89399:60
    at Object.forEach (typescript/stable/lib/typescript.js:280:30)

```

PR Close #33660
2019-11-07 16:47:08 -08:00
c1bd3bc76a fix(common): update CLDR generated files to 36.0.0 (#33584)
PR Close #33584
2019-11-07 22:11:34 +00:00
0248d6b8a7 build: update cldr-data package to 36.0.0 (#33584)
PR Close #33584
2019-11-07 22:11:33 +00:00
4f69ff7e85 test(ivy): introduce a benchmark for duplicate map-based style/class bindings (#33608)
Prior to this patch all the styling benchmarks only tested for
template map-based style/class bindings. Because of each of the bindings
being only present in the template, there was no possibility of
there being any duplicate map-based styling bindings.

This benchmark introduces benchmarking for map-based style/class bindings
that are evaluated from both template bindings as well as directives.

This benchmark can be executed by calling:

```
bazel build //packages/core/test/render3/perf:duplicate_map_based_style_and_class_bindings_lib.min_debug.es2015.js

node dist/bin/packages/core/test/render3/perf/duplicate_map_based_style_and_class_bindings_lib.min_debug.es2015.js
```

The benchmark is also run via the `profile_all.js` script (found in
`packages/core/test/render3/perf/`)

PR Close #33608
2019-11-07 20:34:26 +00:00
9df1178fe7 docs: app-name not rendering on browser (#33613)
Fixes #33612

PR Close #33613
2019-11-07 20:31:48 +00:00
76e52c5297 perf: report cold and minimal times when profiling in a browser (#33653)
PR Close #33653
2019-11-07 20:30:59 +00:00
83b635cb3f fix(ngcc): add reexports only once (#33658)
When ngcc is configured to generate reexports for a package using the
`generateDeepReexports` configuration option, it could incorrectly
render the reexports as often as the number of compiled classes in the
declaration file. This would cause compilation errors due to duplicated
declarations.

PR Close #33658
2019-11-07 20:29:13 +00:00
09cf0cd878 perf(ivy): allign (browser, benchpress, G3) the depth param for the tree benchmark (#33629)
PR Close #33629
2019-11-07 20:02:27 +00:00
cb55f60c74 revert: "fix(ivy): recompile component when template changes in ngc watch mode (#33551)" (#33661)
This reverts commit 8912b11f56.

PR Close #33661
2019-11-07 19:57:56 +00:00
cd8333cf0d fix(ivy): recompile component when template changes in ngc watch mode (#33551)
When the Angular compiler is operated through the ngc binary in watch
mode, changing a template in an external file would not cause the
component to be recompiled if Ivy is enabled.

There was a problem with how a cached compiler host was present that was
unaware of the changed resources, therefore failing to trigger a
recompilation of a component whenever its template changes. This commit
fixes the issue by ensuring that information about modified resources is
correctly available to the cached compiler host.

Fixes #32869

PR Close #33551
2019-11-07 17:52:59 +00:00
854a377232 test(ivy): introduce a benchmark for duplicate style/class bindings (#33600)
Prior to this patch all the styling benchmarks only tested for
template-based style/class bindings. Because of each of the bindings
being only present in the template, there was no possibility of
there being any duplicate bindings. This benchmark introduces
style/class bindings being evaluated from both a template and from
various directives.

This benchmark can be executed by calling:

```
bazel build //packages/core/test/render3/perf:duplicate_style_and_class_bindings_lib.min_debug.es2015.js

node dist/bin/packages/core/test/render3/perf/duplicate_style_and_class_bindings_lib.min_debug.es2015.js
```

The benchmark is also run via the `profile_all.js` script (found in
`packages/core/test/render3/perf/`)

PR Close #33600
2019-11-07 17:50:33 +00:00
8164d28b6c docs(changelog): Fixes typo in changelog (#33618)
Fixes typo in `NgFormSelectorWarning`. Was `NgFromSelectorWarning`
PR Close #33618
2019-11-07 17:49:56 +00:00
59b25da76b fix(common): update CLDR generated files after change to npm sources (#33634)
PR Close #33634
2019-11-07 17:49:20 +00:00
68380e4af2 build: move cldr dependency to npm (#33634)
PR Close #33634
2019-11-07 17:49:20 +00:00
4029c98f16 build(docs-infra): upgrade cli command docs sources to 7184f55eb (#33648)
Updating [angular#9.0.x](https://github.com/angular/angular/tree/9.0.x) from [cli-builds#9.0.x](https://github.com/angular/cli-builds/tree/9.0.x).

##
Relevant changes in [commit range](e598c8e89...7184f55eb):

**Modified**
- help/generate.json

##

PR Close #33648
2019-11-07 17:45:06 +00:00
99ead47bff fix(ivy): match directives on namespaced elements (#33555)
Prior to this change, namespaced elements such as SVG elements would not
participate correctly in directive matching as their namespace was not
ignored, which was the case with the View Engine compiler. This led to
incorrect behavior at runtime and template type checking.

This commit resolved the issue by ignoring the namespace of elements and
attributes like they were in View Engine.

Fixes #32061

PR Close #33555
2019-11-07 15:40:51 +00:00
d09ad82293 fix(ivy): Handle overrides for {providedIn: AModule} in R3TestBed (#33606)
This issue was found when debugging a test failure that was using lazy
loaded modules with the router. When doing this, the router calls
`NgModuleFactory.create` for the loaded module. This module gets a new
injector so the overrides provided in TestBed are not applied unless the
Injectable is in the providers list (which is not the case for
{providedIn...} Injectables).

PR Close #33606
2019-11-07 15:34:20 +00:00
4924d73b8e test(ivy): Have more descriptive names for LView (#33449)
When debugging `LView`s it is easy to get lost since all of them have
the same name. This change does three things:

1. It makes `TView` have an explicit type:
  - `Host`: for the top level `TView` for bootstrap
  - `Component`: for the `TView` which represents components template
  - `Embedded`: for the `TView` which represents an embedded template
2. It changes the name of `LView` to `LHostView`, `LComponentView`, and
  `LEmbeddedView` depending on the `TView` type.
3. For `LComponentView` and `LEmbeddedView` we also append the name of
  of the `context` constructor. The result is that we have `LView`s which
  are name as: `LComponentView_MyComponent` and `LEmbeddedView_NgIfContext`.

The above changes will make it easier to understand the structure of the
application when debugging.

NOTE: All of these are behind `ngDevMode` and will get removed in
production application.

PR Close #33449
2019-11-07 15:33:50 +00:00
7bccef516f fix(compiler-cli): Fix typo $implict (#33633)
Should be $implicit instead.

PR Close #33633
2019-11-07 01:54:17 +00:00
204620291e fix(ivy): better support for i18n attributes on <ng-container>s (#33599)
Prior to this commit, i18n runtime logic used `elementAttributeInternal` function (that uses `setAttribute` function under the hood) for all elements where i18n attributes are present. However the `<ng-container>` elements in a template may also have i18n attributes and calling `setAttribute` fails, since they are represented as comment nodes in DOM. This commit ensures that we call `setAttribute` on nodes with TNodeType.Element type (that support that operation) only.

PR Close #33599
2019-11-07 01:51:42 +00:00
3419b5002f style: Add VSCode recommended launch and task configurations (#33544)
This adds a common configurations used when developing code in VSCode.
Specifically it adds support for launching these targets as tasks and
ind debugger.

- `packages/core/test`
- `packages/core/test/render3`
- `packages/core/test/acceptance`

PR Close #33544
2019-11-07 01:04:41 +00:00
687582fd99 fix(compiler): correctly parse attributes with a dot in the name (#32256)
Previously the compiler would ignore everything in the attribute
name after the first dot. For example
<div [attr.someAttr.attrSuffix]="var"></div>
is turned into <div someAttr="varValue"></div>.

This commit ensures that whole attribute name is captured.
Now <div [attr.someAttr.attrSuffix]="var"></div>
is turned into <div someAttr.attrSuffix="varValue"></div>

PR Close #32256
2019-11-07 01:02:57 +00:00
5968 changed files with 189013 additions and 260762 deletions

View File

@ -1,97 +1,9 @@
# Bazel does not yet support wildcards or other .gitignore semantics for
# .bazelignore. Two issues for this feature request are outstanding:
# https://github.com/bazelbuild/bazel/issues/7093
# https://github.com/bazelbuild/bazel/issues/8106
.git
node_modules
dist
aio/content
aio/node_modules
aio/tools/examples/shared/node_modules
packages/bazel/node_modules
integration/bazel/bazel-bazel
integration/bazel/bazel-bin
integration/bazel/bazel-out
integration/bazel/bazel-testlogs
integration/bazel
integration/bazel-schematics/demo
# All integration test node_modules folders
integration/bazel/node_modules
integration/bazel-schematics/node_modules
integration/cli-hello-world/node_modules
integration/cli-hello-world-ivy-compat/node_modules
integration/cli-hello-world-ivy-i18n/node_modules
integration/cli-hello-world-ivy-minimal/node_modules
integration/cli-hello-world-lazy/node_modules
integration/cli-hello-world-lazy-rollup/node_modules
integration/dynamic-compiler/node_modules
integration/hello_world__closure/node_modules
integration/hello_world__systemjs_umd/node_modules
integration/i18n/node_modules
integration/injectable-def/node_modules
integration/ivy-i18n/node_modules
integration/language_service_plugin/node_modules
integration/ng_elements/node_modules
integration/ng_elements_schematics/node_modules
integration/ng_update/node_modules
integration/ng_update_migrations/node_modules
integration/ngcc/node_modules
integration/platform-server/node_modules
integration/service-worker-schema/node_modules
integration/side-effects/node_modules
integration/terser/node_modules
integration/typings_test_ts36/node_modules
integration/typings_test_ts37/node_modules
# All integration test .yarn_local_cache folders
integration/bazel/.yarn_local_cache
integration/bazel-schematics/.yarn_local_cache
integration/cli-hello-world/.yarn_local_cache
integration/cli-hello-world-ivy-compat/.yarn_local_cache
integration/cli-hello-world-ivy-i18n/.yarn_local_cache
integration/cli-hello-world-ivy-minimal/.yarn_local_cache
integration/cli-hello-world-lazy/.yarn_local_cache
integration/cli-hello-world-lazy-rollup/.yarn_local_cache
integration/dynamic-compiler/.yarn_local_cache
integration/hello_world__closure/.yarn_local_cache
integration/hello_world__systemjs_umd/.yarn_local_cache
integration/i18n/.yarn_local_cache
integration/injectable-def/.yarn_local_cache
integration/ivy-i18n/.yarn_local_cache
integration/language_service_plugin/.yarn_local_cache
integration/ng_elements/.yarn_local_cache
integration/ng_elements_schematics/.yarn_local_cache
integration/ng_update/.yarn_local_cache
integration/ng_update_migrations/.yarn_local_cache
integration/ngcc/.yarn_local_cache
integration/platform-server/.yarn_local_cache
integration/service-worker-schema/.yarn_local_cache
integration/side-effects/.yarn_local_cache
integration/terser/.yarn_local_cache
integration/typings_test_ts36/.yarn_local_cache
integration/typings_test_ts37/.yarn_local_cache
# All integration test NPM_PACKAGE_MANIFEST.json folders
integration/bazel/NPM_PACKAGE_MANIFEST.json
integration/bazel-schematics/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-compat/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-i18n/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-minimal/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-lazy/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-lazy-rollup/NPM_PACKAGE_MANIFEST.json
integration/dynamic-compiler/NPM_PACKAGE_MANIFEST.json
integration/hello_world__closure/NPM_PACKAGE_MANIFEST.json
integration/hello_world__systemjs_umd/NPM_PACKAGE_MANIFEST.json
integration/i18n/NPM_PACKAGE_MANIFEST.json
integration/injectable-def/NPM_PACKAGE_MANIFEST.json
integration/ivy-i18n/NPM_PACKAGE_MANIFEST.json
integration/language_service_plugin/NPM_PACKAGE_MANIFEST.json
integration/ng_elements/NPM_PACKAGE_MANIFEST.json
integration/ng_elements_schematics/NPM_PACKAGE_MANIFEST.json
integration/ng_update/NPM_PACKAGE_MANIFEST.json
integration/ng_update_migrations/NPM_PACKAGE_MANIFEST.json
integration/ngcc/NPM_PACKAGE_MANIFEST.json
integration/platform-server/NPM_PACKAGE_MANIFEST.json
integration/service-worker-schema/NPM_PACKAGE_MANIFEST.json
integration/side-effects/NPM_PACKAGE_MANIFEST.json
integration/terser/NPM_PACKAGE_MANIFEST.json
integration/typings_test_ts36/NPM_PACKAGE_MANIFEST.json
integration/typings_test_ts37/NPM_PACKAGE_MANIFEST.json
packages/bazel/node_modules

View File

@ -33,11 +33,6 @@ build --incompatible_strict_action_env
run --incompatible_strict_action_env
test --incompatible_strict_action_env
# Do not build runfile trees by default. If an execution strategy relies on runfile
# symlink teee, the tree is created on-demand. See: https://github.com/bazelbuild/bazel/issues/6627
# and https://github.com/bazelbuild/bazel/commit/03246077f948f2790a83520e7dccc2625650e6df
build --nobuild_runfile_links
###############################
# Release support #
# Turn on these settings with #
@ -47,8 +42,7 @@ build --nobuild_runfile_links
# Releases should always be stamped with version control info
# This command assumes node on the path and is a workaround for
# https://github.com/bazelbuild/bazel/issues/4802
build:release --workspace_status_command="yarn -s ng-dev release build-env-stamp"
build:release --stamp
build:release --workspace_status_command="node ./tools/bazel_stamp_vars.js"
###############################
# Output #
@ -67,25 +61,27 @@ test --test_output=errors
# Bazel flags for CircleCI are in /.circleci/bazel.linux.rc and /.circleci/bazel.windows.rc
##################################
# Settings for integration tests #
##################################
# Trick bazel into treating BUILD files under integration/bazel as being regular files
# This lets us glob() up all the files inside this integration test to make them inputs to tests
# (Note, we cannot use common --deleted_packages because the bazel version command doesn't support it)
build --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
query --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
################################
# Temporary Settings for Ivy #
################################
# To determine if the compiler used should be Ivy instead of ViewEngine, one can use `--config=ivy`
# on any bazel target. This is a temporary flag until codebase is permanently switched to Ivy.
build --define=angular_ivy_enabled=False
# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=compile=aot` on
# any bazel target. This is a temporary flag until codebase is permanently switched to Ivy.
build --define=compile=legacy
build:view-engine --define=angular_ivy_enabled=False
build:ivy --define=angular_ivy_enabled=True
#######################
# Remote HTTP Caching #
#######################
build --remote_http_cache=https://storage.googleapis.com/angular-team-cache
build --remote_accept_cached=true
build --remote_upload_local_results=false
######################################
# Remote HTTP Caching writes support #
# Turn on these settings with #
# --config=-http-caching #
######################################
build:remote-http-caching --remote_upload_local_results=true
build:remote-http-caching --google_default_credentials
##################################
# Remote Build Execution support #
@ -115,6 +111,7 @@ build:remote --javabase=@rbe_ubuntu1604_angular//java:jdk
build:remote --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
build:remote --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
build:remote --crosstool_top=@rbe_ubuntu1604_angular//cc:toolchain
build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
build:remote --extra_toolchains=@rbe_ubuntu1604_angular//config:cc-toolchain
build:remote --extra_execution_platforms=//tools:rbe_ubuntu1604-angular
build:remote --host_platform=//tools:rbe_ubuntu1604-angular
@ -126,16 +123,6 @@ build:remote --project_id=internal-200822
build:remote --remote_cache=remotebuildexecution.googleapis.com
build:remote --remote_executor=remotebuildexecution.googleapis.com
##################################
# Saucelabs tests settings #
# Turn on these settings with #
# --config=saucelabs #
##################################
# For saucelabs tests we don't want to enable flaky test attempts. Karma has its own integrated
# retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times.
test:saucelabs --flaky_test_attempts=1
###############################
# NodeJS rules settings
# These settings are required for rules_nodejs

View File

@ -1,3 +0,0 @@
3.2.0
# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117]
# When updating the Bazel version you also need to update the RBE toolchains version in package.bzl

View File

@ -12,7 +12,7 @@ We use this as a symmetric AES encryption key to encrypt tokens like
a GitHub token that enables publishing snapshots.
To create the github_token file, we take this approach:
- Find the angular-builds:token in the internal pw database
- Find the angular-builds:token in http://valentine
- Go inside the CircleCI default docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it circleci/node:10.12`
- echo "https://[token]:@github.com" > credentials
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY

View File

@ -4,7 +4,7 @@
# Import config items common to both Linux and Windows setups.
# https://docs.bazel.build/versions/master/guide.html#bazelrc-syntax-and-semantics
try-import %workspace%/.circleci/bazel.common.rc
import %workspace%/.circleci/bazel.common.rc
# Save downloaded repositories in a location that can be cached by CircleCI. This helps us
# speeding up the analysis time significantly with Bazel managed node dependencies on the CI.
@ -14,17 +14,4 @@ build --repository_cache=/home/circleci/bazel_repository_cache
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
# https://circleci.com/docs/2.0/configuration-reference/#resource_class
build --local_cpu_resources=8
build --local_ram_resources=14336
# All build executed remotely should be done using our RBE configuration.
build:remote --google_default_credentials
# Upload to GCP's Build Status viewer to allow for us to have better viewing of execution/build
# logs. This is only done on CI as the BES (GCP's Build Status viewer) API requires credentials
# from service accounts, rather than end user accounts.
build:remote --bes_backend=buildeventservice.googleapis.com
build:remote --bes_timeout=30s
build:remote --bes_results_url="https://source.cloud.google.com/results/invocations/"
build --config=remote
build --local_resources=14336,8.0,1.0

View File

@ -4,18 +4,11 @@
# Import config items common to both Linux and Windows setups.
# https://docs.bazel.build/versions/master/guide.html#bazelrc-syntax-and-semantics
try-import %workspace%/.circleci/bazel.common.rc
import %workspace%/.circleci/bazel.common.rc
# Save downloaded repositories in a location that can be cached by CircleCI. This helps us
# speeding up the analysis time significantly with Bazel managed node dependencies on the CI.
build --repository_cache=C:/Users/circleci/bazel_repository_cache
# Manually set the local resources used in windows CI runs
build --local_ram_resources=13500
build --local_cpu_resources=4
# All windows jobs run on master and should use http caching
build --remote_http_cache=https://storage.googleapis.com/angular-team-cache
build --remote_accept_cached=true
build --remote_upload_local_results=true
build --google_default_credentials
build --config=remote-http-caching

View File

@ -22,18 +22,15 @@ version: 2.1
# **NOTE 1 **: If you change the cache key prefix, also sync the cache_key_fallback to match.
# **NOTE 2 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
var_3: &cache_key v7-angular-node-12-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
# We invalidate the cache if the Bazel version changes because otherwise the `bazelisk` cache
# folder will contain all previously used versions and ultimately cause the cache restoring to
# be slower due to its growing size.
var_4: &cache_key_fallback v7-angular-node-12-{{ checksum ".bazelversion" }}
var_3_win: &cache_key_win v7-angular-win-node-12-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
var_4_win: &cache_key_win_fallback v7-angular-win-node-12-{{ checksum ".bazelversion" }}
var_3: &cache_key v3-angular-node-10.16-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
var_4: &cache_key_fallback v3-angular-node-10.16-
var_3_win: &cache_key_win v5-angular-win-node-12.0-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
var_4_win: &cache_key_win_fallback v5-angular-win-node-12.0-
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
var_5: &components_repo_unit_tests_cache_key v7-angular-components-189d98e8b01b33974328255f085de04251d61567
var_6: &components_repo_unit_tests_cache_key_fallback v7-angular-components-
# Cache key for the Material unit tests job. **Note** when updating the SHA in the cache keys,
# also update the SHA for the "MATERIAL_REPO_COMMIT" environment variable.
var_5: &material_unit_tests_cache_key v5-angular-material-a5cad10cf9ca5db84c307d38d5594c3f1d89ae2b
var_6: &material_unit_tests_cache_key_fallback v5-angular-material-
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
# `build-ivy-npm-packages`.
@ -67,6 +64,9 @@ var_10: &only_on_master
# **NOTE 1**: Pin to exact images using an ID (SHA). See https://circleci.com/docs/2.0/circleci-images/#using-a-docker-image-id-to-pin-an-image-to-a-fixed-version.
# (Using the tag in not necessary when pinning by ID, but include it anyway for documentation purposes.)
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
# **NOTE 3**: If you change the version of the `*-browsers` docker image, make sure the
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
# version that is compatible with the Chrome version in the image.
executors:
default-executor:
parameters:
@ -74,7 +74,21 @@ executors:
type: string
default: medium
docker:
- image: circleci/node:12.14.1@sha256:f9de24fc0017059cc42ef7d07db060008af65a98b1f0cdd1ef3339213226bf6d
- image: circleci/node:10.16@sha256:75c05084fff4afa3683a03c5a04a4a3ad95c536ff2439d8fe14e7e1f5c58b09a
resource_class: << parameters.resource_class >>
working_directory: ~/ng
browsers-executor:
parameters:
resource_class:
type: string
default: medium
docker:
# The browser docker image comes with Chrome and Firefox preinstalled. This is just
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
# docker image with browsers pre-installed.
- image: circleci/node:10.16-browsers@sha256:d2a96fe1cbef51257ee626b5f645e64dade3e886f00ba9cb7e8ea65b4efe8db1
resource_class: << parameters.resource_class >>
working_directory: ~/ng
@ -106,45 +120,19 @@ commands:
- attach_workspace:
at: *workspace_location
# Install shared libs used by Chrome that is either provisioned by
# rules_webtesting or by puppeteer.
install_chrome_libs:
description: Install shared Chrome libs
steps:
- run:
name: Install shared Chrome libs
command: |
sudo apt-get update
# Install GTK+ graphical user interface (libgtk-3-0), advanced linux sound architecture (libasound2)
# and network security service libraries (libnss3) & X11 Screen Saver extension library (libssx1)
# which are dependencies of chrome & needed for karma & protractor headless chrome tests.
# This is a very small install which takes around 7s in comparing to using the full
# circleci/node:x.x.x-browsers image.
sudo apt-get -y install libgtk-3-0 libasound2 libnss3 libxss1
# Install java runtime which is required by some integration tests such as
# //integration:hello_world__closure_test, //integration:i18n_test and
# //integration:ng_elements_test to run the closure compiler
install_java:
description: Install java
steps:
- run:
name: Install java
command: |
sudo apt-get update
# Install java runtime
sudo apt-get install default-jre
# Initializes the CI environment by setting up common environment variables.
init_environment:
description: Initializing environment (setting up variables)
description: Initializing environment (setting up variables, overwriting Yarn)
steps:
- run: ./.circleci/env.sh
- run:
name: Set up environment
environment:
CIRCLE_GIT_BASE_REVISION: << pipeline.git.base_revision >>
CIRCLE_GIT_REVISION: << pipeline.git.revision >>
command: ./.circleci/env.sh
# Overwrite the yarn installed in the docker container with our own version.
name: Overwrite yarn with our own version
command: |
ourYarn=$(realpath ./third_party/github.com/yarnpkg/yarn/releases/download/v1.17.3/bin/yarn.js)
sudo chmod a+x $ourYarn
sudo ln -fs $ourYarn /usr/local/bin/yarn
- run: echo "Yarn version $(yarn --version)"
- run:
# Configure git as the CircleCI `checkout` command does.
# This is needed because we only checkout on the setup job.
@ -156,29 +144,8 @@ commands:
git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true
git config --global gc.auto 0 || true
init_saucelabs_environment:
description: Sets up a domain that resolves to the local host.
steps:
- run:
name: Preparing environment for running tests on Sauce Labs.
command: |
# For SauceLabs jobs, we set up a domain which resolves to the machine which launched
# the tunnel. We do this because devices are sometimes not able to properly resolve
# `localhost` or `127.0.0.1` through the SauceLabs tunnel. Using a domain that does not
# resolve to anything on SauceLabs VMs ensures that such requests are always resolved
# through the tunnel, and resolve to the actual tunnel host machine (i.e. the CircleCI VM).
# More context can be found in: https://github.com/angular/angular/pull/35171.
setPublicVar SAUCE_LOCALHOST_ALIAS_DOMAIN "angular-ci.local"
setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev)
- run:
# Sets up a local domain in the machine's host file that resolves to the local
# host. This domain is helpful in Sauce Labs tests where devices are not able to
# properly resolve `localhost` or `127.0.0.1` through the sauce-connect tunnel.
name: Setting up alias domain for local host.
command: echo "127.0.0.1 $SAUCE_LOCALHOST_ALIAS_DOMAIN" | sudo tee -a /etc/hosts
# Normally this would be an individual job instead of a command.
# But startup and setup time for each individual windows job are high enough to discourage
# But startup and setup time for each invidual windows job are high enough to discourage
# many small jobs, so instead we use a command for setup unless the gain becomes significant.
setup_win:
description: Setup windows node environment
@ -195,11 +162,36 @@ commands:
- *cache_key_win_fallback
# Reinstall to get windows binaries.
- run: yarn install --frozen-lockfile --non-interactive
# Install @bazel/bazelisk globally and use that for the first run.
- setup_circleci_bazel_config_win
# Install @bazel/bazel globally and use that for the first run.
# Workaround for https://github.com/bazelbuild/rules_nodejs/issues/894
# NB: the issue was for @bazel/bazel but the same problem applies to @bazel/bazelisk
- run: yarn global add @bazel/bazelisk@$env:BAZELISK_VERSION
- run: bazelisk info
- run: yarn global add @bazel/bazel@$env:BAZEL_VERSION
- run: bazel info
setup_circleci_bazel_config:
description: Set up CircleCI bazel configuration on Linux
steps:
- run: sudo cp .circleci/bazel.linux.rc $HOME/.bazelrc
setup_circleci_bazel_config_win:
description: Set up CircleCI bazel configuration on Windows
steps:
- run: copy .circleci\bazel.windows.rc $env:USERPROFILE\.bazelrc
- run: mkdir $env:APPDATA\gcloud
# We need ensure that the same default digest is used for encoding and decoding
# with openssl. Openssl versions might have different default digests which can
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
- run: openssl aes-256-cbc -d -in .circleci\gcp_token -md md5 -out "$env:APPDATA\gcloud\application_default_credentials.json" -k "$env:CIRCLE_PROJECT_REPONAME"
setup_bazel_rbe:
description: Setup bazel RBE remote execution
steps:
# We need ensure that the same default digest is used for encoding and decoding
# with openssl. Openssl versions might have different default digests which can
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
- run: openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
- run: echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
- run: ./.circleci/setup-rbe.sh .bazelrc.user
notify_webhook_on_fail:
description: Notify a webhook about failure
@ -223,7 +215,6 @@ jobs:
executor: default-executor
steps:
- checkout
- init_environment
- run:
name: Rebase PR on target branch
# After checkout, rebase on top of target branch.
@ -233,7 +224,7 @@ jobs:
git config user.name "angular-ci"
git config user.email "angular-ci"
# Rebase PR on top of target branch.
node tools/rebase-pr.js
node tools/rebase-pr.js angular/angular ${CIRCLE_PR_NUMBER}
else
echo "This build is not over a PR, nothing to do."
fi
@ -242,6 +233,7 @@ jobs:
keys:
- *cache_key
- *cache_key_fallback
- init_environment
- run:
name: Running Yarn install
command: yarn install --frozen-lockfile --non-interactive
@ -269,25 +261,25 @@ jobs:
- custom_attach_workspace
- init_environment
- run: yarn -s tslint
- run: yarn -s ng-dev format changed $CI_GIT_BASE_REVISION --check
- run: yarn -s ts-circular-deps:check
- run: yarn -s ng-dev pullapprove verify
- run: yarn -s ng-dev commit-message validate-range --range $CI_COMMIT_RANGE
- run: 'yarn bazel:format -mode=check ||
(echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)'
# Run the skylark linter to check our Bazel rules
- run: 'yarn bazel:lint ||
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
- run: yarn gulp lint
- run: node tools/verify-codeownership
test:
executor:
name: default-executor
# Now that large integration tests are running locally in parallel (they can't run on RBE yet
# as they require network access for yarn install), this test is running out of memory
# consistently with the xlarge machine.
# TODO: switch back to xlarge once integration tests are running on remote-exec
resource_class: 2xlarge+
resource_class: xlarge
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- install_java
- setup_circleci_bazel_config
# Setup remote execution and run RBE-compatible tests.
- setup_bazel_rbe
- run:
command: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only
no_output_timeout: 20m
@ -300,7 +292,9 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- setup_circleci_bazel_config
- setup_bazel_rbe
# We need to explicitly specify the --symlink_prefix option because otherwise we would
# not be able to easily find the output bin directory when uploading artifacts for size
# measurements.
@ -327,8 +321,12 @@ jobs:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
destination: core/todo/bundle.br
# NOTE: This is currently limited to master builds only. See the `monitoring` configuration.
saucelabs_view_engine:
# This job is currently a PoC for running tests on SauceLabs via bazel. It runs a subset of the
# tests in `legacy-unit-tests-saucelabs` (see
# [BUILD.bazel](https://github.com/angular/angular/blob/ef44f51d5/BUILD.bazel#L66-L92)).
#
# NOTE: This is currently limited to master builds only. See the `default_workflow` configuration.
test_saucelabs_bazel:
executor:
name: default-executor
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
@ -338,49 +336,36 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- init_saucelabs_environment
- setup_circleci_bazel_config
- setup_bazel_rbe
- run:
name: Run Bazel tests on Saucelabs with ViewEngine
# See /tools/saucelabs/README.md for more info
name: Run Bazel tests in saucelabs
# All web tests are contained within a single //:test_web_all target for Saucelabs
# as running each set of tests as a separate target will attempt to acquire too
# many browsers on Saucelabs (7 per target currently) and some tests will always
# fail to acquire browsers. For example:
# 14 02 2019 19:52:33.170:WARN [launcher]: chrome beta on SauceLabs have not captured in 180000 ms, killing.
# //packages/forms/test:web_test_sauce TIMEOUT in 315.0s
command: |
yarn bazel run //tools/saucelabs:sauce_service_setup
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "ivy-only", ...) except attr("tags", "fixme-saucelabs-ve", ...)')
yarn bazel test --config=saucelabs ${TESTS}
yarn bazel run //tools/saucelabs:sauce_service_stop
no_output_timeout: 40m
- notify_webhook_on_fail:
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
# NOTE: This is currently limited to master builds only. See the `monitoring` configuration.
saucelabs_ivy:
executor:
name: default-executor
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
# container for this job. This is necessary because we launch a lot of browsers concurrently
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
resource_class: xlarge
steps:
- custom_attach_workspace
- init_environment
- init_saucelabs_environment
- run:
name: Run Bazel tests on Saucelabs with Ivy
# See /tools/saucelabs/README.md for more info
command: |
yarn bazel run //tools/saucelabs:sauce_service_setup
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "no-ivy-aot", ...) except attr("tags", "fixme-saucelabs-ivy", ...)')
yarn bazel test --config=saucelabs --config=ivy ${TESTS}
yarn bazel run //tools/saucelabs:sauce_service_stop
no_output_timeout: 40m
./scripts/saucelabs/run-bazel-via-tunnel.sh \
--tunnel-id angular-${CIRCLE_BUILD_NUM}-${CIRCLE_NODE_INDEX} \
--username $SAUCE_USERNAME \
--key $(echo $SAUCE_ACCESS_KEY | rev) \
yarn bazel test //:test_web_all
no_output_timeout: 20m
- notify_webhook_on_fail:
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
test_aio:
executor: default-executor
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# Compile dependencies to ivy
# Running `ngcc` here (instead of implicitly via `ng build`) allows us to take advantage of
# the parallel, async mode speed-up (~20-25s on CI).
- run: yarn --cwd aio ngcc --properties es2015
# Build aio
- run: yarn --cwd aio build --progress=false
# Lint the code
@ -399,11 +384,11 @@ jobs:
- run: yarn --cwd aio redirects-test
deploy_aio:
executor: default-executor
# Needed because before deploying the deploy-production script runs the PWA score tests.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# Deploy angular.io to production (if necessary)
- run: setPublicVar_CI_STABLE_BRANCH
- run: yarn --cwd aio deploy-production
@ -413,11 +398,11 @@ jobs:
viewengine:
type: boolean
default: false
executor: default-executor
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# Build aio (with local Angular packages)
- run: yarn --cwd aio build-local<<# parameters.viewengine >>-with-viewengine<</ parameters.viewengine >>-ci
# Run unit tests
@ -443,23 +428,32 @@ jobs:
test_docs_examples:
parameters:
viewengine:
ivy:
type: boolean
default: false
executor:
name: default-executor
# Needed because the example e2e tests depend on Chrome.
name: browsers-executor
resource_class: xlarge
parallelism: 5
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# Install aio
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
- when:
condition: << parameters.ivy >>
steps:
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
# package installer picks up the locally built packages from that location.
# *Note*: We could also adjust the packages installer, but given we won't have
# two different folders of Angular distributions in the future, we should keep
# the packages installer unchanged.
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
- run: yarn --cwd aio example-e2e --setup --local <<# parameters.viewengine >>--viewengine<</ parameters.viewengine >> --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
- run: yarn --cwd aio example-e2e --setup --local <<# parameters.ivy >>--ivy<</ parameters.ivy >> --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
aio_preview:
@ -479,11 +473,11 @@ jobs:
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
test_aio_preview:
executor: default-executor
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
- run:
name: Wait for preview and run tests
@ -505,7 +499,10 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- run: node scripts/build/build-packages-dist.js
- setup_circleci_bazel_config
- setup_bazel_rbe
- run: node scripts/build-packages-dist.js
# Save the npm packages from //packages/... for other workflow jobs to read
- persist_to_workspace:
@ -521,7 +518,6 @@ jobs:
- "node_modules"
- "aio/node_modules"
- "~/bazel_repository_cache"
- "~/.cache/bazelisk"
# Build the ivy npm packages.
build-ivy-npm-packages:
@ -531,14 +527,38 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- run: node scripts/build/build-ivy-npm-packages.js
- setup_circleci_bazel_config
- setup_bazel_rbe
- run: node scripts/build-ivy-npm-packages.js
# Save the npm packages from //packages/... for other workflow jobs to read
- persist_to_workspace:
root: *workspace_location
paths:
- ng/dist/packages-dist-ivy-aot
- ng/dist/zone.js-dist-ivy-aot
# We run the integration tests outside of Bazel for now.
# They are a separate workflow job so that they can be easily re-run.
# When the tests are ported to bazel test targets, they should move to the "test"
# job above, as part of the bazel test command. That has flaky_test_attempts so the
# need to re-run manually should be alleviated.
# See comments inside the integration/run_tests.sh script.
integration_test:
executor:
# Needed because the integration tests expect Chrome to be installed (e.g cli-hello-world)
name: browsers-executor
# Note: we run Bazel in one of the integration tests, and it can consume >2G
# of memory. Together with the system under test, this can exhaust the RAM
# on a 4G worker so we use a larger machine here too.
resource_class: xlarge
parallelism: 4
steps:
- custom_attach_workspace
- init_environment
# Runs the integration tests in parallel across multiple CircleCI container instances. The
# amount of container nodes for this job is controlled by the "parallelism" option.
- run: ./integration/run_tests.sh ${CIRCLE_NODE_INDEX} ${CIRCLE_NODE_TOTAL}
# This job creates compressed tarballs (`.tgz` files) for all Angular packages and stores them as
# build artifacts. This makes it easy to try out changes from a PR build for testing purposes.
@ -558,14 +578,14 @@ jobs:
# Publish `@angular/*` packages.
- run:
name: Create artifacts for @angular/* packages
command: ./scripts/ci/create-package-archives.sh $CI_BRANCH $CI_COMMIT $NG_PACKAGES_DIR $NG_PACKAGES_ARCHIVES_DIR
command: ./scripts/ci/create-package-archives.sh $CI_PULL_REQUEST $CI_COMMIT $NG_PACKAGES_DIR $NG_PACKAGES_ARCHIVES_DIR
- store_artifacts:
path: *ng_packages_archives_dir
destination: angular
# Publish `zone.js` package.
- run:
name: Create artifacts for zone.js package
command: ./scripts/ci/create-package-archives.sh $CI_BRANCH $CI_COMMIT $ZONEJS_PACKAGES_DIR $ZONEJS_PACKAGES_ARCHIVES_DIR
command: ./scripts/ci/create-package-archives.sh $CI_PULL_REQUEST $CI_COMMIT $ZONEJS_PACKAGES_DIR $ZONEJS_PACKAGES_ARCHIVES_DIR
- store_artifacts:
path: *zonejs_packages_archives_dir
destination: zone.js
@ -596,26 +616,24 @@ jobs:
- run:
name: Decrypt github credentials
# We need ensure that the same default digest is used for encoding and decoding with
# OpenSSL. OpenSSL versions might have different default digests which can cause
# decryption failures based on the installed OpenSSL version. https://stackoverflow.com/a/39641378/4317734
# openssl. Openssl versions might have different default digests which can cause
# decryption failures based on the installed openssl version. https://stackoverflow.com/a/39641378/4317734
command: 'openssl aes-256-cbc -d -in .circleci/github_token -md md5 -k "${KEY}" -out ~/.git_credentials'
- run: ./scripts/ci/publish-build-artifacts.sh
aio_monitoring_stable:
executor: default-executor
# This job needs Chrome to be globally installed because the tests run with Protractor
# which does not load the browser through the Bazel webtesting rules.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- run: setPublicVar_CI_STABLE_BRANCH
- run:
name: Check out `aio/` and yarn from the stable branch
name: Check out `aio/` from the stable branch
command: |
git fetch origin $CI_STABLE_BRANCH
git checkout --force origin/$CI_STABLE_BRANCH -- aio/ .yarn/ .yarnrc
# Ignore yarn's engines check, because we checked out `aio/package.json` from the stable
# branch and there could be a node version skew, which is acceptable in this monitoring job.
- run: yarn config set ignore-engines true
git checkout --force origin/$CI_STABLE_BRANCH -- aio/
- run:
name: Run tests against https://angular.io/
command: ./aio/scripts/test-production.sh https://angular.io/ $CI_AIO_MIN_PWA_SCORE
@ -625,11 +643,12 @@ jobs:
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
aio_monitoring_next:
executor: default-executor
# This job needs Chrome to be globally installed because the tests run with Protractor
# which does not load the browser through the Bazel webtesting rules.
executor: browsers-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- run:
name: Run tests against https://next.angular.io/
command: ./aio/scripts/test-production.sh https://next.angular.io/ $CI_AIO_MIN_PWA_SCORE
@ -648,75 +667,77 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- init_saucelabs_environment
- run:
name: Starting Saucelabs tunnel service
command: ./tools/saucelabs/sauce-service.sh run
name: Preparing environment for running tests on Saucelabs.
command: |
setPublicVar KARMA_JS_BROWSERS $(node -e 'console.log(require("./browser-providers.conf").sauceAliases.CI_REQUIRED.join(","))')
setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev)
- run:
name: Starting Saucelabs tunnel
command: ./scripts/saucelabs/start-tunnel.sh
background: true
- run: yarn tsc -p packages
- run: yarn tsc -p modules
- run: yarn bazel build //packages/zone.js:npm_package
- run:
# Waiting on ready ensures that we don't run tests too early without Saucelabs not being ready.
name: Waiting for Saucelabs tunnel to connect
command: ./tools/saucelabs/sauce-service.sh ready-wait
- run:
name: Running tests on Saucelabs.
command: |
browsers=$(node -e 'console.log(require("./browser-providers.conf").sauceAliases.CI_REQUIRED.join(","))')
yarn karma start ./karma-js.conf.js --single-run --browsers=${browsers}
- run:
name: Stop Saucelabs tunnel service
command: ./tools/saucelabs/sauce-service.sh stop
# Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests
# too early without Saucelabs not being ready.
- run: ./scripts/saucelabs/wait-for-tunnel.sh
- run: yarn karma start ./karma-js.conf.js --single-run --browsers=${KARMA_JS_BROWSERS}
- run: ./scripts/saucelabs/stop-tunnel.sh
# Job that runs all unit tests of the `angular/components` repository.
components-repo-unit-tests:
legacy-misc-tests:
executor: default-executor
steps:
- custom_attach_workspace
- init_environment
- run: yarn gulp check-cycle
# TODO: disabled because the Bazel packages-dist does not seem to have map files for
# the ESM5/ES2015 output. See: https://github.com/angular/angular/issues/27966
# - run: yarn gulp source-map-test
# Job to run unit tests from angular/components. Needs a browser since all
# component unit tests assume they're running in the browser environment.
material-unit-tests:
executor:
name: default-executor
name: browsers-executor
resource_class: xlarge
steps:
- custom_attach_workspace
- init_environment
# Although RBE is configured below for the Material repo, also setup RBE in the Angular repo
# to provision Angular's GCP token into the environment variables.
- setup_bazel_rbe
# Restore the cache before cloning the repository because the clone script re-uses
# the restored repository if present. This reduces the amount of times the components
# repository needs to be cloned (this is slow and increases based on commits in the repo).
- restore_cache:
keys:
- *components_repo_unit_tests_cache_key
# Whenever the `angular/components` SHA is updated, the cache key will no longer
# match. The fallback cache will still match, and CircleCI will restore the most
# recently cached repository folder. Without the fallback cache, we'd need to download
# the repository from scratch and it would slow down the job. This is because we can't
# clone the repository with reduced `--depth`, but rather need to clone the whole
# repository to be able to support arbitrary SHAs.
- *components_repo_unit_tests_cache_key_fallback
- *material_unit_tests_cache_key
- *material_unit_tests_cache_key_fallback
- run:
name: "Fetching angular/components repository"
command: ./scripts/ci/clone_angular_components_repo.sh
name: "Fetching Material repository"
command: ./scripts/ci/clone_angular_material_repo.sh
- run:
# Run yarn install to fetch the Bazel binaries as used in the components repo.
name: Installing dependencies.
# Run yarn install to fetch the Bazel binaries as used in the Material repo.
name: Installing Material dependencies.
# TODO: remove this once the repo has been updated to use NodeJS v12 and Yarn 1.19.1.
# We temporarily ignore the "engines" because the Angular components repository has
# minimum dependency on NodeJS v12 and Yarn 1.19.1, but the framework repository uses
# older versions.
command: yarn --ignore-engines --cwd ${COMPONENTS_REPO_TMP_DIR} install --frozen-lockfile --non-interactive
command: yarn --ignore-engines --cwd ${MATERIAL_REPO_TMP_DIR} install --frozen-lockfile --non-interactive
- save_cache:
key: *components_repo_unit_tests_cache_key
key: *material_unit_tests_cache_key
paths:
# Temporary directory must be kept in sync with the `$COMPONENTS_REPO_TMP_DIR` env
# variable. It needs to be hardcoded here, because env variables interpolation is
# not supported.
- "/tmp/angular-components-repo"
# Material directory must be kept in sync with the `$MATERIAL_REPO_TMP_DIR` env variable.
# It needs to be hardcoded here, because env variables interpolation is not supported.
- "/tmp/material2"
- run:
# Updates the `angular/components` `package.json` file to refer to the release output
# inside the `packages-dist` directory. Note that it's not necessary to perform a yarn
# install as Bazel runs Yarn automatically when needed.
name: Setting up release packages.
command: node scripts/ci/update-deps-to-dist-packages.js ${COMPONENTS_REPO_TMP_DIR}/package.json dist/packages-dist/
name: "Setup Bazel RBE remote execution in Material repo"
command: |
./.circleci/setup-rbe.sh "${MATERIAL_REPO_TMP_DIR}/.bazelrc.user"
- run:
name: "Running `angular/components` unit tests"
command: ./scripts/ci/run_angular_components_unit_tests.sh
name: "Running Material unit tests"
command: ./scripts/ci/run_angular_material_unit_tests.sh
test_zonejs:
executor:
@ -725,6 +746,8 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- setup_circleci_bazel_config
- setup_bazel_rbe
# Install
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
# Run zone.js tools tests
@ -734,7 +757,6 @@ jobs:
cp dist/bin/packages/zone.js/npm_package/dist/zone-mix.js ./packages/zone.js/test/extra/ &&
cp dist/bin/packages/zone.js/npm_package/dist/zone-patch-electron.js ./packages/zone.js/test/extra/ &&
yarn --cwd packages/zone.js electrontest
- run: yarn --cwd packages/zone.js jesttest
# Windows jobs
# Docs: https://circleci.com/docs/2.0/hello-world-windows/
@ -787,9 +809,20 @@ workflows:
- build-ivy-npm-packages:
requires:
- setup
- legacy-misc-tests:
requires:
- build-npm-packages
- legacy-unit-tests-saucelabs:
requires:
- setup
- test_saucelabs_bazel:
# This job is currently a PoC and a subset of `legacy-unit-tests-saucelabs`. Running on
# master only to avoid wasting resources.
#
# TODO: Run this job on all branches (including PRs) as soon as it is not a PoC.
<<: *only_on_master
requires:
- setup
- test_aio:
requires:
- setup
@ -811,10 +844,10 @@ workflows:
requires:
- build-npm-packages
- test_docs_examples:
name: test_docs_examples_viewengine
viewengine: true
name: test_docs_examples_ivy
ivy: true
requires:
- build-npm-packages
- build-ivy-npm-packages
- aio_preview:
# Only run on PR builds. (There can be no previews for non-PR builds.)
<<: *only_on_pull_requests
@ -823,6 +856,9 @@ workflows:
- test_aio_preview:
requires:
- aio_preview
- integration_test:
requires:
- build-npm-packages
- publish_packages_as_artifacts:
requires:
- build-npm-packages
@ -835,17 +871,19 @@ workflows:
# Only publish if tests and integration tests pass
- test
- test_ivy_aot
- integration_test
# Only publish if `aio`/`docs` tests using the locally built Angular packages pass
- test_aio_local
- test_aio_local_viewengine
- test_docs_examples
- test_docs_examples_viewengine
- test_docs_examples_ivy
# Get the artifacts to publish from the build-packages-dist job
# since the publishing script expects the legacy outputs layout.
- build-npm-packages
- build-ivy-npm-packages
- legacy-unit-tests-saucelabs
- components-repo-unit-tests:
- legacy-misc-tests
- material-unit-tests:
requires:
- build-npm-packages
- test_zonejs:
@ -866,7 +904,7 @@ workflows:
requires:
- test_ivy_aot
monitoring:
aio_monitoring:
jobs:
- setup
- aio_monitoring_stable:
@ -875,26 +913,8 @@ workflows:
- aio_monitoring_next:
requires:
- setup
- saucelabs_ivy:
# Testing saucelabs via Bazel currently taking longer than the legacy saucelabs job as it
# each karma_web_test target is provisioning and tearing down browsers which is adding
# a lot of overhead. Running once daily on master only to avoid wasting resources and
# slowing down CI for PRs.
# TODO: Run this job on all branches (including PRs) once karma_web_test targets can
# share provisioned browsers and we can remove the legacy saucelabs job.
requires:
- setup
- saucelabs_view_engine:
# Testing saucelabs via Bazel currently taking longer than the legacy saucelabs job as it
# each karma_web_test target is provisioning and tearing down browsers which is adding
# a lot of overhead. Running once daily on master only to avoid wasting resources and
# slowing down CI for PRs.
# TODO: Run this job on all branches (including PRs) once karma_web_test targets can
# share provisioned browsers and we can remove the legacy saucelabs job.
requires:
- setup
triggers:
- schedule:
<<: *only_on_master
# Runs monitoring jobs at 10:00AM every day.
# Runs AIO monitoring jobs at 10:00AM every day.
cron: "0 10 * * *"

View File

@ -3,7 +3,7 @@
# Variables
readonly projectDir=$(realpath "$(dirname ${BASH_SOURCE[0]})/..")
readonly envHelpersPath="$projectDir/.circleci/env-helpers.inc.sh";
readonly bashEnvCachePath="$projectDir/.circleci/bash_env_cache";
readonly getCommitRangePath="$projectDir/.circleci/get-commit-range.js";
# Load helpers and make them available everywhere (through `$BASH_ENV`).
source $envHelpersPath;
@ -15,23 +15,27 @@ echo "source $envHelpersPath;" >> $BASH_ENV;
####################################################################################################
# See https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables for more info.
####################################################################################################
setPublicVar CI "$CI"
setPublicVar PROJECT_ROOT "$projectDir";
setPublicVar CI_AIO_MIN_PWA_SCORE "95";
# This is the branch being built; e.g. `pull/12345` for PR builds.
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
setPublicVar CI_BUILD_URL "$CIRCLE_BUILD_URL";
# ChromeDriver version compatible with the Chrome version included in the docker image used in
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 75.0.3770.90";
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
# `CI_COMMIT_RANGE` is only used on push builds (a.k.a. non-PR, non-scheduled builds and rerun
# workflows of such builds).
setPublicVar CI_GIT_BASE_REVISION "${CIRCLE_GIT_BASE_REVISION}";
setPublicVar CI_GIT_REVISION "${CIRCLE_GIT_REVISION}";
setPublicVar CI_COMMIT_RANGE "$CIRCLE_GIT_BASE_REVISION..$CIRCLE_GIT_REVISION";
# NOTE: With [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) enabled,
# `CIRCLE_COMPARE_URL` is no longer available and the commit range cannot be reliably
# detected. Fall back to only considering the last commit (which is accurate in the majority
# of cases for push builds).
setPublicVar CI_COMMIT_RANGE "`[[ ${CIRCLE_PR_NUMBER:-false} != false ]] && echo "" || echo "$CIRCLE_SHA1~1...$CIRCLE_SHA1"`";
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";
setPublicVar CI_PR_REPONAME "$CIRCLE_PR_REPONAME";
setPublicVar CI_PR_USERNAME "$CIRCLE_PR_USERNAME";
####################################################################################################
@ -51,63 +55,36 @@ setSecretVar CI_SECRET_PAYLOAD_FIREBASE_TOKEN "$ANGULAR_PAYLOAD_TOKEN";
####################################################################################################
# Define SauceLabs environment variables for CircleCI.
####################################################################################################
setPublicVar SAUCE_USERNAME "angular-framework";
setSecretVar SAUCE_ACCESS_KEY "0c731274ed5f-cbc9-16f4-021a-9835e39f";
# In order to have a meaningful SauceLabs badge on the repo page,
# the angular2-ci account is used only when pushing commits to master;
# in all other cases, the regular angular-ci account is used.
if [ "${CI_PULL_REQUEST}" = "false" ] && [ "${CI_REPO_OWNER}" = "angular" ] && [ "${CI_BRANCH}" = "master" ]; then
setPublicVar SAUCE_USERNAME "angular2-ci";
setSecretVar SAUCE_ACCESS_KEY "693ebc16208a-0b5b-1614-8d66-a2662f4e";
else
setPublicVar SAUCE_USERNAME "angular-ci";
setSecretVar SAUCE_ACCESS_KEY "9b988f434ff8-fbca-8aa4-4ae3-35442987";
fi
# TODO(josephperrott): Remove environment variables once all saucelabs tests are via bazel method.
setPublicVar SAUCE_LOG_FILE /tmp/angular/sauce-connect.log
setPublicVar SAUCE_READY_FILE /tmp/angular/sauce-connect-ready-file.lock
setPublicVar SAUCE_PID_FILE /tmp/angular/sauce-connect-pid-file.lock
setPublicVar SAUCE_TUNNEL_IDENTIFIER "angular-framework-${CIRCLE_BUILD_NUM}-${CIRCLE_NODE_INDEX}"
setPublicVar SAUCE_TUNNEL_IDENTIFIER "angular-${CIRCLE_BUILD_NUM}-${CIRCLE_NODE_INDEX}"
# Amount of seconds we wait for sauceconnect to establish a tunnel instance. In order to not
# acquire CircleCI instances for too long if sauceconnect failed, we need a connect timeout.
setPublicVar SAUCE_READY_FILE_TIMEOUT 120
####################################################################################################
# Define environment variables for the `angular/components` repo unit tests job.
# Define environment variables for the Angular Material unit tests job.
####################################################################################################
# We specifically use a directory within "/tmp" here because we want the cloned repo to be
# completely isolated from angular/angular in order to avoid any bad interactions between
# their separate build setups. **NOTE**: When updating the temporary directory, also update
# the `save_cache` path configuration in `config.yml`
setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
setPublicVar COMPONENTS_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
setPublicVar COMPONENTS_REPO_COMMIT "189d98e8b01b33974328255f085de04251d61567"
# their separate build setups.
setPublicVar MATERIAL_REPO_TMP_DIR "/tmp/material2"
setPublicVar MATERIAL_REPO_URL "https://github.com/angular/material2.git"
setPublicVar MATERIAL_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI "config.yml".
setPublicVar MATERIAL_REPO_COMMIT "a5cad10cf9ca5db84c307d38d5594c3f1d89ae2b"
####################################################################################################
# Decrypt GCP Credentials and store them as the Google default credentials.
####################################################################################################
mkdir -p "$HOME/.config/gcloud";
openssl aes-256-cbc -d -in "${projectDir}/.circleci/gcp_token" \
-md md5 -k "$CIRCLE_PROJECT_REPONAME" -out "$HOME/.config/gcloud/application_default_credentials.json"
####################################################################################################
# Set bazel configuration for CircleCI runs.
####################################################################################################
cp "${projectDir}/.circleci/bazel.linux.rc" "$HOME/.bazelrc";
####################################################################################################
# Create shell script in /tmp for Bazel actions to access CI envs without
# busting the cache. Used by payload-size.sh script in integration tests.
####################################################################################################
readonly bazelVarEnv="/tmp/bazel-ci-env.sh"
echo "# Setup by /.circle/env.sh" > $bazelVarEnv
echo "export PROJECT_ROOT=\"${PROJECT_ROOT}\";" >> $bazelVarEnv
echo "export CI_BRANCH=\"${CI_BRANCH}\";" >> $bazelVarEnv
echo "export CI_BUILD_URL=\"${CI_BUILD_URL}\";" >> $bazelVarEnv
echo "export CI_COMMIT=\"${CI_COMMIT}\";" >> $bazelVarEnv
echo "export CI_COMMIT_RANGE=\"${CI_COMMIT_RANGE}\";" >> $bazelVarEnv
echo "export CI_PULL_REQUEST=\"${CI_PULL_REQUEST}\";" >> $bazelVarEnv
echo "export CI_REPO_NAME=\"${CI_REPO_NAME}\";" >> $bazelVarEnv
echo "export CI_REPO_OWNER=\"${CI_REPO_OWNER}\";" >> $bazelVarEnv
echo "export CI_SECRET_PAYLOAD_FIREBASE_TOKEN=\"${CI_SECRET_PAYLOAD_FIREBASE_TOKEN}\";" >> $bazelVarEnv
####################################################################################################
####################################################################################################
## Source `$BASH_ENV` to make the variables available immediately. ##
## ***NOTE: This must remain the the last action in this script*** ##
####################################################################################################
####################################################################################################
# Source `$BASH_ENV` to make the variables available immediately.
source $BASH_ENV;

View File

@ -0,0 +1,166 @@
#!/usr/bin/env node
/**
* **Usage:**
* ```
* node get-commit-range <build-number> [<compare-url> [<circle-token>]]
* ```
*
* Returns the commit range, either extracting it from `compare-url` (if defined), which is of the
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
*
* > !!! WARNING !!!
* > !!
* > !! When [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) is enabled, the
* > !! `CIRCLE_COMPARE_URL` environment variable is not available at all and this script does not
* > !! work.
* > !!!!!!!!!!!!!!!
*
* **Context:**
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit
* range) on push builds (a.k.a. non-PR, non-scheduled builds). Yet, when a workflow is rerun
* (either from the beginning or from failed jobs) - e.g. when a job flakes - CircleCI does not set
* the `CIRCLE_COMPARE_URL`.
*
* **Implementation details:**
* This script relies on the fact that all rerun workflows share the same CircleCI workspace and the
* (undocumented) fact that the workspace ID happens to be the same as the workflow ID that first
* created it.
*
* For example, for a job on push build workflows, the CircleCI API will return data that look like:
* ```js
* {
* compare: 'THE_COMPARE_URL_WE_ARE_LOOKING_FOR',
* //...
* previous: {
* // ...
* build_num: 12345,
* },
* //...
* workflows: {
* //...
* workflow_id: 'SOME_ID_A',
* workspace_id: 'SOME_ID_A', // Same as `workflow_id`.
* }
* }
* ```
*
* If the workflow is rerun, the data for jobs on the new workflow will look like:
* ```js
* {
* compare: null, // ¯\_(ツ)_/¯
* //...
* previous: {
* // ...
* build_num: 23456,
* },
* //...
* workflows: {
* //...
* workflow_id: 'SOME_ID_B',
* workspace_id: 'SOME_ID_A', // Different from current `workflow_id`.
* // Same as original `workflow_id`. \o/
* }
* }
* ```
*
* This script uses the `previous.build_num` (which points to the previous build number on the same
* branch) to traverse the jobs backwards, until it finds a job from the original workflow. Such a
* job (if found) should also contain the compare URL.
*
* **NOTE 1:**
* This is only useful on workflows which are created by rerunning a workflow for which
* `CIRCLE_COMPARE_URL` was defined.
*
* **NOTE 2:**
* The `circleToken` will be used for CircleCI API requests if provided, but it is not needed for
* accessing the read-only endpoints that we need (as long as the current project is FOSS and the
* corresponding setting is turned on in "Advanced Settings" in the project dashboard).
*
* ---
* Inspired by https://circleci.com/orbs/registry/orb/iynere/compare-url
* (source code: https://github.com/iynere/compare-url-orb).
*
* We are not using the `compare-url` orb for the following reasons:
* 1. (By looking at the code) it would only work if the rerun workflow is the latest workflow on
* the branch (which is not guaranteed to be true).
* 2. It is less efficient (e.g. makes unnecessary CircleCI API requests for builds on different
* branches, installs extra dependencies, persists files to the workspace (as a means of passing
* the result to the calling job), etc.).
* 3. It is slightly more complicated to setup and consume than our own script.
* 4. Its implementation is more complicated than needed for our usecase (e.g. handles different git
* providers, handles newly created branches, etc.).
*/
// Imports
const {get: httpsGet} = require('https');
// Constants
const API_URL_BASE = 'https://circleci.com/api/v1.1/project/github/angular/angular';
const COMPARE_URL_RE = /^.*\/([0-9a-f]+\.\.\.[0-9a-f]+)$/i;
// Run
_main(process.argv.slice(2));
// Helpers
async function _main([buildNumber, compareUrl = '', circleToken = '']) {
try {
if (!buildNumber || isNaN(buildNumber)) {
throw new Error(
'Missing or invalid arguments.\n' +
'Expected: buildNumber (number), compareUrl? (string), circleToken? (string)');
}
if (!compareUrl) {
compareUrl = await getCompareUrl(buildNumber, circleToken);
}
const commitRangeMatch = COMPARE_URL_RE.exec(compareUrl)
const commitRange = commitRangeMatch ? commitRangeMatch[1] : '';
console.log(commitRange);
} catch (err) {
console.error(err);
process.exit(1);
}
}
function getBuildInfo(buildNumber, circleToken) {
console.error(`BUILD ${buildNumber}`);
const url = `${API_URL_BASE}/${buildNumber}?circle-token=${circleToken}`;
return getJson(url);
}
async function getCompareUrl(buildNumber, circleToken) {
let info = await getBuildInfo(buildNumber, circleToken);
const targetWorkflowId = info.workflows.workspace_id;
while (info.workflows.workflow_id !== targetWorkflowId) {
info = await getBuildInfo(info.previous.build_num, circleToken);
}
return info.compare || '';
}
function getJson(url) {
return new Promise((resolve, reject) => {
const opts = {headers: {Accept: 'application/json'}};
const onResponse = res => {
const statusCode = res.statusCode || -1;
const isSuccess = (200 <= statusCode) && (statusCode < 400);
let responseText = '';
res.
on('error', reject).
on('data', d => responseText += d).
on('end', () => isSuccess ?
resolve(JSON.parse(responseText)) :
reject(`Error getting '${url}' (status ${statusCode}):\n${responseText}`));
};
httpsGet(url, opts, onResponse).
on('error', reject).
end();
});
}

20
.circleci/setup-rbe.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# The path of the .bazelrc.user file to update should be passed as first parameter to this script.
# This allows to setup RBE for both the Angular repo and the Material repo.
bazelrc_user="$1"
echo "Writing RBE configuration to ${bazelrc_user}"
touch ${bazelrc_user}
echo -e 'build --config=remote\n' >> ${bazelrc_user}
echo -e 'build:remote --remote_accept_cached=true\n' >> ${bazelrc_user}
echo "Reading from remote cache for bazel remote jobs."
if [[ "$CI_PULL_REQUEST" == "false" ]]; then
echo -e 'build:remote --remote_upload_local_results=true\n' >> ${bazelrc_user}
echo "Uploading local build results to remote cache."
else
echo -e 'build:remote --remote_upload_local_results=false\n' >> ${bazelrc_user}
echo "Not uploading local build results to remote cache."
fi

View File

@ -60,12 +60,11 @@ if (require.resolve === module) {
// Helpers
function _main(args) {
triggerWebhook(...args)
.then(
({statusCode, responseText}) => (200 <= statusCode && statusCode < 400) ?
triggerWebhook(...args).
then(({statusCode, responseText}) => (200 <= statusCode && statusCode < 400) ?
console.log(`Status: ${statusCode}\n${responseText}`) :
Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`)))
.catch(err => {
Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))).
catch(err => {
console.error(err);
process.exit(1);
});
@ -78,12 +77,15 @@ function postJson(url, data) {
const statusCode = res.statusCode || -1;
let responseText = '';
res.on('error', reject)
.on('data', d => responseText += d)
.on('end', () => resolve({statusCode, responseText}));
res.
on('error', reject).
on('data', d => responseText += d).
on('end', () => resolve({statusCode, responseText}));
};
request(url, opts, onResponse).on('error', reject).end(JSON.stringify(data));
request(url, opts, onResponse).
on('error', reject).
end(JSON.stringify(data));
});
}

View File

@ -14,12 +14,12 @@ Add-Content $profile '$Env:path = "${Env:ProgramFiles}\nodejs\;C:\Users\circleci
# Environment variables for Bazel
Add-Content $profile '$Env:BAZEL_SH = "C:\tools\msys64\usr\bin\bash.exe"'
# Get the bazelisk version devdep and store it in a global var for use in the circleci job.
$bazeliskVersion = & ${Env:ProgramFiles}\nodejs\node.exe -e "console.log(require('./package.json').devDependencies['@bazel/bazelisk'])"
# This is a tricky situation: we want $bazeliskVersion to be evaluated but not $Env:BAZELISK_VERSION.
# Get the bazel version devdep and store it in a global var for use in the circleci job.
$bazelVersion = & ${Env:ProgramFiles}\nodejs\node.exe -e "console.log(require('./package.json').devDependencies['@bazel/bazel'])"
# This is a tricky situation: we want $bazelVersion to be evaluated but not $Env:BAZEL_VERSION.
# Formatting works https://stackoverflow.com/questions/32127583/expand-variable-inside-single-quotes
$bazeliskVersionGlobalVar = '$Env:BAZELISK_VERSION = "{0}"' -f $bazeliskVersion
Add-Content $profile $bazeliskVersionGlobalVar
$bazelVersionGlobalVar = '$Env:BAZEL_VERSION = "{0}"' -f $bazelVersion
Add-Content $profile $bazelVersionGlobalVar
# Remove the CircleCI checkout SSH override, because it breaks cloning repositories through Bazel.
# See https://circleci.com/gh/angular/angular/401454 for an example.
@ -27,23 +27,8 @@ Add-Content $profile $bazeliskVersionGlobalVar
git config --global --unset url.ssh://git@github.com.insteadOf
####################################################################################################
# Decrypt GCP Credentials and store them as the Google default credentials.
####################################################################################################
mkdir ${env:APPDATA}\gcloud
openssl aes-256-cbc -d -in .circleci\gcp_token -md md5 -out "$env:APPDATA\gcloud\application_default_credentials.json" -k "$env:CIRCLE_PROJECT_REPONAME"
####################################################################################################
# Set bazel configuration for CircleCI runs.
####################################################################################################
copy .circleci\bazel.windows.rc ${Env:USERPROFILE}\.bazelrc
####################################################################################################
# Install specific version of node.
####################################################################################################
choco install nodejs --version 12.14.1 --no-progress
# These Bazel prereqs aren't needed because the CircleCI image already includes them.
# choco install nodejs --version 10.16.0 --no-progress
# choco install yarn --version 1.16.0 --no-progress
# choco install vcredist2015 --version 14.0.24215.20170201

View File

@ -1,6 +1,5 @@
# Image metadata and config.
FROM circleci/node:10-browsers # Ideally, the image version should be what we use on CI.
# See `executors > browsers-executor` in `.circleci/config.yml`.
FROM circleci/node:10-browsers # This needs to be in sync with what we use on CI.
LABEL name="Angular dev environment" \
description="This image can be used to create a dev environment for building Angular." \
@ -16,8 +15,7 @@ USER root
# Configure `Node.js`/`npm` and install utilities.
RUN npm config --global set user root
RUN npm install --global yarn@latest # Ideally, the version should be what we use on CI.
# See `commands > overwrite_yarn` in `.circleci/config.yml`.
RUN npm install --global yarn@1.19.1 # This needs to be in sync with what we use on CI.
# Go! (And keep going.)

960
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,960 @@
# ==================================================================================
# ==================================================================================
# Angular CODEOWNERS
# ==================================================================================
# ==================================================================================
#
# Configuration of code ownership and review approvals for the angular/angular repo.
#
# More info: https://help.github.com/articles/about-codeowners/
#
# ================================================
# General rules / philosophy
# ================================================
#
# - we trust that people do the right thing and not approve changes they don't feel confident reviewing
# - we use github teams so that we funnel code reviews to the most appropriate reviewer, this is why the team structure is fine-grained
# - we enforce that only approved PRs get merged to ensure that unreviewed code doesn't get accidentally merged
# - we delegate approval rights as much as possible so that we can scale better
# - each group must have at least one person, but several people are preferable to avoid a single point of failure issues
# - most file groups have one or two global approvers groups as fallbacks:
# - @angular/fw-global-approvers: for approving minor changes, large-scale refactorings, and emergency situations.
# - @angular/fw-global-approvers-for-docs-only-changes: for approving minor documentation-only changes that don't require engineering review
# - a small number of file groups have very limited number of reviewers because incorrect changes to the files they guard would have serious consequences (e.g. security, public api)
#
# Configuration nuances:
#
# - This configuration works in conjunction with the protected branch settings that require all changes to be made via pull requests with at least one approval.
# - This approval can come from an appropriate codeowner, or any repo collaborator (person with write access) if the PR is authored by a codeowner.
# - Each codeowners team must have write access to the repo, otherwise their reviews won't count.
#
# In the case of emergency, the repo administrators which include angular-caretaker can bypass this requirement.
# ================================================
# GitHub username registry
# (just to make this file easier to understand)
# ================================================
# alan-agius4 - Alan Agius
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# AndrewKushnir - Andrew Kushnir
# andrewseguin - Andrew Seguin
# atscott - Andrew Scott
# devversion - Paul Gschwendtner
# filipesilva - Filipe Silva
# gkalpak - George Kalpakas
# IgorMinar - Igor Minar
# JiaLiPassion - Jia Li
# josephperrott - Joey Perrott
# kapunahelewong - Kapunahele Wong
# kara - Kara Erickson
# kyliau - Keen Yee Liau
# matsko - Matias Niemelä
# mgechev - Minko Gechev
# mhevery - Misko Hevery
# petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# stephenfluin - Stephen Fluin
# vikerman - Vikram Subramanian
######################################################################################################
#
# Team structure and memberships
# ------------------------------
#
# This section is here just because the GitHub UI is too hard to navigate and audit.
#
# Any changes to team structure or memberships must first be made in this file and only then
# implemented in the GitHub UI.
#######################################################################################################
# ===========================================================
# @angular/framework-global-approvers
# ===========================================================
# Used for approving minor changes, large-scale refactorings, and emergency situations.
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
#
# - IgorMinar
# - josephperrott
# - kara
# - mhevery
# ===========================================================
# @angular/framework-global-approvers-for-docs-only-changes
# ===========================================================
# Used for approving minor documentation-only changes that don't require engineering review.
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
#
# - gkalpak
# - kapunahelewong
# - petebacondarwin
# ===========================================================
# @angular/fw-animations
# ===========================================================
#
# - matsko
# ===========================================================
# @angular/tools-bazel
# ===========================================================
#
# - alexeagle
# - kyliau
# - IgorMinar
# - mgechev
# ===========================================================
# @angular/tools-cli
# ===========================================================
#
# - filipesilva
# - mgechev
# - vikerman
# ===========================================================
# @angular/fw-compiler
# ===========================================================
#
# - alxhub
# ===========================================================
# @angular/fw-ngcc
# ===========================================================
#
# - alxhub
# - gkalpak
# - petebacondarwin
# ===========================================================
# @angular/fw-core
# ===========================================================
#
# - alxhub
# - AndrewKushnir
# - kara
# - mhevery
# - pkozlowski-opensource
# ===========================================================
# @angular/fw-http
# ===========================================================
#
# - alxhub
# - IgorMinar
# ===========================================================
# @angular/fw-elements
# ===========================================================
#
# - andrewseguin
# - gkalpak
# - robwormald
# ===========================================================
# @angular/fw-forms
# ===========================================================
#
# - AndrewKushnir
# ===========================================================
# @angular/tools-language-service
# ===========================================================
#
# - kyliau
# ===========================================================
# @angular/fw-server
# ===========================================================
#
# - alxhub
# - vikerman
# ===========================================================
# @angular/fw-router
# ===========================================================
#
# - atscott
# ===========================================================
# @angular/fw-service-worker
# ===========================================================
#
# - alxhub
# - gkalpak
# - IgorMinar
# ===========================================================
# @angular/fw-upgrade
# ===========================================================
#
# - gkalpak
# - petebacondarwin
# ===========================================================
# @angular/fw-testing
# ===========================================================
#
# - vikerman
# ===========================================================
# @angular/fw-i18n
# ===========================================================
#
# - AndrewKushnir
# - mhevery
# - petebacondarwin
# - vikerman
# ===========================================================
# @angular/fw-security
# ===========================================================
#
# - IgorMinar
# - mhevery
# ===========================================================
# @angular/fw-zones
# ===========================================================
#
# - JiaLiPassion
# - mhevery
# - vikerman
# ===========================================================
# @angular/tools-benchpress
# ===========================================================
#
# - alxhub
# ===========================================================
# @angular/fw-integration
# ===========================================================
#
# - IgorMinar
# - josephperrott
# - mhevery
# ===========================================================
# @angular/docs-infra
# ===========================================================
#
# - gkalpak
# - IgorMinar
# - petebacondarwin
# ===========================================================
# @angular/fw-docs-intro
# ===========================================================
#
# - IgorMinar
# - stephenfluin
# ===========================================================
# @angular/fw-docs-observables
# ===========================================================
#
# - alxhub
# ===========================================================
# @angular/fw-docs-packaging
# ===========================================================
#
# - IgorMinar
# - vikerman
# ===========================================================
# @angular/tools-docs-libraries
# ===========================================================
#
# - alan-agius4
# - IgorMinar
# - mgechev
# - vikerman
# ===========================================================
# @angular/tools-docs-schematics
# ===========================================================
#
# - alan-agius4
# - IgorMinar
# - mgechev
# - vikerman
# ===========================================================
# @angular/fw-docs-marketing
# ===========================================================
#
# - IgorMinar
# - stephenfluin
# ===========================================================
# @angular/fw-public-api
# ===========================================================
#
# - IgorMinar
# ===========================================================
# @angular/dev-infra-framework
# ===========================================================
#
# - devversion
# - filipesilva
# - gkalpak
# - IgorMinar
# - josephperrott
######################################################################################################
#
# CODEOWNERS rules
# -----------------
#
# All the following rules are applied in the order specified in this file.
# The last rule that matches wins!
#
# See https://git-scm.com/docs/gitignore#_pattern_format for pattern syntax docs.
#
######################################################################################################
# ================================================
# Default Owners
# (in case no pattern matches a path in a PR - this should be treated as a bug and result in adding the path to CODEOWNERS)
# ================================================
* @IgorMinar @angular/framework-global-approvers
# ================================================
# Build, CI & Dev-infra Owners
# ================================================
/* @angular/dev-infra-framework
/.buildkite/** @angular/dev-infra-framework
/.circleci/** @angular/dev-infra-framework
/.devcontainer/** @angular/dev-infra-framework
/.github/** @angular/dev-infra-framework
/.vscode/** @angular/dev-infra-framework
/docs/BAZEL.md @angular/dev-infra-framework
/packages/* @angular/dev-infra-framework
/packages/examples/test-utils/** @angular/dev-infra-framework
/packages/private/** @angular/dev-infra-framework
/scripts/** @angular/dev-infra-framework
/third_party/** @angular/dev-infra-framework
/tools/build/** @angular/dev-infra-framework
/tools/cjs-jasmine/** @angular/dev-infra-framework
/tools/gulp-tasks/** @angular/dev-infra-framework
/tools/ngcontainer/** @angular/dev-infra-framework
/tools/npm/** @angular/dev-infra-framework
/tools/npm_workspace/** @angular/dev-infra-framework
/tools/public_api_guard/** @angular/dev-infra-framework
/tools/rxjs/** @angular/dev-infra-framework
/tools/size-tracking/** @angular/dev-infra-framework
/tools/source-map-test/** @angular/dev-infra-framework
/tools/symbol-extractor/** @angular/dev-infra-framework
/tools/testing/** @angular/dev-infra-framework
/tools/ts-api-guardian/** @angular/dev-infra-framework
/tools/tslint/** @angular/dev-infra-framework
/tools/validate-commit-message/** @angular/dev-infra-framework
/tools/yarn/** @angular/dev-infra-framework
/tools/* @angular/dev-infra-framework
*.BAZEL @angular/dev-infra-framework
*.bzl @angular/dev-infra-framework
# ================================================
# @angular/animations
# ================================================
/packages/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/platform-browser/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/complex-animation-sequences.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/reusable-animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/route-animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/transition-and-triggers.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/bazel
# ================================================
/packages/bazel/** @angular/tools-bazel @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/bazel.md @angular/tools-bazel @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/compiler
# @angular/compiler-cli
# ================================================
/packages/compiler/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/compiler/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/compiler-cli/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/angular-compiler-options.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/aot-compiler.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/aot-metadata-errors.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/template-typecheck.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# packages/compiler-cli/ngcc/
# ================================================
/packages/compiler-cli/ngcc/** @angular/fw-ngcc @angular/framework-global-approvers
/aio/content/guide/ngcc.md @angular/fw-ngcc @angular/framework-global-approvers
# ================================================
# Framework/cli integration
#
# a rule to control API changes between @angular/compiler-cli and @angular/cli
# ================================================
/packages/compiler-cli/src/ngtools/** @angular/tools-cli @angular/framework-global-approvers
/aio/content/guide/cli-builder.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ivy.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/web-worker.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/core
# @angular/common (except @angular/common/http)
# @angular/platform-browser
# @angular/platform-browser-dynamic
# @angular/platform-webworker
# @angular/platform-webworker-dynamic
# ================================================
/packages/core/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/core/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/platform-browser/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/platform-browser/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/platform-browser-dynamic/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/platform-webworker/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/platform-webworker-dynamic/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/common/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/docs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/accessibility.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/accessibility/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/architecture-components.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/architecture-modules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/architecture-next-steps.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/architecture-services.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/architecture.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/architecture/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/architecture/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/attribute-directives.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/bootstrapping.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/bootstrapping/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/cheatsheet.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/component-interaction.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/component-styles.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/component-styles/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dependency-injection.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dependency-injection-in-action.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dependency-injection-navtree.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dependency-injection-providers.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/displaying-data.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/displaying-data/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/displaying-data/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dynamic-component-loader.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/dynamic-component-loader/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/dynamic-component-loader/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/entry-components.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/feature-modules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/feature-modules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/feature-modules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/frequent-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/frequent-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/hierarchical-dependency-injection.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/hierarchical-dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/providers-viewproviders/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/resolution-modifiers/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/lazy-loading-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/lazy-loading-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/lazy-loading-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/lifecycle-hooks.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/lifecycle-hooks/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/lifecycle-hooks/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/ngcontainer/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ngmodule-api.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ngmodule-faq.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/ngmodule-faq/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ngmodule-vs-jsmodule.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/module-types.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/template-syntax.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/built-in-template-functions/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/event-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/interpolation/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/binding-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/property-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/attribute-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/two-way-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-reference-variables/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-expression-operators/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/pipes.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/providers.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/providers/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/singleton-services.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/set-document-title.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/set-document-title/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/set-document-title/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/sharing-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/structural-directives.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/user-input.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/user-input/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/user-input/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/common/http
# @angular/http
# ================================================
/packages/common/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/http.md @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/elements
# ================================================
/packages/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/elements.md @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/forms
# ================================================
/packages/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/forms.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/forms-overview.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/forms-overview/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/forms-overview/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/form-validation.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/form-validation/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/form-validation/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/dynamic-form.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/dynamic-form/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/dynamic-form/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/reactive-forms.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/reactive-forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/reactive-forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/language-service
# ================================================
/packages/language-service/** @angular/tools-language-service @angular/framework-global-approvers
/aio/content/guide/language-service.md @angular/tools-language-service @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/language-service/** @angular/tools-language-service @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/platform-server
# ================================================
/packages/platform-server/** @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/universal.md @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/universal/** @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/router
# ================================================
/packages/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/router.md @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/service-worker
# ================================================
/packages/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/service-worker-getting-started.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/service-worker-getting-started/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/app-shell.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/service-worker-communications.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/service-worker-config.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/service-worker-devops.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/service-worker-intro.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/upgrade
# ================================================
/packages/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/examples/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/upgrade.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/upgrade-lazy-load-ajs/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/upgrade-module/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/upgrade-phonecat-1-typescript/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/upgrade-phonecat-2-hybrid/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/upgrade-phonecat-3-final/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/upgrade-performance.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/upgrade-setup.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ajs-quick-reference.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/ajs-quick-reference/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular/**/testing
# ================================================
testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/testing.md @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular i18n
# ================================================
/packages/core/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/core/src/render3/i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/core/src/render3/i18n.md @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/core/src/render3/interfaces/i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/locales/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/src/pipes/date_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/src/pipes/i18n_plural_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/src/pipes/i18n_select_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/common/src/pipes/number_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/compiler/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/compiler/src/render3/view/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/compiler-cli/src/extract_i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/packages/localize/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/i18n.md @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# @angular security
# ================================================
/packages/core/src/sanitization/** @angular/fw-security
/packages/core/test/linker/security_integration_spec.ts @angular/fw-security
/packages/compiler/src/schema/** @angular/fw-security
/packages/platform-browser/src/security/** @angular/fw-security
/aio/content/guide/security.md @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/security/** @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/security/** @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# zone.js
# ================================================
/packages/zone.js/** @angular/fw-zones @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# benchpress
# ================================================
/packages/benchpress/** @angular/tools-benchpress @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# /integration/*
# ================================================
/integration/** @angular/fw-integration @angular/framework-global-approvers
# ================================================
# docs-infra
# ================================================
/aio/* @angular/docs-infra @angular/framework-global-approvers
/aio/aio-builds-setup/** @angular/docs-infra @angular/framework-global-approvers
/aio/content/examples/* @angular/docs-infra @angular/framework-global-approvers
/aio/scripts/** @angular/docs-infra @angular/framework-global-approvers
/aio/src/** @angular/docs-infra @angular/framework-global-approvers
/aio/tests/** @angular/docs-infra @angular/framework-global-approvers
/aio/tools/** @angular/docs-infra @angular/framework-global-approvers
# Hidden docs
/aio/content/guide/docs-style-guide.md @angular/docs-infra @angular/framework-global-approvers
/aio/content/examples/docs-style-guide/** @angular/docs-infra @angular/framework-global-approvers
/aio/content/images/guide/docs-style-guide/** @angular/docs-infra @angular/framework-global-approvers
/aio/content/guide/visual-studio-2015.md @angular/docs-infra @angular/framework-global-approvers
/aio/content/examples/visual-studio-2015/** @angular/docs-infra @angular/framework-global-approvers
# ================================================
# Docs: getting started & tutorial
# ================================================
/aio/content/guide/setup-local.md @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/setup-local/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/tutorial/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/toh/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt0/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt1/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt2/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt3/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt4/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt5/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/toh-pt6/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/getting-started-v0/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/start/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/start/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Docs: observables
# ================================================
/aio/content/guide/observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/comparing-observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/observables-in-angular.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/practical-observable-usage.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/practical-observable-usage/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/rx-library.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/rx-library/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Docs: packaging, tooling, releasing
# ================================================
/aio/content/guide/npm-packages.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/browser-support.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/typescript-configuration.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/setup/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/build.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/build/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/deployment.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/deployment/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/file-structure.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/deprecations.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-renderer.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-undecorated-classes.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-dynamic-flag.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-injectable.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-localize.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/migration-module-with-providers.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/updating-to-version-9.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ivy-compatibility.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/ivy-compatibility-examples.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Docs: libraries
# ================================================
/aio/content/guide/creating-libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/using-libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Docs: schematics
# ================================================
/aio/content/guide/schematics.md @angular/tools-docs-schematics @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/schematics-authoring.md @angular/tools-docs-schematics @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/schematics-for-libraries.md @angular/tools-docs-schematics @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/schematics/** @angular/tools-docs-schematics @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/schematics-for-libraries/** @angular/tools-docs-schematics @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Docs: marketing
# ================================================
/aio/content/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/bios/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/navigation.json @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/license.md @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# Material CI
# ================================================
/tools/material-ci/** @angular/fw-core @angular/framework-global-approvers
# ================================================
# Public API
# ================================================
/tools/public_api_guard/** @angular/fw-public-api
/aio/content/guide/glossary.md @angular/fw-public-api
/aio/content/guide/styleguide.md @angular/fw-public-api
/aio/content/examples/styleguide/** @angular/fw-public-api
/aio/content/images/guide/styleguide/** @angular/fw-public-api
# ================================================
# Special cases
# ================================================
/aio/content/guide/static-query-migration.md @kara @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
# ================================================
# CODEOWNERS Owners owners ...
# ================================================
/.github/CODEOWNERS @IgorMinar @angular/framework-global-approvers

View File

@ -32,7 +32,7 @@ Existing issues often contain information about workarounds, resolution, or prog
## 🔬 Minimal Reproduction
<!--
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-ivy
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-issue-repro2
-->
<!-- ✍️--> https://stackblitz.com/...

View File

@ -30,7 +30,7 @@ merge:
# text to show when the status is success
successDesc: "Does not affect google3"
# link to use for the details
url: "http://go/angular/g3sync"
url: "http://go/angular-g3sync"
# list of patterns to check for the files changed by the PR
# this list must be manually kept in sync with google3/third_party/javascript/angular2/copy.bara.sky
include:
@ -115,7 +115,6 @@ merge:
- "ci/angular: size"
- "cla/google"
- "google3"
- "pullapprove"
# the comment that will be added when the merge label is added despite failing checks, leave empty or set to false to disable

View File

@ -7,7 +7,6 @@ on:
jobs:
lock_closed:
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/lock-closed@66462f6

2
.gitignore vendored
View File

@ -3,8 +3,10 @@
/dist/
/bazel-out
/integration/bazel/bazel-*
e2e_test.*
*.log
node_modules
tools/gulp-tasks/cldr/cldr-data/
# Include when developing application packages.
pubspec.lock

View File

@ -1,118 +0,0 @@
import {MergeConfig} from '../dev-infra/pr/merge/config';
// The configuration for `ng-dev commit-message` commands.
const commitMessage = {
'maxLength': 120,
'minBodyLength': 100,
'minBodyLengthTypeExcludes': ['docs'],
'types': [
'build',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'release',
'style',
'test',
],
'scopes': [
'animations',
'bazel',
'benchpress',
'changelog',
'common',
'compiler',
'compiler-cli',
'core',
'dev-infra',
'docs-infra',
'elements',
'forms',
'http',
'language-service',
'localize',
'migrations',
'ngcc',
'packaging',
'platform-browser',
'platform-browser-dynamic',
'platform-server',
'platform-webworker',
'platform-webworker-dynamic',
'router',
'service-worker',
'upgrade',
've',
'zone.js',
]
};
// The configuration for `ng-dev format` commands.
const format = {
'clang-format': {
'matchers': [
'**/*.{js,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',
]
},
'buildifier': true
};
/** Github metadata information for `ng-dev` commands. */
const github = {
owner: 'angular',
name: 'angular',
};
// Configuration for the `ng-dev pr merge` command. The command can be used
// for merging upstream pull requests into branches based on a PR target label.
const merge = () => {
// TODO: resume dynamically determining patch branch
const patch = '10.0.x';
const config: MergeConfig = {
githubApiMerge: false,
claSignedLabel: 'cla: yes',
mergeReadyLabel: /^PR action: merge(-assistance)?/,
commitMessageFixupLabel: 'commit message fixup',
labels: [
{
pattern: 'PR target: master-only',
branches: ['master'],
},
{
pattern: 'PR target: patch-only',
branches: [patch],
},
{
pattern: 'PR target: master & patch',
branches: ['master', patch],
},
],
requiredBaseCommits: {
// PRs that target either `master` or the patch branch, need to be rebased
// on top of the latest commit message validation fix.
// These SHAs are the commits that update the required license text in the header.
'master': '5aeb9a4124922d8ac08eb73b8f322905a32b0b3a',
[patch]: '27b95ba64a5d99757f4042073fd1860e20e3ed24'
},
};
return config;
};
// Export function to build ng-dev configuration object.
module.exports = {
commitMessage,
format,
github,
merge,
};

2
.nvmrc
View File

@ -1 +1 @@
12.14.1
10.13.0

File diff suppressed because it is too large Load Diff

View File

@ -34,10 +34,10 @@
"name": "IVY:packages/core/test/acceptance",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/bazelisk",
"program": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test/acceptance",
"--config=debug"
],
@ -51,10 +51,10 @@
"name": "IVY:packages/core/test/render3",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/bazelisk",
"program": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test/render3",
"--config=debug"
],
@ -68,10 +68,10 @@
"name": "IVY:packages/core/test",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/bazelisk",
"program": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test",
"--config=debug"
],

View File

@ -6,10 +6,10 @@
{
"label": "IVY:packages/core/test/...",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test",
"packages/core/test/acceptance",
"packages/core/test/render3",
@ -23,7 +23,7 @@
{
"label": "VE:packages/core/test/...",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"packages/core/test",
@ -39,10 +39,10 @@
{
"label": "IVY:packages/core/test/acceptance",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test/acceptance",
],
"group": "test",
@ -54,7 +54,7 @@
{
"label": "VE:packages/core/test/acceptance",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"packages/core/test/acceptance",
@ -68,10 +68,10 @@
{
"label": "IVY:packages/core/test",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test",
],
"group": "test",
@ -83,7 +83,7 @@
{
"label": "VE:packages/core/test",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"packages/core/test",
@ -97,10 +97,10 @@
{
"label": "IVY:packages/core/test/render3",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/bazelisk",
"command": "${workspaceFolder}/node_modules/.bin/bazel",
"args": [
"test",
"--config=ivy",
"--define=compile=aot",
"packages/core/test/render3",
],
"group": "test",

View File

@ -1,13 +0,0 @@
# Yarn Vendoring
We utilize Yarn's `yarn-path` configuration in a shared `.yarnrc` file to enforce
everyone using the same version of Yarn. Yarn checks the `.yarnrc` file to
determine if yarn should delegate the command to a vendored version at the
provided path.
## How to update
To update to the latest version of Yarn as our vendored version:
- Run this command
```sh
yarn policies set-version latest
```
- Remove the previous version

View File

@ -1,5 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.4.js"

View File

@ -1,13 +1,12 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "karma_web_test")
exports_files([
"LICENSE",
"protractor-perf.conf.js",
"karma-js.conf.js",
"browser-providers.conf.js",
"scripts/ci/track-payload-size.sh",
"scripts/ci/payload-size.sh",
"scripts/ci/payload-size.js",
"package.json",
])
alias(
@ -24,7 +23,7 @@ filegroup(
"//packages/zone.js/dist:zone-testing.js",
"//packages/zone.js/dist:task-tracking.js",
"//:test-events.js",
"//:third_party/shims_for_IE.js",
"//:shims_for_IE.js",
# Including systemjs because it defines `__eval`, which produces correct stack traces.
"@npm//:node_modules/systemjs/dist/system.src.js",
"@npm//:node_modules/reflect-metadata/Reflect.js",
@ -47,3 +46,48 @@ filegroup(
"@npm//:node_modules/angular-mocks-1.6/angular-mocks.js",
],
)
# To run a karma_web_test target locally on SauceLabs:
# 1) have SAUCE_USERNAME, SAUCE_ACCESS_KEY (and optionally a SAUCE_TUNNEL_IDENTIFIER) set in your environment
# 2) open a sauce connection with `./scripts/saucelabs/start-tunnel.sh`
# NOTE: start-tunnel.sh uses `node_modules/sauce-connect` which is current linux specific:
# "sauce-connect": "https://saucelabs.com/downloads/sc-4.5.3-linux.tar.gz".
# On OSX or Windows you'll need to use the appropriate sauce-connect binary.
# 3) run target with `yarn bazel test --config=saucelabs <target>`
# NOTE: --config=saucelabs is required as it makes the SAUCE_XXX environment variables available to
# the action. See /.bazelrc.
karma_web_test(
name = "test_web_all",
tags = [
"local",
"manual",
"saucelabs",
],
deps = [
# We combine all tests into a single karma_web_test target
# as running them as seperate targets in parallel leads to too many
# browsers being acquired at once in SauceLabs and the tests flake out
# TODO: this is an example subset of tests below, add all remaining angular tests
"//packages/common/http/test:test_lib",
"//packages/common/http/testing/test:test_lib",
"//packages/common/test:test_lib",
"//packages/core/test:test_lib",
"//packages/forms/test:test_lib",
"//packages/http/test:test_lib",
"//packages/zone.js/test:karma_jasmine_test_ci",
# "//packages/router/test:test_lib",
# //packages/router/test:test_lib fails with:
# IE 11.0.0 (Windows 8.1.0.0) bootstrap should restore the scrolling position FAILED
# Expected undefined to equal 5000.
# at stack (eval code:2338:11)
# at buildExpectationResult (eval code:2305:5)
# at expectationResultFactory (eval code:858:11)
# at Spec.prototype.addExpectationResult (eval code:487:5)
# at addExpectationResult (eval code:802:9)
# at Anonymous function (eval code:2252:7)
# at Anonymous function (eval code:339:25)
# at step (eval code:133:17)
# at Anonymous function (eval code:114:50)
# at fulfilled (eval code:104:47)
],
)

File diff suppressed because it is too large Load Diff

View File

@ -42,9 +42,7 @@ Please consider what kind of change it is:
* For a **Major Feature**, first open an issue and outline your proposal so that it can be
discussed. This will also allow us to better coordinate our efforts, prevent duplication of work,
and help you to craft the change so that it is successfully accepted into the project. **Note**:
Adding a new topic to the documentation, or significantly re-writing a topic, counts as a major
feature.
and help you to craft the change so that it is successfully accepted into the project.
* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
## <a name="submit"></a> Submission Guidelines
@ -57,9 +55,9 @@ We want to fix all the issues as soon as possible, but before fixing a bug we ne
A minimal reproduction allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem.
We will be insisting on a minimal reproduction scenario in order to save maintainers' time and ultimately be able to fix more bugs. Interestingly, from our experience, users often find coding problems themselves while preparing a minimal reproduction. We understand that sometimes it might be hard to extract essential bits of code from a larger codebase but we really need to isolate the problem before we can fix it.
We will be insisting on a minimal reproduction scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal reproduction. We understand that sometimes it might be hard to extract essential bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you, we are going to close an issue that doesn't have enough info to be reproduced.
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
You can file new issues by selecting from our [new issue templates](https://github.com/angular/angular/issues/new/choose) and filling out the issue template.
@ -85,7 +83,8 @@ Before you submit your Pull Request (PR) consider the following guidelines:
1. Run the full Angular test suite, as described in the [developer documentation][dev-doc],
and ensure that all tests pass.
1. Commit your changes using a descriptive commit message that follows our
[commit message conventions](#commit). Adherence to these conventions is necessary because release notes are automatically generated from these messages.
[commit message conventions](#commit). Adherence to these conventions
is necessary because release notes are automatically generated from these messages.
```shell
git commit -a
@ -169,7 +168,7 @@ format that includes a **type**, a **scope** and a **subject**:
The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer than 100 characters! This allows the message to be easier
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
@ -180,13 +179,13 @@ Samples: (even more [samples](https://github.com/angular/angular/commits/master)
docs(changelog): update changelog to beta.5
```
```
fix(release): need to depend on the latest rxjs and zone.js
fix(release): need to depend on latest rxjs and zone.js
The version in our package.json gets copied to the one we publish, and users need the latest of these.
```
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
Must be one of the following:
@ -207,8 +206,6 @@ The scope should be the name of the npm package affected (as perceived by the pe
The following is the list of supported scopes:
* **animations**
* **bazel**
* **benchpress**
* **common**
* **compiler**
* **compiler-cli**
@ -217,7 +214,6 @@ The following is the list of supported scopes:
* **forms**
* **http**
* **language-service**
* **localize**
* **platform-browser**
* **platform-browser-dynamic**
* **platform-server**
@ -236,10 +232,8 @@ There are currently a few exceptions to the "use package name" rule:
* **changelog**: used for updating the release notes in CHANGELOG.md
* **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
repo
* **dev-infra**: used for dev-infra related changes within the directories /scripts, /tools and /dev-infra
* **migrations**: used for changes to the `ng update` migrations.
* **ivy**: used for changes to the [Ivy renderer](https://github.com/angular/angular/issues/21706).
* **ngcc**: used for changes to the [Angular Compatibility Compiler](./packages/compiler-cli/ngcc/README.md)
* **ve**: used for changes specific to ViewEngine (legacy compiler/renderer).
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all
packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a
specific package (e.g. `docs: fix typo in tutorial`).
@ -268,8 +262,8 @@ A detailed explanation can be found in this [document][commit-message-format].
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code
changes to be accepted, the CLA must be signed. It's a quick process, we promise!
* For individuals, we have a [simple click-through form][individual-cla].
* For corporations, we'll need you to
* For individuals we have a [simple click-through form][individual-cla].
* For corporations we'll need you to
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
<hr>
@ -281,10 +275,11 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
* https://help.github.com/articles/about-commit-email-addresses/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA before you commit changes. If not, your PR will fail the CLA check.
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
<hr>
[angular-group]: https://groups.google.com/forum/#!forum/angular
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2020 Google LLC. http://angular.io/license
Copyright (c) 2010-2019 Google LLC. http://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

View File

@ -1,4 +1,5 @@
[![CircleCI](https://circleci.com/gh/angular/angular/tree/master.svg?style=shield)](https://circleci.com/gh/angular/workflows/angular/tree/master)
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06)](https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06)
[![Join the chat at https://gitter.im/angular/angular](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://www.npmjs.com/@angular/core)
@ -20,6 +21,7 @@ Angular is a development platform for building mobile and desktop web applicatio
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
[browserstack]: https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06
[contributing]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md
[quickstart]: https://angular.io/start
[changelog]: https://github.com/angular/angular/blob/master/CHANGELOG.md

View File

@ -8,33 +8,69 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Fetch rules_nodejs so we can install our npm dependencies
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "84abf7ac4234a70924628baa9a73a5a5cbad944c4358cf9abdb4aab29c9a5b77",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.7.0/rules_nodejs-1.7.0.tar.gz"],
sha256 = "9901bc17138a79135048fb0c107ee7a56e91815ec6594c08cb9a17b80276d62b",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.40.0/rules_nodejs-0.40.0.tar.gz"],
)
# Check the rules_nodejs version and download npm dependencies
# Note: bazel (version 2 and after) will check the .bazelversion file so we don't need to
# assert on that.
load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install")
# Check the bazel version and download npm dependencies
load("@build_bazel_rules_nodejs//:index.bzl", "check_bazel_version", "check_rules_nodejs_version", "node_repositories", "yarn_install")
check_rules_nodejs_version(minimum_version_string = "1.7.0")
# Bazel version must be at least the following version because:
# - 0.26.0 managed_directories feature added which is required for nodejs rules 0.30.0
# - 0.27.0 has a fix for managed_directories after `rm -rf node_modules`
check_bazel_version(
message = """
You no longer need to install Bazel on your machine.
Angular has a dependency on the @bazel/bazel package which supplies it.
Try running `yarn bazel` instead.
(If you did run that, check that you've got a fresh `yarn install`)
""",
minimum_bazel_version = "0.27.0",
)
# The NodeJS rules version must be at least the following version because:
# - 0.15.2 Re-introduced the prod_only attribute on yarn_install
# - 0.15.3 Includes a fix for the `jasmine_node_test` rule ignoring target tags
# - 0.16.8 Supports npm installed bazel workspaces
# - 0.26.0 Fix for data files in yarn_install and npm_install
# - 0.27.12 Adds NodeModuleSources provider for transtive npm deps support
# - 0.30.0 yarn_install now uses symlinked node_modules with new managed directories Bazel 0.26.0 feature
# - 0.31.1 entry_point attribute of nodejs_binary & rollup_bundle is now a label
# - 0.32.0 yarn_install and npm_install no longer puts build files under symlinked node_modules
# - 0.32.1 remove override of @bazel/tsetse & exclude typescript lib declarations in node_module_library transitive_declarations
# - 0.32.2 resolves bug in @bazel/hide-bazel-files postinstall step
# - 0.34.0 introduces protractor rule
# - 0.37.1 windows fixes
# - 0.38.2 Adds NpmPackageInfo & JSNamedModuleInfo providers
# - 0.38.3 all providers loaded from //:providers.bzl
check_rules_nodejs_version(minimum_version_string = "0.38.3")
# Setup the Node.js toolchain
node_repositories(
node_repositories = {
"12.14.1-darwin_amd64": ("node-v12.14.1-darwin-x64.tar.gz", "node-v12.14.1-darwin-x64", "0be10a28737527a1e5e3784d3ad844d742fe8b0718acd701fd48f718fd3af78f"),
"12.14.1-linux_amd64": ("node-v12.14.1-linux-x64.tar.xz", "node-v12.14.1-linux-x64", "07cfcaa0aa9d0fcb6e99725408d9e0b07be03b844701588e3ab5dbc395b98e1b"),
"12.14.1-windows_amd64": ("node-v12.14.1-win-x64.zip", "node-v12.14.1-win-x64", "1f96ccce3ba045ecea3f458e189500adb90b8bc1a34de5d82fc10a5bf66ce7e3"),
"10.16.0-darwin_amd64": ("node-v10.16.0-darwin-x64.tar.gz", "node-v10.16.0-darwin-x64", "6c009df1b724026d84ae9a838c5b382662e30f6c5563a0995532f2bece39fa9c"),
"10.16.0-linux_amd64": ("node-v10.16.0-linux-x64.tar.xz", "node-v10.16.0-linux-x64", "1827f5b99084740234de0c506f4dd2202a696ed60f76059696747c34339b9d48"),
"10.16.0-windows_amd64": ("node-v10.16.0-win-x64.zip", "node-v10.16.0-win-x64", "aa22cb357f0fb54ccbc06b19b60e37eefea5d7dd9940912675d3ed988bf9a059"),
},
node_version = "12.14.1",
node_version = "10.16.0",
package_json = ["//:package.json"],
yarn_repositories = {
"1.17.3": ("yarn-v1.17.3.tar.gz", "yarn-v1.17.3", "e3835194409f1b3afa1c62ca82f561f1c29d26580c9e220c36866317e043c6f3"),
},
# yarn 1.13.0 under Bazel has a regression on Windows that causes build errors on rebuilds:
# ```
# ERROR: Source forest creation failed: C:/.../fyuc5c3n/execroot/angular/external (Directory not empty)
# ```
# See https://github.com/angular/angular/pull/29431 for more information.
# It possible that versions of yarn past 1.13.0 do not have this issue, however, before
# advancing this version we need to test manually on Windows that the above error does not
# happen as the issue is not caught by CI.
yarn_version = "1.17.3",
)
load("//integration:angular_integration_test.bzl", "npm_package_archives")
yarn_install(
name = "npm",
manual_build_file_contents = npm_package_archives(),
package_json = "//:package.json",
yarn_lock = "//:yarn.lock",
)
@ -64,7 +100,7 @@ load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories"
web_test_repositories()
load("//dev-infra/browsers:browser_repositories.bzl", "browser_repositories")
load("//tools/browsers:browser_repositories.bzl", "browser_repositories")
browser_repositories()
@ -91,18 +127,17 @@ rbe_autoconfig(
# Need to specify a base container digest in order to ensure that we can use the checked-in
# platform configurations for the "ubuntu16_04" image. Otherwise the autoconfig rule would
# need to pull the image and run it in order determine the toolchain configuration. See:
# https://github.com/bazelbuild/bazel-toolchains/blob/3.2.0/configs/ubuntu16_04_clang/versions.bzl
base_container_digest = "sha256:5e750dd878df9fcf4e185c6f52b9826090f6e532b097f286913a428290622332",
# https://github.com/bazelbuild/bazel-toolchains/blob/1.1.2/configs/ubuntu16_04_clang/versions.bzl
base_container_digest = "sha256:1ab40405810effefa0b2f45824d6d608634ccddbf06366760c341ef6fbead011",
# Note that if you change the `digest`, you might also need to update the
# `base_container_digest` to make sure marketplace.gcr.io/google/rbe-ubuntu16-04-webtest:<digest>
# and marketplace.gcr.io/google/rbe-ubuntu16-04:<base_container_digest> have
# the same Clang and JDK installed. Clang is needed because of the dependency on
# @com_google_protobuf. Java is needed for the Bazel's test executor Java tool.
digest = "sha256:f743114235a43355bf8324e2ba0fa6a597236fe06f7bc99aaa9ac703631c306b",
digest = "sha256:0b8fa87db4b8e5366717a7164342a029d1348d2feea7ecc4b18c780bc2507059",
env = clang_env(),
registry = "marketplace.gcr.io",
# We can't use the default "ubuntu16_04" RBE image provided by the autoconfig because we need
# a specific Linux kernel that comes with "libx11" in order to run headless browser tests.
repository = "google/rbe-ubuntu16-04-webtest",
use_checked_in_confs = "Force",
)

View File

@ -18,8 +18,8 @@ Here are the most important tasks you might need to use:
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
* `yarn build-local-with-viewengine` - same as `build-local`, but in addition also turns on `ViewEngine` (pre-Ivy) mode in aio.
(Note: To turn on `ViewEngine` mode in docs examples, see `yarn boilerplate:add:viewengine` below.)
* `yarn build-local-with-viewengine` - same as `build-local`, but in addition also turns on `ViewEngine` mode in aio.
(Note: Docs examples run in `ViewEngine` mode by default. To turn on `ivy` mode in examples, see `yarn boilerplate:add` below.)
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
@ -34,7 +34,7 @@ Here are the most important tasks you might need to use:
* `yarn docs-test` - run the unit tests for the doc generation code.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
* `yarn boilerplate:add:viewengine` - same as `boilerplate:add` but also turns on `ViewEngine` (pre-Ivy) mode.
* `yarn boilerplate:add:ivy` - same as `boilerplate:add` but also turns on `ivy` mode.
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
@ -44,7 +44,7 @@ Here are the most important tasks you might need to use:
- `--setup`: generate boilerplate, force webdriver update & other setup, then run tests.
- `--local`: run e2e tests with the local version of Angular contained in the "dist" folder.
_Requires `--setup` in order to take effect._
- `--viewengine`: run e2e tests in `ViewEngine` (pre-Ivy) mode.
- `--ivy`: run e2e tests in `ivy` mode.
- `--filter=foo`: limit e2e tests to those containing the word "foo".
> **Note for Windows users**
@ -104,7 +104,7 @@ You also want to see those changes displayed properly in the doc viewer
with a quick, edit/view cycle time.
For this purpose, use the `yarn docs-watch` task, which watches for changes to source files and only
re-processes the files necessary to generate the docs that are related to the file that has changed.
re-processes the the files necessary to generate the docs that are related to the file that has changed.
Since this task takes shortcuts, it is much faster (often less than 1 second) but it won't produce full
fidelity content. For example, links to other docs and code examples may not render correctly. This is
most particularly noticed in links to other docs and in the embedded examples, which may not always render

View File

@ -1,5 +1,5 @@
# Image metadata and config
FROM debian:buster
FROM debian:stretch
LABEL name="angular.io PR preview" \
description="This image implements the PR preview functionality for angular.io." \
@ -37,9 +37,9 @@ ARG TEST_AIO_NGINX_PORT_HTTPS=4433
ARG AIO_SIGNIFICANT_FILES_PATTERN='^(?:aio|packages)/(?!.*[._]spec\\.[jt]s$)'
ARG TEST_AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN
ARG AIO_TRUSTED_PR_LABEL="aio: preview"
ARG TEST_AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL
ARG TEST_AIO_TRUSTED_PR_LABEL="aio: preview"
ARG AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG TEST_AIO_PREVIEW_SERVER_HOSTNAME=$AIO_PREVIEW_SERVER_HOSTNAME
ARG TEST_AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG AIO_ARTIFACT_MAX_SIZE=26214400
ARG TEST_AIO_ARTIFACT_MAX_SIZE=200
ARG AIO_PREVIEW_SERVER_PORT=3000
@ -72,29 +72,24 @@ RUN mkdir /var/log/aio
# Add extra package sources
RUN apt-get update -y && apt-get install -y curl=7.64.0-4+deb10u1
RUN curl --silent --show-error --location https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update -y && apt-get install -y curl
RUN curl --silent --show-error --location https://deb.nodesource.com/setup_10.x | bash -
RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN echo "deb http://ftp.debian.org/debian stretch-backports main" | tee /etc/apt/sources.list.d/backports.list
# Install packages
# NOTE: Some packages (such as `nginx`, `nodejs`, `openssl`) make older versions unavailable on the
# repositories, so we cannot pin to specific versions for these packages :(
# See for example:
# - https://github.com/nodesource/distributions/issues/33
# - https://askubuntu.com/questions/715104/how-can-i-downgrade-openssl-via-apt-get
RUN apt-get update -y && apt-get install -y \
cron=3.0pl1-134+deb10u1 \
dnsmasq=2.80-1 \
nano=3.2-3 \
nginx \
nodejs \
openssl \
rsyslog=8.1901.0-1 \
vim=2:8.1.0875-5 \
yarn=1.22.4-1
RUN yarn global add pm2@4.4.0
cron=3.0pl1-128+deb9u1 \
dnsmasq=2.76-5+deb9u2 \
nano=2.7.4-1 \
nginx=1.10.3-1+deb9u2 \
nodejs=10.15.3-1nodesource1 \
openssl=1.1.0j-1~deb9u1 \
rsyslog=8.24.0-1 \
yarn=1.15.2-1
RUN yarn global add pm2@3.5.0
# Set up log rotation
@ -167,7 +162,8 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
# Set up the Node.js scripts
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
RUN yarn --cwd="$AIO_SCRIPTS_JS_DIR/" install --production --frozen-lockfile
WORKDIR $AIO_SCRIPTS_JS_DIR/
RUN yarn install --production --frozen-lockfile
# Set up health check

View File

@ -35,7 +35,6 @@ export class BuildCleaner {
]);
} catch (error) {
this.logger.error('ERROR:', error);
throw error;
}
}

View File

@ -1,4 +1,5 @@
import * as express from 'express';
import {promisify} from 'util';
import {PreviewServerError} from './preview-error';
/**
@ -12,7 +13,7 @@ export async function respondWithError(res: express.Response, err: any): Promise
}
res.status(err.status);
return new Promise(resolve => res.end(err.message, resolve));
await promisify(res.end.bind(res))(err.message);
}
/**

View File

@ -93,7 +93,7 @@ class Helper {
return fs.readFileSync(absFilePath, 'utf8');
}
public runCmd(cmd: string, opts: cp.ExecOptions = {}): Promise<CmdResult> {
public runCmd(cmd: string, opts: cp.ExecFileOptions = {}): Promise<CmdResult> {
return new Promise(resolve => {
const proc = cp.exec(cmd, opts, (err, stdout, stderr) => resolve({success: !err, err, stdout, stderr}));
this.createCleanUpFn(() => proc.kill());
@ -101,7 +101,7 @@ class Helper {
}
public runForAllSupportedSchemes(suiteFactory: TestSuiteFactory): void {
Object.entries(this.portPerScheme).forEach(([scheme, port]) => suiteFactory(scheme, port));
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
}
public verifyResponse(status: number, regex: string | RegExp = /^/): VerifyCmdResultFn {

View File

@ -15,7 +15,7 @@ describe(`nginx`, () => {
afterEach(() => h.cleanUp());
it('should redirect HTTP to HTTPS', async () => {
it('should redirect HTTP to HTTPS', done => {
const httpHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTP}`;
const httpsHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTPS}`;
const urlMap = {
@ -24,15 +24,16 @@ describe(`nginx`, () => {
[`http://foo.${httpHost}/`]: `https://foo.${httpsHost}/`,
};
const verifyRedirection = async (fromUrl: string, toUrl: string) => {
const result = await h.runCmd(`curl -i ${fromUrl}`);
const verifyRedirection = (httpUrl: string) => h.runCmd(`curl -i ${httpUrl}`).then(result => {
h.verifyResponse(307)(result);
const headers = result.stdout.split(/(?:\r?\n){2,}/)[0];
expect(headers).toContain(`Location: ${toUrl}`);
};
expect(headers).toContain(`Location: ${urlMap[httpUrl]}`);
});
await Promise.all(Object.entries(urlMap).map(urls => verifyRedirection(...urls)));
Promise.
all(Object.keys(urlMap).map(verifyRedirection)).
then(done);
});
@ -61,15 +62,15 @@ describe(`nginx`, () => {
});
it('should return /index.html', async () => {
it('should return /index.html', done => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
await Promise.all([
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)),
]);
]).then(done);
});
@ -89,11 +90,12 @@ describe(`nginx`, () => {
});
it('should return /foo/bar.js', async () => {
it('should return /foo/bar.js', done => {
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`);
await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex));
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex)).
then(done);
});
@ -109,46 +111,47 @@ describe(`nginx`, () => {
});
it('should respond with 403 for directories', async () => {
await Promise.all([
it('should respond with 403 for directories', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/`).then(h.verifyResponse(403)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo`).then(h.verifyResponse(403)),
]);
]).then(done);
});
it('should respond with 404 for unknown paths to files', async () => {
await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`).
then(h.verifyResponse(404));
it('should respond with 404 for unknown paths to files', done => {
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`).
then(h.verifyResponse(404)).
then(done);
});
it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', async () => {
it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', done => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
await Promise.all([
Promise.all([
h.runCmd(`curl -iL ${origin}/foo/baz`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)),
]);
]).then(done);
});
it('should respond with 404 for unknown PRs/SHAs', async () => {
it('should respond with 404 for unknown PRs/SHAs', done => {
const otherPr = 54321;
const otherShortSha = computeShortSha('8'.repeat(40));
await Promise.all([
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}9-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}9.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherShortSha}.${host}`).then(h.verifyResponse(404)),
]);
]).then(done);
});
it('should respond with 404 if the subdomain format is wrong', async () => {
await Promise.all([
it('should respond with 404 if the subdomain format is wrong', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://xpr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://prx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://xx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
@ -157,25 +160,26 @@ describe(`nginx`, () => {
h.runCmd(`curl -iL ${scheme}://${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}_${shortSha9}.${host}`).then(h.verifyResponse(404)),
]);
]).then(done);
});
it('should reject PRs with leading zeros', async () => {
await h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`).
then(h.verifyResponse(404));
it('should reject PRs with leading zeros', done => {
h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`).
then(h.verifyResponse(404)).
then(done);
});
it('should accept SHAs with leading zeros (but not trim the zeros)', async () => {
it('should accept SHAs with leading zeros (but not trim the zeros)', done => {
const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`);
await Promise.all([
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-0${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)),
]);
]).then(done);
});
});
@ -227,23 +231,23 @@ describe(`nginx`, () => {
describe(`${host}/health-check`, () => {
it('should respond with 200', async () => {
await Promise.all([
it('should respond with 200', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://${host}/health-check`).then(h.verifyResponse(200)),
h.runCmd(`curl -iL ${scheme}://${host}/health-check/`).then(h.verifyResponse(200)),
]);
]).then(done);
});
it('should respond with 404 if the path does not match exactly', async () => {
await Promise.all([
it('should respond with 404 if the path does not match exactly', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://${host}/health-check/foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/health-check-foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/health-checknfoo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foo/health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foo-health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foonhealth-check`).then(h.verifyResponse(404)),
]);
]).then(done);
});
});
@ -287,28 +291,29 @@ describe(`nginx`, () => {
describe(`${host}/circle-build`, () => {
it('should disallow non-POST requests', async () => {
it('should disallow non-POST requests', done => {
const url = `${scheme}://${host}/circle-build`;
await Promise.all([
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]);
]).then(done);
});
it('should pass requests through to the preview server', async () => {
await h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`).
then(h.verifyResponse(400, /Incorrect body content. Expected JSON/));
it('should pass requests through to the preview server', done => {
h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`).
then(h.verifyResponse(400, /Incorrect body content. Expected JSON/)).
then(done);
});
it('should respond with 404 for unknown paths', async () => {
it('should respond with 404 for unknown paths', done => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
await Promise.all([
Promise.all([
h.runCmd(`${cmdPrefix}/foo/circle-build/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-circle-build/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/fooncircle-build/`).then(h.verifyResponse(404)),
@ -317,7 +322,7 @@ describe(`nginx`, () => {
h.runCmd(`${cmdPrefix}/circle-buildnfoo/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/circle-build/pr`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
]);
]).then(done);
});
});
@ -327,33 +332,38 @@ describe(`nginx`, () => {
const url = `${scheme}://${host}/pr-updated`;
it('should disallow non-POST requests', async () => {
await Promise.all([
it('should disallow non-POST requests', done => {
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]);
]).then(done);
});
it('should pass requests through to the preview server', async () => {
await h.runCmd(`curl -iLX POST --header "Content-Type: application/json" ${url}`).
then(h.verifyResponse(400, /Missing or empty 'number' field/));
it('should pass requests through to the preview server', done => {
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`;
const cmd1 = `${cmdPrefix} ${url}`;
Promise.all([
h.runCmd(cmd1).then(h.verifyResponse(400, /Missing or empty 'number' field/)),
]).then(done);
});
it('should respond with 404 for unknown paths', async () => {
it('should respond with 404 for unknown paths', done => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
await Promise.all([
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]);
]).then(done);
});
});
@ -364,7 +374,7 @@ describe(`nginx`, () => {
beforeEach(() => {
['index.html', 'foo.js', 'foo/index.html'].forEach(relFilePath => {
const absFilePath = path.join(AIO_BUILDS_DIR, relFilePath);
h.writeFile(absFilePath, {content: `File: /${relFilePath}`});
return h.writeFile(absFilePath, {content: `File: /${relFilePath}`});
});
});

View File

@ -105,8 +105,8 @@ describe('preview-server', () => {
describe(`${host}/circle-build`, () => {
const curl = makeCurl(`${host}/circle-build`);
const curl = makeCurl(`${host}/circle-build`);
it('should disallow non-POST requests', async () => {
const bodyRegex = /^Unknown resource/;
@ -189,7 +189,8 @@ describe('preview-server', () => {
});
it('should respond with 201 if a new public build is created', async () => {
await curl(payload(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).then(h.verifyResponse(201));
await curl(payload(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER))
.then(h.verifyResponse(201));
expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER }).toExistAsABuild();
});
@ -198,7 +199,7 @@ describe('preview-server', () => {
expect({ prNum: PrNums.TRUST_CHECK_UNTRUSTED, isPublic: false }).toExistAsABuild();
});
[true, false].forEach(isPublic => {
[true].forEach(isPublic => {
const build = isPublic ? BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER : BuildNums.TRUST_CHECK_UNTRUSTED;
const prNum = isPublic ? PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER : PrNums.TRUST_CHECK_UNTRUSTED;
const label = isPublic ? 'public' : 'non-public';
@ -363,23 +364,23 @@ describe('preview-server', () => {
describe(`${host}/health-check`, () => {
it('should respond with 200', async () => {
await Promise.all([
it('should respond with 200', done => {
Promise.all([
h.runCmd(`curl -iL ${host}/health-check`).then(h.verifyResponse(200)),
h.runCmd(`curl -iL ${host}/health-check/`).then(h.verifyResponse(200)),
]);
]).then(done);
});
it('should respond with 404 if the path does not match exactly', async () => {
await Promise.all([
it('should respond with 404 if the path does not match exactly', done => {
Promise.all([
h.runCmd(`curl -iL ${host}/health-check/foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/health-check-foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/health-checknfoo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foo/health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foo-health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foonhealth-check`).then(h.verifyResponse(404)),
]);
]).then(done);
});
});
@ -425,18 +426,18 @@ describe('preview-server', () => {
});
it('should respond with 404 for unknown paths', async () => {
it('should respond with 404 for unknown paths', done => {
const mockPayload = JSON.stringify({number: 1}); // MockExternalApiFlags.TRUST_CHECK_ACTIVE_TRUSTED_USER });
const cmdPrefix = `curl -iLX POST --data "${mockPayload}" ${host}`;
await Promise.all([
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]);
]).then(done);
});
@ -550,10 +551,10 @@ describe('preview-server', () => {
describe(`${host}/*`, () => {
it('should respond with 404 for requests to unknown URLs', async () => {
it('should respond with 404 for requests to unknown URLs', done => {
const bodyRegex = /^Unknown resource/;
await Promise.all([
Promise.all([
h.runCmd(`curl -iL ${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL ${host}/`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL ${host}`).then(h.verifyResponse(404, bodyRegex)),
@ -561,7 +562,7 @@ describe('preview-server', () => {
h.runCmd(`curl -iLX POST ${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${host}`).then(h.verifyResponse(404, bodyRegex)),
]);
]).then(done);
});
});

View File

@ -14,41 +14,42 @@
"predev": "yarn build || true",
"dev": "run-p ~~build-watch ~~test-watch",
"lint": "tslint --project tsconfig.json",
"pretest": "run-s build lint",
"pretest": "yarn build",
"test": "yarn ~~test-only",
"pretest-watch": "yarn pretest",
"test-watch": "yarn ~~test-watch",
"~~build": "tsc",
"~~build-watch": "yarn ~~build --watch",
"pre~~test-only": "yarn lint",
"~~test-only": "node dist/test",
"~~test-watch": "nodemon --delay 1 --exec \"yarn ~~test-only\" --watch dist"
},
"dependencies": {
"body-parser": "^1.19.0",
"delete-empty": "^3.0.0",
"express": "^4.17.1",
"jasmine": "^3.5.0",
"nock": "^12.0.3",
"node-fetch": "^2.6.0",
"shelljs": "^0.8.4",
"source-map-support": "^0.5.19",
"tar-stream": "^2.1.2",
"tslib": "^1.11.1"
"body-parser": "^1.18.3",
"delete-empty": "^2.0.0",
"express": "^4.16.3",
"jasmine": "^3.2.0",
"nock": "^9.6.1",
"node-fetch": "^2.2.0",
"shelljs": "^0.8.2",
"source-map-support": "^0.5.9",
"tar-stream": "^1.6.1",
"tslib": "^1.10.0"
},
"devDependencies": {
"@types/body-parser": "^1.19.0",
"@types/express": "^4.17.6",
"@types/jasmine": "^3.5.10",
"@types/nock": "^11.1.0",
"@types/node": "^13.13.2",
"@types/node-fetch": "^2.5.7",
"@types/shelljs": "^0.8.7",
"@types/supertest": "^2.0.8",
"nodemon": "^2.0.3",
"@types/body-parser": "^1.17.0",
"@types/express": "^4.16.0",
"@types/jasmine": "^2.8.8",
"@types/nock": "^9.3.0",
"@types/node": "^10.9.2",
"@types/node-fetch": "^2.1.2",
"@types/shelljs": "^0.8.0",
"@types/supertest": "^2.0.5",
"nodemon": "^1.18.3",
"npm-run-all": "^4.1.5",
"supertest": "^4.0.2",
"tslint": "^6.1.1",
"supertest": "^3.1.0",
"tslint": "^5.11.0",
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
"typescript": "^3.8.3"
"typescript": "^3.0.1"
}
}

View File

@ -15,6 +15,7 @@ const EXISTING_DOWNLOADS = [
'20-1234567-build.zip',
];
const OPEN_PRS = [10, 40];
const ANY_DATE = jasmine.any(String);
// Tests
describe('BuildCleaner', () => {
@ -76,18 +77,22 @@ describe('BuildCleaner', () => {
let cleanerRemoveUnnecessaryDownloadsSpy: jasmine.Spy;
beforeEach(() => {
cleanerGetExistingBuildNumbersSpy = spyOn(cleaner, 'getExistingBuildNumbers').and.resolveTo(EXISTING_BUILDS);
cleanerGetOpenPrNumbersSpy = spyOn(cleaner, 'getOpenPrNumbers').and.resolveTo(OPEN_PRS);
cleanerGetExistingDownloadsSpy = spyOn(cleaner, 'getExistingDownloads').and.resolveTo(EXISTING_DOWNLOADS);
cleanerGetExistingBuildNumbersSpy = spyOn(cleaner, 'getExistingBuildNumbers')
.and.callFake(() => Promise.resolve(EXISTING_BUILDS));
cleanerGetOpenPrNumbersSpy = spyOn(cleaner, 'getOpenPrNumbers')
.and.callFake(() => Promise.resolve(OPEN_PRS));
cleanerGetExistingDownloadsSpy = spyOn(cleaner, 'getExistingDownloads')
.and.callFake(() => Promise.resolve(EXISTING_DOWNLOADS));
cleanerRemoveUnnecessaryBuildsSpy = spyOn(cleaner, 'removeUnnecessaryBuilds');
cleanerRemoveUnnecessaryDownloadsSpy = spyOn(cleaner, 'removeUnnecessaryDownloads');
});
it('should return a promise', async () => {
const promise = cleaner.cleanUp();
expect(promise).toBeInstanceOf(Promise);
expect(promise).toEqual(jasmine.any(Promise));
// Do not complete the test and release the spies synchronously, to avoid running the actual implementations.
await promise;
@ -125,32 +130,52 @@ describe('BuildCleaner', () => {
it('should reject if \'getOpenPrNumbers()\' rejects', async () => {
cleanerGetOpenPrNumbersSpy.and.rejectWith('Test');
await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test');
try {
cleanerGetOpenPrNumbersSpy.and.callFake(() => Promise.reject('Test'));
await cleaner.cleanUp();
} catch (err) {
expect(err).toBe('Test');
}
});
it('should reject if \'getExistingBuildNumbers()\' rejects', async () => {
cleanerGetExistingBuildNumbersSpy.and.rejectWith('Test');
await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test');
try {
cleanerGetExistingBuildNumbersSpy.and.callFake(() => Promise.reject('Test'));
await cleaner.cleanUp();
} catch (err) {
expect(err).toBe('Test');
}
});
it('should reject if \'getExistingDownloads()\' rejects', async () => {
cleanerGetExistingDownloadsSpy.and.rejectWith('Test');
await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test');
try {
cleanerGetExistingDownloadsSpy.and.callFake(() => Promise.reject('Test'));
await cleaner.cleanUp();
} catch (err) {
expect(err).toBe('Test');
}
});
it('should reject if \'removeUnnecessaryBuilds()\' rejects', async () => {
cleanerRemoveUnnecessaryBuildsSpy.and.rejectWith('Test');
await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test');
try {
cleanerRemoveUnnecessaryBuildsSpy.and.callFake(() => Promise.reject('Test'));
await cleaner.cleanUp();
} catch (err) {
expect(err).toBe('Test');
}
});
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
cleanerRemoveUnnecessaryDownloadsSpy.and.rejectWith('Test');
await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test');
try {
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
await cleaner.cleanUp();
} catch (err) {
expect(err).toBe('Test');
}
});
});
@ -162,15 +187,13 @@ describe('BuildCleaner', () => {
let promise: Promise<number[]>;
beforeEach(() => {
fsReaddirSpy = spyOn(fs, 'readdir').and.callFake(
((_: string, cb: typeof readdirCb) => readdirCb = cb) as unknown as typeof fs.readdir,
);
fsReaddirSpy = spyOn(fs, 'readdir').and.callFake((_: string, cb: typeof readdirCb) => readdirCb = cb);
promise = cleaner.getExistingBuildNumbers();
});
it('should return a promise', () => {
expect(promise).toBeInstanceOf(Promise);
expect(promise).toEqual(jasmine.any(Promise));
});
@ -180,27 +203,43 @@ describe('BuildCleaner', () => {
});
it('should reject if an error occurs while getting the files', async () => {
it('should reject if an error occurs while getting the files', done => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
readdirCb('Test');
await expectAsync(promise).toBeRejectedWith('Test');
});
it('should resolve with the returned files (as numbers)', async () => {
it('should resolve with the returned files (as numbers)', done => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, ['12', '34', '56']);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
});
it('should remove `HIDDEN_DIR_PREFIX` from the filenames', async () => {
it('should remove `HIDDEN_DIR_PREFIX` from the filenames', done => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, [`${HIDDEN_DIR_PREFIX}12`, '34', `${HIDDEN_DIR_PREFIX}56`]);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
});
it('should ignore files with non-numeric (or zero) names', async () => {
it('should ignore files with non-numeric (or zero) names', done => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, ['12', 'foo', '34', 'bar', '56', '000']);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
});
});
@ -220,7 +259,7 @@ describe('BuildCleaner', () => {
it('should return a promise', () => {
expect(promise).toBeInstanceOf(Promise);
expect(promise).toEqual(jasmine.any(Promise));
});
@ -229,15 +268,31 @@ describe('BuildCleaner', () => {
});
it('should reject if an error occurs while fetching PRs', async () => {
it('should reject if an error occurs while fetching PRs', done => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
prDeferred.reject('Test');
await expectAsync(promise).toBeRejectedWith('Test');
});
it('should resolve with the numbers of the fetched PRs', async () => {
it('should resolve with the numbers of the fetched PRs', done => {
promise.then(prNumbers => {
expect(prNumbers).toEqual([1, 2, 3]);
done();
});
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
await expectAsync(promise).toBeResolvedTo([1, 2, 3]);
});
it('should log the number of open PRs', () => {
promise.then(prNumbers => {
expect(loggerLogSpy).toHaveBeenCalledWith(
ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
});
});
});
@ -249,15 +304,13 @@ describe('BuildCleaner', () => {
let promise: Promise<string[]>;
beforeEach(() => {
fsReaddirSpy = spyOn(fs, 'readdir').and.callFake(
((_: string, cb: typeof readdirCb) => readdirCb = cb) as unknown as typeof fs.readdir,
);
fsReaddirSpy = spyOn(fs, 'readdir').and.callFake((_: string, cb: typeof readdirCb) => readdirCb = cb);
promise = cleaner.getExistingDownloads();
});
it('should return a promise', () => {
expect(promise).toBeInstanceOf(Promise);
expect(promise).toEqual(jasmine.any(Promise));
});
@ -267,21 +320,33 @@ describe('BuildCleaner', () => {
});
it('should reject if an error occurs while getting the files', async () => {
it('should reject if an error occurs while getting the files', done => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
readdirCb('Test');
await expectAsync(promise).toBeRejectedWith('Test');
});
it('should resolve with the returned file names', async () => {
it('should resolve with the returned file names', done => {
promise.then(result => {
expect(result).toEqual(EXISTING_DOWNLOADS);
done();
});
readdirCb(null, EXISTING_DOWNLOADS);
await expectAsync(promise).toBeResolvedTo(EXISTING_DOWNLOADS);
});
it('should ignore files that do not match the artifactPath', async () => {
it('should ignore files that do not match the artifactPath', done => {
promise.then(result => {
expect(result).toEqual(['10-ABCDEF-build.zip', '30-FFFFFFF-build.zip']);
done();
});
readdirCb(null, ['10-ABCDEF-build.zip', '20-AAAAAAA-otherfile.zip', '30-FFFFFFF-build.zip']);
await expectAsync(promise).toBeResolvedTo(['10-ABCDEF-build.zip', '30-FFFFFFF-build.zip']);
});
});
@ -299,7 +364,7 @@ describe('BuildCleaner', () => {
});
it('should test if the directory exists (and return if it does not)', () => {
it('should test if the directory exists (and return if is does not)', () => {
shellTestSpy.and.returnValue(false);
cleaner.removeDir('/foo/bar');
@ -316,19 +381,22 @@ describe('BuildCleaner', () => {
it('should make the directory and its content writable before removing', () => {
shellRmSpy.and.callFake(() => expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar'));
cleaner.removeDir('/foo/bar');
expect(shellChmodSpy).toHaveBeenCalledBefore(shellRmSpy);
expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar');
expect(shellRmSpy).toHaveBeenCalled();
});
it('should catch errors and log them', () => {
shellRmSpy.and.throwError('Test');
shellRmSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cleaner.removeDir('/foo/bar');
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', new Error('Test'));
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
});
});
@ -381,7 +449,7 @@ describe('BuildCleaner', () => {
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(0);
cleanerRemoveDirSpy.calls.reset();
cleaner.removeUnnecessaryBuilds([1, 2, 3, 4], []);
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], []);
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(8);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/2'));

View File

@ -45,15 +45,25 @@ describe('CircleCIApi', () => {
const errorMessage = 'Invalid request';
const request = nock(BASE_URL).get(`/${buildNum}?circle-token=${TOKEN}`);
try {
request.replyWithError(errorMessage);
await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
await api.getBuildInfo(buildNum);
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI build info request failed ` +
`(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
try {
request.reply(404, errorMessage);
await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
await api.getBuildInfo(buildNum);
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI build info request failed ` +
`(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
});
});
@ -68,7 +78,8 @@ describe('CircleCIApi', () => {
.get(`/${buildNum}/artifacts?circle-token=${TOKEN}`)
.reply(200, [artifact0, artifact1, artifact2]);
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeResolvedTo('https://url/1');
const artifactUrl = await api.getBuildArtifactUrl(buildNum, 'some/path/1');
expect(artifactUrl).toEqual('https://url/1');
request.done();
});
@ -79,15 +90,25 @@ describe('CircleCIApi', () => {
const errorMessage = 'Invalid request';
const request = nock(BASE_URL).get(`/${buildNum}/artifacts?circle-token=${TOKEN}`);
try {
request.replyWithError(errorMessage);
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
await api.getBuildArtifactUrl(buildNum, 'some/path/1');
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` +
`(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
try {
request.reply(404, errorMessage);
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
await api.getBuildArtifactUrl(buildNum, 'some/path/1');
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` +
`(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
});
it('should throw an error if the response does not contain the specified artifact', async () => {
@ -100,9 +121,14 @@ describe('CircleCIApi', () => {
.get(`/${buildNum}/artifacts?circle-token=${TOKEN}`)
.reply(200, [artifact0, artifact1, artifact2]);
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/3')).toBeRejectedWithError(
try {
await api.getBuildArtifactUrl(buildNum, 'some/path/3');
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` +
`(Missing artifact (some/path/3) for CircleCI build: ${buildNum})`);
}
});
});
});

View File

@ -118,7 +118,7 @@ describe('GithubApi', () => {
it('should return a promise', () => {
expect((api as any).getPaginated()).toBeInstanceOf(Promise);
expect((api as any).getPaginated()).toEqual(jasmine.any(Promise));
});
@ -131,30 +131,45 @@ describe('GithubApi', () => {
});
it('should reject if the request fails', async () => {
const responsePromise = (api as any).getPaginated('/foo/bar');
it('should reject if the request fails', done => {
(api as any).getPaginated('/foo/bar').catch((err: any) => {
expect(err).toBe('Test');
done();
});
deferreds[0].reject('Test');
await expectAsync(responsePromise).toBeRejectedWith('Test');
});
it('should resolve with the returned items', async () => {
it('should resolve with the returned items', done => {
const items = [{id: 1}, {id: 2}];
const responsePromise = (api as any).getPaginated('/foo/bar');
deferreds[0].resolve(items);
await expectAsync(responsePromise).toBeResolvedTo(items);
(api as any).getPaginated('/foo/bar').then((data: any) => {
expect(data).toEqual(items);
done();
});
deferreds[0].resolve(items);
});
it('should iteratively call \'get()\' to fetch all items', async () => {
it('should iteratively call \'get()\' to fetch all items', done => {
// Create an array or 250 objects.
const allItems = new Array(250).fill(null).map((_, i) => ({id: i}));
const allItems = '.'.repeat(250).split('').map((_, i) => ({id: i}));
const apiGetSpy = api.get as jasmine.Spy;
(api as any).getPaginated('/foo/bar', {baz: 'qux'}).then((data: any) => {
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
const responsePromise = (api as any).getPaginated('/foo/bar', {baz: 'qux'});
expect(apiGetSpy).toHaveBeenCalledTimes(3);
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
expect(data).toEqual(allItems);
done();
});
deferreds[0].resolve(allItems.slice(0, 100));
setTimeout(() => {
@ -163,13 +178,6 @@ describe('GithubApi', () => {
deferreds[2].resolve(allItems.slice(200));
}, 0);
}, 0);
await expectAsync(responsePromise).toBeResolvedTo(allItems);
expect(apiGetSpy).toHaveBeenCalledTimes(3);
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
});
});
@ -209,8 +217,8 @@ describe('GithubApi', () => {
describe('request()', () => {
it('should return a promise', () => {
nock('https://api.github.com').get('/').reply(200);
expect((api as any).request()).toBeInstanceOf(Promise);
nock('https://api.github.com').get('').reply(200);
expect((api as any).request()).toEqual(jasmine.any(Promise));
});
@ -239,8 +247,9 @@ describe('GithubApi', () => {
nock('https://api.github.com')
.intercept('/path', 'method')
.replyWithError('Test');
await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError('Test');
let message = 'Failed to reject error';
await (api as any).request('method', '/path').catch((err: any) => message = err.message);
expect(message).toEqual('Test');
});
@ -254,69 +263,81 @@ describe('GithubApi', () => {
});
it('should reject if response statusCode is <200', async () => {
it('should reject if response statusCode is <200', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(199);
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed'));
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 199'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('failed');
expect(err).toContain('status: 199');
done();
});
requestHandler.done();
});
it('should reject if response statusCode is >=400', async () => {
it('should reject if response statusCode is >=400', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(400);
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed'));
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 400'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('failed');
expect(err).toContain('status: 400');
done();
});
requestHandler.done();
});
it('should include the response text in the rejection message', async () => {
it('should include the response text in the rejection message', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(500, 'Test');
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('Test'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('Test');
done();
});
requestHandler.done();
});
it('should resolve if returned statusCode is >=200 and <400', async () => {
it('should resolve if returned statusCode is >=200 and <400', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(200);
await expectAsync((api as any).request('method', '/path')).toBeResolved();
(api as any).request('method', '/path').then(done);
requestHandler.done();
});
it('should parse the response body into an object using \'JSON.parse\'', async () => {
it('should parse the response body into an object using \'JSON.parse\'', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(300, '{"foo": "bar"}');
await expectAsync((api as any).request('method', '/path')).toBeResolvedTo({foo: 'bar'});
(api as any).request('method', '/path').then((data: any) => {
expect(data).toEqual({foo: 'bar'});
done();
});
requestHandler.done();
});
it('should reject if the response text is malformed JSON', async () => {
it('should reject if the response text is malformed JSON', done => {
const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method')
.reply(300, '}');
await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError(SyntaxError);
(api as any).request('method', '/path').catch((err: any) => {
expect(err).toEqual(jasmine.any(SyntaxError));
done();
});
requestHandler.done();
});

View File

@ -1,6 +1,6 @@
// Imports
import {GithubApi} from '../../lib/common/github-api';
import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests';
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
// Tests
describe('GithubPullRequests', () => {
@ -47,21 +47,27 @@ describe('GithubPullRequests', () => {
it('should make a POST request to Github with the correct pathname, params and data', () => {
githubApi.post.and.resolveTo();
githubApi.post.and.callFake(() => Promise.resolve());
prs.addComment(42, 'body');
expect(githubApi.post).toHaveBeenCalledWith('/repos/foo/bar/issues/42/comments', null, {body: 'body'});
});
it('should reject if the request fails', async () => {
githubApi.post.and.rejectWith('Test');
await expectAsync(prs.addComment(42, 'body')).toBeRejectedWith('Test');
it('should reject if the request fails', done => {
githubApi.post.and.callFake(() => Promise.reject('Test'));
prs.addComment(42, 'body').catch(err => {
expect(err).toBe('Test');
done();
});
});
it('should resolve with the data from the Github POST', async () => {
githubApi.post.and.resolveTo('Test');
await expectAsync(prs.addComment(42, 'body')).toBeResolvedTo('Test');
it('should resolve with the data from the Github POST', done => {
githubApi.post.and.callFake(() => Promise.resolve('Test'));
prs.addComment(42, 'body').then(data => {
expect(data).toBe('Test');
done();
});
});
});
@ -81,11 +87,13 @@ describe('GithubPullRequests', () => {
});
it('should resolve with the data returned from GitHub', async () => {
it('should resolve with the data returned from GitHub', done => {
const expected: any = {number: 42};
githubApi.get.and.resolveTo(expected);
await expectAsync(prs.fetch(42)).toBeResolvedTo(expected);
githubApi.get.and.callFake(() => Promise.resolve(expected));
prs.fetch(42).then(data => {
expect(data).toEqual(expected);
done();
});
});
});
@ -117,14 +125,9 @@ describe('GithubPullRequests', () => {
});
it('should forward the value returned by \'getPaginated()\'', async () => {
const mockPrs: PullRequest[] = [
{number: 1, user: {login: 'foo'}, labels: []},
{number: 2, user: {login: 'bar'}, labels: []},
];
githubApi.getPaginated.and.resolveTo(mockPrs);
expect(await prs.fetchAll()).toBe(mockPrs);
it('should forward the value returned by \'getPaginated()\'', () => {
githubApi.getPaginated.and.returnValue('Test');
expect(prs.fetchAll() as any).toBe('Test');
});
});
@ -144,11 +147,13 @@ describe('GithubPullRequests', () => {
});
it('should resolve with the data returned from GitHub', async () => {
it('should resolve with the data returned from GitHub', done => {
const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}];
githubApi.getPaginated.and.resolveTo(expected);
await expectAsync(prs.fetchFiles(42)).toBeResolvedTo(expected);
githubApi.getPaginated.and.callFake(() => Promise.resolve(expected));
prs.fetchFiles(42).then(data => {
expect(data).toEqual(expected);
done();
});
});
});

View File

@ -1,5 +1,5 @@
import {GithubApi} from '../../lib/common/github-api';
import {GithubTeams, Team} from '../../lib/common/github-teams';
import {GithubTeams} from '../../lib/common/github-teams';
// Tests
describe('GithubTeams', () => {
@ -16,7 +16,6 @@ describe('GithubTeams', () => {
expect(() => new GithubTeams(githubApi, '')).
toThrowError('Missing or empty required parameter \'githubOrg\'!');
});
});
@ -34,14 +33,9 @@ describe('GithubTeams', () => {
});
it('should forward the value returned by \'getPaginated()\'', async () => {
const mockTeams: Team[] = [
{id: 1, slug: 'foo'},
{id: 2, slug: 'bar'},
];
githubApi.getPaginated.and.resolveTo(mockTeams);
expect(await teams.fetchAll()).toBe(mockTeams);
it('should forward the value returned by \'getPaginated()\'', () => {
githubApi.getPaginated.and.returnValue('Test');
expect(teams.fetchAll() as any).toBe('Test');
});
});
@ -56,77 +50,100 @@ describe('GithubTeams', () => {
it('should return a promise', () => {
githubApi.get.and.resolveTo();
githubApi.get.and.callFake(() => Promise.resolve());
const promise = teams.isMemberById('user', [1]);
expect(promise).toBeInstanceOf(Promise);
expect(promise).toEqual(jasmine.any(Promise));
});
it('should resolve with false if called with an empty array', async () => {
await expectAsync(teams.isMemberById('user', [])).toBeResolvedTo(false);
it('should resolve with false if called with an empty array', done => {
teams.isMemberById('user', []).then(isMember => {
expect(isMember).toBe(false);
expect(githubApi.get).not.toHaveBeenCalled();
done();
});
});
it('should call \'get()\' with the correct pathname', async () => {
githubApi.get.and.resolveTo();
await teams.isMemberById('user', [1]);
it('should call \'get()\' with the correct pathname', done => {
githubApi.get.and.callFake(() => Promise.resolve());
teams.isMemberById('user', [1]).then(() => {
expect(githubApi.get).toHaveBeenCalledWith('/teams/1/memberships/user');
done();
});
});
it('should resolve with false if \'get()\' rejects', async () => {
githubApi.get.and.rejectWith(null);
await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false);
it('should resolve with false if \'get()\' rejects', done => {
githubApi.get.and.callFake(() => Promise.reject(null));
teams.isMemberById('user', [1]).then(isMember => {
expect(isMember).toBe(false);
expect(githubApi.get).toHaveBeenCalled();
done();
});
});
it('should resolve with false if the membership is not active', async () => {
githubApi.get.and.resolveTo({state: 'pending'});
await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false);
it('should resolve with false if the membership is not active', done => {
githubApi.get.and.callFake(() => Promise.resolve({state: 'pending'}));
teams.isMemberById('user', [1]).then(isMember => {
expect(isMember).toBe(false);
expect(githubApi.get).toHaveBeenCalled();
done();
});
});
it('should resolve with true if the membership is active', async () => {
githubApi.get.and.resolveTo({state: 'active'});
await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(true);
it('should resolve with true if the membership is active', done => {
githubApi.get.and.callFake(() => Promise.resolve({state: 'active'}));
teams.isMemberById('user', [1]).then(isMember => {
expect(isMember).toBe(true);
done();
});
});
it('should sequentially call \'get()\' until an active membership is found', async () => {
githubApi.get.
withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}).
withArgs('/teams/2/memberships/user').and.rejectWith(null).
withArgs('/teams/3/memberships/user').and.resolveTo({state: 'active'});
it('should sequentially call \'get()\' until an active membership is found', done => {
const trainedResponses: {[pathname: string]: Promise<{state: string}>} = {
'/teams/1/memberships/user': Promise.resolve({state: 'pending'}),
'/teams/2/memberships/user': Promise.reject(null),
'/teams/3/memberships/user': Promise.resolve({state: 'active'}),
};
githubApi.get.and.callFake((pathname: string) => trainedResponses[pathname]);
await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(true);
teams.isMemberById('user', [1, 2, 3, 4]).then(isMember => {
expect(isMember).toBe(true);
expect(githubApi.get).toHaveBeenCalledTimes(3);
expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user');
expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user');
expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user');
done();
});
});
it('should resolve with false if no active membership is found', async () => {
githubApi.get.
withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}).
withArgs('/teams/2/memberships/user').and.rejectWith(null).
withArgs('/teams/3/memberships/user').and.resolveTo({state: 'not active'}).
withArgs('/teams/4/memberships/user').and.rejectWith(null);
it('should resolve with false if no active membership is found', done => {
const trainedResponses: {[pathname: string]: Promise<{state: string}>} = {
'/teams/1/memberships/user': Promise.resolve({state: 'pending'}),
'/teams/2/memberships/user': Promise.reject(null),
'/teams/3/memberships/user': Promise.resolve({state: 'not active'}),
'/teams/4/memberships/user': Promise.reject(null),
};
githubApi.get.and.callFake((pathname: string) => trainedResponses[pathname]);
await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(false);
teams.isMemberById('user', [1, 2, 3, 4]).then(isMember => {
expect(isMember).toBe(false);
expect(githubApi.get).toHaveBeenCalledTimes(4);
expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user');
expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user');
expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user');
expect(githubApi.get.calls.argsFor(3)[0]).toBe('/teams/4/memberships/user');
done();
});
});
});
@ -140,13 +157,14 @@ describe('GithubTeams', () => {
beforeEach(() => {
teams = new GithubTeams(githubApi, 'foo');
teamsFetchAllSpy = spyOn(teams, 'fetchAll').and.resolveTo([{id: 1, slug: 'team1'}, {id: 2, slug: 'team2'}]);
const mockResponse = Promise.resolve([{id: 1, slug: 'team1'}, {id: 2, slug: 'team2'}]);
teamsFetchAllSpy = spyOn(teams, 'fetchAll').and.returnValue(mockResponse);
teamsIsMemberByIdSpy = spyOn(teams, 'isMemberById');
});
it('should return a promise', () => {
expect(teams.isMemberBySlug('user', ['team-slug'])).toBeInstanceOf(Promise);
expect(teams.isMemberBySlug('user', ['team-slug'])).toEqual(jasmine.any(Promise));
});
@ -156,46 +174,55 @@ describe('GithubTeams', () => {
});
it('should resolve with false if \'fetchAll()\' rejects', async () => {
teamsFetchAllSpy.and.rejectWith(null);
await expectAsync(teams.isMemberBySlug('user', ['team-slug'])).toBeResolvedTo(false);
it('should resolve with false if \'fetchAll()\' rejects', done => {
teamsFetchAllSpy.and.callFake(() => Promise.reject(null));
teams.isMemberBySlug('user', ['team-slug']).then(isMember => {
expect(isMember).toBe(false);
done();
});
});
it('should call \'isMemberById()\' with the correct params if no team is found', async () => {
await teams.isMemberBySlug('user', ['no-match']);
it('should call \'isMemberById()\' with the correct params if no team is found', done => {
teams.isMemberBySlug('user', ['no-match']).then(() => {
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', []);
done();
});
});
it('should call \'isMemberById()\' with the correct params if teams are found', async () => {
await teams.isMemberBySlug('userA', ['team1']);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]);
it('should call \'isMemberById()\' with the correct params if teams are found', done => {
const spy = teamsIsMemberByIdSpy;
await teams.isMemberBySlug('userB', ['team2']);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [2]);
await teams.isMemberBySlug('userC', ['team1', 'team2']);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userC', [1, 2]);
Promise.all([
teams.isMemberBySlug('user', ['team1']).then(() => expect(spy).toHaveBeenCalledWith('user', [1])),
teams.isMemberBySlug('user', ['team2']).then(() => expect(spy).toHaveBeenCalledWith('user', [2])),
teams.isMemberBySlug('user', ['team1', 'team2']).then(() => expect(spy).toHaveBeenCalledWith('user', [1, 2])),
]).then(done);
});
it('should resolve with false if \'isMemberById()\' rejects', async () => {
teamsIsMemberByIdSpy.and.rejectWith(null);
await expectAsync(teams.isMemberBySlug('user', ['team1'])).toBeResolvedTo(false);
it('should resolve with false if \'isMemberById()\' rejects', done => {
teamsIsMemberByIdSpy.and.callFake(() => Promise.reject(null));
teams.isMemberBySlug('user', ['team1']).then(isMember => {
expect(isMember).toBe(false);
expect(teamsIsMemberByIdSpy).toHaveBeenCalled();
done();
});
});
it('should resolve with the value \'isMemberById()\' resolves with', async () => {
teamsIsMemberByIdSpy.and.resolveTo(true);
await expectAsync(teams.isMemberBySlug('userA', ['team1'])).toBeResolvedTo(true);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]);
teamsIsMemberByIdSpy.and.resolveTo(false);
await expectAsync(teams.isMemberBySlug('userB', ['team1'])).toBeResolvedTo(false);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [1]);
teamsIsMemberByIdSpy.and.callFake(() => Promise.resolve(true));
const isMember1 = await teams.isMemberBySlug('user', ['team1']);
expect(isMember1).toBe(true);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', [1]);
teamsIsMemberByIdSpy.and.callFake(() => Promise.resolve(false));
const isMember2 = await teams.isMemberBySlug('user', ['team1']);
expect(isMember2).toBe(false);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', [1]);
});
});

View File

@ -9,8 +9,7 @@ import {Logger} from '../../lib/common/utils';
import {BuildCreator} from '../../lib/preview-server/build-creator';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
import {PreviewServerError} from '../../lib/preview-server/preview-error';
import {customAsyncMatchers} from './jasmine-custom-async-matchers';
import {expectToBePreviewServerError} from './helpers';
// Tests
describe('BuildCreator', () => {
@ -25,7 +24,6 @@ describe('BuildCreator', () => {
const publicShaDir = path.join(publicPrDir, shortSha);
let bc: BuildCreator;
beforeEach(() => jasmine.addAsyncMatchers(customAsyncMatchers));
beforeEach(() => bc = new BuildCreator(buildsDir));
@ -37,8 +35,8 @@ describe('BuildCreator', () => {
it('should extend EventEmitter', () => {
expect(bc).toBeInstanceOf(BuildCreator);
expect(bc).toBeInstanceOf(EventEmitter);
expect(bc).toEqual(jasmine.any(BuildCreator));
expect(bc).toEqual(jasmine.any(EventEmitter));
expect(Object.getPrototypeOf(bc)).toBe(BuildCreator.prototype);
});
@ -69,43 +67,47 @@ describe('BuildCreator', () => {
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
it('should return a promise', async () => {
it('should return a promise', done => {
const promise = bc.create(pr, sha, archive, isPublic);
expect(promise).toBeInstanceOf(Promise);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
// Do not complete the test (and release the spies) synchronously to avoid running the actual
// `extractArchive()`.
await promise;
expect(promise).toEqual(jasmine.any(Promise));
});
it('should update the PR\'s visibility first if necessary', async () => {
await bc.create(pr, sha, archive, isPublic);
it('should update the PR\'s visibility first if necessary', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledBefore(shellMkdirSpy);
bc.create(pr, sha, archive, isPublic).
then(() => {
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
expect(shellMkdirSpy).toHaveBeenCalled();
}).
then(done);
});
it('should create the build directory (and any parent directories)', async () => {
await bc.create(pr, sha, archive, isPublic);
expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir);
it('should create the build directory (and any parent directories)', done => {
bc.create(pr, sha, archive, isPublic).
then(() => expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir)).
then(done);
});
it('should extract the archive contents into the build directory', async () => {
await bc.create(pr, sha, archive, isPublic);
expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir);
it('should extract the archive contents into the build directory', done => {
bc.create(pr, sha, archive, isPublic).
then(() => expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir)).
then(done);
});
it('should emit a CreatedBuildEvent on success', async () => {
it('should emit a CreatedBuildEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => {
expect(type).toBe(CreatedBuildEvent.type);
expect(evt).toBeInstanceOf(CreatedBuildEvent);
expect(evt).toEqual(jasmine.any(CreatedBuildEvent));
expect(evt.pr).toBe(+pr);
expect(evt.sha).toBe(shortSha);
expect(evt.isPublic).toBe(isPublic);
@ -113,108 +115,130 @@ describe('BuildCreator', () => {
emitted = true;
});
await bc.create(pr, sha, archive, isPublic);
expect(emitted).toBe(true);
bc.create(pr, sha, archive, isPublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
describe('on error', () => {
let existsValues: {[dir: string]: boolean};
beforeEach(() => {
bcExistsSpy.and.returnValue(false);
existsValues = {
[prDir]: false,
[shaDir]: false,
};
bcExistsSpy.and.callFake((dir: string) => existsValues[dir]);
});
it('should abort and skip further operations if changing the PR\'s visibility fails', async () => {
it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
const mockError = new PreviewServerError(543, 'Test');
bcUpdatePrVisibilitySpy.and.rejectWith(mockError);
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject(mockError));
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWith(mockError);
bc.create(pr, sha, archive, isPublic).catch(err => {
expect(err).toBe(mockError);
expect(bcExistsSpy).not.toHaveBeenCalled();
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if the build does already exist', async () => {
bcExistsSpy.withArgs(shaDir).and.returnValue(true);
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
it('should abort and skip further operations if the build does already exist', done => {
existsValues[shaDir] = true;
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should detect existing build directory after visibility change', async () => {
bcUpdatePrVisibilitySpy.and.callFake(() => bcExistsSpy.and.returnValue(true));
it('should detect existing build directory after visibility change', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
expect(bcExistsSpy(prDir)).toBe(false);
expect(bcExistsSpy(shaDir)).toBe(false);
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to create the directories', async () => {
it('should abort and skip further operations if it fails to create the directories', done => {
shellMkdirSpy.and.throwError('');
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to extract the archive', async () => {
it('should abort and skip further operations if it fails to extract the archive', done => {
bcExtractArchiveSpy.and.throwError('');
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should delete the PR directory (for new PR)', async () => {
it('should delete the PR directory (for new PR)', done => {
bcExtractArchiveSpy.and.throwError('');
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir);
done();
});
});
it('should delete the SHA directory (for existing PR)', async () => {
bcExistsSpy.withArgs(prDir).and.returnValue(true);
it('should delete the SHA directory (for existing PR)', done => {
existsValues[prDir] = true;
bcExtractArchiveSpy.and.throwError('');
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir);
done();
});
});
it('should reject with an PreviewServerError', async () => {
it('should reject with an PreviewServerError', done => {
// tslint:disable-next-line: no-string-throw
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
500, `Error while creating preview at: ${shaDir}\nTest`);
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBePreviewServerError(err, 500, `Error while creating preview at: ${shaDir}\nTest`);
done();
});
});
it('should pass PreviewServerError instances unmodified', async () => {
it('should pass PreviewServerError instances unmodified', done => {
shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(543, 'Test');
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBePreviewServerError(err, 543, 'Test');
done();
});
});
});
@ -241,12 +265,12 @@ describe('BuildCreator', () => {
});
it('should return a promise', async () => {
it('should return a promise', done => {
const promise = bc.updatePrVisibility(pr, true);
expect(promise).toBeInstanceOf(Promise);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
// Do not complete the test (and release the spies) synchronously to avoid running the actual `extractArchive()`.
await promise;
expect(promise).toEqual(jasmine.any(Promise));
});
@ -255,53 +279,58 @@ describe('BuildCreator', () => {
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
it('should rename the directory', async () => {
await bc.updatePrVisibility(pr, makePublic);
expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
describe('when the visibility is updated', () => {
it('should resolve to true', async () => {
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(true);
it('should resolve to true', done => {
bc.updatePrVisibility(pr, makePublic).
then(result => expect(result).toBe(true)).
then(done);
});
it('should rename the directory', async () => {
await bc.updatePrVisibility(pr, makePublic);
expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
it('should emit a ChangedPrVisibilityEvent on success', async () => {
it('should emit a ChangedPrVisibilityEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toBeInstanceOf(Array);
expect(evt.shas).toEqual(jasmine.any(Array));
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
await bc.updatePrVisibility(pr, makePublic);
expect(emitted).toBe(true);
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
it('should include all shas in the emitted event', async () => {
it('should include all shas in the emitted event', done => {
const shas = ['foo', 'bar', 'baz'];
let emitted = false;
bcListShasByDate.and.resolveTo(shas);
bcListShasByDate.and.callFake(() => Promise.resolve(shas));
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toBe(shas);
expect(evt.isPublic).toBe(makePublic);
@ -309,82 +338,94 @@ describe('BuildCreator', () => {
emitted = true;
});
await bc.updatePrVisibility(pr, makePublic);
expect(emitted).toBe(true);
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
});
it('should do nothing if the visibility is already up-to-date', async () => {
it('should do nothing if the visibility is already up-to-date', done => {
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
it('should do nothing if the PR directory does not exist', async () => {
it('should do nothing if the PR directory does not exist', done => {
bcExistsSpy.and.returnValue(false);
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
describe('on error', () => {
it('should abort and skip further operations if both directories exist', async () => {
it('should abort and skip further operations if both directories exist', done => {
bcExistsSpy.and.returnValue(true);
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBePreviewServerError(err, 409,
`Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to rename the directory', async () => {
it('should abort and skip further operations if it fails to rename the directory', done => {
shellMvSpy.and.throwError('');
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to list the SHAs', async () => {
it('should abort and skip further operations if it fails to list the SHAs', done => {
bcListShasByDate.and.throwError('');
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should reject with an PreviewServerError', async () => {
it('should reject with an PreviewServerError', done => {
// tslint:disable-next-line: no-string-throw
shellMvSpy.and.callFake(() => { throw 'Test'; });
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBePreviewServerError(err, 500,
`Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done();
});
});
it('should pass PreviewServerError instances unmodified', async () => {
it('should pass PreviewServerError instances unmodified', done => {
shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(543, 'Test');
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBePreviewServerError(err, 543, 'Test');
done();
});
});
});
@ -402,14 +443,12 @@ describe('BuildCreator', () => {
beforeEach(() => {
fsAccessCbs = [];
fsAccessSpy = spyOn(fs, 'access').and.callFake(
((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb)) as unknown as typeof fs.access,
);
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
});
it('should return a promise', () => {
expect((bc as any).exists('foo')).toBeInstanceOf(Promise);
expect((bc as any).exists('foo')).toEqual(jasmine.any(Promise));
});
@ -419,29 +458,25 @@ describe('BuildCreator', () => {
});
it('should resolve with \'true\' if \'fs.access()\' succeeds', async () => {
const existsPromises = [
(bc as any).exists('foo'),
(bc as any).exists('bar'),
];
it('should resolve with \'true\' if \'fs.access()\' succeeds', done => {
Promise.
all([(bc as any).exists('foo'), (bc as any).exists('bar')]).
then(results => expect(results).toEqual([true, true])).
then(done);
fsAccessCbs[0]();
fsAccessCbs[1](null);
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([true, true]);
});
it('should resolve with \'false\' if \'fs.access()\' errors', async () => {
const existsPromises = [
(bc as any).exists('foo'),
(bc as any).exists('bar'),
];
it('should resolve with \'false\' if \'fs.access()\' errors', done => {
Promise.
all([(bc as any).exists('foo'), (bc as any).exists('bar')]).
then(results => expect(results).toEqual([false, false])).
then(done);
fsAccessCbs[0]('Error');
fsAccessCbs[1](new Error());
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([false, false]);
});
});
@ -460,15 +495,12 @@ describe('BuildCreator', () => {
consoleWarnSpy = spyOn(Logger.prototype, 'warn');
shellChmodSpy = spyOn(shell, 'chmod');
shellRmSpy = spyOn(shell, 'rm');
cpExecSpy = spyOn(cp, 'exec').and.callFake(
((_: string, cb: (...args: any[]) => void) =>
cpExecCbs.push(cb)) as unknown as typeof cp.exec,
);
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
});
it('should return a promise', () => {
expect((bc as any).extractArchive('foo', 'bar')).toBeInstanceOf(Promise);
expect((bc as any).extractArchive('foo', 'bar')).toEqual(jasmine.any(Promise));
});
@ -480,68 +512,78 @@ describe('BuildCreator', () => {
});
it('should log (as a warning) any stderr output if extracting succeeded', async () => {
const extractPromise = (bc as any).extractArchive('foo', 'bar');
it('should log (as a warning) any stderr output if extracting succeeded', done => {
(bc as any).extractArchive('foo', 'bar').
then(() => expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr')).
then(done);
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
await expectAsync(extractPromise).toBeResolved();
expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr');
});
it('should make the build directory non-writable', async () => {
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]();
it('should make the build directory non-writable', done => {
(bc as any).extractArchive('foo', 'bar').
then(() => expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar')).
then(done);
await expectAsync(extractPromise).toBeResolved();
expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar');
cpExecCbs[0]();
});
it('should delete the build artifact file on success', async () => {
const extractPromise = (bc as any).extractArchive('input/file', 'output/dir');
cpExecCbs[0]();
it('should delete the build artifact file on success', done => {
(bc as any).extractArchive('input/file', 'output/dir').
then(() => expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file')).
then(done);
await expectAsync(extractPromise).toBeResolved();
expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file');
cpExecCbs[0]();
});
describe('on error', () => {
it('should abort and skip further operations if it fails to extract the archive', async () => {
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]('Test');
await expectAsync(extractPromise).toBeRejectedWith('Test');
it('should abort and skip further operations if it fails to extract the archive', done => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => {
expect(shellChmodSpy).not.toHaveBeenCalled();
expect(shellRmSpy).not.toHaveBeenCalled();
expect(err).toBe('Test');
done();
});
cpExecCbs[0]('Test');
});
it('should abort and skip further operations if it fails to make non-writable', async () => {
// tslint:disable-next-line: no-string-throw
shellChmodSpy.and.callFake(() => { throw 'Test'; });
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]();
await expectAsync(extractPromise).toBeRejectedWith('Test');
it('should abort and skip further operations if it fails to make non-writable', done => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => {
expect(shellChmodSpy).toHaveBeenCalled();
expect(shellRmSpy).not.toHaveBeenCalled();
expect(err).toBe('Test');
done();
});
shellChmodSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cpExecCbs[0]();
});
it('should abort and reject if it fails to remove the build artifact file', async () => {
// tslint:disable-next-line: no-string-throw
shellRmSpy.and.callFake(() => { throw 'Test'; });
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]();
await expectAsync(extractPromise).toBeRejectedWith('Test');
it('should abort and reject if it fails to remove the build artifact file', done => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => {
expect(shellChmodSpy).toHaveBeenCalled();
expect(shellRmSpy).toHaveBeenCalled();
expect(err).toBe('Test');
done();
});
shellRmSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cpExecCbs[0]();
});
});
@ -558,54 +600,62 @@ describe('BuildCreator', () => {
});
beforeEach(() => {
shellLsSpy = spyOn(shell, 'ls').and.returnValue([] as unknown as shell.ShellArray);
shellLsSpy = spyOn(shell, 'ls').and.returnValue([]);
});
it('should return a promise', async () => {
it('should return a promise', done => {
const promise = (bc as any).listShasByDate('input/dir');
expect(promise).toBeInstanceOf(Promise);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `ls()`.
// Do not complete the test (and release the spies) synchronously to avoid running the actual `ls()`.
await promise;
expect(promise).toEqual(jasmine.any(Promise));
});
it('should `ls()` files with their metadata', async () => {
await (bc as any).listShasByDate('input/dir');
expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir');
it('should `ls()` files with their metadata', done => {
(bc as any).listShasByDate('input/dir').
then(() => expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir')).
then(done);
});
it('should reject if listing files fails', async () => {
shellLsSpy.and.rejectWith('Test');
await expectAsync((bc as any).listShasByDate('input/dir')).toBeRejectedWith('Test');
it('should reject if listing files fails', done => {
shellLsSpy.and.callFake(() => Promise.reject('Test'));
(bc as any).listShasByDate('input/dir').catch((err: string) => {
expect(err).toBe('Test');
done();
});
});
it('should return the filenames', async () => {
shellLsSpy.and.resolveTo([
it('should return the filenames', done => {
shellLsSpy.and.callFake(() => Promise.resolve([
lsResult('foo', 100),
lsResult('bar', 200),
lsResult('baz', 300),
]);
]));
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'bar', 'baz']);
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['foo', 'bar', 'baz'])).
then(done);
});
it('should sort by date', async () => {
shellLsSpy.and.resolveTo([
it('should sort by date', done => {
shellLsSpy.and.callFake(() => Promise.resolve([
lsResult('foo', 300),
lsResult('bar', 100),
lsResult('baz', 200),
]);
]));
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['bar', 'baz', 'foo'])).
then(done);
});
it('should not break with ShellJS\' custom `sort()` method', async () => {
it('should not break with ShellJS\' custom `sort()` method', done => {
const mockArray = [
lsResult('foo', 300),
lsResult('bar', 100),
@ -613,21 +663,26 @@ describe('BuildCreator', () => {
];
mockArray.sort = jasmine.createSpy('sort');
shellLsSpy.and.resolveTo(mockArray);
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
shellLsSpy.and.callFake(() => Promise.resolve(mockArray));
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => {
expect(shas).toEqual(['bar', 'baz', 'foo']);
expect(mockArray.sort).not.toHaveBeenCalled();
}).
then(done);
});
it('should only include directories', async () => {
shellLsSpy.and.resolveTo([
it('should only include directories', done => {
shellLsSpy.and.callFake(() => Promise.resolve([
lsResult('foo', 100),
lsResult('bar', 200, false),
lsResult('baz', 300),
]);
]));
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'baz']);
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['foo', 'baz'])).
then(done);
});
});

View File

@ -32,18 +32,18 @@ describe('BuildRetriever', () => {
};
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
spyOn(api, 'getBuildInfo').and.resolveTo(BUILD_INFO);
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl').and.resolveTo(BASE_URL + ARTIFACT_PATH);
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
.and.callFake(() => Promise.resolve(BASE_URL + ARTIFACT_PATH));
WRITEFILE_RESULT = undefined;
writeFileSpy = spyOn(fs, 'writeFile').and.callFake(
((_path: string, _buffer: Buffer, callback: fs.NoParamCallback) =>
callback(WRITEFILE_RESULT)) as typeof fs.writeFile,
(_path: string, _buffer: Buffer, callback: (err?: any) => {}) => callback(WRITEFILE_RESULT),
);
EXISTS_RESULT = false;
existsSpy = spyOn(fs, 'exists').and.callFake(
((_path, callback) => callback(EXISTS_RESULT)) as typeof fs.exists,
(_path: string, callback: (exists: boolean) => {}) => callback(EXISTS_RESULT),
);
});
@ -56,7 +56,6 @@ describe('BuildRetriever', () => {
expect(() => new BuildRetriever(api, -1, DOWNLOAD_DIR))
.toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`);
});
it('should fail if the "downloadDir" is missing', () => {
expect(() => new BuildRetriever(api, MAX_DOWNLOAD_SIZE, ''))
.toThrowError(`Missing or empty required parameter 'downloadDir'!`);
@ -73,10 +72,14 @@ describe('BuildRetriever', () => {
});
it('should error if it is not possible to extract the PR number from the branch', async () => {
BUILD_INFO.branch = 'master';
const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
await expectAsync(retriever.getGithubInfo(12345)).toBeRejectedWithError('No PR found in branch field: master');
try {
BUILD_INFO.branch = 'master';
await retriever.getGithubInfo(12345);
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('No PR found in branch field: master');
}
});
});
@ -107,10 +110,12 @@ describe('BuildRetriever', () => {
it('should fail if the artifact is too large', async () => {
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
retriever = new BuildRetriever(api, 10, DOWNLOAD_DIR);
await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
toBeRejectedWith(jasmine.objectContaining({status: 413}));
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected');
} catch (error) {
expect(error.status).toEqual(413);
}
artifactRequest.done();
});
@ -139,39 +144,49 @@ describe('BuildRetriever', () => {
});
it('should fail if the CircleCI API fails', async () => {
getBuildArtifactUrlSpy.and.rejectWith('getBuildArtifactUrl failed');
await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
toBeRejectedWithError('CircleCI artifact download failed (getBuildArtifactUrl failed)');
try {
getBuildArtifactUrlSpy.and.callFake(() => Promise.reject('getBuildArtifactUrl failed'));
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed (getBuildArtifactUrl failed)');
}
});
it('should fail if the URL fetch errors', async () => {
// create a new handler that errors
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).replyWithError('Artifact Request Failed');
await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).toBeRejectedWithError(
'CircleCI artifact download failed ' +
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed ' +
'(request to http://test.com/some/path/build.zip failed, reason: Artifact Request Failed)');
}
artifactRequest.done();
});
it('should fail if the URL fetch 404s', async () => {
// create a new handler that errors
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(404, 'No such artifact');
await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
toBeRejectedWithError('CircleCI artifact download failed (Error 404 - Not Found)');
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed (Error 404 - Not Found)');
}
artifactRequest.done();
});
it('should fail if file write fails', async () => {
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
try {
WRITEFILE_RESULT = 'Test Error';
await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
toBeRejectedWithError('CircleCI artifact download failed (Test Error)');
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed (Test Error)');
}
artifactRequest.done();
});
});

View File

@ -51,10 +51,7 @@ describe('BuildVerifier', () => {
describe('getSignificantFilesChanged', () => {
it('should return false if none of the fetched files match the given pattern', async () => {
const fetchFilesSpy = spyOn(prs, 'fetchFiles');
fetchFilesSpy.and.resolveTo([
{filename: 'a/b/c', sha: 'a1'},
{filename: 'd/e/f', sha: 'b2'},
]);
fetchFilesSpy.and.callFake(() => Promise.resolve([{filename: 'a/b/c'}, {filename: 'd/e/f'}]));
expect(await bv.getSignificantFilesChanged(777, /^x/)).toEqual(false);
expect(fetchFilesSpy).toHaveBeenCalledWith(777);
@ -81,30 +78,37 @@ describe('BuildVerifier', () => {
user: {login: 'username'},
};
prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').and.resolveTo(mockPrInfo);
teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').and.resolveTo(true);
prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').
and.callFake(() => Promise.resolve(mockPrInfo));
teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').
and.callFake(() => Promise.resolve(true));
});
it('should return a promise', async () => {
it('should return a promise', done => {
const promise = bv.getPrIsTrusted(pr);
expect(promise).toBeInstanceOf(Promise);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `GithubTeams#isMemberBySlug()`.
// Do not complete the test (and release the spies) synchronously to avoid running the actual
// `GithubTeams#isMemberBySlug()`.
await promise;
expect(promise).toEqual(jasmine.any(Promise));
});
it('should fetch the corresponding PR', async () => {
await bv.getPrIsTrusted(pr);
it('should fetch the corresponding PR', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(prsFetchSpy).toHaveBeenCalledWith(pr);
done();
});
});
it('should fail if fetching the PR errors', async () => {
prsFetchSpy.and.rejectWith('Test');
await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test');
it('should fail if fetching the PR errors', done => {
prsFetchSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrIsTrusted(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
@ -113,14 +117,19 @@ describe('BuildVerifier', () => {
beforeEach(() => mockPrInfo.labels.push({name: 'trusted: pr-label'}));
it('should resolve to true', async () => {
await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true);
it('should resolve to true', done => {
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(true);
done();
});
});
it('should not try to verify the author\'s membership status', async () => {
await expectAsync(bv.getPrIsTrusted(pr));
it('should not try to verify the author\'s membership status', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(teamsIsMemberBySlugSpy).not.toHaveBeenCalled();
done();
});
});
});
@ -128,27 +137,40 @@ describe('BuildVerifier', () => {
describe('when the PR does not have the "trusted PR" label', () => {
it('should verify the PR author\'s membership in the specified teams', async () => {
await bv.getPrIsTrusted(pr);
it('should verify the PR author\'s membership in the specified teams', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']);
done();
});
});
it('should fail if verifying membership errors', async () => {
teamsIsMemberBySlugSpy.and.rejectWith('Test');
await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test');
it('should fail if verifying membership errors', done => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrIsTrusted(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
it('should resolve to true if the PR\'s author is a member', async () => {
teamsIsMemberBySlugSpy.and.resolveTo(true);
await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true);
it('should resolve to true if the PR\'s author is a member', done => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.resolve(true));
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(true);
done();
});
});
it('should resolve to false if the PR\'s author is not a member', async () => {
teamsIsMemberBySlugSpy.and.resolveTo(false);
await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(false);
it('should resolve to false if the PR\'s author is not a member', done => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.resolve(false));
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(false);
done();
});
});
});

View File

@ -0,0 +1,11 @@
import {PreviewServerError} from '../../lib/preview-server/preview-error';
export const expectToBePreviewServerError = (actual: PreviewServerError, status?: number, message?: string) => {
expect(actual).toEqual(jasmine.any(PreviewServerError));
if (status != null) {
expect(actual.status).toBe(status);
}
if (message != null) {
expect(actual.message).toBe(message);
}
};

View File

@ -1,5 +0,0 @@
declare module jasmine {
interface AsyncMatchers {
toBeRejectedWithPreviewServerError(status: number, message?: string | RegExp): Promise<void>;
}
}

View File

@ -1,59 +0,0 @@
import {PreviewServerError} from '../../lib/preview-server/preview-error';
// Matchers
const toBeRejectedWithPreviewServerError: jasmine.CustomAsyncMatcherFactory = () => {
return {
async compare(actualPromise: Promise<never>, expectedStatus: number, expectedMessage?: string | RegExp) {
if (!(actualPromise instanceof Promise)) {
throw new Error(`Expected '${toBeRejectedWithPreviewServerError.name}()' to be called on a promise.`);
}
try {
await actualPromise;
return {
pass: false,
message: `Expected a promise to be rejected with a '${PreviewServerError.name}', but it was resolved.`,
};
} catch (actualError) {
const actualPrintValue = stringify(actualError);
const expectedPrintValue =
stringify(new PreviewServerError(expectedStatus, expectedMessage && `${expectedMessage}`));
const pass = errorMatches(actualError, expectedStatus, expectedMessage);
const message =
`Expected a promise ${pass ? 'not ' : ''}to be rejected with ${expectedPrintValue}, but is was` +
`${pass ? '' : ` rejected with ${actualPrintValue}`}.`;
return {pass, message};
}
},
};
// Helpers
function errorMatches(actualErr: unknown, expectedStatus: number, expectedMsg?: string | RegExp): boolean {
if (!(actualErr instanceof PreviewServerError)) return false;
if (actualErr.status !== expectedStatus) return false;
return messageMatches(actualErr.message, expectedMsg);
}
function messageMatches(actualMsg: string, expectedMsg?: string | RegExp): boolean {
if (typeof expectedMsg === 'undefined') return true;
if (typeof expectedMsg === 'string') return actualMsg === expectedMsg;
return expectedMsg.test(actualMsg);
}
function stringify(value: unknown): string {
if (value instanceof PreviewServerError) {
return `${PreviewServerError.name}(${value.status}${value.message ? `, ${value.message}` : ''})`;
}
return jasmine.pp(value);
}
};
// Exports
export const customAsyncMatchers: jasmine.CustomAsyncMatcherFactories = {
toBeRejectedWithPreviewServerError,
};

View File

@ -9,8 +9,8 @@ describe('PreviewServerError', () => {
it('should extend Error', () => {
expect(err).toBeInstanceOf(PreviewServerError);
expect(err).toBeInstanceOf(Error);
expect(err).toEqual(jasmine.any(PreviewServerError));
expect(err).toEqual(jasmine.any(Error));
expect(Object.getPrototypeOf(err)).toBe(PreviewServerError.prototype);
});

View File

@ -1,4 +1,5 @@
// Imports
import * as express from 'express';
import * as http from 'http';
import * as supertest from 'supertest';
import {CircleCiApi} from '../../lib/common/circle-ci-api';
@ -133,7 +134,7 @@ describe('PreviewServerFactory', () => {
const buildCreator = jasmine.any(BuildCreator);
expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(buildRetriever, buildVerifier, buildCreator, defaultConfig);
const middleware = usfCreateMiddlewareSpy.calls.mostRecent().returnValue;
const middleware: express.Express = usfCreateMiddlewareSpy.calls.mostRecent().returnValue;
expect(httpCreateServerSpy).toHaveBeenCalledWith(middleware);
});
@ -229,7 +230,7 @@ describe('PreviewServerFactory', () => {
expect(prsAddCommentSpy).toHaveBeenCalledTimes(2);
expect(prs).toBe(allCalls[1].object);
expect(prs).toBeInstanceOf(GithubPullRequests);
expect(prs).toEqual(jasmine.any(GithubPullRequests));
expect(prs.repoSlug).toBe('organisation/repo');
});
@ -301,8 +302,9 @@ describe('PreviewServerFactory', () => {
let bvGetSignificantFilesChangedSpy: jasmine.Spy;
beforeEach(() => {
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.resolveTo(true);
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').and.resolveTo(true);
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').
and.returnValue(Promise.resolve(true));
});
@ -329,7 +331,7 @@ describe('PreviewServerFactory', () => {
it('should respond appropriately if the PR did not touch any significant files', async () => {
bvGetSignificantFilesChangedSpy.and.resolveTo(false);
bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false));
const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'};
const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`;
@ -343,7 +345,7 @@ describe('PreviewServerFactory', () => {
it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
bvGetPrIsTrustedSpy.and.resolveTo(false);
bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false));
const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
const expectedLog =
@ -370,7 +372,7 @@ describe('PreviewServerFactory', () => {
it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
bvGetSignificantFilesChangedSpy.and.rejectWith('getSignificantFilesChanged error');
bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error'));
await agent.get(url).expect(500, 'getSignificantFilesChanged error');
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
@ -378,10 +380,11 @@ describe('PreviewServerFactory', () => {
it('should respond with error if `getPrIsTrusted()` fails', async () => {
bvGetPrIsTrustedSpy.and.throwError('getPrIsTrusted error');
const error = new Error('getPrIsTrusted error');
bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
await agent.get(url).expect(500, 'getPrIsTrusted error');
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', new Error('getPrIsTrusted error'));
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error);
});
});
@ -494,7 +497,7 @@ describe('PreviewServerFactory', () => {
// Note it is important to put the `reject` into `and.callFake`;
// If you just `and.returnValue` the rejected promise
// then you get an "unhandled rejection" message in the console.
getGithubInfoSpy.and.rejectWith('Test Error');
getGithubInfoSpy.and.callFake(() => Promise.reject('Test Error'));
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
@ -515,7 +518,7 @@ describe('PreviewServerFactory', () => {
});
it('should fail if the artifact fetch request fails', async () => {
downloadBuildArtifactSpy.and.rejectWith('Test Error');
downloadBuildArtifactSpy.and.callFake(() => Promise.reject('Test Error'));
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -524,7 +527,7 @@ describe('PreviewServerFactory', () => {
});
it('should fail if verifying the PR fails', async () => {
getPrIsTrustedSpy.and.rejectWith('Test Error');
getPrIsTrustedSpy.and.callFake(() => Promise.reject('Test Error'));
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -533,7 +536,7 @@ describe('PreviewServerFactory', () => {
});
it('should fail if creating the preview build fails', async () => {
createBuildSpy.and.rejectWith('Test Error');
createBuildSpy.and.callFake(() => Promise.reject('Test Error'));
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -602,7 +605,7 @@ describe('PreviewServerFactory', () => {
it('should propagate errors from BuildVerifier', async () => {
bvGetPrIsTrustedSpy.and.rejectWith('Test');
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
await createRequest(+pr).expect(500, 'Test');
@ -612,9 +615,7 @@ describe('PreviewServerFactory', () => {
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
bvGetPrIsTrustedSpy.
withArgs(24).and.resolveTo(false).
withArgs(42).and.resolveTo(true);
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
await createRequest(24);
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
@ -625,7 +626,7 @@ describe('PreviewServerFactory', () => {
it('should propagate errors from BuildCreator', async () => {
bcUpdatePrVisibilitySpy.and.rejectWith('Test');
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
await createRequest(+pr).expect(500, 'Test');
});
@ -633,9 +634,7 @@ describe('PreviewServerFactory', () => {
describe('on success', () => {
it('should respond with 200 (action: undefined)', async () => {
bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs);
@ -643,9 +642,7 @@ describe('PreviewServerFactory', () => {
it('should respond with 200 (action: labeled)', async () => {
bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs);
@ -653,9 +650,7 @@ describe('PreviewServerFactory', () => {
it('should respond with 200 (action: unlabeled)', async () => {
bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs);

View File

@ -39,7 +39,7 @@ describe('preview-server/utils', () => {
throwRequestError(505, 'ERROR MESSAGE', request);
} catch (error) {
caught = true;
expect(error).toBeInstanceOf(PreviewServerError);
expect(error).toEqual(jasmine.any(PreviewServerError));
expect(error.status).toEqual(505);
expect(error.message).toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`);
}

File diff suppressed because it is too large Load Diff

View File

@ -8,32 +8,10 @@ exitCode=0
# Helpers
function checkCert {
local certPath=$1
if [[ ! -f "$certPath" ]]; then
echo "Certificate '$certPath' does not exist. Skipping expiration check..."
return
fi
openssl x509 -checkend 0 -in "$certPath" -noout > /dev/null
reportStatus "Certificate '$certPath'"
if [[ $? -ne 0 ]]; then
echo " [WARN]"
echo " If you did not provide the certificate explicitly, try running the"
echo " 'docker build' command again with the '--no-cache' option to generate"
echo " a new self-signed certificate."
fi
}
function reportStatus {
local lastExitCode=$?
echo "$1: $([[ $lastExitCode -eq 0 ]] && echo OK || echo NOT OK)"
[[ $lastExitCode -eq 0 ]] || exitCode=1
return $lastExitCode
}
@ -50,16 +28,6 @@ for s in ${services[@]}; do
done
# Check SSL/TLS certificates expiration
certs=(
"$AIO_LOCALCERTS_DIR/$AIO_DOMAIN_NAME.crt"
"$TEST_AIO_LOCALCERTS_DIR/$TEST_AIO_DOMAIN_NAME.crt"
)
for c in ${certs[@]}; do
checkCert $c
done
# Check servers
origins=(
http://$AIO_PREVIEW_SERVER_HOSTNAME:$AIO_PREVIEW_SERVER_PORT

View File

@ -3,7 +3,7 @@
## Create `aio-builds` persistent disk (if not already exists)
- Follow instructions [here](https://cloud.google.com/compute/docs/disks/add-persistent-disk#create_disk).
- `sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/disk/by-id/google-aio-builds`
- `sudo mkfs.ext4 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/disk/by-id/google-aio-builds`
## Mount disk
@ -14,7 +14,7 @@
## Mount disk on boot
- Run:
```sh
echo UUID=`sudo blkid -s UUID -o value /dev/disk/by-id/google-aio-builds` \
/mnt/disks/aio-builds ext4 defaults,discard,nofail 0 2 | sudo tee -a /etc/fstab
```
echo UUID=`sudo blkid -s UUID -o value /dev/disk/by-id/google-aio-builds` \
/mnt/disks/aio-builds ext4 discard,defaults,nofail 0 2 | sudo tee -a /etc/fstab
```

View File

@ -1,11 +1,10 @@
# VM setup - Create docker image
## Install git, Node.js and yarn
- `sudo apt-get update`
- `sudo apt-get install -y git`
- Install the latest stable version of [Node.js](https://nodejs.org/en/download).
- Install the latest stable version of [yarn](https://classic.yarnpkg.com/en/docs/install).
## Install node and yarn
- Install [nvm](https://github.com/creationix/nvm#installation).
- Install node.js: `nvm install 8`
- Install yarn: `npm -g install yarn`
## Checkout repository
@ -17,11 +16,7 @@
- You can overwrite the default environment variables inside the image, by passing new values using
`--build-arg`.
**Note 1:** The script has to execute docker commands with `sudo`.
**Note 2:**
The script has to execute `yarn` commands, so make sure `yarn` is on the `PATH` when invoking the
script.
**Note:** The script has to execute docker commands with `sudo`.
## Example
@ -31,7 +26,7 @@ The following commands would create a docker image from GitHub repo `foo/bar` to
- `git clone https://github.com/foo/bar.git foobar`
- Run:
```sh
```
./foobar/aio-builds-setup/scripts/create-image.sh foobar-builds \
--build-arg AIO_REPO_SLUG=foo/bar \
--build-arg AIO_DOMAIN_NAME=foobar-builds.io \

View File

@ -3,17 +3,24 @@
## Install docker
Official installation instructions: https://docs.docker.com/engine/install
Example:
_Debian (jessie):_
- `sudo apt-get update`
- `sudo apt-get install -y apt-transport-https ca-certificates curl git software-properties-common`
- `curl -fsSL https://apt.dockerproject.org/gpg | sudo apt-key add -`
- `apt-key fingerprint 58118E89F3A912897C070ADBF76221572C52609D`
- `sudo add-apt-repository "deb https://apt.dockerproject.org/repo/ debian-$(lsb_release -cs) main"`
- `sudo apt-get update`
- `sudo apt-get -y install docker-engine`
_Debian (buster):_
_Ubuntu (16.04):_
- `sudo apt-get update`
- `sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common`
- `curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -`
- `sudo apt-key fingerprint 0EBFCD88`
- `sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"`
- `sudo apt-get install -y curl git linux-image-extra-$(uname -r) linux-image-extra-virtual`
- `sudo apt-get install -y apt-transport-https ca-certificates`
- `curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add -`
- `apt-key fingerprint 58118E89F3A912897C070ADBF76221572C52609D`
- `sudo add-apt-repository "deb https://apt.dockerproject.org/repo/ ubuntu-$(lsb_release -cs) main"`
- `sudo apt-get update`
- `sudo apt-get -y install docker-ce docker-ce-cli containerd.io`
- `sudo apt-get -y install docker-engine`
## Start the docker

View File

@ -8,16 +8,16 @@ VM host to update the preview server based on changes in the source code.
The script will pull the latest changes from the origin's master branch and examine if there have
been any changes in files inside the preview server source code directory (see below). If there are,
it will create a new image and verify that it works as expected. Finally, it will stop and remove
it will create a new image and verify that is works as expected. Finally, it will stop and remove
the old docker container and image, create a new container based on the new image and start it.
The script assumes that the preview server source code is in the repository's
`aio/aio-builds-setup/` directory and expects the following inputs:
- **$1**: `HOST_REPO_DIR`
- **$2**: `HOST_SECRETS_DIR`
- **$3**: `HOST_BUILDS_DIR`
- **$4**: `HOST_LOCALCERTS_DIR`
- **$2**: `HOST_LOCALCERTS_DIR`
- **$3**: `HOST_SECRETS_DIR`
- **$4**: `HOST_BUILDS_DIR`
- **$5**: `HOST_LOGS_DIR`
See [here](vm-setup--create-host-dirs-and-files.md) for more info on what each input directory is
@ -25,38 +25,28 @@ used for.
**Note 1:** The script has to execute docker commands with `sudo`.
**Note 2:**
The script has to execute `yarn` commands, so make sure `yarn` is on the `PATH` when invoking the
script.
**Note 3:** Make sure the user that executes the script has access to update the repository.
**Note 2:** Make sure the user that executes the script has access to update the repository
## Run the script manually
You may choose to manually run the script, when necessary. Example:
```sh
```
update-preview-server.sh \
/path/to/repo \
/path/to/localcerts \
/path/to/secrets \
/path/to/builds \
/path/to/localcerts \
/path/to/logs
```
## Run the script automatically
You may choose to automatically trigger the script, e.g. using a cronjob. For example, the following
cronjob entry would run the script every 30 minutes, update the preview server (if necessary) and
log its output to `update-preview-server.log` (assuming the user has the necessary permissions):
cronjob entry would run the script every hour and update the preview server (assuming the user has
the necessary permissions):
```
# Periodically check for changes and update the preview server (if necessary)
*/30 * * * * /path/to/update-preview-server.sh /path/to/repo /path/to/secrets /path/to/builds /path/to/localcerts /path/to/logs >> /path/to/update-preview-server.log 2>&1
*/30 * * * * /path/to/update-preview-server.sh /path/to/repo /path/to/localcerts /path/to/secrets /path/to/builds /path/to/logs
```
**Note:**
Keep in mind that cron jobs run in non-interactive, non-login shells. This means that the execution
context might be different compared to when running the same commands from an interactive, login
shell. For example, `.bashrc` files are normally _not_ sourced automatically in cron jobs. See
[here](http://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) for more info.

View File

@ -7,14 +7,13 @@ echo -e "\n\n[`date`] - Updating the preview server..."
# Input
readonly HOST_REPO_DIR=$1
readonly HOST_SECRETS_DIR=$2
readonly HOST_BUILDS_DIR=$3
readonly HOST_LOCALCERTS_DIR=$4
readonly HOST_LOCALCERTS_DIR=$2
readonly HOST_SECRETS_DIR=$3
readonly HOST_BUILDS_DIR=$4
readonly HOST_LOGS_DIR=$5
# Constants
readonly PROVISIONAL_TAG=provisional
readonly PROVISIONAL_IMAGE_NAME=aio-builds:$PROVISIONAL_TAG
readonly PROVISIONAL_IMAGE_NAME=aio-builds:provisional
readonly LATEST_IMAGE_NAME=aio-builds:latest
readonly CONTAINER_NAME=aio
@ -31,7 +30,7 @@ readonly CONTAINER_NAME=aio
# Do not update the server unless files inside `aio-builds-setup/` have changed
# or the last attempt failed (identified by the provisional image still being around).
readonly relevantChangedFilesCount=$(git diff --name-only $lastDeployedCommit...HEAD | grep -P "^aio/aio-builds-setup/" | wc -l)
readonly lastAttemptFailed=$(sudo docker image ls | grep "$PROVISIONAL_TAG" >> /dev/fd/3 && echo "true" || echo "false")
readonly lastAttemptFailed=$(sudo docker rmi "$PROVISIONAL_IMAGE_NAME" >> /dev/fd/3 && echo "true" || echo "false")
if [[ $relevantChangedFilesCount -eq 0 ]] && [[ "$lastAttemptFailed" != "true" ]]; then
echo "Skipping update because no relevant files have been touched."
exit 0
@ -61,9 +60,9 @@ readonly CONTAINER_NAME=aio
--publish 80:80 \
--publish 443:443 \
--restart unless-stopped \
--volume $HOST_LOCALCERTS_DIR:/etc/ssl/localcerts:ro \
--volume $HOST_SECRETS_DIR:/aio-secrets:ro \
--volume $HOST_BUILDS_DIR:/var/www/aio-builds \
--volume $HOST_LOCALCERTS_DIR:/etc/ssl/localcerts:ro \
--volume $HOST_LOGS_DIR:/var/log/aio \
"$LATEST_IMAGE_NAME"

View File

@ -5,8 +5,7 @@
"packageManager": "yarn",
"warnings": {
"typescriptMismatch": false
},
"analytics": false
}
},
"newProjectRoot": "projects",
"projects": {
@ -98,9 +97,6 @@
}
],
"serviceWorker": true
},
"ci": {
"progress": false
}
}
},
@ -123,7 +119,7 @@
"browserTarget": "site:build:archive"
},
"ci": {
"browserTarget": "site:build:ci"
"progress": false
}
}
},

View File

@ -109,3 +109,9 @@ Options that specify files can be given as absolute paths, or as paths relative
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
In addition to any general options, each artifact or library defines its own options in a *schematic*.
Schematic options are supplied to the command in the same format as immediate command options.
### Building with Bazel
Optionally, you can configure the Angular CLI to use [Bazel](https://docs.bazel.build) as the build tool. For more information, see [Building with Bazel](guide/bazel).

View File

@ -82,6 +82,9 @@ upgrade-phonecat-2-hybrid/aot/**/*
# styleguide
!styleguide/src/systemjs.custom.js
# universal
!universal/webpack.server.config.js
# stackblitz
*stackblitz.no-link.html

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Accessibility Example</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

View File

@ -5,5 +5,6 @@
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Accessibility"]
}

View File

@ -89,14 +89,14 @@ describe('Animation Tests', () => {
sleepFor(2000);
});
it('should be inactive with a blue background', async () => {
it('should be inactive with an orange background', async () => {
const toggleButton = statusSlider.getToggleButton();
const container = statusSlider.getComponentContainer();
let text = await container.getText();
if (text === 'Active') {
await toggleButton.click();
await browser.wait(async () => await container.getCssValue('backgroundColor') === inactiveColor, 3000);
await browser.wait(async () => await container.getCssValue('backgroundColor') === inactiveColor, 2000);
}
text = await container.getText();
@ -106,14 +106,14 @@ describe('Animation Tests', () => {
expect(bgColor).toBe(inactiveColor);
});
it('should be active with an orange background', async () => {
it('should be active with a blue background', async () => {
const toggleButton = statusSlider.getToggleButton();
const container = statusSlider.getComponentContainer();
let text = await container.getText();
if (text === 'Inactive') {
await toggleButton.click();
await browser.wait(async () => await container.getCssValue('backgroundColor') === activeColor, 3000);
await browser.wait(async () => await container.getCssValue('backgroundColor') === activeColor, 2000);
}
text = await container.getText();

View File

@ -12,8 +12,6 @@ Toggle All Animations <input type="checkbox" [checked]="!animationsDisabled" (cl
<a id="auto" routerLink="/auto" routerLinkActive="active">Auto Calculation</a>
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
<a id="insert-remove" routerLink="/insert-remove" routerLinkActive="active">Insert/Remove</a>
</nav>
<!-- #docregion route-animations-outlet -->

View File

@ -35,7 +35,6 @@ import { InsertRemoveComponent } from './insert-remove.component';
{ path: 'hero-groups', component: HeroListGroupPageComponent },
{ path: 'enter-leave', component: HeroListEnterLeavePageComponent },
{ path: 'auto', component: HeroListAutoCalcPageComponent },
{ path: 'insert-remove', component: InsertRemoveComponent},
{ path: 'home', component: HomeComponent, data: {animation: 'HomePage'} },
{ path: 'about', component: AboutComponent, data: {animation: 'AboutPage'} },

View File

@ -1,4 +1,4 @@
export interface Hero {
export class Hero {
id: number;
name: string;
}

View File

@ -1,7 +1,4 @@
<!-- #docplaster -->
<h2>Insert/Remove</h2>
<nav>
<button (click)="toggle()">Toggle Insert/Remove</button>
</nav>

View File

@ -9,10 +9,10 @@ import { trigger, transition, animate, style } from '@angular/animations';
trigger('myInsertRemoveTrigger', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
animate('5s', style({ opacity: 1 })),
]),
transition(':leave', [
animate('100ms', style({ opacity: 0 }))
animate('5s', style({ opacity: 0 }))
])
]),
// #enddocregion enter-leave-trigger

View File

@ -5,6 +5,5 @@
"!**/*.js",
"!**/*.[1,2,3].*"
],
"file": "src/app/app.component.ts",
"tags": ["animations"]
}

View File

@ -4,7 +4,7 @@ import { protractor, browser, element, by, ElementFinder } from 'protractor';
const nameSuffix = 'X';
interface Hero {
class Hero {
id: number;
name: string;
}

View File

@ -5,5 +5,5 @@
"!**/*.js",
"!**/*.[1].*"
],
"file": "src/app/hero-list.component.html"
"file": "src/app/app.module.ts"
}

View File

@ -25,12 +25,23 @@ describe('Attribute binding example', function () {
});
it('should display a blue div with a red border', function () {
expect(element.all(by.css('div')).get(1).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
expect(element.all(by.css('div')).get(4).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
});
it('should display a div with many classes', function () {
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('special');
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('clearance');
it('should display a div with replaced classes', function () {
expect(element.all(by.css('div')).get(5).getAttribute('class')).toEqual('new-class');
});
it('should display four buttons', function() {
let redButton = element.all(by.css('button')).get(1);
let saveButton = element.all(by.css('button')).get(2);
let bigButton = element.all(by.css('button')).get(3);
let smallButton = element.all(by.css('button')).get(4);
expect(redButton.getCssValue('color')).toEqual('rgba(255, 0, 0, 1)');
expect(saveButton.getCssValue('background-color')).toEqual('rgba(0, 255, 255, 1)');
expect(bigButton.getText()).toBe('Big');
expect(smallButton.getText()).toBe('Small');
});
});

View File

@ -27,41 +27,39 @@
<hr />
<h2>Styling precedence</h2>
<h2>Class binding</h2>
<!-- #docregion basic-specificity -->
<h3>Basic specificity</h3>
<!-- #docregion is-special -->
<h3>toggle the "special" class on/off with a property:</h3>
<div [class.special]="isSpecial">The class binding is special.</div>
<!-- The `class.special` binding will override any value for the `special` class in `classExpr`. -->
<div [class.special]="isSpecial" [class]="classExpr">Some text.</div>
<h3>binding to class.special overrides the class attribute:</h3>
<div class="special" [class.special]="!isSpecial">This one is not so special.</div>
<!-- The `style.color` binding will override any value for the `color` property in `styleExpr`. -->
<div [style.color]="color" [style]="styleExpr">Some text.</div>
<!-- #enddocregion basic-specificity -->
<h3>Using the bind- syntax:</h3>
<div bind-class.special="isSpecial">This class binding is special too.</div>
<!-- #enddocregion is-special -->
<!-- #docregion source-specificity -->
<h3>Source specificity</h3>
<!-- #docregion add-class -->
<h3>Add a class:</h3>
<div class="item clearance special" [class.item-clearance]="itemClearance">Add another class</div>
<!-- #enddocregion add-class -->
<!-- The `class.special` template binding will override any host binding to the `special` class set by `dirWithClassBinding` or `comp-with-host-binding`.-->
<comp-with-host-binding [class.special]="isSpecial" dirWithClassBinding>Some text.</comp-with-host-binding>
<!-- #docregion class-override -->
<h3>Overwrite all existing classes with a new class:</h3>
<div class="item clearance special" [attr.class]="resetClasses">Reset all classes at once</div>
<!-- #enddocregion class-override -->
<!-- The `style.color` template binding will override any host binding to the `color` property set by `dirWithStyleBinding` or `comp-with-host-binding`. -->
<comp-with-host-binding [style.color]="color" dirWithStyleBinding>Some text.</comp-with-host-binding>
<!-- #enddocregion source-specificity -->
<hr />
<!-- #docregion dynamic-priority -->
<h3>Dynamic vs static</h3>
<!-- If `classExpr` has a value for the `special` class, this value will override the `class="special"` below -->
<div class="special" [class]="classExpr">Some text.</div>
<!-- If `styleExpr` has a value for the `color` property, this value will override the `style="color: blue"` below -->
<div style="color: blue" [style]="styleExpr">Some text.</div>
<!-- #enddocregion dynamic-priority -->
<!-- #docregion style-delegation -->
<comp-with-host-binding dirWithHostBinding></comp-with-host-binding>
<!-- #enddocregion style-delegation -->
<h2>Style binding</h2>
<!-- #docregion style-binding-->
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
<!-- #enddocregion style-binding -->
<!-- #docregion style-binding-condition-->
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
<!-- #enddocregion style-binding-condition-->

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -8,8 +8,8 @@ import { Component } from '@angular/core';
export class AppComponent {
actionName = 'Go for it';
isSpecial = true;
itemClearance = true;
resetClasses = 'new-class';
canSave = true;
classExpr = 'special clearance';
styleExpr = 'color: red';
color = 'blue';
}

View File

@ -3,13 +3,11 @@ import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CompWithHostBindingComponent } from './comp-with-host-binding.component';
@NgModule({
declarations: [
AppComponent,
CompWithHostBindingComponent
AppComponent
],
imports: [
BrowserModule

View File

@ -1,16 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'comp-with-host-binding',
template: 'I am a component!',
host: {
'[class.special]': 'isSpecial',
'[style.color]': 'color',
'[style.width]': 'width'
}
})
export class CompWithHostBindingComponent {
isSpecial = false;
color = 'green';
width = '200px';
}

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>AttributeBinding</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

View File

@ -5,6 +5,5 @@
"!**/*.js",
"!**/*.[1,2,3].*"
],
"file": "src/app/highlight.directive.ts",
"tags": ["attribute", "directive"]
}

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,32 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
});
TestBed.compileComponents();
});
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app works!'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app works!');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
});

View File

@ -0,0 +1,8 @@
import { ItemDirective } from './item.directive';
describe('ItemDirective', () => {
it('should create an instance', () => {
const directive = new ItemDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>NgmoduleDemo</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemDetailComponent } from './item-detail.component';
describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>BuiltInDirectives</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

View File

@ -0,0 +1,16 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
});

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Built-in Template Functions Example</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

View File

@ -1,48 +0,0 @@
// #docregion
import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { logging, schema } from '@angular-devkit/core';
describe('Command Runner Builder', () => {
let architect: Architect;
let architectHost: TestingArchitectHost;
beforeEach(async () => {
const registry = new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
// TestingArchitectHost() takes workspace and current directories.
// Since we don't use those, both are the same in this case.
architectHost = new TestingArchitectHost(__dirname, __dirname);
architect = new Architect(architectHost, registry);
// This will either take a Node package name, or a path to the directory
// for the package.json file.
await architectHost.addBuilderFromPackage('..');
});
it('can run node', async () => {
// Create a logger that keeps an array of all messages that were logged.
const logger = new logging.Logger('');
const logs = [];
logger.subscribe(ev => logs.push(ev.message));
// A "run" can have multiple outputs, and contains progress information.
const run = await architect.scheduleBuilder('@example/command-runner:command', {
command: 'node',
args: ['--print', '\'foo\''],
}, { logger }); // We pass the logger for checking later.
// The "result" member (of type BuilderOutput) is the next output.
const output = await run.result;
// Stop the builder from running. This stops Architect from keeping
// the builder-associated states in memory, since builders keep waiting
// to be scheduled.
await run.stop();
// Expect that foo was logged
expect(logs).toContain('foo');
});
});
// #enddocregion

View File

@ -1,46 +0,0 @@
// #docplaster
// #docregion builder, builder-skeleton, handling-output, progress-reporting
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
// #enddocregion builder-skeleton
import * as childProcess from 'child_process';
// #docregion builder-skeleton
interface Options extends JsonObject {
command: string;
args: string[];
}
export default createBuilder(commandBuilder);
function commandBuilder(
options: Options,
context: BuilderContext,
): Promise<BuilderOutput> {
// #enddocregion builder, builder-skeleton, handling-output
// #docregion report-status
context.reportStatus(`Executing "${options.command}"...`);
// #docregion builder, handling-output
const child = childProcess.spawn(options.command, options.args);
// #enddocregion builder, report-status
child.stdout.on('data', data => {
context.logger.info(data.toString());
});
child.stderr.on('data', data => {
context.logger.error(data.toString());
});
// #docregion builder
return new Promise(resolve => {
// #enddocregion builder, handling-output
context.reportStatus(`Done.`);
// #docregion builder, handling-output
child.on('close', code => {
resolve({ success: code === 0 });
});
});
// #docregion builder-skeleton
}
// #enddocregion builder, builder-skeleton, handling-output, progress-reporting

View File

@ -1,40 +0,0 @@
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
// #docregion observable
// declare a publishing operation
const observable = new Observable<number>(observer => {
// Subscriber fn...
});
// initiate execution
observable.subscribe(() => {
// observer handles notifications
});
// #enddocregion observable
// #docregion unsubscribe
const subscription = observable.subscribe(() => {
// observer handles notifications
});
subscription.unsubscribe();
// #enddocregion unsubscribe
// #docregion error
observable.subscribe(() => {
throw Error('my error');
});
// #enddocregion error
// #docregion chain
observable.pipe(map(v => 2 * v));
// #enddocregion chain

View File

@ -1,25 +0,0 @@
// #docregion promise
// initiate execution
const promise = new Promise<number>((resolve, reject) => {
// Executer fn...
});
promise.then(value => {
// handle result here
});
// #enddocregion promise
// #docregion chain
promise.then(v => 2 * v);
// #enddocregion chain
// #docregion error
promise.then(() => {
throw Error('my error');
});
// #enddocregion error

View File

@ -1,10 +1,9 @@
{
"tests": [
"e2e": [
{
"cmd": "yarn",
"args": [
"e2e",
"--protractor-config=e2e/protractor-puppeteer.conf.js",
"--no-webdriver-update",
"--port={PORT}"
]

View File

@ -1,4 +1,4 @@
export interface Hero {
export class Hero {
name: string;
}

View File

@ -6,6 +6,5 @@
"!**/*.native.*",
"!**/*.[1].*"
],
"file": "src/app/hero-app.component.ts",
"tags": ["CSS"]
}

View File

@ -23,7 +23,7 @@ export class HeroContactComponent {
@Host() // limit search for logger; hides the application-wide logger
@Optional() // ok if the logger doesn't exist
private loggerService?: LoggerService
private loggerService: LoggerService
// #enddocregion ctor-params
) {
if (loggerService) {

View File

@ -52,7 +52,7 @@ const templateC = `
export class CarolComponent {
name = 'Carol';
// #docregion carol-ctor
constructor( @Optional() public parent?: Parent ) { }
constructor( @Optional() public parent: Parent ) { }
// #enddocregion carol-ctor
}
// #enddocregion carol-class
@ -64,7 +64,7 @@ export class CarolComponent {
})
export class ChrisComponent {
name = 'Chris';
constructor( @Optional() public parent?: Parent ) { }
constructor( @Optional() public parent: Parent ) { }
}
////// Craig ///////////
@ -81,7 +81,7 @@ export class ChrisComponent {
</div>`
})
export class CraigComponent {
constructor( @Optional() public alex?: Base ) { }
constructor( @Optional() public alex: Base ) { }
}
// #enddocregion craig
@ -105,7 +105,7 @@ const templateB = `
export class BarryComponent implements Parent {
name = 'Barry';
// #docregion barry-ctor
constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
// #enddocregion barry-ctor
}
// #enddocregion barry
@ -117,7 +117,7 @@ export class BarryComponent implements Parent {
})
export class BobComponent implements Parent {
name = 'Bob';
constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
}
@Component({
@ -129,7 +129,7 @@ export class BobComponent implements Parent {
})
export class BethComponent implements Parent {
name = 'Beth';
constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
}
///////// A - Grandparent //////
@ -200,7 +200,7 @@ export class AliceComponent implements Parent
</div>`
})
export class CathyComponent {
constructor( @Optional() public alex?: AlexComponent ) { }
constructor( @Optional() public alex: AlexComponent ) { }
}
// #enddocregion cathy

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