Compare commits

...

1090 Commits
4.1.1 ... 4.4.x

Author SHA1 Message Date
018750154d test: fix firebase deployment script test
When I fixed the project id in 2c4850dc58,
I didn't realize we had a test that verified the wrong behavior.
2018-05-04 15:10:17 -07:00
b19216d58b fix(aio): correct project id for deployment of archive sites 2018-05-03 15:09:17 -07:00
84fc1a3663 docs: add changelog for the 4.4.7 release 2018-04-16 02:00:31 -06:00
1c40be26c6 release: cut the 4.4.7 release 2018-04-16 02:00:02 -06:00
2c5cf19c6d fix(core): use appropriate inert document strategy for Firefox & Safari (#22077)
Both Firefox and Safari are vulnerable to XSS if we use an inert document
created via `document.implementation.createHTMLDocument()`.

Now we check for those vulnerabilities and then use a DOMParser or XHR
strategy if needed.

Further the platform-server has its own library for parsing HTML, so we
sniff for that (by checking whether DOMParser exists) and fall back to
the standard strategy.

Thanks to @cure53 for the heads up on this issue.
2018-02-13 10:05:14 -08:00
0dacf6d5f1 ci: use sudo: false on Travis (#21641)
Related to #21422.

PR Close #21641
2018-02-11 21:18:03 +02:00
e9f1d44015 ci: downgrade Chromium to a version that does not cause flakes
There seems to be some issue that causes Chrome/ChromeDriver to
unexpectedly reload during the aio e2e tests, causing flakes. It is not
clear what exactly is causing the reloading, but to the best of my
knowledge it is something inside Chrome or ChromeDriver.

Pinning Chrome to r494239 (between 62.0.3185.0 and 62.0.3186.0) fixes
the flakes.

Fixes #20159
2018-02-11 21:18:03 +02:00
1d9024ee9a build: pin ChromeDriver version (#20940)
Since our version of Chromium is also pinned, a new ChromeDriver (that
drops support for our Chromium version) can cause random (and unrelated
to the corresponding changes) errors on CI.
This commit pins the version of ChromeDriver and it should now be
manually upgraded to a vrsion that is compatible with th currently used
Chromium version.

PR Close #20940
2018-02-11 21:18:03 +02:00
6a6164ab4f revert: ci: use chrome stable (#18307)
This reverts commit 8bcb268140.
2018-02-11 19:39:55 +02:00
7231f5e26a docs: add changelog for 4.4.6 2017-10-18 16:14:53 -07:00
86415223cb release: cut the 4.4.6 release 2017-10-18 16:12:10 -07:00
269f5acc54 fix(common): attempt to JSON.parse errors for JSON responses (#19773)
Prior behavior for HttpClient was to parse the body as JSON when
responseType was set to 'json', even if the response was
unsuccessful. This changed due to a recent bugfix, and
unsuccessful responses had their bodies treated as strings.

There is no guarantee that if a service returns JSON in the
successful case that it will do so for errors. However, users
indicate that most APIs in the wild do work this way. Therefore,
this commit changes the error case behavior to attempt a JSON
parsing of the response body, and falls back on returning it as
a string if that fails.

PR Close #19773
2017-10-18 12:58:49 -07:00
6e6c866de9 docs(aio): changed confusing term (#19762)
Controller should be decorator I believe

PR Close #19762
2017-10-18 12:58:40 -07:00
732ed92cb7 test(animations): ensure :enter callbacks fire on container insertion (#19674)
PR Close #19674
2017-10-18 12:58:26 -07:00
53a807ae09 fix(router): RouterLinkActive should update its state right after checking the children
Closes #18983
2017-10-18 12:57:53 -07:00
3342a8253b fix(aio): make tests less flaky (#19784)
PR Close #19784
2017-10-18 10:19:48 -07:00
630c19f52d build: remove required BrowserStack run as it fails with “Access denied” (#19769)
See #19768
PR Close #19769
2017-10-17 15:53:05 -07:00
af8c2fa4be build: don’t make BrowserStack required as it fails with “Access denied”
See #19768
2017-10-17 14:56:41 -07:00
0789601dd6 build: fix broken path for animations in .pullapprove (#19453)
PR Close #19453
2017-10-17 10:44:43 -07:00
ce0ac46e42 style: fix formatting of check-node-modules (#19720)
PR Close #19720
2017-10-17 10:41:18 -07:00
b531d87580 ci: validate commit messages correctly when not on master (#19720)
PR Close #19720
2017-10-17 10:41:18 -07:00
23a2154817 docs(aio): update 2018 events (#19706)
update ac 2017 dates

PR Close #19706
2017-10-17 10:41:09 -07:00
76d2496f24 perf(animations): reduce size of bundle by removing AST classes (#19673)
This CL refactors the animation AST code to make use of interfaces
instead of classes. Given that interfaces are not persisted during
runtime the removal of classes should nicely cut down on size for the
animations-browser bundle.

PR Close #19673
2017-10-17 10:41:01 -07:00
b85cb410f1 docs(aio): change in-mem-web-api version for examples (#19668)
PR Close #19668
2017-10-17 10:40:52 -07:00
1be22df0df ci(aio): raise payload limit to accommodate the new search feature (#19704)
PR Close #19704
2017-10-13 15:20:23 -07:00
a805839d38 feat(aio): add search to 404 page (#19704)
The 404 page will now run a search based on the given URL to offer
suggestions for the page that the user really wanted.

PR Close #19704
2017-10-13 15:20:23 -07:00
3ac61a7550 refactor(aio): move SearchResultsComponent into shared module (#19704)
This will allow it to be used by an embedded component.

PR Close #19704
2017-10-13 15:20:23 -07:00
57ea33bc5c feat(aio): allow SearchService to have multiple clients (#19704)
PR Close #19704
2017-10-13 15:20:23 -07:00
4891649d68 test(aio): tidy up e2e tests that used invalid URLs (#19704)
PR Close #19704
2017-10-13 15:20:23 -07:00
93aba1bb1c build(aio): remove unused imports and local variables (#19704)
PR Close #19704
2017-10-13 15:20:23 -07:00
f983a6c615 fix(animations): properly support boolean-based transitions and state changes (#19672)
Closes #9396
Closes #12337

PR Close #19672
2017-10-13 09:40:29 -07:00
18f1b016e5 build(aio): consider devDependencies when overwriting dependencies of local Angular packages (#19687)
Previously, only `dependencies` were taken into account.

PR Close #19687
2017-10-13 09:30:10 -07:00
591dcc26af docs(animations): add missing bracket to fadeAnimation
Closes #18899
2017-10-13 09:14:29 -07:00
4acd322128 fix(core): don't refer to hydration in docs anymore.
Closes #18458
2017-10-13 09:05:30 -07:00
32a814bdfa docs: removing unnecessary commits from change log 2017-10-12 13:32:23 -07:00
912068e71c docs: add changelog for 4.4.5 2017-10-12 10:26:21 -07:00
df8e57dc5d release: cut the 4.4.5 release 2017-10-12 10:07:33 -07:00
f27f6e498f docs(aio): improve the Angular Connect homepage banner (#19684)
The original logo was too small.
The conference is over two days.

PR Close #19684
2017-10-12 09:49:58 -07:00
01bfbcb84a build(aio): update yarn.lock (#19683)
PR Close #19683
2017-10-12 09:49:48 -07:00
a15abbb324 docs(aio): change Material 2 to Material (#19663)
it is just called Angular Material now
PR Close #19663
2017-10-11 16:54:09 -07:00
28c29d560e build: fix rxjs version for aio (#19585)
PR Close #19585
2017-10-11 15:30:52 -07:00
65ca7fd4aa docs(aio): update banner ad (#19669)
PR Close #19669
2017-10-11 14:45:06 -07:00
3e3f918bb3 ci: freeze yarn lockfile when installing dependencies (#19616)
PR Close #19616
2017-10-11 14:37:38 -07:00
fc1dcffbdd build(aio): freeze lockfile when setting up examples (#19616)
PR Close #19616
2017-10-11 14:37:38 -07:00
ee3c681f98 build(aio): freeze yarn lockfile for aio-builds-setup scripts (#19616)
PR Close #19616
2017-10-11 14:37:37 -07:00
6225fedcb8 build(aio): freeze lockfile when installing example dependencies (#19616)
PR Close #19616
2017-10-11 14:37:37 -07:00
2905069559 ci(aio): freeze the lockfile for CI builds (#19616)
The CI will now fail if the dependencies in the AIO
package.json have been modified without the
lockfile being updated.

PR Close #19616
2017-10-11 14:37:37 -07:00
47202dd747 build(aio): improve accessor rendering (#19637)
Includes an update to `dgeni-packages@0.22.0` which provides more info
about accessors if required.

PR Close #19637
2017-10-11 14:37:28 -07:00
fb130c4eae build(aio): append information about links in and out of docs (#19583)
Closes #19560

PR Close #19583
2017-10-11 14:37:16 -07:00
734378c90b fix(compiler): TestBed.overrideProvider should keep imported NgModules eager (#19624)
Before, as soon as a user called `TestBed.overrideProvider` for a provider
of a `NgModule` that was imported via `TestBed.configureTestingModule`,
that `NgModule` became lazy.

This commit changes this behavior to keep the `NgModule` eager,
with or without a call to `TestBed.overrideProvider`.

PR Close #19624
2017-10-11 14:01:04 -07:00
3959b7ef28 refactor(compiler): introduce TestBed.deprecatedOverrideProvider (#19558)
This allows use to fix `TestBed.overrideProvider` to keep imported `NgModule`s eager,
while allowing our users to still keep the old semantics until they have fixed their
tests.

PR Close #19558
2017-10-11 14:00:32 -07:00
e292548523 fix(compiler): correctly instantiate eager providers that are used via Injector.get (#19558)
Closes #15501

PR Close #19558
2017-10-11 14:00:32 -07:00
c2506a78a1 docs(aio): add copy about ngNoForm to NgForm API doc (#19561)
PR Close #19561
2017-10-11 14:00:12 -07:00
34f70c6de2 build(aio): sort API list alphabetically (#19566)
Closes #19559

PR Close #19566
2017-10-11 13:52:19 -07:00
3d9c2a6352 build: add a Git .mailmap with my new name (#19550)
In this way my past contribution is mapped correctly.

PR Close #19550
2017-10-11 13:52:06 -07:00
3232125650 build(aio): remove unnecessary -- from yarn commands (#19565)
Essentially backports ffceae0a0 and f7199aa8c to 4.4.x.

PR Close #19565
2017-10-11 13:26:07 -07:00
c9f8718d2a fix(tsc-wrapped): don't rewrite imports when annotating for closure (#19579)
Port of f24ea59f74 to 4.4.x

Closure no longer needs to have the imports rewritten so avoid
rewriting as this can cause issues when the source directory
structure differs from what is deployed.

Fixes: #17171

PR Close #19579
2017-10-11 13:25:01 -07:00
f89ac92e5e test(aio): temporarily disable failing examples e2e tests (#19600)
Temporarily disable a couple of failing upgrade example e2e tests
to unblock the 4.4.x branch while investigating and fixing the
issues.
It seems to be some typings-related issues that TypeScript 2.5.3
complains about.

(Note: The same tests are temporarily disabled on master too.)

PR Close #19600
2017-10-10 15:58:45 -07:00
f82efcf942 build(aio): do not fail on first yarn setup (#19600)
PR Close #19600
2017-10-10 15:58:45 -07:00
dc22f4dc69 build(aio): fix overwriting with local Angular packages that depend on other local ones (#19600)
PR Close #19600
2017-10-10 15:58:45 -07:00
173ccf03ab test(aio): fix testing of NgPackagesInstaller (#19600)
PR Close #19600
2017-10-10 15:58:44 -07:00
1d5c3c1c9b build(aio): example-boilerplate is no longer responsible for yarn install (#19600)
The tooling for boilerplate was also running `yarn install` on the examples'
shared folder. But since this is handled by `ng-packages-installer` this
commit refactors the tools so that the boilerplate no longer does this
anymore.

PR Close #19600
2017-10-10 15:58:44 -07:00
e4dac421db build(aio): ignore @angular/mobile-toolkit in dist builds (#19600)
The new v5 version of this toolkit is too incompatible for us to run
AIO against both versions. So for now we will ignore this libary when
running against local Angular distributions and continue to use the
version on npm.

PR Close #19600
2017-10-10 15:58:44 -07:00
c639cdd3b3 build(aio): support ignoring dist packages in "local" mode (#19600)
PR Close #19600
2017-10-10 15:58:44 -07:00
5bcfa7cdfe build(aio): provide cleaner scripts for local build switching (#19600)
PR Close #19600
2017-10-10 15:58:44 -07:00
d8fd892e71 ci(aio): use custom package.json to run with local distributables (#19600)
Closes #19388

PR Close #19600
2017-10-10 15:58:44 -07:00
074a997302 build(aio): add support for using the locally built Angular packages for aio (#19600)
This commit allows building angular.io against the locally built Angular
packages. It adds two new npm scripts:

- `setup-local`: Same as `setup`, but overwrites the Angular packages for both
  angular.io and the examples boilerplate with the locally built ones.
- `build-local`: Same as `build`, but uses `setup-local` instead of `setup`
  under the hood, thus overwriting installed Angular packages with locally built
  ones.

Fixes #18611

PR Close #19600
2017-10-10 15:58:44 -07:00
62616f541a build(aio): ensure webdriver is updated when switching between local and npm deps (#19600)
PR Close #19600
2017-10-10 15:58:44 -07:00
156442f80d docs(aio): fix animations example to work with Angular v5 (#19600)
PR Close #19600
2017-10-10 15:58:44 -07:00
37112f549a build(aio): upgrade ts-node to support newer TypeScript config (#19600)
This was causing `Cannot find type definition file for 'jasmine'. (2688)`
errors when running Protractor.

PR Close #19600
2017-10-10 15:58:44 -07:00
cf4b4d53ba fix(aio): fix SearchService to work with TypeScript 2.4 (#19600)
The call to `race` required a type parameter to disambiguate the return type.

PR Close #19600
2017-10-10 15:58:43 -07:00
d45e3aa433 fix(aio): downgrade yarn to 1.0.2 temporarily (#19600)
PR Close #19600
2017-10-10 15:58:43 -07:00
ccc25ee901 fix: reformat files from previous cherry-picks 2017-10-05 13:54:46 -07:00
3052063c05 refactor(compiler): remove stale metadata classes for animations
These are no longer needed as animations are a purely
runtime concept.
2017-10-05 13:54:46 -07:00
e107322f5c refactor(core): add a checkIndex to the compiler view nodes
Each node now has two index: nodeIndex and checkIndex.

nodeIndex is the index in both the view definition and the view data.
checkIndex is the index in in the update function (update directives and update
renderer).

While nodeIndex and checkIndex have the same value for now, having both of them
will allow changing the structure of view definition after compilation (ie for
runtime translations).
2017-10-05 13:54:46 -07:00
f0774254de refactor(compiler): misc minor refactor / cleanup (#19189)
PR Close #19189
2017-10-05 13:54:46 -07:00
6c66031c4a fix(core): make dynamic & inline code checking behave the same (#19189)
PR Close #19189
2017-10-05 13:54:46 -07:00
8f668807cf Revert "fix(platform-browser): support customEqualityTesters when overriding Jasmine toEqual"
This reverts commit cc8ae32503.

Reason: broke a few existing tests.
2017-10-05 13:25:05 -07:00
f98eb35179 docs(aio): document Custom Async validators
docs(aio): Custom Async validators edits

incorporating suggestions from kapunahelewong
2017-10-04 15:08:00 -07:00
cc8ae32503 fix(platform-browser): support customEqualityTesters when overriding Jasmine toEqual 2017-10-04 15:01:11 -07:00
95f3b1dbe6 fix(compiler): disallow references for select and index evaluation
Also fixes an issue where enum values of 0 or '``' where not treated
correctly.

Fixes: #18170
2017-10-04 14:54:53 -07:00
6b96b069bf docs(aio): fix typo in preview server config file comment 2017-10-04 12:43:37 -07:00
ecfe85b06c docs(aio): document the special treatment of undefined 2017-10-04 12:43:28 -07:00
5439d4cd49 docs(aio): fix broken link in toh-pt5 2017-10-04 12:43:10 -07:00
df91fd032d build: update npm dependencies 2017-10-04 10:22:48 -07:00
2d5ef15e08 build: switch from npm to yarn 2017-10-04 10:22:48 -07:00
544a7ad0a5 docs(aio): Fix incorrect wording 2017-10-03 08:16:16 -07:00
ec2c1bec4a build(aio): fix various API rendering issues
Upgrading to dgeni-packages 0.21.4 gives us
access to more properties on the API docs, which
allows us to fix the following issues:

Closes #19450
Closes #19452
Closes #19456
2017-10-03 08:15:25 -07:00
e064d2607d docs(aio): add ngVikings to the events page 2017-10-03 08:14:30 -07:00
d489ad595d docs: add changelog for 4.4.4 2017-09-28 12:18:14 -07:00
0a3753bcce release: cut the 4.4.4 release 2017-09-28 12:13:13 -07:00
7fc2dceaf5 fix(compiler): do not consider a reference with members as a reference (#19466)
fixes: #18170
ref: #19454
2017-09-28 12:04:20 -07:00
c3b39bac52 fix(compiler): correctly map error message locations (#19424) 2017-09-28 10:55:17 -07:00
2cd88bfb0f docs(animations): document usage of negative limit values for query (#19451) 2017-09-28 09:35:10 -07:00
0cefb0b79b docs(aio): add copy about NgForOf (#18686) 2017-09-27 15:29:23 -07:00
55a7443974 docs(aio): edit summary and next step headers (#16962) 2017-09-27 13:47:03 -07:00
f9ebaf1b90 docs(aio): remove links to top of documents (#16971) 2017-09-27 13:46:50 -07:00
bc81fbdd27 fix(animations): properly support the query limit option value (#19419)
Closes #19232
2017-09-26 13:18:47 -07:00
c7aa8a132d fix(compiler): skip   when trimming / removing whitespaces (#19310)
Fixes #19304
2017-09-25 13:35:32 -07:00
5c99b01512 build(aio): run the upload server as a non-previleged user
closes #19352

Previously, the upload server (for PR previews) was run as root and
"downleveled" to a non-privileged user from inside the node script.

Now, with the latest version of `pm2` (which is used to run the upload server
scripts), we can get rid of that workaround and set the desired UID directly
through `pm2`.
2017-09-25 12:05:00 -07:00
992ba33a28 build(aio): upgrade all preview server dependencies 2017-09-25 12:04:51 -07:00
17e7c58981 build(aio): add metadata aliases for directives, components and pipes (#19317)
This change will enable people to link to the API docs via their selectors
or names, as used in a template.

Since the selectors can be quite complex we are not able to get 100%
accuracy.

Closes #16787
2017-09-25 12:04:41 -07:00
b8f15d2b77 build(aio): render class/interface "descendants" in API docs (#19343)
For classes, the tree of subclasses is rendered, recursively.

For interfaces, the descendants are separated into child interfaces, which
extend the interface, and classes, which implement the interface.

Closes #19306
2017-09-25 12:04:28 -07:00
5f9a10aab9 build(aio): ensure decorators with shared interface types are found (#19361)
Closes #19358
2017-09-25 12:03:31 -07:00
04bc5257a6 docs(aio): tidy up tooling documentation (#18151)
PR Close #18151
2017-09-21 11:27:01 -07:00
5bca58e748 docs(aio): applying some feedback (#18151)
PR Close #18151
2017-09-21 11:27:01 -07:00
526a67f8f4 docs(aio): add zipper documentation (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
53a27e07b1 docs(aio): complete the plunker-builder docs (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
ba0fb1e055 docs(aio): add boilerplate readme (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
ed2c8aa6f8 docs(aio): high-level documentation of the transforms folder (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
9226760120 docs(aio): high-level documentation of AIO tooling (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
b9ee8b46a0 refactor(core): viewDef related refactoring (#19272)
- optimize the way node flags are propagated in `viewDef()`,
- fix `elementDef()` signature to make `namespaceAndName` nullable,
- move render parent computation with the parent computation

PR Close #19272
2017-09-21 11:26:43 -07:00
e7eb0b8b7c docs: update README and author guide about doc build errors (#19276)
- tells reader about `yarn serve-and-sync`.
- directs reader to look to docs-style-guide if get doc build error.
- update docs-style-guide to warn about ignored code files.

PR Close #19276
2017-09-21 09:56:33 -07:00
ae52851458 fix(tsc-wrapped): add metadata for type declarations (#19040)
PR Close #19040
2017-09-20 17:06:25 -07:00
e63cf3b89e ci(aio): correctly serialize commit messages with "double-quotes" (#19184)
PR Close #19184
2017-09-20 17:05:31 -07:00
9624fda082 build(aio): improve error message for ignored example files (#19265)
Addresses https://github.com/angular/angular/pull/18707#issuecomment-330396771

PR Close #19265
2017-09-20 16:50:36 -07:00
e19b6a8f38 build(aio): remove commented out code (#19265)
PR Close #19265
2017-09-20 16:50:36 -07:00
2aa6b54201 build: pin docker image to a tag for CircleCI (#19295)
Fixes the broken build
PR Close #19295
2017-09-20 16:50:20 -07:00
fe8550d278 docs: animations - replace iterator with simple code style (#18965)
Replaces iterator facade over the HeroService because webpack threw up.
Also this was an obscure distraction for readers with no obvious benefits.

PR Close #18965
2017-09-20 16:50:05 -07:00
e2e8ba6ffa release: cut the 4.4.3 release 2017-09-19 15:22:05 -07:00
60cdf9dc38 docs(CHANGELOG.MD): add release notes for 4.4.2 and 4.4.3 2017-09-19 15:21:51 -07:00
0371538d10 fix(tsc-wrapped): deduplicate metadata only when the module is the same (#19261)
Fixes #19219
PR Close #19261
2017-09-19 14:25:27 -07:00
351331a81b ci: update yarn to 1.0.2 (#19270)
PR Close #19270
2017-09-19 13:53:09 -07:00
0a54713f8a build: automatically version tsc-wrapped just like other packages (#19270)
This removes manual steps from the release list.

I had to modify a few integration tests not to install compiler-cli dirrectly
because it depends on tsc-wrapped which yarn was eager to fetch from npm rather
than from the local build.

PR Close #19270
2017-09-19 13:53:09 -07:00
49d122e560 release: cut the 4.4.2 release 2017-09-18 21:14:03 -07:00
ff8729423e docs(changelog): fix the changelog link for comparing 4.4.1 to 4.3.6 2017-09-15 16:00:56 -07:00
0c72f7d358 docs: update combined changelog for 4.4 stable 2017-09-15 15:53:54 -07:00
818f4a751e release: cut the 4.4.1 release 2017-09-15 15:35:25 -07:00
4e7d2bd5bf docs: add changelog for 4.4.0 2017-09-15 14:58:32 -07:00
395ac510f7 release: cut the 4.4.0 release 2017-09-15 14:51:21 -07:00
b20c5d2c37 fix(upgrade): remove code setting id attribute. (#19182)
The id was leftover from previous iterations of ngUpgrade and is
no longer needed. Moreover, setting it can clash with CSS usage of id.

Fixes #18446

PR Close #19182
2017-09-15 17:19:50 -04:00
ea02b1ccfa fix(upgrade): remove code setting id attribute.
The id was leftover from previous iterations of ngUpgrade and is
no longer needed. Moreover, setting it can clash with CSS usage of id.

Fixes #18446
2017-09-15 17:19:50 -04:00
0bafd03e85 revert: test(packaging): added test for source map correctness
This reverts commit 86f7b4170c.
2017-09-15 11:55:29 -07:00
e8d1858c64 revert: build(platform-browser): fix typo
This reverts commit 991a802a8e.
2017-09-15 13:47:07 -04:00
d1efc5ae90 revert: fix(upgrade): remove code setting id attribute
This reverts commit 1302e54947.
2017-09-13 13:19:19 -07:00
86f7b4170c test(packaging): added test for source map correctness 2017-09-13 13:48:36 -04:00
9d93c859d7 build(aio): auto-link more code items
We now parse all code blocks, after they have been rendered by dgeni
and insert links to API docs that match "words" in the code.
2017-09-12 14:06:26 -04:00
5baa069b16 docs(aio): fix typo 2017-09-12 14:06:14 -04:00
12b7d00747 fix(aio): relax search on titles further
This change will now match `ControlValueAccessor` for the query `accessor`.

Closes #18872
2017-09-12 14:06:02 -04:00
d777d79c61 build(aio): do not render annotations block for directives 2017-09-12 14:05:46 -04:00
062a772e48 build(aio): improve rendering of directive selectors 2017-09-12 14:05:41 -04:00
3618cc6d34 build(aio): do not render comments in decorators
Closes #18873
2017-09-12 14:05:35 -04:00
9413ca8a2e build(aio): update karma & systemjs config for HttpClient
While adding the references to the `HttpClient` packages it also crucially
adds ref to new “tslib” library required by `HttpClient`.
2017-09-12 13:46:12 -04:00
1302e54947 fix(upgrade): remove code setting id attribute.
The id was leftover from previous iterations of ngUpgrade and is
no longer needed. Moreover, setting it can clash with CSS usage of id.

Fixes #18446
2017-09-08 16:30:18 -07:00
edf423af3d build(aio): update package.json for angular-in-mem-web-api 0.4.0 2017-09-08 18:25:20 -04:00
37086748bf docs(aio): provide link text for AbstractControl references in reactive directives for forms
Closes #17484
2017-09-08 18:25:14 -04:00
6c3f1f70ba build(aio): render metadata members from decorator ancestors 2017-09-08 18:25:08 -04:00
8a8c4d37aa build(aio): render ancestor members in directives
See `CheckBoxRequiredValidator` for an example.
2017-09-08 18:25:00 -04:00
f6a7183c52 fix(tsc-wrapped): fix metadata symbol reference 2017-09-08 17:26:28 -04:00
c86e16db5f feat(aio): include more API results in search
By adding a more relaxed search on the title
of docs, we are more likely to catch API docs.

The additional search terms match anything
with a word in the title that starts with the
characters of the first term in the search.

E.g. if the search is "ngCont guide" then
search for "ngCont guide titleWords:ngCont*"
2017-09-08 17:01:39 -04:00
c3907893c1 build(aio): don't show constructor detail if there is none 2017-09-08 17:01:39 -04:00
d61c6f996a build(aio): support rendering of constructor overloads in API docs
Closes #18258
2017-09-08 17:01:39 -04:00
bd04cd61f8 docs(aio): change Stack Overflow link 2017-09-08 16:29:17 -04:00
96dcfafe45 docs(aio): Updated jqwidgets resource desc and url
Updated the description and url of the jqwidgets item in the resources page
2017-09-07 16:07:52 -04:00
991a802a8e build(platform-browser): fix typo
fixes #19033
2017-09-07 16:07:27 -04:00
48ae1a6574 fix(tsc-wrapped): deduplicate metadata for re-exported modules 2017-09-07 16:06:12 -04:00
dd2d1be006 fix(aio): align footer background image repeat (#19035)
Refs #17465
PR Close #19035
2017-09-05 23:25:06 -05:00
5369de80d6 docs(aio): add changelog, as hidden, to navigation (#19028)
This ensures that the changelog page is formatted correctly.

Closes #17604

PR Close #19028
2017-09-05 23:25:06 -05:00
552dbfc2f1 docs(aio): add Nir Kaufman to GDE resources (#19012)
PR Close #19012
2017-09-05 23:25:06 -05:00
0aa4cbdbc8 docs(forms): clarify ControlValueAccessor docs (#19008)
Closes #18174

PR Close #19008
2017-09-05 23:25:06 -05:00
9f16c2620c docs(aio): updating about page for team changes (#19003)
PR Close #19003
2017-09-05 23:25:06 -05:00
9b256a9144 build(aio): render the extends ancestors of classes in API docs (#18927)
PR Close #18927
2017-09-05 23:25:06 -05:00
0f1476be33 build(aio): ensure dgeni can load all angular Typescript modules (#18927)
PR Close #18927
2017-09-05 23:25:06 -05:00
769b2aada2 docs(aio): add new lib to Data Libraries (#18656)
add AngularCommerse, set of components to build e-commerce solutions with Angular + Firebase

PR Close #18656
2017-09-05 23:25:06 -05:00
301236e1a5 docs(aio): Updated usage of Observables in router guide. Added section for advanced redirects (#18197)
PR Close #18197
2017-09-05 23:25:06 -05:00
aeb98dbcdf docs: add changelog for 4.4.0-RC.0 2017-09-01 22:01:41 -07:00
8036d05412 release: cut the 4.4.0-RC.0 release 2017-09-01 23:49:51 -05:00
7d137d7f88 fix(core): complete EventEmitter in QueryList on component destroy (#18902)
Fixes #18741

PR Close #18902
2017-09-01 22:52:03 -05:00
b8b551cf2b perf(core): add option to remove blank text nodes from compiled templates (#18823)
PR Close #18823
2017-09-01 13:30:04 -05:00
7ec28fe9af feat(compiler): allow multiple exportAs names (#18723)
This change allows users to specify multiple exportAs names for a
directive by giving a comma-delimited list inside the string.

The primary motivation for this change is to allow these names to be
changed in a backwards compatible way.

PR Close #18723
2017-09-01 13:26:10 -05:00
1cc3fe21b6 fix(animations): do not leak DOM nodes/styling for host triggered animations (#18853)
Closes #18606

PR Close #18853
2017-09-01 10:24:14 -07:00
ba7d70e5e0 build: fix changelog to remove AIO line items (#18956)
fixes 18740

PR Close #18956
2017-08-30 18:10:56 -07:00
497e0178cc fix(compiler): normalize the locale name (#18963)
PR Close #18963
2017-08-30 17:48:08 -07:00
8821723526 fix(common): fix XSSI prefix stripping by using JSON.parse always (#18466)
Currently HttpClient sends requests for JSON data with the
XMLHttpRequest.responseType set to 'json'. With this flag, the browser
will attempt to parse the response as JSON, but will return 'null' on
any errors. If the JSON response contains an XSSI-prevention prefix,
this will cause the browser's parsing to fail, which is unrecoverable.

The only compelling reason to use the responseType 'json' is for
performance (especially if the browser offloads JSON parsing to a
separate thread). I'm not aware of any browser which does this currently,
nor of any plans to do so. JSON.parse and responseType 'json' both
end up using the same V8 code path in Chrome to implement the parse.

Thus, this change switches all JSON parsing in HttpClient to use
JSON.parse directly.

Fixes #18396, #18453.

PR Close #18466
2017-08-29 17:19:02 -07:00
a203a959ae fix(common): fix improper packaging for @angular/common/http (#18613)
PR Close #18613
2017-08-29 17:16:56 -07:00
dfe2bad663 build: Add GitHub scripts for rebasing PRs (#18359)
PR Close #18359
2017-08-28 18:33:11 -05:00
f09a266e01 docs: add changelog for 4.3.6 2017-08-23 15:01:42 -05:00
3853fff795 release: cut the 4.3.6 release 2017-08-23 14:59:49 -05:00
641be64544 docs(aio): add info about --local option in the readme (#18824)
PR Close #18824
2017-08-23 13:19:23 -05:00
bcf211bdb3 docs(aio): fix "Error handling" section in "HttpClient" (#18821)
Removed additional curly brackets to fix blocks. Also replaced tab with 2 spaces.

PR Close #18821
2017-08-23 13:19:15 -05:00
ee5591d583 fix(core): make sure onStable runs in the right zone (#18706)
Make sure the callbacks to the NgZone callbacks run in the right zone
with or without the rxjs Zone patch -
1ed83d08ac.

PR Close #18706
2017-08-23 13:18:47 -05:00
1f43713506 fix(aio): do not redirect API pages on archive and next deployments (#18791)
PR Close #18791
2017-08-21 18:34:55 -05:00
325b9b4562 docs(aio): add ngAtlanta to the events page (#18649)
PR Close #18649
2017-08-21 18:34:25 -05:00
88abdbd50b docs(aio): update resources to include NinjaCodeGen Angular CRUD generator (#18518)
PR Close #18518
2017-08-21 18:34:17 -05:00
14d34c9bdf style(animations): format integration spec (#18805)
PR Close #18805
2017-08-21 17:09:53 -05:00
e1f45a33b7 fix(animations): restore auto-style support for removed DOM nodes (#18787)
PR Close #18787
2017-08-18 23:32:41 -05:00
9a754f9f0f fix(animations): make sure animation cancellations respect AUTO style values (#18787)
Closes #17450

PR Close #18787
2017-08-18 23:32:34 -05:00
c3dcbf9cb3 fix(animations): make sure @.disabled respects disabled parent/sub animation sequences (#18715)
Prior to this fix if @parent and @child animations ran at the same
time within a disabled region then there was a chance that a @child
sub animation would never complete. This would cause *directives to
never close a removal when a @child trigger was placed on them. This
patch fixes this issue.

PR Close #18715
2017-08-18 23:32:28 -05:00
5d68c830d2 fix(animations): ensure animations are disabled on the element containing the @.disabled flag (#18714)
Prior to fix this fix, @.disabled would only work to disable child
animations. Now it will also disable animations for the element that has
the @.disabled flag (which makes more sense).

PR Close #18714
2017-08-18 23:32:21 -05:00
ac58914b97 feat(animations): allow @.disabled property to work without an expression (#18713)
PR Close #18713
2017-08-18 23:32:13 -05:00
Joe
77ebd2b020 docs(aio): fix card inconsistency (#18726)
PR Close #18726
2017-08-18 23:15:36 -05:00
fec3b1a0e9 fix(core): correct order in ContentChildren query result (#18326)
Fixes #16568

PR Close #18326
2017-08-18 23:15:17 -05:00
3b571a4f3d build(aio): do not auto-link code elements already inside a link (#18776)
Closes #18769

PR Close #18776
2017-08-18 13:38:43 -05:00
Tea
efee81eb57 docs(aio): typo in template-syntax guide (#18765)
PR Close #18765
2017-08-18 13:38:16 -05:00
a7a698c36f docs(aio): update resource for codelyzer (#18742)
PR Close #18742
2017-08-18 13:22:11 -05:00
b5f1dc32d1 test(aio): fix error logged during tests (#18659)
The fixed test expected there to be a doc version without a URL. This used to be
the case but not any more. As a result, an error was logged in the test output
(but no failure).

This commit fixes it by ensuring that a version without a URL exists.

PR Close #18659
2017-08-18 13:20:22 -05:00
eef28144ce docs(aio): move code snippet to appropriate location (#18650)
PR Close #18650
2017-08-18 13:19:15 -05:00
f9b290570e fix(animations): resolve error when using AnimationBuilder with platform-server (#18642)
Use an injected DOCUMENT instead of assuming the global 'document'
exists.

Fixes #18635.

PR Close #18642
2017-08-18 13:15:05 -05:00
4852f55875 build(packaging): increase node memory for tests (#18731)
PR Close #18731
2017-08-18 13:15:05 -05:00
793f31b9b3 feat(common): add an empty DeprecatedI18NPipesModule module
Adding an empty module to ease the migration to the i18n pipes.
2017-08-18 13:15:05 -05:00
7e94405271 docs: add changelog for 4.3.5 2017-08-16 10:51:23 -07:00
6076a8d7bb release: cut the 4.3.5 release 2017-08-16 10:49:38 -07:00
a1624f217c fix(forms): re-assigning options should not clear select
Fixes #18330
2017-08-16 10:34:23 -07:00
b2f4d53bf0 docs(forms): fix reactive-forms guide typo
closes #17943
2017-08-15 16:43:21 -07:00
7662cefe6f docs: remove TypeScript to JavaScript guide & sample 2017-08-15 16:35:01 -07:00
1cb607697a build(aio): switch from @angular/http to @angular/common/http
```
$ ls -l dist/*.js

 14942            dist/0.b19e913fbdd6507d346b.chunk.js
  1535            dist/inline.a1b446562b36eebb766d.bundle.js
524385  (+  682)  dist/main.19fec4390ff7837ee6ef.bundle.js
 37402            dist/polyfills.9f7e0e53bce2a6c8326e.bundle.js
 54001            dist/worker-basic.min.js

632265  (+  682)  total
```
2017-08-15 15:16:10 -07:00
1990c3c722 build(aio): upgrade zone.js to 0.8.16
```
$ ls -l dist/*.js

 14942            dist/0.b19e913fbdd6507d346b.chunk.js
  1535            dist/inline.a1b446562b36eebb766d.bundle.js
523703            dist/main.19fec4390ff7837ee6ef.bundle.js
 37402  (+ 3088)  dist/polyfills.9f7e0e53bce2a6c8326e.bundle.js
 54001            dist/worker-basic.min.js

631583  (+ 3088)  total
```
2017-08-15 15:16:10 -07:00
b589d85d6f build(aio): upgrade @angular/* to 5.0.0-beta.3
```
$ ls -l dist/*.js

 14942            dist/0.b19e913fbdd6507d346b.chunk.js
  1535            dist/inline.7813f9128903f164bc00.bundle.js
523703  (-18484)  dist/main.19fec4390ff7837ee6ef.bundle.js
 34314            dist/polyfills.9b05df3b6c9270ebf575.bundle.js
 54001            dist/worker-basic.min.js

628495  (-18484)  total
```
2017-08-15 15:16:10 -07:00
03ec3a2169 build(aio): upgrade @angular/* to 4.3.4
```
$ ls -l dist/*.js

 14942            dist/0.b19e913fbdd6507d346b.chunk.js
  1535            dist/inline.dd77b84267809087d225.bundle.js
542187  (+ 2191)  dist/main.f3ffdb5bb1a5bcec2163.bundle.js
 34314            dist/polyfills.9b05df3b6c9270ebf575.bundle.js
 54001            dist/worker-basic.min.js

646979  (+ 2191)  total
```
2017-08-15 15:16:10 -07:00
a5baed6b97 build(aio): upgrade @angular/cli to 1.3.0
```
$ ls -l dist/*.js

 14942  (+    4)  dist/0.b19e913fbdd6507d346b.chunk.js
  1535            dist/inline.e07e02e29b7fc93816c6.bundle.js
539996  (-56433)  dist/main.f466098a873c1169a6dc.bundle.js
 34314  (-   33)  dist/polyfills.9b05df3b6c9270ebf575.bundle.js
 54001            dist/worker-basic.min.js

644788  (-56462)  total
```
2017-08-15 15:16:10 -07:00
259fc91305 docs(core): correct code examples for ChangeDetectorRef 2017-08-15 15:12:35 -07:00
a618d6e4ce docs(forms): add api docs for AbstractControlDirective 2017-08-15 15:07:44 -07:00
b315a84ba0 docs(aio): add Metadata guide based on Chuck’s docs
Chuck’s gist
https://gist.github.com/chuckjaz/65dcc2fd5f4f5463e492ed0cb93bca60#file-Angular%20Metadata-md
Also chuck’s doc on metadata-related errors (link needed)
2017-08-15 12:21:23 -07:00
972538be7a fix(core): forbid destroyed views to be inserted or moved in VC
Fixes #18615
2017-08-14 12:09:22 -07:00
d7be4f12b5 perf(aio): update to new version of build-optimizer 2017-08-11 13:29:01 -07:00
b9c1c913c1 fix(aio): skip PWA test when redeploying non-public commit 2017-08-11 13:29:01 -07:00
06e479ff66 docs: add changelog for 4.3.4 2017-08-10 09:40:01 -07:00
0065868f37 release: cut the 4.3.4 release 2017-08-10 09:38:13 -07:00
77fa3c3e48 test(aio): fix tests for example-boilerplate (#18619)
PR #18520 was accidentally merged into 4.3.x, which uses a different
location for building `tsc-wrapped`. This commit reverts the changes
from #18520 that were not compatible with 4.3.x.
2017-08-09 20:28:43 -07:00
f4cb45345d test(aio): fix running docs examples against local builds (#18520)
This commit also updates the version of `@angular/cli` used for docs examples.
The previous (transient) dependency `@ngtools/webpack` was not compatible with
`@angular/compiler-cli@>=5` and was breaking when running against the local
builds (currently at 5.0.0-beta.2). The version of `@ngtools/webpack` used by
the latest `@angular/cli` version is compatible with `@angular/compiler-cli@5`.
2017-08-09 14:21:39 -07:00
9329bfb86a fix(aio): add missing code snippet (#18547)
The snippet got lost some time during the migration from the old version (it is
[present in v2][1]).

[1]: https://v2.angular.io/docs/ts/latest/cookbook/aot-compiler.html#!#running-the-application

Fixes #18544
2017-08-09 14:20:50 -07:00
3efc88fb81 test(aio): fix the deploy-to-firebase tests
This commit also ensures that if the tests fail, the script exits with an error.

closes #18595
2017-08-09 14:18:07 -07:00
954b09022a ci(aio): fix deploying the stable branch to Firebase
The `deploy-to-firebase.sh` always expects there to be a
`src/extra-files/<mode>` directory and breaks if it doesn't exist.
2017-08-09 14:17:47 -07:00
71f5e78bcb test(animations): disable buggy test in Chrome 39 (#18483)
Fixes #15793
2017-08-09 14:16:03 -07:00
f0c3ed0f14 ci(aio): fix deploying to firebase (#18590) 2017-08-08 14:00:59 -07:00
c8fd3f5237 docs(common): fix the DatePipe API docs (#18548) 2017-08-07 11:47:38 -07:00
e0660b1b72 fix(animations): support persisting dynamic styles within animation states (#18468)
Closes #18423
Closes #17505
2017-08-07 11:40:34 -07:00
5a165ebcef fix(animations): revert container/queried animations accordingly during cancel (#18516) 2017-08-07 11:39:04 -07:00
3212f8c826 docs(aio): typo & update the bio (#18559) 2017-08-07 10:20:52 -07:00
c421ccaae9 ci(aio): compute AIO deployment mode
There are now 3 modes for deployment: next, stable, archive.
We compute which mode (and other deployment properties)
from the `TRAVIS_BRANCH` and the `STABLE_BRANCH`.

If the TRAVIS_BRANCH is master we deploy as "next".
If the `TRAVIS_BRANCH` matches the `STABLE_BRANCH` we deploy as "stable".

Otherwise if the branch has a major version lower than the stable version
and its minor version is highest of similar branches we deploy as "archive".

For "archive" deployments we compute the firebase project and deployment
url based on the major version of the `TRAVIS_BRANCH`.

As well as choosing where to deploy the build, we also use this
to select the environment file for the AIO Angular app.
This will enable the app to change its rendering and behaviour
based on its mode.

See #18287
2017-08-04 09:15:38 -07:00
bbec7db7ba feat(aio): add "archive" and "next" color themes 2017-08-04 09:15:38 -07:00
00134ae4e0 feat(aio): redirect marketing pages to docs if deploy mode is archive
See #18287
2017-08-04 09:15:38 -07:00
07bd459baa feat(aio): add deploy mode to version picker
See #18287
2017-08-04 09:15:38 -07:00
302adf1081 feat(aio): enable deployment mode to be set via URL query
The deployment mode set from the environment provided at build time;
or overridden by the `mode` query parameter: e.g. `...?mode=archive`

See #18287
2017-08-04 09:15:38 -07:00
1a6a13425b feat(aio): update UI based on deployment mode
* Add a banner if the mode is "archive"
* Add a `mode-...` class to the `aio-shell` element to enable
mode based theming.

See #18287
2017-08-04 09:15:38 -07:00
072a772ca6 ci(aio): include extra files in AIO deployment based on mode
Any files that are inside the `extra-files/{mode}` folder
will be copied over to the `dist` folder before deployment
to Firebase.

See #18287
2017-08-04 09:15:38 -07:00
5f0e0a46fd ci(aio): compute AIO deployment mode
There are now 3 modes for deployment: next, stable, archive.
We compute which mode (and other deployment properties)
from the `TRAVIS_BRANCH` and the `STABLE_BRANCH`.

If the TRAVIS_BRANCH is master we deploy as "next".
Otherwise if the branch is the highest of its minor versions
we deploy as "stable" if the `TRAVIS_BRANCH` matches the `STABLE_BRANCH` or
else "archive".

For "archive" deployments we compute the firebase project and deployment
url based on the major version of the `TRAVIS_BRANCH`.

As well as choosing where to deploy the build, we also use this
to select the environment file for the AIO Angular app.
This will enable the app to change its rendering and behaviour
based on its mode.

See #18287
2017-08-04 09:15:38 -07:00
c7b72aa575 docs(aio): tech edits to form validation (PR #18495) 2017-08-03 13:58:22 -07:00
732eb61957 docs(forms): update and re-organize validation guide 2017-08-03 13:57:58 -07:00
e7e7622971 fix(compiler): ignore @import in multi-line css (#18452)
Fixes #18038
2017-08-03 11:01:24 -07:00
4176832266 refactor(router): compile router cleanly with TypeScript 2.4 (#18465) 2017-08-02 17:32:27 -07:00
71de92a189 docs(router): fix typo (#18479) 2017-08-02 17:31:33 -07:00
e0021d4cf5 refactor(platform-browser): compiler platform-browser packages cleanly (#18464) 2017-08-02 16:31:14 -07:00
4e44102e31 refactor(forms): compile forms cleanly with TypeScript 2.4 (#18462) 2017-08-02 16:29:57 -07:00
111b70d108 refactor(upgrade): compile upgrade cleanly with TypeScript 2.4 (#18461) 2017-08-02 16:28:38 -07:00
5e4054b8f3 fix(compiler): cleanly compile with TypeScript 2.4 (#18456) 2017-08-02 16:27:14 -07:00
5afc7abcb0 fix(benchpress): compile cleanly with TS 2.4 (#18455) 2017-08-02 16:24:33 -07:00
65d0888708 fix(aio): fix layout of the webpack guide (#18493)
This is possibly a temporary fix for the layout, until we decide whether we want
to remove the guide or properly add it to the SideNav menu.

Fixes #17912
2017-08-02 16:01:02 -07:00
adfd2373b8 ci(aio): Add commit message to payload data (#18137) 2017-08-02 15:59:54 -07:00
3a82af3bde docs(aio): fix missing anchor-open in i18n documentation (#18476)
The Internationalisation documentation, "Translate text nodes" section, has an incomplete
markdown anchor, and leaks markdown into the page. Fix the anchor by adding the opening bracket.
2017-08-02 15:56:25 -07:00
3af62306b4 fix(aio): correctly redirect cookbook/a1-a2-quick-reference.html (#18418)
Fixes #18415
2017-08-02 15:54:49 -07:00
afe339396f test(common): skip some DatePipe tests in old Chrome where Intl is buggy (#15784) 2017-08-02 15:52:27 -07:00
c4b51bf689 docs(aio): replace old blog link in footer (#18448)
Fixes #18233
2017-08-02 15:46:25 -07:00
b65fe3e44e ci: remove chromium fold reference (#18445) 2017-08-02 15:44:26 -07:00
116ee334fb build(aio): use cli 1.3.0-rc (#18290) 2017-08-02 15:37:39 -07:00
dbc5c5817a docs: improve github labels by introducing "PR target" labels (#18436)
I also renamed all "pr_*" lables to "PR *" lables, removed obsolete
"chore" label, and added docs label.
2017-08-02 15:31:56 -07:00
baf4ce0dd0 build: enable TSLint on the packages folder (#18459)
porting PRs #18392 and #18441 to 4.x
2017-08-02 15:23:33 -07:00
24db1ed938 docs: add changelog for 4.3.3 2017-08-02 13:00:35 -07:00
82798e9d04 release: cut the 4.3.3 release 2017-08-02 12:57:34 -07:00
da8bb1b45b docs(aio): fixed list format in FormArray section 2017-07-31 11:40:27 -07:00
f5cbc2ee25 fix(compiler): fix for element needing implicit parent placed in top-level ng-container
fixes #18314
2017-07-31 11:40:20 -07:00
cbc1986c6f fix(aio): fix links to source for paths with symlinks
Fixes #18353
2017-07-31 11:39:47 -07:00
0982f993cb ci: short-circuit npm install for aio builds that use yarn only 2017-07-31 11:39:41 -07:00
a5a29b0591 docs(aio): delay ngUpgrade e2e test to avoid flakes 2017-07-31 11:39:36 -07:00
a8f3197f24 build: short-circuit build for AIO tasks 2017-07-31 11:39:31 -07:00
e6f37120fe docs(aio): fix deprecated protractor API usage
`browser.getLocationAbsUrl()` is deprecated.
We should use `browser.getCurrentUrl()` instead.
2017-07-31 11:39:25 -07:00
6840b7bda9 ci(aio): test the example e2e files using local build of Angular 2017-07-31 11:39:21 -07:00
68f458909a build(aio): ignore generated aot files
Assets such as images and data which are generated
by the aot build were not being ignored.
2017-07-31 11:39:14 -07:00
12acecf756 docs(aio): remove generated styles.css file
This file should have been ignored as it is created
during the build of the example
2017-07-31 11:39:10 -07:00
cfbed40ab6 build(aio): support overriding the Angular packages in examples with locally built ones 2017-07-31 11:39:03 -07:00
fe1a6b8e42 build(aio): refactor and test the example-boilerplate tool 2017-07-31 11:38:59 -07:00
13e29c4e89 ci: shard the aio example e2e tests 2017-07-31 11:38:53 -07:00
fd52b178ed ci(aio): support sharding of example e2e tests 2017-07-31 11:38:48 -07:00
ca1f071b2e build(aio): upgrade @angular/material to 2.0.0-meta.8 2017-07-31 11:38:43 -07:00
296adbbb72 build(aio): upgrade @angular/* to 4.3.1 2017-07-31 11:38:37 -07:00
c795ee1176 docs(aio): fix URLSearchParams interface link to MDN
Fixes #18367
2017-07-31 11:38:33 -07:00
b550618afd refactor(aio): move content-specific images to content/images/
Fixes #17053
2017-07-31 11:38:28 -07:00
d08d6eebff refactor(aio): rename unused directories to _unused 2017-07-31 11:38:23 -07:00
e9789abd05 fix(aio): fix link to logo in example 2017-07-31 11:38:17 -07:00
f2ec2cbb99 refactor(aio): move unused images to unused directories
This prevents the ServiceWorker from prefetching unnecessary files.
2017-07-31 11:38:09 -07:00
8de2ace80a docs: add changelog for 4.3.2 2017-07-27 15:51:46 -05:00
c977994864 release: cut the 4.3.2 release 2017-07-27 15:51:46 -05:00
12b8e1af55 Revert "fix(router): should throw when lazy loaded module doesn't define any routes (#15001)"
This reverts commit be49e0ee93.
2017-07-27 13:05:36 -07:00
9a188485f5 ci: correct bad bazel merge 2017-07-27 12:43:29 -07:00
45a10419bc Revert "build: Bazel builds ngfactories for packages/core (#18289)"
This reverts commit bcea196530.
2017-07-27 10:51:41 -07:00
2245748c14 test(compiler): fix xliff2 integration test for the 4.3 branch (#18363)
Closes #18363
2017-07-27 10:20:34 -07:00
bcea196530 build: Bazel builds ngfactories for packages/core (#18289)
PR Close #18289
2017-07-26 16:31:26 -07:00
b9e32c833a fix(router): child CanActivate guard should wait for parent to complete (#18110)
Closes #15670

PR Close #18110
2017-07-26 17:52:47 -05:00
be49e0ee93 fix(router): should throw when lazy loaded module doesn't define any routes (#15001)
Closes #14596

PR Close #15001
2017-07-26 16:49:50 -05:00
bf95655a1a docs(aio): add my details as a contributor (#18315)
PR Close #18315
2017-07-26 16:49:50 -05:00
6bf5b84fa4 fix(aio): correctly process markdown link in "Browser Support" (#18349)
The markdown processor expects an empty line between an opening tag and the
markdown content. (If there is no empty line, the content is interpreted as
plain HTML.)
Previously, the line between the opening `<td>` and the content contained
whitespace, which caused the content to be interpreted as HTML and not markdown.

Fixes #18312

PR Close #18349
2017-07-26 16:11:11 -05:00
4836565ca7 docs(aio): update examples to 4.3 2017-07-26 12:08:29 -05:00
750e4e8156 docs(aio) - Fixed link to the glossary dash-case term (#18311)
PR Close #18311
2017-07-26 12:08:29 -05:00
a0846194b7 fix(compiler): add equiv & disp attributes to Xliff2 ICU placeholders (#18283)
Fixes #17344

PR Close #18283
2017-07-26 12:08:29 -05:00
bcf6b90c95 docs(platform-server): inline PlatformOptions and add doc strings (#18264)
Fix documentation for the options passed into renderModule and
renderModuleFactory.

PR Close #18264
2017-07-26 12:08:29 -05:00
3ca2a0aa37 refactor(compiler-cli): add support for browser compiler bundle (#17979)
PR Close #17979
2017-07-26 12:08:29 -05:00
b4be96c65d ci: use chrome stable (#18307) 2017-07-26 12:08:29 -05:00
434ff5fecb build: update bazel rules to latest (#18289) 2017-07-26 12:08:24 -05:00
a1bb9c2d42 fix(core): invoke error handler outside of the Angular Zone (#18269)
In Node.JS console.log/error/warn functions actually resuls in a socket
write which in turn is considered by Zone.js as an async task.

This means that if there is any exception during change detection in a platform-server
application the error handler will make the Angular Zone unstable which
in turn will cause change detection to run on next tick and cause an
infinite loop.

It is also better to run the error handler outside of the Angular Zone
in general on all platforms so that an error in the error handler itself doesn't cause an
infinite loop.

Fixes #17073, #7774.

PR Close #18269
2017-07-26 12:04:47 -05:00
7e626bef0a ci: force precise on Travis (#18282)
PR Close #18282
2017-07-26 12:04:47 -05:00
a1e83a8ed2 test: fix bad merge (#18267)
PR Close #18267
2017-07-26 12:04:47 -05:00
cbeb197aa5 fix(animations): export BrowserModule as apart of BrowserAnimationsModule (#18263)
PR Close #18263
2017-07-26 12:04:47 -05:00
0330fa6b82 docs(aio): pngcrush all pngs (#18243)
PR Close #18243
2017-07-26 12:04:47 -05:00
97135e8fd5 fix(platform-server): don't clobber parse5 properties when setting (#18237)
element properties.

Fixes #17050.

We now store all element properties in a separate 'properties' bag.

PR Close #18237
2017-07-26 12:04:47 -05:00
35bd07fc7b refactor(common): CleanUp HttpClient's imports (#18120)
PR Close #18120
2017-07-26 12:04:47 -05:00
a8ac77b645 fix(compiler): allow numbers for ICU message cases in lexer (#18095)
Closes #18095
Fixes #17799
2017-07-26 12:04:47 -05:00
9ecd377a51 ci(aio): fix aio payload script 2017-07-26 12:04:47 -05:00
76171bd8b4 test(upgrade): fail tests when there are AngularJS errors (#18209) 2017-07-26 12:04:47 -05:00
1f106d75bc fix(upgrade): throw error if trying to get injector before setting (#18209)
Previously, `undefined` would be returned.
This change makes it easier to identify incorrect uses/bugs.
(Discussed in https://github.com/angular/angular/pull/18213#issuecomment-316191308.)
2017-07-26 12:04:47 -05:00
a4fae8c405 aio: debounce search and delay index building (#18134)
* feat(aio): debounce search requests

* feat(aio): delay loading search worker and index
2017-07-26 12:04:46 -05:00
33c07b3394 fix(aio): invalid formatting in architecure.md (#18159)
Introduced in e110a80caf (diff-9ac9c6a9277eea9856d75249a7c0a40aL127)
2017-07-26 12:00:01 -05:00
c9d06e676f docs(aio): replace old blog link (#18252)
Fixes #18233
* Docs(aio): Replaced old blog link Now with the Link to the new Angular.io Blog
* Removed double braces
2017-07-26 12:00:01 -05:00
c7c65d9fda docs: fixing invisible tag in README (#18245) 2017-07-26 12:00:01 -05:00
257a9e3e6f ci: test bazel builds on travis (#18240) 2017-07-26 12:00:01 -05:00
c7c0a1688e docs(aio): Fixed typo with closing div 2017-07-26 12:00:00 -05:00
7e95e2b0ba docs(aio): Fix http guide 2017-07-26 12:00:00 -05:00
ddc286f4b5 docs: fix wrong link in CONTRIBUTING.md (#18228)
The link to the CONTRIBUTING.md file was not correct
2017-07-26 12:00:00 -05:00
3d17a3672e docs(router): minor typo (#18226)
Fix a minor typo in the description of a router spec.
2017-07-26 12:00:00 -05:00
61d253f5fd docs(aio): fix typo in NgModule FAQs (#18211)
Fix #18133. Fix typo 'added' to 'add' in the 'Why does lazy loading create a child injector' section.
2017-07-26 12:00:00 -05:00
54be25a7a1 docs: add changelog for 4.3.1 2017-07-19 12:54:45 -07:00
b1757037fb release: cut the 4.3.1 release 2017-07-19 12:53:17 -07:00
f0476fcff0 fix(compiler-cli): don't generate empty <target/> when extracting xliff
Fixes #15754
2017-07-19 09:50:09 -07:00
a5c4bb5b96 fix(animations): make sure @.disabled works in non-animation components
Note 4.3 only!

Prior to this fix when [@.disabled] was used in a component that
contained zero animation code it wouldn't register properly because the
renderer associated with that component was not an animation renderer.
This patch ensures that it gets registered even when there are no
animations set.
2017-07-19 09:50:03 -07:00
4c1f32b0db fix(animations): do not crash animations if a nested component fires CD during CD
Closes #18193
2017-07-19 09:49:57 -07:00
383d8969ab fix(animations): always camelcase style property names that contain auto styles
Closes #17938
2017-07-19 09:49:47 -07:00
333ffd8d32 fix(animations): capture cancelled animation styles within grouped animations
Closes #17170
2017-07-19 09:49:42 -07:00
d4679a0bc2 docs(aio): fix typo in Router documentation
Fix title and link to RouteConfigLoadEnd documentation
2017-07-19 15:02:14 +01:00
4ce29f3a5b fix(platform-server): provide XhrFactory for HttpClient 2017-07-18 14:19:18 -07:00
17b7bc3e06 fix(common): send flushed body as error instead of null
fix #18181
2017-07-18 10:58:27 -07:00
f19bd5f4f3 docs(http): Make name of injected HttpTestingController consistent 2017-07-18 10:54:42 -07:00
d503d25f29 docs(platform-server): add doc string for PlatformOptions 2017-07-18 10:54:37 -07:00
5d275e994a fix(router): terminal route in custom matcher 2017-07-18 10:54:32 -07:00
d8c8b13bb8 docs: fix typo 2017-07-18 10:54:26 -07:00
4671168635 fix(compiler): ensure jit external id arguments names are unique
Fixes: #17558, #17378, #8676
2017-07-18 10:54:21 -07:00
1ac78bfd5d fix(router): canDeactivate guards should run from bottom to top
Closes #15657.
2017-07-18 10:54:15 -07:00
4340beacea fix(router): should navigate to the same url when config changes
Closes #15535
2017-07-18 10:54:09 -07:00
ec89f378fc fix(router): should run resolvers for the same route concurrently
Fixes #14279
2017-07-18 10:54:04 -07:00
4dd6863bc2 docs(aio): fix HttpClient setting new header sample 2017-07-18 10:53:58 -07:00
37c626e673 fix(aio): remove title attribute from CodeExampleComponent
This was causing browser to add an unwanted tooltip that appeared
when the user hovers over the code.

See #17524
2017-07-18 17:55:54 +01:00
f0a110928b fix(aio): add quote to module 2017-07-18 17:54:51 +01:00
c39e7d1eb2 fix(aio): do not wrap <code-tabs> tab labels
Fixes #17751
2017-07-18 17:45:20 +01:00
799bffb431 docs(aio): fix cheatsheet layout for narrow screens
* Tell the app that this will have no Table of Contents, since we have no
h2 headings anyway.
* Remove all the `nbsp;` from the code since that doesn't help with layout
* Remove side padding from sidenav-content when screen is narrow
* Restyle the cheatsheet table when the screen is narrow
2017-07-18 17:33:36 +01:00
fda607cc2f build(aio): fail doc-gen if referenced images are missing 2017-07-18 11:46:15 +01:00
cc3aa68123 docs(aio): fix broken image sources 2017-07-18 11:46:15 +01:00
306621d2d6 docs(aio): fix up broken links 2017-07-18 11:46:15 +01:00
d204f7aa2a build(aio): abort doc-gen on dangling links 2017-07-18 11:46:14 +01:00
a94f5e8cbb build(aio): abort doc-gen if an example is missing
Closes #16936
2017-07-18 11:46:14 +01:00
1390afef23 docs: fix HttpClient sample 2017-07-17 14:18:41 -07:00
b0346a6e45 docs(aio): fix HttpClient's interceptor sample 2017-07-17 14:18:41 -07:00
e5da059994 docs(http): fix "Expecting and answering requests" example mistake
Possibly overlooked testing documentation mistake fixed:
- `http.get('/data')` probably ought to be paired with `httpMock.expectOne('/data')` instead of `httpMock.expectOne('/data')`.
2017-07-17 14:18:41 -07:00
ac92c3bb26 docs: fix HttpClient logging's sample 2017-07-17 14:18:41 -07:00
87157d7089 docs(http): fixed syntax error in AuthInterceptor example 2017-07-17 14:18:41 -07:00
611dd12f0f ci: add GK and PBD to aio content and marketing groups 2017-07-17 14:18:41 -07:00
969ce9dc2b fix(aio): remove title from callout 2017-07-15 15:39:40 +01:00
34834a9e79 fix(aio): remove unused news.html file
Although outdated and not used, the file would be picked up and showed in search
results.
2017-07-15 15:29:59 +01:00
6e2ddccc2c build(aio): render type parameters of API function exports
Fixes #18123
2017-07-15 08:54:34 +01:00
55742e4737 Revert "revert: revert: ci(aio): exclude changes in aio/content folder"
This reverts commit 3d85f72652.

Still causing repeated flakes on master.
2017-07-14 14:55:56 -07:00
0091b1e8db refactor(upgrade): clean up some types 2017-07-14 14:55:46 -07:00
a0b06befb6 docs: add changelog for 4.3.0 2017-07-14 13:12:37 -07:00
4fbb5b29ea release: cut the 4.3.0 release 2017-07-14 12:50:40 -07:00
e0fa727594 fix(aio): document XSRF protection in HttpClient 2017-07-14 12:42:37 -07:00
3ecc5e5398 fix(common): rename HttpXsrfModule to HttpClientXsrfModule 2017-07-14 12:40:52 -07:00
f7686d4124 Revert "fix(animations): make sure @.disabled works in non-animation components"
This reverts commit 01a2688848.
2017-07-14 11:00:29 -07:00
01a2688848 fix(animations): make sure @.disabled works in non-animation components
Note 4.3 only!

Prior to this fix when [@.disabled] was used in a component that
contained zero animation code it wouldn't register properly because the
renderer associated with that component was not an animation renderer.
This patch ensures that it gets registered even when there are no
animations set.
2017-07-14 10:34:58 -07:00
8e56c3cb30 docs: fix type error in HttpClient sample 2017-07-14 09:35:25 -07:00
7955cacec4 build(aio): turn on debugging of the payload.sh script to debug ci failures 2017-07-13 22:50:05 -07:00
dd04f09483 feat(common): on-by-default XSRF support in HttpClient (#18108)
Fixes #18100
2017-07-13 17:22:02 -07:00
3d85f72652 revert: revert: ci(aio): exclude changes in aio/content folder
This reverts commit 72143e80da.

it seems that the previous build failure was a travis flake.

reverting the revert...
2017-07-13 16:22:57 -07:00
9f28e838d3 fix(common): expose reportProgress option on HttpClient API (#18083) 2017-07-13 16:19:52 -07:00
ddb766e456 feat(compiler): do not evaluate metadata expressions that can use references (#18001) 2017-07-13 16:16:56 -07:00
72143e80da revert: ci(aio): exclude changes in aio/content folder
This reverts commit 1bf7ba87a0.

The commit caused master builds to fail with:

```
$ scripts/payload.sh
error Command failed with exit code 1.
error Command failed with exit code 1.
```
https://travis-ci.org/angular/angular/jobs/253405260#L1024
2017-07-13 15:32:44 -07:00
bc1ea8c54b docs: rewrite 'Server Communication' doc guide to use HttpClient (#18079) 2017-07-13 15:22:07 -07:00
45ffe54ae4 fix(tsc-wrapped): support as and class expressions (#16904) 2017-07-13 15:17:16 -07:00
1bf7ba87a0 ci(aio): exclude changes in aio/content folder 2017-07-13 15:13:37 -07:00
db96c963a8 fix(aio): add <hr> in between overloads 2017-07-13 14:58:33 -07:00
18559897a0 fix(common): document HttpClient, fixing a few other issues 2017-07-13 14:58:33 -07:00
ce0f4f0d7c fix(tsc-wrapped): report errors for invalid ast forms (#17994)
Fixes: #17993
2017-07-13 14:55:59 -07:00
4f1e4ffa4e fix(common): don't guess Content-Type for FormData bodies (#18104)
Fixes #18096
2017-07-13 12:28:20 -07:00
f0beb4d750 docs(aio): add Dart as a language option (#18080) 2017-07-13 12:21:28 -07:00
bc3b2ac251 docs(aio): Updated resources to UI Components (#18067)
With https:// the site gives console errors, because it loads fonts with http://
2017-07-13 12:19:31 -07:00
ffda3e41e0 build(aio): simplify the primary overload's anchor
The primary link to a member should simply be its name, and not require
the parameter list.
2017-07-13 12:16:20 -07:00
a301dba68f build(aio): improve API docs layout and styling 2017-07-13 12:16:20 -07:00
04f3a4a7a5 build(aio): fix githubLinks line numbers
The API docs have changed. The line numbers are now found in
`startingLine` and `endingLine` properties rather than the `location`
property, which moved into the `fileInfo` property anyway.
2017-07-13 12:16:20 -07:00
f06ce9adc8 build(aio): truncate API overview parameters at one line 2017-07-13 12:16:20 -07:00
660eec4a23 build(aio): upgrade to dgeni-packages 0.20.0-rc.5
This fixes unwanted extra overloads in interfaces and abstract classes
This provides the overload grouping support and fixes API doc line numbers
2017-07-13 12:16:20 -07:00
be3352a084 build(aio): render grouped overloads 2017-07-13 12:16:20 -07:00
998049ec9b build(aio): truncate API overview parameters at one line 2017-07-13 12:16:20 -07:00
a7ea0086ee ci: fast-forward to master/HEAD on all circle jobs (#18101)
This problem was introduced by a bad merge.
2017-07-13 12:16:02 -07:00
edb8375a5f docs: adjust the release schedule with the 4.3.0-rc.0 release date 2017-07-11 15:52:06 -07:00
26b9492315 docs(aio): Added resource link to amexio-ng-extensions [duplicate of #17785] (#17790) 2017-07-11 15:06:14 -07:00
e110a80caf docs(aio): change Angular Module to NgModule (#16964) 2017-07-11 15:05:20 -07:00
20127c1456 docs: fix typos and grammar (#17818) 2017-07-11 13:00:53 -07:00
a50d935a48 docs(benchpress): fix android broken links (#17568) 2017-07-11 12:54:34 -07:00
7c479f073e docs: document using Bazel (#17701) 2017-07-11 12:48:50 -07:00
bbf2133fa9 docs: update ECMAScript info (#17801) 2017-07-11 12:44:56 -07:00
4300439ab2 docs: improve grammar and wording on CONTRIBUTING.md (#17855) 2017-07-11 12:40:30 -07:00
ec14679668 docs: add @angular/animations to list (#17488)
@angular/animations was missing from the SemVer supported API surface list, now added.
2017-07-11 12:30:01 -07:00
df06e8b7a4 fix(router): fix outdated homepage url in NPM package (#17899) 2017-07-11 12:00:56 -07:00
1b1a6ba0bb docs: fix 404 link to SFEIR website (#17913)
Also, I moved SFEIR training description to the "onsite" section since SFEIR doesn't deliver online training.
2017-07-11 12:00:27 -07:00
876ca9ee3a docs(aio): fix host usage in styleguide (#17932) 2017-07-11 11:58:26 -07:00
d9b03be08f test: git ignore e2e test folders (#17984) 2017-07-11 11:55:24 -07:00
b6aad07634 build: remove duplicate dependency on TypeScript
TypeScript was added as a dependency in 9a7f5d580. I assume the intention was to
upgrade the existing devDependency instead.
2017-07-11 11:53:43 -07:00
3d0406c247 docs: update required Node/npm versions in DEVELOPER.md 2017-07-11 11:53:43 -07:00
db3bcc939e docs: fix CHANGELOG typo (#18026) 2017-07-11 11:51:46 -07:00
4d45fe6fb5 fix(aio): fix tab animations on CodeTabsComponent
In our attempt to remove the material ripple effect from tab labels, we were
killing all `transform`-based animations on other `md-tab-group` elements, such
as animating the content when entering/leaving. (This wasn't an issue on Chrome,
because it didn't respect our `!important` flag.)
This commit fixes it by properly hiding the ripple effect (using a feature
introduced in angular/material2@e4789c7b8) and allowing other animations to
execute normally.

Fixes #17998
2017-07-11 11:49:22 -07:00
076ea2281f test(aio): add tests for CodeTabsComponent 2017-07-11 11:49:22 -07:00
aec39c28d8 refactor(aio): remove unnecessary comments/imports, improve indentation 2017-07-11 11:49:22 -07:00
b9525ece77 docs: Unbreak README Formatting (#18043)
- Makes headers consistent
 - Removes extra `)`
 - Makes browserstack line on a separate line
2017-07-11 11:48:37 -07:00
719101338a docs: does please -> does (#18044)
Fixes grammar issue.
2017-07-11 11:46:57 -07:00
e131f6bbe8 docs: various minor formatting changes (#18046) 2017-07-11 11:46:34 -07:00
a9757ec674 docs: Fix code example in the ChangeDetectorRef docs (#18051)
Minor example fix: mark the 'ref' constructor parameter as a private property
2017-07-11 11:45:32 -07:00
9003770f02 docs: fix style in helper text (#18055) 2017-07-11 11:43:52 -07:00
e8bbf86e66 build(aio): add terms from heading to the search index 2017-07-11 15:04:37 +01:00
3a0886dc12 build(aio): capture all the headings from a doc in the vFile.headings property 2017-07-11 15:04:37 +01:00
062a7aa2cf build(aio): revert to general purpose search algorithm
Now that we have upgraded to the latest lunr search engine, the results
from the standard `search` method are more appropriate.
So we do not need to create our own special queries to get good results.
2017-07-11 15:04:37 +01:00
e28f097fc2 ci: add aio/content to aio group
This commit also simplifies the rules for the aio content and marketing groups
2017-07-11 11:55:09 +01:00
b30c5fc874 docs(aio): fix dangling links 2017-07-11 11:44:05 +01:00
d52ab8e2c9 build(aio): fix matchUpDirectiveDecorators processor 2017-07-11 11:44:05 +01:00
df7b875f6c build(aio): fix mergeDecoratorDocs processor 2017-07-11 11:44:05 +01:00
0e71836cd5 docs(aio): fix potential invalid jsdoc tag 2017-07-11 11:44:05 +01:00
470a7c6bcd build(aio): use dedicated overview for decorator API docs 2017-07-11 11:44:05 +01:00
3abf208235 build(aio): update doc-gen templates 2017-07-11 11:44:05 +01:00
92c18d167e build(aio): fix mergeDecoratorDocs processor 2017-07-11 11:44:05 +01:00
99b38f52cb build(aio): fix matchUpDirectiveDecorators processor 2017-07-11 11:44:05 +01:00
633ec30291 build(aio): fix test description 2017-07-11 11:44:05 +01:00
57cfcb0830 build(aio): refactor filterMemberDocs to be more general
Now it also filters function-overloads, hence the name change.
2017-07-11 11:44:05 +01:00
433d479a1e build(aio): upgrade to dgeni-packages v0.20.0-rc.1 2017-07-11 11:44:05 +01:00
7c4ac68e66 build(aio): remove unused script and GitHub token
Since 808bd4af4, we are no longer pre-verifying PRs before uploading the build
artifacts to the preview server, thus we no longer need the
`travis-preverify-pr.sh` script or the `GITHUB_TEAM_MEMBERSHIP_CHECK_KEY`
variable.
2017-07-11 11:03:29 +01:00
20556346a3 feat(aio): add API endpoint for notifying about PR updates
This commit adds an API endpoint for notifying the preview server about PR
updates (`/pr-updated`). According to the update, the preview server can take
several actions. Currently, it will only check and (if necessary) update the
PR's preview visibility (but more actions could be supported in the future).
The API can be used with an automatic trigger (e.g. a GitHub webhook) to
instantly update a PR's preview visibility when it changes.

Fixes #16526
2017-07-10 12:30:45 +01:00
5a417b8514 feat(aio): implement a way to check and update a PR\'s preview visibility
Previously, `BuildCreator#changePrVisibility()` would throw an error if the PR's
visibility was already up-to-date or if the PR directory did not exist (e.g. was
removed). This method was only used from inside `BuildCreator#create()`, which
had already checked for the existence of the directories.

This commit renames `changePrVisibility()` to `updatePrVisibility()` and makes
it more "forgiving" (i.e. it will only throw if both public and non-public
directories exist). This allows it to be used on events that may or may not have
caused the PR's visibility to change (e.g. a GitHub webhook triggered whenever a
PR's labels change).
2017-07-10 12:30:45 +01:00
8cfc2e2ec0 refactor(aio): unify error messages for invalid requests to upload-server
Previously, there was a distinction between GET requests to invalid URLs and all
other requests. This was mainly because the upload-server only accepts GET
requests, but that is not a hard limitation and may change in the future.

Thus, it makes sense to return a 404 response for requests to invalid URLs
regardless of the method used.
2017-07-10 12:30:45 +01:00
11647e4c78 refactor(aio): use dedicated constants.ts file for e2e-specific constants 2017-07-10 12:30:45 +01:00
9e1b61326c refactor(aio): move script to a more relevant directory 2017-07-10 12:30:45 +01:00
cb7609109d fix(aio): fix text blurring when hovering over item in resources
Fixes #17907
2017-07-10 12:24:10 +01:00
b3d90365b6 fix(aio): add new common/http package into API list 2017-07-10 12:23:47 +01:00
cc611c93b6 docs: add changelog for 4.3.0-rc.0 2017-07-07 22:10:17 -07:00
8928a58796 release: cut the 4.3.0-rc.0 release 2017-07-07 22:07:13 -07:00
05a33d5035 docs: add changelog for 4.2.6 2017-07-07 22:03:53 -07:00
09f1609f81 style(http): fix linting error in http/testing (#18002) 2017-07-07 17:02:07 -07:00
c723d42d0a refactor: fix typos (#18000) 2017-07-07 16:55:17 -07:00
9dd550fa1e ci(aio): only deploy latest commits to staging/production (#17988)
Fixes #17941
2017-07-07 16:35:07 -07:00
abbac4bc69 feat(aio): use new ngo (#17977) 2017-07-07 16:34:47 -07:00
671a175dfb fix(compiler-cli): fix relative source paths on windows for extracted msg (#17915)
Fixes #16639
2017-07-07 16:33:40 -07:00
c1474f33be feat(compiler-cli): add parameters to ngc main needed by bazel rules (#17885) 2017-07-07 16:29:39 -07:00
0ede642cb9 docs(aio): Update NgStyle and NgClass to use quoted keys 2017-07-07 16:17:33 -07:00
9c1f6fd06f fix(compiler): emits quoted keys only iff they are quoted in the original template
fixes #14292
2017-07-07 16:17:33 -07:00
798947efa4 feat(compiler): adds support for quoted object keys in the parser 2017-07-07 16:17:33 -07:00
7ae8ad6aab fix(compiler): fix types 2017-07-07 16:17:33 -07:00
9c3386b1b7 fix(compiler): remove i18n markup even if no translations (#17999)
Fixes #11042
2017-07-07 16:16:49 -07:00
2ba3ada27f revert: "refactor(compiler-cli): remove the dependency on fs in codegen.ts (#17738)"
This reverts commit b116901400.
2017-07-07 16:16:25 -07:00
8e28382e4a feat(animations): support disabling animations for sub elements
Closes #16483
2017-07-07 14:58:40 -07:00
3203639d7d fix(animations): ensure :animating queries collect previous animation elements properly 2017-07-07 14:58:40 -07:00
f85b543cc1 fix(animations): properly detect state transition changes for object literals 2017-07-07 14:58:40 -07:00
c81ad9d19d feat(common): two missing features in HttpClient (#17996)
- Add params to HttpRequest API
- Add optional description to testing APIs
2017-07-07 14:56:36 -07:00
37797e2b4e feat(common): new HttpClient API
HttpClient is an evolution of the existing Angular HTTP API, which exists
alongside of it in a separate package, @angular/common/http. This structure
ensures that existing codebases can slowly migrate to the new API.

The new API improves significantly on the ergonomics and features of the legacy
API. A partial list of new features includes:

* Typed, synchronous response body access, including support for JSON body types
* JSON is an assumed default and no longer needs to be explicitly parsed
* Interceptors allow middleware logic to be inserted into the pipeline
* Immutable request/response objects
* Progress events for both request upload and response download
* Post-request verification & flush based testing framework
2017-07-07 12:09:32 -07:00
2a7ebbe982 refactor: move DOCUMENT from platform/browser to common 2017-07-07 12:09:32 -07:00
72747e5213 ci: disable sauce-connect logging for realz (#17995)
follow up on #17947
2017-07-07 11:59:40 -07:00
504500de50 fix(aio): activate ServiceWorker updates asap (#17699)
Previouly, whenever a new ServiceWorker update was detected the user was
prompted to update (with a notification). This turned out to be more distracting
than helpful. Also, one would get notifications on all open browser tabs/windows
and had to manually reload each one in order for the whole content (including
the app) to be updated.

This commit changes the update strategy as follows:
- Whenever a new update is detected, it is immediately activated (and all
  tabs/windows will be notified).
- Once an update is activated (regardless of whether the activation was
  initiated by the current tab/window or not), a flag will be set to do a
  "full page navigation" the next time the user navigates to a document.

Benefits:
- All tabs/windows are updated asap.
- The updates are applied authomatically, without the user's needing to do
  anything.
- The updates are applied in a way that:
  a. Ensures that the app and content versions are always compatible.
  b. Does not distract the user from their usual workflow.

NOTE:
The "full page navigation" may cause a flash (while the page is loading from
scratch), but this is expected to be minimal, since at that point almost all
necessary resources are cached by and served from the ServiceWorker.

Fixes #17539
2017-07-07 11:17:19 -07:00
e1174f3774 fix(aio): home & marketing styles cleanup (#17926) 2017-07-07 11:16:51 -07:00
6bae73c076 refactor(common): replace Object.assign with the spread operator (#17982)
`Object.assign` is not available in all supported browsers and one had to
provide a polyfill. This commit replaces `Object.assign` with the spread
operator (`...`), which TypeScript will transpile to ES5-compatible code.
2017-07-07 08:55:29 -07:00
11db3bd85e fix(upgrade): bring the dynamic version closer to the static one
(#17971)

This commit changes the dynamic version of ngUpgrade to use `UpgradeHelper`,
thus bringing its behavior (wrt upgraded components) much closer to
`upgrade/static`. Fixes/features include:

- Fix template compilation: Now takes place in the correct DOM context, instead
  of in a detached node (thus has access to required ancestors etc).
- Fix support for the `$onInit()` lifecycle hook.
- Fix single-slot transclusion (including optional transclusion and fallback
  content).
- Add support for multi-slot transclusion (inclusing optional slots and fallback
  content).
- Add support for binding required controllers to the directive's controller
  (and make the `require` behavior more consistent with AngularJS).
- Add support for pre-/post-linking functions.

(This also ports the fixes from #16627 to the dynamic version.)

Fixes #11044
2017-07-07 08:54:39 -07:00
0193be7c9b feat(upgrade): fix support for directive.link in upgraded components
(#17971)

Although, pre- and post-linking functions are correctly called during directive
linking, directives with `link.post` would throw an error. Interestingly, having
`link.pre` only or defining `link: fn` (which is an alias for `link.post: fn`)
would not throw.

This commit removes this check and allows directives with pre- and/or
post-linking functions to work.
2017-07-07 08:54:09 -07:00
2ea73513ea refactor(upgrade): move shareable functionality to UpgradeHelper class (#17971)
This functionality can be potentionally re-used by the dynamic version.
2017-07-07 08:53:16 -07:00
67e9c62013 fix(upgrade): fix transclusion on upgraded components (#17971)
Previously, only simple, single-slot transclusion worked on upgraded components.
This commit fixes/adds support for the following:

- Multi-slot transclusion.
- Using fallback content when no transclusion content is provided.
- Destroy unused scope (when using fallback content).

Fixes #13271
2017-07-07 08:52:46 -07:00
227dbbcfba fix(language-service): ignore hover of symbols not in the TypeScript program (#17969)
Fixes: #17965
2017-07-07 08:47:28 -07:00
cb16e9c747 fix(tsc-wrapped): emit exports metadata in flat modules (#17893)
Fixes: #17888
2017-07-07 08:46:42 -07:00
3b2d2c467a fix(language-service): do not crash when hovering over a label definitions (#17974)
Fixes: #17972
2017-07-07 08:46:18 -07:00
ae27af7399 fix(router): encode URLs the same way AngularJS did (closer to spec) (#17890)
fixes #16067
2017-07-06 17:10:25 -07:00
c69fff15c9 fix(core): fix re-insertions in the iterable differ (#17891)
fixes #17852
2017-07-06 12:11:47 -07:00
dd7c1134e3 feat(compiler): update the schema by extracting from latest chrome (#17858)
adds:
- the slot element,
- the slot attribute on [Element],
- a few other attributes.
2017-07-06 12:11:34 -07:00
b116901400 refactor(compiler-cli): remove the dependency on fs in codegen.ts (#17738) 2017-07-06 12:11:21 -07:00
70981c601e feat(aio): serve-and-sync command (#17850) 2017-07-06 11:51:58 -07:00
f2f61c9cf0 ci: allow chuck to approve animations (#17940) 2017-07-06 11:49:44 -07:00
1bb2476804 docs(aio): add instructions for example e2e tests to README (#17819) 2017-07-06 11:15:31 -07:00
ec58246a1b docs(aio): add DevExtreme to the UI Components section of resources.json (#17939) 2017-07-06 11:15:15 -07:00
6fc5174a13 build: disable sauce connect logging (#17947)
this change is expected to mitigate the flakes on CI that occur when
we cat the log in the print-logs.sh
2017-07-06 11:00:00 -07:00
105e920b69 fix(animations): properly handle cancelled animation style application 2017-07-06 10:18:12 -07:00
858dea98e5 fix(animations): properly cleanup query artificats when animation construction fails 2017-07-06 10:18:12 -07:00
71ee0c5b03 ci(aio): raise polyfill payload limits 2017-07-06 14:46:53 +01:00
578bdeb522 fix(router): export missing UrlMatcher and UrlMatchResult types
Fixes #15140
2017-07-05 15:26:27 -07:00
6282a86135 ci: add angular.io content related groups to .pullapprove.yaml 2017-07-05 15:26:27 -07:00
e9b67243ed build(aio): upgrade to angular@4.2.4 and zone.js 2017-07-05 15:26:27 -07:00
fa1c187abc build: upgrade yarn to 0.24.6 2017-07-05 15:26:27 -07:00
b51697c197 ci: add Chuck to the owners of core (#17892) 2017-07-05 15:10:11 -07:00
c6b75b0823 docs: add animations package to CONTRIBUTING.md (#15413) 2017-07-05 14:34:13 -07:00
Joe
0d7e1a9b4e docs(changelog): Correct typos (#16966) 2017-07-05 14:33:47 -07:00
9d15d85391 docs: README - use correctly encoded link for npm badge (#17707)
closes #14990, an old PR that was approved but to old/difficult to merge
2017-07-05 14:33:25 -07:00
92d7ecf627 docs: shefali edits to Authors Style Guide (#17853) 2017-07-05 14:33:06 -07:00
9263da570f ci(aio): Fix the payload script only check for changes in aio/scripts 2017-07-03 10:40:19 +01:00
dc88e0a881 docs(aio): update quickstart to latest cli 2017-07-03 08:25:19 +01:00
fa34ed8bf3 ci(aio): use valid database path for Firebase payload size upload
Firebase does not allow `.` in the path, so when trying to upload payload size
data for branches like `4.2.x`, the following error is thrown:

```
HTTP Error: 400, Invalid path: Invalid token in path
```

This commit fixes it by replacing `.` with `_` in branch names.
2017-07-03 08:19:59 +01:00
f54a901b8d docs(aio): fix visual-studio-2015 typo
This fixes #17283
2017-07-03 08:12:28 +01:00
8a1a989a1c feat(router): add router-level events for GuardsCheck and Resolve (#17601) 2017-07-01 10:30:17 -07:00
b479ed9407 perf(core): refactor NgZone, decrease size by 1.2Kb (#17773)
- Remove getters
- Hide private methods for better property renaming

```
497893 May 31 11:26 core.umd.js
718073 May 31 11:26 core.umd.js.map
217108 May 31 11:26 core.umd.min.js
575092 May 31 11:26 core.umd.min.js.map
```

```
495594 May 31 11:28 core.umd.js
716943 May 31 11:28 core.umd.js.map
215826 May 31 11:28 core.umd.min.js
574401 May 31 11:28 core.umd.min.js.map
```
diff: 1,282
2017-07-01 10:29:56 -07:00
d5dc53ead8 ci(aio): avoid printing too large PWA report (and restore print-logs.sh)
There have been some issues lately with Travis jobs failing due to
`print-log.sh`. This is likely due to trying to print the Lighthouse PWA report,
which is too large.
This commit stops printing that report (since it was rarely used and is pretty
easy to acquire when needed) and restores the `print-logs.sh` script (that was
temporarily removed with dfcca66fd).
2017-07-01 14:40:36 +01:00
8e00161601 docs(aio): Add resources contributor info 2017-07-01 14:38:26 +01:00
01d4eae984 ci(aio): use correct URL for preview in PWA score test
In 4268c8289, the preview URLs were changed to not use the whole SHA, but just
the 7 first characters.
2017-06-30 13:02:28 +01:00
154154dde2 docs(aio): fix quickstart-cli bad HTML escape chars 2017-06-30 12:23:11 +01:00
4459e0c1c8 fix(aio): restore visible sidenav keyboard focus; shift TOC up
closes #17665
Restores keyboard focus that was removed by commit b8b91d3.
Raises the right-TOC by 20px (96px->76px) because was too far down.

To prevent keyboard focus on hidden child nodes,
also collapses inner expanded nodes when parent node is collapsed.
The implicit parent node of top nodes is always expanded.
2017-06-30 11:58:10 +01:00
b052ef5f1e fix(aio): do not include hidden content in window title
The window title is derived based on the current document's `<h1>` heading. Such
headings may contain hidden/non-visible content (e.g. textual name of font
ligatures: `<i class="material-icons">link</i>`) that should not be included in
the title.

This commit fixes this by using `innerText` (instead of `textContent`) to
extract the visible text from the `<h1>` heading. It will still fall back to
`textContent` on browsers that do not support `innerText` (e.g. Firefox 44).

Fixes #17732
2017-06-30 11:54:01 +01:00
40921bb927 docs: add changelog for 4.2.5 2017-06-29 17:19:57 -07:00
dfcca66fdc ci: remove print-logs.sh to try to fix failing builds on Travis 2017-06-29 14:54:34 -07:00
1ac9085b0a docs: add changelog for 4.3.0-beta.1 2017-06-29 14:41:40 -07:00
1cfe67dac4 release: cut the 4.3.0-beta.1 release 2017-06-29 14:38:52 -07:00
8d01db4638 docs: Updated router guide content and examples for paramMap
and queryParamMap, tracing, and incidental improvements.
closes #16991 and #16259 which it also fixes.
2017-06-29 09:35:39 -07:00
4268c82898 feat(aio): use shorter URLs for previews
Use the 7 first characters of the 40-chars long SHAs for shorter/cleaner URLs.
The collision probability is extremely low (since all SHAs are further
"namespaced" under the corresponding PR). In case of a collision, the second PR
will not be deployed, in order to avoid overwriting the original build.

(This is a design decision to keep the implementation simple. It can be changed
later if necessary.)
2017-06-29 09:35:22 -07:00
3c4eef99be fix(aio): clean up non-public previews
The previous clean-up code for PR directories on the preview server assumed that
all directories were named after the PR number. With the changes introduced
in #17640 it is possible to have PR directories that do not follow that naming
convention (e.g. "non-public" directories).

This PR ensures that both public and non-public directories are removed when
cleaning up.
2017-06-29 09:35:22 -07:00
96b17034e1 feat(aio): cross reference readme and docs styleguide 2017-06-29 09:35:04 -07:00
e47a77f941 docs(aio): update punctuation mark added in TOOLS guide 2017-06-29 09:33:12 -07:00
af14b1e384 test(animations): add proper tests to check multiple combinations of :enter/:leave animation queries 2017-06-28 18:18:10 -07:00
40f77cb563 fix(animations): properly collect :enter nodes that exist within multi-level DOM trees
Closes #17632
2017-06-28 18:18:10 -07:00
6c1a8daafc docs: correct grammar in CONTRIBUTING.md 2017-06-27 16:23:57 -07:00
d699c354db fix(animations): do not remove container nodes when children are queried by a parent animation
Closes #17746
2017-06-27 11:00:17 -07:00
34f3832af9 fix(animations): do not delay style() values before a stagger() runs
Closes #17412
2017-06-27 11:00:06 -07:00
f1626574dd fix(aio): build scripts-js before creating a new docker image for the preview server
When creating a new docker image for the preview server, the TypeScript source
code in `scripts-js/` is not copied over. Instead only the generated JavaScript
core in `scripts-js/dist/` are. Because of that, it is necessary to have run
`yarn build` before running `docker build`, so that the new docker image
contains the latest changes in `scripts-js/`.

This was previously part of the `create-image.sh` script, but was accidentally
removed in 21d213dfc.
2017-06-27 10:11:06 -07:00
68fc65dbcb ci: add npm postinstall back to the lint step so node_modules doesn't get out of date 2017-06-26 17:12:06 -07:00
693f79e88a docs: fix spelling of case variants in naming.md
unify variant forms of spelling letter cases (upper-case, uppercase, lower case)
2017-06-26 13:43:37 -07:00
448d9f9f46 test(compiler): add a test for components not part of any NgModule 2017-06-26 13:24:36 -07:00
8786ba95fb build(aio): boilerplate wont be removed by default now 2017-06-26 11:05:34 -07:00
97bb374218 fix(aio): Typo in Setup Anatomy documentation page
Solves #17076
2017-06-26 11:04:46 -07:00
233044e337 docs(aio): fix typo 2017-06-26 11:04:32 -07:00
f365a0f45c docs(aio): animations typos fixed 2017-06-26 11:04:13 -07:00
263c1a1d7e refactor: remove unused imports of the deprecated Renderer 2017-06-23 17:24:40 -07:00
3097083277 feat(compiler-cli): new compiler api and command-line using TypeScript transformers 2017-06-23 16:18:44 -07:00
43c187b624 refactor(forms): remove usage of deprecated Renderer 2017-06-23 16:12:52 -07:00
3165fd3dc9 refactor(router): remove usage of deprecated Renderer 2017-06-23 16:12:43 -07:00
e80851d98b fix(core): add needed closure compiler warning suppression 2017-06-23 14:44:00 -07:00
b754e600e3 feat(compiler): add support ::ng-deep
- /deep/ is deprecated and being removed from Chrome
- >>> is semantically invalid in a stylesheet
- sass will no longer support either in any version of sass

-> use ::ng-deep in emulated shadow DOM mode

Because the deep combinator is deprecated in the CSS spec,
`/deep/`, `>>>` and `::ng-deep` are also deprecated in emulated shadow DOM mode
and will be removed in the future.

see https://www.chromestatus.com/features/6750456638341120
2017-06-23 14:43:42 -07:00
81734cf7b6 refactor(compiler): add tracking comment for self-import detection
The underlying cause of #17386 needs to be investigated and as a
simple string compare should have been valid.
2017-06-23 14:43:24 -07:00
30f4fe26e0 docs(aio): cleanup rollup-config script 2017-06-23 11:55:55 -07:00
d6265dfcbe ci(aio): Change the firebase token 2017-06-23 11:55:20 -07:00
d51f86291f ci(aio): address comments 2017-06-23 11:55:20 -07:00
97ace57d39 ci(aio): fix test 2017-06-23 11:55:20 -07:00
86949e0c20 ci(aio): remove umd 2017-06-23 11:55:20 -07:00
6924780ae9 ci(aio): debug 2017-06-23 11:55:20 -07:00
1b0b69eeec ci(aio): add back deploy-preview 2017-06-23 11:55:20 -07:00
fa85389f62 ci(aio): updated limits 2017-06-23 11:55:20 -07:00
2e55857c82 ci(aio): rename limits file and address comments 2017-06-23 11:55:20 -07:00
ca970f5ee5 ci(aio): Also track umd.min.js file size 2017-06-23 11:55:20 -07:00
204a2cf942 ci(aio): Add payload size limit file 2017-06-23 11:55:20 -07:00
0440251919 ci(aio): upload aio payload size to firebase
ci(aio): Add timestamp and change data
2017-06-23 11:55:20 -07:00
08ecfd891d build: No longer need to bazel build twice 2017-06-23 11:54:51 -07:00
7395a64668 build: circleci workflows to run lint in parallel 2017-06-23 11:54:51 -07:00
979bfd07e1 refactor(aio): provide fallback values for secrets (useful during dev) 2017-06-23 11:54:20 -07:00
b6ce814279 refactor(aio): enable -u flag on preview server scripts 2017-06-23 11:54:20 -07:00
66088fef1a docs(aio): document preview server HTTP status codes 2017-06-23 11:54:20 -07:00
808bd4af41 ci(aio): deploy previews for all PRs
PRs that could not be automatically verified will not be publicly accessible,
until manually verified.
2017-06-23 11:54:20 -07:00
f90b35a85e test(aio): add e2e tests for non-public previews 2017-06-23 11:54:20 -07:00
8ae0eec230 feat(aio): enable previews for any PR
This commit introduces the ability to show previews for PRs by any author. It works as follows:

- The build artifacts of all PRs are uploaded to the preview server.
- Automatically verified PRs (i.e. from trusted authors or having a specific label) are deployed and
  publicly accessible as usual.
- PRs that could not be automatically verified are stored for later use (after re-verification).
- A PR can be marked as "trusted" and make its preview publicly accessible by adding the GitHub
  label specified in the `AIO_TRUSTED_PR_LABEL` env var of the preview server.

At the moment, there is no automatic mechanism for notifying the preview server about changes to the
PR's verification status. The PR's "visibility" will be checked and updated every time a new build
is uploaded.
2017-06-23 11:54:20 -07:00
0fe685102f refactor(aio): simplify preview server build events 2017-06-23 11:54:20 -07:00
a98440bb85 test(aio): add missing unit test for preview server 2017-06-23 11:54:20 -07:00
3112311134 test(aio): fix preview server tests on Windows 2017-06-23 11:54:20 -07:00
1b13bdea4b build(aio): upgrade preview server dependencies 2017-06-23 11:54:20 -07:00
3ce9d51a9c fix(aio): prefix location.assign with window.
No practical effect but clears the TS compiler warning.
2017-06-23 11:53:53 -07:00
14d2de13bb fix(aio): fix links on /about in Firefox
Fixes #17661
2017-06-23 11:53:22 -07:00
5713e7c9b6 refactor(aio): clean up aio-contributor template and styles 2017-06-23 11:53:22 -07:00
87206e1986 fix(aio): preserve newlines when copying code
Before 4f37f8643, we were using `innerText` to retrieved the code content for
copying. This preserved the text layout (including newlines), but suffered from
other issues (browser support, performance). With 4f37f8643 we switched to
`textContent`, which works well except in the following case:
When `prettify` formats the code to have line numbers, it removes the newlines
and uses `<li>` elements instead. This affects `textContent`.

This commit fixes this by keeping a reference of the code as text and using that
for copying.

Fixes #17659
2017-06-23 11:53:01 -07:00
414c7e956b ci: test merge commits on circle
We expect this behavior because it's what Travis does. Also it's better because we want
to test what happens if we merge the PR, not the status of the PR branch.
2017-06-23 11:52:41 -07:00
6191d53a78 docs(aio): Update resources.json - fixed language error 2017-06-23 11:52:22 -07:00
7d30ccc4a9 docs(aio): update resources - Add Compodoc - documentation tool for Angular app 2017-06-23 11:52:22 -07:00
494a0d064a fix(aio): fix topbar nav-item focus style
Fixing it requires upgrading `@angular/material` to v2.0.0-beta.7.

Fixes #17216
2017-06-23 11:51:17 -07:00
849200b576 docs: remove unnecessary newline 2017-06-23 11:50:48 -07:00
60273a941f docs: fix “under to hood” typo in changelog.md
closes #14856
2017-06-23 09:04:40 -07:00
7ba720a62a docs(aio): resources.json - replace “Angular 2” in titles/descriptions
closes #16965
2017-06-23 09:04:21 -07:00
eacc36bbd5 docs(aio): update Bash for Windows info
As of the Creator's Update, Bash on Ubuntu on Windows supports all npm commands. 
https://blogs.msdn.microsoft.com/commandline/2017/04/11/windows-10-creators-update-whats-new-in-bashwsl-windows-console/
Update setup documentation accordingly.
2017-06-23 09:04:04 -07:00
8b4acbbcbf docs(aio): minor fixes for the upgrade guide
Fixes #17093
2017-06-23 09:03:28 -07:00
b1fe63d081 fix(aio): add missing WeakMap polyfill 2017-06-23 09:03:07 -07:00
f2ee1dcdb7 fix(animations): do not validate style overlap errors in different transitions 2017-06-23 09:02:37 -07:00
21018af2bf docs(aio): replace “Angular 1” in upgrade phonecat script 2017-06-22 14:57:02 -07:00
67ffbae6f9 docs(aio): fix promises link in toh-pt4
closes #16050
2017-06-22 14:56:41 -07:00
5dd5bfde72 docs(aio): remove "_" from private property name
follow best practices from the documentation: https://angular.io/guide/styleguide#properties-and-methods
2017-06-22 14:55:42 -07:00
400486ced7 docs(aio): correct typos in Tour of Heroes HeroSearchService 2017-06-22 14:55:17 -07:00
1a947e4b75 docs(aio): Added resources to UI Components (#17635)
docs(aio): Added resources to UI Components
2017-06-22 14:52:58 -07:00
92bcfefc35 docs(aio): remove min, max & move built-in validators paragraph 2017-06-22 14:51:05 -07:00
133b5e6e36 docs(aio): added link to Russian Angular Courses 2017-06-22 14:46:24 -07:00
5c576d3b9d docs: add documentation for LTS versions 2017-06-22 14:45:36 -07:00
68b64a261a docs: add missing colon in the Constants section of NAMING.md 2017-06-22 14:40:33 -07:00
68f939ea8c docs: Fix Stack Overflow being repeatedly misspelt as a single word
(To see that it is two words, see e.g. the page title at https://stackoverflow.com/ or the
spelling at http://stackoverflow.com/tour)
2017-06-22 14:39:10 -07:00
8c129d73b8 docs: Fix some non-code being formatted as code in the router docs 2017-06-22 14:38:55 -07:00
1fc0d05565 docs: add changelog for 4.3.0-beta.0 2017-06-21 17:21:39 -07:00
20a04f9076 release: cut the 4.3.0-beta.0 release 2017-06-21 17:16:04 -07:00
02a38d3ea5 docs: add changelog for 4.2.4 2017-06-21 17:11:19 -07:00
37cdc4f759 docs(aio): ward's changes 2017-06-21 16:21:20 -07:00
b1dab181e0 docs(aio): change to overall prose 2017-06-21 16:21:20 -07:00
fd6c4e371b docs(aio): create author style guide 2017-06-21 16:21:20 -07:00
4352dd27c4 fix(compiler): avoid emitting self importing factories
Fixes: #17389
2017-06-21 16:19:43 -07:00
34cc3f2982 feat(aio): display “Searching ..." while building search index
closes #15923
2017-06-21 14:31:32 -07:00
97fd2480e7 fix(aio): fix patch script on windows
Running the patch script on Windows (with `patch` available) yields an invalid syntax warning, and does not apply patches.

```
kamik@T460p MINGW64 /d/work/angular/aio (master)
$ yarn postinstall
yarn postinstall v0.24.6
$ node tools/cli-patches/patch.js && uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map
The syntax of the command is incorrect.
Done in 1.52s.
```
2017-06-21 13:51:35 -07:00
1d93cf2e85 build(aio): ensure all doc tests are run
It is not possible to run all the docs tests directly via the  jasmine CLI.
Instead we now have a small script that will run jasmine via its library.
2017-06-21 13:51:15 -07:00
3fb98fe4ea build(aio): do not recurse node_modules when loading examples in doc-gen 2017-06-21 13:51:15 -07:00
bb804dd3e9 feat(aio): select contributor group with URL “about?group=gde”
closes #17656
also adds test for ContributorListComponent.
2017-06-21 13:50:57 -07:00
0034bb28e5 fix(aio): restore component-styles/exclude hidden children 2017-06-21 11:33:19 -07:00
ca51e020cb fix(aio): redirect "cli-quickstart" to "quickstart" 2017-06-21 11:33:19 -07:00
d16852898f docs(aio): fix numbered list in testing.md
This list renders OK in the github UI but not at https://angular.io/guide/testing#setup
2017-06-20 16:43:26 -07:00
46ddf501a9 fix(tsc-wrapped): skip collecting metadata for default functions
Fixes: #17518
2017-06-20 14:22:49 -07:00
8c89cc4fc5 fix(compiler-cli): find lazy routes in nested module import arrays
Fixes: #17531
2017-06-20 14:21:14 -07:00
00874c27f4 docs(aio): update resource listing 2017-06-20 12:58:02 -07:00
c59c390cdc fix: argument destructuring sometimes breaks strictNullChecks
Destructuring of the form:

function foo({a, b}: {a?, b?} = {})

breaks strictNullChecks, due to the TypeScript bug https://github.com/microsoft/typescript/issues/10078.
This change eliminates usage of destructuring in function argument lists in cases where it would leak
into the public API .d.ts.
2017-06-20 12:56:08 -07:00
009651e14f refactor(core): remove toString() method from DefaultKeyValueDiffer
toString() from DefaultKeyValueDiffer is only used in tests and should not
be part of the production code. toString() methods from differs add
~ 0.3KB (min+gzip) to the production bundle size.
2017-06-20 12:55:20 -07:00
f194f18dbd fix(language-service): infer any ngForOf of type any
Fixes: #17611
2017-06-20 12:04:51 -07:00
4e6be15069 fix(language-service): rollup tslib into the language service package
Fixes: #17614
2017-06-20 11:50:20 -07:00
3e685f98c6 fix(forms): roll back breaking change with min/max directives
With 4.2, we introduced the min and max validator directives. This was actually a breaking change because their selectors could include custom value accessors using the min/max properties for their own purposes.

For now, we are rolling back the change by removing the exports. At the least, we should wait to add them until a major version. In the meantime, we will have further discussion about what the best solution is going forward for all validator directives.

Closes #17491.

----

PR #17551 tried to roll this back, but did not remove the dead code. This failed internal tests that were checking that all declared directives were used.
This PR rolls back the original PR and commit the same as #17551 while also removing the dead code.
2017-06-20 09:04:50 -07:00
6c8e7dd63e revert: fix(forms): temp roll back breaking change with min/max directives
This reverts commit 232bd9395d.
2017-06-20 09:04:50 -07:00
2447bd1bac fix(aio): leave results panel open when opening search result on new page
Fixes #17580
2017-06-19 15:12:46 -07:00
1c6a252596 build(aio): upgrade jasmine to v2.6.4
This version fixes the DISCONNECTED errors (described in #17543) and removes the
need to the workaround (8af203c).
The relevant jasmine commit is jasmine/jasmine@c60d66994.
2017-06-19 15:12:25 -07:00
d3c92a307a fix(router): update the version placeholder so that it gets replaced during the build
Fixes #17403
2017-06-19 15:11:06 -07:00
8f5836cb14 docs(TRIAGE_AND_LABELS): update labels to reflect the current state 2017-06-19 14:53:55 -07:00
319ce182db docs(RELEASE_SCHEDULE): fix version numbers for August/September releases 2017-06-19 14:46:23 -07:00
aa92f3a721 docs(RELEASE_SCHEDULE): update the release schedule w/ recent regression patch releases 2017-06-19 11:33:44 -07:00
afbb6bb797 docs: remove aio from changelog 2017-06-19 11:14:35 -07:00
4f37f86433 fix(aio): switch from innerText to textContent to support older browsers
`innerText` is not supported in Firefox prior to v45. In most cases (at least
the ones we are interested in), `innerText` and `textContent` work equally well,
but `textContent` is more performant (as it doesn't require a reflow).

From [MDN][1] on the differences of `innerText` vs `textContent`:

> - [...]
> - `innerText` is aware of style and will not return the text of hidden
>   elements, whereas `textContent` will.
> - As `innerText` is aware of CSS styling, it will trigger a reflow, whereas
>   `textContent` will not.
> - [...]

[1]: https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText

Fixes #17585
2017-06-19 10:32:36 -07:00
3093c55e9e build(aio): update @angular/service-worker to 1.0.0-beta.16
This version includes a fix for potential cache corruption and invalid redirect behavior in rare cases.
2017-06-16 13:33:37 -07:00
5ac3919259 feat(core): update zone.js to 0.8.12 2017-06-16 12:13:14 -07:00
f58211d9d8 fix(aio): improve no-javascript screen 2017-06-16 12:04:45 -07:00
fe126cb737 fix(aio): remove unused icon reference and file
These icons are not there (and never were afaict).

Fixes #17561
2017-06-16 12:03:43 -07:00
adc1b129e4 fix(aio): correctly redirect /docs/ts/latest and /styleguide
Previously, we had redirect rules for Firebase for `/docs/ts/latest` and
`/styleguide`, but once the ServiceWorker was activated, it would take over
routing and rewrite these requests to `/index.html`.
This commit fixes it by excluding them from ServiceWorker routing.

Fixes #17542
2017-06-16 12:02:52 -07:00
9315ab88d7 test(compiler): fix typo "mamespace" 2017-06-16 11:17:15 -07:00
1ddbddb0db docs: add changelog for 4.2.3 2017-06-16 09:42:31 -07:00
232bd9395d fix(forms): temp roll back breaking change with min/max directives
With 4.2, we introduced the min and max validator directives. This was actually a breaking change because
their selectors could include custom value accessors using the min/max properties for their own purposes.

For now, we are rolling back the change by removing the exports.

Closes #17491.
2017-06-16 09:20:04 -07:00
956a7e95d7 docs(aio): re-add biography entry for devversion
With SHA 2c3e948e61 the biography of Paul Gschwendtner has been accidentally removed.

This re-adds the biography entry (picture still present) as requested on Slack.
2017-06-16 07:56:15 +01:00
ce00fa3627 build(aio): do not fail if check-env for the main angular project fails
Fixes #17434
2017-06-16 07:51:18 +01:00
3515860b15 build: fix link to DEVELOPER.md in check-environment.js 2017-06-16 07:51:18 +01:00
77e717e872 build(aio): remove dependency on build artifacts from parent folder 2017-06-16 07:51:18 +01:00
b46cc744b3 build: remove redundant line
The same value is set a few lines below.
2017-06-16 07:51:18 +01:00
2cc931ed2a fix(aio): add missing redirect rule for /styleguide
Fixes #17542
2017-06-16 07:49:51 +01:00
e096a85874 fix(animations): remove duplicate license header 2017-06-15 14:41:59 -07:00
2c3e948e61 docs(aio): update about page 2017-06-15 07:49:41 +01:00
53f57d74b8 feat(aio): add iphone pwa features 2017-06-15 07:47:49 +01:00
2b1de07f02 docs(aio): incorporate Ward's comments 2017-06-15 07:46:44 +01:00
fb877696bf docs(aio): add short section on built-in validators, copy edits 2017-06-15 07:46:44 +01:00
01173b9441 fix(aio): do not log messages in production
In dev mode, all messages passed to `Logger` will be logged.
In production mode, only warnings and errors will be logged.

Fixes #17453
2017-06-15 07:46:03 +01:00
0564dd25e2 refactor(aio): remove unused Logger dependencies 2017-06-15 07:46:03 +01:00
ba0f6decc3 build(aio): extra redirect rule 2017-06-15 07:45:47 +01:00
06a0cf2e31 docs(aio): http guide shows how to import toPromise operator from rxjs
Solves #17454
2017-06-15 07:45:12 +01:00
a8afa65a54 fix(aio): specify large image for PWA splash-screen 2017-06-14 09:36:54 -07:00
f73a4c229c build(aio): upgrade lighthouse to v2.1 2017-06-14 09:36:54 -07:00
d378a29565 ci(aio): fail the build if the PWA score is too low
Previously, there was an issue with testing the PWA score on staging and failing
the build was temporarily disabled. It works now, so we need to enable failing
the build is the score drops below some threshold.
2017-06-14 09:36:37 -07:00
2bdf2feea7 test(aio): add async beforeEach to prevent Chrome disconnects
Related to 3d5f520ff0 from #17405
2017-06-14 15:29:35 +01:00
607fb1fff8 fix(aio): always cover the whole footer with its background
Fixes #17465
2017-06-14 15:01:06 +01:00
38fc2a0055 fix(aio): fix trackBy demo in template-syntax article 2017-06-14 12:04:59 +01:00
0c07f8c099 test(platform-server): fix and re-enable integration tests 2017-06-13 15:50:23 -07:00
d8d21c77d5 ci: update github templates (#17466) 2017-06-13 14:49:13 -07:00
b4cd20cbbc fix(aio): make the footer links clickable on all browsers
The footer background (implemented via `footer:after`) had a higher `z-index`
than other footer elements and was obscuring the footer links on certain
browsers (Firefox, Edge, IE), which made them unclickable.
This commit lowers the index of `footer:after`, so that links are clickable on
these browsers.

Fixes #17460
2017-06-13 14:05:37 -07:00
f840afb983 ci: disable platform-server integration test as it is currently broken 2017-06-13 14:01:22 -07:00
93d1b4ed9d refactor(compiler): remove duplicate code 2017-06-13 13:37:33 -07:00
fa81c8eeb3 ci: use npm_install for bazel
using yarn_install polluted our node_modules cache because it disregards the npm_shrinkwrap.json
2017-06-13 13:35:01 -07:00
98308cd79c docs(aio): update typescript for examples/webpack to same as cli 2017-06-13 10:28:10 -07:00
052331fabc build(aio): add staging environment
You can now specify what environment you are building
by add it to the `yarn build` command. For example:

```
yarn build -- --env=stage
```

Moreover the `deploy-to-firebase.sh` script will automatically apply the
appropriate environment.
2017-06-13 13:43:36 +01:00
541c9a94bf docs(aio): i18n guide - updates for v4 2017-06-13 12:38:38 +01:00
12452d4ab4 build(aio): increase docs integration test timeouts
The API docs tests have very variable run times, depending
upon the build environment.
This change doubles their test timeout values to prevent
false-negative failures.
2017-06-13 11:56:45 +01:00
709a3f6de7 fix(aio): fix scrolling to elements near the bottom of the page
Previously, we always assumed that elements would be scrolled to the top of the
page, when calling `element.scrollIntoView()`. This is not true for elements
that cannot be scrolled to the top, e.g. when the viewport height is larger than
the height of the content after the element (common for small sections near the
end of the page).
In such cases, we would unnecessarily scroll up to account for the static
toolbar, which was unnecessary (since the element was not behind the toolbar
anyway) and caused ScrollSpy to fail to identify the scrolled-to section as
active.

This commit fixes it by ensuring that we do not scroll more than necessary in
order to align the top of the element with the bottom of the toolbar.

Fixes #17452
2017-06-13 11:54:05 +01:00
7caa0a8aa4 fix(aio): show search results when search box gets focus
Due to a previous commit, the search was only triggered
if the query changed, and not when the search box regained
focus.
2017-06-13 11:26:35 +01:00
0f56296c24 fix(aio): use locally hosted lunr library
The library is downloaded from npm but then
copied into the assets folder (and ignored by git)
as part of the postinstall step.
2017-06-13 11:26:35 +01:00
0a846a2fce fix(aio): make search results better
* update to latest version of lunr search
* add trailing wildcard to search terms to increase matches
* fix unwanted error when escape was pressed

Closes #17417
2017-06-13 11:26:35 +01:00
bffccf4622 fix(aio): fix buttons in "Home" and "Features"
Using `<a>` inside a `<button>` is not syntactically valid HTML and breaks on
some browsers (e.g. Firefox). Furthermore, clicking the button doesn't do
anything unless you click on the link (e.g. clicking on the padding around the
link does nothing), which is inconvenient and confusing.

Fixes #17448
2017-06-13 11:24:36 +01:00
fc774a1871 docs: clarify when non-null-assertion-operator is needed in template-syntax 2017-06-13 08:02:30 +01:00
d647db222c fix(aio): ensure that API filter page can display 3 columns in wide view
Fixes #17251
2017-06-13 07:41:10 +01:00
0c7eb93889 fix(aio): tidy up layout of api filter page
* Remove the "info-banner" styling from the filters.
* Fix alignment of the search box on a narrow screen (closes #17395)
* Remove unnecessary whitespace before section headers
2017-06-13 07:41:10 +01:00
0658e1da7f docs(aio): rename Upgrade docs to cheatsheet 2017-06-13 07:15:05 +01:00
d2d8e5d40f fix(aio): remove outline from search input on focus
Closes #17396
2017-06-13 06:56:21 +01:00
5e794492c1 build(aio): make deploy-to-firebase.sh executable 2017-06-12 16:14:17 -07:00
7a2d2efff8 docs: add changelog for 4.2.2 2017-06-12 15:50:10 -07:00
e95062d1df release: cut the 4.2.2 release 2017-06-12 15:50:10 -07:00
0268818fbb build(aio): update Google Analytics property id for the prod environment
This is the same property ID as the one used by the current angular.io.
2017-06-12 15:49:41 -07:00
234268eec2 ci(aio): deploy commits on the stable branch to production
The current stable branch is determined based on the current version mapped to
the npm `latest` tag (by replacing the patch version number with 'x' - e.g.
`1.2.3` --> `1.2.x`).
PRs against the stable branch will be deployed to the preview server (as long as
the rest of the requirements are met). Commits on the stable branch itself will
be deployed to production.

Fixes #16908
2017-06-12 15:49:22 -07:00
ed73d4f3ac refactor(compiler): don’t always compile .ngfactory.ts files by default
This puts the behavior introduced in 573b8611bc behind the new flag
`alwaysCompileGeneratedCode` to not break users that might have relied
on this behavior.
2017-06-12 15:27:02 -07:00
6ca46929fa fix(animations): properly collect :enter nodes in a partially updated collection
This PR fixes an issue where `query(':enter')` will only collect elements up until it an element that is found that isn't apart of the `:enter` query.

Closes #17440
2017-06-12 15:26:46 -07:00
185075d870 fix(animations): compute removal node height correctly 2017-06-12 12:18:26 -07:00
451257a2fd fix(animations): do not treat a 0 animation state as void 2017-06-12 12:18:15 -07:00
3d5f520ff0 test(aio): fix Chrome getting disconnected during unit tests
Chrome (v58+) often gets disconnected during unit tests (causing them to fail).
This has been happening locally (on Windows) and on Travis. The exact reasons
are not known, but it seems that some of the `AppComponent` tests are to blame.

Based on the discussion in https://github.com/jasmine/jasmine/issues/1327 (and
plenty of trial-and-error), using Jasmine's `done()` callback before each of
test (even calling it synchronously) fixes the issue.
2017-06-12 13:59:44 +01:00
a1065bfaf0 build(aio): upgrade Jasmine-related dependencies
According to https://github.com/jasmine/jasmine/issues/1327#issuecomment-306924549,
this might help with help with frequent DISCONNECTED errors (especially on Travis).
2017-06-12 13:59:44 +01:00
d6f345cc3c build(aio): upgrade Karma-related dependencies 2017-06-12 13:59:44 +01:00
d961911a98 docs(aio): fix broken example in upgrade guide
Fixes #17109
2017-06-11 18:37:27 +01:00
e543272eb3 fix(aio): remove gap between sidenav menus
Fixes #17394
2017-06-11 15:05:04 +01:00
c8c0ceeee9 build(bazel): fix the typescript path in the BUILD file (#17316) 2017-06-09 18:32:36 -07:00
afbc2b0082 build(aio): upgrade purify to deal with messed up comment in animations code 2017-06-09 18:31:50 -07:00
69ec1e2e31 build(aio): upgrade to @angular/*@4.2.1 2017-06-09 18:31:50 -07:00
ff15509bc7 build(aio): turn on webpack 3.0 scope-hoisting 2017-06-09 18:31:50 -07:00
7e22a2fc0b build(aio): remove duplicate webpack version 2017-06-09 18:31:50 -07:00
0a48c92d2c build(aio): upgrade to purify 0.0.24 that supports webpack 3.0 2017-06-09 18:31:50 -07:00
991ca92a9d build(aio): turn off vendor chunking
This was done to prevent webpack from generating cross chunk registry calls which prevent DCE.
2017-06-09 18:31:50 -07:00
c550f885a4 build(aio): upgrade to an unreleased version of cli with webpack 3.0-rc.0 2017-06-09 18:31:50 -07:00
851038c3a8 build(aio): upgrade @angular/service-worker to beta.15 2017-06-09 18:31:50 -07:00
df7774c018 docs(aio): restore animation original value (#17385) 2017-06-09 18:27:11 -07:00
15090a8ad4 docs: add changelog for 4.2.1 2017-06-09 16:11:29 -07:00
8575e3f71c release: cut the 4.2.1 release 2017-06-09 16:04:09 -07:00
90b0713e32 refactor(compiler): don’t write summaries for jit by default
The default is false externally but true internally at Google.
2017-06-09 15:58:53 -07:00
d56b7ed96d docs(aio): reorganise the sidenav menu (#16934)
Reorganizes the items in the sidenav menu and consolidates the quickstart
and cli-quickstart guides into one.
2017-06-09 14:48:53 -07:00
db5e5067a0 build(aio): update typescript to 2.3.2 (#17382) 2017-06-09 14:36:36 -07:00
0020dad595 docs(aio): update examples to 4.2 (#17367) 2017-06-09 14:35:35 -07:00
c2d31fb01e fix(http): move destructuring inside {Request,Response}Options ctor
Previously the RequestOptions/ResponseOptions classes had constructors
with a destructured argument hash (represented by the
{Request,Response}OptionsArgs type). This type consists entirely of
optional members.

This produces a .d.ts file which includes the constructor declaration:

constructor({param, otherParam}?: OptionsArgs);

However, this declaration doesn't type-check properly. TypeScript
determines the actual type of the hash parameter to be OptionsArgs | undefined,
which it then concludes does not have a `param` or `otherParam` member.

This is a bug in TypeScript ( https://github.com/microsoft/typescript/issues/10078 ).
As a workaround, destructuring is moved inside the method, where it does not produce
broken artifacts in the .d.ts.

Fixes #16663.
2017-06-09 14:34:39 -07:00
65d49d5c94 fix(aio): temporarily remove link to source from the API pages (#17371)
We need to come up with a better design (possibly involving an icon button) to
link to the source code (for viewing and/or editing).

Fixes #17254
2017-06-09 14:14:39 -07:00
3a99af2696 docs(packaging): update homepage in @angular/tsc-wrapped (#17352)
Motivation: `yarn outdated`, for exmaple, shows the homepage URL on the command line. If copy-pasting or clicking on the URL, it's nice to see the repo's page instead of a 404.
2017-06-09 13:35:23 -07:00
503fd1fbaf docs(aio): fix image width on the homepage 2017-06-09 13:32:52 -07:00
4c74dba2f3 docs(aio): update events page 2017-06-09 13:32:52 -07:00
33df13ad65 build(aio): update to 4.2 (#17369) 2017-06-09 13:31:12 -07:00
ce18c68eca fix(aio): temporarily remove the focus style of top-bar nav items
Temporarily addresses #17216 until we upgrade to an `@angular/material` version
that includes angular/material2@3bc82f6dc.
2017-06-09 20:37:12 +01:00
2d5623911a fix(aio): sidebar folder state after select item
Closes #17245 and #17253

When the user selects a doc item in the side nav:
1) expand folder(s) leading to the selected doc item
2) on a wide display, keep other already expanded folders open
3) on narrow (mobile) display, collapse other expanded folders

Used to do (3) when wide. Issue #17245 asks for (2).

That logic was bypassed for selected node when we allowed headers to have content
because that unintentionally expanded the header’s folder when selected.
Because the selected node is no longer a header with content, removing this exclusion
also means that folders are expanded/collapsed with above logic even for API pages.
2017-06-09 16:53:54 +01:00
a0b30e5dfb docs(aio): remove example specific .gitignore files
All the files that should be ignored are defined in the
`aio/content/examples/.gitigore` folder.
2017-06-09 14:33:21 +01:00
76a920651c docs(aio): remove cli-quickstart styles.css from git 2017-06-09 14:33:21 +01:00
ef6609a723 docs(aio): remove example specific styles from the shared boilerplate styles.css 2017-06-09 14:33:21 +01:00
c0dcb342f3 docs(aio): create styles.css snippet file for toh-pt5 example 2017-06-09 14:33:21 +01:00
8a9a5ecdd7 docs(aio): move example specific styles into router example app.css file 2017-06-09 14:33:21 +01:00
12c5ead39c docs(aio): do not ignore i18n example js file 2017-06-09 14:33:21 +01:00
3db6b6ca7a build(aio): ignore example files that are gitignored
Rather than hard coding excludes into the dgeni config,
use the fact that we are already ignoring the boilerplate
and generated files via the .gitignore file.
2017-06-09 14:33:21 +01:00
e894f5c399 fix(aio): expand the main content width when there is no ToC
Previously, the main content would always leave a 18% margin on the right to be
occupied by the ToC (even if there was no ToC).
This commit lets the main content expand to the right to occupy all the
available space when there is no ToC.

Fixes #17205
Fixes #17270
2017-06-09 13:33:51 +01:00
8524187869 fix(aio): restrain scrolling inside ToC (when cursor over ToC)
Previously, when scrolling the ToC and reaching the top/bottom, further
mousewheel events would result in scrolling the window (and thus the main
content). This is standard browser behavior. In the case of the ToC though, the
`ScrollSpy` would detect scrolling in the main content and scroll the active ToC
to entry into view, thus resetting the scroll position of the ToC.

Reproduction:
1. Open  `~/guide/template-syntax`.
2. Start scrolling through the long ToC.
3. Try to go to the bottom of the ToC.
4. Once you reach the bottom, the main content starts scrolling down.
5. The first section ("HTML in templates") becomes "active", so the ToC is
   scrolled back up to make its corresponding entry visible.
6. Go back to step 2.

This commit improves the UX, by not allowing the main content to scroll when the
cursor is ovr the ToC and the user has scrolled all the way to the top/bottom of
it.
2017-06-09 10:38:06 +01:00
f7422a9607 fix(aio): animate hamburger in/out
Fixes #17215
2017-06-09 08:17:48 +01:00
cf0a9e0730 docs: add changelog for 4.2.0 2017-06-08 15:38:20 -07:00
8e5beb024f release: cut the 4.2.0 release 2017-06-08 15:38:20 -07:00
576d6d8f86 feat(aio): update the home page and docs landing page content (#17325)
Closes #17268
Closes #17230
2017-06-08 15:26:15 -07:00
41a765d715 fix(aio): re-color code pretty print
closes #17246
2017-06-08 14:00:16 -07:00
f370fd36e0 docs: "TypeScript to JavaScript" code tab renamed “ES6 + Decorators”
closes #17260
2017-06-08 14:00:06 -07:00
a222c3e609 fix(forms): fix min and max validator behavior on non-numbers 2017-06-08 13:59:17 -07:00
5a71df0cb3 build(aio): rename features header 2017-06-08 12:57:42 -07:00
6a46cabb10 docs: security guide images
closes #17263
2017-06-08 12:57:13 -07:00
74673545c0 docs(aio): document the non-null assertion operator 2017-06-08 12:55:19 -07:00
75a311f250 fix(aio): style and fix hover color for features cta button (#17342) 2017-06-08 12:09:33 -07:00
391ed6334d fix(aio): copy code button overlap fix on mobile (#17302) 2017-06-08 12:08:03 -07:00
0940e6d6ed fix(aio): contributor img offset and hover fix (#17338) 2017-06-08 12:00:43 -07:00
4d2ee51bb0 fix(aio): remove glossary l-sub-section class (#17305) 2017-06-08 11:58:49 -07:00
680128bc09 fix(aio): alert, subsection and callout width fix (#17303) 2017-06-08 11:57:09 -07:00
5364b51979 fix(aio): scroll to top immediately when doc changes (#17310) 2017-06-08 11:56:36 -07:00
67ef0f0c8f fix(aio): api list column overflow fix (#17300) 2017-06-08 11:54:57 -07:00
e9886d701d fix(animations): evaluate substitutions on option param values 2017-06-08 10:12:52 -07:00
022835bab2 ci: update Chromium to 464641 (Chrome v59) 2017-06-08 09:36:35 -07:00
8be2e4c325 fix(aio): make the ToC left hand-side bar shorter (#17280)
Make the ToC left hand-side bar shorter, so that it starts at the initial bullet
and ends at the final bullet.

Fixes #17221
2017-06-08 00:56:00 -07:00
33ba3e31ed fix(aio): smoother transition from page with SideNav to homepage (#17312)
When navigating from a page with open SideNav to a page without closed SideNav,
the main content area animates from a non-zero left margin to zero left margin.
Additionally, the top-bar on the homepage is transparent, which allows the white
background behind the main content to be seen while the left margin is animated
to zero, making it appear as if something (e.g. the SideNav covers the top-bar).

This commit works around this issue, by not making the top-bar transparent
immediately when navigating to the homepage, but animating it from its blue
color to transparent with a delay.

Fixes #17248
2017-06-08 00:49:54 -07:00
cba2b3c72d docs(aio): add “nougat” to compat chart
closes #17262
2017-06-07 12:40:50 -07:00
a4a2901294 fix(aio): remove ... separator from search results
An ellipsis was used to separate the most relevant search
results from the alphabetic list. The separator was confusing
because it was not clear what it represented.

This has been removed and the most relevant results are now
indicated by styling with a more bold font and a bit of whitespace
between them and the rest of the results.

To keep things consistent, if there are fewer than 5 results all the
results are now displayed as priorityPages.

Closes #17233
2017-06-07 12:40:39 -07:00
bb46f54ad7 refactor(aio): use the SelectMenuComponent for all select menus
The API filters and the docs version switcher now use
the SelectMenuComponent.

Fixes #16367 and #17055
2017-06-07 11:27:10 -07:00
c9b930dd82 feat(aio): add aio-select component
Provide the functionality for select menus in a single reusable component.
2017-06-07 11:27:10 -07:00
a39f7d63bb docs(aio): use h1 rather than divs and hide heading anchors
In the marketing pages we do not want to show heading anchors on hover.
Previously, this was achieved by using div rather than heading elements.
Now we can use semantically accurate headings while hiding the anchor.

Closes #17244
Closes #17264
2017-06-07 11:26:27 -07:00
e317f7d51c feat(aio): do not display anchor if heading has no-anchor class 2017-06-07 11:26:27 -07:00
3ddd28d37d fix(aio): update contrast on mediumgray color 2017-06-07 11:26:13 -07:00
fe6b39d585 perf(animations): do not create a closure each time a node is removed 2017-06-07 11:16:17 -07:00
d837bfc2d7 fix(aio): fix scrolling to an element
Previously, the top-bar's height wasn't taken into account when scrolling an
element into view. As a result, the element would be hidden behind the top-bar.
Taking the top-bar height into account was not necessary before #17155, because
the top-bar was not fixed (i.e. it scrolled away).

This commit fixes the scrolling behavior by accounting for the top-bar's height
when scrolling an element into view.

(This partially reverts #17102.)

Fixes #17219
Fixes #17226
2017-06-07 14:37:46 +01:00
4759975be6 fix(aio): adjust SideNav top to avoid extra whitespace
Partially addresses #17096.
2017-06-07 13:37:30 +01:00
078a4b00a7 fix(aio): fix button alignment and jump
- Fix alignment on get started button on home page
- Remove jump by removing arrow from learn more button on home page
2017-06-07 09:11:20 +01:00
b00b80a45b feat(compiler-cli): introduce synchronous codegen API 2017-06-06 14:12:02 -07:00
269bbe0e7d fix(upgrade): call setInterval outside the Angular zone
This wraps the $interval service when using upgrade to run the
$interval() call outside the Angular zone. However, the callback is
invoked within the Angular zone, so changes still propagate to
downgraded components.
2017-06-06 14:11:21 -07:00
bb2fc6b8da refactor(platform-browser): Remove setGlobalVar from DOM adapter 2017-06-06 13:26:23 -07:00
d4e196035c build(aio): update features and presskit marketing pages 2017-06-06 13:08:48 -07:00
b8979c8701 fix(aio): fix and clean up topbar styling
Restore the changes introduced in #17075, which wre accidentally overwritten
while rebasing #17155. Also, simplify the topbar positioning rules.
2017-06-06 13:08:39 -07:00
bfdd3398f6 fix(aio): make ScrollSpy respond quicker to scroll events
Fixes ##17220
2017-06-06 13:08:30 -07:00
3a121a621f build(aio): migrate contribute page to html 2017-06-06 16:38:29 +01:00
c06f4fc702 docs(aio): remove tutorial from docs landing page 2017-06-06 16:38:09 +01:00
95f1ea2f12 fix(aio): topnav toolbar position styles
- Fixed topnav on all mobile
- Fixed topnav on all docs pages
- Absolute topnav on all marketing pages
- Cleanup and code consolidation for all top-menu styles
- Add styling to topnav links on focus
2017-06-06 14:51:19 +01:00
784347f61f fix(aio): ensure all views can indicate the active node (#17194)
When more than one node matches a url, the last
node defined in the navigation.json file won. This
meant that, for instance, items in both the
TopBarNarrow and the Footer views would not
indicate that they were active.

Now, each url is associated with a map of current
nodes keyed off their view.

Closes #17022
2017-06-05 23:36:22 -07:00
42176a7ac4 build(aio): upgrade ngo and purify (#17210) 2017-06-05 23:03:26 -07:00
76b4b80a23 fix(aio): fix search box overlap for small devices (#17075)
- Adjust search box height
- Adjust search box standard width and width for smaller devices
- Fix search jump outside of specified max width
2017-06-05 22:57:17 -07:00
7c78282ce8 docs: fix typo in 'Routes' docs 2017-06-05 11:18:43 -07:00
47c2a2e411 ci: switch CircleCI to 2.0
This lets us use a docker container to provide things like the Bazel build tool
2017-06-05 11:18:20 -07:00
5faf520067 build: Introduce Bazel build rules
So far this just compiles the core and common packages.
2017-06-05 11:18:20 -07:00
02d74cafba ci: update pullapprove to reflect animation ownership 2017-06-05 11:17:24 -07:00
734f30d14c build(aio): ignore .ngsummary.ts files in examples 2017-06-05 11:12:59 -07:00
f2d810febc fix(aio): fix ToC styling
- Make the left bar gray (instead of blue).
- Show gray dot when hovering over an element.
- Hide left bar and dots in embedded mode.
2017-06-04 15:12:29 +01:00
819514aeba fix(animations): ensure web-animations understands a numeric CSS perspective value
Closes #14007
2017-06-02 17:05:15 -07:00
b55adee982 perf(animations): only apply :leave flags if animations are set to run 2017-06-02 17:05:06 -07:00
4c32cb952f fix(router): opening links in new window
Shift-clicks on router-links should not prevent browser default action.

A follow on to:
1ac9dda93d
2017-06-02 17:32:12 -04:00
35f714e438 fix(aio): place progress bar at the top
Previously, the progress bar would be placed right under the static top bar. Now
that the top bar i not tatic any more, it makes more sense to place the progress
bar at the top of the page.

Fixes #17103
2017-06-02 17:31:53 -04:00
d5ce086089 build(aio): fix up API doc-gen templates
* Remove whitespace before type specifiers
* Generate `new` and `call` member info for interfaces
* Ensure that there is no double space after class names
2017-06-02 13:18:09 -04:00
82ec02daf8 docs: add changelog for 4.2.0-rc.2 2017-06-01 14:27:41 -07:00
8e7a3f031b release: cut the 4.2.0-rc.2 release 2017-06-01 14:25:19 -07:00
7822187b17 fix(aio): fix scrolling to top (#17102)
Previously, the `#top-of-page` element (used when scrolling to top) was placed
inside the content section (which at the time had zero top margin and padding).
Furthermore, there was a top offset applied when scrolling that took the static
top bar's height into account. Since now the top bar is not static any more and
the content section has a non-zero top padding, scrolling to top does not work
as expected.

This commit fixes this by:
- Moving the `#top-of-page` element to the top of the `aio-shell`.
- Stop accounting for the top bar's top.

Fixes #17006
2017-06-01 14:03:10 -07:00
068133ec85 fix(animations): do not retain deleted nodes during an non-removal animation (#17153)
Closes #17086
2017-06-01 14:02:41 -07:00
598fdad089 docs: update the release schedule (#17162) 2017-06-01 13:23:34 -07:00
ad6a57e0a3 perf(animations): do not place enterId values on elements for querying purposes (#17150) 2017-06-01 11:39:53 -07:00
230255f887 feat(compiler-cli): produce template diagnostics error messages (#17125)
Refactoring the compiler to use transformers moves the code generation
after type-checking which suppresses the errors TypeScript would
generate in the user code.

`TypeChecker` currently produces the same factory code that was
generated prior the switch to transfomers, getting back the same
diagnostics as before. The refactoring will allow the code to
diverge from the factory code and allow better diagnostic error
messages than was previously possible by type-checking the factories.
2017-06-01 10:13:50 -07:00
1338995ee9 fix(aio): fix home learn more button alignment (#17154) 2017-06-01 09:37:42 -07:00
47e4fca7fd fix(aio): swap feature headers for divs to remove anchors and fix feature image resizing (#17156) 2017-06-01 09:37:27 -07:00
665e7071fa fix(animations): always change to desired animation state even if no transition fires (#17025)
Fixes #16947
2017-05-31 15:36:41 -07:00
c20f60b144 fix(router): make remove trailing slash consistent with URL params
closes #16069
2017-05-31 14:35:47 -07:00
1408357198 docs: add details about the WeakMap polyfill for 4.x in the changelog (#17146) 2017-05-31 14:34:51 -07:00
1f9a3dd1e6 fix(aio): fix typo in web worker check (#17133) 2017-05-31 05:43:55 -05:00
11505fa0d8 fix(aio): disable search on browsers that don't support web workers 2017-05-31 05:22:39 -05:00
9326e062d8 fix(aio): googlebot polyfill fix 2017-05-31 05:22:39 -05:00
cb2cb7c3bd build(aio): display "call" and "new" members of interfaces in API docs 2017-05-31 08:45:50 +03:00
92d99ba279 build(aio): upgrade to dgeni-packages 0.19.1
This provides the fix where static class members were not being displayed
in API docs, closes #17024
2017-05-31 08:45:50 +03:00
dfbbbb5e3e build(aio): the the captured h1 as the title for the search index
If there is no title already provided, use the one captured from the renderedContent.
2017-05-31 08:45:31 +03:00
89f317915d build(aio): use the captured h1 as the title if necessary
If the doc does not already have a `title` tag
then use the one captured from the renderedContent in
the final JSON output, instead.
2017-05-31 08:45:31 +03:00
4d5fa5c855 build(aio): capture the h1 title and attach it to the document
The HTML post-processing now collects any h1
that is found in the renderedContent and attaches
it to the doc via the `doc.vFile.title` property.
2017-05-31 08:45:31 +03:00
2f35392cd8 fix(aio): make the search-pane larger (and improve the search-box) (#17105)
* fix(aio): make the search-pane larger

Fixes #17094

* feat(aio): give the search-box a type of "search"

This enables browsers to style it better (e.g. add an `x` button for clearing
the field, which allows users to quickly reset the search query and hide
results).
2017-05-30 15:15:54 -07:00
b056adc032 fix(aio): fix SideNav items alignment (#17106)
Fixes #17097
2017-05-30 15:15:43 -07:00
0d894a18fc fix(aio): fix ToC styling (#17110)
There seems to have been a bad rebase of #16228 on top of #16959, which affected
ToC styles from both PRs. This commit restores the horizontal line under `.h1`
elements and the vertical blue bar on the left-hand side of the ToC (with the
circle running along the bar to indicate the active section).

Fixes #17098
2017-05-30 15:15:31 -07:00
e5138081ec build: update router package.json placeholder (#17060) 2017-05-30 13:19:54 -07:00
8ffa483bb6 fix(compiler): enableLegacyTemplate should not be ignored (#17051)
Fixes #15555
2017-05-30 13:19:42 -07:00
ea8a43def0 feat(tsc-wrapped): always convert shorthand imports (#16898)
Now converts shorthand imports for every TypeScript target. Tsickle is able to expand index shorthand imports for every TypeScript target and module possibility.

Expanding shorthand imports for CommonJS modules is also helpful when testing in the browser. Module loaders like SystemJS are not able to understand directory imports (or index shorthand imports)
2017-05-30 10:46:54 -07:00
535d9da6b6 fix(common): always use 'other' case for locales with no plural rules (#16990)
Locales with no rules were using the wrong plural rule instead of the default.
2017-05-30 10:46:36 -07:00
18bf77204e feat(compiler): emit typescript nodes from an output ast (#16823) 2017-05-30 10:43:13 -07:00
160221c815 test(platform-server): fix target of TS outDir for integration test (#17118) 2017-05-30 10:40:03 -07:00
6220b49463 build(aio): upgrade to ngo with top-level NewExpression and CallExpression prefixing (#17063) 2017-05-27 11:46:52 -07:00
aa683a765d fix(aio): add polyfill for GoogleBot (#17062)
I chose to use unknown=polyfill option as per recommendation found at https://github.com/Financial-Times/polyfill-service/issues/852#issuecomment-272097936
2017-05-27 11:44:53 -07:00
b8b91d3418 feat(aio): marketing design refresh
- Add styling for active TOC item
- Add sidenav styles
- Change header tags to divs from index marketing page to remove anchors
- Fix use of card mixin and create separate card-docs class
- Add marketing styling
- Topnav styling when on home landing page
- Remove hamburger menu on home page
- Add fully rounded border to top nav toolbar search input
- Add mobile styles
- Add title banner to other marketing pages
2017-05-27 07:24:03 +01:00
0018acfede fix(aio): remove es2015 syntax from the onerror webmaster tools debugger (#17056) 2017-05-26 16:48:55 -06:00
51d7a65a2b docs: add changelog for 4.2.0-rc.1 2017-05-26 14:38:41 -07:00
a1724f7816 release: cut the 4.2.0-rc.1 release 2017-05-26 14:32:20 -07:00
d108479d84 fix(animations): use a lightweight renderer for non-animation components (#17003)
This reverts commit c0981b8e13.
2017-05-26 14:39:42 -06:00
cd5bc64658 docs(aio): update headers for toc (#16969) 2017-05-26 13:28:06 -06:00
5f723cb92c feat(aio): temporarily add debugging code for google crawler (#17046) 2017-05-26 13:25:33 -06:00
7ffb75f476 refactor(compiler): change bundle to flatModuleIndex in the code (#17028) 2017-05-26 09:13:28 -07:00
c0981b8e13 Revert "fix(animations): use a lightweight renderer for non-animation components (#17003)"
This reverts commit 3ab86bd661.
2017-05-26 08:55:23 -07:00
80e506563c Revert "refactor(compiler): change bundle to flatModuleIndex in the code (#17028)"
This reverts commit 5aa53d70aa.
2017-05-26 08:54:50 -07:00
694951096c feat(aio): provide noop service worker file
Use this file if we need to turn off the service worker in deployed sites
in an emergency where the worker has a bug that is blocking the application
from working.

Closes #16897
2017-05-26 14:24:41 +01:00
9e7e178585 build(aio): add google webmaster tools verification file
this file needs to remain in the repo for us to remain verified as owners of aio-staging.firebaseapp.com.
2017-05-26 01:25:58 -07:00
db3113ba16 build(aio): externalize @angular/cli patches into a js file
This improves code readability and maintainability.
2017-05-26 01:19:59 -07:00
3117f565c1 ci(aio): switch over to shared account for deployment to staging and production 2017-05-26 01:19:59 -07:00
8443d199b2 build(aio): use purify to include tslib
cli's scripts setup evals strings which requires too much escaping that bloats the code
2017-05-26 01:19:59 -07:00
8e2f72c644 build(aio): turn on pure_getters option in uglify 2017-05-26 01:19:59 -07:00
0c1d1e2396 build(aio): upgrade purify to 0.0.11 with better coverage 2017-05-26 01:19:59 -07:00
740450287d build(aio): upgrade ngo to 0.0.3 with static prop unhoisting 2017-05-26 01:19:59 -07:00
0c691af1d2 build(aio): start using tsc importHelpers option for the app
this means we'll be temporarily duplicating the helpers (onces included via scripts
and secondly imported via es imports) - once rxjs, core and material migrate over
to tslib, we can drop the scripts/global dupe.
2017-05-26 06:02:12 +01:00
5aa53d70aa refactor(compiler): change bundle to flatModuleIndex in the code (#17028) 2017-05-25 19:02:35 -06:00
3ab86bd661 fix(animations): use a lightweight renderer for non-animation components (#17003) 2017-05-25 18:54:35 -06:00
2538094e13 fix(aio): group topbar items into a single sidenav node when narrow screen (#17001)
The `TopBarNarrow` now only shows a single top level container, "About Angular",
and the original `TopBar` items will be children of this container.

The `TopBarNarrow` styling is changed to match the rest of the `SideNav`.
2017-05-25 17:44:27 -06:00
b37a0484d4 docs(aio): restore missing files (#16995) 2017-05-25 17:44:03 -06:00
4c5e28e53a build(aio): less verbose boilerplate generator (#16970) 2017-05-25 17:43:46 -06:00
e10d763446 fix(aio): correctly handle re-navigation to the empty path (#16997) 2017-05-25 17:43:34 -06:00
573b8611bc fix(compiler): compile .ngfactory.ts files even if nobody references them. (#16899)
This is especially important for library authors, as they will
not reference the .ngfactory.ts files.

Fixes #16741
2017-05-25 11:00:26 -06:00
966eb2fbd0 aio: add h1 title to floating table of contents (#16959)
* refactor(aio): use explicit CSS class for TOC container

This makes the styling less fragile to changes in the HTML

* fix(aio): schedule TocComponent.activeIndex updates via AsapScheduler

We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes,
which, in turn, are caused by the rendering that happened due to a ChangeDetection.

Without asap, we would be updating the model while still in a ChangeDetection handler,
which is disallowed by Angular.

* refactor(aio): do not instantiate floating ToC if not displayed

* feat(aio): display the h1 at the top of the floating TOC

Closes #16900

* refactor(aio): combine the TOC booleans flags into a "type" state

* refactor(aio): remove unnecessary `hostElement` property

* fix(aio): ensure that transition works on TOC

* fix(aio): use strict equality in ToC template
2017-05-24 14:09:55 -06:00
b0c5018c70 build(aio): better error message for invalid links (#16993) 2017-05-24 11:59:06 -06:00
19a509a92c fix(aio): use a special version of the TopBar if the screen is narrow
If there is a `TopBarNarrow` nav view then use this when the screen is narrow.
Otherwise just use the normal `TopBar`.

This commit also creates such a narrow topbar view where the "Docs" item is
in a different position

Closes #16940
2017-05-24 09:01:57 +01:00
9e17a147ec fix(aio): align top-level nav-items consistently
Fixes #16939
2017-05-24 08:21:41 +01:00
eba59aaf87 refactor(compiler): add ability to produce stub .ngfactory / .ngsummary files (#16963)
These files are needed so that:
- user code can compile even without real codegen
- as tsc transformers cannot create but only change existing files
  in the transformation pipeline.
2017-05-23 14:40:50 -06:00
fa809ec8cf build: import tslib rather than output TS helpers (#16901) 2017-05-23 14:01:39 -06:00
1651a8f189 fix(tsc-wrapped): ignore |null and |undefined when collecting types (#16222) 2017-05-23 11:53:38 -06:00
27761b4500 fix(compiler): do not report type errors for arguments with @Inject (#16222)
Fixes #15424
2017-05-23 11:53:38 -06:00
a80ac0a8d3 fix(core): make decorators closure safe (#16905)
This is required as e.g. `token` from `@Inject` is
accessed in string form via makeParamDecorator
but as a property in the `ReflectiveInjector`.

Closes #16889 as this is a more general fix.
2017-05-23 11:52:40 -06:00
5af143e8e4 refactor(compiler): allow sync AOT compilation (#16832).
AOT compilation can be executed synchronously now,
if the `ReosurceLoader` returns a string directly
(and no `Promise`).
2017-05-23 10:41:23 -06:00
255d7226d1 ci: add vicb as owner to compiler-cli (#16832) 2017-05-23 10:41:23 -06:00
50abca4583 refactor(compiler): don’t rely on global reflector (#16832)
Using the global reflector made it impossible
to compile multiple programs at the same time.
2017-05-23 10:41:23 -06:00
de8d7c65f2 refactor(compiler): emit OutputAst and not sources (#16832)
This is in preparation for creating typescript nodes
directly from `OutputAst` nodes.
2017-05-23 10:41:23 -06:00
6123b9c0c6 refactor(compiler): make OutputAst contain the moduleName, not the filePath (#16832).
The goal of this change is to simplify the emitters,
as we will soon create a new one to emit TypeScript nodes directly.
2017-05-23 10:41:23 -06:00
3b28c75d1f test(aio): avoid warnings from Material during tests 2017-05-23 08:00:07 +01:00
368169dc15 feat(aio): add progress bar
closes #16110
2017-05-23 08:00:07 +01:00
f5b2ce0206 docs: update the release schedule (#16943) 2017-05-22 18:38:31 -06:00
78e3be12a4 docs: update the release schedule (#16942) 2017-05-22 18:25:49 -06:00
e7d9fd8056 fix(animations): repair flicker issues with WA polyfill (#16937)
Fixes #16919
Fixes #16918
2017-05-22 17:57:16 -06:00
08dfe91b95 feat(compiler): add location note to extracted xliff2 files (#16791)
Add source location as a note tag as `<note category="location">path/to/file.ts:start_line[,end_line]</note>`.
`[,end_line]` part is optional and specified only if the end line is different from the start line.

Fixes  #16531
2017-05-22 12:20:45 -06:00
85d4c4b82e feat(core): update zone.js to 0.8.10 and expose the flush method (#16860)
`flush()` can now be used from within fakeAsync tests to simulate moving
time forward until all macrotask events have been cleared from the
event queue.
2017-05-22 12:19:21 -06:00
6e41add867 feat(tsc-wrapped): support template literals in metadata collection (#16880)
Fixes: #16876
2017-05-22 12:19:09 -06:00
11c10b2ab8 fix(tsc-wrapped): resolve short-hand literal values to locals (#16873)
Fixes: #16872
2017-05-22 11:18:44 -06:00
98849de127 build: fix public-api:update gulp task (#16859)
PR #16834 makes the task faster but it doesn't work any more!
Reverting to the slower but correct task for now.
2017-05-22 11:07:21 -06:00
afd703d08c build: remove router-specific versioning process (#16810) 2017-05-22 11:06:46 -06:00
ce18fdb399 fix(aio): use the current version in the version picker
Previously we hardcoded the current version into the navigation items.
Now only previous versions are included there. The current version is
computed from the currentVersion info.

Closes #16909
2017-05-22 13:48:07 +01:00
670771da33 build(aio): make the postinstall script more Windows-friendly 2017-05-21 15:37:22 +01:00
c8b08f3a59 feat(aio): implement ScrollSpy service (to highlight the active section in ToC) 2017-05-20 12:08:38 +01:00
3d382dc750 docs: add changelog for 4.2.0-rc.0 2017-05-19 16:03:18 -07:00
9081f84543 release: cut the 4.2.0-rc.0 release 2017-05-19 15:54:52 -07:00
f1a9e3c1bb feat(animations): introduce routeable animation support 2017-05-19 13:45:22 -07:00
b10029c18b docs(animations): add documentation for new animation features 2017-05-19 12:52:04 -07:00
5d4f5434fd refactor(router): don't run the change detection every time an outlet is activated
fix(router): inside on push // SQUASH after review
2017-05-19 11:55:15 -07:00
81ca51a8f0 refactor(router): cleanup, simplification 2017-05-19 11:55:15 -07:00
6cb93c1fac fix(animations): only require one flushMicrotasks call when testing animations 2017-05-19 10:45:20 -07:00
eed67ddafb feat(aio): update aio-shell class when sidenav opens or closes 2017-05-19 14:10:00 +01:00
be9e8b99ff fix(aio): support IE via conditionally loaded polyfills
Closes #16519
2017-05-19 11:26:29 +01:00
8a0e5659c0 docs(aio): tidy up docs that use float clearing
Closes #16681
2017-05-19 09:47:53 +01:00
2842b0cc3d fix(aio): ensure that subsections clear floats correctly 2017-05-19 09:47:53 +01:00
a42322da0c feat(aio): code copy button has tooltip and aria-label
Based on optional title passed in from parent element such as CodeExample or CodeTabs.
Darkens uncovered copy button slightly as recommended for a11y.
PR #16808
2017-05-19 09:42:48 +01:00
4ccb2269a5 feat(aio): buttons for TOC "Contents" label
- Use buttons for the TOC “Contents” label when embedded-and-expandable or TOC on the right to satisfy a11y.
- Add aria-pressed setting for the toggles in TOC and NavItem.
- Clicking the right panel TOC “Contents” button scrolls to top.
- When embedded use same rotating caret as sidebar
- When embedded and no secondaries, “Content” is just a label.
- Gray background for focused buttons rather than outline because can’t get carets to work with outline.
2017-05-19 09:42:30 +01:00
b836aca999 docs(aio): rework of the upgrade guide
This commit was worked on by a number of people including
@filipesilva, @gkalpak and @wardbell. It contains changes that:

* remove unused files,
* fix the bootstrap approach to ensure that bootstrap is in the correct Zone
* fix unclosed code-example tags
* replace use of "we" with "you"
* remove broken dual router example

Related to angular/angular.io#3541
2017-05-19 09:42:17 +01:00
7d9f96abf0 fix(animations): make sure reuseable animation subtitutions work without default params (#16875) 2017-05-18 12:59:54 -07:00
86b7bd9c8e revert: refactor(router): cleanup, simplification
This reverts commit 44d48d9d7a.
2017-05-18 11:57:22 -07:00
a0a6029915 revert: refactor(router): don't run the change detection every time an outlet is activated
This reverts commit 198edb3109.
2017-05-18 11:57:22 -07:00
6531806996 build(aio): do not ignore is-on-https in lighthouse check
Lighthouse v1.6.5 treats localhost/1.2.7.0.0.1 as secure domains (i.e. as if they where HTTPS), so we need to stop handling the is-on-https audit specially.
2017-05-18 15:48:49 +01:00
3a604ba0bf fix(aio): fix PWA testing on Windows (and reduce flaky-ness)
Adding a delay after Lighthouse has run and before killing the Chrome process
avoids `ECONNREFUSED` errors on Windows and will hopefully prevent such errors
occasionally appearing on CI.

Based on info in:
https://github.com/paulirish/pwmetrics/issues/63#issuecomment-282721068
2017-05-18 15:48:49 +01:00
39f2977fa8 fix(aio): fix up typing errors in tests
The new version of Jasmine types identified issues with our unit tests.
2017-05-18 15:48:49 +01:00
faacbe4dac build(aio): upgrade to @angular/cli@1.1.0-rc.0 2017-05-18 15:48:49 +01:00
47c72e4627 perf(aio): load reflect-metadata polyfill only in the dev mode 2017-05-18 15:48:49 +01:00
59136fdbe4 build(aio): add ngo + purify 2017-05-18 15:48:49 +01:00
35d1922006 ci(aio): log the full PWA testing results
This will help diagnose errors or regressions in PWA scores.
2017-05-18 15:48:49 +01:00
b40aae54b7 fix(aio): fix PWA testing in cases where atob/btoa is necessary
In some cases (unclear when), traceviewer-js, used by Lighthouse under the hood,
assumes `atob`/`btoa` are defined in the global scope. This is true for browser
environments, but not on node.

As a result, some aggregations that required access to model-tracing failed to
produce results, dropping the overall PWA score.

This affected #16665 (e.g. commit 0de6eec7a).
2017-05-18 15:48:49 +01:00
baa3fbab46 build(aio): upgrade @angular/* and friends, remove obsolete deps 2017-05-18 15:48:49 +01:00
3361a7b834 build(aio): minor update-preview-server.sh improvements 2017-05-18 14:41:54 +01:00
0e13a5956c fix(aio): add small delay before autoscrolling
This delay allows any async layout to complete
before we autoscroll to a heading anchor.

Closes #16242
2017-05-18 14:37:45 +01:00
9466908c22 build(aio): add script for updating the preview server 2017-05-18 08:21:25 +01:00
93d27d283a build(aio): fix syntax error in preview server's Dockerfile 2017-05-18 08:21:25 +01:00
6f59a4a5b2 docs(aio): minor docs fixes for aio-builds-setup/ 2017-05-18 08:21:25 +01:00
71c3103312 build(aio): reduce images weight 2017-05-18 08:20:10 +01:00
198edb3109 refactor(router): don't run the change detection every time an outlet is activated 2017-05-17 19:32:04 -07:00
44d48d9d7a refactor(router): cleanup, simplification 2017-05-17 19:32:04 -07:00
712630ca65 perf(animations): reduce size of animations bundle
before:
animations.umd.js = 33k
animations.umd.min.js = 9.0k

after:
animations.umd.js = 31k
animations.umd.min.js = 7.9k
2017-05-17 16:30:36 -07:00
6416e79933 build: do not create the bundles when updating the public API
- 1.7x faster on my machine (2.7 vs 4.6 min)
- should solve the memory issue
2017-05-17 08:34:50 -07:00
238c5238a5 fix(aio): remove searchbox shrink animation 2017-05-17 08:22:28 +01:00
593fe5ed25 build(aio): enable HTTP/2 on the preview server (#16826)
Fixes #16780
2017-05-16 21:07:28 -07:00
54a6e4ff9e refactor(animations): make animation testing work with fixture.whenRenderingDone 2017-05-16 17:39:57 -07:00
8a6eb1ac78 refactor(animations): single animation engine code pass 2017-05-16 17:39:57 -07:00
16c8167886 feat(animations): introduce a wave of new animation features 2017-05-16 17:39:57 -07:00
d761059e4d fix(compiler-cli): allow '==' to compare nullable types (#16731)
Fixes: #16729

* fix(compiler-cli): diagnose issues in conditional of ternary

Fixes: #16730
2017-05-16 16:36:51 -07:00
c805082648 fix(platform-server): wait for async app initializers to complete before removing server side styles (#16712)
This fixes a flicker when transitioning from server rendered page to client rendered page in lazy loaded routes by waiting for the lazy loaded route to finish loading, assuming initialNavigation on the route is set to 'enabled'.

Fixes #15716
2017-05-16 15:14:55 -07:00
9a7f5d580f ci: update build to use TypeScript 2.3.2 (#16707) 2017-05-16 13:29:38 -07:00
af99cf2a41 build(aio): compute `{@link ...} titles more effectively (#16813)
If a usage of `{@link ...}` does not provide a title then
compute it based on the `title` and/or `name` properties
or set the link to invalid.

Closes #16811
2017-05-16 10:21:31 -07:00
221f85af3f fix(aio): no white band below toolbar when mobile
At 600px wide, the buttons on tool bar shrank from 64 to 56px.
Should shrink the content’s top padding from 64 to 56 as well.
2017-05-16 10:43:12 +01:00
a68ad6d58d feat(aio): sidenav headers should be focusable buttons
Sidenav headers had been anchors w/o hrefs. These can’t take focus which makes you can’t navigate through them with keyboard. For a11y purposes, this PR turns them into buttons.
2017-05-16 10:02:08 +01:00
7ae0440cca docs(aio): remove fundamentals/techniques intro pages 2017-05-16 09:45:48 +01:00
2ee7662737 test(aio): remove compileComponents from all tests
By reflex we began all component tests with an async `beforeEach` that called `compileComponents`.
In at least one case (`live-example.component.spec.ts`) that led to the `it` tests being async as well.
There is no need to call `.compileComponents` because CLI web pack + plugin inlines all templates and styles.
While `.compileComponents` was harmless, it added complexity and distraction which we should not inflict on future readers and testers.
2017-05-16 09:45:04 +01:00
44195858e6 docs(aio): fix toh e2e test 2017-05-16 09:42:59 +01:00
39b92f7e54 feat: introduce TestBed.overrideProvider (#16725)
This allows to overwrite all providers for a token, not matter
where they were defined.

This can be used to test JIT and AOT’ed code in the same way.

Design doc: https://docs.google.com/document/d/1VmTkz0EbEVSWfEEWEvQ5sXyQXSCvtMOw4t7pKU-jOwc/edit?usp=sharing
2017-05-15 13:12:10 -07:00
1eba623d12 fix(compiler-cli): import routing module with forRoot (#16438) 2017-05-15 13:11:39 -07:00
ea2b24fb4e fix: add typescript 2.3.2 typings test (#16738)
Closes #16663
2017-05-15 13:10:43 -07:00
a9d9aa18a0 build(aio): automatically link code blocks to API docs 2017-05-15 10:56:38 +01:00
ecc356ecea fix(aio): set focus back to search box on "escape"
For improved accessibility, the focus should be sent
back to the input box when we are displaying the
search results and the user hits ESC.

See #16005
2017-05-14 15:46:24 +01:00
98e31290a7 refactor(aio): centralise search ui management
Previously the logic for deciding when to display
the search result was spread between different
parts of the application and used non-intuitive logic
such as sending a blank results set to the searchResults.

This commit moves the management of displaying
the search results (and also setting focus of the
search input box) to the AppComponent. This makes
it easier to understand what happens and why; but
also allows the search UI components to be more
easily reused (such as embedding them in the 404
page).
2017-05-14 15:46:24 +01:00
7a759df49e refactor(aio): remove unused mock method 2017-05-14 15:46:24 +01:00
38c524d655 feat(core): introduce fixture.whenRenderingDone for testing (#16732) 2017-05-12 13:49:16 -07:00
06264645fd build: use subshells when changing directories
This prevents being left in the wrong directory in case of error.
2017-05-12 12:19:51 -07:00
21d213dfc7 refactor(aio): drop run (from yarn run), rename script and remove unnecessary cmds 2017-05-12 11:37:21 -07:00
3065fc6cca ci(aio): build aio-builds-setup scripts before pre-verifying PR
(Coincidentally), this wasn't an issue before fdfeaaf1f, because
pre-verification was run after `test.sh`, during which `aio-builds-setup` was
built.
Now that `deploy-staging.sh` is being run before `test.sh`, we need to build
the `aio-builds-setup` scripts first.
2017-05-12 11:37:21 -07:00
bcefc61da4 ci(aio): correctly catch PR preview pre-verification errors
Previously, `aio/aio-builds-setup/scripts/travis-preverify-pr.sh` was supposed
to exit with 1 if a PR did not meet the preconditions and 2 if an error occurred
during pre-verification.
It relied on the exit codes of the node script that did the actual work, but
didn't account for errors that would be thrown in the `sh` script itself (e.g.
if the node script was not available). This caused such errors to appear as
non-verified PRs, instead of real errors that should fail the build.

This commit swaps the exit codes, so that now a 2 means non-verified PR and 1
designates an error.
2017-05-12 11:37:21 -07:00
569b1e0eb7 fix(router): Wrap Promise-like instances in native Promises (#16759)
Hybrid apps (mix of Angular and AngularJS) might return AngularJS implementation
of Promises that do not play well with the change detection. Wrapping them in
native Promises fix this issue.

This could be the case when a Resolver returns a `$q` promise.
2017-05-12 10:03:54 -07:00
b83fe6f2a4 fix(upgrade): Prevent renaming of $inject property (#16706)
Use bracket notation to access $inject in downgradeInjectable to
support property renaming. Since the return type is any,
Closure compiler renames $inject.
2017-05-12 10:03:38 -07:00
b016984c04 docs(aio): resize images 2017-05-12 14:28:09 +01:00
90bc5b221b fix(aio): only register ServiceWorker in production mode
Since abb36e3cb, we no longer rely on the cli to set up ServiceWorker, but do it
manually as part of `yarn build`. When using `ng serve`, registering the
ServiceWorker fails, because we haven't created `ngsw-manifest.json` nor copied
`worker-basic.min.js` into dist.

This commit works around this, by only registering the service worker in
production mode (which is what the cli does too).

Caveat:
It is not possible to enable ServiceWorker with `ng serve`/`yarn start` and
using the `--prod` flag will try to register it, but fail because the necessary
files (`ngsw-manifest.json` and `worker-basic.min.js`) will not be available.
(As a work-around, you can use `yarn build` and serve the files in `dist/` with
`yarn http-server -- dist -p 4200`.)
2017-05-12 14:17:27 +01:00
37f1fcbfc8 docs: remove angular.io from changelog (#16723) 2017-05-11 23:46:55 -07:00
4a599eec45 ci: fix syntax error in deploy.sh (#16739) 2017-05-11 15:52:39 -07:00
fdfeaaf1f3 ci(aio): deploy previews early (right after build) (#16734)
Previously, PR previews were deployed after successfully running all tests.
While this makes sense for staging/production deployments, previews should be
available as soon as possible (and regardless of the outcome of tests).

Fixes #16705
2017-05-11 15:12:23 -07:00
decb4cc4a3 ci(aio): create previews for all PRs that touch non-spec files in aio/ or packages/ (#16733)
Previously, no previews would be deployed for PRs that didn't touch files inside
`aio/`. Now, previews will be deployed for PRs that touch non-spec files inside
either `aio/` or `packages/` (as long as other preconditions are met).

Partially addresses #16526.
2017-05-11 15:02:13 -07:00
44c7ac0fe9 fix(upgrade): use quote to prevent ClossureCompiler obfuscating $event. (#16724)
This is critical for AngularJS to get the $event object in template.
2017-05-11 13:54:04 -07:00
abb36e3cba fix(aio): switch to ngu-sw-manifest gen to unblock critical path (#16709) 2017-05-11 13:21:27 -07:00
84c30be164 docs: add saved replies (#16726) 2017-05-11 13:17:58 -07:00
5afaa39e68 refactor(compiler): produce more dense generated code (#16666)
This changes the formatting to be less verbose but still
tries to be readable.
2017-05-11 10:28:48 -07:00
ce1d7c4a6e refactor: use view engine also for NgModuleFactorys (#16658)
* refactor(core): provide error message in stack for reflective DI

Fixes #16355

* fix(compiler): make AOT work with `noUnusedParameters`

Fixes #15532

* refactor: use view engine also for `NgModuleFactory`s

This is a prerequisite for being able to mock providers
in AOTed code later on.
2017-05-11 10:26:02 -07:00
b9521b568f feat(compiler): support a non-null postfix assert (#16672)
Template expressions can now use a post-fix `!` operator
that asserts the target of the operator is not null. This is
similar to the TypeScript non-null assert operator. Expressions
generated in factories will be generated with the non-null assert
operator.

Closes: #10855
2017-05-11 10:15:54 -07:00
2eca6e67e1 test(aio): add tests for renderAttributes helper 2017-05-11 10:30:10 +01:00
c757e5794f build(aio): serve gzipped content from the preview server
Fixes #16699
2017-05-11 10:29:24 +01:00
67dc970ce4 docs(common): remove h1s from API docs
Only one h1 is allowed per document, and this is provided by the template.
So we cannot have any h1 tags (or `#` markdown shorthand) in any API docs.

Closes #16193
2017-05-11 08:15:19 +01:00
56ccd5e6a1 docs(aio): remove excess h1s from guides
Only one h1 is allowed per document.

(Also took the opportunity to remove unnecessary blank lines from these
docs, and a bit of general tidying.)

Closes #16193
2017-05-11 08:15:19 +01:00
bcbee13e26 build(aio): fail build if more than h1 is found in a doc
Closes #16193
2017-05-11 08:15:19 +01:00
d28a3f7878 docs(router): Change CanDeactivate to CanLoad (#16237)
fix mistake in docs. CanDeactivate should be CanLoad
2017-05-10 16:34:34 -07:00
162dffb7e8 docs: add changelog for 4.2.0-beta.1 2017-05-10 16:26:34 -07:00
0f0b9896b7 release: cut the 4.2.0-beta.1 release 2017-05-10 16:26:34 -07:00
e242e20ca3 fix(aio): style TOC scrollbar similar to other scrollbars 2017-05-10 15:02:44 +01:00
f148ebe99e docs(aio): remove warning when testing in the webpack guide 2017-05-10 15:02:21 +01:00
f795f649cf refactor(aio): remove redundant styles
Due to the new structure of `live-example`, the only style in
`_live-example.scss` was not applied. This is OK according to
https://github.com/angular/angular/issues/15935#issuecomment-295482503, since we
want it to take the full width.

Fixes #15935
2017-05-10 14:49:37 +01:00
063db641f8 fix(aio): style sidenav-content scrollbar similar to other scrollbars 2017-05-10 14:48:29 +01:00
b44f5c69e1 build(aio): make less verbose tools 2017-05-10 14:26:14 +01:00
56833a6171 docs(aio): more content fixes 2017-05-10 12:51:01 +01:00
efa2928547 docs(aio): add a progress reporter for the webpack guide 2017-05-10 11:54:39 +01:00
f5335d17ec build(aio): fail the doc-gen if there is an invalid {@link ...} tag
This fail behaviour is only turned on for `yarn docs`;
in `yarn docs-watch` you only receive a warning.
This is because you can get false errors when watching
since we don't parse all the docs in that case.
2017-05-10 11:39:07 +01:00
dc7d24267d feat(aio): update code style 2017-05-10 07:57:45 +01:00
412ab3f20c fix(aio): basic fix to the TOC styling
Without any major refactoring these changes bring the TOC much closer to
how the Google developer docs TOC looks.

Closes #16646
2017-05-10 07:49:59 +01:00
954c08e97c fix(aio): remove unwanted code styling from styleguide doc
Closes #16514
2017-05-10 07:49:03 +01:00
a2dcb7b476 build: update key for pushing to PACKAGE-builds repos (#16667) 2017-05-09 16:37:05 -07:00
bb0902c592 refactor(compiler-cli): move the expression expression type checker (#16562)
The expression type checker moved from the language service
to the compiler-cli in preparation to using it to check
template expressions.
2017-05-09 16:16:50 -07:00
9e661e58d1 docs(aio): image sweep (#16609)
* fix(aio): allow code blocks to clear floated images

Previously the negative margin on the code headings were causing
floated images to overlay the start of a code block. Now all code block
successfully clear all floated elements.

* feat(aio): add a `.clear` class for clearing floating images

* fix(aio): tidy up image styles

The css rules for `img.right` and `img.left` allow authors easy
access to floating an image on the left or right, respectively.

The `.image-display` rule which was always found on a figure
has been simplified so that all figures have this styling. It is very
unlikely that a figure will be used outside the content area; and
at this time it seems like `figure` is as good an indicator that we
want this kind of styling as anything.

Now that images are all tagged with width and height values, we cannot
assume to modify these dimensions via CSS as it can cause the image to
lose its correct proportions.  Until we find a better solition we must set
`height` to `auto` when the screen width is below 1300px to ensure that
these images maintain their proportions as they get shrunk to fit.

* docs(aio): general tidy up of image HTML in guides

Previously, the guides have a lot of inline image styling and unnecessary
use of the `image-display` css class.
Images over 700px are problematic for guide docs, so those have been given
specific widths and associated heights.

* docs(aio): use correct anchor for "back to the top" link

The `#toc` anchor does not work when the page is
wide enough that the TOC is floating to the side.

* build(aio): add `#top-of-page` to path variants for link checking

Since the `#top-of-page` is outside the rendered docs
the `checkAnchorLinks` processor doesn't find them
as valid targets for links.
Adding them as a `pathVariant` solves this problem
but will still catch links to docs that do not actually exist.

* fix(aio): ensure that headings clear floated images

* fix(aio): do not force live-example embedded image to 100% size

This made them look too big, generally. Leaving them with no size means
that they will look reasonable in large viewports and switch to 100% width
in narrow viewports.
2017-05-09 15:53:32 -07:00
d0e72a8f8f docs(*) fix dangling links in API docs (#16632)
* docs(animations): fix links to `Component` animations

* docs(core): fix links to `ReflectiveInjector` methods

The `resolve` and other methods were moved from the
`Injector` to the `ReflectiveInjector`.

* docs(core): fix links to `Renderer`

The local links were assuming that that methods were on the
current document (e.g. `RootRenderer`), but they are actually
on the `Renderer` class.

* docs(router): fix links to methods

* docs(forms): fix links to methods

* docs(core): fix links to methods

* docs(router): fix API page links and an internal link
2017-05-09 15:51:37 -07:00
b44eb328e0 docs(core): remove link to non-existent class in InjectableDecorator (#16653)
Closes #16640
2017-05-09 15:51:23 -07:00
7b4a8d53a7 build(aio): remove "pure" property from pipe template (#16655)
This information is not relevant to users, right now.

Closes #16641
2017-05-09 15:44:10 -07:00
73505e2ff0 feat(aio): scrolling tweaks per 16619 (#16660) 2017-05-09 15:36:24 -07:00
e11f5294ab build(aio): run docs after plunkers and zips (in setup) 2017-05-09 14:33:40 +01:00
0190df9cf3 build(aio): local assets are not dangling links
Closes #16615
Closes #16616
2017-05-09 14:33:40 +01:00
1af42aedfa docs(aio): doc guide tweaks (GS thru Components)
Working through obvious non-image content defects, from top of menu (Getting Started) through Fundamentals/Components.
2017-05-09 14:27:27 +01:00
215611aa9e refactor(aio): rename auto-scroll -> scroll and refactor
Refactor inspired by @gkalpak feedback on PR #16619
2017-05-09 14:11:23 +01:00
98d83b2e2d feat(aio): scroll back to top when TOC more-items clicked to close
closes  #16482
Usability feature. When long embedded TOC, collapsing it should scroll user to TOC top, not leave in the middle of the page.
2017-05-09 14:11:23 +01:00
cf7689ea7b refactor(aio): simplify/clarify some observables 2017-05-09 14:10:58 +01:00
2848f0499f fix(aio): do not nav to an image url
closes #16608
Formerly, tried to navigate when user clicked an anchor with an image url (to view image in a new tab) resulting in 404.
Now ignores href URL with any extension and lets browser handle it.
2017-05-09 14:10:36 +01:00
5d4b36f80f fix(router): fix redirect to a URL with a param having multiple values (#16376)
fixes #16310

PR Close #16376
2017-05-08 17:50:33 -05:00
415a0f8047 test(router): simplify redirect tests (#16376) 2017-05-08 17:50:29 -05:00
e0a8376237 fix(compiler): avoid a ...null spread in extraction (#16547)
This code only runs in ES5 mode in the test suite, so this is difficult to test.
However `updateFromTemplate` is being called with a spread operator, as
`...updateFromTemplate(...)`. The spread operator should fail on `null` values.
This change avoids the problem by always returning a (possibly empty) array.

PR Close #16547
2017-05-08 17:49:43 -05:00
f0f65443c6 fix(core): detach projected views when a parent view is destroyed (#16592)
This also clarifies via a test 
that we only update projected views when the view is created or destroyed,
but not when it is attached/detached/moved.

Fixes #15578

PR Close #16592
2017-05-08 17:49:02 -05:00
fcc91d862f fix(core): projected views should be dirty checked when the declaring component is dirty checked. (#16592)
Previously a projected view was only dirty checked when the
component in which it was inserted was dirty checked.

This fix changes the behavior so that a view is also dirty checked if
the declaring component is dirty checked.

Note: This does not change the order of change detection,
only the fact whether a projected view is dirty checked or not.

Fixes #14321
2017-05-08 17:48:58 -05:00
3887d8a429 feat(aio): close sidenav in narrow (mobile) mode when select a new document (#16617)
closes #16603
As before this PR, when wide (side-by-side), the sidenav open/close status only changes when nav to/from marketing page in which case it opens for guide/api and closes for marketing page.
2017-05-08 15:41:19 -07:00
041f57cb7d feat(aio): focus search when / is pressed (#16636)
This will aid accessibility.

Closes #16129
2017-05-08 15:40:32 -07:00
c5ce0408a1 docs(core): EventEmitter docs for isAsync defaults (#15780)
- solves #15758
2017-05-08 10:47:53 -07:00
98dd609f89 docs(animations): remove duplicate word (#16508) 2017-05-08 10:47:13 -07:00
6dc67772b0 docs(aio): move links from nav groups to items
Fundamentals and Techniques nav groups we also links to pages. This caused
counterintuitive behaviour when clicking on them.

This commit moves each link from the group item to a children item, called
Introduction.

Closes #16604
2017-05-07 08:14:10 +01:00
5cf64266f8 docs(aio): fix sizing of marketing doc images
This commit ensures that all the images in the marketing docs are correcly
sized.

Related to #16600
2017-05-06 14:35:24 +01:00
03513e9d70 fix(aio): constrain header-link styles
The CSS rule for positioning the automated header links was too general,
causing other links inside headings to be positioned incorrectly.

Closes #16573
2017-05-06 14:32:49 +01:00
b9ed97c0d9 fix(aio): do not display a Toc initially
Previously the `hasToc` was initialised to true, which caused a flash of
unwanted "Contents" [sic] even if the page was not going to need a ToC.

Closes #16597
2017-05-06 14:32:09 +01:00
799be9c98a feat(aio): TOC float right + service refactor
TOC appears in right panel when wide and hides embedded TOC
Right TOC panel height adjusts dynamically during scroll
Refactored `TocService` and its tests for clarity.
2017-05-06 07:13:31 +01:00
566dab1140 build(aio): upgrade to @angular/service-worker@1.0.0-beta.11 (#16594) 2017-05-05 16:16:55 -07:00
42dc2c19ee refactor(aio): remove redundant test
The search worker is now initialised from the `SearchBoxComponent`, which
has its own tests for this.

Closes #15593
2017-05-05 13:43:02 -07:00
518eb540aa refactor(aio): tweak the CSS to account for new CSS classes on aio-shell 2017-05-05 13:42:34 -07:00
309ada5df5 feat(aio): add helper CSS classes to the aio-shell for fine grained styling
Alternative to #16564
Closes #16549
2017-05-05 13:42:34 -07:00
9da63408b0 fix(http): flatten metadata for @angular/http/testing
@angular/http/testing used to publish a metadata structure which paralleled
the .d.ts structure. This causes ngc to write incorrect imports for this
bundle when compiling providers using MockBackend and other http testing
classes.

This change restructures the @angular/http/testing build a bit, modeling it
after @angular/platform-browser-animations, and produces a FESM structure
that has flat metadata.

Fixes #15521.
2017-05-05 14:20:46 -04:00
7ae7a8440c fix(http): introduce encodingHint for text() for better ArrayBuffer support
Currently, if a Response has an ArrayBuffer body and text() is called, Angular
attempts to convert the ArrayBuffer to a string. Doing this requires knowing
the encoding of the bytes in the buffer, which is context that we don't have.

Instead, we assume that the buffer is encoded in UTF-16, and attempt to process
it that way. Unfortunately the approach chosen (interpret buffer as Uint16Array and
create a Javascript string from each entry using String.fromCharCode) is incorrect
as it does not handle UTF-16 surrogate pairs. What Angular actually implements, then,
is UCS-2 decoding, which is equivalent to UTF-16 with characters restricted to the
base plane.

No standard way of decoding UTF-8 or UTF-16 exists in the browser today. APIs like
TextDecoder are only supported in a few browsers, and although hacks like using the
FileReader API with a Blob to force browsers to do content encoding detection and
decoding exist, they're slow and not compatible with the synchronous text() API.

Thus, this bug is fixed by introducing an encodingHint parameter to text(). The
default value of this parameter is 'legacy', indicating that the existing broken
behavior should be used - this prevents breaking existing apps. The only other
possible value of the hint is 'iso-8859' which interprets each byte of the buffer
with String.fromCharCode. UTF-8 and UTF-16 are not supported - it is up to the
consumer to get the ArrayBuffer and decode it themselves.

The parameter is a hint, as it's not always used (for example, if the conversion
to text doesn't involve an ArrayBuffer source). Additionally, this leaves the door
open for future implementations to perform more sophisticated encoding detection
and ignore the user-provided value if it can be proven to be incorrect.

Fixes #15932.

PR Close #16420
2017-05-05 14:20:36 -04:00
aef524506b fix(http): honor RequestArgs.search and RequestArgs.params map type
Currently `new Request({search: ...})` is not honored, and
`new Request({params: {'x': 'y'}) doesn't work either, as this object would have
toString() called. This change allows both of these cases to work, as proved by
the 2 new tests.

Fixes #15761

PR Close #16392
2017-05-05 14:20:26 -04:00
547c363473 feat: add .ngsummary.ts files to support AOT unit tests
Design doc: https://docs.google.com/document/d/1VmTkz0EbEVSWfEEWEvQ5sXyQXSCvtMOw4t7pKU-jOwc/edit?usp=sharing
2017-05-05 13:23:53 -04:00
2714644528 docs(aio): fix broken links discovered May 4th 2017-05-05 11:05:22 +01:00
d27588b5fb feat(aio): api label styles
- Moved info bar section in pipe template to be the first section to match other templates
- Fixed label styling for type label
- Added label styling for status label
2017-05-05 11:04:55 +01:00
a8379a46cf docs(aio): remove toc from all marketing pages 2017-05-05 10:43:36 +01:00
b7caa3e024 fix(aio): do not route eplnkr URLs to / from ServiceWorker 2017-05-05 10:42:58 +01:00
3e33482386 feat(aio): should not send in-page navigations to Google Analytics
closes #16521
`LocationService` sends `GaService` a url stripped of fragment and query strings.
`GaService` already guards against re-send of the prior url so it will only report doc changes.
2017-05-05 10:37:36 +01:00
5057e16874 build(aio): throw an error if a code-example tag is not closed 2017-05-04 15:52:17 -07:00
9449eff6fd docs(aio): improve tutorial next-steps prose/links 2017-05-04 15:51:57 -07:00
03610526e5 docs(aio): remove hardcoded TOC 2017-05-04 15:51:37 -07:00
cd28df627c docs: add changelog for 4.2.0-beta.0 2017-05-04 14:41:39 -07:00
59d62bc5aa release: cut the 4.2.0-beta.0 version 2017-05-04 14:37:20 -07:00
8f46db3701 docs: add changelog for 4.1.1 2017-05-04 14:35:18 -07:00
109229246c test(compiler-cli): add test for missingTranslation parameter 2017-05-04 15:07:27 -04:00
5856298798 test: cleanup rxjs custom build
The latest rxjs release works with closure compiler out of the box.
We no longer need to compile our own.

Also put closure options into a file rather than using a shell script.
2017-05-04 15:07:27 -04:00
7f9c589ba3 feat(core): add begin and end renderer methods to track change detection 2017-05-04 15:07:27 -04:00
8931e71c5c build(aio): ensure that tutorial index arrives in the tutorial search area
Fixes #16457
2017-05-04 13:45:04 +01:00
5bc435eba3 feat(aio): provide icon for API packages in search 2017-05-04 13:45:04 +01:00
4dabec6a48 build(aio): compute search title for certain API docs 2017-05-04 13:45:04 +01:00
978376a46e build(aio): doc.searchTitle can override name in search results 2017-05-04 13:45:04 +01:00
895f47a9c2 refactor(aio): remove work-around for browsers without ServiceWorker support
This essentially reverts #15731, which is no longer necessary after
angular/mobile-toolkit@eeb4b22 (which is included in v1.0.0-beta.10).
2017-05-04 12:28:19 +01:00
aec65dee71 refactor(aio): simplify DocViewer 2017-05-04 12:23:49 +01:00
2f66932bd1 fix(aio): make aio-top-menu .nav-link cover the whole <li>
Discussed in https://github.com/angular/angular/pull/16513/files#r114630560.
2017-05-03 19:34:58 -07:00
919ff12377 fix(aio): cheatsheet table layout fix 2017-05-03 19:34:12 -07:00
6748aeabb6 build(aio): update to dgeni-packages 0.19.0
This contains an updated dependency to TypeScript 2.1, which supports the
language constructs that we are using in Angular.
2017-05-03 19:33:58 -07:00
b3e63c09ab fix(upgrade): initialize all inputs in time for ngOnChanges()
Previously, non-bracketed inputs (e.g. `xyz="foo"`) on downgraded components
were initialized using `attrs.$observe()` (which uses `$evalAsync()` under the
hood), while bracketed inputs (e.g. `[xyz]="'foo'"`) were initialized using
`$watch()`. If the downgraded component was created during a `$digest` (e.g. by
an `ng-if` watcher), the non-bracketed inputs were not initialized in time for
the initial call to `ngOnChanges()` and `ngOnInit()`.

This commit fixes it by using `$watch()` to initialize all inputs. `$observe()`
is still used for subsequent updates on non-bracketed inputs, because it is more
performant.

Fixes #16212
2017-05-03 19:32:57 -07:00
77b8a76f2e feat(aio): toc styling
- TOC styling
- TOC container placeholder
2017-05-03 19:32:41 -07:00
55b8de9fdd build(aio): lint examples 2017-05-03 13:44:41 -07:00
eb56ab38dc fix(aio): remove top bar menu item focus jump 2017-05-03 13:43:42 -07:00
f29c6bbc6f docs(aio): fix missing title warnings
This commit provides missing titles for Press kit.
License appears not to want a title.
ngmodule and resources2 are no longer needed.
2017-05-03 13:42:10 -07:00
464701a899 docs(aio): revert removal of tutorial examples
These files were inadvertently removed in #16488 but are actually still referenced.

Closes #16503
2017-05-03 13:41:05 -07:00
5b96fb9320 build(aio): rename src/content to src/generated
This commit will definitely require a clean up of your
working folder:

```
cd aio
git clean -xdf
yarn setup
```
2017-05-03 13:40:46 -07:00
1cfb263ee3 docs(aio): hide the copy button for code in StyleGuide tables. 2017-05-03 13:38:25 -07:00
9ca2b4c967 feat(aio): set “avoid” class by convention (filename has “.avoid.”)
Most important for StyleGuide which lost example css class in migration.
Also hides copy for “avoid” files.
2017-05-03 13:30:45 -07:00
673d8ae583 feat(aio): add attribute utils for code atty interpretation.
These utils support flexible, natural attribute interpretation as applied to code-example and code-pane. Then apply those utils to code-example and live-example
2017-05-03 13:30:45 -07:00
8760bf7be4 build(aio): fix paths to template macros in overview-dump template 2017-05-03 13:30:11 -07:00
ea02073c84 build(aio): mark API docs as not having a TOC 2017-05-03 13:30:11 -07:00
b051d7ff48 build(aio): refactor API templates for clean start 2017-05-03 13:30:11 -07:00
cade722e48 build(aio): upgrade to dgeni-packages@0.18.0
This contains a fix for the typescript module reader.
Previously, TS modules that were of the form:

```
a/b/index.ts
```

Would be given the name `index` and id of `a/b/index`.
This is not desirable, so the new version of dgeni-packages/typescript
removes this `index` from the id and name, which results in name of
`b` and id of `a/b`.
2017-05-03 13:30:11 -07:00
58817f55e1 fix(aio): do not collapse API code example indentation 2017-05-03 13:30:11 -07:00
3f1d7f7a76 build(aio): add the api folder to the list of template folders 2017-05-03 13:30:11 -07:00
c8dc116951 docs(aio): fixing new url on glossary date pipe 2017-05-03 13:29:04 -07:00
9684d78cae build: update concurrently to latest version
The regression in `concurrently` v3.2.0 (which made us roll back to v3.1.0
in #14378) has been fixed in v3.3.0 (see kimmobrunfeldt/concurrently#89).
2017-05-03 09:35:57 -07:00
71f5b73296 docs: fix links in api docs 2017-05-03 09:22:32 +01:00
5ba8c14893 docs(aio): remove unnecessary anchor links from styleguide 2017-05-03 09:21:18 +01:00
ea9d8a6bc7 docs(aio): move pipes guide under components 2017-05-03 09:01:05 +01:00
9650ff0824 docs(aio): add hero “Zero” to toh-6
Update to in-memory-web-api should handle id=0.
Make sure this works by having a hero with id=0 in ToH.
Coincidentally delete lingering dead app/ folders
Todo: fix ToH images (which have to do anyway)
2017-05-03 08:03:55 +01:00
00dce1698a docs(aio): grammatical fix. remove duplicate word 'our' 2017-05-03 08:03:03 +01:00
c946a929b7 refactor(compiler): simplify AOT tests 2017-05-02 15:51:54 -07:00
21c96a5af1 feat(aio): sidenav styling extended 2017-05-02 15:14:21 -07:00
a0b9c23100 feat(aio): support hiding the copy button on code-examole components
In the API docs there are occasions where we do not wish the code snippet
to have a copy button. This commit supports that by providing a new `hideCopy`
attribute.
2017-05-02 15:14:03 -07:00
cb5bc76766 feat(aio): move search results under search box
This enables keyboarders to tab from the search box into the results.
Important for a11y according to issue #16005.
2017-05-02 15:13:40 -07:00
1c8772a879 fix(aio): delete guide/index.md which is no longer used 2017-05-02 15:13:15 -07:00
79ed0e7121 feat(aio): display API icons in search results
Add API symbols for `let` and `var` so don’t have to translate those types into `const`
Also replace <hr> in search results HTML with ellipses icon.
2017-05-02 15:12:58 -07:00
0c69903123 feat(aio): top 5 weighted search results shown when many area results 2017-05-02 15:12:58 -07:00
04dc24820d feat(aio): comment cleanup, API table styles
- API table styling
- Cleanup on resource page comments
2017-05-02 10:57:54 -07:00
e263e19a2a fix(core): don’t stop change detection because of errors
- prevents unsubscribing from the zone on error
- prevents unsubscribing from directive `EventEmitter`s on error
- prevents detaching views in dev mode if there on error
- ensures that `ngOnInit` is only called 1x (also in prod mode)

Fixes #9531
Fixes #2413
Fixes #15925
2017-05-01 18:56:25 -04:00
ac220fc2bb docs(aio): fix image size on homepage 2017-05-01 15:52:51 -07:00
3b80472bad style(aio): fix comment on postProcessHtml processor
Fixes https://github.com/angular/angular/pull/16336#discussion_r113891332
2017-05-01 15:52:51 -07:00
ca17d4f639 build(aio): auto-fill width/height to all image tags
Parse all `<img>` tags, during doc-gen, and insert the width and height of
the sourceed image, if neither are already specified.

Warnings are reported if the `<img>` tag has no `src` attribute or the image
cannot be loaded.

The work is done in the `addImageDimensions` post-processor, which must be
configured with a `basePath` so that it knows where to find the images.

Closes #15888
2017-05-01 15:52:51 -07:00
64335d3521 build(aio): capture and log errors and warnings when post-processing HTML 2017-05-01 15:52:51 -07:00
9945ce2259 build(aio): move copyContentAssets processor to the base package
This allows other processors who need to know about the copyContentAssets
processors to ensure that the runs after the content has been copied.
2017-05-01 15:52:51 -07:00
6d9da73090 build(aio): move attribute utils to helpers folder
This allows these utility functions to be reused across packages.
2017-05-01 15:52:51 -07:00
c889fb1ef5 build(aio): include new packages in docs-watch 2017-05-01 15:52:51 -07:00
d32bc5df3e feat(aio): add tooltip to nav node if none specified 2017-05-01 15:52:35 -07:00
efaf502e95 feat(aio): tell user when no search results
closes #16294
2017-05-01 15:51:44 -07:00
ff32c53099 docs(aio): fix lots of broken links
Closes #16317
2017-05-01 15:51:26 -07:00
235eb17cca build(aio): improve info-bar template
The "npm module" and "NgModule" are now rendered correctly.

Closes #16055
2017-05-01 15:51:03 -07:00
aaa562898f fix(aio): correct position of sidenav icons 2017-05-01 15:50:34 -07:00
c8fd904c32 fix(aio): remove max width on marketing pages and footer
- Removed the max-width on all marketing pages
- Moved the footer so that it extends fully horizontally on all screen sizes
2017-05-01 15:50:17 -07:00
65a8f7f6c9 fix(aio): search result styling
- Changed search result link hover state to white to be more legible
- Increased the max-height on search results container to remove the jitter
- Changed search results to have space-around vs space-between for more appropriate spacing
2017-05-01 15:49:46 -07:00
f32bfcdbe5 build(aio): convert triple backtick blocks to code-example tags
Closes #16246
2017-04-30 09:07:28 -07:00
24b9067047 build(aio): remove unused {@exampleTabs} inline tag 2017-04-30 09:07:28 -07:00
de8de84cea test(aio): confirm ga tracks user’s # fragment clicks within a page (#16441)
This was the behavior and we think we still want to do it.
Added comment and test to confirm that is our present intention.
2017-04-30 09:07:12 -07:00
8d300ffbfc fix(aio): header click should always toggle expand/collapse (#16439)
Also adds class tests for this NavItemComponent.
Not (yet) testing effects on the templated HTML
2017-04-30 09:07:00 -07:00
f74dafcb07 docs(aio): Add numbers and steps to Tutorial navigation (#16421) 2017-04-30 09:06:36 -07:00
065b76d14c feat(core): upgrade dep on zone.js to 0.8.9 (#16401)
PR Close #16401
2017-04-28 22:50:48 -05:00
253345c0c0 fix(language-service): remove asserts for non-null expressions (#16422)
Reworked some of the code so asserts are no longer necessary.
Added additional and potentially redundant checks
Added checks where the null checker found real problems.

PR Close #16422
2017-04-28 22:50:31 -05:00
270d694ed2 docs(common): fix API docs for NgComponentOutlet (#16411)
fixes #16373

PR Close #16411
2017-04-28 18:11:24 -05:00
f4b771a0c6 feat(language-service): provide external file list to TypeScript (#16417)
Also ensures that it only calls base language service for files
that it contains.

PR Close #16417
2017-04-28 18:11:18 -05:00
a4de214e2b fix(core): don’t set ng-version for dynamically created components (#16394)
Angular uses the `ng-version` attribute to indicate which elements
were used to bootstrap an application. However, after 4.0 we also
added this attribute for all dynamically created components.

Fixes #15880

PR Close #16394
2017-04-28 17:41:50 -05:00
aa8bba4865 fix(core): allow to detach OnPush components (#16394)
Fixes #9720
2017-04-28 17:41:45 -05:00
392d584572 fix(core): allow directives to inject the component’s ChangeDetectorRef. (#16394)
When a directive lives on the same element as a component
(e.g. `<my-comp myDir>`), the directive was not able to get hold
of the `ChangeDetectorRef` of the component on that element. However,
as directives are supposed to decorate components, this is incorrect.

This commit enables this use case.

Closes #12816
2017-04-28 17:41:31 -05:00
900a88b15d feat(core): allow custom selector when bootstrapping components (#15668)
- fixes #7136

PR Close #15668
2017-04-28 17:41:04 -05:00
cb384e8ed3 ci(language-service): update ci tests to official 2.3 build (#16415)
PR Close #16415
2017-04-28 17:40:53 -05:00
a2b2afb21c feat(aio): add marketing class to AppComponent <aio-shell> on mkt page (#16395)
Also navigation.json Doc page should be hidden in sidebar, shown in top
2017-04-28 15:34:20 -07:00
b70b9606eb docs(aio): apply guidelines to ts-to-js - adapt for aio site (#16054) 2017-04-28 15:34:00 -07:00
938406122e docs(aio): add missing word (#16328) 2017-04-28 15:33:23 -07:00
951a575db3 docs(aio): fix broken NgModules link in tutorial (#16398)
Brackets included in link title contents were breaking markdown
parsing, preventing correct link generation.
2017-04-28 15:32:46 -07:00
8c50457385 fix: public API golden files (#16414) 2017-04-28 09:48:15 -07:00
8c09d10ba9 fix: strictNullCheck support. (#16389) (#16389)
Fix #16357

Workaround for https://github.com/Microsoft/TypeScript/issues/10078

Closes #16389

PR Close #16389
2017-04-28 09:15:00 -05:00
c04e51cb15 fix(aio): do not create ToC for file-not-found and fetching-error documents
Related to #16078.
2017-04-28 15:06:05 +01:00
7b94f493b9 feat(aio): code snippet source available & shown when code missing
Tells reader (usually the author) what code file is missing
Also when no linenums specified, turn them on if num of lines > 10
2017-04-28 11:07:45 +01:00
f1f04fa782 feat(aio): implement gkalpak review suggestions 2017-04-28 09:45:09 +01:00
4be1966a21 feat(aio): revise Docs page; docs version selector in sidenav 2017-04-28 09:40:39 +01:00
de25cfc0cb build(aio): move autolink-headings to post-processing (#16336)
The autolinking is now done on the `renderedContent` which means it also
captures and autolinks headings that were generated outside of markdown.

PR Close #16336
2017-04-27 23:42:04 -05:00
de36a9b718 fix(aio): improve positioning of heading-anchors 2017-04-27 23:42:04 -05:00
883a3250e4 fix(aio): ensure styling of icons is specific to usage
Previously the CSS styling for material icons was too broad and affected
all instances of icons. This commit constrains the position of copy button
icons only to copy buttons.
2017-04-27 23:42:04 -05:00
1ceb2f9c79 build(aio): add new post-process dgeni package
This package will allow us to do complex post-processing
on the HTML that is rendered by Nunjucks.
2017-04-27 23:42:04 -05:00
fbde2a8010 docs(aio): correct abbreviation (#16295)
change abbreviation from 'ES' to 'ES2015'. the 'ES2015' abbreviation is used elsewhere in the tutorial.
2017-04-27 21:39:46 -07:00
81925fa66d feat(forms): introduce min and max validators (#15813)
PR Close #15813
2017-04-27 17:39:17 -05:00
6e2abcd5fc feat(compiler-cli): add param to set MissingTranslationStrategy on ngc (#15987)
This commit adds a new parameter to ngc named `missingTranslation`  to set the MissingTranslationStrategy for AoT, it takes the value `error`, `warning` or `ignore`.

Fixes #15808

PR Close #15987
2017-04-27 17:38:59 -05:00
3f46645f5f feat(aio): add Table of Contents (toc) component. (#16078) 2017-04-27 15:32:46 -07:00
2a7f63650c build(aio): update @angular/service-worker to v1.0.0-beta.10 2017-04-27 10:14:32 +01:00
cd5b1f306a refactor(aio): move /guide/docs.md to /marketing/docs.md
Because:
1. `docs` feels like a "top level" page, similar to `features`, `resources`, `events` etc.
2. This enables the ServiceWorker to pre-fetch/cache the document (similar to what happens with all
   other direct children of `content/docs/`), without the need for special-casing it in
   `ngsw-manifest.json`.
2017-04-27 10:14:32 +01:00
749bcf3d9c feat(aio): cache external resources in the ServiceWorker
Cache resources that are necessary for displaying the basic pages when offline.
2017-04-27 10:14:32 +01:00
a9027a2570 fix(aio): fix ServiceWorker routing 2017-04-27 10:14:32 +01:00
72e32b1f17 fix(aio): update files ignored by the ServiceWorker
Fixes #16198
2017-04-27 10:14:32 +01:00
96648ba98a fix(aio): fix incorrect image URLs in guides 2017-04-27 10:14:32 +01:00
6561d46349 refactor(aio): move content-related images to content/images/ 2017-04-27 10:14:32 +01:00
2e5e37ac58 refactor(aio): move unused images to unused/ sub-directories 2017-04-27 10:14:32 +01:00
6bac4e65ce build(aio): update dgeni-packages to 0.17.2 (#16358)
This version has fix for the jsdoc parser, which was
causing false negative warnings and errors in the doc
gen.

Closes #16319
2017-04-26 14:53:33 -07:00
a28f5eeed3 docs: update 4.1.0 changelog, remove aio 2017-04-26 12:11:45 -07:00
c0c50133e3 docs: update 4.1.0 changelog, remove aio 2017-04-26 12:11:03 -07:00
2114 changed files with 79698 additions and 61707 deletions

6
.bazelrc Normal file
View File

@ -0,0 +1,6 @@
# Disable sandboxing because it's too slow.
# https://github.com/bazelbuild/bazel/issues/2424
build --spawn_strategy=standalone
# Performance: avoid stat'ing input files
build --watchfs

55
.circleci/config.yml Normal file
View File

@ -0,0 +1,55 @@
# Configuration file for https://circleci.com/gh/angular/angular
# Note: YAML anchors allow an object to be re-used, reducing duplication.
# The ampersand declares an alias for an object, then later the `<<: *name`
# syntax dereferences it.
# See http://blog.daemonl.com/2016/02/yaml.html
# To validate changes, use an online parser, eg.
# http://yaml-online-parser.appspot.com/
# Settings common to each job
anchor_1: &job_defaults
working_directory: ~/ng
docker:
- image: angular/ngcontainer:0.0.2
# After checkout, rebase on top of master.
# Similar to travis behavior, but not quite the same.
# See https://discuss.circleci.com/t/1662
anchor_2: &post_checkout
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
version: 2
jobs:
lint:
<<: *job_defaults
steps:
- checkout:
<<: *post_checkout
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run: yarn install --freeze-lockfile --non-interactive
- run: ./node_modules/.bin/gulp lint
build:
<<: *job_defaults
steps:
- checkout:
<<: *post_checkout
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run: bazel run @yarn//:yarn
- run: bazel build ...
- save_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- "node_modules"
workflows:
version: 2
default_workflow:
jobs:
- lint
- build

View File

@ -1,39 +1,57 @@
<!--
IF YOU DON'T FILL OUT THE FOLLOWING INFORMATION WE MIGHT CLOSE YOUR ISSUE WITHOUT INVESTIGATING
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
-->
**I'm submitting a ...** (check one with "x")
```
[ ] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
```
## I'm submitting a...
<!-- Check one of the following options with "x" -->
<pre><code>
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
</code></pre>
**Current behavior**
<!-- Describe how the bug manifests. -->
## Current behavior
<!-- Describe how the issue manifests. -->
**Expected behavior**
<!-- Describe what the behavior would be without the bug. -->
**Minimal reproduction of the problem with instructions**
## Expected behavior
<!-- Describe what the desired behavior would be. -->
## Minimal reproduction of the problem with instructions
<!--
If the current behavior is a bug or you can illustrate your feature request better with an example,
please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
-->
**What is the motivation / use case for changing the behavior?**
<!-- Describe the motivation or the concrete use case -->
## What is the motivation / use case for changing the behavior?
<!-- Describe the motivation or the concrete use case. -->
**Please tell us about your environment:**
<!-- Operating system, IDE, package manager, HTTP server, ... -->
* **Angular version:** 2.0.X
## Environment
<pre><code>
Angular version: X.Y.Z
<!-- Check whether this is still an issue in the most recent Angular version -->
* **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
<!-- All browsers where this could be reproduced -->
Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
* **Language:** [all | TypeScript X.X | ES6/7 | ES5]
For Tooling issues:
- Node version: XX <!-- run `node --version` -->
- Platform: <!-- Mac, Linux, Windows -->
* **Node (for AoT issues):** `node --version` =
Others:
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->
</code></pre>

View File

@ -1,10 +1,15 @@
**Please check if the PR fulfills these requirements**
## PR Checklist
Please check if your PR fulfills the following requirements:
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
**What kind of change does this PR introduce?** (check one with "x")
## PR Type
What kind of change does this PR introduce?
<!-- Please check the one that applies to this PR using "x". -->
```
[ ] Bugfix
[ ] Feature
@ -12,25 +17,27 @@
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] angular.io application / infrastructure changes
[ ] Other... Please describe:
```
**What is the current behavior?** (You can also link to an open issue here)
## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
Issue Number: N/A
**What is the new behavior?**
## What is the new behavior?
**Does this PR introduce a breaking change?** (check one with "x")
## Does this PR introduce a breaking change?
```
[ ] Yes
[ ] No
```
If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
**Other information**:
## Other information

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
.DS_STORE
/dist/
bazel-*
e2e_test.*
node_modules
bower_components

1
.mailmap Normal file
View File

@ -0,0 +1 @@
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>

View File

@ -8,9 +8,11 @@
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# chuckjaz - Chuck Jazdzewski
# Foxandxss - Jesús Rodríguez
# gkalpak - George Kalpakas
# IgorMinar - Igor Minar
# jasonaden - Jason Aden
# juleskremer - Jules Kremer
# kara - Kara Erickson
# matsko - Matias Niemelä
# mhevery - Misko Hevery
@ -18,10 +20,11 @@
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# tbosch - Tobias Bosch
# tinayuangao - Tina Gao
# vicb - Victor Berchet
# vikerman - Vikram Subramanian
# wardbell - Ward Bell
# tinayuangao - Tina Gao
version: 2
@ -93,19 +96,21 @@ groups:
- "packages/core/*"
users:
- tbosch #primary
- chuckjaz
- mhevery
- vicb
- IgorMinar #fallback
compiler/animations:
animations:
conditions:
files:
- "packages/compiler/src/animation/*"
- "packages/animations/*"
- "packages/platform-browser/animations/*"
users:
- matsko #primary
- tbosch
- IgorMinar #fallback
- chuckjaz #fallback
- mhevery #fallback
- IgorMinar #fallback
compiler/i18n:
conditions:
@ -136,6 +141,7 @@ groups:
users:
- alexeagle
- chuckjaz
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
@ -248,10 +254,46 @@ groups:
angular.io:
conditions:
files:
- "aio/*"
include:
- "aio/*"
exclude:
- "aio/content/*"
users:
- IgorMinar #primary
- petebacondarwin #secondary
- petebacondarwin #primary
- IgorMinar
- gkalpak
- wardbell
- mhevery #fallback
angular.io-guide-and-tutorial:
conditions:
files:
include:
- "aio/content/*"
exclude:
- "aio/content/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
- juleskremer #primary
- Foxandxss
- stephenfluin
- wardbell
- petebacondarwin
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
angular.io-marketing:
conditions:
files:
include:
- "aio/content/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
- juleskremer #primary
- stephenfluin
- petebacondarwin
- gkalpak
- IgorMinar #fallback
- mhevery #fallback

View File

@ -1,5 +1,6 @@
language: node_js
sudo: false
dist: trusty
node_js:
- '6.9.5'
@ -32,30 +33,36 @@ env:
global:
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
# FIREBASE_TOKEN
# This is needed for publishing builds to the "aio-staging" firebase site.
# TODO(i): the token was generated using the iminar@google account, we should switch to a shared/role-base account.
- secure: "MPx3UM77o5IlhT75PKHL0FXoB5tSXDc3vnCXCd1sRy4XUTZ9vjcV6nNuyqEf+SOw659bGbC1FI4mACGx1Q+z7MQDR85b1mcA9uSgHDkh+IR82CnCVdaX9d1RXafdJIArahxfmorbiiPPLyPIKggo7ituRm+2c+iraoCkE/pXxYg="
# This is needed for publishing builds to the "aio-staging" and "angular-io" firebase projects.
# This token was generated using the aio-deploy@angular.io account using `firebase login:ci` and password from valentine
- secure: "L5CyQmpwWtoR4Qi4xlWQh/cL1M6ZeJL4W4QAr4HdKFMgYt9h+Whqkymyh2NxwmCbPvWa7yUd+OiLQUDCY7L2VIg16hTwoe2CgYDyQA0BEwLzxtRrJXl93TfwMlrUx5JSIzAccD6D4sjtz8kSFMomK2Nls33xOXOukwyhVMjd0Cg="
# ANGULAR_PAYLOAD_FIREBASE_TOKEN
# This is for payload size data to "angular-payload-size" firebase project
# This token was generated using the payload@angular.io account using `firebase login:ci` and password from valentine
- secure: "SxotP/ymNy6uWAVbfwM9BlwETPEBpkRvU/F7fCtQDDic99WfQHzzUSQqHTk8eKk3GrGAOSL09vT0WfStQYEIGEoS5UHWNgOnelxhw+d5EnaoB8vQ0dKQBTK092hQg4feFprr+B/tCasyMV6mVwpUzZMbIJNn/Rx7H5g1bp+Gkfg="
matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=e2e
- CI_MODE=e2e_2
- CI_MODE=js
- CI_MODE=saucelabs_required
- CI_MODE=browserstack_required
# deactivated, see #19768
# - CI_MODE=browserstack_required
- CI_MODE=saucelabs_optional
- CI_MODE=browserstack_optional
- CI_MODE=docs_test
- CI_MODE=aio_tools_test
- CI_MODE=aio
- CI_MODE=aio_e2e
- CI_MODE=aio_e2e AIO_SHARD=0
- CI_MODE=aio_e2e AIO_SHARD=1
- CI_MODE=bazel
matrix:
fast_finish: true
allow_failures:
- env: "CI_MODE=saucelabs_optional"
- env: "CI_MODE=browserstack_optional"
- env: "CI_MODE=aio_e2e"
before_install:
# source the env.sh script so that the exported variables are available to other scripts later on

25
BUILD.bazel Normal file
View File

@ -0,0 +1,25 @@
package(default_visibility = ["//visibility:public"])
exports_files(["tsconfig.json"])
# This rule belongs in node_modules/BUILD
# It's here as a workaround for
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940
filegroup(
name = "node_modules",
srcs = glob([
# Performance workaround: list individual files
# This won't scale in the general case.
# TODO(alexeagle): figure out what to do
"node_modules/typescript/**",
"node_modules/zone.js/**",
"node_modules/rxjs/**/*.d.ts",
"node_modules/rxjs/**/*.js",
"node_modules/@types/**/*.d.ts",
"node_modules/tsickle/**",
"node_modules/hammerjs/**/*.d.ts",
"node_modules/protobufjs/**",
"node_modules/bytebuffer/**",
"node_modules/reflect-metadata/**",
"node_modules/minimist/**/*.js",
]),
)

File diff suppressed because it is too large Load Diff

View File

@ -17,15 +17,15 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
## <a name="question"></a> Got a Question or Problem?
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
StackOverflow is a much better place to ask questions since:
Stack Overflow is a much better place to ask questions since:
- there are thousands of people willing to help on StackOverflow
- there are thousands of people willing to help on Stack Overflow
- questions and answers stay available for public viewing so your question / answer might help someone else
- StackOverflow's voting system assures that the best answers are prominently visible.
- Stack Overflow's voting system assures that the best answers are prominently visible.
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
@ -198,8 +198,7 @@ Must be one of the following:
* **fix**: A bug fix
* **perf**: A code change that improves performance
* **refactor**: A code change that neither fixes a bug nor adds a feature
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
semi-colons, etc)
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* **test**: Adding missing tests or correcting existing tests
### Scope
@ -207,6 +206,7 @@ The scope should be the name of the npm package affected (as perceived by person
The following is the list of supported scopes:
* **animations**
* **common**
* **compiler**
* **compiler-cli**
@ -223,7 +223,7 @@ The following is the list of supported scopes:
* **upgrade**
* **tsc-wrapped**
There is currently few exception to the "use package name" rule:
There are currently a few exceptions to the "use package name" rule:
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
* **changelog**: used for updating the release notes in CHANGELOG.md

View File

@ -3,22 +3,21 @@
[![Join the chat at https://gitter.im/angular/angular](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://www.npmjs.com/@angular/core)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
Angular
=========
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript (JS) and other languages.
# Angular
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript and other languages.
## Quickstart
[Get started in 5 minutes][quickstart].
## Want to help?
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our

17
WORKSPACE Normal file
View File

@ -0,0 +1,17 @@
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
tag = "0.0.5",
)
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
node_repositories(package_json = "//:package.json")
git_repository(
name = "build_bazel_rules_angular",
remote = "https://github.com/bazelbuild/rules_angular.git",
tag = "0.0.1",
)

View File

@ -9,10 +9,11 @@
"outDir": "dist",
"assets": [
"assets",
"content",
"generated",
"app/search/search-worker.js",
"favicon.ico",
"pwa-manifest.json"
"pwa-manifest.json",
"google385281288605d160.html"
],
"index": "index.html",
"main": "main.ts",
@ -21,17 +22,18 @@
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "aio",
"serviceWorker": true,
"serviceWorker": false,
"styles": [
"styles.scss"
],
"scripts": [
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
"next": "environments/environment.next.ts",
"stable": "environments/environment.stable.ts",
"archive": "environments/environment.archive.ts"
}
}
],

View File

@ -1,5 +0,0 @@
{
"projects": {
"staging": "aio-staging"
}
}

5
aio/.gitignore vendored
View File

@ -3,7 +3,7 @@
# compiled output
/dist
/out-tsc
/src/content
/src/generated
/tmp
# dependencies
@ -43,3 +43,6 @@ protractor-results*.txt
# System Files
.DS_Store
Thumbs.db
# copied dependencies
src/assets/js/lunr*

View File

@ -13,9 +13,14 @@ You should run all these tasks from the `angular/aio` folder.
Here are the most important tasks you might need to use:
* `yarn` - install all the dependencies.
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
* `yarn setup` - install all the dependencies, boilerplate, plunkers, zips and run dgeni on the docs.
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
* `yarn lint` - check that the doc-viewer code follows our style rules.
* `yarn test` - watch all the source files, for the doc-viewer, and run all the unit tests when any change.
* `yarn e2e` - run all the e2e tests for the doc-viewer.
@ -25,15 +30,33 @@ Here are the most important tasks you might need to use:
* `yarn docs-lint` - check that the doc gen code follows our style rules.
* `yarn docs-test` - run the unit tests for the doc generation code.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
* `yarn example-e2e` - run all e2e tests for examples
- `yarn example-e2e --setup` - force webdriver update & other setup, then run tests
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
## Using ServiceWorker locally
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
with webpack serving the files from memory).
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
with `yarn http-server dist -p 4200`.
For more details see #16745.
## Guide to authoring
There are two types of content in the documentatation:
There are two types of content in the documentation:
* **API docs**: descriptions of the modules, classes, interfaces, decorators, etc that make up the Angular platform.
API docs are generated directly from the source code.
@ -47,6 +70,9 @@ More specifically, there are sub-folders that contain particular types of conten
We use the [dgeni](https://github.com/angular/dgeni) tool to convert these files into docs that can be viewed in the doc-viewer.
The [Authors Style Guide](https://angular.io/guide/docs-style-guide) prescribes guidelines for
writing guide pages, explains how to use the documentation classes and components, and how to markup sample source code to produce code snippets.
### Generating the complete docs
The main task for generating the docs is `yarn docs`. This will process all the source files (API and other),
@ -56,7 +82,7 @@ extracting the documentation and generating JSON files that can be consumed by t
Full doc generation can take up to one minute. That's too slow for efficient document creation and editing.
You can make small changes in a smart editor that displays formatted markdown:
You can make small changes in a smart editor that displays formatted markdown:
>In VS Code, _Cmd-K, V_ opens markdown preview in side pane; _Cmd-B_ toggles left sidebar
You also want to see those changes displayed properly in the doc viewer
@ -85,8 +111,16 @@ yarn start
yarn docs-watch
```
>Alternatively, try the consolidated `serve-and-sync` command that builds, watches and serves in the same terminal window
```bash
yarn serve-and-sync
```
* Open a browser at https://localhost:4200/ and navigate to the document on which you want to work.
You can automatically open the browser by using `yarn start -- -o` in the first terminal.
You can automatically open the browser by using `yarn start -o` in the first terminal.
* Make changes to the page's associated doc or example files. Every time a file is saved, the doc will
be regenerated, the app will rebuild and the page will reload.
* If you get a build error complaining about examples or any other odd behavior, be sure to consult
the [Authors Style Guide](https://angular.io/guide/docs-style-guide).

View File

@ -29,6 +29,8 @@ ARG AIO_NGINX_PORT_HTTPS=443
ARG TEST_AIO_NGINX_PORT_HTTPS=4433
ARG AIO_REPO_SLUG=angular/angular
ARG TEST_AIO_REPO_SLUG=test-repo/test-slug
ARG AIO_TRUSTED_PR_LABEL="aio: preview"
ARG TEST_AIO_TRUSTED_PR_LABEL="aio: preview"
ARG AIO_UPLOAD_HOSTNAME=upload.localhost
ARG TEST_AIO_UPLOAD_HOSTNAME=upload.localhost
ARG AIO_UPLOAD_MAX_SIZE=20971520
@ -48,9 +50,11 @@ ENV AIO_BUILDS_DIR=$AIO_BUILDS_DIR TEST_AIO_BUILDS_DIR=$TEST
AIO_REPO_SLUG=$AIO_REPO_SLUG TEST_AIO_REPO_SLUG=$TEST_AIO_REPO_SLUG \
AIO_SCRIPTS_JS_DIR=/usr/share/aio-scripts-js \
AIO_SCRIPTS_SH_DIR=/usr/share/aio-scripts-sh \
AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL TEST_AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL \
AIO_UPLOAD_HOSTNAME=$AIO_UPLOAD_HOSTNAME TEST_AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME \
AIO_UPLOAD_MAX_SIZE=$AIO_UPLOAD_MAX_SIZE TEST_AIO_UPLOAD_MAX_SIZE=$TEST_AIO_UPLOAD_MAX_SIZE \
AIO_UPLOAD_PORT=$AIO_UPLOAD_PORT TEST_AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT \
AIO_WWW_USER=www-data \
NODE_ENV=production
@ -63,6 +67,7 @@ RUN apt-get update -y && apt-get install -y curl
RUN curl --silent --show-error --location https://deb.nodesource.com/setup_6.x | bash -
RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/backports.list
# Install packages
@ -71,11 +76,11 @@ RUN apt-get update -y && apt-get install -y \
cron \
dnsmasq \
nano \
nginx \
nodejs \
openssl \
rsyslog \
yarn
RUN apt-get install -t jessie-backports -y nginx
RUN yarn global add pm2@2
@ -109,31 +114,31 @@ RUN update-ca-certificates
# Set up nginx (for production and testing)
RUN rm /etc/nginx/sites-enabled/*
RUN sed -i -E "s|^user\s+\S+;|user $AIO_WWW_USER;|" /etc/nginx/nginx.conf
RUN rm -f /etc/nginx/conf.d/*
RUN rm -f /etc/nginx/sites-enabled/*
COPY nginx/aio-builds.conf /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$AIO_BUILDS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$AIO_DOMAIN_NAME|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$AIO_UPLOAD_PORT|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN ln -s /etc/nginx/sites-available/aio-builds-prod.conf /etc/nginx/sites-enabled/aio-builds-prod.conf
COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-prod.conf
COPY nginx/aio-builds.conf /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$TEST_AIO_DOMAIN_NAME|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$TEST_AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$TEST_AIO_UPLOAD_PORT|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN ln -s /etc/nginx/sites-available/aio-builds-test.conf /etc/nginx/sites-enabled/aio-builds-test.conf
COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$TEST_AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$TEST_AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$TEST_AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-test.conf
# Set up pm2
@ -151,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
# Set up the Node.js scripts
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
WORKDIR $AIO_SCRIPTS_JS_DIR/
RUN yarn install --production
RUN yarn install --production --freeze-lockfile
# Set up health check

View File

@ -6,7 +6,7 @@ server=8.8.4.4
# Listen for DHCP and DNS requests only on this address.
listen-address=127.0.0.1
# Force an IP addres for these domains.
# Force an IP address for these domains.
address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1
address=/{{$AIO_UPLOAD_HOSTNAME}}/127.0.0.1
address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1

View File

@ -15,18 +15,24 @@ server {
# Serve PR-preview requests
server {
server_name "~^pr(?<pr>[1-9][0-9]*)-(?<sha>[0-9a-f]{40})\.";
server_name "~^pr(?<pr>[1-9][0-9]*)-(?<sha>[0-9a-f]{7,40})\.";
listen {{$AIO_NGINX_PORT_HTTPS}} ssl;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl;
listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
root {{$AIO_BUILDS_DIR}}/$pr/$sha;
disable_symlinks on from=$document_root;
index index.html;
gzip on;
gzip_comp_level 7;
gzip_types *;
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
@ -43,11 +49,13 @@ server {
server {
server_name _;
listen {{$AIO_NGINX_PORT_HTTPS}} ssl default_server;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl;
listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2 default_server;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
@ -80,6 +88,21 @@ server {
resolver 127.0.0.1;
}
# Notify about PR changes
location "~^/pr-updated/?$" {
if ($request_method != "POST") {
add_header Allow "POST";
return 405;
}
proxy_pass_request_headers on;
proxy_redirect off;
proxy_method POST;
proxy_pass http://{{$AIO_UPLOAD_HOSTNAME}}:{{$AIO_UPLOAD_PORT}}$request_uri;
resolver 127.0.0.1;
}
# Everything else
location / {
return 404;

View File

@ -2,6 +2,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as shell from 'shelljs';
import {HIDDEN_DIR_PREFIX} from '../common/constants';
import {GithubPullRequests} from '../common/github-pull-requests';
import {assertNotMissingOrEmpty} from '../common/utils';
@ -31,8 +32,9 @@ export class BuildCleaner {
}
const buildNumbers = files.
map(Number). // Convert string to number
filter(Boolean); // Ignore NaN (or 0), because they are not builds
map(name => name.replace(HIDDEN_DIR_PREFIX, '')). // Remove the "hidden dir" prefix
map(Number). // Convert string to number
filter(Boolean); // Ignore NaN (or 0), because they are not builds
resolve(buildNumbers);
});
@ -49,9 +51,11 @@ export class BuildCleaner {
protected removeDir(dir: string) {
try {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
(shell as any).chmod('-R', 'a+w', dir);
shell.rm('-rf', dir);
if (shell.test('-d', dir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
(shell as any).chmod('-R', 'a+w', dir);
shell.rm('-rf', dir);
}
} catch (err) {
console.error(`ERROR: Unable to remove '${dir}' due to:`, err);
}
@ -64,8 +68,14 @@ export class BuildCleaner {
console.log(`Open pull requests: ${openPrNumbers.length}`);
console.log(`Removing ${toRemove.length} build(s): ${toRemove.join(', ')}`);
// Try removing public dirs.
toRemove.
map(num => path.join(this.buildsDir, String(num))).
forEach(dir => this.removeDir(dir));
// Try removing hidden dirs.
toRemove.
map(num => path.join(this.buildsDir, HIDDEN_DIR_PREFIX + String(num))).
forEach(dir => this.removeDir(dir));
}
}

View File

@ -0,0 +1,3 @@
// Constants
export const HIDDEN_DIR_PREFIX = 'hidden--';
export const SHORT_SHA_LEN = 7;

View File

@ -63,7 +63,7 @@ export class GithubApi {
return items;
}
return this.getPaginated(pathname, baseParams, currentPage + 1).then(moreItems => [...items, ...moreItems]);
return this.getPaginated<T>(pathname, baseParams, currentPage + 1).then(moreItems => [...items, ...moreItems]);
});
}

View File

@ -6,6 +6,7 @@ import {GithubApi} from './github-api';
export interface PullRequest {
number: number;
user: {login: string};
labels: {name: string}[];
}
export type PullRequestState = 'all' | 'closed' | 'open';
@ -30,7 +31,8 @@ export class GithubPullRequests extends GithubApi {
}
public fetch(pr: number): Promise<PullRequest> {
return this.get<PullRequest>(`/repos/${this.repoSlug}/pulls/${pr}`);
// Using the `/issues/` URL, because the `/pulls/` one does not provide labels.
return this.get<PullRequest>(`/repos/${this.repoSlug}/issues/${pr}`);
}
public fetchAll(state: PullRequestState = 'all'): Promise<PullRequest[]> {

View File

@ -4,8 +4,9 @@ import {EventEmitter} from 'events';
import * as fs from 'fs';
import * as path from 'path';
import * as shell from 'shelljs';
import {HIDDEN_DIR_PREFIX, SHORT_SHA_LEN} from '../common/constants';
import {assertNotMissingOrEmpty} from '../common/utils';
import {CreatedBuildEvent} from './build-events';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
import {UploadError} from './upload-error';
// Classes
@ -17,16 +18,22 @@ export class BuildCreator extends EventEmitter {
}
// Methods - Public
public create(pr: string, sha: string, archivePath: string): Promise<any> {
const prDir = path.join(this.buildsDir, pr);
public create(pr: string, sha: string, archivePath: string, isPublic: boolean): Promise<void> {
// Use only part of the SHA for more readable URLs.
sha = sha.substr(0, SHORT_SHA_LEN);
const {newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
const shaDir = path.join(prDir, sha);
let dirToRemoveOnError: string;
return Promise.
all([this.exists(prDir), this.exists(shaDir)]).
return Promise.resolve().
// If the same PR exists with different visibility, update the visibility first.
then(() => this.updatePrVisibility(pr, isPublic)).
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
then(([prDirExisted, shaDirExisted]) => {
if (shaDirExisted) {
throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`);
const publicOrNot = isPublic ? 'public' : 'non-public';
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
}
dirToRemoveOnError = prDirExisted ? shaDir : prDir;
@ -34,7 +41,8 @@ export class BuildCreator extends EventEmitter {
return Promise.resolve().
then(() => shell.mkdir('-p', shaDir)).
then(() => this.extractArchive(archivePath, shaDir)).
then(() => this.emit(CreatedBuildEvent.type, new CreatedBuildEvent(+pr, sha)));
then(() => this.emit(CreatedBuildEvent.type, new CreatedBuildEvent(+pr, sha, isPublic))).
then(() => undefined);
}).
catch(err => {
if (dirToRemoveOnError) {
@ -49,6 +57,36 @@ export class BuildCreator extends EventEmitter {
});
}
public updatePrVisibility(pr: string, makePublic: boolean): Promise<boolean> {
const {oldPrDir: otherVisPrDir, newPrDir: targetVisPrDir} = this.getCandidatePrDirs(pr, makePublic);
return Promise.
all([this.exists(otherVisPrDir), this.exists(targetVisPrDir)]).
then(([otherVisPrDirExisted, targetVisPrDirExisted]) => {
if (!otherVisPrDirExisted) {
// No visibility change: Either the visibility is up-to-date or the PR does not exist.
return false;
} else if (targetVisPrDirExisted) {
// Error: Directories for both visibilities exist.
throw new UploadError(409, `Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`);
}
// Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`.
return Promise.resolve().
then(() => shell.mv(otherVisPrDir, targetVisPrDir)).
then(() => this.listShasByDate(targetVisPrDir)).
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
then(() => true);
}).
catch(err => {
if (!(err instanceof UploadError)) {
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
}
throw err;
});
}
// Methods - Protected
protected exists(fileOrDir: string): Promise<boolean> {
return new Promise(resolve => fs.access(fileOrDir, err => resolve(!err)));
@ -78,4 +116,26 @@ export class BuildCreator extends EventEmitter {
});
});
}
protected getCandidatePrDirs(pr: string, isPublic: boolean) {
const hiddenPrDir = path.join(this.buildsDir, HIDDEN_DIR_PREFIX + pr);
const publicPrDir = path.join(this.buildsDir, pr);
const oldPrDir = isPublic ? hiddenPrDir : publicPrDir;
const newPrDir = isPublic ? publicPrDir : hiddenPrDir;
return {oldPrDir, newPrDir};
}
protected listShasByDate(inputDir: string): Promise<string[]> {
return Promise.resolve().
then(() => shell.ls('-l', inputDir) as any as Promise<(fs.Stats & {name: string})[]>).
// Keep directories only.
// (Also, convert to standard Array - ShellJS provides custom `sort()` method for sorting file contents.)
then(items => items.filter(item => item.isDirectory())).
// Sort by modification date.
then(items => items.sort((a, b) => a.mtime.getTime() - b.mtime.getTime())).
// Return directory names.
then(items => items.map(item => item.name));
}
}

View File

@ -1,15 +1,16 @@
// Classes
export class BuildEvent {
export class ChangedPrVisibilityEvent {
// Properties - Public, Static
public static type = 'pr.changedVisibility';
// Constructor
constructor(public type: string, public pr: number, public sha: string) {}
constructor(public pr: number, public shas: string[], public isPublic: boolean) {}
}
export class CreatedBuildEvent extends BuildEvent {
export class CreatedBuildEvent {
// Properties - Public, Static
public static type = 'build.created';
// Constructor
constructor(pr: number, sha: string) {
super(CreatedBuildEvent.type, pr, sha);
}
constructor(public pr: number, public sha: string, public isPublic: boolean) {}
}

View File

@ -1,6 +1,6 @@
// Imports
import * as jwt from 'jsonwebtoken';
import {GithubPullRequests} from '../common/github-pull-requests';
import {GithubPullRequests, PullRequest} from '../common/github-pull-requests';
import {GithubTeams} from '../common/github-teams';
import {assertNotMissingOrEmpty} from '../common/utils';
import {UploadError} from './upload-error';
@ -11,6 +11,12 @@ interface JwtPayload {
'pull-request': number;
}
// Enums
export enum BUILD_VERIFICATION_STATUS {
verifiedAndTrusted,
verifiedNotTrusted,
}
// Classes
export class BuildVerifier {
// Properties - Protected
@ -19,27 +25,27 @@ export class BuildVerifier {
// Constructor
constructor(protected secret: string, githubToken: string, protected repoSlug: string, organization: string,
protected allowedTeamSlugs: string[]) {
protected allowedTeamSlugs: string[], protected trustedPrLabel: string) {
assertNotMissingOrEmpty('secret', secret);
assertNotMissingOrEmpty('githubToken', githubToken);
assertNotMissingOrEmpty('repoSlug', repoSlug);
assertNotMissingOrEmpty('organization', organization);
assertNotMissingOrEmpty('allowedTeamSlugs', allowedTeamSlugs && allowedTeamSlugs.join(''));
assertNotMissingOrEmpty('trustedPrLabel', trustedPrLabel);
this.githubPullRequests = new GithubPullRequests(githubToken, repoSlug);
this.githubTeams = new GithubTeams(githubToken, organization);
}
// Methods - Public
public getPrAuthorTeamMembership(pr: number): Promise<{author: string, isMember: boolean}> {
public getPrIsTrusted(pr: number): Promise<boolean> {
return Promise.resolve().
then(() => this.githubPullRequests.fetch(pr)).
then(prInfo => prInfo.user.login).
then(author => this.githubTeams.isMemberBySlug(author, this.allowedTeamSlugs).
then(isMember => ({author, isMember})));
then(prInfo => this.hasLabel(prInfo, this.trustedPrLabel) ||
this.githubTeams.isMemberBySlug(prInfo.user.login, this.allowedTeamSlugs));
}
public verify(expectedPr: number, authHeader: string): Promise<void> {
public verify(expectedPr: number, authHeader: string): Promise<BUILD_VERIFICATION_STATUS> {
return Promise.resolve().
then(() => this.extractJwtString(authHeader)).
then(jwtString => this.verifyJwt(expectedPr, jwtString)).
@ -52,9 +58,13 @@ export class BuildVerifier {
return input.replace(/^token +/i, '');
}
protected hasLabel(prInfo: PullRequest, label: string) {
return prInfo.labels.some(labelObj => labelObj.name === label);
}
protected verifyJwt(expectedPr: number, token: string): Promise<JwtPayload> {
return new Promise((resolve, reject) => {
jwt.verify(token, this.secret, {issuer: 'Travis CI, GmbH'}, (err, payload) => {
jwt.verify(token, this.secret, {issuer: 'Travis CI, GmbH'}, (err, payload: JwtPayload) => {
if (err) {
reject(err.message || err);
} else if (payload.slug !== this.repoSlug) {
@ -68,11 +78,10 @@ export class BuildVerifier {
});
}
protected verifyPr(pr: number): Promise<void> {
return this.getPrAuthorTeamMembership(pr).
then(({author, isMember}) => isMember ? Promise.resolve() : Promise.reject(
`User '${author}' is not an active member of any of the following teams: ` +
`${this.allowedTeamSlugs.join(', ')}`,
));
protected verifyPr(pr: number): Promise<BUILD_VERIFICATION_STATUS> {
return this.getPrIsTrusted(pr).
then(isTrusted => Promise.resolve(isTrusted ?
BUILD_VERIFICATION_STATUS.verifiedAndTrusted :
BUILD_VERIFICATION_STATUS.verifiedNotTrusted));
}
}

View File

@ -12,28 +12,28 @@ function _main() {
const repoSlug = getEnvVar('AIO_REPO_SLUG');
const organization = getEnvVar('AIO_GITHUB_ORGANIZATION');
const allowedTeamSlugs = getEnvVar('AIO_GITHUB_TEAM_SLUGS').split(',');
const trustedPrLabel = getEnvVar('AIO_TRUSTED_PR_LABEL');
const pr = +getEnvVar('AIO_PREVERIFY_PR');
const buildVerifier = new BuildVerifier(secret, githubToken, repoSlug, organization, allowedTeamSlugs);
const buildVerifier = new BuildVerifier(secret, githubToken, repoSlug, organization, allowedTeamSlugs,
trustedPrLabel);
// Exit codes:
// - 0: The PR author is a member.
// - 1: The PR author is not a member.
// - 2: An error occurred.
buildVerifier.getPrAuthorTeamMembership(pr).
then(({author, isMember}) => {
if (isMember) {
process.exit(0);
} else {
const errorMessage = `User '${author}' is not an active member of any of the following teams: ` +
`${allowedTeamSlugs.join(', ')}`;
onError(errorMessage, 1);
// - 0: The PR can be automatically trusted (i.e. author belongs to trusted team or PR has the "trusted PR" label).
// - 1: An error occurred.
// - 2: The PR cannot be automatically trusted.
buildVerifier.getPrIsTrusted(pr).
then(isTrusted => {
if (!isTrusted) {
console.warn(
`The PR cannot be automatically verified, because it doesn't have the "${trustedPrLabel}" label and the ` +
`the author is not an active member of any of the following teams: ${allowedTeamSlugs.join(', ')}`);
}
}).
catch(err => onError(err, 2));
}
function onError(err: string, exitCode: number) {
console.error(err);
process.exit(exitCode || 1);
process.exit(isTrusted ? 0 : 2);
}).
catch(err => {
console.error(err);
process.exit(1);
});
}

View File

@ -1,10 +0,0 @@
// Imports
import {GithubPullRequests} from '../common/github-pull-requests';
import {BuildVerifier} from './build-verifier';
// Run
// TODO(gkalpak): Add e2e tests to cover these interactions as well.
GithubPullRequests.prototype.addComment = () => Promise.resolve();
BuildVerifier.prototype.verify = () => Promise.resolve();
// tslint:disable-next-line: no-var-requires
require('./index');

View File

@ -1,6 +1,3 @@
// TODO(gkalpak): Find more suitable way to run as `www-data`.
process.setuid('www-data');
// Imports
import {getEnvVar} from '../common/utils';
import {uploadServerFactory} from './upload-server-factory';
@ -13,6 +10,7 @@ const AIO_GITHUB_TEAM_SLUGS = getEnvVar('AIO_GITHUB_TEAM_SLUGS');
const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN');
const AIO_PREVIEW_DEPLOYMENT_TOKEN = getEnvVar('AIO_PREVIEW_DEPLOYMENT_TOKEN');
const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG');
const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL');
const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME');
const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT');
@ -30,6 +28,7 @@ function _main() {
githubToken: AIO_GITHUB_TOKEN,
repoSlug: AIO_REPO_SLUG,
secret: AIO_PREVIEW_DEPLOYMENT_TOKEN,
trustedPrLabel: AIO_TRUSTED_PR_LABEL,
}).
listen(AIO_UPLOAD_PORT, AIO_UPLOAD_HOSTNAME);
}

View File

@ -1,11 +1,12 @@
// Imports
import * as bodyParser from 'body-parser';
import * as express from 'express';
import * as http from 'http';
import {GithubPullRequests} from '../common/github-pull-requests';
import {assertNotMissingOrEmpty} from '../common/utils';
import {BuildCreator} from './build-creator';
import {CreatedBuildEvent} from './build-events';
import {BuildVerifier} from './build-verifier';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from './build-verifier';
import {UploadError} from './upload-error';
// Constants
@ -21,6 +22,7 @@ interface UploadServerConfig {
githubToken: string;
repoSlug: string;
secret: string;
trustedPrLabel: string;
}
// Classes
@ -34,14 +36,16 @@ class UploadServerFactory {
githubToken,
repoSlug,
secret,
trustedPrLabel,
}: UploadServerConfig): http.Server {
assertNotMissingOrEmpty('domainName', domainName);
const buildVerifier = new BuildVerifier(secret, githubToken, repoSlug, githubOrganization, githubTeamSlugs);
const buildVerifier = new BuildVerifier(secret, githubToken, repoSlug, githubOrganization, githubTeamSlugs,
trustedPrLabel);
const buildCreator = this.createBuildCreator(buildsDir, githubToken, repoSlug, domainName);
const middleware = this.createMiddleware(buildVerifier, buildCreator);
const httpServer = http.createServer(middleware);
const httpServer = http.createServer(middleware as any);
httpServer.on('listening', () => {
const info = httpServer.address();
@ -56,12 +60,24 @@ class UploadServerFactory {
domainName: string): BuildCreator {
const buildCreator = new BuildCreator(buildsDir);
const githubPullRequests = new GithubPullRequests(githubToken, repoSlug);
const postPreviewsComment = (pr: number, shas: string[]) => {
const body = shas.
map(sha => `You can preview ${sha} at https://pr${pr}-${sha}.${domainName}/.`).
join('\n');
buildCreator.on(CreatedBuildEvent.type, ({pr, sha}: CreatedBuildEvent) => {
const body = `The angular.io preview for ${sha} is available [here][1].\n\n` +
`[1]: https://pr${pr}-${sha}.${domainName}/`;
return githubPullRequests.addComment(pr, body);
};
githubPullRequests.addComment(pr, body);
buildCreator.on(CreatedBuildEvent.type, ({pr, sha, isPublic}: CreatedBuildEvent) => {
if (isPublic) {
postPreviewsComment(pr, [sha]);
}
});
buildCreator.on(ChangedPrVisibilityEvent.type, ({pr, shas, isPublic}: ChangedPrVisibilityEvent) => {
if (isPublic && shas.length) {
postPreviewsComment(pr, shas);
}
});
return buildCreator;
@ -69,6 +85,7 @@ class UploadServerFactory {
protected createMiddleware(buildVerifier: BuildVerifier, buildCreator: BuildCreator): express.Express {
const middleware = express();
const jsonParser = bodyParser.json();
middleware.get(/^\/create-build\/([1-9][0-9]*)\/([0-9a-f]{40})\/?$/, (req, res) => {
const pr = req.params[0];
@ -80,17 +97,33 @@ class UploadServerFactory {
this.throwRequestError(401, `Missing or empty '${AUTHORIZATION_HEADER}' header`, req);
} else if (!archive) {
this.throwRequestError(400, `Missing or empty '${X_FILE_HEADER}' header`, req);
} else {
Promise.resolve().
then(() => buildVerifier.verify(+pr, authHeader)).
then(verStatus => verStatus === BUILD_VERIFICATION_STATUS.verifiedAndTrusted).
then(isPublic => buildCreator.create(pr, sha, archive, isPublic).
then(() => res.sendStatus(isPublic ? 201 : 202))).
catch(err => this.respondWithError(res, err));
}
buildVerifier.
verify(+pr, authHeader).
then(() => buildCreator.create(pr, sha, archive)).
then(() => res.sendStatus(201)).
catch(err => this.respondWithError(res, err));
});
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
middleware.get('*', req => this.throwRequestError(404, 'Unknown resource', req));
middleware.all('*', req => this.throwRequestError(405, 'Unsupported method', req));
middleware.post(/^\/pr-updated\/?$/, jsonParser, (req, res) => {
const {action, number: prNo}: {action?: string, number?: number} = req.body;
const visMayHaveChanged = !action || (action === 'labeled') || (action === 'unlabeled');
if (!visMayHaveChanged) {
res.sendStatus(200);
} else if (!prNo) {
this.throwRequestError(400, `Missing or empty 'number' field`, req);
} else {
Promise.resolve().
then(() => buildVerifier.getPrIsTrusted(prNo)).
then(isPublic => buildCreator.updatePrVisibility(String(prNo), isPublic)).
then(() => res.sendStatus(200)).
catch(err => this.respondWithError(res, err));
}
});
middleware.all('*', req => this.throwRequestError(404, 'Unknown resource', req));
middleware.use((err: any, _req: any, res: express.Response, _next: any) => this.respondWithError(res, err));
return middleware;
@ -109,7 +142,10 @@ class UploadServerFactory {
}
protected throwRequestError(status: number, error: string, req: express.Request) {
throw new UploadError(status, `${error} in request: ${req.method} ${req.originalUrl}`);
const message = `${error} in request: ${req.method} ${req.originalUrl}` +
(!req.body ? '' : ` ${JSON.stringify(req.body)}`);
throw new UploadError(status, message);
}
}

View File

@ -0,0 +1,16 @@
// Using the values below, we can fake the response of the corresponding methods in tests. This is
// necessary, because the test upload-server will be running as a separate node process, so we will
// not have direct access to the code (e.g. for mocking).
// (See also 'lib/verify-setup/start-test-upload-server.ts'.)
/* tslint:disable: variable-name */
// Special values to be used as `authHeader` in `BuildVerifier#verify()`.
export const BV_verify_error = 'FAKE_VERIFICATION_ERROR';
export const BV_verify_verifiedNotTrusted = 'FAKE_VERIFIED_NOT_TRUSTED';
// Special values to be used as `pr` in `BuildVerifier#getPrIsTrusted()`.
export const BV_getPrIsTrusted_error = 32203;
export const BV_getPrIsTrusted_notTrusted = 72457;
/* tslint:enable: variable-name */

View File

@ -4,10 +4,10 @@ import * as fs from 'fs';
import * as http from 'http';
import * as path from 'path';
import * as shell from 'shelljs';
import {HIDDEN_DIR_PREFIX, SHORT_SHA_LEN} from '../common/constants';
import {getEnvVar} from '../common/utils';
// Constans
const SERVER_USER = 'www-data';
const TEST_AIO_BUILDS_DIR = getEnvVar('TEST_AIO_BUILDS_DIR');
const TEST_AIO_NGINX_HOSTNAME = getEnvVar('TEST_AIO_NGINX_HOSTNAME');
const TEST_AIO_NGINX_PORT_HTTP = +getEnvVar('TEST_AIO_NGINX_PORT_HTTP');
@ -15,9 +15,10 @@ const TEST_AIO_NGINX_PORT_HTTPS = +getEnvVar('TEST_AIO_NGINX_PORT_HTTPS');
const TEST_AIO_UPLOAD_HOSTNAME = getEnvVar('TEST_AIO_UPLOAD_HOSTNAME');
const TEST_AIO_UPLOAD_MAX_SIZE = +getEnvVar('TEST_AIO_UPLOAD_MAX_SIZE');
const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT');
const WWW_USER = getEnvVar('AIO_WWW_USER');
// Interfaces - Types
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
export interface FileSpecs { content?: string; size?: number; }
export type CleanUpFn = () => void;
@ -31,10 +32,10 @@ class Helper {
public get nginxHostname() { return TEST_AIO_NGINX_HOSTNAME; }
public get nginxPortHttp() { return TEST_AIO_NGINX_PORT_HTTP; }
public get nginxPortHttps() { return TEST_AIO_NGINX_PORT_HTTPS; }
public get serverUser() { return SERVER_USER; }
public get uploadHostname() { return TEST_AIO_UPLOAD_HOSTNAME; }
public get uploadPort() { return TEST_AIO_UPLOAD_PORT; }
public get uploadMaxSize() { return TEST_AIO_UPLOAD_MAX_SIZE; }
public get wwwUser() { return WWW_USER; }
// Properties - Protected
protected cleanUpFns: CleanUpFn[] = [];
@ -46,10 +47,16 @@ class Helper {
// Constructor
constructor() {
shell.mkdir('-p', this.buildsDir);
shell.exec(`chown -R ${this.serverUser} ${this.buildsDir}`);
shell.exec(`chown -R ${this.wwwUser} ${this.buildsDir}`);
}
// Methods - Public
public buildExists(pr: string, sha = '', isPublic = true, legacy = false): boolean {
const prDir = this.getPrDir(pr, isPublic);
const dir = !sha ? prDir : this.getShaDir(prDir, sha, legacy);
return fs.existsSync(dir);
}
public cleanUp() {
while (this.cleanUpFns.length) {
// Clean-up fns remove themselves from the list.
@ -62,11 +69,11 @@ class Helper {
}
public createDummyArchive(pr: string, sha: string, archivePath: string): CleanUpFn {
const inputDir = path.join(this.buildsDir, 'uploaded', pr, sha);
const inputDir = this.getShaDir(this.getPrDir(`uploaded/${pr}`, true), sha);
const cmd1 = `tar --create --gzip --directory "${inputDir}" --file "${archivePath}" .`;
const cmd2 = `chown ${this.serverUser} ${archivePath}`;
const cmd2 = `chown ${this.wwwUser} ${archivePath}`;
const cleanUpTemp = this.createDummyBuild(`uploaded/${pr}`, sha, true);
const cleanUpTemp = this.createDummyBuild(`uploaded/${pr}`, sha, true, true);
shell.exec(cmd1);
shell.exec(cmd2);
cleanUpTemp();
@ -74,21 +81,21 @@ class Helper {
return this.createCleanUpFn(() => shell.rm('-rf', archivePath));
}
public createDummyBuild(pr: string, sha: string, force = false): CleanUpFn {
const prDir = path.join(this.buildsDir, pr);
const shaDir = path.join(prDir, sha);
public createDummyBuild(pr: string, sha: string, isPublic = true, force = false, legacy = false): CleanUpFn {
const prDir = this.getPrDir(pr, isPublic);
const shaDir = this.getShaDir(prDir, sha, legacy);
const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js');
this.writeFile(idxPath, {content: `PR: ${pr} | SHA: ${sha} | File: /index.html`}, force);
this.writeFile(barPath, {content: `PR: ${pr} | SHA: ${sha} | File: /foo/bar.js`}, force);
shell.exec(`chown -R ${this.serverUser} ${prDir}`);
shell.exec(`chown -R ${this.wwwUser} ${prDir}`);
return this.createCleanUpFn(() => shell.rm('-rf', prDir));
}
public deletePrDir(pr: string) {
const prDir = path.join(this.buildsDir, pr);
public deletePrDir(pr: string, isPublic = true) {
const prDir = this.getPrDir(pr, isPublic);
if (fs.existsSync(prDir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
@ -97,8 +104,22 @@ class Helper {
}
}
public readBuildFile(pr: string, sha: string, relFilePath: string): string {
const absFilePath = path.join(this.buildsDir, pr, sha, relFilePath);
public getPrDir(pr: string, isPublic: boolean): string {
const prDirName = isPublic ? pr : HIDDEN_DIR_PREFIX + pr;
return path.join(this.buildsDir, prDirName);
}
public getShaDir(prDir: string, sha: string, legacy = false): string {
return path.join(prDir, legacy ? sha : this.getShordSha(sha));
}
public getShordSha(sha: string): string {
return sha.substr(0, SHORT_SHA_LEN);
}
public readBuildFile(pr: string, sha: string, relFilePath: string, isPublic = true, legacy = false): string {
const shaDir = this.getShaDir(this.getPrDir(pr, isPublic), sha, legacy);
const absFilePath = path.join(shaDir, relFilePath);
return fs.readFileSync(absFilePath, 'utf8');
}
@ -122,14 +143,15 @@ class Helper {
statusText = status[1];
} else {
statusCode = status;
statusText = http.STATUS_CODES[statusCode];
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
}
return (result: CmdResult) => {
const [headers, body] = result.stdout.
split(/(?:\r?\n){2,}/).
map(s => s.trim()).
slice(-2);
slice(-2); // In case of redirect, discard the previous headers.
// Only keep the last to sections (final headers and body).
if (!result.success) {
console.log('Stdout:', result.stdout);
@ -143,8 +165,10 @@ class Helper {
};
}
public writeBuildFile(pr: string, sha: string, relFilePath: string, content: string): CleanUpFn {
const absFilePath = path.join(this.buildsDir, pr, sha, relFilePath);
public writeBuildFile(pr: string, sha: string, relFilePath: string, content: string, isPublic = true,
legacy = false): CleanUpFn {
const shaDir = this.getShaDir(this.getPrDir(pr, isPublic), sha, legacy);
const absFilePath = path.join(shaDir, relFilePath);
return this.writeFile(absFilePath, {content}, true);
}
@ -166,13 +190,13 @@ class Helper {
// Create a file with the specified content.
fs.writeFileSync(filePath, content || '');
}
shell.exec(`chown ${this.serverUser} ${filePath}`);
shell.exec(`chown ${this.wwwUser} ${filePath}`);
return this.createCleanUpFn(() => shell.rm('-rf', cleanUpTarget));
}
// Methods - Protected
protected createCleanUpFn(fn: Function): CleanUpFn {
protected createCleanUpFn(fn: () => void): CleanUpFn {
const cleanUpFn = () => {
const idx = this.cleanUpFns.indexOf(cleanUpFn);
if (idx !== -1) {

View File

@ -31,111 +31,184 @@ describe(`nginx`, () => {
});
h.runForAllSupportedSchemes((scheme, port) => describe(`nginx (on ${scheme.toUpperCase()})`, () => {
h.runForAllSupportedSchemes((scheme, port) => describe(`(on ${scheme.toUpperCase()})`, () => {
const hostname = h.nginxHostname;
const host = `${hostname}:${port}`;
const pr = '9';
const sha9 = '9'.repeat(40);
const sha0 = '0'.repeat(40);
const shortSha9 = h.getShordSha(sha9);
const shortSha0 = h.getShordSha(sha0);
describe(`pr<pr>-<sha>.${host}/*`, () => {
beforeEach(() => {
h.createDummyBuild(pr, sha9);
h.createDummyBuild(pr, sha0);
describe('(for public builds)', () => {
beforeEach(() => {
h.createDummyBuild(pr, sha9);
h.createDummyBuild(pr, sha0);
});
it('should return /index.html', done => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)),
]).then(done);
});
it('should return /index.html (for legacy builds)', done => {
const origin = `${scheme}://pr${pr}-${sha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
h.createDummyBuild(pr, sha9, true, false, true);
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)),
]).then(done);
});
it('should return /foo/bar.js', done => {
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`);
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex)).
then(done);
});
it('should return /foo/bar.js (for legacy builds)', done => {
const origin = `${scheme}://pr${pr}-${sha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`);
h.createDummyBuild(pr, sha9, true, false, true);
h.runCmd(`curl -iL ${origin}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex)).
then(done);
});
it('should respond with 403 for directories', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/`).then(h.verifyResponse(403)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo`).then(h.verifyResponse(403)),
]).then(done);
});
it('should respond with 404 for unknown paths to files', done => {
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`).
then(h.verifyResponse(404)).
then(done);
});
it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', done => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
Promise.all([
h.runCmd(`curl -iL ${origin}/foo/baz`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)),
]).then(done);
});
it('should respond with 404 for unknown PRs/SHAs', done => {
const otherPr = 54321;
const otherShortSha = h.getShordSha('8'.repeat(40));
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}9-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}9.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherShortSha}.${host}`).then(h.verifyResponse(404)),
]).then(done);
});
it('should respond with 404 if the subdomain format is wrong', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://xpr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://prx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://xx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://p${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://r${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}_${shortSha9}.${host}`).then(h.verifyResponse(404)),
]).then(done);
});
it('should reject PRs with leading zeros', done => {
h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`).
then(h.verifyResponse(404)).
then(done);
});
it('should accept SHAs with leading zeros (but not trim the zeros)', done => {
const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`);
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-0${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)),
]).then(done);
});
});
it('should return /index.html', done => {
const origin = `${scheme}://pr${pr}-${sha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
describe('(for hidden builds)', () => {
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)),
]).then(done);
});
it('should respond with 404 for any file or directory', done => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const assert404 = h.verifyResponse(404);
h.createDummyBuild(pr, sha9, false);
expect(h.buildExists(pr, sha9, false)).toBe(true);
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(assert404),
h.runCmd(`curl -iL ${origin}/`).then(assert404),
h.runCmd(`curl -iL ${origin}`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo/bar.js`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo/`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo`).then(assert404),
]).then(done);
});
it('should return /foo/bar.js', done => {
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`);
it('should respond with 404 for any file or directory (for legacy builds)', done => {
const origin = `${scheme}://pr${pr}-${sha9}.${host}`;
const assert404 = h.verifyResponse(404);
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex)).
then(done);
});
h.createDummyBuild(pr, sha9, false, false, true);
expect(h.buildExists(pr, sha9, false, true)).toBe(true);
Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(assert404),
h.runCmd(`curl -iL ${origin}/`).then(assert404),
h.runCmd(`curl -iL ${origin}`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo/bar.js`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo/`).then(assert404),
h.runCmd(`curl -iL ${origin}/foo`).then(assert404),
]).then(done);
});
it('should respond with 403 for directories', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo/`).then(h.verifyResponse(403)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo`).then(h.verifyResponse(403)),
]).then(done);
});
it('should respond with 404 for unknown paths to files', done => {
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo/baz.css`).
then(h.verifyResponse(404)).
then(done);
});
it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', done => {
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo/baz`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)),
]).then(done);
});
it('should respond with 404 for unknown PRs/SHAs', done => {
const otherPr = 54321;
const otherSha = '8'.repeat(40);
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}9-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}9.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherSha}.${host}`).then(h.verifyResponse(404)),
]).then(done);
});
it('should respond with 404 if the subdomain format is wrong', done => {
Promise.all([
h.runCmd(`curl -iL ${scheme}://xpr${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://prx${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://xx${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://p${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://r${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${pr}-${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}_${sha9}.${host}`).then(h.verifyResponse(404)),
]).then(done);
});
it('should reject PRs with leading zeros', done => {
h.runCmd(`curl -iL ${scheme}://pr0${pr}-${sha9}.${host}`).
then(h.verifyResponse(404)).
then(done);
});
it('should accept SHAs with leading zeros (but not trim the zeros)', done => {
const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`);
Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-0${sha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)),
]).then(done);
});
});
@ -244,9 +317,54 @@ describe(`nginx`, () => {
});
describe(`${host}/pr-updated`, () => {
const url = `${scheme}://${host}/pr-updated`;
it('should disallow non-POST requests', done => {
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
]).then(done);
});
it('should pass requests through to the upload server', done => {
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`;
const cmd1 = `${cmdPrefix} ${url}`;
const cmd2 = `${cmdPrefix} --data '{"number":${pr}}' ${url}`;
const cmd3 = `${cmdPrefix} --data '{"number":${pr},"action":"foo"}' ${url}`;
Promise.all([
h.runCmd(cmd1).then(h.verifyResponse(400, /Missing or empty 'number' field/)),
h.runCmd(cmd2).then(h.verifyResponse(200)),
h.runCmd(cmd3).then(h.verifyResponse(200)),
]).then(done);
});
it('should respond with 404 for unknown paths', done => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done);
});
});
describe(`${host}/*`, () => {
it('should respond with 404 for unkown URLs (even if the resource exists)', done => {
it('should respond with 404 for unknown URLs (even if the resource exists)', done => {
['index.html', 'foo.js', 'foo/index.html'].forEach(relFilePath => {
const absFilePath = path.join(h.buildsDir, relFilePath);
h.writeFile(absFilePath, {content: `File: /${relFilePath}`});

View File

@ -1,5 +1,6 @@
// Imports
import * as path from 'path';
import * as c from './constants';
import {helper as h} from './helper';
// Tests
@ -12,73 +13,307 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
const archivePath = path.join(h.buildsDir, 'snapshot.tar.gz');
const getFile = (pr: string, sha: string, file: string) =>
h.runCmd(`curl -iL ${scheme}://pr${pr}-${sha}.${host}/${file}`);
const uploadBuild = (pr: string, sha: string, archive: string) => {
const curlPost = 'curl -iLX POST --header "Authorization: Token FOO"';
h.runCmd(`curl -iL ${scheme}://pr${pr}-${h.getShordSha(sha)}.${host}/${file}`);
const uploadBuild = (pr: string, sha: string, archive: string, authHeader = 'Token FOO') => {
const curlPost = `curl -iLX POST --header "Authorization: ${authHeader}"`;
return h.runCmd(`${curlPost} --data-binary "@${archive}" ${scheme}://${host}/create-build/${pr}/${sha}`);
};
const prUpdated = (pr: number, action?: string) => {
const url = `${scheme}://${host}/pr-updated`;
const payloadStr = JSON.stringify({number: pr, action});
return h.runCmd(`curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`);
};
beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000);
afterEach(() => {
h.deletePrDir(pr9);
h.deletePrDir(pr9, false);
h.cleanUp();
});
it('should be able to upload and serve a build for a new PR', done => {
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
describe('for a new/non-existing PR', () => {
h.createDummyArchive(pr9, sha9, archivePath);
it('should be able to upload and serve a public build', done => {
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath).
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
it('should be able to upload but not serve a hidden build', done => {
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
])).
then(() => {
expect(h.buildExists(pr9, sha9)).toBe(false);
expect(h.buildExists(pr9, sha9, false)).toBe(true);
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
expect(h.readBuildFile(pr9, sha9, 'foo/bar.js', false)).toMatch(barContentRegex9);
}).
then(done);
});
it('should reject an upload if verification fails', done => {
const errorRegex9 = new RegExp(`Error while verifying upload for PR ${pr9}: Test`);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
then(h.verifyResponse(403, errorRegex9)).
then(() => {
expect(h.buildExists(pr9)).toBe(false);
expect(h.buildExists(pr9, '', false)).toBe(false);
}).
then(done);
});
it('should be able to notify that a PR has been updated (and do nothing)', done => {
prUpdated(+pr9).
then(h.verifyResponse(200)).
then(() => {
// The PR should still not exist.
expect(h.buildExists(pr9, '', false)).toBe(false);
expect(h.buildExists(pr9, '', true)).toBe(false);
}).
then(done);
});
uploadBuild(pr9, sha9, archivePath).
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
it('should be able to upload and serve a build for an existing PR', done => {
const regexPrefix0 = `^PR: ${pr9} \\| SHA: ${sha0} \\| File:`;
const idxContentRegex0 = new RegExp(`${regexPrefix0} \\/index\\.html$`);
const barContentRegex0 = new RegExp(`${regexPrefix0} \\/foo\\/bar\\.js$`);
describe('for an existing PR', () => {
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
it('should be able to upload and serve a public build', done => {
const regexPrefix0 = `^PR: ${pr9} \\| SHA: ${sha0} \\| File:`;
const idxContentRegex0 = new RegExp(`${regexPrefix0} \\/index\\.html$`);
const barContentRegex0 = new RegExp(`${regexPrefix0} \\/foo\\/bar\\.js$`);
h.createDummyBuild(pr9, sha0);
h.createDummyArchive(pr9, sha9, archivePath);
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
uploadBuild(pr9, sha9, archivePath).
then(() => Promise.all([
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(200, idxContentRegex0)),
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex0)),
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
h.createDummyBuild(pr9, sha0);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath).
then(() => Promise.all([
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(200, idxContentRegex0)),
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex0)),
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
it('should not be able to overwrite a build', done => {
const regexPrefix9 = `^PR: ${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
it('should be able to upload but not serve a hidden build', done => {
const regexPrefix0 = `^PR: ${pr9} \\| SHA: ${sha0} \\| File:`;
const idxContentRegex0 = new RegExp(`${regexPrefix0} \\/index\\.html$`);
const barContentRegex0 = new RegExp(`${regexPrefix0} \\/foo\\/bar\\.js$`);
h.createDummyBuild(pr9, sha9);
h.createDummyArchive(pr9, sha9, archivePath);
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
h.createDummyBuild(pr9, sha0, false);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
then(() => Promise.all([
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(404)),
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(404)),
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
])).
then(() => {
expect(h.buildExists(pr9, sha9)).toBe(false);
expect(h.buildExists(pr9, sha9, false)).toBe(true);
expect(h.readBuildFile(pr9, sha0, 'index.html', false)).toMatch(idxContentRegex0);
expect(h.readBuildFile(pr9, sha0, 'foo/bar.js', false)).toMatch(barContentRegex0);
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
expect(h.readBuildFile(pr9, sha9, 'foo/bar.js', false)).toMatch(barContentRegex9);
}).
then(done);
});
it('should reject an upload if verification fails', done => {
const errorRegex9 = new RegExp(`Error while verifying upload for PR ${pr9}: Test`);
h.createDummyBuild(pr9, sha0);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
then(h.verifyResponse(403, errorRegex9)).
then(() => {
expect(h.buildExists(pr9)).toBe(true);
expect(h.buildExists(pr9, sha0)).toBe(true);
expect(h.buildExists(pr9, sha9)).toBe(false);
}).
then(done);
});
it('should not be able to overwrite an existing public build', done => {
const regexPrefix9 = `^PR: ${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
h.createDummyBuild(pr9, sha9);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath).
then(h.verifyResponse(409)).
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
it('should not be able to overwrite an existing hidden build', done => {
const regexPrefix9 = `^PR: ${pr9} \\| SHA: ${sha9} \\| File:`;
const idxContentRegex9 = new RegExp(`${regexPrefix9} \\/index\\.html$`);
const barContentRegex9 = new RegExp(`${regexPrefix9} \\/foo\\/bar\\.js$`);
h.createDummyBuild(pr9, sha9, false);
h.createDummyArchive(pr9, sha9, archivePath);
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
then(h.verifyResponse(409)).
then(() => {
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
expect(h.readBuildFile(pr9, sha9, 'foo/bar.js', false)).toMatch(barContentRegex9);
}).
then(done);
});
it('should be able to request re-checking visibility (if outdated)', done => {
const publicPr = pr9;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
// PR visibilities are outdated (i.e. the opposte of what the should).
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
Promise.
all([
prUpdated(+publicPr).then(h.verifyResponse(200)),
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
]).
then(() => {
// PR visibilities should have been updated.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
}).
then(() => {
h.deletePrDir(publicPr, true);
h.deletePrDir(hiddenPr, false);
}).
then(done);
});
it('should be able to request re-checking visibility (if up-to-date)', done => {
const publicPr = pr9;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
h.createDummyBuild(publicPr, sha9, true);
h.createDummyBuild(hiddenPr, sha9, false);
// PR visibilities are already up-to-date.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
Promise.
all([
prUpdated(+publicPr).then(h.verifyResponse(200)),
prUpdated(+hiddenPr).then(h.verifyResponse(200)),
]).
then(() => {
// PR visibilities are still up-to-date.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
}).
then(done);
});
it('should reject a request if re-checking visibility fails', done => {
const errorPr = String(c.BV_getPrIsTrusted_error);
h.createDummyBuild(errorPr, sha9, true);
expect(h.buildExists(errorPr, '', false)).toBe(false);
expect(h.buildExists(errorPr, '', true)).toBe(true);
prUpdated(+errorPr).
then(h.verifyResponse(500, /Test/)).
then(() => {
// PR visibility should not have been updated.
expect(h.buildExists(errorPr, '', false)).toBe(false);
expect(h.buildExists(errorPr, '', true)).toBe(true);
}).
then(done);
});
it('should reject a request if updating visibility fails', done => {
// One way to cause an error is to have both a public and a hidden directory for the same PR.
h.createDummyBuild(pr9, sha9, false);
h.createDummyBuild(pr9, sha9, true);
const hiddenPrDir = h.getPrDir(pr9, false);
const publicPrDir = h.getPrDir(pr9, true);
const bodyRegex = new RegExp(`Request to move '${hiddenPrDir}' to existing directory '${publicPrDir}'`);
expect(h.buildExists(pr9, '', false)).toBe(true);
expect(h.buildExists(pr9, '', true)).toBe(true);
prUpdated(+pr9).
then(h.verifyResponse(409, bodyRegex)).
then(() => {
// PR visibility should not have been updated.
expect(h.buildExists(pr9, '', false)).toBe(true);
expect(h.buildExists(pr9, '', true)).toBe(true);
}).
then(done);
});
uploadBuild(pr9, sha9, archivePath).
then(h.verifyResponse(409)).
then(() => Promise.all([
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)),
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)),
])).
then(done);
});
}));

View File

@ -0,0 +1,38 @@
// Imports
import {GithubPullRequests} from '../common/github-pull-requests';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from '../upload-server/build-verifier';
import {UploadError} from '../upload-server/upload-error';
import * as c from './constants';
// Run
// TODO(gkalpak): Add e2e tests to cover these interactions as well.
GithubPullRequests.prototype.addComment = () => Promise.resolve();
BuildVerifier.prototype.getPrIsTrusted = (pr: number) => {
switch (pr) {
case c.BV_getPrIsTrusted_error:
// For e2e tests, fake an error.
return Promise.reject('Test');
case c.BV_getPrIsTrusted_notTrusted:
// For e2e tests, fake an untrusted PR (`false`).
return Promise.resolve(false);
default:
// For e2e tests, default to trusted PRs (`true`).
return Promise.resolve(true);
}
};
BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
switch (authHeader) {
case c.BV_verify_error:
// For e2e tests, fake a verification error.
return Promise.reject(new UploadError(403, `Error while verifying upload for PR ${expectedPr}: Test`));
case c.BV_verify_verifiedNotTrusted:
// For e2e tests, fake a `verifiedNotTrusted` verification status.
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
default:
// For e2e tests, default to `verifiedAndTrusted` verification status.
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedAndTrusted);
}
};
// tslint:disable-next-line: no-var-requires
require('../upload-server/index');

View File

@ -1,6 +1,7 @@
// Imports
import * as fs from 'fs';
import * as path from 'path';
import * as c from './constants';
import {CmdResult, helper as h} from './helper';
// Tests
@ -19,18 +20,19 @@ describe('upload-server (on HTTP)', () => {
describe(`${host}/create-build/<pr>/<sha>`, () => {
const authorizationHeader = `--header "Authorization: Token FOO"`;
const xFileHeader = `--header "X-File: ${h.buildsDir}/snapshot.tar.gz"`;
const curl = `curl -iL ${authorizationHeader} ${xFileHeader}`;
const defaultHeaders = `${authorizationHeader} ${xFileHeader}`;
const curl = (url: string, headers = defaultHeaders) => `curl -iL ${headers} ${url}`;
it('should disallow non-GET requests', done => {
const url = `http://${host}/create-build/${pr}/${sha9}`;
const bodyRegex = /^Unsupported method/;
const bodyRegex = /^Unknown resource/;
Promise.all([
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});
@ -42,8 +44,8 @@ describe('upload-server (on HTTP)', () => {
const bodyRegex = /^Missing or empty 'AUTHORIZATION' header/;
Promise.all([
h.runCmd(`curl -iL ${headers1} ${url}`).then(h.verifyResponse(401, bodyRegex)),
h.runCmd(`curl -iL ${headers2} ${url}`).then(h.verifyResponse(401, bodyRegex)),
h.runCmd(curl(url, headers1)).then(h.verifyResponse(401, bodyRegex)),
h.runCmd(curl(url, headers2)).then(h.verifyResponse(401, bodyRegex)),
]).then(done);
});
@ -55,14 +57,25 @@ describe('upload-server (on HTTP)', () => {
const bodyRegex = /^Missing or empty 'X-FILE' header/;
Promise.all([
h.runCmd(`curl -iL ${headers1} ${url}`).then(h.verifyResponse(400, bodyRegex)),
h.runCmd(`curl -iL ${headers2} ${url}`).then(h.verifyResponse(400, bodyRegex)),
h.runCmd(curl(url, headers1)).then(h.verifyResponse(400, bodyRegex)),
h.runCmd(curl(url, headers2)).then(h.verifyResponse(400, bodyRegex)),
]).then(done);
});
it('should reject requests for which the PR verification fails', done => {
const headers = `--header "Authorization: ${c.BV_verify_error}" ${xFileHeader}`;
const url = `http://${host}/create-build/${pr}/${sha9}`;
const bodyRegex = new RegExp(`Error while verifying upload for PR ${pr}: Test`);
h.runCmd(curl(url, headers)).
then(h.verifyResponse(403, bodyRegex)).
then(done);
});
it('should respond with 404 for unknown paths', done => {
const cmdPrefix = `${curl} http://${host}`;
const cmdPrefix = curl(`http://${host}`);
Promise.all([
h.runCmd(`${cmdPrefix}/foo/create-build/${pr}/${sha9}`).then(h.verifyResponse(404)),
@ -78,7 +91,7 @@ describe('upload-server (on HTTP)', () => {
it('should reject PRs with leading zeros', done => {
h.runCmd(`${curl} http://${host}/create-build/0${pr}/${sha9}`).
h.runCmd(curl(`http://${host}/create-build/0${pr}/${sha9}`)).
then(h.verifyResponse(404)).
then(done);
});
@ -86,129 +99,254 @@ describe('upload-server (on HTTP)', () => {
it('should accept SHAs with leading zeros (but not trim the zeros)', done => {
Promise.all([
h.runCmd(`${curl} http://${host}/create-build/${pr}/0${sha9}`).then(h.verifyResponse(404)),
h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`).then(h.verifyResponse(500)),
h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha0}`).then(h.verifyResponse(500)),
h.runCmd(curl(`http://${host}/create-build/${pr}/0${sha9}`)).then(h.verifyResponse(404)),
h.runCmd(curl(`http://${host}/create-build/${pr}/${sha9}`)).then(h.verifyResponse(500)),
h.runCmd(curl(`http://${host}/create-build/${pr}/${sha0}`)).then(h.verifyResponse(500)),
]).then(done);
});
it('should not overwrite existing builds', done => {
h.createDummyBuild(pr, sha9);
expect(h.readBuildFile(pr, sha9, 'index.html')).toContain('index.html');
h.writeBuildFile(pr, sha9, 'index.html', 'My content');
expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content');
h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
then(() => expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content')).
then(done);
});
[true, false].forEach(isPublic => describe(`(for ${isPublic ? 'public' : 'hidden'} builds)`, () => {
const authorizationHeader2 = isPublic ?
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`);
it('should delete the PR directory on error (for new PR)', done => {
const prDir = path.join(h.buildsDir, pr);
it('should not overwrite existing builds', done => {
h.createDummyBuild(pr, sha9, isPublic);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain('index.html');
h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(500)).
then(() => expect(fs.existsSync(prDir)).toBe(false)).
then(done);
});
h.writeBuildFile(pr, sha9, 'index.html', 'My content', isPublic);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
it('should only delete the SHA directory on error (for existing PR)', done => {
const prDir = path.join(h.buildsDir, pr);
const shaDir = path.join(prDir, sha9);
h.createDummyBuild(pr, sha0);
h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(500)).
then(() => {
expect(fs.existsSync(shaDir)).toBe(false);
expect(fs.existsSync(prDir)).toBe(true);
}).
then(done);
});
describe('on successful upload', () => {
const archivePath = path.join(h.buildsDir, 'snapshot.tar.gz');
let uploadPromise: Promise<CmdResult>;
beforeEach(() => {
h.createDummyArchive(pr, sha9, archivePath);
uploadPromise = h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`);
});
afterEach(() => h.deletePrDir(pr));
it('should respond with 201', done => {
uploadPromise.then(h.verifyResponse(201)).then(done);
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(409, overwriteRe)).
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
then(done);
});
it('should extract the contents of the uploaded file', done => {
uploadPromise.
it('should not overwrite existing builds (even if the SHA is different)', done => {
// Since only the first few characters of the SHA are used, it is possible for two different
// SHAs to correspond to the same directory. In that case, we don't want the second SHA to
// overwrite the first.
const sha9Almost = sha9.replace(/.$/, '8');
expect(sha9Almost).not.toBe(sha9);
h.createDummyBuild(pr, sha9, isPublic);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain('index.html');
h.writeBuildFile(pr, sha9, 'index.html', 'My content', isPublic);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9Almost}`).
then(h.verifyResponse(409, overwriteRe)).
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
then(done);
});
it('should delete the PR directory on error (for new PR)', done => {
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(500)).
then(() => expect(h.buildExists(pr, '', isPublic)).toBe(false)).
then(done);
});
it('should only delete the SHA directory on error (for existing PR)', done => {
h.createDummyBuild(pr, sha0, isPublic);
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(500)).
then(() => {
expect(h.readBuildFile(pr, sha9, 'index.html')).toContain(`uploaded/${pr}`);
expect(h.readBuildFile(pr, sha9, 'foo/bar.js')).toContain(`uploaded/${pr}`);
expect(h.buildExists(pr, sha9, isPublic)).toBe(false);
expect(h.buildExists(pr, '', isPublic)).toBe(true);
}).
then(done);
});
it(`should create files/directories owned by '${h.serverUser}'`, done => {
const shaDir = path.join(h.buildsDir, pr, sha9);
const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js');
describe('on successful upload', () => {
const archivePath = path.join(h.buildsDir, 'snapshot.tar.gz');
const statusCode = isPublic ? 201 : 202;
let uploadPromise: Promise<CmdResult>;
beforeEach(() => {
h.createDummyArchive(pr, sha9, archivePath);
uploadPromise = h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`);
});
afterEach(() => h.deletePrDir(pr, isPublic));
it(`should respond with ${statusCode}`, done => {
uploadPromise.then(h.verifyResponse(statusCode)).then(done);
});
it('should extract the contents of the uploaded file', done => {
uploadPromise.
then(() => {
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain(`uploaded/${pr}`);
expect(h.readBuildFile(pr, sha9, 'foo/bar.js', isPublic)).toContain(`uploaded/${pr}`);
}).
then(done);
});
it(`should create files/directories owned by '${h.wwwUser}'`, done => {
const prDir = h.getPrDir(pr, isPublic);
const shaDir = h.getShaDir(prDir, sha9);
const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js');
uploadPromise.
then(() => Promise.all([
h.runCmd(`find ${shaDir}`),
h.runCmd(`find ${shaDir} -user ${h.wwwUser}`),
])).
then(([{stdout: allFiles}, {stdout: userFiles}]) => {
expect(userFiles).toBe(allFiles);
expect(userFiles).toContain(shaDir);
expect(userFiles).toContain(idxPath);
expect(userFiles).toContain(barPath);
}).
then(done);
});
it('should delete the uploaded file', done => {
expect(fs.existsSync(archivePath)).toBe(true);
uploadPromise.
then(() => expect(fs.existsSync(archivePath)).toBe(false)).
then(done);
});
it('should make the build directory non-writable', done => {
const prDir = h.getPrDir(pr, isPublic);
const shaDir = h.getShaDir(prDir, sha9);
const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js');
// See https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588.
const isNotWritable = (fileOrDir: string) => {
const mode = fs.statSync(fileOrDir).mode;
// tslint:disable-next-line: no-bitwise
return !(mode & parseInt('222', 8));
};
uploadPromise.
then(() => {
expect(isNotWritable(shaDir)).toBe(true);
expect(isNotWritable(idxPath)).toBe(true);
expect(isNotWritable(barPath)).toBe(true);
}).
then(done);
});
it('should ignore a legacy 40-chars long build directory (even if it starts with the same chars)', done => {
// It is possible that 40-chars long build directories exist, if they had been deployed
// before implementing the shorter build directory names. In that case, we don't want the
// second (shorter) name to be considered the same as the old one (even if they originate
// from the same SHA).
h.createDummyBuild(pr, sha9, isPublic, false, true);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic, true)).toContain('index.html');
h.writeBuildFile(pr, sha9, 'index.html', 'My content', isPublic, true);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic, true)).toBe('My content');
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(statusCode)).
then(() => {
expect(h.buildExists(pr, sha9, isPublic)).toBe(true);
expect(h.buildExists(pr, sha9, isPublic, true)).toBe(true);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain('index.html');
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic, true)).toBe('My content');
}).
then(done);
});
uploadPromise.
then(() => Promise.all([
h.runCmd(`find ${shaDir}`),
h.runCmd(`find ${shaDir} -user ${h.serverUser}`),
])).
then(([{stdout: allFiles}, {stdout: userFiles}]) => {
expect(userFiles).toBe(allFiles);
expect(userFiles).toContain(shaDir);
expect(userFiles).toContain(idxPath);
expect(userFiles).toContain(barPath);
}).
then(done);
});
it('should delete the uploaded file', done => {
expect(fs.existsSync(archivePath)).toBe(true);
uploadPromise.
then(() => expect(fs.existsSync(archivePath)).toBe(false)).
then(done);
});
describe('when the PR\'s visibility has changed', () => {
const archivePath = path.join(h.buildsDir, 'snapshot.tar.gz');
const statusCode = isPublic ? 201 : 202;
it('should make the build directory non-writable', done => {
const shaDir = path.join(h.buildsDir, pr, sha9);
const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js');
// See https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588.
const isNotWritable = (fileOrDir: string) => {
const mode = fs.statSync(fileOrDir).mode;
// tslint:disable-next-line: no-bitwise
return !(mode & parseInt('222', 8));
const checkPrVisibility = (isPublic2: boolean) => {
expect(h.buildExists(pr, '', isPublic2)).toBe(true);
expect(h.buildExists(pr, '', !isPublic2)).toBe(false);
expect(h.buildExists(pr, sha0, isPublic2)).toBe(true);
expect(h.buildExists(pr, sha0, !isPublic2)).toBe(false);
};
const uploadBuild = (sha: string) => h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha}`);
beforeEach(() => {
h.createDummyBuild(pr, sha0, !isPublic);
h.createDummyArchive(pr, sha9, archivePath);
checkPrVisibility(!isPublic);
});
afterEach(() => h.deletePrDir(pr, isPublic));
it('should update the PR\'s visibility', done => {
uploadBuild(sha9).
then(h.verifyResponse(statusCode)).
then(() => {
checkPrVisibility(isPublic);
expect(h.buildExists(pr, sha9, isPublic)).toBe(true);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain(`uploaded/${pr}`);
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toContain(sha9);
}).
then(done);
});
it('should not overwrite existing builds (but keep the updated visibility)', done => {
expect(h.buildExists(pr, sha0, isPublic)).toBe(false);
uploadBuild(sha0).
then(h.verifyResponse(409, overwriteRe)).
then(() => {
checkPrVisibility(isPublic);
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr);
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).not.toContain(`uploaded/${pr}`);
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(sha0);
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).not.toContain(sha9);
}).
then(done);
});
it('should reject the request if it fails to update the PR\'s visibility', done => {
// One way to cause an error is to have both a public and a hidden directory for the same PR.
h.createDummyBuild(pr, sha0, isPublic);
expect(h.buildExists(pr, sha0, isPublic)).toBe(true);
expect(h.buildExists(pr, sha0, !isPublic)).toBe(true);
const errorRegex = new RegExp(`^Request to move '${h.getPrDir(pr, !isPublic)}' ` +
`to existing directory '${h.getPrDir(pr, isPublic)}'.`);
uploadBuild(sha9).
then(h.verifyResponse(409, errorRegex)).
then(() => {
expect(h.buildExists(pr, sha0, isPublic)).toBe(true);
expect(h.buildExists(pr, sha0, !isPublic)).toBe(true);
expect(h.buildExists(pr, sha9, isPublic)).toBe(false);
expect(h.buildExists(pr, sha9, !isPublic)).toBe(false);
}).
then(done);
});
uploadPromise.
then(() => {
expect(isNotWritable(shaDir)).toBe(true);
expect(isNotWritable(idxPath)).toBe(true);
expect(isNotWritable(barPath)).toBe(true);
}).
then(done);
});
});
}));
});
@ -237,27 +375,194 @@ describe('upload-server (on HTTP)', () => {
});
describe(`${host}/pr-updated`, () => {
const url = `http://${host}/pr-updated`;
// Helpers
const curl = (payload?: {number: number, action?: string}) => {
const payloadStr = payload && JSON.stringify(payload) || '';
return `curl -iLX POST --header "Content-Type: application/json" --data '${payloadStr}' ${url}`;
};
it('should disallow non-POST requests', done => {
const bodyRegex = /^Unknown resource in request/;
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});
it('should respond with 400 for requests without a payload', done => {
const bodyRegex = /^Missing or empty 'number' field in request/;
h.runCmd(curl()).
then(h.verifyResponse(400, bodyRegex)).
then(done);
});
it('should respond with 400 for requests without a \'number\' field', done => {
const bodyRegex = /^Missing or empty 'number' field in request/;
Promise.all([
h.runCmd(curl({} as any)).then(h.verifyResponse(400, bodyRegex)),
h.runCmd(curl({number: null} as any)).then(h.verifyResponse(400, bodyRegex)),
]).then(done);
});
it('should reject requests for which checking the PR visibility fails', done => {
h.runCmd(curl({number: c.BV_getPrIsTrusted_error})).
then(h.verifyResponse(500, /Test/)).
then(done);
});
it('should respond with 404 for unknown paths', done => {
const mockPayload = JSON.stringify({number: +pr});
const cmdPrefix = `curl -iLX POST --data "${mockPayload}" http://${host}`;
Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done);
});
it('should do nothing if PR\'s visibility is already up-to-date', done => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
const checkVisibilities = () => {
// Public build is already public.
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
// Hidden build is already hidden.
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
};
h.createDummyBuild(publicPr, sha9, true);
h.createDummyBuild(hiddenPr, sha9, false);
checkVisibilities();
Promise.
all([
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
]).
// Visibilities should not have changed, because the specified action could not have triggered a change.
then(checkVisibilities).
then(done);
});
it('should do nothing if \'action\' implies no visibility change', done => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
const checkVisibilities = () => {
// Public build is hidden atm.
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
// Hidden build is public atm.
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
};
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
checkVisibilities();
Promise.
all([
h.runCmd(curl({number: +publicPr, action: 'foo'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'foo'})).then(h.verifyResponse(200)),
]).
// Visibilities should not have changed, because the specified action could not have triggered a change.
then(checkVisibilities).
then(done);
});
describe('when the visiblity has changed', () => {
const publicPr = pr;
const hiddenPr = String(c.BV_getPrIsTrusted_notTrusted);
beforeEach(() => {
// Create initial PR builds with opposite visibilities as the ones that will be reported:
// - The now public PR was previously hidden.
// - The now hidden PR was previously public.
h.createDummyBuild(publicPr, sha9, false);
h.createDummyBuild(hiddenPr, sha9, true);
expect(h.buildExists(publicPr, '', false)).toBe(true);
expect(h.buildExists(publicPr, '', true)).toBe(false);
expect(h.buildExists(hiddenPr, '', false)).toBe(false);
expect(h.buildExists(hiddenPr, '', true)).toBe(true);
});
afterEach(() => {
// Expect PRs' visibility to have been updated:
// - The public PR should be actually public (previously it was hidden).
// - The hidden PR should be actually hidden (previously it was public).
expect(h.buildExists(publicPr, '', false)).toBe(false);
expect(h.buildExists(publicPr, '', true)).toBe(true);
expect(h.buildExists(hiddenPr, '', false)).toBe(true);
expect(h.buildExists(hiddenPr, '', true)).toBe(false);
h.deletePrDir(publicPr, true);
h.deletePrDir(hiddenPr, false);
});
it('should update the PR\'s visibility (action: undefined)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr})).then(h.verifyResponse(200)),
]).then(done);
});
it('should update the PR\'s visibility (action: labeled)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr, action: 'labeled'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'labeled'})).then(h.verifyResponse(200)),
]).then(done);
});
it('should update the PR\'s visibility (action: unlabeled)', done => {
Promise.all([
h.runCmd(curl({number: +publicPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
h.runCmd(curl({number: +hiddenPr, action: 'unlabeled'})).then(h.verifyResponse(200)),
]).then(done);
});
});
});
describe(`${host}/*`, () => {
it('should respond with 404 for GET requests to unknown URLs', done => {
it('should respond with 404 for requests to unknown URLs', done => {
const bodyRegex = /^Unknown resource/;
Promise.all([
h.runCmd(`curl -iL http://${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL http://${host}/`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL http://${host}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});
it('should respond with 405 for non-GET requests to any URL', done => {
const bodyRegex = /^Unsupported method/;
Promise.all([
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(405, bodyRegex)),
h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done);
});

View File

@ -6,38 +6,40 @@
"author": "Angular",
"license": "MIT",
"scripts": {
"prebuild": "yarn run clean",
"prebuild": "yarn clean-dist",
"build": "tsc",
"build-watch": "yarn run tsc -- --watch",
"clean": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
"dev": "concurrently --kill-others --raw --success first \"yarn run build-watch\" \"yarn run test-watch\"",
"build-watch": "yarn tsc --watch",
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
"lint": "tslint --project tsconfig.json",
"pre~~test-only": "yarn run lint",
"pre~~test-only": "yarn lint",
"~~test-only": "node dist/test",
"pretest": "yarn run build",
"test": "yarn run ~~test-only",
"pretest-watch": "yarn run build",
"test-watch": "nodemon --exec \"yarn run ~~test-only\" --watch dist"
"pretest": "yarn build",
"test": "yarn ~~test-only",
"pretest-watch": "yarn build",
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
},
"dependencies": {
"express": "^4.14.1",
"jasmine": "^2.5.3",
"jsonwebtoken": "^7.3.0",
"shelljs": "^0.7.6"
"body-parser": "^1.18.2",
"express": "^4.15.4",
"jasmine": "^2.8.0",
"jsonwebtoken": "^8.0.1",
"shelljs": "^0.7.8",
"tslib": "^1.7.1"
},
"devDependencies": {
"@types/express": "^4.0.35",
"@types/jasmine": "^2.5.43",
"@types/jsonwebtoken": "^7.2.0",
"@types/node": "^7.0.5",
"@types/shelljs": "^0.7.0",
"@types/supertest": "^2.0.0",
"concurrently": "^3.3.0",
"eslint": "^3.15.0",
"eslint-plugin-jasmine": "^2.2.0",
"nodemon": "^1.11.0",
"@types/body-parser": "^1.16.5",
"@types/express": "^4.0.37",
"@types/jasmine": "^2.6.0",
"@types/jsonwebtoken": "^7.2.3",
"@types/node": "^8.0.30",
"@types/shelljs": "^0.7.4",
"@types/supertest": "^2.0.3",
"concurrently": "^3.5.0",
"nodemon": "^1.12.1",
"supertest": "^3.0.0",
"tslint": "^4.4.2",
"typescript": "^2.1.6"
"tslint": "^5.7.0",
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
"typescript": "^2.5.2"
}
}

View File

@ -1,7 +1,9 @@
// Imports
import * as fs from 'fs';
import * as path from 'path';
import * as shell from 'shelljs';
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
// Tests
@ -37,8 +39,8 @@ describe('BuildCleaner', () => {
let cleanerGetExistingBuildNumbersSpy: jasmine.Spy;
let cleanerGetOpenPrNumbersSpy: jasmine.Spy;
let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy;
let existingBuildsDeferred: {resolve: Function, reject: Function};
let openPrsDeferred: {resolve: Function, reject: Function};
let existingBuildsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
let openPrsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
let promise: Promise<void>;
beforeEach(() => {
@ -114,7 +116,7 @@ describe('BuildCleaner', () => {
it('should resolve with the value returned by \'removeUnnecessaryBuilds()\'', done => {
promise.then(result => {
expect(result).toBe('Test');
expect(result as any).toBe('Test');
done();
});
@ -170,6 +172,16 @@ describe('BuildCleaner', () => {
});
it('should remove `HIDDEN_DIR_PREFIX` from the filenames', done => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, [`${HIDDEN_DIR_PREFIX}12`, '34', `${HIDDEN_DIR_PREFIX}56`]);
});
it('should ignore files with non-numeric (or zero) names', done => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
@ -183,7 +195,7 @@ describe('BuildCleaner', () => {
describe('getOpenPrNumbers()', () => {
let prDeferred: {resolve: Function, reject: Function};
let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void};
let promise: Promise<number[]>;
beforeEach(() => {
@ -230,10 +242,22 @@ describe('BuildCleaner', () => {
describe('removeDir()', () => {
let shellChmodSpy: jasmine.Spy;
let shellRmSpy: jasmine.Spy;
let shellTestSpy: jasmine.Spy;
beforeEach(() => {
shellChmodSpy = spyOn(shell, 'chmod');
shellRmSpy = spyOn(shell, 'rm');
shellTestSpy = spyOn(shell, 'test').and.returnValue(true);
});
it('should test if the directory exists (and return if is does not)', () => {
shellTestSpy.and.returnValue(false);
(cleaner as any).removeDir('/foo/bar');
expect(shellTestSpy).toHaveBeenCalledWith('-d', '/foo/bar');
expect(shellChmodSpy).not.toHaveBeenCalled();
expect(shellRmSpy).not.toHaveBeenCalled();
});
@ -253,7 +277,10 @@ describe('BuildCleaner', () => {
it('should catch errors and log them', () => {
const consoleErrorSpy = spyOn(console, 'error');
shellRmSpy.and.callFake(() => { throw 'Test'; });
shellRmSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
(cleaner as any).removeDir('/foo/bar');
@ -287,17 +314,28 @@ describe('BuildCleaner', () => {
it('should construct full paths to directories (by prepending \'buildsDir\')', () => {
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3], []);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/2');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/1'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/2'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/3'));
});
it('should try removing hidden directories as well', () => {
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3], []);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}2`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`));
});
it('should remove the builds that do not correspond to open PRs', () => {
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], [2, 4]);
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(2);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3');
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(4);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/1'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/3'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`));
cleanerRemoveDirSpy.calls.reset();
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], [1, 2, 3, 4]);
@ -305,11 +343,15 @@ describe('BuildCleaner', () => {
cleanerRemoveDirSpy.calls.reset();
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], []);
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(4);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/2');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3');
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/4');
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(8);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/1'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/2'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/3'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize('/foo/bar/4'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}2`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(path.normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}4`));
cleanerRemoveDirSpy.calls.reset();
});

View File

@ -56,7 +56,7 @@ describe('GithubApi', () => {
it('should not pass data to \'request()\'', () => {
(api.get as Function)('foo', {}, {});
(api.get as any)('foo', {}, {});
expect(apiRequestSpy).toHaveBeenCalled();
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
@ -144,7 +144,7 @@ describe('GithubApi', () => {
describe('getPaginated()', () => {
let deferreds: {resolve: Function, reject: Function}[];
let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[];
beforeEach(() => {
deferreds = [];
@ -292,7 +292,7 @@ describe('GithubApi', () => {
describe('onResponse', () => {
let promise: Promise<void>;
let promise: Promise<object>;
let respond: (statusCode: number) => IncomingMessage;
beforeEach(() => {

View File

@ -22,7 +22,7 @@ describe('GithubPullRequests', () => {
describe('addComment()', () => {
let prs: GithubPullRequests;
let deferred: {resolve: Function, reject: Function};
let deferred: {resolve: (v: any) => void, reject: (v: any) => void};
beforeEach(() => {
prs = new GithubPullRequests('12345', 'foo/bar');
@ -66,7 +66,7 @@ describe('GithubPullRequests', () => {
it('should resolve with the returned response', done => {
prs.addComment(42, 'body').then(data => {
expect(data).toEqual('Test');
expect(data as any).toBe('Test');
done();
});
@ -76,6 +76,30 @@ describe('GithubPullRequests', () => {
});
describe('fetch()', () => {
let prs: GithubPullRequests;
let prsGetSpy: jasmine.Spy;
beforeEach(() => {
prs = new GithubPullRequests('12345', 'foo/bar');
prsGetSpy = spyOn(prs as any, 'get');
});
it('should call \'get()\' with the correct pathname', () => {
prs.fetch(42);
expect(prsGetSpy).toHaveBeenCalledWith('/repos/foo/bar/issues/42');
});
it('should forward the value returned by \'get()\'', () => {
prsGetSpy.and.returnValue('Test');
expect(prs.fetch(42) as any).toBe('Test');
});
});
describe('fetchAll()', () => {
let prs: GithubPullRequests;
let prsGetPaginatedSpy: jasmine.Spy;
@ -109,7 +133,7 @@ describe('GithubPullRequests', () => {
it('should forward the value returned by \'getPaginated()\'', () => {
prsGetPaginatedSpy.and.returnValue('Test');
expect(prs.fetchAll()).toBe('Test');
expect(prs.fetchAll() as any).toBe('Test');
});
});

View File

@ -38,7 +38,7 @@ describe('GithubTeams', () => {
it('should forward the value returned by \'getPaginated()\'', () => {
teamsGetPaginatedSpy.and.returnValue('Test');
expect(teams.fetchAll()).toBe('Test');
expect(teams.fetchAll() as any).toBe('Test');
});
});
@ -50,12 +50,16 @@ describe('GithubTeams', () => {
beforeEach(() => {
teams = new GithubTeams('12345', 'foo');
teamsGetSpy = spyOn(teams, 'get');
teamsGetSpy = spyOn(teams, 'get').and.returnValue(Promise.resolve(null));
});
it('should return a promise', () => {
expect(teams.isMemberById('user', [1])).toEqual(jasmine.any(Promise));
it('should return a promise', done => {
const promise = teams.isMemberById('user', [1]);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `get()`.
expect(promise).toEqual(jasmine.any(Promise));
});
@ -69,7 +73,6 @@ describe('GithubTeams', () => {
it('should call \'get()\' with the correct pathname', done => {
teamsGetSpy.and.returnValue(Promise.resolve(null));
teams.isMemberById('user', [1]).then(() => {
expect(teamsGetSpy).toHaveBeenCalledWith('/teams/1/memberships/user');
done();

View File

@ -2,9 +2,11 @@
import * as cp from 'child_process';
import {EventEmitter} from 'events';
import * as fs from 'fs';
import * as path from 'path';
import * as shell from 'shelljs';
import {SHORT_SHA_LEN} from '../../lib/common/constants';
import {BuildCreator} from '../../lib/upload-server/build-creator';
import {CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {UploadError} from '../../lib/upload-server/upload-error';
import {expectToBeUploadError} from './helpers';
@ -12,10 +14,13 @@ import {expectToBeUploadError} from './helpers';
describe('BuildCreator', () => {
const pr = '9';
const sha = '9'.repeat(40);
const shortSha = sha.substr(0, SHORT_SHA_LEN);
const archive = 'snapshot.tar.gz';
const buildsDir = 'builds/dir';
const prDir = `${buildsDir}/${pr}`;
const shaDir = `${prDir}/${sha}`;
const hiddenPrDir = path.join(buildsDir, `hidden--${pr}`);
const publicPrDir = path.join(buildsDir, pr);
const hiddenShaDir = path.join(hiddenPrDir, shortSha);
const publicShaDir = path.join(publicPrDir, shortSha);
let bc: BuildCreator;
beforeEach(() => bc = new BuildCreator(buildsDir));
@ -42,6 +47,7 @@ describe('BuildCreator', () => {
let bcEmitSpy: jasmine.Spy;
let bcExistsSpy: jasmine.Spy;
let bcExtractArchiveSpy: jasmine.Spy;
let bcUpdatePrVisibilitySpy: jasmine.Spy;
let shellMkdirSpy: jasmine.Spy;
let shellRmSpy: jasmine.Spy;
@ -49,13 +55,217 @@ describe('BuildCreator', () => {
bcEmitSpy = spyOn(bc, 'emit');
bcExistsSpy = spyOn(bc as any, 'exists');
bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive');
bcUpdatePrVisibilitySpy = spyOn(bc, 'updatePrVisibility');
shellMkdirSpy = spyOn(shell, 'mkdir');
shellRmSpy = spyOn(shell, 'rm');
});
[true, false].forEach(isPublic => {
const prDir = isPublic ? publicPrDir : hiddenPrDir;
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
it('should return a promise', done => {
const promise = bc.create(pr, sha, archive, isPublic);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
expect(promise).toEqual(jasmine.any(Promise));
});
it('should update the PR\'s visibility first if necessary', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
bc.create(pr, sha, archive, isPublic).
then(() => {
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
expect(shellMkdirSpy).toHaveBeenCalled();
}).
then(done);
});
it('should create the build directory (and any parent directories)', done => {
bc.create(pr, sha, archive, isPublic).
then(() => expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir)).
then(done);
});
it('should extract the archive contents into the build directory', done => {
bc.create(pr, sha, archive, isPublic).
then(() => expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir)).
then(done);
});
it('should emit a CreatedBuildEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => {
expect(type).toBe(CreatedBuildEvent.type);
expect(evt).toEqual(jasmine.any(CreatedBuildEvent));
expect(evt.pr).toBe(+pr);
expect(evt.sha).toBe(shortSha);
expect(evt.isPublic).toBe(isPublic);
emitted = true;
});
bc.create(pr, sha, archive, isPublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
describe('on error', () => {
let existsValues: {[dir: string]: boolean};
beforeEach(() => {
existsValues = {
[prDir]: false,
[shaDir]: false,
};
bcExistsSpy.and.callFake((dir: string) => existsValues[dir]);
});
it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
const mockError = new UploadError(543, 'Test');
bcUpdatePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
bc.create(pr, sha, archive, isPublic).catch(err => {
expect(err).toBe(mockError);
expect(bcExistsSpy).not.toHaveBeenCalled();
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if the build does already exist', done => {
existsValues[shaDir] = true;
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should detect existing build directory after visibility change', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true);
expect(bcExistsSpy(prDir)).toBe(false);
expect(bcExistsSpy(shaDir)).toBe(false);
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to create the directories', done => {
shellMkdirSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should abort and skip further operations if it fails to extract the archive', done => {
bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should delete the PR directory (for new PR)', done => {
bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir);
done();
});
});
it('should delete the SHA directory (for existing PR)', done => {
existsValues[prDir] = true;
bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir);
done();
});
});
it('should reject with an UploadError', done => {
// tslint:disable-next-line: no-string-throw
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`);
done();
});
});
it('should pass UploadError instances unmodified', done => {
shellMkdirSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBeUploadError(err, 543, 'Test');
done();
});
});
});
});
});
describe('updatePrVisibility()', () => {
let bcEmitSpy: jasmine.Spy;
let bcExistsSpy: jasmine.Spy;
let bcListShasByDate: jasmine.Spy;
let shellMvSpy: jasmine.Spy;
beforeEach(() => {
bcEmitSpy = spyOn(bc, 'emit');
bcExistsSpy = spyOn(bc as any, 'exists');
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
shellMvSpy = spyOn(shell, 'mv');
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
bcListShasByDate.and.returnValue([]);
});
it('should return a promise', done => {
const promise = bc.create(pr, sha, archive);
const promise = bc.updatePrVisibility(pr, true);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `extractArchive()`.
@ -63,106 +273,158 @@ describe('BuildCreator', () => {
});
it('should throw if the build does already exist', done => {
bcExistsSpy.and.returnValue(true);
bc.create(pr, sha, archive).catch(err => {
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
done();
});
});
[true, false].forEach(makePublic => {
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
it('should create the build directory (and any parent directories)', done => {
bc.create(pr, sha, archive).
then(() => expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir)).
then(done);
});
it('should extract the archive contents into the build directory', done => {
bc.create(pr, sha, archive).
then(() => expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir)).
then(done);
});
it('should emit a CreatedBuildEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => {
expect(type).toBe(CreatedBuildEvent.type);
expect(evt).toEqual(jasmine.any(CreatedBuildEvent));
expect(evt.pr).toBe(+pr);
expect(evt.sha).toBe(sha);
emitted = true;
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
bc.create(pr, sha, archive).
then(() => expect(emitted).toBe(true)).
then(done);
});
describe('when the visibility is updated', () => {
describe('on error', () => {
it('should abort and skip further operations if it fails to create the directories', done => {
shellMkdirSpy.and.throwError('');
bc.create(pr, sha, archive).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
it('should resolve to true', done => {
bc.updatePrVisibility(pr, makePublic).
then(result => expect(result).toBe(true)).
then(done);
});
it('should rename the directory', done => {
bc.updatePrVisibility(pr, makePublic).
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
then(done);
});
it('should emit a ChangedPrVisibilityEvent on success', done => {
let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toEqual(jasmine.any(Array));
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
it('should include all shas in the emitted event', done => {
const shas = ['foo', 'bar', 'baz'];
let emitted = false;
bcListShasByDate.and.returnValue(Promise.resolve(shas));
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent));
expect(evt.pr).toBe(+pr);
expect(evt.shas).toBe(shas);
expect(evt.isPublic).toBe(makePublic);
emitted = true;
});
bc.updatePrVisibility(pr, makePublic).
then(() => expect(emitted).toBe(true)).
then(done);
});
});
it('should abort and skip further operations if it fails to extract the archive', done => {
bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive).catch(() => {
expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
it('should do nothing if the visibility is already up-to-date', done => {
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
it('should delete the PR directory (for new PR)', done => {
bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir);
done();
});
it('should do nothing if the PR directory does not exist', done => {
bcExistsSpy.and.returnValue(false);
bc.updatePrVisibility(pr, makePublic).
then(result => {
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
});
it('should delete the SHA directory (for existing PR)', done => {
bcExistsSpy.and.callFake((path: string) => path !== shaDir);
bcExtractArchiveSpy.and.throwError('');
describe('on error', () => {
bc.create(pr, sha, archive).catch(() => {
expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir);
done();
it('should abort and skip further operations if both directories exist', done => {
bcExistsSpy.and.returnValue(true);
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
});
it('should reject with an UploadError', done => {
shellMkdirSpy.and.callFake(() => {throw 'Test'; });
bc.create(pr, sha, archive).catch(err => {
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`);
done();
it('should abort and skip further operations if it fails to rename the directory', done => {
shellMvSpy.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
});
it('should pass UploadError instances unmodified', done => {
shellMkdirSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
bc.create(pr, sha, archive).catch(err => {
expectToBeUploadError(err, 543, 'Test');
done();
it('should abort and skip further operations if it fails to list the SHAs', done => {
bcListShasByDate.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
});
it('should reject with an UploadError', done => {
// tslint:disable-next-line: no-string-throw
shellMvSpy.and.callFake(() => { throw 'Test'; });
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done();
});
});
it('should pass UploadError instances unmodified', done => {
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); });
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 543, 'Test');
done();
});
});
});
});
@ -174,11 +436,11 @@ describe('BuildCreator', () => {
describe('exists()', () => {
let fsAccessSpy: jasmine.Spy;
let fsAccessCbs: Function[];
let fsAccessCbs: ((v?: any) => void)[];
beforeEach(() => {
fsAccessCbs = [];
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: Function) => fsAccessCbs.push(cb));
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
});
@ -222,7 +484,7 @@ describe('BuildCreator', () => {
let shellChmodSpy: jasmine.Spy;
let shellRmSpy: jasmine.Spy;
let cpExecSpy: jasmine.Spy;
let cpExecCbs: Function[];
let cpExecCbs: ((...args: any[]) => void)[];
beforeEach(() => {
cpExecCbs = [];
@ -230,7 +492,7 @@ describe('BuildCreator', () => {
consoleWarnSpy = spyOn(console, 'warn');
shellChmodSpy = spyOn(shell, 'chmod');
shellRmSpy = spyOn(shell, 'rm');
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: Function) => cpExecCbs.push(cb));
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
});
@ -296,7 +558,11 @@ describe('BuildCreator', () => {
done();
});
shellChmodSpy.and.callFake(() => { throw 'Test'; });
shellChmodSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cpExecCbs[0]();
});
@ -309,7 +575,11 @@ describe('BuildCreator', () => {
done();
});
shellRmSpy.and.callFake(() => { throw 'Test'; });
shellRmSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cpExecCbs[0]();
});
@ -317,4 +587,101 @@ describe('BuildCreator', () => {
});
describe('listShasByDate()', () => {
let shellLsSpy: jasmine.Spy;
const lsResult = (name: string, mtimeMs: number, isDirectory = true) => ({
isDirectory: () => isDirectory,
mtime: new Date(mtimeMs),
name,
});
beforeEach(() => {
shellLsSpy = spyOn(shell, 'ls').and.returnValue([]);
});
it('should return a promise', done => {
const promise = (bc as any).listShasByDate('input/dir');
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `ls()`.
expect(promise).toEqual(jasmine.any(Promise));
});
it('should `ls()` files with their metadata', done => {
(bc as any).listShasByDate('input/dir').
then(() => expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir')).
then(done);
});
it('should reject if listing files fails', done => {
shellLsSpy.and.returnValue(Promise.reject('Test'));
(bc as any).listShasByDate('input/dir').catch((err: string) => {
expect(err).toBe('Test');
done();
});
});
it('should return the filenames', done => {
shellLsSpy.and.returnValue(Promise.resolve([
lsResult('foo', 100),
lsResult('bar', 200),
lsResult('baz', 300),
]));
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['foo', 'bar', 'baz'])).
then(done);
});
it('should sort by date', done => {
shellLsSpy.and.returnValue(Promise.resolve([
lsResult('foo', 300),
lsResult('bar', 100),
lsResult('baz', 200),
]));
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['bar', 'baz', 'foo'])).
then(done);
});
it('should not break with ShellJS\' custom `sort()` method', done => {
const mockArray = [
lsResult('foo', 300),
lsResult('bar', 100),
lsResult('baz', 200),
];
mockArray.sort = jasmine.createSpy('sort');
shellLsSpy.and.returnValue(Promise.resolve(mockArray));
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => {
expect(shas).toEqual(['bar', 'baz', 'foo']);
expect(mockArray.sort).not.toHaveBeenCalled();
}).
then(done);
});
it('should only include directories', done => {
shellLsSpy.and.returnValue(Promise.resolve([
lsResult('foo', 100),
lsResult('bar', 200, false),
lsResult('baz', 300),
]));
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => expect(shas).toEqual(['foo', 'baz'])).
then(done);
});
});
});

View File

@ -1,15 +1,15 @@
// Imports
import {BuildEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events';
// Tests
describe('BuildEvent', () => {
let evt: BuildEvent;
describe('ChangedPrVisibilityEvent', () => {
let evt: ChangedPrVisibilityEvent;
beforeEach(() => evt = new BuildEvent('foo', 42, 'bar'));
beforeEach(() => evt = new ChangedPrVisibilityEvent(42, ['foo', 'bar'], true));
it('should have a \'type\' property', () => {
expect(evt.type).toBe('foo');
it('should have a static \'type\' property', () => {
expect(ChangedPrVisibilityEvent.type).toBe('pr.changedVisibility');
});
@ -18,8 +18,13 @@ describe('BuildEvent', () => {
});
it('should have a \'sha\' property', () => {
expect(evt.sha).toBe('bar');
it('should have a \'shas\' property', () => {
expect(evt.shas).toEqual(['foo', 'bar']);
});
it('should have an \'isPublic\' property', () => {
expect(evt.isPublic).toBe(true);
});
});
@ -28,7 +33,7 @@ describe('BuildEvent', () => {
describe('CreatedBuildEvent', () => {
let evt: CreatedBuildEvent;
beforeEach(() => evt = new CreatedBuildEvent(42, 'bar'));
beforeEach(() => evt = new CreatedBuildEvent(42, 'bar', true));
it('should have a static \'type\' property', () => {
@ -36,19 +41,6 @@ describe('CreatedBuildEvent', () => {
});
it('should extend BuildEvent', () => {
expect(evt).toEqual(jasmine.any(CreatedBuildEvent));
expect(evt).toEqual(jasmine.any(BuildEvent));
expect(Object.getPrototypeOf(evt)).toBe(CreatedBuildEvent.prototype);
});
it('should automatically set the \'type\'', () => {
expect(evt.type).toBe(CreatedBuildEvent.type);
});
it('should have a \'pr\' property', () => {
expect(evt.pr).toBe(42);
});
@ -58,4 +50,9 @@ describe('CreatedBuildEvent', () => {
expect(evt.sha).toBe('bar');
});
it('should have an \'isPublic\' property', () => {
expect(evt.isPublic).toBe(true);
});
});

View File

@ -1,8 +1,8 @@
// Imports
import * as jwt from 'jsonwebtoken';
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests';
import {GithubTeams} from '../../lib/common/github-teams';
import {BuildVerifier} from '../../lib/upload-server/build-verifier';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from '../../lib/upload-server/build-verifier';
import {expectToBeUploadError} from './helpers';
// Tests
@ -13,14 +13,15 @@ describe('BuildVerifier', () => {
organization: 'organization',
repoSlug: 'repo/slug',
secret: 'secret',
trustedPrLabel: 'trusted: pr-label',
};
let bv: BuildVerifier;
// Helpers
const createBuildVerifier = (partialConfig: Partial<typeof defaultConfig> = {}) => {
const cfg = {...defaultConfig, ...partialConfig};
const cfg = {...defaultConfig, ...partialConfig} as typeof defaultConfig;
return new BuildVerifier(cfg.secret, cfg.githubToken, cfg.repoSlug, cfg.organization,
cfg.allowedTeamSlugs);
cfg.allowedTeamSlugs, cfg.trustedPrLabel);
};
beforeEach(() => bv = createBuildVerifier());
@ -28,12 +29,13 @@ describe('BuildVerifier', () => {
describe('constructor()', () => {
['secret', 'githubToken', 'repoSlug', 'organization', 'allowedTeamSlugs'].forEach(param => {
it(`should throw if '${param}' is missing or empty`, () => {
expect(() => createBuildVerifier({[param]: ''})).
toThrowError(`Missing or empty required parameter '${param}'!`);
['secret', 'githubToken', 'repoSlug', 'organization', 'allowedTeamSlugs', 'trustedPrLabel'].
forEach(param => {
it(`should throw if '${param}' is missing or empty`, () => {
expect(() => createBuildVerifier({[param]: ''})).
toThrowError(`Missing or empty required parameter '${param}'!`);
});
});
});
it('should throw if \'allowedTeamSlugs\' is an empty array', () => {
@ -44,6 +46,122 @@ describe('BuildVerifier', () => {
});
describe('getPrIsTrusted()', () => {
const pr = 9;
let mockPrInfo: PullRequest;
let prsFetchSpy: jasmine.Spy;
let teamsIsMemberBySlugSpy: jasmine.Spy;
beforeEach(() => {
mockPrInfo = {
labels: [
{name: 'foo'},
{name: 'bar'},
],
number: 9,
user: {login: 'username'},
};
prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').
and.returnValue(Promise.resolve(mockPrInfo));
teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').
and.returnValue(Promise.resolve(true));
});
it('should return a promise', done => {
const promise = bv.getPrIsTrusted(pr);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `GithubTeams#isMemberBySlug()`.
expect(promise).toEqual(jasmine.any(Promise));
});
it('should fetch the corresponding PR', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(prsFetchSpy).toHaveBeenCalledWith(pr);
done();
});
});
it('should fail if fetching the PR errors', done => {
prsFetchSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrIsTrusted(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
describe('when the PR has the "trusted PR" label', () => {
beforeEach(() => mockPrInfo.labels.push({name: 'trusted: pr-label'}));
it('should resolve to true', done => {
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(true);
done();
});
});
it('should not try to verify the author\'s membership status', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(teamsIsMemberBySlugSpy).not.toHaveBeenCalled();
done();
});
});
});
describe('when the PR does not have the "trusted PR" label', () => {
it('should verify the PR author\'s membership in the specified teams', done => {
bv.getPrIsTrusted(pr).then(() => {
expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']);
done();
});
});
it('should fail if verifying membership errors', done => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrIsTrusted(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
it('should resolve to true if the PR\'s author is a member', done => {
teamsIsMemberBySlugSpy.and.returnValue(Promise.resolve(true));
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(true);
done();
});
});
it('should resolve to false if the PR\'s author is not a member', done => {
teamsIsMemberBySlugSpy.and.returnValue(Promise.resolve(false));
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(false);
done();
});
});
});
});
describe('verify()', () => {
const pr = 9;
const defaultJwt = {
@ -53,22 +171,21 @@ describe('BuildVerifier', () => {
'pull-request': pr,
'slug': defaultConfig.repoSlug,
};
let bvGetPrAuthorTeamMembership: jasmine.Spy;
let bvGetPrIsTrusted: jasmine.Spy;
// Heleprs
const createAuthHeader = (partialJwt: Partial<typeof defaultJwt> = {}, secret: string = defaultConfig.secret) =>
`Token ${jwt.sign({...defaultJwt, ...partialJwt}, secret)}`;
beforeEach(() => {
bvGetPrAuthorTeamMembership = spyOn(bv, 'getPrAuthorTeamMembership').
and.returnValue(Promise.resolve({author: 'some-author', isMember: true}));
bvGetPrIsTrusted = spyOn(bv, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
});
it('should return a promise', done => {
const promise = bv.verify(pr, createAuthHeader());
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `bvGetPrAuthorTeamMembership()`.
// to avoid running the actual `bvGetPrIsTrusted()`.
expect(promise).toEqual(jasmine.any(Promise));
});
@ -148,16 +265,16 @@ describe('BuildVerifier', () => {
});
it('should call \'getPrAuthorTeamMembership()\' if the token is valid', done => {
it('should call \'getPrIsTrusted()\' if the token is valid', done => {
bv.verify(pr, createAuthHeader()).then(() => {
expect(bvGetPrAuthorTeamMembership).toHaveBeenCalledWith(pr);
expect(bvGetPrIsTrusted).toHaveBeenCalledWith(pr);
done();
});
});
it('should fail if \'getPrAuthorTeamMembership()\' rejects', done => {
bvGetPrAuthorTeamMembership.and.callFake(() => Promise.reject('Test'));
it('should fail if \'getPrIsTrusted()\' rejects', done => {
bvGetPrIsTrusted.and.callFake(() => Promise.reject('Test'));
bv.verify(pr, createAuthHeader()).catch(err => {
expectToBeUploadError(err, 403, `Error while verifying upload for PR ${pr}: Test`);
done();
@ -165,97 +282,22 @@ describe('BuildVerifier', () => {
});
it('should fail if \'getPrAuthorTeamMembership()\' reports no membership', done => {
const errorMessage = `Error while verifying upload for PR ${pr}: User 'test' is not an active member of any of ` +
'the following teams: team1, team2';
bvGetPrAuthorTeamMembership.and.returnValue(Promise.resolve({author: 'test', isMember: false}));
bv.verify(pr, createAuthHeader()).catch(err => {
expectToBeUploadError(err, 403, errorMessage);
it('should resolve to `verifiedNotTrusted` if \'getPrIsTrusted()\' returns false', done => {
bvGetPrIsTrusted.and.returnValue(Promise.resolve(false));
bv.verify(pr, createAuthHeader()).then(value => {
expect(value).toBe(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
done();
});
});
it('should succeed if everything checks outs', done => {
bv.verify(pr, createAuthHeader()).then(done);
});
});
describe('getPrAuthorTeamMembership()', () => {
const pr = 9;
let prsFetchSpy: jasmine.Spy;
let teamsIsMemberBySlugSpy: jasmine.Spy;
beforeEach(() => {
prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').
and.returnValue(Promise.resolve({user: {login: 'username'}}));
teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').
and.returnValue(Promise.resolve(true));
});
it('should return a promise', done => {
const promise = bv.getPrAuthorTeamMembership(pr);
promise.then(done); // Do not complete the test (and release the spies) synchronously
// to avoid running the actual `GithubTeams#isMemberBySlug()`.
expect(promise).toEqual(jasmine.any(Promise));
});
it('should fetch the corresponding PR', done => {
bv.getPrAuthorTeamMembership(pr).then(() => {
expect(prsFetchSpy).toHaveBeenCalledWith(pr);
it('should resolve to `verifiedAndTrusted` if \'getPrIsTrusted()\' returns true', done => {
bv.verify(pr, createAuthHeader()).then(value => {
expect(value).toBe(BUILD_VERIFICATION_STATUS.verifiedAndTrusted);
done();
});
});
it('should fail if fetching the PR errors', done => {
prsFetchSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrAuthorTeamMembership(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
it('should verify the PR author\'s membership in the specified teams', done => {
bv.getPrAuthorTeamMembership(pr).then(() => {
expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']);
done();
});
});
it('should fail if verifying membership errors', done => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.reject('Test'));
bv.getPrAuthorTeamMembership(pr).catch(err => {
expect(err).toBe('Test');
done();
});
});
it('should return the PR\'s author and whether they are members', done => {
teamsIsMemberBySlugSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
Promise.all([
bv.getPrAuthorTeamMembership(pr).then(({author, isMember}) => {
expect(author).toBe('username');
expect(isMember).toBe(true);
}),
bv.getPrAuthorTeamMembership(pr).then(({author, isMember}) => {
expect(author).toBe('username');
expect(isMember).toBe(false);
}),
]).then(done);
});
});
});

View File

@ -4,8 +4,8 @@ import * as http from 'http';
import * as supertest from 'supertest';
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
import {BuildCreator} from '../../lib/upload-server/build-creator';
import {CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {BuildVerifier} from '../../lib/upload-server/build-verifier';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from '../../lib/upload-server/build-verifier';
import {uploadServerFactory as usf} from '../../lib/upload-server/upload-server-factory';
// Tests
@ -18,11 +18,12 @@ describe('uploadServerFactory', () => {
githubToken: '12345',
repoSlug: 'repo/slug',
secret: 'secret',
trustedPrLabel: 'trusted: pr-label',
};
// Helpers
const createUploadServer = (partialConfig: Partial<typeof defaultConfig> = {}) =>
usf.create({...defaultConfig, ...partialConfig});
usf.create({...defaultConfig, ...partialConfig} as typeof defaultConfig);
describe('create()', () => {
@ -75,6 +76,12 @@ describe('uploadServerFactory', () => {
});
it('should throw if \'trustedPrLabel\' is missing or empty', () => {
expect(() => createUploadServer({trustedPrLabel: ''})).
toThrowError('Missing or empty required parameter \'trustedPrLabel\'!');
});
it('should return an http.Server', () => {
const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
const server = createUploadServer();
@ -109,7 +116,7 @@ describe('uploadServerFactory', () => {
it('should log the server address info on \'listening\'', () => {
const consoleInfoSpy = spyOn(console, 'info');
const server = createUploadServer('builds/dir');
const server = createUploadServer();
server.address = () => ({address: 'foo', family: '', port: 1337});
expect(consoleInfoSpy).not.toHaveBeenCalled();
@ -141,26 +148,71 @@ describe('uploadServerFactory', () => {
});
it('should post a comment on GitHub on \'build.created\'', () => {
const prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment');
const commentBody = 'The angular.io preview for 1234567890 is available [here][1].\n\n' +
'[1]: https://pr42-1234567890.domain.name/';
describe('on \'build.created\'', () => {
let prsAddCommentSpy: jasmine.Spy;
buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890'});
beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'));
it('should post a comment on GitHub for public previews', () => {
const commentBody = 'You can preview 1234567890 at https://pr42-1234567890.domain.name/.';
buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true});
expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody);
});
it('should not post a comment on GitHub for non-public previews', () => {
buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: false});
expect(prsAddCommentSpy).not.toHaveBeenCalled();
});
});
describe('on \'pr.changedVisibility\'', () => {
let prsAddCommentSpy: jasmine.Spy;
beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'));
it('should post a comment on GitHub (for all SHAs) for PRs made public', () => {
const commentBody = 'You can preview 12345 at https://pr42-12345.domain.name/.\n' +
'You can preview 67890 at https://pr42-67890.domain.name/.';
buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true});
expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody);
});
it('should not post a comment on GitHub if no SHAs were affected', () => {
buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: [], isPublic: true});
expect(prsAddCommentSpy).not.toHaveBeenCalled();
});
it('should not post a comment on GitHub for PRs made non-public', () => {
buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: false});
expect(prsAddCommentSpy).not.toHaveBeenCalled();
});
expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody);
});
it('should pass the correct \'githubToken\' and \'repoSlug\' to GithubPullRequests', () => {
const prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment');
buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890'});
const prs = prsAddCommentSpy.calls.mostRecent().object;
buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true});
buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true});
const allCalls = prsAddCommentSpy.calls.all();
const prs = allCalls[0].object;
expect(prsAddCommentSpy).toHaveBeenCalledTimes(2);
expect(prs).toBe(allCalls[1].object);
expect(prs).toEqual(jasmine.any(GithubPullRequests));
expect((prs as any).repoSlug).toBe('repo/slug');
expect((prs as any).requestHeaders.Authorization).toContain('12345');
expect(prs.repoSlug).toBe('repo/slug');
expect(prs.requestHeaders.Authorization).toContain('12345');
});
});
@ -184,6 +236,7 @@ describe('uploadServerFactory', () => {
defaultConfig.repoSlug,
defaultConfig.githubOrganization,
defaultConfig.githubTeamSlugs,
defaultConfig.trustedPrLabel,
);
buildCreator = new BuildCreator(defaultConfig.buildsDir);
agent = supertest.agent((usf as any).createMiddleware(buildVerifier, buildCreator));
@ -199,17 +252,18 @@ describe('uploadServerFactory', () => {
let buildCreatorCreateSpy: jasmine.Spy;
beforeEach(() => {
buildVerifierVerifySpy = spyOn(buildVerifier, 'verify').and.returnValue(Promise.resolve());
const verStatus = BUILD_VERIFICATION_STATUS.verifiedAndTrusted;
buildVerifierVerifySpy = spyOn(buildVerifier, 'verify').and.returnValue(Promise.resolve(verStatus));
buildCreatorCreateSpy = spyOn(buildCreator, 'create').and.returnValue(Promise.resolve());
});
it('should respond with 405 for non-GET requests', done => {
it('should respond with 404 for non-GET requests', done => {
verifyRequests([
agent.put(`/create-build/${pr}/${sha}`).expect(405),
agent.post(`/create-build/${pr}/${sha}`).expect(405),
agent.patch(`/create-build/${pr}/${sha}`).expect(405),
agent.delete(`/create-build/${pr}/${sha}`).expect(405),
agent.put(`/create-build/${pr}/${sha}`).expect(404),
agent.post(`/create-build/${pr}/${sha}`).expect(404),
agent.patch(`/create-build/${pr}/${sha}`).expect(404),
agent.delete(`/create-build/${pr}/${sha}`).expect(404),
], done);
});
@ -284,14 +338,17 @@ describe('uploadServerFactory', () => {
it('should call \'BuildCreator#create()\' with the correct arguments', done => {
const req = agent.
get(`/create-build/${pr}/${sha}`).
set('AUTHORIZATION', 'foo').
set('X-FILE', 'bar');
buildVerifierVerifySpy.and.returnValues(
Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedAndTrusted),
Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted));
promisifyRequest(req).
then(() => expect(buildCreatorCreateSpy).toHaveBeenCalledWith(pr, sha, 'bar')).
then(done, done.fail);
const req1 = agent.get(`/create-build/${pr}/${sha}`).set('AUTHORIZATION', 'foo').set('X-FILE', 'bar');
const req2 = agent.get(`/create-build/${pr}/${sha}`).set('AUTHORIZATION', 'foo').set('X-FILE', 'bar');
Promise.all([
promisifyRequest(req1).then(() => expect(buildCreatorCreateSpy).toHaveBeenCalledWith(pr, sha, 'bar', true)),
promisifyRequest(req2).then(() => expect(buildCreatorCreateSpy).toHaveBeenCalledWith(pr, sha, 'bar', false)),
]).then(done, done.fail);
});
@ -307,7 +364,7 @@ describe('uploadServerFactory', () => {
});
it('should respond with 201 on successful upload', done => {
it('should respond with 201 on successful upload (for public builds)', done => {
const req = agent.
get(`/create-build/${pr}/${sha}`).
set('AUTHORIZATION', 'foo').
@ -318,6 +375,18 @@ describe('uploadServerFactory', () => {
});
it('should respond with 202 on successful upload (for hidden builds)', done => {
buildVerifierVerifySpy.and.returnValue(Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted));
const req = agent.
get(`/create-build/${pr}/${sha}`).
set('AUTHORIZATION', 'foo').
set('X-FILE', 'bar').
expect(202, http.STATUS_CODES[202]);
verifyRequests([req], done);
});
it('should reject PRs with leading zeros', done => {
verifyRequests([agent.get(`/create-build/0${pr}/${sha}`).expect(404)], done);
});
@ -349,12 +418,12 @@ describe('uploadServerFactory', () => {
});
it('should respond with 405 for non-GET requests', done => {
it('should respond with 404 for non-GET requests', done => {
verifyRequests([
agent.put('/health-check').expect(405),
agent.post('/health-check').expect(405),
agent.patch('/health-check').expect(405),
agent.delete('/health-check').expect(405),
agent.put('/health-check').expect(404),
agent.post('/health-check').expect(404),
agent.patch('/health-check').expect(404),
agent.delete('/health-check').expect(404),
], done);
});
@ -373,11 +442,141 @@ describe('uploadServerFactory', () => {
});
describe('GET *', () => {
describe('POST /pr-updated', () => {
const pr = '9';
const url = '/pr-updated';
let bvGetPrIsTrustedSpy: jasmine.Spy;
let bcUpdatePrVisibilitySpy: jasmine.Spy;
// Helpers
const createRequest = (num: number, action?: string) =>
agent.post(url).send({number: num, action});
beforeEach(() => {
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted');
bcUpdatePrVisibilitySpy = spyOn(buildCreator, 'updatePrVisibility');
});
it('should respond with 404 for non-POST requests', done => {
verifyRequests([
agent.get(url).expect(404),
agent.put(url).expect(404),
agent.patch(url).expect(404),
agent.delete(url).expect(404),
], done);
});
it('should respond with 400 for requests without a payload', done => {
const responseBody = `Missing or empty 'number' field in request: POST ${url} {}`;
const request1 = agent.post(url);
const request2 = agent.post(url).send();
verifyRequests([
request1.expect(400, responseBody),
request2.expect(400, responseBody),
], done);
});
it('should respond with 400 for requests without a \'number\' field', done => {
const responseBodyPrefix = `Missing or empty 'number' field in request: POST ${url}`;
const request1 = agent.post(url).send({});
const request2 = agent.post(url).send({number: null});
verifyRequests([
request1.expect(400, `${responseBodyPrefix} {}`),
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
], done);
});
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', done => {
const req = createRequest(+pr);
promisifyRequest(req).
then(() => expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9)).
then(done, done.fail);
});
it('should propagate errors from BuildVerifier', done => {
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
const req = createRequest(+pr).expect(500, 'Test');
promisifyRequest(req).
then(() => {
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
}).
then(done, done.fail);
});
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', done => {
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
const req1 = createRequest(24);
const req2 = createRequest(42);
Promise.all([
promisifyRequest(req1).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('24', false)),
promisifyRequest(req2).then(() => expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith('42', true)),
]).then(done, done.fail);
});
it('should propagate errors from BuildCreator', done => {
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
const req = createRequest(+pr).expect(500, 'Test');
verifyRequests([req], done);
});
describe('on success', () => {
it('should respond with 200 (action: undefined)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (action: labeled)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (action: unlabeled)', done => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
verifyRequests(reqs, done);
});
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', done => {
const promises = ['foo', 'notlabeled'].
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
map(promisifyRequest);
Promise.all(promises).
then(() => {
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
}).
then(done, done.fail);
});
it('should respond with 404', done => {
const responseBody = 'Unknown resource in request: GET /some/url';
verifyRequests([agent.get('/some/url').expect(404, responseBody)], done);
});
});
@ -385,14 +584,15 @@ describe('uploadServerFactory', () => {
describe('ALL *', () => {
it('should respond with 405', done => {
const responseFor = (method: string) => `Unsupported method in request: ${method.toUpperCase()} /some/url`;
it('should respond with 404', done => {
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
verifyRequests([
agent.put('/some/url').expect(405, responseFor('put')),
agent.post('/some/url').expect(405, responseFor('post')),
agent.patch('/some/url').expect(405, responseFor('patch')),
agent.delete('/some/url').expect(405, responseFor('delete')),
agent.get('/some/url').expect(404, responseFor('get')),
agent.put('/some/url').expect(404, responseFor('put')),
agent.post('/some/url').expect(404, responseFor('post')),
agent.patch('/some/url').expect(404, responseFor('patch')),
agent.delete('/some/url').expect(404, responseFor('delete')),
], done);
});

View File

@ -1,25 +1,66 @@
{
"compilerOptions": {
"alwaysStrict": true,
"forceConsistentCasingInFileNames": true,
"inlineSourceMap": true,
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [
"es2016"
],
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"pretty": true,
"rootDir": ".",
"skipLibCheck": true,
"strictNullChecks": true,
"target": "es5",
"es2015",
"es2016.array.include"
], /* Specify library files to be included in the compilation: */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
"importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [
"node_modules/@types"
]
], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Other */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
"pretty": true, /* Stylize errors and messages using color and context. */
"skipLibCheck": true /* Skip type checking of all declaration files (*.d.ts). */
},
"include": [
"lib/**/*",

View File

@ -1,15 +1,24 @@
{
"extends": "tslint:recommended",
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"array-type": [true, "array"],
"arrow-parens": [true, "ban-single-arg-parens"],
"interface-name": [true, "never-prefix"],
"max-classes-per-file": [true, 4],
"no-consecutive-blank-lines": [true, 2],
"no-console": false,
"no-console": [false],
"no-focused-test": true,
"no-namespace": [true, "allow-declarations"],
"no-skipped-test": true,
"no-string-literal": false,
"quotemark": [true, "single"],
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"]
}
},
"rulesDirectory": [
"node_modules/tslint-jasmine-noSkipOrFocus"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#!/bin/bash
set -e -o pipefail
set -eu -o pipefail
# Set up env variables
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)

View File

@ -1,5 +1,6 @@
#!/bin/bash
set +e -o pipefail
# Using `+e` so that all checks are run and we get a complete report (even if some checks failed).
set +e -u -o pipefail
# Variables

View File

@ -1,5 +1,5 @@
#!/bin/bash
set -e -o pipefail
set -eu -o pipefail
exec >> /var/log/aio/init.log
exec 2>&1

View File

@ -1,15 +1,14 @@
#!/bin/bash
set -e -o pipefail
set -eu -o pipefail
# Set up env variables for production
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null)
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || echo "MISSING_GITHUB_TOKEN")
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "MISSING_PREVIEW_DEPLOYMENT_TOKEN")
# Start the upload-server instance
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
# (Currently, there doesn't seem to be a straight forward way.)
action=$([ "$1" == "stop" ] && echo "stop" || echo "start")
pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server \
--uid $AIO_WWW_USER \
--log /var/log/aio/upload-server-prod.log \
--name aio-upload-server-prod \
${@:2}

View File

@ -1,13 +1,13 @@
#!/bin/bash
set -e -o pipefail
set -eu -o pipefail
# Set up env variables for testing
export AIO_BUILDS_DIR=$TEST_AIO_BUILDS_DIR
export AIO_DOMAIN_NAME=$TEST_AIO_DOMAIN_NAME
export AIO_GITHUB_ORGANIZATION=$TEST_AIO_GITHUB_ORGANIZATION
export AIO_GITHUB_TEAM_SLUGS=$TEST_AIO_GITHUB_TEAM_SLUGS
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$TEST_AIO_PREVIEW_DEPLOYMENT_TOKEN
export AIO_REPO_SLUG=$TEST_AIO_REPO_SLUG
export AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL
export AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME
export AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT
@ -15,13 +15,12 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/TEST_GITHUB_TOKEN 2>/dev/null
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/TEST_PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "TEST_PREVIEW_DEPLOYMENT_TOKEN")
# Start the upload-server instance
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
# (Currently, there doesn't seem to be a straight forward way.)
appName=aio-upload-server-test
if [[ "$1" == "stop" ]]; then
pm2 delete $appName
else
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server/index-test.js \
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
--uid $AIO_WWW_USER \
--log /var/log/aio/upload-server-test.log \
--name $appName \
--no-autorestart \

View File

@ -1,5 +1,5 @@
#!/bin/bash
set -e -o pipefail
set -eu -o pipefail
logFile=/var/log/aio/verify-setup.log
uploadServerLogFile=/var/log/aio/upload-server-verify-setup.log

View File

@ -4,7 +4,8 @@
## Overview
- [General overview](overview--general.md)
- [Security model](overview--security-model.md)
- [Available Commands](overview--scripts-and-commands.md)
- [Available scripts and commands](overview--scripts-and-commands.md)
- [HTTP status codes](overview--http-status-codes.md)
## Setting up the VM
@ -20,7 +21,11 @@
## Starting the docker container
- [Create docker image](vm-setup--start-docker-container.md)
- [Start docker container](vm-setup--start-docker-container.md)
## Updating the docker container
- [Update docker container](vm-setup--update-docker-container.md)
## Miscellaneous

View File

@ -17,7 +17,7 @@ you don't need to specify values for those.
The domain name of the server.
- `AIO_GITHUB_ORGANIZATION`:
The GitHub organization whose teams arew whitelisted for accepting uploads.
The GitHub organization whose teams are whitelisted for accepting uploads.
See also `AIO_GITHUB_TEAM_SLUGS`.
- `AIO_GITHUB_TEAM_SLUGS`:
@ -39,6 +39,11 @@ you don't need to specify values for those.
- `AIO_REPO_SLUG`:
The repository slug (in the form `<user>/<repo>`) for which PRs will be uploaded.
- `AIO_TRUSTED_PR_LABEL`:
The PR whose presence indicates the PR has been manually verified and is allowed to have its
build artifacts publicly served. This is useful for enabling previews for any PR (not only those
from trusted authors).
- `AIO_UPLOAD_HOSTNAME`:
The internal hostname for accessing the Node.js upload-server. This is used by nginx for
delegating upload requests and also for performing a periodic health-check.

View File

@ -3,10 +3,9 @@
TODO (gkalpak): Add docs. Mention:
- Travis' JWT addon (+ limitations).
Relevant files: `.travis.yml`
Relevant files: `.travis.yml`, `scripts/ci/env.sh`
- Testing on CI.
Relevant files: `ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
- Preverifying on CI.
Relevant files: `ci/deploy.sh`, `aio/aio-builds-setup/scripts/travis-preverify-pr.sh`
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
- Deploying from CI.
Relevant files: `ci/deploy.sh`, `aio/scripts/deploy-preview.sh`
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-preview.sh`,
`aio/scripts/deploy-to-firebase.sh`

View File

@ -33,36 +33,69 @@ container:
### On CI (Travis)
- Build job completes successfully (i.e. build succeeds and tests pass).
- Build job completes successfully.
- The CI script checks whether the build job was initiated by a PR against the angular/angular
master branch.
- The CI script checks whether the PR has touched any files inside the angular.io project directory
(currently `aio/`).
- The CI script checks whether the author of the PR is a member of one of the whitelisted GitHub
teams (and therefore allowed to upload).
- The CI script checks whether the PR has touched any files that might affect the angular.io app
(currently the `aio/` or `packages/` directories, ignoring spec files).
- Optionally, the CI script can check whether the PR can be automatically verified (i.e. if the
author of the PR is a member of one of the whitelisted GitHub teams or the PR has the specified
"trusted PR" label).
**Note:**
For security reasons, the same checks will be performed on the server as well. This is an optional
step with the purpose of:
1. Avoiding the wasted overhead associated with uploads that are going to be rejected (e.g.
building the artifacts, sending them to the server, running checks on the server, etc).
2. Avoiding failing the build (due to an error response from the server) or requiring additional
logic for detecting the reasons of the failure.
- The CI script gzip and upload the build artifacts to the server.
step that can be used in case one wants to apply special logic depending on the outcome of the
pre-verification. For example:
1. One might want to deploy automatically verified PRs only. In that case, the pre-verification
helps avoid the wasted overhead associated with uploads that are going to be rejected (e.g.
building the artifacts, sending them to the server, running checks on the server, detecting the
reasons of deployment failure and whether to fail the build, etc).
2. One might want to apply additional logic (e.g. different tests) depending on whether the PR is
automatically verified or not).
- The CI script gzips and uploads the build artifacts to the server.
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
### Uploading build artifacts
- nginx receives upload request.
- nginx receives the upload request.
- nginx checks that the uploaded gzip archive does not exceed the specified max file size, stores it
in a temporary location and passes the filepath to the Node.js upload-server.
- The upload-server verifies that the uploaded file is not trying to overwrite an existing build,
and runs several checks to determine whether the request should be accepted (more details can be
- The upload-server runs several checks to determine whether the request should be accepted and
whether it should be publicly accessible or stored for later verification (more details can be
found [here](overview--security-model.md)).
- The upload-server deploys the artifacts to a sub-directory named after the PR number and SHA:
`<PR>/<SHA>/`
- The upload-server posts a comment on the corresponding PR on GitHub mentioning the SHA and the
the link where the preview can be found.
- The upload-server changes the "visibility" of the associated PR, if necessary. For example, if
builds for the same PR had been previously deployed as non-public and the current build has been
automatically verified, all previous builds are made public as well.
If the PR transitions from "non-public" to "public", the upload-server posts a comment on the
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
- The upload-server verifies that the uploaded file is not trying to overwrite an existing build.
- The upload-server deploys the artifacts to a sub-directory named after the PR number and the first
few characters of the SHA: `<PR>/<SHA>/`
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
number and SHA.)
- If the PR is publicly accessible, the upload-server posts a comment on the corresponding PR on
GitHub mentioning the SHA and the link where the preview can be found.
More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).
### Updating PR visibility
- nginx receives a natification that a PR has been updated and passes it through to the
upload-server. This could, for example, be sent by a GitHub webhook every time a PR's labels
change.
E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":<PR>,"action":"labeled"}`)
- The request contains the PR number (as `number`) and optionally the action that triggered the
request (as `action`) in the payload.
- The upload-server verifies the payload and determines whether the `action` (if specified) could
have led to PR visibility changes. Only requests that omit the `action` field altogether or
specify an action that can affect visibility are further processed.
(Currently, the only actions that are considered capable of affecting visibility are `labeled` and
`unlabeled`.)
- The upload-server re-checks and if necessary updates the PR's visibility.
More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).
### Serving build artifacts
@ -71,6 +104,9 @@ More info on how to set things up on CI can be found [here](misc--integrate-with
- nginx maps the subdomain to the correct sub-directory and serves the resource.
E.g.: `/<PR>/<SHA>/path/to/resource`
More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md).
### Removing obsolete artifacts
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a

View File

@ -0,0 +1,85 @@
# Overview - HTTP Status Codes
This is a list of all the possible HTTP status codes returned by the nginx anf upload servers, along
with a bried explanation of what they mean:
## `http://*.ngbuilds.io/*`
- **307 (Temporary Redirect)**:
All non-HTTPS requests. 308 (Permanent Redirect) would be more appropriate, but is not supported
by all agents (e.g. cURL).
## `https://pr<pr>-<sha>.ngbuilds.io/*`
- **200 (OK)**:
File was found or URL was rewritten to `/index.html` (i.e. all paths that have no `.` in final
segment).
- **403 (Forbidden)**:
Trying to access a sub-directory.
- **404 (Not Found)**:
File not found.
## `https://ngbuilds.io/create-build/<pr>/<sha>`
- **201 (Created)**:
Build deployed successfully and is publicly available.
- **202 (Accepted)**:
Build not automatically verifiable. Stored for later deployment (after re-verification).
- **400 (Bad Request)**:
No payload.
- **401 (Unauthorized)**:
No `AUTHORIZATION` header.
- **403 (Forbidden)**:
Unable to verify build (e.g. invalid JWT token, or unable to talk to 3rd-party APIs, etc).
- **405 (Method Not Allowed)**:
Request method other than POST.
- **409 (Conflict)**:
Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or
change PR visibility when the destination directory does already exist).
- **413 (Payload Too Large)**:
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
## `https://ngbuilds.io/health-check`
- **200 (OK)**:
The server is healthy (i.e. up and running and processing requests).
## `https://ngbuilds.io/pr-updated`
- **200 (OK)**:
Request processed successfully. Processing may or may not have resulted in further actions.
- **400 (Bad Request)**:
No payload or no `number` field in payload.
- **405 (Method Not Allowed)**:
Request method other than POST.
- **409 (Conflict)**:
Request to overwrite existing (public or non-public) directory (i.e. directories for both
visibilities exist).
(Normally, this should not happen.)
## `https://*.ngbuilds.io/*`
- **404 (Not Found)**:
Request not matched by the above rules.
- **500 (Internal Server Error)**:
Error while processing a request matched by the above rules.

View File

@ -8,19 +8,17 @@ This is an overview of the available scripts and commands.
The scripts are located inside `<aio-builds-setup-dir>/scripts/`. The following scripts are
available:
- `build.sh`:
- `create-image.sh`:
Can be used for creating a preconfigured docker image.
See [here](vm-setup--create-docker-image.md) for more info.
- `test.sh`
- `test.sh`:
Can be used for running the tests for `<aio-builds-setup-dir>/dockerbuild/scripts-js/`. This is
useful for CI integration. See [here](misc--integrate-with-ci.md) for more info.
- `travis-preverify-pr.sh`
Can be used for "preverifying" a PR before uploading the artifacts to the server. It checks that
the author of the PR a member of one of the specified GitHub teams and therefore allowed to upload
build artifacts. This is useful for CI integration. See [here](misc--integrate-with-ci.md) for
more info.
- `update-preview-server.sh`:
Can be used for updating the docker container (and image) based on the latest changes checked out
from a git repository. See [here](vm-setup--update-docker-container.md) for more info.
## Commands

View File

@ -41,12 +41,13 @@ part of the CI setup and serving them publicly.
The implemented approach can be broken up to the following sub-tasks:
1. Verify which PR the uploaded artifacts correspond to.
2. Determine the author of the PR.
3. Check whether the PR author is a member of some whitelisted GitHub team.
4. Deploy the artifacts to the corresponding PR's directory.
5. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
2. Fetch the PR's metadata, including author and labels.
3. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
4. If necessary, update the corresponding PR's verification status.
5. Deploy the artifacts to the corresponding PR's directory.
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
during deployment will remain valid until the artifacts are removed).
6. Prevent uploaded files from accessing anything outside their directory.
7. Prevent uploaded files from accessing anything outside their directory.
### Implementation details
@ -65,35 +66,51 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
_There are currently certain limitation in the implementation of the JWT addon._
_See the next section for more details._
2. **Determine the author of the PR.**
2. **Fetch the PR's metadata, including author and labels**.
Once we have securely associated the uploaded artifaacts to a PR, we retrieve the PR's metadata -
including the author's username - using the [GitHub API](https://developer.github.com/v3/).
Once we have securely associated the uploaded artifacts to a PR, we retrieve the PR's metadata -
including the author's username and the labels - using the
[GitHub API](https://developer.github.com/v3/).
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
[@mary-poppins](https://github.com/mary-poppins)).
3. **Check whether the PR author is a member of some whitelisted GitHub team.**
3. **Check whether the PR can be automatically verified as "trusted"**.
Again using the GitHub API, we can verify the author's membership in one of the
whitelisted/trusted GitHub teams. For this operation, we need a PErsonal Access Token with the
`read:org` scope issued by a user that can "see" the specified GitHub organization.
Here too, we use token by @mary-poppins.
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
and publicly accessible on the preview server. There are two ways to check that:
1. We can verify that the PR has a pre-determined label, which marks it as "safe for preview".
Such a label can only have been added by a maintainer (with the necessary rights) and
designates that they have manually verified the PR contents.
2. We can verify (again using the GitHub API) the author's membership in one of the
whitelisted/trusted GitHub teams. For this operation, we need a Personal Access Token with the
`read:org` scope issued by a user that can "see" the specified GitHub organization.
Here too, we use the token by @mary-poppins.
4. **Deploy the artifacts to the corresponding PR's directory.**
4. **If necessary update the corresponding PR's verification status**.
With the preceeding steps, we have verified that the uploaded artifacts have been uploaded by
Travis and correspond to a PR whose author is a member of a trusted team. Essentially, as long as
sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to "project" the trust we have
in a team's members through the PR and Travis to the build artifacts.
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
whether it is publicly accessible or not), based on the new verification status. For example, if
a PR was initially considered "not trusted" but the check triggered by a new build determined
otherwise, the PR (and all the previously uploaded previews) are made public. It works the same
way if a PR has gone from "trusted" to "not trusted".
5. **Prevent overwriting previously deployed artifacts**.
5. **Deploy the artifacts to the corresponding PR's directory.**
In order to enforce this restriction (and ensure that the deployed artifacts validity is
With the preceding steps, we have verified that the uploaded artifacts have been uploaded by
Travis. Additionally, we have determined whether the PR can be trusted to have its previews
publicly accessible or whether further verification is necessary. The artifacts will be stored to
the PR's directory, but will not be publicly accessible unless the PR has been verified.
Essentially, as long as sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to
"project" the trust we have in a team's members through the PR and Travis to the build artifacts.
6. **Prevent overwriting previously deployed artifacts**.
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
preserved throughout their "lifetime"), the server that handles the upload (currently a Node.js
Express server) rejects uploads that target an existing directory.
_Note: A PR can contain multiple uploads; one for each SHA that was built on Travis._
6. **Prevent uploaded files from accessing anything outside their directory.**
7. **Prevent uploaded files from accessing anything outside their directory.**
Nginx (which is used to serve the uploaded artifacts) has been configured to not follow symlinks
outside of the directory where the build artifacts are stored.
@ -104,6 +121,11 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
- Each trusted PR author has full control over the content that is uploaded for their PRs. Part of
the security model relies on the trustworthiness of these authors.
- Adding the specified label on a PR and marking it as trusted, gives the author full control over
the content that is uploaded for the specific PR (e.g. by pushing more commits to it). The user
adding the label is responsible for ensuring that this control is not abused and that the PR is
either closed (one way of another) or the access is revoked.
- If anyone gets access to the `PREVIEW_DEPLOYMENT_TOKEN` (a.k.a. `NGBUILDS_IO_KEY` on
angular/angular) variable generated for each Travis job, they will be able to impersonate the
corresponding PR's author on the preview server for as long as the token is valid (currently 90

View File

@ -6,11 +6,11 @@
## Build docker image
- `<aio-builds-setup-dir>/scripts/build.sh [<name>[:<tag>] [--build-arg <NAME>=<value> ...]]`
- `<aio-builds-setup-dir>/scripts/create-image.sh [<name>[:<tag>] [--build-arg <NAME>=<value> ...]]`
- You can overwrite the default environment variables inside the image, by passing new values using
`--build-arg`.
**Note:** The build script has to execute docker commands with `sudo`.
**Note:** The script has to execute docker commands with `sudo`.
## Example
@ -25,7 +25,7 @@ The following commands would create a docker image from GitHub repo `foo/bar` to
--build-arg AIO_REPO_SLUG=foo/bar \
--build-arg AIO_DOMAIN_NAME=foobar-builds.io \
--build-arg AIO_GITHUB_ORGANIZATION=foo \
--build-arg AIO_GITHUB_TEMA_SLUGS=bar-core,bar-docs-authors
--build-arg AIO_GITHUB_TEAM_SLUGS=bar-core,bar-docs-authors
```
A full list of the available environment variables can be found

View File

@ -7,16 +7,16 @@ command:
```
sudo docker run \
-d \
--detach \
--dns 127.0.0.1 \
--name <instance-name> \
-p 80:80 \
-p 443:443 \
--publish 80:80 \
--publish 443:443 \
--restart unless-stopped \
[-v <host-cert-dir>:/etc/ssl/localcerts:ro] \
-v <host-secrets-dir>:/aio-secrets:ro \
-v <host-builds-dir>:/var/www/aio-builds \
[-v <host-logs-dir>:/var/log/aio] \
[--volume <host-cert-dir>:/etc/ssl/localcerts:ro] \
--volume <host-secrets-dir>:/aio-secrets:ro \
--volume <host-builds-dir>:/var/www/aio-builds \
[--volume <host-logs-dir>:/var/log/aio] \
<name>[:<tag>]
```
@ -27,7 +27,7 @@ can be found [here](https://docs.docker.com/engine/reference/run/).
sudo docker run \
# Start as a daemon.
-d \
--detach \
# Use the local DNS server.
# (This is necessary for mapping internal URLs, e.g. for the Node.js upload-server.)
@ -37,9 +37,9 @@ sudo docker run \
# Useful for running `docker` commands, e.g.: `docker stop <instance-name>`
--name <instance-name> \
# Map ports of the hosr VM (left) to ports of the docker container (right)
-p 80:80 \
-p 443:443 \
# Map ports of the host VM (left) to ports of the docker container (right)
--publish 80:80 \
--publish 443:443 \
# Automatically restart the container (unless it was explicitly stopped by the user).
# (This ensures that the container will be automatically started on boot.)
@ -48,22 +48,22 @@ sudo docker run \
# The directory the contains the SSL certificates.
# (See [here](vm-setup--create-host-dirs-and-files.md) for more info.)
# If not provided, the container will use self-signed certificates.
[-v <host-cert-dir>:/etc/ssl/localcerts:ro] \
[--volume <host-cert-dir>:/etc/ssl/localcerts:ro] \
# The directory the contains the secrets (e.g. GitHub token, JWT secret, etc).
# (See [here](vm-setup--set-up-secrets.md) for more info.)
-v <host-secrets-dir>:/aio-secrets:ro \
--volume <host-secrets-dir>:/aio-secrets:ro \
# The uploaded build artifacts will stored to and served from this directory.
# (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) -
# this will be a directory inside the disk.)
-v <host-builds-dir>:/var/www/aio-builds \
--volume <host-builds-dir>:/var/www/aio-builds \
# The directory where the logs are being kept.
# (See [here](vm-setup--create-host-dirs-and-files.md) for more info.)
# If not provided, the logs will be kept inside the container, which means they will be lost
# whenever a new container is created.
[-v <host-logs-dir>:/var/log/aio] \
[--volume <host-logs-dir>:/var/log/aio] \
# The name of the docker image to use (and an optional tag; defaults to `latest`).
# (See [here](vm-setup--create-docker-image.md) for instructions on how to create the iamge.)
@ -78,15 +78,15 @@ by the container for accesing secrets and SSL certificates and keeping the build
```
sudo docker run \
-d \
--detach \
--dns 127.0.0.1 \
--name foobar-builds-1 \
-p 80:80 \
-p 443:443 \
--publish 80:80 \
--publish 443:443 \
--restart unless-stopped \
-v /etc/ssl/localcerts:/etc/ssl/localcerts:ro \
-v /foobar-secrets:/aio-secrets:ro \
-v /mnt/disks/foobar-builds:/var/www/aio-builds \
-v /foobar-logs:/var/log/aio \
--volume /etc/ssl/localcerts:/etc/ssl/localcerts:ro \
--volume /foobar-secrets:/aio-secrets:ro \
--volume /mnt/disks/foobar-builds:/var/www/aio-builds \
--volume /foobar-logs:/var/log/aio \
foobar-builds
```

View File

@ -0,0 +1,52 @@
# VM setup - Update docker container
## Overview
Assuming you have cloned the repository containing the preview server code (as described
[here](vm-setup--create-docker-image.md)), you can use the `update-preview-server.sh` script on the
VM host to update the preview server based on changes in the source code.
The script will pull the latest changes from the origin's master branch and examine if there have
been any changes in files inside the preview server source code directory (see below). If there are,
it will create a new image and verify that is works as expected. Finally, it will stop and remove
the old docker container and image, create a new container based on the new image and start it.
The script assumes that the preview server source code is in the repository's
`aio/aio-builds-setup/` directory and expects the following inputs:
- **$1**: `HOST_REPO_DIR`
- **$2**: `HOST_LOCALCERTS_DIR`
- **$3**: `HOST_SECRETS_DIR`
- **$4**: `HOST_BUILDS_DIR`
- **$5**: `HOST_LOGS_DIR`
See [here](vm-setup--create-host-dirs-and-files.md) for more info on what each input directory is
used for.
**Note 1:** The script has to execute docker commands with `sudo`.
**Note 2:** Make sure the user that executes the script has access to update the repository
## Run the script manually
You may choose to manually run the script, when necessary. Example:
```
update-preview-server.sh \
/path/to/repo \
/path/to/localcerts \
/path/to/secrets \
/path/to/builds \
/path/to/logs
```
## Run the script automatically
You may choose to automatically trigger the script, e.g. using a cronjob. For example, the following
cronjob entry would run the script every hour and update the preview server (assuming the user has
the necessary permissions):
```
# Periodically check for changes and update the preview server (if necessary)
*/30 * * * * /path/to/update-preview-server.sh /path/to/repo /path/to/localcerts /path/to/secrets /path/to/builds /path/to/logs
```

View File

@ -2,14 +2,16 @@
set -eux -o pipefail
# Set up env
source "`dirname $0`/env.sh"
source "`dirname $0`/_env.sh"
readonly defaultImageNameAndTag="aio-builds:latest"
# Build `scripts-js/`
cd "$SCRIPTS_JS_DIR"
yarn install
yarn run build
cd -
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
(
cd "$SCRIPTS_JS_DIR"
yarn install --freeze-lockfile --non-interactive
yarn build
)
# Create docker image
readonly nameAndOptionalTag=${1:-$defaultImageNameAndTag}

View File

@ -2,10 +2,11 @@
set -eux -o pipefail
# Set up env
source "`dirname $0`/env.sh"
source "`dirname $0`/_env.sh"
# Test `scripts-js/`
cd "$SCRIPTS_JS_DIR"
yarn install
yarn test
cd -
(
cd "$SCRIPTS_JS_DIR"
yarn install --freeze-lockfile --non-interactive
yarn test
)

View File

@ -1,13 +0,0 @@
#!/bin/bash
set -eux -o pipefail
# Set up env
source "`dirname $0`/env.sh"
# Preverify PR
AIO_GITHUB_ORGANIZATION="angular" \
AIO_GITHUB_TEAM_SLUGS="angular-core,aio-contributors" \
AIO_GITHUB_TOKEN=$(echo ${GITHUB_TEAM_MEMBERSHIP_CHECK_KEY} | rev) \
AIO_REPO_SLUG=$TRAVIS_REPO_SLUG \
AIO_PREVERIFY_PR=$TRAVIS_PULL_REQUEST \
node "$SCRIPTS_JS_DIR/dist/lib/upload-server/index-preverify-pr"

View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -eux -o pipefail
exec 3>&1
echo "[`date`] - Updating the preview server..."
# Input
readonly HOST_REPO_DIR=$1
readonly HOST_LOCALCERTS_DIR=$2
readonly HOST_SECRETS_DIR=$3
readonly HOST_BUILDS_DIR=$4
readonly HOST_LOGS_DIR=$5
# Constants
readonly PROVISIONAL_IMAGE_NAME=aio-builds:provisional
readonly LATEST_IMAGE_NAME=aio-builds:latest
readonly CONTAINER_NAME=aio
# Run
(
cd "$HOST_REPO_DIR"
readonly lastDeployedCommit=$(git rev-parse HEAD)
echo "Currently at commit $lastDeployedCommit."
# Pull latest master from origin.
git pull origin master
# Do not update the server unless files inside `aio-builds-setup/` have changed
# or the last attempt failed (identified by the provisional image still being around).
readonly relevantChangedFilesCount=$(git diff --name-only $lastDeployedCommit...HEAD | grep -P "^aio/aio-builds-setup/" | wc -l)
readonly lastAttemptFailed=$(sudo docker rmi "$PROVISIONAL_IMAGE_NAME" >> /dev/fd/3 && echo "true" || echo "false")
if [[ $relevantChangedFilesCount -eq 0 ]] && [[ "$lastAttemptFailed" != "true" ]]; then
echo "Skipping update because no relevant files have been touched."
exit 0
fi
# Create and verify a new docker image.
aio/aio-builds-setup/scripts/create-image.sh "$PROVISIONAL_IMAGE_NAME"
readonly imageVerified=$(sudo docker run --dns 127.0.0.1 --rm --volume $HOST_SECRETS_DIR:/aio-secrets:ro "$PROVISIONAL_IMAGE_NAME" /bin/bash -c "aio-init && aio-health-check && aio-verify-setup" >> /dev/fd/3 && echo "true" || echo "false")
if [[ "$imageVerified" != "true" ]]; then
echo "Failed to verify new docker image. Aborting update!"
exit 1
fi
# Remove the old container and replace the docker image.
sudo docker stop "$CONTAINER_NAME" || true
sudo docker rm "$CONTAINER_NAME" || true
sudo docker rmi "$LATEST_IMAGE_NAME" || true
sudo docker tag "$PROVISIONAL_IMAGE_NAME" "$LATEST_IMAGE_NAME"
sudo docker rmi "$PROVISIONAL_IMAGE_NAME"
# Create and start a docker container based on the new image.
sudo docker run \
--detach \
--dns 127.0.0.1 \
--name "$CONTAINER_NAME" \
--publish 80:80 \
--publish 443:443 \
--restart unless-stopped \
--volume $HOST_LOCALCERTS_DIR:/etc/ssl/localcerts:ro \
--volume $HOST_SECRETS_DIR:/aio-secrets:ro \
--volume $HOST_BUILDS_DIR:/var/www/aio-builds \
--volume $HOST_LOGS_DIR:/var/log/aio \
"$LATEST_IMAGE_NAME"
echo "The new docker image has been successfully deployed."
)

View File

@ -43,22 +43,18 @@ dist/
**/app/**/*.ajs.js
# aot
**/*.ngfactory.ts
**/*.ngsummary.json
**/*.shim.ngstyle.ts
**/*.metadata.json
!aot/bs-config.json
!aot/index.html
*/aot/**/*
!*/aot/bs-config.json
!*/aot/index.html
!rollup-config.js
# i18n
!i18n/src/systemjs-text-plugin.js
# testing
!testing/src/browser-test-shim.js
!testing/karma*.js
# TS to JS
!ts-to-js/js*/**/*.js
ts-to-js/js*/**/system*.js
# webpack
!webpack/**/config/*.js
!webpack/**/*webpack*.js

View File

@ -14,13 +14,13 @@
<h1>Example Snippets</h1>
<!-- #docregion ngClass -->
<div [ngClass]="{active: isActive}">
<div [ngClass]="{'active': isActive}">
<!-- #enddocregion ngClass -->
[ngClass] active
</div>
<!-- #docregion ngClass -->
<div [ngClass]="{active: isActive,
shazam: isImportant}">
<div [ngClass]="{'active': isActive,
'shazam': isImportant}">
<!-- #enddocregion ngClass -->
[ngClass] active and boldly important
</div>
@ -57,7 +57,7 @@
<p></p>
<!-- #docregion ngStyle -->
<div [ngStyle]="{color: colorPreference}">
<div [ngStyle]="{'color': colorPreference}">
<!-- #enddocregion ngStyle -->
color preference #1
</div>

View File

@ -19,7 +19,7 @@ export class AppComponent {
movie: IMovie = null;
movies: IMovie[] = [];
showImage = true;
title: string = 'AngularJS to Angular Quick Ref Cookbook';
title = 'AngularJS to Angular Quick Ref Cookbook';
toggleImage(event: UIEvent) {
this.showImage = !this.showImage;
this.eventType = (event && event.type) || 'not provided';

View File

@ -19,7 +19,7 @@ import { MovieService } from './movie.service';
export class MovieListComponent {
// #enddocregion class
favoriteHero: string;
showImage: boolean = false;
showImage = false;
movies: IMovie[];
// #docregion di

View File

@ -1 +0,0 @@
**/*.js

View File

@ -335,17 +335,17 @@ describe('Animation Tests', () => {
});
}
function getBoundingClientWidth(el: ElementFinder): promise.Promise<number> {
function getBoundingClientWidth(el: ElementFinder) {
return browser.executeScript(
'return arguments[0].getBoundingClientRect().width',
el.getWebElement()
);
) as PromiseLike<number>;
}
function getOffsetWidth(el: ElementFinder): promise.Promise<number> {
function getOffsetWidth(el: ElementFinder) {
return browser.executeScript(
'return arguments[0].offsetWidth',
el.getWebElement()
);
) as PromiseLike<number>;
}
});

View File

@ -1,5 +1,6 @@
// #docregion animations-module
// #docplaster
import { NgModule } from '@angular/core';
// #docregion animations-module
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// #enddocregion animations-module
@ -15,11 +16,12 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
import { HeroListGroupsComponent } from './hero-list-groups.component';
import { HeroListMultistepComponent } from './hero-list-multistep.component';
import { HeroListTimingsComponent } from './hero-list-timings.component';
// #docregion animations-module
// #docregion animation-module
@NgModule({
imports: [ BrowserModule, BrowserAnimationsModule ],
// #enddocregion animation-module
// ... more stuff ...
// #enddocregion animations-module
declarations: [
HeroTeamBuilderComponent,
HeroListBasicComponent,
@ -34,5 +36,8 @@ import { HeroListTimingsComponent } from './hero-list-timings.component';
HeroListGroupsComponent
],
bootstrap: [ HeroTeamBuilderComponent ]
// #docregion animations-module
})
export class AppModule { }
// #enddocregion animations-module

View File

@ -10,7 +10,7 @@ import {
transition
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-auto',
@ -43,5 +43,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListAutoComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -14,7 +14,7 @@ import {
} from '@angular/animations';
// #enddocregion imports
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-basic',
@ -66,5 +66,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListBasicComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -13,7 +13,7 @@ import {
} from '@angular/animations';
// #enddocregion imports
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-combined-transitions',
@ -55,5 +55,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListCombinedTransitionsComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -10,7 +10,7 @@ import {
transition
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-enter-leave-states',
@ -59,5 +59,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListEnterLeaveStatesComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -10,7 +10,7 @@ import {
transition
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-enter-leave',
@ -47,5 +47,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListEnterLeaveComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -11,7 +11,7 @@ import {
group
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-groups',
@ -76,5 +76,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListGroupsComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -12,7 +12,7 @@ import {
} from '@angular/animations';
// #enddocregion imports
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-inline-styles',
@ -56,5 +56,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListInlineStylesComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -12,7 +12,7 @@ import {
AnimationEvent
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-multistep',
@ -59,7 +59,7 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListMultistepComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
animationStarted(event: AnimationEvent) {
console.warn('Animation started: ', event);

View File

@ -10,7 +10,7 @@ import {
transition
} from '@angular/animations';
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-timings',
@ -44,7 +44,7 @@ import { Heroes } from './hero.service';
animate('0.2s ease-in')
]),
transition('* => void', [
animate('0.2s 10 ease-out', style({
animate('0.2s 0.1s ease-out', style({
opacity: 0,
transform: 'translateX(100%)'
}))
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListTimingsComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -13,7 +13,7 @@ import {
} from '@angular/animations';
// #enddocregion imports
import { Heroes } from './hero.service';
import { Hero } from './hero.service';
@Component({
selector: 'hero-list-twoway',
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
// #enddocregion animationdef
})
export class HeroListTwowayComponent {
@Input() heroes: Heroes;
@Input() heroes: Hero[];
}

View File

@ -1,40 +1,41 @@
import { Component } from '@angular/core';
import { Heroes } from './hero.service';
import { Hero, HeroService } from './hero.service';
@Component({
selector: 'hero-team-builder',
template: `
<div class="buttons">
<button [disabled]="!heroes.canAdd()" (click)="heroes.addInactive()">Add inactive hero</button>
<button [disabled]="!heroes.canAdd()" (click)="heroes.addActive()">Add active hero</button>
<button [disabled]="!heroes.canRemove()" (click)="heroes.remove()">Remove hero</button>
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
</div>
<div class="columns">
<div class="column">
<h4>Basic State</h4>
<p>Switch between active/inactive on click.</p>
<hero-list-basic [heroes]=heroes></hero-list-basic>
<hero-list-basic [heroes]="heroes"></hero-list-basic>
</div>
<div class="column">
<h4>Styles inline in transitions</h4>
<p>Animated effect on click, no persistend end styles.</p>
<hero-list-inline-styles [heroes]=heroes></hero-list-inline-styles>
<p>Animated effect on click, no persistent end styles.</p>
<hero-list-inline-styles [heroes]="heroes"></hero-list-inline-styles>
</div>
<div class="column">
<h4>Combined transition syntax</h4>
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
<hero-list-combined-transitions [heroes]=heroes></hero-list-combined-transitions>
<hero-list-combined-transitions [heroes]="heroes"></hero-list-combined-transitions>
</div>
<div class="column">
<h4>Two-way transition syntax</h4>
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
<hero-list-twoway [heroes]=heroes></hero-list-twoway>
<hero-list-twoway [heroes]="heroes"></hero-list-twoway>
</div>
<div class="column">
<h4>Enter & Leave</h4>
<p>Enter and leave animations using the void state.</p>
<hero-list-enter-leave [heroes]=heroes></hero-list-enter-leave>
<hero-list-enter-leave [heroes]="heroes"></hero-list-enter-leave>
</div>
</div>
<div class="columns">
@ -44,27 +45,27 @@ import { Heroes } from './hero.service';
Enter and leave animations combined with active/inactive state animations.
Different enter and leave transitions depending on state.
</p>
<hero-list-enter-leave-states [heroes]=heroes></hero-list-enter-leave-states>
<hero-list-enter-leave-states [heroes]="heroes"></hero-list-enter-leave-states>
</div>
<div class="column">
<h4>Auto Style Calc</h4>
<p>Leave animation from the current computed height using the auto-style value *.</p>
<hero-list-auto [heroes]=heroes></hero-list-auto>
<hero-list-auto [heroes]="heroes"></hero-list-auto>
</div>
<div class="column">
<h4>Different Timings</h4>
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
<hero-list-timings [heroes]=heroes></hero-list-timings>
<hero-list-timings [heroes]="heroes"></hero-list-timings>
</div>
<div class="column">
<h4>Multiple Keyframes</h4>
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
<hero-list-multistep [heroes]=heroes></hero-list-multistep>
<hero-list-multistep [heroes]="heroes"></hero-list-multistep>
</div>
<div class="column">
<h4>Parallel Groups</h4>
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
<hero-list-groups [heroes]=heroes></hero-list-groups>
<hero-list-groups [heroes]="heroes"></hero-list-groups>
</div>
</div>
`,
@ -87,8 +88,12 @@ import { Heroes } from './hero.service';
min-height: 6em;
}
`],
providers: [Heroes]
providers: [HeroService]
})
export class HeroTeamBuilderComponent {
constructor(private heroes: Heroes) { }
heroes: Hero[];
constructor(private heroService: HeroService) {
this.heroes = heroService.heroes;
}
}

View File

@ -1,16 +1,16 @@
import { Injectable } from '@angular/core';
class Hero {
constructor(public name: string,
public state = 'inactive') {
}
// #docregion hero
export class Hero {
constructor(public name: string, public state = 'inactive') { }
toggleState() {
this.state = (this.state === 'active' ? 'inactive' : 'active');
this.state = this.state === 'active' ? 'inactive' : 'active';
}
}
// #enddocregion hero
let ALL_HEROES = [
const ALL_HEROES = [
'Windstorm',
'RubberMan',
'Bombasto',
@ -25,36 +25,30 @@ let ALL_HEROES = [
].map(name => new Hero(name));
@Injectable()
export class Heroes implements Iterable<Hero> {
export class HeroService {
currentHeroes: Hero[] = [];
[Symbol.iterator]() {
return this.currentHeroes.values();
}
heroes: Hero[] = [];
canAdd() {
return this.currentHeroes.length < ALL_HEROES.length;
return this.heroes.length < ALL_HEROES.length;
}
canRemove() {
return this.currentHeroes.length > 0;
return this.heroes.length > 0;
}
addActive() {
let hero = ALL_HEROES[this.currentHeroes.length];
hero.state = 'active';
this.currentHeroes.push(hero);
addActive(active = true) {
let hero = ALL_HEROES[this.heroes.length];
hero.state = active ? 'active' : 'inactive';
this.heroes.push(hero);
}
addInactive() {
let hero = ALL_HEROES[this.currentHeroes.length];
hero.state = 'inactive';
this.currentHeroes.push(hero);
this.addActive(false);
}
remove() {
this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
this.heroes.length -= 1;
}
}

View File

@ -1,7 +0,0 @@
**/*.ngfactory.ts
**/*.ngsummary.json
**/*.shim.ngstyle.ts
**/*.metadata.json
dist
!app/tsconfig.json
!rollup-config.js

View File

@ -1,8 +1,7 @@
// #docregion
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
import uglify from 'rollup-plugin-uglify';
// #docregion config
export default {
@ -30,5 +29,5 @@ export default {
uglify()
// #enddocregion uglify
]
}
};
// #enddocregion config

View File

@ -1,4 +1,4 @@
'use strict'; // necessary for es6 output in node
'use strict'; // necessary for es6 output in node
import { protractor, browser, element, by, ElementFinder } from 'protractor';

View File

@ -6,7 +6,7 @@ import { Hero } from './hero';
const HEROES = [
new Hero('Windstorm', 'Weather mastery'),
new Hero('Mr. Nice', 'Killing them with kindness'),
new Hero('Magneta', 'Manipulates metalic objects')
new Hero('Magneta', 'Manipulates metallic objects')
];
@Injectable()

View File

@ -1,4 +1,4 @@
'use strict'; // necessary for es6 output in node
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor';

View File

@ -28,7 +28,7 @@ export class HighlightDirective {
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
// #enddocregion mouse-methods,
// #enddocregion mouse-methods,
// #docregion color
@Input() highlightColor: string;

View File

@ -1,5 +1,5 @@
/* tslint:disable:member-ordering */
// #docregion imports,
// #docregion imports,
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
// #enddocregion imports

View File

@ -1,45 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
Thumbs.db
!src/styles.css
!karma.conf.js
!protractor.conf.js

View File

@ -9,6 +9,6 @@ describe('cli-quickstart App', () => {
it('should display message saying app works', () => {
let pageTitle = element(by.css('app-root h1')).getText();
expect(pageTitle).toEqual('My First Angular App');
expect(pageTitle).toEqual('Welcome to My First Angular App!!');
});
});

View File

@ -9,6 +9,6 @@ describe('my-app App', function() {
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('app works!');
expect(page.getParagraphText()).toEqual('Welcome to app!!');
});
});

View File

@ -1,18 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016"
],
"outDir": "../dist/out-tsc-e2e",
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es6",
"types":[
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}

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