Compare commits
213 Commits
Author | SHA1 | Date | |
---|---|---|---|
b69f0faee6 | |||
9c4fcd370a | |||
8b8b4cb794 | |||
53378749ef | |||
3e61bf76c4 | |||
b4fab438ea | |||
426191bc4e | |||
6307581f54 | |||
591d894329 | |||
48772f189f | |||
70b1ff11d5 | |||
f1c9197e07 | |||
991f8ad687 | |||
efa45b9299 | |||
45ae14c461 | |||
5ea9b62d58 | |||
276357780c | |||
2108c23b42 | |||
d48b7d3a8e | |||
00c97417e3 | |||
16e6c6e7f9 | |||
f9c4c3710e | |||
eb90ba1321 | |||
9e626415eb | |||
f632ca4284 | |||
620c2161f6 | |||
30beb52673 | |||
fe09e10d02 | |||
ee7d134d8e | |||
2ab90578f3 | |||
8f7cce38f8 | |||
a3180937c1 | |||
3d52675c1b | |||
14afb9d314 | |||
62b973773f | |||
8bdc921502 | |||
cd99c51a40 | |||
3fb93fdb5e | |||
cf5752738a | |||
00de9ff531 | |||
12a2099265 | |||
cdc0c8ff58 | |||
14fbc2e7c0 | |||
ea8fe9316e | |||
b658afc3de | |||
706a48e9c1 | |||
d472aa201a | |||
6df57adab3 | |||
54df1e287d | |||
6d55a807cd | |||
670f2e5599 | |||
408bb89e8f | |||
ed45830044 | |||
37b200010b | |||
571adbe40e | |||
10bfa8a11e | |||
c460757d2e | |||
1a476b81ab | |||
27b87e893c | |||
7080fccb38 | |||
54f96bfe27 | |||
61e6618429 | |||
e1fea47400 | |||
f31b0d664d | |||
79b634692a | |||
6909171c0a | |||
ec4ae60fb8 | |||
7559b7898e | |||
7728d7efc6 | |||
2c1ef08466 | |||
5f1dcfc228 | |||
f9f2b23afc | |||
ad3661cff7 | |||
804173c8a6 | |||
a0c790e7bd | |||
7205d7a568 | |||
e57eec9b13 | |||
5e1b339f46 | |||
6b23e2f427 | |||
08dba3f93b | |||
2624fa5ae2 | |||
a1458d9cb9 | |||
ffbdf74e92 | |||
589826f5e5 | |||
6f5e6374f8 | |||
a309d62806 | |||
9699c02642 | |||
6040c66e95 | |||
347fec5e8c | |||
c7bbf0573a | |||
c2fda10a73 | |||
b38edeb055 | |||
2407beb17e | |||
b6418039e0 | |||
7dd7722220 | |||
9b14f62387 | |||
107655e43f | |||
162bddf0b8 | |||
81c18b7241 | |||
0c4b561999 | |||
563577eeab | |||
17e000a678 | |||
b8806a656f | |||
ee5cd52d5f | |||
84a72b774e | |||
9e28f58ec8 | |||
10e2db000c | |||
098d35ef64 | |||
d5a8f6ebfe | |||
9415e6603f | |||
927ea2462e | |||
1d6465b596 | |||
58012df46f | |||
21c5b177ef | |||
bc10291d42 | |||
9344c86242 | |||
6b5b3a7269 | |||
58c5e86209 | |||
1657fa756f | |||
2ff85c6790 | |||
353de5191c | |||
ea5ac89b50 | |||
f5ce60e6bc | |||
7a2903993a | |||
a29a0df183 | |||
746fed453a | |||
64e1b9cc9a | |||
0947f4a358 | |||
f10d5a5572 | |||
93f57d7d70 | |||
af1c4db0e4 | |||
4f43029570 | |||
8a09015211 | |||
f4b7bf7e38 | |||
d20313698d | |||
74954f1a8d | |||
368aed087c | |||
975341a77e | |||
c112232fa3 | |||
36348325de | |||
fdbe62f112 | |||
af9ba91e5c | |||
b840ec3684 | |||
b38552f36e | |||
572885dcc8 | |||
33906489ad | |||
59299de374 | |||
f99793700f | |||
77860a0023 | |||
93d834a0b2 | |||
63a5f33e99 | |||
20eb5cfd59 | |||
4ab7353a9f | |||
eb23460e09 | |||
2e8efdaffa | |||
3cfb13f746 | |||
7de1ae2be8 | |||
341b812a10 | |||
09512c7770 | |||
1ec0a53fec | |||
3607a48a08 | |||
62957fa515 | |||
ab81c1c068 | |||
25234227ec | |||
71a8ef5a15 | |||
4dd6deca16 | |||
f8db05ef7d | |||
623b9c6e58 | |||
8a547eeee0 | |||
4211432fc8 | |||
b8c39cdf71 | |||
9c7a84de51 | |||
dbc6a4cb12 | |||
784410e3c8 | |||
90a5a1ef43 | |||
301f99cd6c | |||
64e63b9422 | |||
b192dd5761 | |||
96aa3bb135 | |||
8abc1df2c1 | |||
f5eb528a5c | |||
5cf06a9f3f | |||
ab90f63575 | |||
150d271f79 | |||
a686eb2c9e | |||
bfa788935a | |||
64fa100a71 | |||
5fae987bfa | |||
9c4cda1c7d | |||
d9cbe56b63 | |||
86df7108b0 | |||
e7a4f92be7 | |||
76af452d29 | |||
11dfb685f4 | |||
d363aa0aa4 | |||
209d74c342 | |||
fec8f6febe | |||
ee9daaf4c8 | |||
4164369db4 | |||
747b6a61b8 | |||
9ed836c939 | |||
e4c82443f9 | |||
a2f232166b | |||
668f9ede65 | |||
b784829512 | |||
ad4fee7053 | |||
2d31e17251 | |||
39cff565ee | |||
203c5ba1b3 | |||
eda7bb5c3e | |||
1480a30050 | |||
d6087f75e2 | |||
dc084a5bdf |
18
.bazelrc
18
.bazelrc
@ -1,18 +1,6 @@
|
||||
# Make compilation fast, by keeping a few copies of the compilers
|
||||
# running as daemons, and cache SourceFile AST's to reduce parse time.
|
||||
build --strategy=TypeScriptCompile=worker
|
||||
build --strategy=AngularTemplateCompile=worker
|
||||
|
||||
# Don't create bazel-* symlinks in the WORKSPACE directory.
|
||||
# These require .gitignore and may scare users.
|
||||
# Also, it's a workaround for https://github.com/bazelbuild/rules_typescript/issues/12
|
||||
# which affects the common case of having `tsconfig.json` in the WORKSPACE directory.
|
||||
#
|
||||
# Instead, you should run `bazel info bazel-bin` to find out where the outputs went.
|
||||
build --symlink_prefix=/
|
||||
# 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
|
||||
|
||||
# Don't print all the .d.ts output locations after builds
|
||||
build --show_result=0
|
@ -1,50 +1,36 @@
|
||||
# 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
|
||||
defaults: &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"
|
||||
- image: angular/ngcontainer
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
lint:
|
||||
<<: *job_defaults
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# After checkout, rebase on top of master.
|
||||
# Similar to travis behavior, but not quite the same.
|
||||
# See https://discuss.circleci.com/t/1662
|
||||
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
||||
- run: yarn install --frozen-lockfile --non-interactive
|
||||
- run: npm install
|
||||
- run: npm run postinstall
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
<<: *job_defaults
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build packages/...
|
||||
- run: bazel test @angular//...
|
||||
- run: bazel run @io_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
paths:
|
||||
- "node_modules"
|
||||
|
||||
|
14
.github/ISSUE_TEMPLATE.md
vendored
14
.github/ISSUE_TEMPLATE.md
vendored
@ -1,14 +1,14 @@
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
|
||||
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
||||
ISSUES MISSING IMPORTANT INFORMATION MIGHT BE CLOSED WITHOUT INVESTIGATION.
|
||||
-->
|
||||
|
||||
## I'm submitting a...
|
||||
## 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 -->
|
||||
[ ] Regression (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
|
||||
@ -32,7 +32,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
|
||||
<!-- Describe the motivation or the concrete use case. -->
|
||||
|
||||
|
||||
## Environment
|
||||
## Please tell us about your environment
|
||||
|
||||
<pre><code>
|
||||
Angular version: X.Y.Z
|
||||
@ -49,8 +49,8 @@ Browser:
|
||||
- [ ] Edge version XX
|
||||
|
||||
For Tooling issues:
|
||||
- Node version: XX <!-- run `node --version` -->
|
||||
- Platform: <!-- Mac, Linux, Windows -->
|
||||
- Node version: XX <!-- use `node --version` -->
|
||||
- Platform: <!-- Mac, Linux, Windows -->
|
||||
|
||||
Others:
|
||||
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,5 +1,5 @@
|
||||
## PR Checklist
|
||||
Please check if your PR fulfills the following requirements:
|
||||
Does 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)
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,10 +2,8 @@
|
||||
|
||||
/dist/
|
||||
bazel-*
|
||||
e2e_test.*
|
||||
node_modules
|
||||
bower_components
|
||||
tools/gulp-tasks/cldr/cldr-data/
|
||||
|
||||
# Include when developing application packages.
|
||||
pubspec.lock
|
||||
|
@ -7,12 +7,9 @@
|
||||
#
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# brocco - Mike Brocchi
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# filipesilva - Filipe Silva
|
||||
# Foxandxss - Jesús Rodríguez
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# juleskremer - Jules Kremer
|
||||
@ -63,19 +60,6 @@ groups:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
bazel:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "WORKSPACE"
|
||||
- "*.bazel"
|
||||
- "*.bzl"
|
||||
- "packages/bazel/*"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- chuckjaz
|
||||
- vikerman #fallback
|
||||
|
||||
build-and-ci:
|
||||
conditions:
|
||||
files:
|
||||
@ -85,6 +69,7 @@ groups:
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "tools/public_api_guard/*"
|
||||
- "aio/*"
|
||||
users:
|
||||
@ -104,6 +89,7 @@ groups:
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
|
||||
core:
|
||||
conditions:
|
||||
files:
|
||||
@ -118,7 +104,7 @@ groups:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animations/*"
|
||||
- "packages/animation/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
@ -147,23 +133,11 @@ groups:
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler-cli/ngtools:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- hansl
|
||||
- filipesilva #fallback
|
||||
- brocco #fallback
|
||||
|
||||
compiler-cli:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "packages/compiler-cli/*"
|
||||
- "packages/bazel/*"
|
||||
exclude:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "packages/compiler-cli/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- chuckjaz
|
||||
@ -175,10 +149,7 @@ groups:
|
||||
common:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "packages/common/*"
|
||||
exclude:
|
||||
- "packages/common/http/*"
|
||||
- "packages/common/*"
|
||||
users:
|
||||
- pkozlowski-opensource #primary
|
||||
- vicb
|
||||
@ -198,7 +169,6 @@ groups:
|
||||
http:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/common/http/*"
|
||||
- "packages/http/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
@ -269,15 +239,7 @@ groups:
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
service-worker:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/service-worker/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
|
||||
benchpress:
|
||||
conditions:
|
||||
@ -306,18 +268,16 @@ groups:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "aio/content/*"
|
||||
exclude:
|
||||
- "aio/content/marketing/*"
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
- "aio/content/examples/*"
|
||||
- "aio/content/guide/*"
|
||||
- "aio/content/images/*"
|
||||
- "aio/content/tutorial/*"
|
||||
- "aio/content/file-not-found.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
@ -325,13 +285,15 @@ groups:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "aio/content/marketing/*"
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
- "aio/content/*"
|
||||
exclude:
|
||||
- "aio/content/examples/*"
|
||||
- "aio/content/guide/*"
|
||||
- "aio/content/images/*"
|
||||
- "aio/content/tutorial/*"
|
||||
- "aio/content/file-not-found.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
13
.travis.yml
13
.travis.yml
@ -1,6 +1,5 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
@ -48,23 +47,19 @@ env:
|
||||
- CI_MODE=e2e_2
|
||||
- CI_MODE=js
|
||||
- CI_MODE=saucelabs_required
|
||||
# deactivated, see #19768
|
||||
# - CI_MODE=browserstack_required
|
||||
- CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
- CI_MODE=docs_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_optional
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
- CI_MODE=aio_e2e
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
- env: "CI_MODE=aio_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
|
||||
|
18
BUILD
Normal file
18
BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
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/**/*.d.ts",
|
||||
"node_modules/rxjs/**/*.d.ts",
|
||||
"node_modules/@types/**/*.d.ts",
|
||||
]),
|
||||
)
|
31
BUILD.bazel
31
BUILD.bazel
@ -1,31 +0,0 @@
|
||||
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",
|
||||
# Performance workaround: list individual files
|
||||
# Reduces the number of files as inputs to nodejs_binary:
|
||||
# bazel query "deps(:node_modules)" | wc -l
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [
|
||||
"jasmine",
|
||||
"typescript",
|
||||
"zone.js",
|
||||
"rxjs",
|
||||
"@types",
|
||||
"tsickle",
|
||||
"hammerjs",
|
||||
"protobufjs",
|
||||
"bytebuffer",
|
||||
"reflect-metadata",
|
||||
"minimist",
|
||||
] for ext in [
|
||||
"*.js",
|
||||
"*.json",
|
||||
"*.d.ts",
|
||||
]]),
|
||||
)
|
4555
CHANGELOG.md
4555
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
||||
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
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`.
|
||||
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 [Stack Overflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
|
||||
|
||||
Stack Overflow is a much better place to ask questions since:
|
||||
|
||||
@ -25,7 +25,7 @@ Stack Overflow is a much better place to ask questions since:
|
||||
- questions and answers stay available for public viewing so your question / answer might help someone else
|
||||
- Stack Overflow's voting system assures that the best answers are prominently visible.
|
||||
|
||||
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
|
||||
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting 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].
|
||||
|
||||
@ -220,8 +220,8 @@ The following is the list of supported scopes:
|
||||
* **platform-webworker**
|
||||
* **platform-webworker-dynamic**
|
||||
* **router**
|
||||
* **service-worker**
|
||||
* **upgrade**
|
||||
* **tsc-wrapped**
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
|
13
README.md
13
README.md
@ -1,30 +1,31 @@
|
||||
[](https://travis-ci.org/angular/angular)
|
||||
[](https://circleci.com/gh/angular/angular/tree/master)
|
||||
[](https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06)
|
||||
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](https://www.npmjs.com/@angular/core)
|
||||
|
||||
)
|
||||
|
||||
[](https://saucelabs.com/u/angular2-ci)
|
||||
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
||||
|
||||
*Safari (7+), iOS (7+) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
||||
Angular
|
||||
=========
|
||||
|
||||
# Angular
|
||||
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript (JS) and other languages.
|
||||
|
||||
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
|
||||
guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
|
||||
|
||||
[browserstack]: https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06
|
||||
[browserstack]: https://www.browserstack.com/
|
||||
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[quickstart]: https://angular.io/docs/ts/latest/quickstart.html
|
||||
[ng]: http://angular.io
|
||||
|
23
WORKSPACE
23
WORKSPACE
@ -1,24 +1,11 @@
|
||||
workspace(name = "angular_src")
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
remote = "https://github.com/bazelbuild/rules_nodejs.git",
|
||||
# TODO(alexeagle): use the correct tag here.
|
||||
commit = "2c6243df53fd33fdab283ebdd01582e4eb815db8",
|
||||
name = "io_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
commit = "3a8404d",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
local_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
path = "node_modules/@bazel/typescript",
|
||||
)
|
||||
|
||||
local_repository(
|
||||
name = "angular",
|
||||
path = "packages/bazel",
|
||||
)
|
||||
node_repositories(package_json = "//:package.json")
|
||||
|
@ -31,9 +31,8 @@
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"next": "environments/environment.next.ts",
|
||||
"stable": "environments/environment.stable.ts",
|
||||
"archive": "environments/environment.archive.ts"
|
||||
"stage": "environments/environment.stage.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -13,11 +13,7 @@ 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 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 setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
||||
|
||||
* `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.
|
||||
@ -30,33 +26,32 @@ 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. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
|
||||
* `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 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 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
|
||||
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`.
|
||||
with `yarn http-server -- dist -p 4200`.
|
||||
|
||||
For more details see #16745.
|
||||
|
||||
|
||||
## Guide to authoring
|
||||
|
||||
There are two types of content in the documentation:
|
||||
There are two types of content in the documentatation:
|
||||
|
||||
* **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.
|
||||
@ -111,16 +106,8 @@ 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).
|
||||
|
@ -156,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 --frozen-lockfile
|
||||
RUN yarn install --production
|
||||
|
||||
|
||||
# Set up health check
|
||||
|
@ -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 address for these domains.
|
||||
# Force an IP addres 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
|
||||
|
@ -88,21 +88,6 @@ 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;
|
||||
|
@ -18,22 +18,49 @@ export class BuildCreator extends EventEmitter {
|
||||
}
|
||||
|
||||
// Methods - Public
|
||||
public changePrVisibility(pr: string, makePublic: boolean): Promise<void> {
|
||||
const {oldPrDir, newPrDir} = this.getCandidatePrDirs(pr, makePublic);
|
||||
|
||||
return Promise.
|
||||
all([this.exists(oldPrDir), this.exists(newPrDir)]).
|
||||
then(([oldPrDirExisted, newPrDirExisted]) => {
|
||||
if (!oldPrDirExisted) {
|
||||
throw new UploadError(404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
|
||||
} else if (newPrDirExisted) {
|
||||
throw new UploadError(409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
|
||||
}
|
||||
|
||||
return Promise.resolve().
|
||||
then(() => shell.mv(oldPrDir, newPrDir)).
|
||||
then(() => this.listShasByDate(newPrDir)).
|
||||
then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))).
|
||||
then(() => undefined);
|
||||
}).
|
||||
catch(err => {
|
||||
if (!(err instanceof UploadError)) {
|
||||
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
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 {oldPrDir: otherVisPrDir, newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic);
|
||||
const shaDir = path.join(prDir, sha);
|
||||
let dirToRemoveOnError: string;
|
||||
|
||||
return Promise.resolve().
|
||||
then(() => this.exists(otherVisPrDir)).
|
||||
// If the same PR exists with different visibility, update the visibility first.
|
||||
then(() => this.updatePrVisibility(pr, isPublic)).
|
||||
then(otherVisPrDirExisted => (otherVisPrDirExisted && this.changePrVisibility(pr, isPublic)) as any).
|
||||
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
|
||||
then(([prDirExisted, shaDirExisted]) => {
|
||||
if (shaDirExisted) {
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
}
|
||||
|
||||
dirToRemoveOnError = prDirExisted ? shaDir : prDir;
|
||||
@ -57,36 +84,6 @@ 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)));
|
||||
|
@ -1,31 +1,17 @@
|
||||
// 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';
|
||||
import {BUILD_VERIFICATION_STATUS, BuildVerifier} from './build-verifier';
|
||||
import {UploadError} from './upload-error';
|
||||
|
||||
// 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:
|
||||
case 'FAKE_VERIFICATION_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:
|
||||
case 'FAKE_VERIFIED_NOT_TRUSTED':
|
||||
// For e2e tests, fake a `verifiedNotTrusted` verification status.
|
||||
return Promise.resolve(BUILD_VERIFICATION_STATUS.verifiedNotTrusted);
|
||||
default:
|
||||
@ -35,4 +21,4 @@ BuildVerifier.prototype.verify = (expectedPr: number, authHeader: string) => {
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: no-var-requires
|
||||
require('../upload-server/index');
|
||||
require('./index');
|
@ -13,8 +13,10 @@ 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');
|
||||
const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Run
|
||||
process.setuid(AIO_WWW_USER); // TODO(gkalpak): Find more suitable way to run as `www-data`.
|
||||
_main();
|
||||
|
||||
// Functions
|
||||
|
@ -1,5 +1,4 @@
|
||||
// Imports
|
||||
import * as bodyParser from 'body-parser';
|
||||
import * as express from 'express';
|
||||
import * as http from 'http';
|
||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||
@ -85,7 +84,6 @@ 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];
|
||||
@ -98,8 +96,8 @@ class UploadServerFactory {
|
||||
} else if (!archive) {
|
||||
this.throwRequestError(400, `Missing or empty '${X_FILE_HEADER}' header`, req);
|
||||
} else {
|
||||
Promise.resolve().
|
||||
then(() => buildVerifier.verify(+pr, authHeader)).
|
||||
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))).
|
||||
@ -107,23 +105,8 @@ class UploadServerFactory {
|
||||
}
|
||||
});
|
||||
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
||||
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.get('*', req => this.throwRequestError(404, 'Unknown resource', req));
|
||||
middleware.all('*', req => this.throwRequestError(405, 'Unsupported method', req));
|
||||
middleware.use((err: any, _req: any, res: express.Response, _next: any) => this.respondWithError(res, err));
|
||||
|
||||
return middleware;
|
||||
@ -142,10 +125,7 @@ class UploadServerFactory {
|
||||
}
|
||||
|
||||
protected throwRequestError(status: number, error: string, req: express.Request) {
|
||||
const message = `${error} in request: ${req.method} ${req.originalUrl}` +
|
||||
(!req.body ? '' : ` ${JSON.stringify(req.body)}`);
|
||||
|
||||
throw new UploadError(status, message);
|
||||
throw new UploadError(status, `${error} in request: ${req.method} ${req.originalUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
// 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 */
|
@ -18,7 +18,7 @@ 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 | null; stdout: string; stderr: string; }
|
||||
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
|
||||
export interface FileSpecs { content?: string; size?: number; }
|
||||
|
||||
export type CleanUpFn = () => void;
|
||||
@ -143,7 +143,7 @@ class Helper {
|
||||
statusText = status[1];
|
||||
} else {
|
||||
statusCode = status;
|
||||
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
|
||||
statusText = http.STATUS_CODES[statusCode];
|
||||
}
|
||||
|
||||
return (result: CmdResult) => {
|
||||
@ -196,7 +196,7 @@ class Helper {
|
||||
}
|
||||
|
||||
// Methods - Protected
|
||||
protected createCleanUpFn(fn: () => void): CleanUpFn {
|
||||
protected createCleanUpFn(fn: Function): CleanUpFn {
|
||||
const cleanUpFn = () => {
|
||||
const idx = this.cleanUpFns.indexOf(cleanUpFn);
|
||||
if (idx !== -1) {
|
||||
|
@ -317,51 +317,6 @@ 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 unknown URLs (even if the resource exists)', done => {
|
||||
|
@ -1,6 +1,5 @@
|
||||
// Imports
|
||||
import * as path from 'path';
|
||||
import * as c from './constants';
|
||||
import {helper as h} from './helper';
|
||||
|
||||
// Tests
|
||||
@ -15,14 +14,12 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
const getFile = (pr: string, sha: string, file: string) =>
|
||||
h.runCmd(`curl -iL ${scheme}://pr${pr}-${h.getShordSha(sha)}.${host}/${file}`);
|
||||
const uploadBuild = (pr: string, sha: string, archive: string, authHeader = 'Token FOO') => {
|
||||
// Using `FAKE_VERIFICATION_ERROR` or `FAKE_VERIFIED_NOT_TRUSTED` as `authHeader`,
|
||||
// we can fake the response of the overwritten `BuildVerifier.verify()` method.
|
||||
// (See 'lib/upload-server/index-test.ts'.)
|
||||
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(() => {
|
||||
@ -32,7 +29,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
});
|
||||
|
||||
|
||||
describe('for a new/non-existing PR', () => {
|
||||
describe('for a new PR', () => {
|
||||
|
||||
it('should be able to upload and serve a public build', done => {
|
||||
const regexPrefix9 = `^PR: uploaded\\/${pr9} \\| SHA: ${sha9} \\| File:`;
|
||||
@ -57,7 +54,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
|
||||
h.createDummyArchive(pr9, sha9, archivePath);
|
||||
|
||||
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
||||
then(() => Promise.all([
|
||||
getFile(pr9, sha9, 'index.html').then(h.verifyResponse(404)),
|
||||
getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(404)),
|
||||
@ -77,7 +74,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
|
||||
h.createDummyArchive(pr9, sha9, archivePath);
|
||||
|
||||
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
|
||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
|
||||
then(h.verifyResponse(403, errorRegex9)).
|
||||
then(() => {
|
||||
expect(h.buildExists(pr9)).toBe(false);
|
||||
@ -86,18 +83,6 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -138,7 +123,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
h.createDummyBuild(pr9, sha0, false);
|
||||
h.createDummyArchive(pr9, sha9, archivePath);
|
||||
|
||||
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
||||
then(() => Promise.all([
|
||||
getFile(pr9, sha0, 'index.html').then(h.verifyResponse(404)),
|
||||
getFile(pr9, sha0, 'foo/bar.js').then(h.verifyResponse(404)),
|
||||
@ -163,7 +148,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
h.createDummyBuild(pr9, sha0);
|
||||
h.createDummyArchive(pr9, sha9, archivePath);
|
||||
|
||||
uploadBuild(pr9, sha9, archivePath, c.BV_verify_error).
|
||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFICATION_ERROR').
|
||||
then(h.verifyResponse(403, errorRegex9)).
|
||||
then(() => {
|
||||
expect(h.buildExists(pr9)).toBe(true);
|
||||
@ -201,7 +186,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
h.createDummyBuild(pr9, sha9, false);
|
||||
h.createDummyArchive(pr9, sha9, archivePath);
|
||||
|
||||
uploadBuild(pr9, sha9, archivePath, c.BV_verify_verifiedNotTrusted).
|
||||
uploadBuild(pr9, sha9, archivePath, 'FAKE_VERIFIED_NOT_TRUSTED').
|
||||
then(h.verifyResponse(409)).
|
||||
then(() => {
|
||||
expect(h.readBuildFile(pr9, sha9, 'index.html', false)).toMatch(idxContentRegex9);
|
||||
@ -210,110 +195,6 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}));
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Imports
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as c from './constants';
|
||||
import {CmdResult, helper as h} from './helper';
|
||||
|
||||
// Tests
|
||||
@ -26,13 +25,13 @@ describe('upload-server (on HTTP)', () => {
|
||||
|
||||
it('should disallow non-GET requests', done => {
|
||||
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
||||
const bodyRegex = /^Unknown resource/;
|
||||
const bodyRegex = /^Unsupported method/;
|
||||
|
||||
Promise.all([
|
||||
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)),
|
||||
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)),
|
||||
]).then(done);
|
||||
});
|
||||
|
||||
@ -64,7 +63,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
|
||||
|
||||
it('should reject requests for which the PR verification fails', done => {
|
||||
const headers = `--header "Authorization: ${c.BV_verify_error}" ${xFileHeader}`;
|
||||
const headers = `--header "Authorization: FAKE_VERIFICATION_ERROR" ${xFileHeader}`;
|
||||
const url = `http://${host}/create-build/${pr}/${sha9}`;
|
||||
const bodyRegex = new RegExp(`Error while verifying upload for PR ${pr}: Test`);
|
||||
|
||||
@ -108,9 +107,8 @@ describe('upload-server (on HTTP)', () => {
|
||||
|
||||
[true, false].forEach(isPublic => describe(`(for ${isPublic ? 'public' : 'hidden'} builds)`, () => {
|
||||
const authorizationHeader2 = isPublic ?
|
||||
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
|
||||
authorizationHeader : '--header "Authorization: FAKE_VERIFIED_NOT_TRUSTED"';
|
||||
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
|
||||
const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`);
|
||||
|
||||
|
||||
it('should not overwrite existing builds', done => {
|
||||
@ -121,7 +119,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
|
||||
|
||||
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -142,7 +140,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
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(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -311,7 +309,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.buildExists(pr, sha0, isPublic)).toBe(false);
|
||||
|
||||
uploadBuild(sha0).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(() => {
|
||||
checkPrVisibility(isPublic);
|
||||
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr);
|
||||
@ -375,194 +373,27 @@ 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 requests to unknown URLs', done => {
|
||||
it('should respond with 404 for GET 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)),
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
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)),
|
||||
]).then(done);
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc --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",
|
||||
@ -20,26 +20,24 @@
|
||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"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"
|
||||
"express": "^4.14.1",
|
||||
"jasmine": "^2.5.3",
|
||||
"jsonwebtoken": "^7.3.0",
|
||||
"shelljs": "^0.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"supertest": "^3.0.0",
|
||||
"tslint": "^5.7.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||
"typescript": "^2.5.2"
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.1.6"
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ describe('BuildCleaner', () => {
|
||||
let cleanerGetExistingBuildNumbersSpy: jasmine.Spy;
|
||||
let cleanerGetOpenPrNumbersSpy: jasmine.Spy;
|
||||
let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy;
|
||||
let existingBuildsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let openPrsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let existingBuildsDeferred: {resolve: Function, reject: Function};
|
||||
let openPrsDeferred: {resolve: Function, reject: Function};
|
||||
let promise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -195,7 +195,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
|
||||
describe('getOpenPrNumbers()', () => {
|
||||
let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
let prDeferred: {resolve: Function, reject: Function};
|
||||
let promise: Promise<number[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -277,10 +277,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
it('should catch errors and log them', () => {
|
||||
const consoleErrorSpy = spyOn(console, 'error');
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
|
||||
(cleaner as any).removeDir('/foo/bar');
|
||||
|
||||
|
@ -56,7 +56,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
it('should not pass data to \'request()\'', () => {
|
||||
(api.get as any)('foo', {}, {});
|
||||
(api.get as Function)('foo', {}, {});
|
||||
|
||||
expect(apiRequestSpy).toHaveBeenCalled();
|
||||
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
|
||||
@ -144,7 +144,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('getPaginated()', () => {
|
||||
let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[];
|
||||
let deferreds: {resolve: Function, reject: Function}[];
|
||||
|
||||
beforeEach(() => {
|
||||
deferreds = [];
|
||||
@ -292,7 +292,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('onResponse', () => {
|
||||
let promise: Promise<object>;
|
||||
let promise: Promise<Object>;
|
||||
let respond: (statusCode: number) => IncomingMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -22,7 +22,7 @@ describe('GithubPullRequests', () => {
|
||||
|
||||
describe('addComment()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
let deferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
let deferred: {resolve: Function, reject: Function};
|
||||
|
||||
beforeEach(() => {
|
||||
prs = new GithubPullRequests('12345', 'foo/bar');
|
||||
|
@ -43,25 +43,178 @@ describe('BuildCreator', () => {
|
||||
});
|
||||
|
||||
|
||||
describe('create()', () => {
|
||||
describe('changePrVisibility()', () => {
|
||||
let bcEmitSpy: jasmine.Spy;
|
||||
let bcExistsSpy: jasmine.Spy;
|
||||
let bcExtractArchiveSpy: jasmine.Spy;
|
||||
let bcUpdatePrVisibilitySpy: jasmine.Spy;
|
||||
let shellMkdirSpy: jasmine.Spy;
|
||||
let shellRmSpy: 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.changePrVisibility(pr, true);
|
||||
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));
|
||||
});
|
||||
|
||||
|
||||
[true, false].forEach(makePublic => {
|
||||
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
|
||||
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
|
||||
|
||||
|
||||
it('should rename the directory', done => {
|
||||
bc.changePrVisibility(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.changePrVisibility(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.changePrVisibility(pr, makePublic).
|
||||
then(() => expect(emitted).toBe(true)).
|
||||
then(done);
|
||||
});
|
||||
|
||||
|
||||
describe('on error', () => {
|
||||
|
||||
it('should abort and skip further operations if the old directory does not exist', done => {
|
||||
bcExistsSpy.and.callFake((dir: string) => dir !== oldPrDir);
|
||||
bc.changePrVisibility(pr, makePublic).catch(err => {
|
||||
expectToBeUploadError(err, 404, `Request to move non-existing directory '${oldPrDir}' to '${newPrDir}'.`);
|
||||
expect(shellMvSpy).not.toHaveBeenCalled();
|
||||
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should abort and skip further operations if the new directory does already exist', done => {
|
||||
bcExistsSpy.and.returnValue(true);
|
||||
bc.changePrVisibility(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 abort and skip further operations if it fails to rename the directory', done => {
|
||||
shellMvSpy.and.throwError('');
|
||||
bc.changePrVisibility(pr, makePublic).catch(() => {
|
||||
expect(shellMvSpy).toHaveBeenCalled();
|
||||
expect(bcListShasByDate).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should abort and skip further operations if it fails to list the SHAs', done => {
|
||||
bcListShasByDate.and.throwError('');
|
||||
bc.changePrVisibility(pr, makePublic).catch(() => {
|
||||
expect(shellMvSpy).toHaveBeenCalled();
|
||||
expect(bcListShasByDate).toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.changePrVisibility(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.changePrVisibility(pr, makePublic).catch(err => {
|
||||
expectToBeUploadError(err, 543, 'Test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('create()', () => {
|
||||
let bcChangePrVisibilitySpy: jasmine.Spy;
|
||||
let bcEmitSpy: jasmine.Spy;
|
||||
let bcExistsSpy: jasmine.Spy;
|
||||
let bcExtractArchiveSpy: jasmine.Spy;
|
||||
let shellMkdirSpy: jasmine.Spy;
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
bcChangePrVisibilitySpy = spyOn(bc, 'changePrVisibility');
|
||||
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 otherVisPrDir = isPublic ? hiddenPrDir : publicPrDir;
|
||||
const prDir = isPublic ? publicPrDir : hiddenPrDir;
|
||||
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
|
||||
|
||||
@ -75,12 +228,20 @@ describe('BuildCreator', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should not update the PR\'s visibility first if not necessary', done => {
|
||||
bc.create(pr, sha, archive, isPublic).
|
||||
then(() => expect(bcChangePrVisibilitySpy).not.toHaveBeenCalled()).
|
||||
then(done);
|
||||
});
|
||||
|
||||
|
||||
it('should update the PR\'s visibility first if necessary', done => {
|
||||
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
|
||||
bcChangePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled());
|
||||
bcExistsSpy.and.callFake((dir: string) => dir === otherVisPrDir);
|
||||
|
||||
bc.create(pr, sha, archive, isPublic).
|
||||
then(() => {
|
||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
|
||||
expect(bcChangePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
|
||||
expect(shellMkdirSpy).toHaveBeenCalled();
|
||||
}).
|
||||
then(done);
|
||||
@ -125,6 +286,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
existsValues = {
|
||||
[otherVisPrDir]: false,
|
||||
[prDir]: false,
|
||||
[shaDir]: false,
|
||||
};
|
||||
@ -135,12 +297,14 @@ describe('BuildCreator', () => {
|
||||
|
||||
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));
|
||||
|
||||
existsValues[otherVisPrDir] = true;
|
||||
bcChangePrVisibilitySpy.and.returnValue(Promise.reject(mockError));
|
||||
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expect(err).toBe(mockError);
|
||||
|
||||
expect(bcExistsSpy).not.toHaveBeenCalled();
|
||||
expect(bcExistsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -153,8 +317,7 @@ describe('BuildCreator', () => {
|
||||
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}`);
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -164,14 +327,11 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
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);
|
||||
existsValues[otherVisPrDir] = true;
|
||||
bcChangePrVisibilitySpy.and.callFake(() => existsValues[prDir] = 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}`);
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -223,7 +383,6 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
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`);
|
||||
@ -247,200 +406,15 @@ describe('BuildCreator', () => {
|
||||
});
|
||||
|
||||
|
||||
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.updatePrVisibility(pr, true);
|
||||
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));
|
||||
});
|
||||
|
||||
|
||||
[true, false].forEach(makePublic => {
|
||||
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
|
||||
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
|
||||
|
||||
|
||||
it('should rename the directory', done => {
|
||||
bc.updatePrVisibility(pr, makePublic).
|
||||
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)).
|
||||
then(done);
|
||||
});
|
||||
|
||||
|
||||
describe('when the visibility is updated', () => {
|
||||
|
||||
it('should resolve to true', 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 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 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);
|
||||
});
|
||||
|
||||
|
||||
describe('on error', () => {
|
||||
|
||||
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 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 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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Protected methods
|
||||
|
||||
describe('exists()', () => {
|
||||
let fsAccessSpy: jasmine.Spy;
|
||||
let fsAccessCbs: ((v?: any) => void)[];
|
||||
let fsAccessCbs: Function[];
|
||||
|
||||
beforeEach(() => {
|
||||
fsAccessCbs = [];
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: Function) => fsAccessCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -484,7 +458,7 @@ describe('BuildCreator', () => {
|
||||
let shellChmodSpy: jasmine.Spy;
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
let cpExecSpy: jasmine.Spy;
|
||||
let cpExecCbs: ((...args: any[]) => void)[];
|
||||
let cpExecCbs: Function[];
|
||||
|
||||
beforeEach(() => {
|
||||
cpExecCbs = [];
|
||||
@ -492,7 +466,7 @@ describe('BuildCreator', () => {
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
shellChmodSpy = spyOn(shell, 'chmod');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: Function) => cpExecCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -558,11 +532,7 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellChmodSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
shellChmodSpy.and.callFake(() => { throw 'Test'; });
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
@ -575,11 +545,7 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
|
@ -116,7 +116,7 @@ describe('uploadServerFactory', () => {
|
||||
|
||||
it('should log the server address info on \'listening\'', () => {
|
||||
const consoleInfoSpy = spyOn(console, 'info');
|
||||
const server = createUploadServer();
|
||||
const server = createUploadServer('builds/dir');
|
||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
@ -258,12 +258,12 @@ describe('uploadServerFactory', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 for non-GET requests', done => {
|
||||
it('should respond with 405 for non-GET requests', done => {
|
||||
verifyRequests([
|
||||
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),
|
||||
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),
|
||||
], done);
|
||||
});
|
||||
|
||||
@ -418,12 +418,12 @@ describe('uploadServerFactory', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should respond with 404 for non-GET requests', done => {
|
||||
it('should respond with 405 for non-GET requests', done => {
|
||||
verifyRequests([
|
||||
agent.put('/health-check').expect(404),
|
||||
agent.post('/health-check').expect(404),
|
||||
agent.patch('/health-check').expect(404),
|
||||
agent.delete('/health-check').expect(404),
|
||||
agent.put('/health-check').expect(405),
|
||||
agent.post('/health-check').expect(405),
|
||||
agent.patch('/health-check').expect(405),
|
||||
agent.delete('/health-check').expect(405),
|
||||
], done);
|
||||
});
|
||||
|
||||
@ -442,141 +442,11 @@ describe('uploadServerFactory', () => {
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
});
|
||||
describe('GET *', () => {
|
||||
|
||||
it('should respond with 404', done => {
|
||||
const responseBody = 'Unknown resource in request: GET /some/url';
|
||||
verifyRequests([agent.get('/some/url').expect(404, responseBody)], done);
|
||||
});
|
||||
|
||||
});
|
||||
@ -584,15 +454,14 @@ describe('uploadServerFactory', () => {
|
||||
|
||||
describe('ALL *', () => {
|
||||
|
||||
it('should respond with 404', done => {
|
||||
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
||||
it('should respond with 405', done => {
|
||||
const responseFor = (method: string) => `Unsupported method in request: ${method.toUpperCase()} /some/url`;
|
||||
|
||||
verifyRequests([
|
||||
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')),
|
||||
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')),
|
||||
], done);
|
||||
});
|
||||
|
||||
|
@ -1,66 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* 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'. */
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
"lib": [
|
||||
"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. */
|
||||
"es2016"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"pretty": true,
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es5",
|
||||
"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/**/*",
|
||||
|
@ -1,24 +1,15 @@
|
||||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"extends": "tslint:recommended",
|
||||
"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-focused-test": true,
|
||||
"no-console": false,
|
||||
"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
@ -6,9 +6,10 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || ec
|
||||
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}
|
||||
|
@ -15,12 +15,13 @@ 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/verify-setup/start-test-upload-server.js \
|
||||
--uid $AIO_WWW_USER \
|
||||
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server/index-test.js \
|
||||
--log /var/log/aio/upload-server-test.log \
|
||||
--name $appName \
|
||||
--no-autorestart \
|
||||
|
@ -3,9 +3,10 @@
|
||||
|
||||
TODO (gkalpak): Add docs. Mention:
|
||||
- Travis' JWT addon (+ limitations).
|
||||
Relevant files: `.travis.yml`, `scripts/ci/env.sh`
|
||||
Relevant files: `.travis.yml`
|
||||
- Testing on CI.
|
||||
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
||||
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`
|
||||
- Deploying from CI.
|
||||
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-preview.sh`,
|
||||
`aio/scripts/deploy-to-firebase.sh`
|
||||
Relevant files: `ci/deploy.sh`, `aio/scripts/deploy-preview.sh`
|
||||
|
@ -80,31 +80,13 @@ 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
|
||||
- nginx receives a request for an uploaded resource on a subdomain corresponding to the PR and SHA.
|
||||
E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource`
|
||||
- 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
|
||||
Again, more info on the possible HTTP status codes and their meaning can be found
|
||||
[here](overview--http-status-codes.md).
|
||||
|
||||
|
||||
|
@ -42,40 +42,21 @@ with a bried explanation of what they mean:
|
||||
- **403 (Forbidden)**:
|
||||
Unable to verify build (e.g. invalid JWT token, or unable to talk to 3rd-party APIs, etc).
|
||||
|
||||
- **404 (Not Found)**:
|
||||
Tried to change PR visibility but the source directory did not exist.
|
||||
(Currently, this can only happen as a rare race condition during build deployment.)
|
||||
|
||||
- **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).
|
||||
Request to overwrite existing 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)**:
|
||||
|
@ -16,6 +16,13 @@ available:
|
||||
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 "pre-verifying" a PR before uploading the artifacts to the server. It checks
|
||||
whether the author of the PR is a member of one of the specified GitHub teams (therefore allowed
|
||||
to upload build artifacts) or the PR has the specified "trusted PR" label (meaning it has been
|
||||
manually verified by a trusted member). 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.
|
||||
|
@ -9,7 +9,7 @@ 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 old docker container and image, create and 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:
|
||||
|
@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
|
||||
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
yarn install
|
||||
yarn build
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
|
||||
# Test `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
yarn install
|
||||
yarn test
|
||||
)
|
||||
|
26
aio/aio-builds-setup/scripts/travis-preverify-pr.sh
Executable file
26
aio/aio-builds-setup/scripts/travis-preverify-pr.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
set -eux -o pipefail
|
||||
|
||||
# Set up env
|
||||
source "`dirname $0`/_env.sh"
|
||||
|
||||
# Build `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn build
|
||||
)
|
||||
|
||||
# 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_TRUSTED_PR_LABEL="aio: preview" \
|
||||
AIO_PREVERIFY_PR=$TRAVIS_PULL_REQUEST \
|
||||
node "$SCRIPTS_JS_DIR/dist/lib/upload-server/index-preverify-pr"
|
||||
|
||||
# Exit codes:
|
||||
# - 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.
|
@ -3,7 +3,7 @@
|
||||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
echo "[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
@ -37,7 +37,7 @@ readonly CONTAINER_NAME=aio
|
||||
fi
|
||||
|
||||
# Create and verify a new docker image.
|
||||
aio/aio-builds-setup/scripts/create-image.sh "$PROVISIONAL_IMAGE_NAME" --no-cache
|
||||
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
|
||||
|
40
aio/content/examples/.gitignore
vendored
40
aio/content/examples/.gitignore
vendored
@ -1,23 +1,8 @@
|
||||
# boilerplate files
|
||||
**/src/environments/environment.prod.ts
|
||||
**/src/environments/environment.ts
|
||||
**/src/assets/.gitkeep
|
||||
**/src/favicon.ico
|
||||
**/src/styles.css
|
||||
**/src/systemjs-angular-loader.js
|
||||
**/src/systemjs.config.js
|
||||
**/src/tsconfig.json
|
||||
**/src/favicon.ico
|
||||
**/src/polyfills.ts
|
||||
**/src/test.ts
|
||||
**/src/tsconfig.app.json
|
||||
**/src/tsconfig.spec.json
|
||||
**/src/typings.d.ts
|
||||
**/e2e/app.po.ts
|
||||
**/e2e/tsconfig.e2e.json
|
||||
**/.angular-cli.json
|
||||
**/.editorconfig
|
||||
**/tsconfig.json
|
||||
**/bs-config.e2e.json
|
||||
**/bs-config.json
|
||||
**/package.json
|
||||
@ -30,7 +15,11 @@
|
||||
# built files
|
||||
*.map
|
||||
_test-output
|
||||
protractor-helpers.js
|
||||
*/e2e-spec.js
|
||||
**/*.js
|
||||
**/ts/**/*.js
|
||||
**/js-es6*/**/*.js
|
||||
dist/
|
||||
|
||||
|
||||
@ -54,31 +43,32 @@ dist/
|
||||
**/app/**/*.ajs.js
|
||||
|
||||
# aot
|
||||
**/*.ngfactory.ts
|
||||
**/*.ngsummary.json
|
||||
**/*.ngsummary.ts
|
||||
**/*.shim.ngstyle.ts
|
||||
**/*.metadata.json
|
||||
!aot/bs-config.json
|
||||
!aot/index.html
|
||||
!rollup-config.js
|
||||
aot-compiler/**/*.d.ts
|
||||
aot-compiler/**/*.factory.d.ts
|
||||
|
||||
# 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
|
||||
!webpack/src/polyfills.ts
|
||||
|
||||
# styleguide
|
||||
!styleguide/src/systemjs.custom.js
|
||||
|
||||
# universal
|
||||
!universal/webpack.server.config.js
|
||||
|
||||
# plunkers
|
||||
*plnkr.no-link.html
|
||||
|
||||
# ngUpgrade testing
|
||||
!upgrade-phonecat-*/**/karma.conf.js
|
||||
!upgrade-phonecat-*/**/karma-test-shim.js
|
||||
|
@ -83,7 +83,7 @@ describe('AngularJS to Angular Quick Reference Tests', function () {
|
||||
}
|
||||
|
||||
function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) {
|
||||
let posterButton = element(by.css('app-movie-list tr > th > button'));
|
||||
let posterButton = element(by.css('movie-list tr > th > button'));
|
||||
expect(posterButton.getText()).toBe(expectedButtonText);
|
||||
|
||||
posterButton.click().then(function () {
|
||||
@ -92,11 +92,11 @@ describe('AngularJS to Angular Quick Reference Tests', function () {
|
||||
}
|
||||
|
||||
function getMovieRows() {
|
||||
return element.all(by.css('app-movie-list tbody > tr'));
|
||||
return element.all(by.css('movie-list tbody > tr'));
|
||||
}
|
||||
|
||||
function testFavoriteHero(heroName: string, expectedLabel: string) {
|
||||
let movieListComp = element(by.tagName('app-movie-list'));
|
||||
let movieListComp = element(by.tagName('movie-list'));
|
||||
let heroInput = movieListComp.element(by.tagName('input'));
|
||||
let favoriteHeroLabel = movieListComp.element(by.tagName('h3'));
|
||||
let resultLabel = movieListComp.element(by.css('span > p'));
|
@ -4,7 +4,7 @@ import { MovieService } from './movie.service';
|
||||
import { IMovie } from './movie';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ],
|
||||
providers: [ MovieService ]
|
||||
|
@ -8,7 +8,7 @@ import { MovieService } from './movie.service';
|
||||
|
||||
// #docregion component
|
||||
@Component({
|
||||
selector: 'app-movie-list',
|
||||
selector: 'movie-list',
|
||||
templateUrl: './movie-list.component.html',
|
||||
// #docregion style-url
|
||||
styleUrls: [ './movie-list.component.css' ],
|
||||
|
@ -8,7 +8,7 @@ export class MovieService {
|
||||
return [
|
||||
{
|
||||
hero: 'Celeritas',
|
||||
imageurl: 'assets/images/hero.png',
|
||||
imageurl: 'images/hero.png',
|
||||
movieId: 1,
|
||||
mpaa: 'pg-13',
|
||||
releaseDate: '2015-12-19T00:00:00',
|
||||
@ -19,7 +19,7 @@ export class MovieService {
|
||||
},
|
||||
{
|
||||
hero: 'Mr. Nice',
|
||||
imageurl: 'assets/images/villain.png',
|
||||
imageurl: 'images/villain.png',
|
||||
movieId: 2,
|
||||
mpaa: 'pg-13',
|
||||
releaseDate: '2015-12-18T00:00:00',
|
||||
@ -30,7 +30,7 @@ export class MovieService {
|
||||
},
|
||||
{
|
||||
hero: 'Angular',
|
||||
imageurl: 'assets/images/ng-logo.png',
|
||||
imageurl: 'images/ng-logo.png',
|
||||
movieId: 3,
|
||||
mpaa: 'pg-13',
|
||||
releaseDate: '2015-12-17T00:00:00',
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
@ -8,10 +8,21 @@
|
||||
<!-- #docregion style -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- #enddocregion style -->
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<my-app>Loading app...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -1,12 +1,5 @@
|
||||
// #docregion
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
@ -29,7 +29,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-basic'));
|
||||
host = element(by.css('hero-list-basic'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
@ -58,7 +58,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-inline-styles'));
|
||||
host = element(by.css('hero-list-inline-styles'));
|
||||
});
|
||||
|
||||
it('are not kept after animation', () => {
|
||||
@ -79,7 +79,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-combined-transitions'));
|
||||
host = element(by.css('hero-list-combined-transitions'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
@ -108,7 +108,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-twoway'));
|
||||
host = element(by.css('hero-list-twoway'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
@ -137,7 +137,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-enter-leave'));
|
||||
host = element(by.css('hero-list-enter-leave'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
@ -157,7 +157,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-enter-leave-states'));
|
||||
host = element(by.css('hero-list-enter-leave-states'));
|
||||
});
|
||||
|
||||
it('adds and removes and animates between active and inactive', () => {
|
||||
@ -186,7 +186,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-auto'));
|
||||
host = element(by.css('hero-list-auto'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
@ -206,7 +206,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-timings'));
|
||||
host = element(by.css('hero-list-timings'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
@ -227,7 +227,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-multistep'));
|
||||
host = element(by.css('hero-list-multistep'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
@ -248,7 +248,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-groups'));
|
||||
host = element(by.css('hero-list-groups'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
@ -269,7 +269,7 @@ describe('Animation Tests', () => {
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-basic'));
|
||||
host = element(by.css('hero-list-basic'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
@ -335,17 +335,17 @@ describe('Animation Tests', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientWidth(el: ElementFinder) {
|
||||
function getBoundingClientWidth(el: ElementFinder): promise.Promise<number> {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].getBoundingClientRect().width',
|
||||
el.getWebElement()
|
||||
) as PromiseLike<number>;
|
||||
);
|
||||
}
|
||||
|
||||
function getOffsetWidth(el: ElementFinder) {
|
||||
function getOffsetWidth(el: ElementFinder): promise.Promise<number> {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].offsetWidth',
|
||||
el.getWebElement()
|
||||
) as PromiseLike<number>;
|
||||
);
|
||||
}
|
||||
});
|
@ -1,6 +1,5 @@
|
||||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion animations-module
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
// #enddocregion animations-module
|
||||
@ -16,12 +15,11 @@ 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 ],
|
||||
// ... more stuff ...
|
||||
// #enddocregion animations-module
|
||||
// #enddocregion animation-module
|
||||
declarations: [
|
||||
HeroTeamBuilderComponent,
|
||||
HeroListBasicComponent,
|
||||
@ -36,8 +34,5 @@ import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
HeroListGroupsComponent
|
||||
],
|
||||
bootstrap: [ HeroTeamBuilderComponent ]
|
||||
// #docregion animations-module
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion animations-module
|
||||
|
||||
|
@ -10,10 +10,10 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-auto',
|
||||
selector: 'hero-list-auto',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -43,5 +43,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-basic',
|
||||
selector: 'hero-list-basic',
|
||||
// #enddocregion
|
||||
/* The click event calls hero.toggleState(), which
|
||||
* causes the state of that hero to switch from
|
||||
@ -66,5 +66,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListBasicComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-combined-transitions',
|
||||
selector: 'hero-list-combined-transitions',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -55,5 +55,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListCombinedTransitionsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-enter-leave-states',
|
||||
selector: 'hero-list-enter-leave-states',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -59,5 +59,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveStatesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-enter-leave',
|
||||
selector: 'hero-list-enter-leave',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -47,5 +47,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -11,10 +11,10 @@ import {
|
||||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-groups',
|
||||
selector: 'hero-list-groups',
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
@ -76,5 +76,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-inline-styles',
|
||||
selector: 'hero-list-inline-styles',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -56,5 +56,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListInlineStylesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
AnimationEvent
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-multistep',
|
||||
selector: 'hero-list-multistep',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -59,7 +59,7 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListMultistepComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
|
||||
animationStarted(event: AnimationEvent) {
|
||||
console.warn('Animation started: ', event);
|
||||
|
@ -10,10 +10,10 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-timings',
|
||||
selector: 'hero-list-timings',
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
@ -54,5 +54,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTimingsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-twoway',
|
||||
selector: 'hero-list-twoway',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
@ -54,5 +54,5 @@ import { Hero } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTwowayComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Heroes;
|
||||
}
|
||||
|
@ -1,41 +1,40 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
import { Heroes } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
selector: 'hero-team-builder',
|
||||
template: `
|
||||
<div class="buttons">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Basic State</h4>
|
||||
<p>Switch between active/inactive on click.</p>
|
||||
<app-hero-list-basic [heroes]="heroes"></app-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>
|
||||
<app-hero-list-inline-styles [heroes]="heroes"></app-hero-list-inline-styles>
|
||||
<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>
|
||||
<app-hero-list-combined-transitions [heroes]="heroes"></app-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>
|
||||
<app-hero-list-twoway [heroes]="heroes"></app-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>
|
||||
<app-hero-list-enter-leave [heroes]="heroes"></app-hero-list-enter-leave>
|
||||
<hero-list-enter-leave [heroes]=heroes></hero-list-enter-leave>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@ -45,27 +44,27 @@ import { Hero, HeroService } from './hero.service';
|
||||
Enter and leave animations combined with active/inactive state animations.
|
||||
Different enter and leave transitions depending on state.
|
||||
</p>
|
||||
<app-hero-list-enter-leave-states [heroes]="heroes"></app-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>
|
||||
<app-hero-list-auto [heroes]="heroes"></app-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>
|
||||
<app-hero-list-timings [heroes]="heroes"></app-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>
|
||||
<app-hero-list-multistep [heroes]="heroes"></app-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>
|
||||
<app-hero-list-groups [heroes]="heroes"></app-hero-list-groups>
|
||||
<hero-list-groups [heroes]=heroes></hero-list-groups>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -88,12 +87,8 @@ import { Hero, HeroService } from './hero.service';
|
||||
min-height: 6em;
|
||||
}
|
||||
`],
|
||||
providers: [HeroService]
|
||||
providers: [Heroes]
|
||||
})
|
||||
export class HeroTeamBuilderComponent {
|
||||
heroes: Hero[];
|
||||
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = heroService.heroes;
|
||||
}
|
||||
constructor(private heroes: Heroes) { }
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// #docregion hero
|
||||
export class Hero {
|
||||
constructor(public name: string, public state = 'inactive') { }
|
||||
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
|
||||
|
||||
const ALL_HEROES = [
|
||||
let ALL_HEROES = [
|
||||
'Windstorm',
|
||||
'RubberMan',
|
||||
'Bombasto',
|
||||
@ -25,30 +25,36 @@ const ALL_HEROES = [
|
||||
].map(name => new Hero(name));
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
export class Heroes implements Iterable<Hero> {
|
||||
|
||||
heroes: Hero[] = [];
|
||||
currentHeroes: Hero[] = [];
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.currentHeroes.values();
|
||||
}
|
||||
|
||||
canAdd() {
|
||||
return this.heroes.length < ALL_HEROES.length;
|
||||
return this.currentHeroes.length < ALL_HEROES.length;
|
||||
}
|
||||
|
||||
canRemove() {
|
||||
return this.heroes.length > 0;
|
||||
return this.currentHeroes.length > 0;
|
||||
}
|
||||
|
||||
addActive(active = true) {
|
||||
let hero = ALL_HEROES[this.heroes.length];
|
||||
hero.state = active ? 'active' : 'inactive';
|
||||
this.heroes.push(hero);
|
||||
addActive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'active';
|
||||
this.currentHeroes.push(hero);
|
||||
}
|
||||
|
||||
addInactive() {
|
||||
this.addActive(false);
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'inactive';
|
||||
this.currentHeroes.push(hero);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.heroes.length -= 1;
|
||||
this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,11 +5,26 @@
|
||||
<title>Animations</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfill for Web Animations -->
|
||||
<script src="https://unpkg.com/web-animations-js@2.2.1"></script>
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 style="visibility: hidden;">External H1 Title for E2E test</h1>
|
||||
<app-root></app-root>
|
||||
<hero-team-builder></hero-team-builder>
|
||||
<button style="visibility: hidden;">External button for E2E test</button>
|
||||
<ul style="visibility: hidden;">
|
||||
<li>External list for E2E test</li>
|
||||
|
@ -1,11 +1,4 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
27
aio/content/examples/aot-compiler/e2e-spec.ts
Normal file
27
aio/content/examples/aot-compiler/e2e-spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
/* tslint:disable:quotemark */
|
||||
describe('AOT Compilation', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should load page and click button', function (done: any) {
|
||||
let headingSelector = element.all(by.css('h1')).get(0);
|
||||
expect(headingSelector.getText()).toEqual('Hello Angular');
|
||||
|
||||
expect(element.all(by.xpath('//div[text()="Magneta"]')).get(0).isPresent()).toBe(true);
|
||||
expect(element.all(by.xpath('//div[text()="Bombasto"]')).get(0).isPresent()).toBe(true);
|
||||
expect(element.all(by.xpath('//div[text()="Magma"]')).get(0).isPresent()).toBe(true);
|
||||
expect(element.all(by.xpath('//div[text()="Tornado"]')).get(0).isPresent()).toBe(true);
|
||||
|
||||
let toggleButton = element.all(by.css('button')).get(0);
|
||||
toggleButton.click().then(function() {
|
||||
expect(headingSelector.isPresent()).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
3
aio/content/examples/aot-compiler/example-config.json
Normal file
3
aio/content/examples/aot-compiler/example-config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"build": "build:aot"
|
||||
}
|
33
aio/content/examples/aot-compiler/rollup-config.js
Normal file
33
aio/content/examples/aot-compiler/rollup-config.js
Normal file
@ -0,0 +1,33 @@
|
||||
// #docregion
|
||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import uglify from 'rollup-plugin-uglify';
|
||||
|
||||
// #docregion config
|
||||
export default {
|
||||
entry: 'src/main.js',
|
||||
dest: 'src/build.js', // output a single application bundle
|
||||
sourceMap: false,
|
||||
format: 'iife',
|
||||
onwarn: function(warning) {
|
||||
// Skip certain warnings
|
||||
|
||||
// should intercept ... but doesn't in some rollup versions
|
||||
if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; }
|
||||
|
||||
// console.warn everything else
|
||||
console.warn( warning.message );
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({jsnext: true, module: true}),
|
||||
// #docregion commonjs
|
||||
commonjs({
|
||||
include: 'node_modules/rxjs/**',
|
||||
}),
|
||||
// #enddocregion commonjs
|
||||
// #docregion uglify
|
||||
uglify()
|
||||
// #enddocregion uglify
|
||||
]
|
||||
};
|
||||
// #enddocregion config
|
@ -0,0 +1,7 @@
|
||||
<!-- #docregion -->
|
||||
<button (click)="toggleHeading()">Toggle Heading</button>
|
||||
<h1 *ngIf="showHeading">Hello Angular</h1>
|
||||
|
||||
<h3>List of Heroes</h3>
|
||||
<div *ngFor="let hero of heroes">{{hero}}</div>
|
||||
|
15
aio/content/examples/aot-compiler/src/app/app.component.ts
Normal file
15
aio/content/examples/aot-compiler/src/app/app.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
showHeading = true;
|
||||
heroes = ['Magneta', 'Bombasto', 'Magma', 'Tornado'];
|
||||
|
||||
toggleHeading() {
|
||||
this.showHeading = !this.showHeading;
|
||||
}
|
||||
}
|
12
aio/content/examples/aot-compiler/src/app/app.module.ts
Normal file
12
aio/content/examples/aot-compiler/src/app/app.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule ],
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
24
aio/content/examples/aot-compiler/src/index-jit.html
Normal file
24
aio/content/examples/aot-compiler/src/index-jit.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Ahead of time compilation (JIT)</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<!-- #docregion jit -->
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main-jit.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
<!-- #enddocregion jit -->
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
20
aio/content/examples/aot-compiler/src/index.html
Normal file
20
aio/content/examples/aot-compiler/src/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Ahead of time compilation</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
<!-- #docregion bundle -->
|
||||
<script src="build.js"></script>
|
||||
<!-- #enddocregion bundle -->
|
||||
</html>
|
6
aio/content/examples/aot-compiler/src/main-jit.ts
Normal file
6
aio/content/examples/aot-compiler/src/main-jit.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// #docregion
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
console.log('Running JIT compiled');
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
6
aio/content/examples/aot-compiler/src/main.ts
Normal file
6
aio/content/examples/aot-compiler/src/main.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// #docregion
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';
|
||||
|
||||
console.log('Running AOT compiled');
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
26
aio/content/examples/aot-compiler/tsconfig-aot.json
Normal file
26
aio/content/examples/aot-compiler/tsconfig-aot.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/app/app.module.ts",
|
||||
"src/main.ts"
|
||||
],
|
||||
|
||||
"angularCompilerOptions": {
|
||||
"genDir": "aot",
|
||||
"skipMetadataEmit" : true
|
||||
}
|
||||
}
|
@ -79,10 +79,10 @@ function salesTaxTests() {
|
||||
|
||||
function getPageElts() {
|
||||
return {
|
||||
heroes: element.all(by.css('app-root li')),
|
||||
heroDetail: element(by.css('app-root app-hero-detail')),
|
||||
salesTaxAmountInput: element(by.css('app-root app-sales-tax input')),
|
||||
salesTaxDetail: element(by.css('app-root app-sales-tax div'))
|
||||
heroes: element.all(by.css('my-app li')),
|
||||
heroDetail: element(by.css('my-app hero-detail')),
|
||||
salesTaxAmountInput: element(by.css('my-app sales-tax input')),
|
||||
salesTaxDetail: element(by.css('my-app sales-tax div'))
|
||||
};
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import { Component } from '@angular/core';
|
||||
// #enddocregion import
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<app-hero-list></app-hero-list>
|
||||
<app-sales-tax></app-sales-tax>
|
||||
<hero-list></hero-list>
|
||||
<sales-tax></sales-tax>
|
||||
`
|
||||
})
|
||||
// #docregion export
|
||||
|
@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-detail',
|
||||
selector: 'hero-detail',
|
||||
templateUrl: './hero-detail.component.html'
|
||||
})
|
||||
export class HeroDetailComponent {
|
||||
|
@ -1,9 +1,9 @@
|
||||
<!--#docregion binding -->
|
||||
<li>{{hero.name}}</li>
|
||||
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
<li (click)="selectHero(hero)"></li>
|
||||
<!--#enddocregion binding -->
|
||||
|
||||
<!--#docregion structural -->
|
||||
<li *ngFor="let hero of heroes"></li>
|
||||
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>
|
||||
<hero-detail *ngIf="selectedHero"></hero-detail>
|
||||
|
@ -8,4 +8,4 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<app-hero-detail *ngIf="selectedHero" [hero]="selectedHero"></app-hero-detail>
|
||||
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>
|
||||
|
@ -5,7 +5,7 @@ import { HeroService } from './hero.service';
|
||||
|
||||
// #docregion metadata, providers
|
||||
@Component({
|
||||
selector: 'app-hero-list',
|
||||
selector: 'hero-list',
|
||||
templateUrl: './hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import { SalesTaxService } from './sales-tax.service';
|
||||
import { TaxRateService } from './tax-rate.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sales-tax',
|
||||
selector: 'sales-tax',
|
||||
template: `
|
||||
<h2>Sales Tax Calculator</h2>
|
||||
Amount: <input #amountBox (change)="0">
|
||||
|
@ -1,13 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<title>Architecture of Angular</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="/">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -1,12 +1,5 @@
|
||||
// #docregion
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
@ -1,14 +1,14 @@
|
||||
<!-- #docregion -->
|
||||
<h1>My First Attribute Directive</h1>
|
||||
<!-- #docregion applied -->
|
||||
<p appHightlight>Highlight me!</p>
|
||||
<p myHighlight>Highlight me!</p>
|
||||
<!-- #enddocregion applied, -->
|
||||
|
||||
<!-- #docregion color-1 -->
|
||||
<p appHightlight highlightColor="yellow">Highlighted in yellow</p>
|
||||
<p appHightlight [highlightColor]="'orange'">Highlighted in orange</p>
|
||||
<p myHighlight highlightColor="yellow">Highlighted in yellow</p>
|
||||
<p myHighlight [highlightColor]="'orange'">Highlighted in orange</p>
|
||||
<!-- #enddocregion color-1 -->
|
||||
|
||||
<!-- #docregion color-2 -->
|
||||
<p appHightlight [highlightColor]="color">Highlighted with parent component's color</p>
|
||||
<p myHighlight [highlightColor]="color">Highlighted with parent component's color</p>
|
||||
<!-- #enddocregion color-2 -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.1.html'
|
||||
})
|
||||
// #docregion class
|
||||
|
@ -8,11 +8,11 @@
|
||||
<input type="radio" name="colors" (click)="color='cyan'">Cyan
|
||||
</div>
|
||||
<!-- #docregion color -->
|
||||
<p [appHighlight]="color">Highlight me!</p>
|
||||
<p [myHighlight]="color">Highlight me!</p>
|
||||
<!-- #enddocregion color, v2 -->
|
||||
|
||||
<!-- #docregion defaultColor -->
|
||||
<p [appHighlight]="color" defaultColor="violet">
|
||||
<p [myHighlight]="color" defaultColor="violet">
|
||||
Highlight me too!
|
||||
</p>
|
||||
<!-- #enddocregion defaultColor, -->
|
||||
@ -20,5 +20,5 @@
|
||||
<hr>
|
||||
<p><i>Mouse over the following lines to see fixed highlights</i></p>
|
||||
|
||||
<p [appHighlight]="'yellow'">Highlighted in yellow</p>
|
||||
<p appHighlight="orange">Highlighted in orange</p>
|
||||
<p [myHighlight]="'yellow'">Highlighted in yellow</p>
|
||||
<p myHighlight="orange">Highlighted in orange</p>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
// #docregion class
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion
|
||||
import { Directive, ElementRef, Input } from '@angular/core';
|
||||
|
||||
@Directive({ selector: '[appHighlight]' })
|
||||
@Directive({ selector: '[myHighlight]' })
|
||||
export class HighlightDirective {
|
||||
constructor(el: ElementRef) {
|
||||
el.nativeElement.style.backgroundColor = 'yellow';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user