Compare commits

...

47 Commits

Author SHA1 Message Date
1f0c1f3ff2 release: cut the v9.1.12 release 2020-07-08 12:36:30 -07:00
a86a41210f fix(docs-infra): fix deploy-to-firebase.sh for master and v10.0.x branches (#37762)
The deployment to aio is currently failing because #37721 introduced
"project" entry into the firebase.json which means that we now need to
select the deployment target before deploying to firebase.

This change fixes the issue and refactors the file to be easier to read.

I also added extra echo statements so that the CI logs are easier to
read in case we need to troubleshoot future issues.

PR Close #37762
2020-06-25 17:58:21 -07:00
20c44a6240 fix(docs-infra): fix typo in the deploy-to-firebase.sh script
This typo caused the script to fail on Linux (interestingly it works fine on Mac).

This is a painful reminder that we should not write any more Bash scripts EVER. shelljs FTW! :-)
2020-06-25 15:03:12 -07:00
0feb560136 fixup! feat(docs-infra): update deploy-to-firebase.sh script to support v9 multisite setup 2020-06-25 13:13:58 -07:00
f4d06445a3 feat(docs-infra): update deploy-to-firebase.sh script to support v9 multisite setup
v9.angular.io was used to pilot the firebase hosting multisites setup for angular.io.

The deployments so far have been done manually to control the deployment process.

This change, automates the deployment for v9.angular.io so that future deployments can be made from
the CI.

See https://angular-team.atlassian.net/browse/DEV-125 for more info.

In the process of updating the scripts I rediscovered a bug in the deploy-to-firebase.sh script that
incorrect compared two numbers as strings. This previously worked correctly because we were comparing
single digit numbers. With the release of v10, we now compare 9 > 10 which behaves differently for
strings and numbers. The bug was fixed by switching to an arithmetic comparison of the two variables.

This bug has been fixed on the master branch but not on the 9.1.x branch. I realized this during the
rebase, but found my version to be a bit cleaner, so I kept it.
2020-06-25 13:10:26 -07:00
6c1ab479ee fix(core): infinite loop if injectable using inheritance has a custom decorator
If we detect that an injectable class is inheriting from another injectable, we generate code that looks something like this:

```
const baseFactory = ɵɵgetInheritedFactory(Child);

@Injectable()
class Parent {}

@Injectable()
class Child extends Parent {
  static ɵfac = (t) => baseFactory(t || Child)
}
```

This usually works fine, because the `ɵɵgetInheritedFactory` resolves to the factory of `Parent`, but the logic can break down if the `Child` class has a custom decorator. Custom decorators can return a new class that extends the original once, which means that the `ɵɵgetInheritedFactory` call will now resolve to the factory of the `Child`, causing an infinite loop.

These changes fix the issue by changing the inherited factory resolution logic so that it walks up the prototype chain class-by-class, while skipping classes that have the same factory as the class that was passed in.

Fixes #35733.
2020-06-12 22:01:18 +02:00
f7997256fc release: cut the v9.1.11 release 2020-06-10 11:18:17 -07:00
dc9da17f37 Revert "fix(elements): fire custom element output events during component initialization"
This reverts commit 454e073918. This
commit was found to cause some tests inside Google to fail.
2020-06-10 17:31:04 +00:00
99c41d6db0 release: cut the v9.1.10 release 2020-06-09 15:05:14 -07:00
1aae94a421 perf(ngcc): cache parsed tsconfig between runs
This commit will store a cached copy of the parsed tsconfig
that can be reused if the tsconfig path is the same.

This will improve the ngcc "noop" case, where there is no processing
to do, when the entry-points have already been processed.
Previously we were parsing this config every time we checked for
entry-points to process, which can take up to seconds in some
cases.

Cherry-picked from #37417 (6e7bd939f6).

Resolves #36882
2020-06-08 10:54:30 +01:00
f0f319b1d6 ci: temporarily disable Android 10 browser unit tests on Saucelabs (#37399)
Disabling Android 10 browser unit tests on Saucelabs due to errors.

After remediation from Saucelabs to correct the discovered failures, this change can be reverted to renable the tests on Android 10.

Example of failures seen:

```
02 06 2020 14:03:05.048:INFO [SaucelabsLauncher]: Chrome 10.0 (Android) session at https://saucelabs.com/tests/54f5fb181db644a3b4779187c2309000

02 06 2020 14:03:06.869:INFO [Chrome Mobile 74.0.3729 (Android 0.0.0)]: Disconnected browser returned on socket E-bi0p0NKtghk-HcAAAO with id 85563367.

Chrome Mobile 74.0.3729 (Android 0.0.0) ERROR: Error: XHR error loading http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js

	Error loading http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js as "../internal/operators/zip" from http://angular-ci.local:9876/base/node_modules/rxjs/operators/index.js

Error: XHR error loading http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js

    at error (http://angular-ci.local:9876/base/node_modules/systemjs/dist/system.src.js?1c6a6c12fec50a8db7aeebe8e06e2b70135c0615:1028:16)

    at XMLHttpRequest.xhr.onreadystatechange [as __zone_symbol__ON_PROPERTYreadystatechange] (http://angular-ci.local:9876/base/node_modules/systemjs/dist/system.src.js?1c6a6c12fec50a8db7aeebe8e06e2b70135c0615:1036:13)

    at XMLHttpRequest.wrapFn (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:809:43)

    at ZoneDelegate.invokeTask (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:432:35)

    at Zone.runTask (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:201:55)

    at ZoneTask.invokeTask [as invoke] (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:514:38)

    at invokeTask (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:1722:18)

    at XMLHttpRequest.globalZoneAwareCallback (http://angular-ci.local:9876/base/dist/bin/packages/zone.js/npm_package/dist/zone.js?942d01da94828e1c75e8527fa8d06f363d6379ce:1748:21)
```

PR Close #37399
2020-06-05 22:45:49 +03:00
454e073918 fix(elements): fire custom element output events during component initialization
Previously, event listeners for component output events attached on an
Angular custom element before inserting it into the DOM (i.e. before
instantiating the underlying component) didn't fire for events emitted
during initialization lifecycle hooks, such as `ngAfterContentInit`,
`ngAfterViewInit`, `ngOnChanges` (initial call) and `ngOnInit`.
The reason was that that `NgElementImpl` [subscribed to events][1]
_after_ calling [ngElementStrategy#connect()][2], which is where the
[initial change detection][3] takes place (running the initialization
lifecycle hooks).

This commit fixes this by:
1. Ensuring `ComponentNgElementStrategy#events` is defined and available
   for subscribing to, even before instantiating the component.
2. Ensuring `NgElementImpl` subscribes to `NgElementStrategy#events`
   before calling `NgElementStrategy#connect()` (which initializes the
   component instance).

Jira issue: [FW-2010](https://angular-team.atlassian.net/browse/FW-2010)

[1]: c0143cb2ab/packages/elements/src/create-custom-element.ts (L167-L170)
[2]: c0143cb2ab/packages/elements/src/create-custom-element.ts (L164)
[3]: c0143cb2ab/packages/elements/src/component-factory-strategy.ts (L158)

Fixes #36141
2020-06-05 20:16:36 +03:00
76d64331bd refactor(elements): remove unnecessary non-null assertions and as any type-casts
This commit removes some unnecessary non-null assertions (`!`) and
`as any` type-casts from the `elements` package.
2020-06-05 20:16:35 +03:00
6855396449 docs: update aio in support for #BlackLivesMatter
Update angular.io in support for #BlackLivesMatter. The PR updates the
styles of the landing page and changes the current survey notification.
2020-06-02 17:34:57 -07:00
0b96908225 feat(dev-infra): migrate release tool to use new logging system (#37232)
Migrate the release tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
65f52f3761 feat(dev-infra): migrate ts-circular-dependencies tool to use new logging system (#37232)
Migrate the ts-circular-dependencies tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
9ce55d08f8 feat(dev-infra): migrate merge tool to use new logging system (#37232)
Migrate the merge tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
765c1c5b17 feat(dev-infra): migrate ng-dev utils to use new logging system (#37232)
Migrate the ng-dev utils to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
ccfa3426f7 feat(dev-infra): migrate pullapprove tool to use new logging system (#37232)
Migrate the pullapprove tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
83379fa0bb feat(dev-infra): migrate rebase tool to use new logging system (#37232)
Migrate the rebase tool in ng-dev to use new logging system rather
than directly calling console.*  to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
2ffd44ad96 feat(dev-infra): migrate discover-new-conflicts tool to use new logging system (#37232)
Migrate the discover-new-conflicts tool in ng-dev to use new logging system
rather than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
4a84842bbb feat(dev-infra): migrate commit-message tool to use new logging system (#37232)
Migrate the commit-message tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
daa715a1ca feat(dev-infra): migrate format tool to use new logging system (#37232)
Migrate the formatting tool in ng-dev to use new logging system rather
than directly calling console.* to create a better experience
for users.

PR Close #37232
2020-05-21 17:40:36 -04:00
31bce80771 feat(dev-infra): add group functions to logging system and remove color param (#37232)
Adds .group and .groupEnd functions to each of the logging functions
to allow creating groups in the logged output.  Additionally removes
the color parameter from logging functions, in favor of the color
being applied to the string at the call site.

PR Close #37232
2020-05-21 17:40:35 -04:00
711e4d4cc2 build: use static patch value for targetting branches in merge config (#37245)
Due to the desired patch branch (10.0.x) being on a semver version
that is unreleased as stable (there is no 10.0.0 on latest, it is on
next) our logic for determining target patch branches does not work.

This change is a workaround to unblock merging in the repo while a
longer term answer is discovered.

PR Close #37245
2020-05-21 17:07:26 -04:00
fdc5941fe7 feat(dev-infra): expose script for determining merge branches (#37217)
The components repo and framework repository follow the same patch
branch concept. We should be able to share a script for determining
these merge branches.

Additonally the logic has been improved compared to the old merge script because
we no longer consult `git ls-remote` unless really needed. Currently,
`git ls-remote` is always consulted, even though not necessarily needed.

This can slow down the merge script and the caretaker process when a
couple of PRs are merged (personally saw around ~4 seconds per merge).

Additionally, the new logic is more strict and will ensure (in most
cases) that no wrong patch/minor branch is determined. Previously,
the script just used the lexicographically greatest patch branch.
This _could_ be wrong when a new patch branch has been created too
early, or by accident.

PR Close #37217
2020-05-21 10:35:24 -07:00
ae10a541bf fix(dev-infra): ensure ts-node is registered with commonjs as module (#37217)
We recently added support for automatic registration of `ts-node`
when the dev-infra configuration is loaded.

In addition to registering ts-node, we should also ensure that the
`commonjs` module is set up. By default, `ts-node` would use ES module
imports that are not supported by default in NodeJS.

PR Close #37217
2020-05-21 10:35:24 -07:00
b797913d10 release: cut the v9.1.9 release 2020-05-20 15:26:59 -07:00
1465372a0e fix(elements): do not break when the constructor of an Angular Element is not called (#36114) (#37226)
Previously, the correct behavior of Angular custom elements relied on
the constructor being called (and thus the `injector` property being
initialized). However, some polyfills (e.g. `document-register-element`)
do not call the constructor of custom elements, which resulted in the
`injector` property being undefined and the `NgElementStrategy` failing
to be instantiated.

This commit fixes it by being tolerant to the `injector` property being
undefined and falling back to the injector passed to the
`createCustomElement()` config.

NOTE:
We don't have proper tests exercising the situation where the
constructor is not called. For now this is tested using a Google
internal test suite (which is how this issue was caught).
This commit also adds a rudimentary unit test to emulate this situation.

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
a33cb2d39b fix(elements): capture input properties set before upgrading the element (#36114) (#37226)
Previously, if an element started out as a regular `HTMLElement` (not a
Custom Element) and was later upgraded to a Custom Element, any
properties corresponding to component inputs that were set on the
element before upgrading it would not be captured correctly and thus not
reflected on the instantiated component.

This commit fixes it by ensuring that such properties are captured
correctly.

Jira issue: [FW-2006](https://angular-team.atlassian.net/browse/FW-2006)

Fixes #30848
Closes #31416

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
1202d17b4c test(elements): clean up TestStrategy between tests (#36114) (#37226)
Previously, the `TestStrategy` `NgElementStrategy` used in
`createCustomElement()` tests was created once and re-used in each test
(due to complications related to how `customElements.register()` works).
As a result, the `TestStrategy` instance's state (e.g. inputs) could be
polluted from previous tests and affect subsequent ones.

This commit ensures the strategy instance is reset before each test.

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
d837828317 test(elements): only declare helpers if needed (#36114) (#37226)
Previously, helper modules/components classes were declared even if the
tests were not run (because the environment did not support Custom
Elements for example).

This commit moves the declaration of the helpers inside the `describe()`
block, so they are not declared unnecessarily. This is in preparation of
adding more helpers that need to access variables declared inside the
`describe()` block.

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
dc210bc75b refactor(elements): remove unused imports and properties (#36114) (#37226)
- Remove imports that are not used.
- Remove private class properties that are not used outside of the
  constructor.

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
75ead94ed5 refactor(elements): simplify accessing NgElementStrategy (#36114) (#37226)
Previously, we had to check whether `NgElementStrategy` had been
instantiated before accessing it. This was tedious and error prone,
since it was easy to forget to add the check in new call sites.

This commit switches to using a getter, so that the check has to be
performed in one place and is transparent to call sites (including any
future ones).

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
6ac0042aef fix(elements): correctly handle getting/setting properties before connecting the element (#36114) (#37226)
`createCustomElements()` creates some getters/setters for properties
corresponding to component inputs that delegate to the
`NgElementStrategy`. However, it is not guaranteed that the element's
`NgElementStrategy` will have been created when these getters/setters
are called, because some polyfills (e.g. `document-register-element`) do
not call the constructor.

Previously, trying to get/set input properties before connecting the
element to the DOM (via `connectedCallback()`) would fail due to
`NgElementStrategy` not being created.

This commit ensures that the `NgElementStrategy` is always created
before used inside the input property getters/setters (similar to how it
is done for other methods of `NgElement`).

Mentioned in https://github.com/angular/angular/pull/31416/files#r300326698.

PR Close #36114

PR Close #37226
2020-05-20 14:49:21 -07:00
3eee53603c docs: update docs for microbenchmarks (#37140)
Update docs in the micro benchmarks to include:
* How to run with no turbo inlining
* Where to find the profiles in the DevTools
* Best way to debug benchmarks (using the profile_in_browser rather than --inspect-brk)

PR Close #37140
2020-05-20 13:37:28 -07:00
5c94fa97c1 build: complete removal of bazel format yarn commands (#37148)
Remove bazel yarn format deprecation message to complete the removal of
formatting bazel related files via yarn command command.

PR Close #37148
2020-05-20 13:32:41 -07:00
468f4a3f9e feat(dev-infra): create logging utils with support for defined logging levels (#37192)
Creates common logging functions at different levels. Allows for providing
logging statements which are actually printed to the console based on the
LOG_LEVEL environment variable.

PR Close #37192
2020-05-20 13:32:06 -07:00
a2393bef02 fix(dev-infra): ignore dev-infra scope while checking for features in patch branch (#37210)
This commit updates the script that checks master and patch branches to ignore features with `dev-infra` scope
 while verifying that there are no feature commits in patch branch. It's ok and in fact desirable for dev-infra features to be on the patch branch.

PR Close #37210
2020-05-20 13:31:38 -07:00
5bd1c7bbf7 ci: testing folders in compiler-cli should not require fw-testingapproval (#37220)
The `fw-testing` PullApprove group is really designed to
capture the top level public testing API groups in packages
like `common` and `router`.

The compiler-cli also has some folders that contain the path
segment `testing` but these should not require `fw-testing`
PullApprove approval.

This commit excludes the whole of `compiler-cli` package from
the `fw-testing` group.

PR Close #37220
2020-05-20 13:31:07 -07:00
19313f7dad Revert "fix(elements): correctly handle getting/setting properties before connecting the element (#36114)"
This reverts commit 363f14c893
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:07:39 -07:00
4e2500278c Revert "refactor(elements): simplify accessing NgElementStrategy (#36114)"
This reverts commit fa073bafbd
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:07:17 -07:00
2c4cfa6f2c Revert "refactor(elements): remove unused imports and properties (#36114)"
This reverts commit 2ed66acdd9
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:06:52 -07:00
c8f0c3b637 Revert "test(elements): only declare helpers if needed (#36114)"
This reverts commit 7e56222afb
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:06:29 -07:00
cf4883240b Revert "test(elements): clean up TestStrategy between tests (#36114)"
This reverts commit d98d0dbf17
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:06:08 -07:00
6f3157fe6d Revert "fix(elements): capture input properties set before upgrading the element (#36114)"
This reverts commit 1c8f1799d4
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:05:46 -07:00
12d8af50dd Revert "fix(elements): do not break when the constructor of an Angular Element is not called (#36114)"
This reverts commit 87b9f08d3b
because it is causing the side effects test to break on the
9.1.x branch.
2020-05-20 11:04:55 -07:00
52 changed files with 808 additions and 296 deletions

View File

@ -1,5 +1,3 @@
import {exec} from 'shelljs';
import {MergeConfig} from './dev-infra/pr/merge/config';
// The configuration for `ng-dev commit-message` commands.
@ -82,33 +80,11 @@ const github = {
name: 'angular',
};
/**
* Gets the name of the current patch branch. The patch branch is determined by
* looking for upstream branches that follow the format of `{major}.{minor}.x`.
*/
const getPatchBranchName = (): string => {
const branches =
exec(
`git ls-remote --heads https://github.com/${github.owner}/${github.name}.git`,
{silent: true})
.trim()
.split('\n');
for (let i = branches.length - 1; i >= 0; i--) {
const branchName = branches[i];
const matches = branchName.match(/refs\/heads\/([0-9]+\.[0-9]+\.x)/);
if (matches !== null) {
return matches[1];
}
}
throw Error('Could not determine patch branch name.');
};
// 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 = () => {
const patchBranch = getPatchBranchName();
// TODO: resume dynamically determining patch branch
const patch = '10.0.x';
const config: MergeConfig = {
githubApiMerge: false,
claSignedLabel: 'cla: yes',
@ -121,18 +97,18 @@ const merge = () => {
},
{
pattern: 'PR target: patch-only',
branches: [patchBranch],
branches: [patch],
},
{
pattern: 'PR target: master & patch',
branches: ['master', patchBranch],
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.
'master': '4341743b4a6d7e23c6f944aa9e34166b701369a1',
[patchBranch]: '2a53f471592f424538802907aca1f60f1177a86d'
[patch]: '2a53f471592f424538802907aca1f60f1177a86d'
},
};
return config;

View File

@ -568,7 +568,7 @@ groups:
- *can-be-global-approved
- *can-be-global-docs-approved
- >
contains_any_globs(files, [
contains_any_globs(files.exclude('packages/compiler-cli/**'), [
'**/testing/**',
'aio/content/guide/testing.md',
'aio/content/examples/testing/**',

View File

@ -1,3 +1,49 @@
<a name="9.1.12"></a>
## [9.1.12](https://github.com/angular/angular/compare/9.1.11...9.1.12) (2020-07-08)
### Bug Fixes
* **core:** infinite loop if injectable using inheritance has a custom decorator ([6c1ab47](https://github.com/angular/angular/commit/6c1ab47)), closes [#35733](https://github.com/angular/angular/issues/35733)
<a name="9.1.11"></a>
## [9.1.11](https://github.com/angular/angular/compare/9.1.10...9.1.11) (2020-06-10)
### Reverts
* **elements:** fire custom element output events during component initialization ([dc9da17](https://github.com/angular/angular/commit/dc9da17))
<a name="9.1.10"></a>
## [9.1.10](https://github.com/angular/angular/compare/9.1.9...9.1.10) (2020-06-09)
### Bug Fixes
* **elements:** fire custom element output events during component initialization ([454e073](https://github.com/angular/angular/commit/454e073)), closes [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts#L167-L170](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts/issues/L167-L170) [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts#L164](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts/issues/L164) [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/component-factory-strategy.ts#L158](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/component-factory-strategy.ts/issues/L158) [#36141](https://github.com/angular/angular/issues/36141)
### Performance Improvements
* **ngcc:** cache parsed tsconfig between runs ([1aae94a](https://github.com/angular/angular/commit/1aae94a)), closes [#37417](https://github.com/angular/angular/issues/37417) [#36882](https://github.com/angular/angular/issues/36882)
<a name="9.1.9"></a>
## [9.1.9](https://github.com/angular/angular/compare/9.1.8...9.1.9) (2020-05-20)
This release contains a re-submit of the following 3 commits with fixes for TS 3.8.
### Bug Fixes
* **elements:** capture input properties set before upgrading the element ([#36114](https://github.com/angular/angular/issues/36114)) ([#37226](https://github.com/angular/angular/issues/37226)) ([a33cb2d](https://github.com/angular/angular/commit/a33cb2d)), closes [#30848](https://github.com/angular/angular/issues/30848) [#31416](https://github.com/angular/angular/issues/31416)
* **elements:** correctly handle getting/setting properties before connecting the element ([#36114](https://github.com/angular/angular/issues/36114)) ([#37226](https://github.com/angular/angular/issues/37226)) ([6ac0042](https://github.com/angular/angular/commit/6ac0042)), closes [/github.com/angular/angular/pull/31416/files#r300326698](https://github.com//github.com/angular/angular/pull/31416/files/issues/r300326698)
* **elements:** do not break when the constructor of an Angular Element is not called ([#36114](https://github.com/angular/angular/issues/36114)) ([#37226](https://github.com/angular/angular/issues/37226)) ([1465372](https://github.com/angular/angular/commit/1465372))
<a name="9.1.8"></a>
## [9.1.8](https://github.com/angular/angular/compare/9.1.6...9.1.8) (2020-05-20)

View File

@ -1,5 +1,6 @@
{
"hosting": {
"target": "aio",
"public": "dist",
"cleanUrls": true,
"redirects": [

View File

@ -33,7 +33,7 @@ else
readonly majorVersionStable=${CI_STABLE_BRANCH%%.*}
# Do not deploy if the major version is not less than the stable branch major version
if [[ !( "$majorVersion" < "$majorVersionStable" ) ]]; then
if (( $majorVersion >= $majorVersionStable )); then
echo "Skipping deploy of branch \"$CI_BRANCH\" to firebase."
echo "We only deploy archive branches with the major version less than the stable branch: \"$CI_STABLE_BRANCH\""
exit 0
@ -64,16 +64,27 @@ fi
case $deployEnv in
next)
readonly projectId=aio-staging
readonly siteId=$projectId
readonly deployedUrl=https://next.angular.io/
readonly firebaseToken=$CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN
;;
stable)
readonly projectId=angular-io
readonly siteId=$projectId
readonly deployedUrl=https://angular.io/
readonly firebaseToken=$CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN
;;
archive)
readonly projectId=v${majorVersion}-angular-io
# Special case v9-angular-io because its piloting the firebase hosting "multisites" setup
# See https://angular-team.atlassian.net/browse/DEV-125 for more info.
if [[ "$majorVersion" == "9" ]]; then
readonly projectId=aio-staging
readonly siteId=v9-angular-io
else
readonly projectId=v${majorVersion}-angular-io
readonly siteId=$projectId
fi
readonly deployedUrl=https://v${majorVersion}.angular.io/
readonly firebaseToken=$CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN
;;
@ -82,6 +93,7 @@ esac
echo "Git branch : $CI_BRANCH"
echo "Build/deploy mode : $deployEnv"
echo "Firebase project : $projectId"
echo "Firebase site : $siteId"
echo "Deployment URL : $deployedUrl"
if [[ ${1:-} == "--dry-run" ]]; then
@ -92,23 +104,29 @@ fi
(
cd "`dirname $0`/.."
# Build the app
echo "\n\n\n==== Build the aio app ====\n"
yarn build --configuration=$deployEnv --progress=false
# Include any mode-specific files
echo "\n\n\n==== Add any mode-specific files into the aio distribution ====\n"
cp -rf src/extra-files/$deployEnv/. dist/
# Set deployedUrl as parameter in the opensearch description
echo "\n\n\n==== Update opensearch descriptor for aio with the deployedUrl ====\n"
# deployedUrl must end with /
yarn set-opensearch-url $deployedUrl
# Check payload size
echo "\n\n\n==== Check payload size and upload the numbers to firebase db ====\n"
yarn payload-size
# Deploy to Firebase
yarn firebase use "$projectId" --token "$firebaseToken"
yarn firebase deploy --message "Commit: $CI_COMMIT" --non-interactive --token "$firebaseToken"
# Run PWA-score tests
echo "\n\n\n==== Deploy aio to firebase hosting ====\n"
yarn firebase use "${projectId}" --token "$firebaseToken"
yarn firebase target:apply hosting aio $siteId --token "$firebaseToken"
yarn firebase deploy --only hosting:aio --message "Commit: $CI_COMMIT" --non-interactive --token "$firebaseToken"
echo "\n\n\n==== Run PWA-score tests ====\n"
yarn test-pwa-score "$deployedUrl" "$CI_AIO_MIN_PWA_SCORE"
)

View File

@ -68,6 +68,7 @@ function check {
expected="Git branch : master
Build/deploy mode : next
Firebase project : aio-staging
Firebase site : aio-staging
Deployment URL : https://next.angular.io/"
check "$actual" "$expected"
)
@ -103,6 +104,7 @@ Deployment URL : https://next.angular.io/"
expected="Git branch : 4.3.x
Build/deploy mode : stable
Firebase project : angular-io
Firebase site : angular-io
Deployment URL : https://angular.io/"
check "$actual" "$expected"
)
@ -139,10 +141,37 @@ Deployment URL : https://angular.io/"
expected="Git branch : 2.4.x
Build/deploy mode : archive
Firebase project : v2-angular-io
Firebase site : v2-angular-io
Deployment URL : https://v2.angular.io/"
check "$actual" "$expected"
)
(
echo ===== archive - v9-angular-io multisite special case - deploy success
actual=$(
export BASH_ENV=/dev/null
export CI_REPO_OWNER=angular
export CI_REPO_NAME=angular
export CI_PULL_REQUEST=false
export CI_BRANCH=9.1.x
export CI_STABLE_BRANCH=10.0.x
export CI_COMMIT=$(git ls-remote origin 9.1.x | cut -c1-40)
export CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN=XXXXX
$deployToFirebaseDryRun
)
expected="Git branch : 9.1.x
Build/deploy mode : archive
Firebase project : aio-staging
Firebase site : v9-angular-io
Deployment URL : https://v9.angular.io/"
# TODO: This test incorrectly expects the Firebase project to be v9-angular-io.
# v9-angular-io is a "multisites" project currently within the aio-staging project
# This setup is temporary and was created in order to deploy v9.angular.io without
# disruptions.
# See https://angular-team.atlassian.net/browse/DEV-125 for more info.
check "$actual" "$expected"
)
(
echo ===== archive - skip deploy - commit not HEAD
actual=$(

View File

@ -5,13 +5,9 @@
</div>
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
<mat-toolbar-row class="notification-container">
<aio-notification notificationId="survey-march-2020" expirationDate="2020-04-15" [dismissOnContentClick]="true" (dismissed)="notificationDismissed()">
<a href="https://goo.gle/angular-survey-2020">
<mat-icon class="icon" svgIcon="insert_comment" aria-label="Announcement"></mat-icon>
<span class="message">Help Angular by taking a <b>1 minute survey</b>!</span>
<span class="action-button">Go to survey</span>
</a>
<mat-toolbar-row class="notification-container blm-message">
<aio-notification notificationId="blm-2020" expirationDate="2022-04-15" [dismissOnContentClick]="true" (dismissed)="notificationDismissed()">
#BlackLivesMatter
</aio-notification>
</mat-toolbar-row>
<mat-toolbar-row>

View File

@ -183,8 +183,8 @@ section#intro {
// ANGULAR LINE
.background-sky {
background-color: $blue;
background: $bluegradient;
background-color: $black;
background: $black;
color: $white;
}

View File

@ -20,9 +20,16 @@ mat-toolbar.mat-toolbar {
}
}
.blm-message {
text-align: center;
justify-content: center;
background: #2d2d2d;
font-size: 0.75em;
}
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR
aio-shell.page-home mat-toolbar.mat-toolbar {
background-color: $blue;
background-color: $black;
@media (min-width: 481px) {
&:not(.transitioning) {

View File

@ -30,7 +30,16 @@ var CIconfiguration = {
'Android7': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android8': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android9': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
// Disable Android 10 tests due to infrastructure failure.
// ex:
// Chrome Mobile 74.0.3729 (Android 0.0.0) ERROR:
// Error: XHR error loading
// http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js
//
// Error loading http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js as
// "../internal/operators/zip" from
// http://angular-ci.local:9876/base/node_modules/rxjs/operators/index.js
'Android10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari12': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari13': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},

View File

@ -32,6 +32,7 @@ ts_library(
"@npm//@types/events",
"@npm//@types/jasmine",
"@npm//@types/node",
"@npm//inquirer",
],
)

View File

@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as yargs from 'yargs';
import {info} from '../utils/console';
import {validateFile} from './validate-file';
import {validateCommitRange} from './validate-range';
@ -51,10 +54,10 @@ export function buildCommitMessageParser(localYargs: yargs.Argv) {
// If on CI, and not pull request number is provided, assume the branch
// being run on is an upstream branch.
if (process.env['CI'] && process.env['CI_PULL_REQUEST'] === 'false') {
console.info(
`Since valid commit messages are enforced by PR linting on CI, we do not\n` +
`need to validate commit messages on CI runs on upstream branches.\n\n` +
`Skipping check of provided commit range`);
info(`Since valid commit messages are enforced by PR linting on CI, we do not`);
info(`need to validate commit messages on CI runs on upstream branches.`);
info();
info(`Skipping check of provided commit range`);
return;
}
validateCommitRange(argv.range);

View File

@ -9,6 +9,7 @@ import {readFileSync} from 'fs';
import {resolve} from 'path';
import {getRepoBaseDir} from '../utils/config';
import {info} from '../utils/console';
import {validateCommitMessage} from './validate';
@ -16,7 +17,7 @@ import {validateCommitMessage} from './validate';
export function validateFile(filePath: string) {
const commitMessage = readFileSync(resolve(getRepoBaseDir(), filePath), 'utf8');
if (validateCommitMessage(commitMessage)) {
console.info('√ Valid commit message');
info('√ Valid commit message');
return;
}
// If the validation did not return true, exit as a failure.

View File

@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {exec} from 'shelljs';
import {info} from '../utils/console';
import {parseCommitMessage, validateCommitMessage, ValidateCommitMessageOptions} from './validate';
// Whether the provided commit is a fixup commit.
@ -31,7 +34,7 @@ export function validateCommitRange(range: string) {
// Separate the commits from a single string into individual commits
const commits = result.split(randomValueSeparator).map(l => l.trim()).filter(line => !!line);
console.info(`Examining ${commits.length} commit(s) in the provided range: ${range}`);
info(`Examining ${commits.length} commit(s) in the provided range: ${range}`);
// Check each commit in the commit range. Commits are allowed to be fixup commits for other
// commits in the provided commit range.
@ -46,7 +49,7 @@ export function validateCommitRange(range: string) {
});
if (allCommitsInRangeValid) {
console.info('√ All commit messages in range valid.');
info('√ All commit messages in range valid.');
} else {
// Exit with a non-zero exit code if invalid commit messages have
// been discovered.

View File

@ -5,6 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {error} from '../utils/console';
import {getCommitMessageConfig} from './config';
/** Options for commit message validation. */
@ -62,8 +64,8 @@ export function parseCommitMessage(commitMsg: string) {
/** Validate a commit message against using the local repo's config. */
export function validateCommitMessage(
commitMsg: string, options: ValidateCommitMessageOptions = {}) {
function error(errorMessage: string) {
console.error(
function printError(errorMessage: string) {
error(
`INVALID COMMIT MSG: \n` +
`${'─'.repeat(40)}\n` +
`${commitMsg}\n` +
@ -91,7 +93,7 @@ export function validateCommitMessage(
// the git history anyway, unless the options provided to not allow squash commits.
if (commit.isSquash) {
if (options.disallowSquash) {
error('The commit must be manually squashed into the target commit');
printError('The commit must be manually squashed into the target commit');
return false;
}
return true;
@ -104,7 +106,7 @@ export function validateCommitMessage(
// check.
if (commit.isFixup) {
if (options.nonFixupCommitHeaders && !options.nonFixupCommitHeaders.includes(commit.header)) {
error(
printError(
'Unable to find match for fixup commit among prior commits: ' +
(options.nonFixupCommitHeaders.map(x => `\n ${x}`).join('') || '-'));
return false;
@ -117,22 +119,23 @@ export function validateCommitMessage(
// Checking commit header //
////////////////////////////
if (commit.header.length > config.maxLineLength) {
error(`The commit message header is longer than ${config.maxLineLength} characters`);
printError(`The commit message header is longer than ${config.maxLineLength} characters`);
return false;
}
if (!commit.type) {
error(`The commit message header does not match the expected format.`);
printError(`The commit message header does not match the expected format.`);
return false;
}
if (!config.types.includes(commit.type)) {
error(`'${commit.type}' is not an allowed type.\n => TYPES: ${config.types.join(', ')}`);
printError(`'${commit.type}' is not an allowed type.\n => TYPES: ${config.types.join(', ')}`);
return false;
}
if (commit.scope && !config.scopes.includes(commit.scope)) {
error(`'${commit.scope}' is not an allowed scope.\n => SCOPES: ${config.scopes.join(', ')}`);
printError(
`'${commit.scope}' is not an allowed scope.\n => SCOPES: ${config.scopes.join(', ')}`);
return false;
}
@ -146,14 +149,14 @@ export function validateCommitMessage(
//////////////////////////
if (commit.bodyWithoutLinking.trim().length < config.minBodyLength) {
error(`The commit message body does not meet the minimum length of ${
printError(`The commit message body does not meet the minimum length of ${
config.minBodyLength} characters`);
return false;
}
const bodyByLine = commit.body.split('\n');
if (bodyByLine.some(line => line.length > config.maxLineLength)) {
error(
printError(
`The commit messsage body contains lines greater than ${config.maxLineLength} characters`);
return false;
}

View File

@ -34,35 +34,9 @@ export function buildFormatParser(localYargs: yargs.Argv) {
const executionCmd = check ? checkFiles : formatFiles;
executionCmd(allChangedFilesSince(sha));
})
.command(
'files <files..>', 'Run the formatter on provided files', {},
({check, files}) => {
const executionCmd = check ? checkFiles : formatFiles;
executionCmd(files);
})
// TODO(josephperrott): remove this hidden command after deprecation period.
.command('deprecation-warning [originalCommand]', false, {}, ({originalCommand}) => {
console.warn(`\`yarn ${
originalCommand}\` is deprecated in favor of running the formatter via ng-dev`);
console.warn();
console.warn(`As a replacement of \`yarn ${originalCommand}\`, run:`);
switch (originalCommand) {
case 'bazel:format':
case 'bazel:lint-fix':
console.warn(` yarn ng-dev format all`);
break;
case 'bazel:lint':
console.warn(` yarn ng-dev format all --check`);
break;
default:
console.warn(`Error: Unrecognized previous command.`);
}
console.warn();
console.warn(`You can find more usage information by running:`);
console.warn(` yarn ng-dev format --help`);
console.warn();
console.warn(`For more on the rationale and effects of this deprecation visit:`);
console.warn(` https://github.com/angular/angular/pull/36842#issue-410321447`);
.command('files <files..>', 'Run the formatter on provided files', {}, ({check, files}) => {
const executionCmd = check ? checkFiles : formatFiles;
executionCmd(files);
});
}

View File

@ -7,6 +7,9 @@
*/
import {prompt} from 'inquirer';
import {error, info} from '../utils/console';
import {runFormatterInParallel} from './run-commands-parallel';
/**
@ -17,16 +20,16 @@ export async function formatFiles(files: string[]) {
let failures = await runFormatterInParallel(files, 'format');
if (failures === false) {
console.info('No files matched for formatting.');
info('No files matched for formatting.');
process.exit(0);
}
// The process should exit as a failure if any of the files failed to format.
if (failures.length !== 0) {
console.error(`Formatting failed, see errors above for more information.`);
error(`Formatting failed, see errors above for more information.`);
process.exit(1);
}
console.info(`√ Formatting complete.`);
info(`√ Formatting complete.`);
process.exit(0);
}
@ -38,18 +41,18 @@ export async function checkFiles(files: string[]) {
const failures = await runFormatterInParallel(files, 'check');
if (failures === false) {
console.info('No files matched for formatting check.');
info('No files matched for formatting check.');
process.exit(0);
}
if (failures.length) {
// Provide output expressing which files are failing formatting.
console.group('\nThe following files are out of format:');
info.group('\nThe following files are out of format:');
for (const file of failures) {
console.info(` - ${file}`);
info(` - ${file}`);
}
console.groupEnd();
console.info();
info.groupEnd();
info();
// If the command is run in a non-CI environment, prompt to format the files immediately.
let runFormatter = false;
@ -67,13 +70,13 @@ export async function checkFiles(files: string[]) {
process.exit(0);
} else {
// Inform user how to format files in the future.
console.info();
console.info(`To format the failing file run the following command:`);
console.info(` yarn ng-dev format files ${failures.join(' ')}`);
info();
info(`To format the failing file run the following command:`);
info(` yarn ng-dev format files ${failures.join(' ')}`);
process.exit(1);
}
} else {
console.info('√ All files correctly formatted.');
info('√ All files correctly formatted.');
process.exit(0);
}
}

View File

@ -9,6 +9,7 @@
import {join} from 'path';
import {getRepoBaseDir} from '../../utils/config';
import {error} from '../../utils/console';
import {Formatter} from './base-formatter';
@ -35,9 +36,9 @@ export class Buildifier extends Formatter {
callback:
(file: string, code: number, _: string, stderr: string) => {
if (code !== 0) {
console.error(`Error running buildifier on: ${file}`);
console.error(stderr);
console.error();
error(`Error running buildifier on: ${file}`);
error(stderr);
error();
return true;
}
return false;

View File

@ -9,6 +9,7 @@
import {join} from 'path';
import {getRepoBaseDir} from '../../utils/config';
import {error} from '../../utils/console';
import {Formatter} from './base-formatter';
@ -35,9 +36,9 @@ export class ClangFormat extends Formatter {
callback:
(file: string, code: number, _: string, stderr: string) => {
if (code !== 0) {
console.error(`Error running clang-format on: ${file}`);
console.error(stderr);
console.error();
error(`Error running clang-format on: ${file}`);
error(stderr);
error();
return true;
}
return false;

View File

@ -11,6 +11,8 @@ import * as multimatch from 'multimatch';
import {cpus} from 'os';
import {exec} from 'shelljs';
import {info} from '../utils/console';
import {Formatter, FormatterAction, getActiveFormatters} from './formatters';
const AVAILABLE_THREADS = Math.max(cpus().length - 1, 1);
@ -47,10 +49,10 @@ export function runFormatterInParallel(allFiles: string[], action: FormatterActi
switch (action) {
case 'format':
console.info(`Formatting ${pendingCommands.length} file(s)`);
info(`Formatting ${pendingCommands.length} file(s)`);
break;
case 'check':
console.info(`Checking format of ${pendingCommands.length} file(s)`);
info(`Checking format of ${pendingCommands.length} file(s)`);
break;
default:
throw Error(`Invalid format action "${action}": allowed actions are "format" and "check"`);

View File

@ -1,5 +1,7 @@
import {Arguments, Argv} from 'yargs';
import {error} from '../../utils/console';
import {discoverNewConflictsForPr} from './index';
/** Builds the discover-new-conflicts pull request command. */
@ -16,7 +18,7 @@ export function buildDiscoverNewConflictsCommand(yargs: Argv) {
export async function handleDiscoverNewConflictsCommand({prNumber, date}: Arguments) {
// If a provided date is not able to be parsed, yargs provides it as NaN.
if (isNaN(date)) {
console.error('Unable to parse the value provided via --date flag');
error('Unable to parse the value provided via --date flag');
process.exit(1);
}
await discoverNewConflictsForPr(prNumber, date);

View File

@ -10,6 +10,7 @@ import {Bar} from 'cli-progress';
import {types as graphQLTypes} from 'typed-graphqlify';
import {getConfig, NgDevConfig} from '../../utils/config';
import {error, info} from '../../utils/console';
import {getCurrentBranch, hasLocalChanges} from '../../utils/git';
import {getPendingPrs} from '../../utils/github';
import {exec} from '../../utils/shelljs';
@ -57,7 +58,7 @@ export async function discoverNewConflictsForPr(
// If there are any local changes in the current repository state, the
// check cannot run as it needs to move between branches.
if (hasLocalChanges()) {
console.error('Cannot run with local changes. Please make sure there are no local changes.');
error('Cannot run with local changes. Please make sure there are no local changes.');
process.exit(1);
}
@ -68,15 +69,15 @@ export async function discoverNewConflictsForPr(
/* PRs which were found to be conflicting. */
const conflicts: Array<PullRequest> = [];
console.info(`Requesting pending PRs from Github`);
info(`Requesting pending PRs from Github`);
/** List of PRs from github currently known as mergable. */
const allPendingPRs = (await getPendingPrs(PR_SCHEMA, config.github)).map(processPr);
/** The PR which is being checked against. */
const requestedPr = allPendingPRs.find(pr => pr.number === newPrNumber);
if (requestedPr === undefined) {
console.error(
error(
`The request PR, #${newPrNumber} was not found as a pending PR on github, please confirm`);
console.error(`the PR number is correct and is an open PR`);
error(`the PR number is correct and is an open PR`);
process.exit(1);
}
@ -89,8 +90,8 @@ export async function discoverNewConflictsForPr(
// PRs updated after the provided date
pr.updatedAt >= updatedAfter);
});
console.info(`Retrieved ${allPendingPRs.length} total pending PRs`);
console.info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
info(`Retrieved ${allPendingPRs.length} total pending PRs`);
info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
// Fetch and checkout the PR being checked.
exec(`git fetch ${requestedPr.headRef.repository.url} ${requestedPr.headRef.name}`);
@ -100,7 +101,7 @@ export async function discoverNewConflictsForPr(
exec(`git fetch ${requestedPr.baseRef.repository.url} ${requestedPr.baseRef.name}`);
const result = exec(`git rebase FETCH_HEAD`);
if (result.code) {
console.error('The requested PR currently has conflicts');
error('The requested PR currently has conflicts');
cleanUpGitState(originalBranch);
process.exit(1);
}
@ -125,21 +126,23 @@ export async function discoverNewConflictsForPr(
}
// End the progress bar as all PRs have been processed.
progressBar.stop();
console.info(`\nResult:`);
info();
info(`Result:`);
cleanUpGitState(originalBranch);
// If no conflicts are found, exit successfully.
if (conflicts.length === 0) {
console.info(`No new conflicting PRs found after #${newPrNumber} merging`);
info(`No new conflicting PRs found after #${newPrNumber} merging`);
process.exit(0);
}
// Inform about discovered conflicts, exit with failure.
console.error(`${conflicts.length} PR(s) which conflict(s) after #${newPrNumber} merges:`);
error.group(`${conflicts.length} PR(s) which conflict(s) after #${newPrNumber} merges:`);
for (const pr of conflicts) {
console.error(` - ${pr.number}: ${pr.title}`);
error(` - ${pr.number}: ${pr.title}`);
}
error.groupEnd();
process.exit(1);
}

View File

@ -11,6 +11,7 @@ ts_library(
"@npm//@octokit/rest",
"@npm//@types/inquirer",
"@npm//@types/node",
"@npm//@types/semver",
"@npm//@types/yargs",
"@npm//chalk",
],

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import chalk from 'chalk';
import {Arguments, Argv} from 'yargs';
import {error, red, yellow} from '../../utils/console';
import {GITHUB_TOKEN_GENERATE_URL, mergePullRequest} from './index';
/** Builds the options for the merge command. */
@ -22,10 +24,9 @@ export function buildMergeCommand(yargs: Argv) {
export async function handleMergeCommand(args: Arguments) {
const githubToken = args.githubToken || process.env.GITHUB_TOKEN || process.env.TOKEN;
if (!githubToken) {
console.error(
chalk.red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
console.error(chalk.red('Alternatively, pass the `--github-token` command line flag.'));
console.error(chalk.yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
error(red('Alternatively, pass the `--github-token` command line flag.'));
error(yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
process.exit(1);
}

View File

@ -0,0 +1,68 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as semver from 'semver';
import {exec} from '../../utils/shelljs';
/**
* Helper function that can be used to determine merge branches based on a given
* project version. The function determines merge branches primarily through the
* specified version, but falls back to consulting the NPM registry when needed.
*
* Consulting the NPM registry for determining the patch branch may slow down merging,
* so whenever possible, the branches are determined statically based on the current
* version. In some cases, consulting the NPM registry is inevitable because for major
* pre-releases, we cannot determine the latest stable minor version from the current
* pre-release version.
*/
export function determineMergeBranches(
currentVersion: string, npmPackageName: string): {minor: string, patch: string} {
const projectVersion = semver.parse(currentVersion);
if (projectVersion === null) {
throw Error('Cannot parse version set in project "package.json" file.');
}
const {major, minor, patch, prerelease} = projectVersion;
const isMajor = minor === 0 && patch === 0;
const isMinor = minor !== 0 && patch === 0;
// If there is no prerelease, then we compute patch and minor branches based
// on the current version major and minor.
if (prerelease.length === 0) {
return {minor: `${major}.x`, patch: `${major}.${minor}.x`};
}
// If current version is set to a minor prerelease, we can compute the merge branches
// statically. e.g. if we are set to `9.3.0-next.0`, then our merge branches should
// be set to `9.x` and `9.2.x`.
if (isMinor) {
return {minor: `${major}.x`, patch: `${major}.${minor - 1}.x`};
} else if (!isMajor) {
throw Error('Unexpected version. Cannot have prerelease for patch version.');
}
// If we are set to a major prerelease, we cannot statically determine the stable patch
// branch (as the latest minor segment is unknown). We determine it by looking in the NPM
// registry for the latest stable release that will tell us about the current minor segment.
// e.g. if the current major is `v10.0.0-next.0`, then we need to look for the latest release.
// Let's say this is `v9.2.6`. Our patch branch will then be called `9.2.x`.
const latestVersion = exec(`yarn -s info ${npmPackageName} dist-tags.latest`).trim();
if (!latestVersion) {
throw Error('Could not determine version of latest release.');
}
const expectedMajor = major - 1;
const parsedLatestVersion = semver.parse(latestVersion);
if (parsedLatestVersion === null) {
throw Error(`Could not parse latest version from NPM registry: ${latestVersion}`);
} else if (parsedLatestVersion.major !== expectedMajor) {
throw Error(
`Expected latest release to have major version: v${expectedMajor}, ` +
`but got: v${latestVersion}`);
}
return {patch: `${expectedMajor}.${parsedLatestVersion.minor}.x`, minor: `${expectedMajor}.x`};
}

View File

@ -8,6 +8,9 @@
import * as Octokit from '@octokit/rest';
import {spawnSync, SpawnSyncOptions, SpawnSyncReturns} from 'child_process';
import {info} from '../../utils/console';
import {MergeConfigWithRemote} from './config';
/** Error for failed Github API requests. */
@ -74,7 +77,7 @@ export class GitClient {
// To improve the debugging experience in case something fails, we print all executed
// Git commands. Note that we do not want to print the token if is contained in the
// command. It's common to share errors with others if the tool failed.
console.info('Executing: git', this.omitGithubTokenFromMessage(args.join(' ')));
info('Executing: git', this.omitGithubTokenFromMessage(args.join(' ')));
const result = spawnSync('git', args, {
cwd: this._projectRoot,

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import chalk from 'chalk';
import {getRepoBaseDir} from '../../utils/config';
import {promptConfirm} from '../../utils/console';
import {error, green, info, promptConfirm, red, yellow} from '../../utils/console';
import {loadAndValidateConfig, MergeConfigWithRemote} from './config';
import {GithubApiRequestError} from './git';
@ -40,8 +39,8 @@ export async function mergePullRequest(
if (config === undefined) {
const {config: _config, errors} = loadAndValidateConfig();
if (errors) {
console.error(chalk.red('Invalid configuration:'));
errors.forEach(desc => console.error(chalk.yellow(` - ${desc}`)));
error(red('Invalid configuration:'));
errors.forEach(desc => error(yellow(` - ${desc}`)));
process.exit(1);
}
config = _config!;
@ -65,9 +64,9 @@ export async function mergePullRequest(
// Catch errors to the Github API for invalid requests. We want to
// exit the script with a better explanation of the error.
if (e instanceof GithubApiRequestError && e.status === 401) {
console.error(chalk.red('Github API request failed. ' + e.message));
console.error(chalk.yellow('Please ensure that your provided token is valid.'));
console.error(chalk.yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
error(red('Github API request failed. ' + e.message));
error(yellow('Please ensure that your provided token is valid.'));
error(yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
process.exit(1);
}
throw e;
@ -99,25 +98,25 @@ export async function mergePullRequest(
switch (status) {
case MergeStatus.SUCCESS:
console.info(chalk.green(`Successfully merged the pull request: ${prNumber}`));
info(green(`Successfully merged the pull request: ${prNumber}`));
return true;
case MergeStatus.DIRTY_WORKING_DIR:
console.error(chalk.red(
`Local working repository not clean. Please make sure there are ` +
`no uncommitted changes.`));
error(
red(`Local working repository not clean. Please make sure there are ` +
`no uncommitted changes.`));
return false;
case MergeStatus.UNKNOWN_GIT_ERROR:
console.error(chalk.red(
'An unknown Git error has been thrown. Please check the output ' +
'above for details.'));
error(
red('An unknown Git error has been thrown. Please check the output ' +
'above for details.'));
return false;
case MergeStatus.FAILED:
console.error(chalk.yellow(`Could not merge the specified pull request.`));
console.error(chalk.red(failure!.message));
error(yellow(`Could not merge the specified pull request.`));
error(red(failure!.message));
if (canForciblyMerge && !disableForceMergePrompt) {
console.info();
console.info(chalk.yellow('The pull request above failed due to non-critical errors.'));
console.info(chalk.yellow(`This error can be forcibly ignored if desired.`));
info();
info(yellow('The pull request above failed due to non-critical errors.'));
info(yellow(`This error can be forcibly ignored if desired.`));
return await promptAndPerformForceMerge();
}
return false;

View File

@ -8,6 +8,8 @@
import {Arguments, Argv} from 'yargs';
import {error} from '../../utils/console';
import {rebasePr} from './index';
/** URL to the Github page where personal access tokens can be generated. */
@ -25,9 +27,9 @@ export function buildRebaseCommand(yargs: Argv) {
export async function handleRebaseCommand(args: Arguments) {
const githubToken = args.githubToken || process.env.GITHUB_TOKEN || process.env.TOKEN;
if (!githubToken) {
console.error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
console.error('Alternatively, pass the `--github-token` command line flag.');
console.error(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`);
error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
error('Alternatively, pass the `--github-token` command line flag.');
error(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`);
process.exit(1);
}

View File

@ -6,12 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {prompt} from 'inquirer';
import {types as graphQLTypes} from 'typed-graphqlify';
import {URL} from 'url';
import {getConfig, NgDevConfig} from '../../utils/config';
import {promptConfirm} from '../../utils/console';
import {error, info, promptConfirm} from '../../utils/console';
import {getCurrentBranch, hasLocalChanges} from '../../utils/git';
import {getPr} from '../../utils/github';
import {exec} from '../../utils/shelljs';
@ -45,7 +44,7 @@ export async function rebasePr(
prNumber: number, githubToken: string, config: Pick<NgDevConfig, 'github'> = getConfig()) {
// TODO: Rely on a common assertNoLocalChanges function.
if (hasLocalChanges()) {
console.error('Cannot perform rebase of PR with local changes.');
error('Cannot perform rebase of PR with local changes.');
process.exit(1);
}
@ -65,7 +64,7 @@ export async function rebasePr(
// If the PR does not allow maintainers to modify it, exit as the rebased PR cannot
// be pushed up.
if (!pr.maintainerCanModify && !pr.viewerDidAuthor) {
console.error(
error(
`Cannot rebase as you did not author the PR and the PR does not allow maintainers` +
`to modify the PR`);
process.exit(1);
@ -73,51 +72,48 @@ export async function rebasePr(
try {
// Fetch the branch at the commit of the PR, and check it out in a detached state.
console.info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
exec(`git fetch ${headRefUrl} ${pr.headRef.name}`);
exec(`git checkout --detach FETCH_HEAD`);
// Fetch the PRs target branch and rebase onto it.
console.info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
exec(`git fetch ${baseRefUrl} ${pr.baseRef.name}`);
console.info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
const rebaseResult = exec(`git rebase FETCH_HEAD`);
// If the rebase was clean, push the rebased PR up to the authors fork.
if (rebaseResult.code === 0) {
console.info(`Rebase was able to complete automatically without conflicts`);
console.info(`Pushing rebased PR #${prNumber} to ${fullHeadRef}`);
info(`Rebase was able to complete automatically without conflicts`);
info(`Pushing rebased PR #${prNumber} to ${fullHeadRef}`);
exec(`git push ${baseRefUrl} HEAD:${pr.baseRef.name} --force-with-lease`);
console.info(`Rebased and updated PR #${prNumber}`);
info(`Rebased and updated PR #${prNumber}`);
cleanUpGitState();
process.exit(0);
}
} catch (err) {
console.error(err.message);
error(err.message);
cleanUpGitState();
process.exit(1);
}
// On automatic rebase failures, prompt to choose if the rebase should be continued
// manually or aborted now.
console.info(`Rebase was unable to complete automatically without conflicts.`);
info(`Rebase was unable to complete automatically without conflicts.`);
// If the command is run in a non-CI environment, prompt to format the files immediately.
const continueRebase =
process.env['CI'] === undefined && await promptConfirm('Manually complete rebase?');
if (continueRebase) {
console.info(
`After manually completing rebase, run the following command to update PR #${prNumber}:`);
console.info(
` $ git push ${pr.baseRef.repository.url} HEAD:${pr.baseRef.name} --force-with-lease`);
console.info();
console.info(
`To abort the rebase and return to the state of the repository before this command`);
console.info(`run the following command:`);
console.info(` $ git rebase --abort && git reset --hard && git checkout ${originalBranch}`);
info(`After manually completing rebase, run the following command to update PR #${prNumber}:`);
info(` $ git push ${pr.baseRef.repository.url} HEAD:${pr.baseRef.name} --force-with-lease`);
info();
info(`To abort the rebase and return to the state of the repository before this command`);
info(`run the following command:`);
info(` $ git rebase --abort && git reset --hard && git checkout ${originalBranch}`);
process.exit(1);
} else {
console.info(`Cleaning up git state, and restoring previous state.`);
info(`Cleaning up git state, and restoring previous state.`);
}
cleanUpGitState();

View File

@ -5,6 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {error} from '../utils/console';
import {convertConditionToFunction} from './condition_evaluator';
import {PullApproveGroupConfig} from './parse-yaml';
@ -58,9 +60,11 @@ export class PullApproveGroup {
matchedFiles: new Set(),
});
} catch (e) {
console.error(`Could not parse condition in group: ${this.groupName}`);
console.error(` - ${expression}`);
console.error(`Error:`, e.message, e.stack);
error(`Could not parse condition in group: ${this.groupName}`);
error(` - ${expression}`);
error(`Error:`);
error(e.message);
error(e.stack);
process.exit(1);
}
});
@ -84,7 +88,7 @@ export class PullApproveGroup {
`From the [${this.groupName}] group:\n` +
` - ${expression}` +
`\n\n${e.message} ${e.stack}\n\n`;
console.error(errMessage);
error(errMessage);
process.exit(1);
}
});

View File

@ -6,18 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {info} from '../utils/console';
import {PullApproveGroupResult} from './group';
/** Create logs for each pullapprove group result. */
export function logGroup(group: PullApproveGroupResult, matched = true) {
const conditions = matched ? group.matchedConditions : group.unmatchedConditions;
console.groupCollapsed(`[${group.groupName}]`);
info.group(`[${group.groupName}]`);
if (conditions.length) {
conditions.forEach(matcher => {
const count = matcher.matchedFiles.size;
console.info(`${count} ${count === 1 ? 'match' : 'matches'} - ${matcher.expression}`)
info(`${count} ${count === 1 ? 'match' : 'matches'} - ${matcher.expression}`);
});
console.groupEnd();
info.groupEnd();
}
}
@ -30,7 +31,7 @@ export function logHeader(...params: string[]) {
const rightSpace = fillWidth - leftSpace - headerText.length;
const fill = (count: number, content: string) => content.repeat(count);
console.info(`${fill(fillWidth, '─')}`);
console.info(`${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}`);
console.info(`${fill(fillWidth, '─')}`);
info(`${fill(fillWidth, '─')}`);
info(`${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}`);
info(`${fill(fillWidth, '─')}`);
}

View File

@ -10,6 +10,7 @@ import * as path from 'path';
import {cd, exec, set} from 'shelljs';
import {getRepoBaseDir} from '../utils/config';
import {info} from '../utils/console';
import {PullApproveGroup} from './group';
import {logGroup, logHeader} from './logging';
@ -67,38 +68,39 @@ export function verify(verbose = false) {
*/
logHeader('Overall Result');
if (verificationSucceeded) {
console.info('PullApprove verification succeeded!');
info('PullApprove verification succeeded!');
} else {
console.info(`PullApprove verification failed.\n`);
console.info(`Please update '.pullapprove.yml' to ensure that all necessary`);
console.info(`files/directories have owners and all patterns that appear in`);
console.info(`the file correspond to actual files/directories in the repo.`);
info(`PullApprove verification failed.`);
info();
info(`Please update '.pullapprove.yml' to ensure that all necessary`);
info(`files/directories have owners and all patterns that appear in`);
info(`the file correspond to actual files/directories in the repo.`);
}
/**
* File by file Summary
*/
logHeader('PullApprove results by file');
console.groupCollapsed(`Matched Files (${matchedFiles.length} files)`);
verbose && matchedFiles.forEach(file => console.info(file));
console.groupEnd();
console.groupCollapsed(`Unmatched Files (${unmatchedFiles.length} files)`);
unmatchedFiles.forEach(file => console.info(file));
console.groupEnd();
info.group(`Matched Files (${matchedFiles.length} files)`);
verbose && matchedFiles.forEach(file => info(file));
info.groupEnd();
info.group(`Unmatched Files (${unmatchedFiles.length} files)`);
unmatchedFiles.forEach(file => info(file));
info.groupEnd();
/**
* Group by group Summary
*/
logHeader('PullApprove results by group');
console.groupCollapsed(`Groups skipped (${groupsSkipped.length} groups)`);
verbose && groupsSkipped.forEach(group => console.info(`${group.groupName}`));
console.groupEnd();
info.group(`Groups skipped (${groupsSkipped.length} groups)`);
verbose && groupsSkipped.forEach(group => info(`${group.groupName}`));
info.groupEnd();
const matchedGroups = resultsByGroup.filter(group => !group.unmatchedCount);
console.groupCollapsed(`Matched conditions by Group (${matchedGroups.length} groups)`);
info.group(`Matched conditions by Group (${matchedGroups.length} groups)`);
verbose && matchedGroups.forEach(group => logGroup(group));
console.groupEnd();
info.groupEnd();
const unmatchedGroups = resultsByGroup.filter(group => group.unmatchedCount);
console.groupCollapsed(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`);
info.group(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`);
unmatchedGroups.forEach(group => logGroup(group, false));
console.groupEnd();
info.groupEnd();
// Provide correct exit code based on verification success.
process.exit(verificationSucceeded ? 0 : 1);

View File

@ -8,6 +8,7 @@ ts_library(
module_name = "@angular/dev-infra-private/release",
visibility = ["//dev-infra:__subpackages__"],
deps = [
"//dev-infra/utils",
"@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yargs",

View File

@ -8,6 +8,8 @@
import {exec as _exec} from 'shelljs';
import {info} from '../utils/console';
/**
* Log the environment variables expected by bazel for stamping.
*

View File

@ -16,6 +16,7 @@
"inquirer": "<from-root>",
"minimatch": "<from-root>",
"multimatch": "<from-root>",
"semver": "<from-root>",
"shelljs": "<from-root>",
"typed-graphqlify": "<from-root>",
"yaml": "<from-root>",

View File

@ -8,6 +8,8 @@
import {dirname, isAbsolute, resolve} from 'path';
import {error} from '../utils/console';
import {ModuleResolver} from './analyzer';
@ -52,8 +54,8 @@ export function loadTestConfig(configPath: string): CircularDependenciesTestConf
}
return config;
} catch (e) {
console.error('Could not load test configuration file at: ' + configPath);
console.error(`Failed with: ${e.message}`);
error('Could not load test configuration file at: ' + configPath);
error(`Failed with: ${e.message}`);
process.exit(1);
}
}

View File

@ -12,7 +12,8 @@ import {sync as globSync} from 'glob';
import {isAbsolute, relative, resolve} from 'path';
import * as ts from 'typescript';
import * as yargs from 'yargs';
import chalk from 'chalk';
import {green, info, error, red, yellow} from '../utils/console';
import {Analyzer, ReferenceChain} from './analyzer';
import {compareGoldens, convertReferenceChainToGolden, Golden} from './golden';
@ -66,15 +67,14 @@ export function main(
const actual = convertReferenceChainToGolden(cycles, baseDir);
console.info(
chalk.green(` Current number of cycles: ${chalk.yellow(cycles.length.toString())}`));
info(green(` Current number of cycles: ${yellow(cycles.length.toString())}`));
if (approve) {
writeFileSync(goldenFile, JSON.stringify(actual, null, 2));
console.info(chalk.green('✅ Updated golden file.'));
info(green('✅ Updated golden file.'));
return 0;
} else if (!existsSync(goldenFile)) {
console.error(chalk.red(`❌ Could not find golden file: ${goldenFile}`));
error(red(`❌ Could not find golden file: ${goldenFile}`));
return 1;
}
@ -84,17 +84,15 @@ export function main(
// it's common that third-party modules are not resolved/visited. Also generated files
// from the View Engine compiler (i.e. factories, summaries) cannot be resolved.
if (printWarnings && warningsCount !== 0) {
console.info(chalk.yellow('⚠ The following imports could not be resolved:'));
Array.from(analyzer.unresolvedModules)
.sort()
.forEach(specifier => console.info(`${specifier}`));
info(yellow('⚠ The following imports could not be resolved:'));
Array.from(analyzer.unresolvedModules).sort().forEach(specifier => info(`${specifier}`));
analyzer.unresolvedFiles.forEach((value, key) => {
console.info(`${getRelativePath(baseDir, key)}`);
value.sort().forEach(specifier => console.info(` ${specifier}`));
info(`${getRelativePath(baseDir, key)}`);
value.sort().forEach(specifier => info(` ${specifier}`));
});
} else {
console.info(chalk.yellow(`${warningsCount} imports could not be resolved.`));
console.info(chalk.yellow(` Please rerun with "--warnings" to inspect unresolved imports.`));
info(yellow(`${warningsCount} imports could not be resolved.`));
info(yellow(` Please rerun with "--warnings" to inspect unresolved imports.`));
}
const expected: Golden = JSON.parse(readFileSync(goldenFile, 'utf8'));
@ -102,25 +100,24 @@ export function main(
const isMatching = fixedCircularDeps.length === 0 && newCircularDeps.length === 0;
if (isMatching) {
console.info(chalk.green('✅ Golden matches current circular dependencies.'));
info(green('✅ Golden matches current circular dependencies.'));
return 0;
}
console.error(chalk.red('❌ Golden does not match current circular dependencies.'));
error(red('❌ Golden does not match current circular dependencies.'));
if (newCircularDeps.length !== 0) {
console.error(chalk.yellow(` New circular dependencies which are not allowed:`));
newCircularDeps.forEach(c => console.error(`${convertReferenceChainToString(c)}`));
console.error();
error(yellow(` New circular dependencies which are not allowed:`));
newCircularDeps.forEach(c => error(`${convertReferenceChainToString(c)}`));
error();
}
if (fixedCircularDeps.length !== 0) {
console.error(
chalk.yellow(` Fixed circular dependencies that need to be removed from the golden:`));
fixedCircularDeps.forEach(c => console.error(`${convertReferenceChainToString(c)}`));
console.error();
error(yellow(` Fixed circular dependencies that need to be removed from the golden:`));
fixedCircularDeps.forEach(c => error(` ${convertReferenceChainToString(c)}`));
error();
if (approveCommand) {
console.info(chalk.yellow(` Please approve the new golden with: ${approveCommand}`));
info(yellow(` Please approve the new golden with: ${approveCommand}`));
} else {
console.info(chalk.yellow(
info(yellow(
` Please update the golden. The following command can be ` +
`run: yarn ts-circular-deps approve ${getRelativePath(process.cwd(), goldenFile)}.`));
}

View File

@ -10,6 +10,7 @@ ts_library(
"@npm//@types/inquirer",
"@npm//@types/node",
"@npm//@types/shelljs",
"@npm//chalk",
"@npm//shelljs",
"@npm//tslib",
"@npm//typed-graphqlify",

View File

@ -7,8 +7,10 @@
*/
import {existsSync} from 'fs';
import {join} from 'path';
import {dirname, join} from 'path';
import {exec} from 'shelljs';
import {error} from './console';
import {isTsNodeAvailable} from './ts-node';
/**
@ -83,14 +85,19 @@ function readConfigFile(configPath: string): object {
// version of the given configuration seems to exist, set up `ts-node` if available.
if (require.extensions['.ts'] === undefined && existsSync(`${configPath}.ts`) &&
isTsNodeAvailable()) {
require('ts-node').register({skipProject: true, transpileOnly: true});
// Ensure the module target is set to `commonjs`. This is necessary because the
// dev-infra tool runs in NodeJS which does not support ES modules by default.
// Additionally, set the `dir` option to the directory that contains the configuration
// file. This allows for custom compiler options (such as `--strict`).
require('ts-node').register(
{dir: dirname(configPath), transpileOnly: true, compilerOptions: {module: 'commonjs'}});
}
try {
return require(configPath)
return require(configPath);
} catch (e) {
console.error('Could not read configuration file.');
console.error(e);
error('Could not read configuration file.');
error(e);
process.exit(1);
}
}
@ -103,9 +110,9 @@ export function assertNoErrors(errors: string[]) {
if (errors.length == 0) {
return;
}
console.error(`Errors discovered while loading configuration file:`);
for (const error of errors) {
console.error(` - ${error}`);
error(`Errors discovered while loading configuration file:`);
for (const err of errors) {
error(` - ${err}`);
}
process.exit(1);
}

View File

@ -6,8 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import chalk from 'chalk';
import {prompt} from 'inquirer';
/** Reexport of chalk colors for convenient access. */
export const red: typeof chalk = chalk.red;
export const green: typeof chalk = chalk.green;
export const yellow: typeof chalk = chalk.yellow;
/** Prompts the user with a confirmation question and a specified message. */
export async function promptConfirm(message: string, defaultValue = false): Promise<boolean> {
return (await prompt<{result: boolean}>({
@ -18,3 +25,86 @@ export async function promptConfirm(message: string, defaultValue = false): Prom
}))
.result;
}
/**
* Supported levels for logging functions.
*
* Levels are mapped to numbers to represent a hierarchy of logging levels.
*/
export enum LOG_LEVELS {
SILENT = 0,
ERROR = 1,
WARN = 2,
LOG = 3,
INFO = 4,
DEBUG = 5,
}
/** Default log level for the tool. */
export const DEFAULT_LOG_LEVEL = LOG_LEVELS.INFO;
/** Write to the console for at INFO logging level */
export const info = buildLogLevelFunction(() => console.info, LOG_LEVELS.INFO);
/** Write to the console for at ERROR logging level */
export const error = buildLogLevelFunction(() => console.error, LOG_LEVELS.ERROR);
/** Write to the console for at DEBUG logging level */
export const debug = buildLogLevelFunction(() => console.debug, LOG_LEVELS.DEBUG);
/** Write to the console for at LOG logging level */
// tslint:disable-next-line: no-console
export const log = buildLogLevelFunction(() => console.log, LOG_LEVELS.LOG);
/** Write to the console for at WARN logging level */
export const warn = buildLogLevelFunction(() => console.warn, LOG_LEVELS.WARN);
/** Build an instance of a logging function for the provided level. */
function buildLogLevelFunction(loadCommand: () => Function, level: LOG_LEVELS) {
/** Write to stdout for the LOG_LEVEL. */
const loggingFunction = (...text: string[]) => {
runConsoleCommand(loadCommand, level, ...text);
};
/** Start a group at the LOG_LEVEL, optionally starting it as collapsed. */
loggingFunction.group = (text: string, collapsed = false) => {
const command = collapsed ? console.groupCollapsed : console.group;
runConsoleCommand(() => command, level, text);
};
/** End the group at the LOG_LEVEL. */
loggingFunction.groupEnd = () => {
runConsoleCommand(() => console.groupEnd, level);
};
return loggingFunction;
}
/**
* Run the console command provided, if the environments logging level greater than the
* provided logging level.
*
* The loadCommand takes in a function which is called to retrieve the console.* function
* to allow for jasmine spies to still work in testing. Without this method of retrieval
* the console.* function, the function is saved into the closure of the created logging
* function before jasmine can spy.
*/
function runConsoleCommand(loadCommand: () => Function, logLevel: LOG_LEVELS, ...text: string[]) {
if (getLogLevel() >= logLevel) {
loadCommand()(...text);
}
}
/**
* Retrieve the log level from environment variables, if the value found
* based on the LOG_LEVEL environment variable is undefined, return the default
* logging level.
*/
function getLogLevel() {
const logLevelEnvValue: any = (process.env[`LOG_LEVEL`] || '').toUpperCase();
const logLevel = LOG_LEVELS[logLevelEnvValue];
if (logLevel === undefined) {
return DEFAULT_LOG_LEVEL;
}
return logLevel;
}

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "9.1.8",
"version": "9.1.12",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@ -16,9 +16,11 @@
"url": "https://github.com/angular/angular.git"
},
"scripts": {
"bazel:format": "yarn -s ng-dev format deprecation-warning bazel:format",
"bazel:lint": "yarn -s ng-dev format deprecation-warning bazel:lint",
"bazel:lint-fix": "yarn -s ng-dev format deprecation-warning bazel:lint-fix",
"/": "",
"// 1": "Many developer of our checks/scripts/tools have moved to our ng-dev tool",
"// 2": "Find the usage you are looking for with:",
"// 3": "yarn ng-dev --help",
"/ ": "",
"preinstall": "node tools/yarn/check-yarn.js",
"postinstall": "node scripts/webdriver-manager-update.js && node --preserve-symlinks --preserve-symlinks-main ./tools/postinstall-patches.js",
"check-env": "gulp check-env",

View File

@ -12,7 +12,7 @@ import {AsyncNgccOptions, NgccOptions, SyncNgccOptions} from './src/ngcc_options
export {ConsoleLogger} from './src/logging/console_logger';
export {Logger, LogLevel} from './src/logging/logger';
export {AsyncNgccOptions, NgccOptions, SyncNgccOptions} from './src/ngcc_options';
export {AsyncNgccOptions, clearTsConfigCache, NgccOptions, SyncNgccOptions} from './src/ngcc_options';
export {PathMappings} from './src/path_mappings';
export function process(options: AsyncNgccOptions): Promise<void>;

View File

@ -156,7 +156,7 @@ export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOp
const absBasePath = absoluteFrom(options.basePath);
const projectPath = fileSystem.dirname(absBasePath);
const tsConfig =
options.tsConfigPath !== null ? readConfiguration(options.tsConfigPath || projectPath) : null;
options.tsConfigPath !== null ? getTsConfig(options.tsConfigPath || projectPath) : null;
let {
basePath,
@ -200,3 +200,28 @@ export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOp
new InPlaceFileWriter(fileSystem, logger, errorOnFailedEntryPoint),
};
}
let tsConfigCache: ParsedConfiguration|null = null;
let tsConfigPathCache: string|null = null;
/**
* Get the parsed configuration object for the given `tsConfigPath`.
*
* This function will cache the previous parsed configuration object to avoid unnecessary processing
* of the tsconfig.json in the case that it is requested repeatedly.
*
* This makes the assumption, which is true as of writing, that the contents of tsconfig.json and
* its dependencies will not change during the life of the process running ngcc.
*/
function getTsConfig(tsConfigPath: string): ParsedConfiguration|null {
if (tsConfigPath !== tsConfigPathCache) {
tsConfigPathCache = tsConfigPath;
tsConfigCache = readConfiguration(tsConfigPath);
}
return tsConfigCache;
}
export function clearTsConfigCache() {
tsConfigPathCache = null;
tsConfigCache = null;
}

View File

@ -14,6 +14,7 @@ import {Folder, MockFileSystem, runInEachFileSystem, TestFile} from '../../../sr
import {loadStandardTestFiles, loadTestFiles} from '../../../test/helpers';
import {getLockFilePath} from '../../src/locking/lock_file';
import {mainNgcc} from '../../src/main';
import {clearTsConfigCache} from '../../src/ngcc_options';
import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker';
import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point';
import {EntryPointManifestFile} from '../../src/packages/entry_point_manifest';
@ -41,6 +42,10 @@ runInEachFileSystem(() => {
spyOn(os, 'cpus').and.returnValue([{model: 'Mock CPU'} as any]);
});
afterEach(() => {
clearTsConfigCache();
});
it('should run ngcc without errors for esm2015', () => {
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']}))
.not.toThrow();

View File

@ -0,0 +1,78 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {clearTsConfigCache, getSharedSetup, NgccOptions} from '../src/ngcc_options';
import {MockLogger} from './helpers/mock_logger';
runInEachFileSystem(() => {
let fs: FileSystem;
let _abs: typeof absoluteFrom;
let projectPath: AbsoluteFsPath;
beforeEach(() => {
fs = getFileSystem();
_abs = absoluteFrom;
projectPath = _abs('/project');
});
describe('getSharedSetup()', () => {
let pathToProjectTsConfig: AbsoluteFsPath;
let pathToCustomTsConfig: AbsoluteFsPath;
beforeEach(() => {
clearTsConfigCache();
pathToProjectTsConfig = fs.resolve(projectPath, 'tsconfig.json');
fs.ensureDir(fs.dirname(pathToProjectTsConfig));
fs.writeFile(pathToProjectTsConfig, '{"files": ["src/index.ts"]}');
pathToCustomTsConfig = _abs('/path/to/tsconfig.json');
fs.ensureDir(fs.dirname(pathToCustomTsConfig));
fs.writeFile(pathToCustomTsConfig, '{"files": ["custom/index.ts"]}');
});
it('should load the tsconfig.json at the project root if tsConfigPath is `undefined`', () => {
const setup = getSharedSetup({...createOptions()});
expect(setup.tsConfigPath).toBeUndefined();
expect(setup.tsConfig?.rootNames).toEqual([fs.resolve(projectPath, 'src/index.ts')]);
});
it('should load a specific tsconfig.json if tsConfigPath is a string', () => {
const setup = getSharedSetup({...createOptions(), tsConfigPath: pathToCustomTsConfig});
expect(setup.tsConfigPath).toEqual(pathToCustomTsConfig);
expect(setup.tsConfig?.rootNames).toEqual([_abs('/path/to/custom/index.ts')]);
});
it('should not load a tsconfig.json if tsConfigPath is `null`', () => {
const setup = getSharedSetup({...createOptions(), tsConfigPath: null});
expect(setup.tsConfigPath).toBe(null);
expect(setup.tsConfig).toBe(null);
});
});
/**
* This function creates an object that contains the minimal required properties for NgccOptions.
*/
function createOptions(): NgccOptions {
return {
async: false,
basePath: fs.resolve(projectPath, 'node_modules'),
propertiesToConsider: ['es2015'],
compileAllFormats: false,
createNewEntryPointFormats: false,
logger: new MockLogger(),
fileSystem: getFileSystem(),
errorOnFailedEntryPoint: true,
enableI18nLegacyMessageIdFormat: true,
invalidateEntryPointManifest: false,
};
}
});

View File

@ -657,16 +657,31 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
*/
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
return noSideEffects(() => {
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
if (factory !== null) {
return factory;
} else {
// There is no factory defined. Either this was improper usage of inheritance
// (no Angular decorator on the superclass) or there is no constructor at all
// in the inheritance chain. Since the two cases cannot be distinguished, the
// latter has to be assumed.
return (t) => new t();
const ownConstructor = type.prototype.constructor;
const ownFactory = ownConstructor[NG_FACTORY_DEF] || ɵɵgetFactoryOf(ownConstructor);
const objectPrototype = Object.prototype;
let parent = Object.getPrototypeOf(type.prototype).constructor;
// Go up the prototype until we hit `Object`.
while (parent && parent !== objectPrototype) {
const factory = parent[NG_FACTORY_DEF] || ɵɵgetFactoryOf(parent);
// If we hit something that has a factory and the factory isn't the same as the type,
// we've found the inherited factory. Note the check that the factory isn't the type's
// own factory is redundant in most cases, but if the user has custom decorators on the
// class, this lookup will start one level down in the prototype chain, causing us to
// find the own factory first and potentially triggering an infinite loop downstream.
if (factory && factory !== ownFactory) {
return factory;
}
parent = Object.getPrototypeOf(parent);
}
// There is no factory defined. Either this was improper usage of inheritance
// (no Angular decorator on the superclass) or there is no constructor at all
// in the inheritance chain. Since the two cases cannot be distinguished, the
// latter has to be assumed.
return t => new t();
});
}

View File

@ -80,5 +80,7 @@ To profile, append `_profile` to the target name and attach a debugger via chrom
To interactively edit/rerun benchmarks use `ibazel` instead of `bazel`.
To debug
- Follow the directions in `profile_in_browser.html`
OR
- `yarn bazel build --config=ivy //packages/core/test/render3/perf:noop_change_detection`
- `node --inspect-brk bazel-out/darwin-fastbuild/bin/packages/core/test/render3/perf/noop_change_detection.min_debug.es2015.js`

View File

@ -8,7 +8,11 @@
<ol>
<li>Build the benchmark using <tt>yarn bazel build //packages/core/test/render3/perf:${BENCHMARK}.min_debug.es2015.js --config=ivy</tt></li>
<li>Open this file using the <tt>file://</tt> protocol and add <tt>?benchmark=BENCHMARK</tt> to the URL.</li>
<li>Open debug console for details</li>
<li>
Note: You should likely run this in an incognito browser with the "no-turbo-inlining" flag.<br />
On Chrome, the command would be <code>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome -incognito --js-flags="--no-turbo-inlining"</code>
</li>
<li>Open debug console for details. Benchmark profiles are available in the "JavaScript Profiler" tab of Chrome DevTools.</li>
</ol>
</body>
</html>

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, InjectorType, Provider, RendererFactory2, ViewContainerRef, ɵNgModuleDef as NgModuleDef, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '../../src/core';
import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, InjectorType, Provider, RendererFactory2, Type, ViewContainerRef, ɵNgModuleDef as NgModuleDef, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '../../src/core';
import {forwardRef} from '../../src/di/forward_ref';
import {createInjector} from '../../src/di/r3_injector';
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetInheritedFactory, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
import {getInjector} from '../../src/render3/util/discovery_utils';
@ -1282,7 +1282,126 @@ describe('providers', () => {
expect(injector.get(Some).location).toEqual('From app component');
});
});
// Note: these tests check the behavior of `getInheritedFactory` specifically.
// Since `getInheritedFactory` is only generated in AOT, the tests can't be
// ported directly to TestBed while running in JIT mode.
describe('getInheritedFactory on class with custom decorator', () => {
function addFoo() {
return (constructor: Type<any>): any => {
const decoratedClass = class Extender extends constructor { foo = 'bar'; };
// On IE10 child classes don't inherit static properties by default. If we detect
// such a case, try to account for it so the tests are consistent between browsers.
if (Object.getPrototypeOf(decoratedClass) !== constructor) {
decoratedClass.prototype = constructor.prototype;
}
return decoratedClass;
};
}
it('should find the correct factories if a parent class has a custom decorator', () => {
class GrandParent {
static ɵfac = function GrandParent_Factory() {};
}
@addFoo()
class Parent extends GrandParent {
static ɵfac = function Parent_Factory() {};
}
class Child extends Parent {
static ɵfac = function Child_Factory() {};
}
expect(ɵɵgetInheritedFactory(Child).name).toBe('Parent_Factory');
expect(ɵɵgetInheritedFactory(Parent).name).toBe('GrandParent_Factory');
expect(ɵɵgetInheritedFactory(GrandParent).name).toBeFalsy();
});
it('should find the correct factories if a child class has a custom decorator', () => {
class GrandParent {
static ɵfac = function GrandParent_Factory() {};
}
class Parent extends GrandParent {
static ɵfac = function Parent_Factory() {};
}
@addFoo()
class Child extends Parent {
static ɵfac = function Child_Factory() {};
}
expect(ɵɵgetInheritedFactory(Child).name).toBe('Parent_Factory');
expect(ɵɵgetInheritedFactory(Parent).name).toBe('GrandParent_Factory');
expect(ɵɵgetInheritedFactory(GrandParent).name).toBeFalsy();
});
it('should find the correct factories if a grandparent class has a custom decorator', () => {
@addFoo()
class GrandParent {
static ɵfac = function GrandParent_Factory() {};
}
class Parent extends GrandParent {
static ɵfac = function Parent_Factory() {};
}
class Child extends Parent {
static ɵfac = function Child_Factory() {};
}
expect(ɵɵgetInheritedFactory(Child).name).toBe('Parent_Factory');
expect(ɵɵgetInheritedFactory(Parent).name).toBe('GrandParent_Factory');
expect(ɵɵgetInheritedFactory(GrandParent).name).toBeFalsy();
});
it('should find the correct factories if all classes have a custom decorator', () => {
@addFoo()
class GrandParent {
static ɵfac = function GrandParent_Factory() {};
}
@addFoo()
class Parent extends GrandParent {
static ɵfac = function Parent_Factory() {};
}
@addFoo()
class Child extends Parent {
static ɵfac = function Child_Factory() {};
}
expect(ɵɵgetInheritedFactory(Child).name).toBe('Parent_Factory');
expect(ɵɵgetInheritedFactory(Parent).name).toBe('GrandParent_Factory');
expect(ɵɵgetInheritedFactory(GrandParent).name).toBeFalsy();
});
it('should find the correct factories if parent and grandparent classes have a custom decorator',
() => {
@addFoo()
class GrandParent {
static ɵfac = function GrandParent_Factory() {};
}
@addFoo()
class Parent extends GrandParent {
static ɵfac = function Parent_Factory() {};
}
class Child extends Parent {
static ɵfac = function Child_Factory() {};
}
expect(ɵɵgetInheritedFactory(Child).name).toBe('Parent_Factory');
expect(ɵɵgetInheritedFactory(Parent).name).toBe('GrandParent_Factory');
expect(ɵɵgetInheritedFactory(GrandParent).name).toBeFalsy();
});
});
});
interface ComponentTest {
providers?: Provider[];
viewProviders?: Provider[];

View File

@ -48,8 +48,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
events!: Observable<NgElementStrategyEvent>;
/** Reference to the component that was created on connect. */
// TODO(issue/24571): remove '!'.
private componentRef!: ComponentRef<any>|null;
private componentRef: ComponentRef<any>|null = null;
/** Changes that have been made to the component ref since the last time onChanges was called. */
private inputChanges: SimpleChanges|null = null;
@ -86,7 +85,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
return;
}
if (!this.componentRef) {
if (this.componentRef === null) {
this.initializeComponent(element);
}
}
@ -97,15 +96,15 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
*/
disconnect() {
// Return if there is no componentRef or the component is already scheduled for destruction
if (!this.componentRef || this.scheduledDestroyFn !== null) {
if (this.componentRef === null || this.scheduledDestroyFn !== null) {
return;
}
// Schedule the component to be destroyed after a small timeout in case it is being
// moved elsewhere in the DOM
this.scheduledDestroyFn = scheduler.schedule(() => {
if (this.componentRef) {
this.componentRef!.destroy();
if (this.componentRef !== null) {
this.componentRef.destroy();
this.componentRef = null;
}
}, DESTROY_DELAY);
@ -116,11 +115,11 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
* retrieved from the cached initialization values.
*/
getInputValue(property: string): any {
if (!this.componentRef) {
if (this.componentRef === null) {
return this.initialInputValues.get(property);
}
return (this.componentRef.instance as any)[property];
return this.componentRef.instance[property];
}
/**
@ -128,7 +127,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
* cached and set when the component is created.
*/
setInputValue(property: string, value: any): void {
if (!this.componentRef) {
if (this.componentRef === null) {
this.initialInputValues.set(property, value);
return;
}
@ -142,7 +141,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
}
this.recordInputChange(property, value);
(this.componentRef.instance as any)[property] = value;
this.componentRef.instance[property] = value;
this.scheduleDetectChanges();
}
@ -156,11 +155,10 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
extractProjectableNodes(element, this.componentFactory.ngContentSelectors);
this.componentRef = this.componentFactory.create(childInjector, projectableNodes, element);
this.implementsOnChanges =
isFunction((this.componentRef.instance as any as OnChanges).ngOnChanges);
this.implementsOnChanges = isFunction((this.componentRef.instance as OnChanges).ngOnChanges);
this.initializeInputs();
this.initializeOutputs();
this.initializeOutputs(this.componentRef);
this.detectChanges();
@ -188,17 +186,17 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
}
/** Sets up listeners for the component's outputs so that the events stream emits the events. */
protected initializeOutputs(): void {
protected initializeOutputs(componentRef: ComponentRef<any>): void {
const eventEmitters = this.componentFactory.outputs.map(({propName, templateName}) => {
const emitter = (this.componentRef!.instance as any)[propName] as EventEmitter<any>;
return emitter.pipe(map((value: any) => ({name: templateName, value})));
const emitter: EventEmitter<any> = componentRef.instance[propName];
return emitter.pipe(map(value => ({name: templateName, value})));
});
this.events = merge(...eventEmitters);
}
/** Calls ngOnChanges with all the inputs that have changed since the last call. */
protected callNgOnChanges(): void {
protected callNgOnChanges(componentRef: ComponentRef<any>): void {
if (!this.implementsOnChanges || this.inputChanges === null) {
return;
}
@ -207,7 +205,7 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
// during ngOnChanges.
const inputChanges = this.inputChanges;
this.inputChanges = null;
(this.componentRef!.instance as any as OnChanges).ngOnChanges(inputChanges);
(componentRef.instance as OnChanges).ngOnChanges(inputChanges);
}
/**
@ -230,7 +228,8 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
*/
protected recordInputChange(property: string, currentValue: any): void {
// Do not record the change if the component does not implement `OnChanges`.
if (this.componentRef && !this.implementsOnChanges) {
// (We can only determine that after the component has been instantiated.)
if (this.componentRef !== null && !this.implementsOnChanges) {
return;
}
@ -255,11 +254,11 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
/** Runs change detection on the component. */
protected detectChanges(): void {
if (!this.componentRef) {
if (this.componentRef === null) {
return;
}
this.callNgOnChanges();
this.componentRef!.changeDetectorRef.detectChanges();
this.callNgOnChanges(this.componentRef);
this.componentRef.changeDetectorRef.detectChanges();
}
}

View File

@ -209,12 +209,6 @@ export function createCustomElement<P>(
}
}
// TypeScript 3.9+ defines getters/setters as configurable but non-enumerable properties (in
// compliance with the spec). This breaks emulated inheritance in ES5 on environments that do not
// natively support `Object.setPrototypeOf()` (such as IE 9-10).
// Update the property descriptor of `NgElementImpl#ngElementStrategy` to make it enumerable.
Object.defineProperty(NgElementImpl.prototype, 'ngElementStrategy', {enumerable: true});
// Add getters and setters to the prototype for each property input.
defineInputGettersSetters(inputs, NgElementImpl.prototype);

View File

@ -24,7 +24,7 @@ const semver = require('semver');
// Ignore commits that have specific patterns in commit message, it's ok for these commits to be
// present only in one branch. Ignoring them reduced the "noise" in the final output.
const ignorePatterns = [
const ignoreCommitPatterns = [
'release:',
'docs: release notes',
// These commits are created to update cli command docs sources with the most recent sha (stored
@ -34,6 +34,13 @@ const ignorePatterns = [
'build(docs-infra): upgrade cli command docs sources',
];
// Ignore feature commits that have specific patterns in commit message, it's ok for these commits
// to be present in patch branch.
const ignoreFeatureCheckPatterns = [
// It is ok and in fact desirable for dev-infra features to be on the patch branch.
'feat(dev-infra):'
];
// String to be displayed as a version for initial commits in a branch
// (before first release from that branch).
const initialVersion = 'initial';
@ -59,6 +66,11 @@ function maybeExtractReleaseVersion(commit) {
return matches ? matches[1] || matches[2] : null;
}
// Checks whether commit message matches any patterns in ignore list.
function shouldIgnoreCommit(commitMessage, ignorePatterns) {
return ignorePatterns.some(pattern => commitMessage.indexOf(pattern) > -1);
}
/**
* @param rawGitCommits
* @returns {Map<string, [string, string]>} - Map of commit message to [commit info, version]
@ -67,12 +79,12 @@ function collectCommitsAsMap(rawGitCommits) {
const commits = toArray(rawGitCommits);
const commitsMap = new Map();
let version = initialVersion;
commits.reverse().forEach((item) => {
const skip = ignorePatterns.some(pattern => item.indexOf(pattern) > -1);
commits.reverse().forEach((commit) => {
const ignore = shouldIgnoreCommit(commit, ignoreCommitPatterns);
// Keep track of the current version while going though the list of commits, so that we can use
// this information in the output (i.e. display a version when a commit was introduced).
version = maybeExtractReleaseVersion(item) || version;
if (!skip) {
version = maybeExtractReleaseVersion(commit) || version;
if (!ignore) {
// Extract original commit description from commit message, so that we can find matching
// commit in other commit range. For example, for the following commit message:
//
@ -80,8 +92,8 @@ function collectCommitsAsMap(rawGitCommits) {
//
// we extract only "feat: update the locale files" part and use it as a key, since commit SHA
// and PR number may be different for the same commit in master and patch branches.
const key = item.slice(11).replace(/\(\#\d+\)/g, '').trim();
commitsMap.set(key, [item, version]);
const key = commit.slice(11).replace(/\(\#\d+\)/g, '').trim();
commitsMap.set(key, [commit, version]);
}
});
return commitsMap;
@ -113,7 +125,7 @@ function diff(mapA, mapB) {
*/
function listFeatures(commitsMap) {
return Array.from(commitsMap.keys()).reduce((result, key) => {
if (key.startsWith('feat')) {
if (key.startsWith('feat') && !shouldIgnoreCommit(key, ignoreFeatureCheckPatterns)) {
const value = commitsMap.get(key);
result.push(getCommitInfoAsString(value[1], value[0]));
}