Compare commits
219 Commits
Author | SHA1 | Date | |
---|---|---|---|
3988ebf432 | |||
5099b79545 | |||
038d06d2e9 | |||
9e1aff9fe6 | |||
a41f331cb4 | |||
71628f1837 | |||
df878a6b60 | |||
48d7f4e8b5 | |||
66f5d27e50 | |||
91dd160b21 | |||
1c44b71fd2 | |||
a5e0ae501d | |||
2d0e642dbe | |||
9ea656f20e | |||
97ae7aed41 | |||
678b4209c8 | |||
b7be4f55be | |||
e7c72ab556 | |||
af785f9e91 | |||
1ac5d68827 | |||
2c987625ae | |||
a77f567403 | |||
110c81f359 | |||
ef4b5c7e59 | |||
c69362442d | |||
6c8791ee32 | |||
274dc1e972 | |||
d9bd86050b | |||
076374ba4f | |||
e117b1ffd2 | |||
8d7fbb614b | |||
a31cfc521c | |||
55a1ce7adf | |||
9f3da659aa | |||
8f9aeaaa67 | |||
b9a5ce1c06 | |||
f67229efa3 | |||
f707f545aa | |||
62f4ea5f0f | |||
ecc3406ca6 | |||
e244b5180e | |||
f85d3d7857 | |||
b404d47b16 | |||
815d1ffa19 | |||
d1063c62b3 | |||
3a0b7355e5 | |||
3bdd4e249f | |||
2c1f55069f | |||
e72f741e78 | |||
f0bcfd0e78 | |||
82e06766b8 | |||
eea1600a38 | |||
8f8c390c75 | |||
23a96dca2d | |||
6f7df8a1fa | |||
92298e5271 | |||
27f0817000 | |||
4596fc0217 | |||
46de203f85 | |||
d752a8907b | |||
4fe369e188 | |||
d8930bbdc2 | |||
ad7be5087c | |||
a4405d7c6f | |||
88f7ddb27d | |||
98f5acebdb | |||
ff78149ec2 | |||
66b7870da7 | |||
82088a8489 | |||
ebcf762132 | |||
ed6b68babf | |||
2e09115c0c | |||
4a8d56a820 | |||
0a3dd872e3 | |||
3e690e0062 | |||
7f8d6c1066 | |||
c6d502f7f8 | |||
7aff3641a1 | |||
2194b5a5c3 | |||
8a35290686 | |||
e40519c32a | |||
b560189c0e | |||
59cfc8a729 | |||
72ed2e90d0 | |||
4e82a76998 | |||
51d5b433d0 | |||
cc0d0a9d1e | |||
82f26fe5f5 | |||
8de57c9887 | |||
ace4e4ffa5 | |||
1fa97903a3 | |||
7e61645b82 | |||
46b0ce9fc6 | |||
78750a7fec | |||
77d9975eb2 | |||
7eed4ee837 | |||
292b435495 | |||
5939c420ce | |||
a5cc9dbb53 | |||
2b810a4e57 | |||
2acf369664 | |||
860b79289f | |||
b519d41f42 | |||
faf184ad63 | |||
1e0f455855 | |||
ced30982df | |||
fed429b0cc | |||
9cb3107dda | |||
548a972c2a | |||
20dcc25eed | |||
620d1402fe | |||
36fb4f4fdb | |||
ea83445149 | |||
1319ff4376 | |||
9c1311c801 | |||
2ce93482b9 | |||
ed2a47f822 | |||
cdee9add01 | |||
2f85b1691a | |||
bf441e8b9e | |||
1c86e9b3b2 | |||
9d6e869899 | |||
e906bf4f31 | |||
5f08bdf8b9 | |||
f1ed022a4d | |||
151e4b9fcc | |||
d0f089a55d | |||
cb05f9bbe9 | |||
fda30cb3e3 | |||
2951e721df | |||
3449f1e256 | |||
6480d1b288 | |||
e76211aa32 | |||
a16de8f842 | |||
24f1dd3b81 | |||
f39551ce7e | |||
3beb7116af | |||
4b1a825efc | |||
01e62551f5 | |||
2f23533a25 | |||
054fbbe8b8 | |||
155d938e04 | |||
94a2ac7884 | |||
b75a98522a | |||
d7dc1b5e44 | |||
e075ea7ae7 | |||
415519acd3 | |||
8cbb836985 | |||
8d0f8bd657 | |||
66547d8fd0 | |||
6e7d5f0925 | |||
29dfa5570a | |||
0c028a03ec | |||
a54c049051 | |||
40904ce0c4 | |||
88f01f5653 | |||
c66794c265 | |||
e4acd83541 | |||
a57f8a1301 | |||
ae9b4e6fa7 | |||
478eca31c7 | |||
2e1603938c | |||
0c9c2accc2 | |||
0fb41e5ced | |||
3f43dbb642 | |||
5069c06906 | |||
58698d7806 | |||
e26c25a062 | |||
0a6434b066 | |||
ff3550c304 | |||
6d4a14082c | |||
9ddf269c2c | |||
25a76a1492 | |||
8439a6ec2a | |||
1ef2eae3aa | |||
d5d034a0ff | |||
5ca35b3cd2 | |||
0a6a3f3163 | |||
3a601382e6 | |||
7a1fdde69e | |||
cbc2ea1b1a | |||
bdf801b0e8 | |||
fe5e8b7177 | |||
11f0f98ad8 | |||
801b534421 | |||
0fc83215e2 | |||
3d3a1a4642 | |||
32a40ba5de | |||
045271230d | |||
ec31f6bf9a | |||
4798d77088 | |||
08c6762039 | |||
26516045e7 | |||
a83b9f7911 | |||
1b7c77e49f | |||
3ab31a4be6 | |||
43dcf77123 | |||
d4bf2da3bd | |||
fa3882845a | |||
fa59748e00 | |||
c38ecb3b5b | |||
875efa8492 | |||
74964bde99 | |||
785fb5cc5a | |||
26d9f0278b | |||
22ebd53c17 | |||
a972c039c3 | |||
f5e18029fa | |||
317c7087c5 | |||
39abe7b7c1 | |||
36a7705a44 | |||
50a21885cf | |||
e86f3d9a49 | |||
738f2961ba | |||
f2bf8287ba | |||
9d5b34e1e7 | |||
d237f4014a | |||
8743a9bfd6 | |||
514d03f2d0 |
@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
aio/node_modules
|
|
@ -1,42 +0,0 @@
|
|||||||
# Heavily based on https://github.com/StefanScherer/dockerfiles-windows/ images.
|
|
||||||
# Combines the node windowsservercore image with the Bazel Prerequisites (https://docs.bazel.build/versions/master/install-windows.html).
|
|
||||||
# msys install taken from https://github.com/StefanScherer/dockerfiles-windows/issues/30
|
|
||||||
# VS redist install taken from https://github.com/StefanScherer/dockerfiles-windows/blob/master/apache/Dockerfile
|
|
||||||
# The nanoserver image won't work because MSYS2 does not run in it https://github.com/Alexpux/MSYS2-packages/issues/1493
|
|
||||||
|
|
||||||
# Before building this image, you must locally build node-windows:10.13.0-windowsservercore-1803.
|
|
||||||
# Clone https://github.com/StefanScherer/dockerfiles-windows/commit/4ce7101a766b9b880ac262479dd9126b64d656cf and build using
|
|
||||||
# docker build -t node-windows:10.13.0-windowsservercore-1803 --build-arg core=microsoft/windowsservercore:1803 --build-arg target=microsoft/windowsservercore:1803 .
|
|
||||||
FROM node-windows:10.13.0-windowsservercore-1803
|
|
||||||
|
|
||||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
|
||||||
|
|
||||||
# Install 7zip to extract msys2
|
|
||||||
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe
|
|
||||||
# For some reason the last letter in the destination directory is lost. So '/D=C:\\7zip0' will extract to '/D=C:\\7zip'.
|
|
||||||
RUN Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait
|
|
||||||
|
|
||||||
# Extract msys2
|
|
||||||
RUN Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz
|
|
||||||
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait
|
|
||||||
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait
|
|
||||||
RUN Remove-Item msys2.tar.xz
|
|
||||||
RUN Remove-Item msys2.tar
|
|
||||||
RUN Remove-Item 7z.exe
|
|
||||||
RUN Remove-Item -Recurse 7zip
|
|
||||||
|
|
||||||
# Add MSYS2 to PATH, and set BAZEL_SH
|
|
||||||
RUN [Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine)
|
|
||||||
RUN [Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine)
|
|
||||||
|
|
||||||
# Install Microsoft Visual C++ Redistributable for Visual Studio 2015
|
|
||||||
RUN Invoke-WebRequest -UseBasicParsing 'https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe' -OutFile vc_redist.x64.exe
|
|
||||||
RUN Start-Process 'c:\\vc_redist.x64.exe' -ArgumentList '/Install', '/Passive', '/NoRestart' -NoNewWindow -Wait
|
|
||||||
RUN Remove-Item vc_redist.x64.exe
|
|
||||||
|
|
||||||
# Add a fix for https://github.com/docker/for-win/issues/2920 as entry point to the container.
|
|
||||||
SHELL ["cmd", "/c"]
|
|
||||||
COPY "fix-msys64.cmd" "C:\\fix-msys64.cmd"
|
|
||||||
ENTRYPOINT cmd /C C:\\fix-msys64.cmd && cmd /c
|
|
||||||
|
|
||||||
CMD ["cmd.exe"]
|
|
@ -1,96 +0,0 @@
|
|||||||
# BuildKite configuration
|
|
||||||
|
|
||||||
This folder contains configuration for the [BuildKite](https://buildkite.com) based CI checks for
|
|
||||||
this repository.
|
|
||||||
|
|
||||||
BuildKite is a CI provider that provides build coordination and reports while we provide the
|
|
||||||
infrastructure.
|
|
||||||
|
|
||||||
CI runs are triggered by new PRs and will show up on the GitHub checks interface, along with the
|
|
||||||
other current CI solutions.
|
|
||||||
|
|
||||||
Currently it is only used for tests on Windows platforms.
|
|
||||||
|
|
||||||
|
|
||||||
## The build pipeline
|
|
||||||
|
|
||||||
BuildKite uses a pipeline for each repository. The `pipeline.yml` file defines pipeline
|
|
||||||
[build steps](https://buildkite.com/docs/pipelines/defining-steps) for this repository.
|
|
||||||
|
|
||||||
Run results can be seen in the GitHub checks interface and in the
|
|
||||||
[pipeline dashboard](https://buildkite.com/angular/angular).
|
|
||||||
|
|
||||||
Although most configuration is done via `pipeline.yml`, some options are only available
|
|
||||||
in the online [pipeline settings](https://buildkite.com/angular/angular/settings).
|
|
||||||
|
|
||||||
|
|
||||||
## Infrastructure
|
|
||||||
|
|
||||||
BuildKite does not provide the host machines where the builds runs, providing instead the
|
|
||||||
[BuildKite Agent](https://buildkite.com/docs/agent/v3) that should be run our own infrastructure.
|
|
||||||
|
|
||||||
|
|
||||||
### Agents
|
|
||||||
|
|
||||||
This agent polls the BuildKite API for builds, runs them, and reports back the results.
|
|
||||||
Agents are the unit of concurrency: each agent can run one build at any given time.
|
|
||||||
Adding agents allows more builds to be ran at the same time.
|
|
||||||
|
|
||||||
Individual agents can have tags, and pipeline steps can target only agents with certain tags via the
|
|
||||||
`agents` field in `pipeline.yml`.
|
|
||||||
For example: agents on Windows machines are tagged as `windows`, and the Windows specific build
|
|
||||||
steps list `windows: true` in their `agents` field.
|
|
||||||
|
|
||||||
You can see the current agent pool, along with their tags, in the
|
|
||||||
[agents list](https://buildkite.com/organizations/angular/agents).
|
|
||||||
|
|
||||||
|
|
||||||
### Our host machines
|
|
||||||
|
|
||||||
We use [Google Cloud](https://cloud.google.com/) as our cloud provider, under the
|
|
||||||
[Angular project](https://console.cloud.google.com/home/dashboard?project=internal-200822).
|
|
||||||
To access this project you need need to be logged in with a Google account that's a member of
|
|
||||||
team@angular.io.
|
|
||||||
For googlers this may be your google.com account, for others it is an angular.io account.
|
|
||||||
|
|
||||||
In this project we have a number of Windows VMs running, each of them with several agents.
|
|
||||||
The `provision-windows-buildkite.ps1` file contains instructions on how to create new host VMs that
|
|
||||||
are fully configured to run the BuildKite agents as services.
|
|
||||||
|
|
||||||
Our pipeline uses [docker-buildkite-plugin](https://github.com/buildkite-plugins/docker-buildkite-plugin)
|
|
||||||
to run build steps inside docker containers.
|
|
||||||
This way we achieve isolation and hermeticity.
|
|
||||||
|
|
||||||
The `Dockerfile` file describes a custom Docker image that includes NodeJs, Yarn, and the Bazel
|
|
||||||
pre-requisites on Windows.
|
|
||||||
|
|
||||||
To upload a new version of the docker image, follow any build instructions in `Dockerfile` and then
|
|
||||||
run `docker build -t angular/node-bazel-windows:NEW_VERSION`, followed by
|
|
||||||
`docker push angular/node-bazel-windows:NEW_VERSION`.
|
|
||||||
After being pushed it should be available online, and you can use the new version in `pipeline.yml`.
|
|
||||||
|
|
||||||
|
|
||||||
## Caretaker
|
|
||||||
|
|
||||||
BuildKite status can be found at https://www.buildkitestatus.com/.
|
|
||||||
|
|
||||||
Issues related to the BuildKite setup should be escalated to the Tools Team via the current
|
|
||||||
caretaker, followed by Alex Eagle and Filipe Silva.
|
|
||||||
|
|
||||||
Support requests should be submitted via email to support@buildkite.com and cc Igor, Misko, Alex,
|
|
||||||
Jeremy and Manu
|
|
||||||
|
|
||||||
|
|
||||||
## Rollout strategy
|
|
||||||
|
|
||||||
At the moment our BuildKite CI uses 1 host VM running 4 agents, thus being capable of 4 concurrent
|
|
||||||
builds.
|
|
||||||
The only test running is `bazel test //tools/ts-api-guardian:all`, and the PR check is not
|
|
||||||
mandatory.
|
|
||||||
|
|
||||||
In the future we should add cache support to speed up the initial `yarn` install, and also Bazel
|
|
||||||
remote caching to speed up Bazel builds.
|
|
||||||
|
|
||||||
After the current setup is verified as stable and reliable the GitHub PR check can become mandatory.
|
|
||||||
|
|
||||||
The tests ran should also be expanded to cover most, if not all, of the Bazel tests.
|
|
@ -1,6 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Fix for https://github.com/docker/for-win/issues/2920
|
|
||||||
REM echo "Fixing msys64 folder..."
|
|
||||||
REM Touch all .dll files inside C:\msys64\
|
|
||||||
forfiles /p C:\msys64\ /s /m *.dll /c "cmd /c Copy /B @path+,, >NUL"
|
|
||||||
REM echo "Fixed msys64 folder."
|
|
@ -1,10 +0,0 @@
|
|||||||
steps:
|
|
||||||
- label: windows-test
|
|
||||||
commands:
|
|
||||||
- "yarn install --frozen-lockfile --non-interactive --network-timeout 100000"
|
|
||||||
- "yarn bazel test //tools/ts-api-guardian:all --noshow_progress"
|
|
||||||
plugins:
|
|
||||||
- docker#v2.1.0:
|
|
||||||
image: "filipesilva/node-bazel-windows:0.0.2"
|
|
||||||
agents:
|
|
||||||
windows: true
|
|
@ -1,92 +0,0 @@
|
|||||||
# PowerShell script to provision a Windows Server with BuildKite
|
|
||||||
# This script follows https://buildkite.com/docs/agent/v3/windows.
|
|
||||||
|
|
||||||
# Instructions
|
|
||||||
|
|
||||||
# VM creation:
|
|
||||||
# In Google Cloud Platform, create a Compute Engine instance.
|
|
||||||
# We recommend machine type n1-highcpu-16 (16 vCPUs, 14.4 GB memory).
|
|
||||||
# Use a windows boot disk with container support such as
|
|
||||||
# "Windows Server version 1803 Datacenter Core for Containers".
|
|
||||||
# Give it a name, then click "Create".
|
|
||||||
|
|
||||||
# VM setup:
|
|
||||||
# In the Compute Engine menu, select "VM Instances". Click on the VM name you chose before.
|
|
||||||
# Click "Set Windows Password" to choose a username and password.
|
|
||||||
# Click RDP to open a remote desktop via browser, using the username and password.
|
|
||||||
# In the Windows command prompt start an elevated powershell by inputing
|
|
||||||
# "powershell -Command "Start-Process PowerShell -Verb RunAs" followed by Enter.
|
|
||||||
# Download and execute this script from GitHub, passing the token (mandatory), tags (optional)
|
|
||||||
# and number of agents (optional) as args:
|
|
||||||
# ```
|
|
||||||
# Invoke-WebRequest -Uri https://raw.githubusercontent.com/angular/angular/master/.buildkite/provision-windows-buildkite.ps1 -OutFile provision.ps1
|
|
||||||
# .\provision.ps1 -token "MY_TOKEN" -tags "windows=true,another_tag=true" -agents 4
|
|
||||||
# ```
|
|
||||||
# The VM should restart and be fully configured.
|
|
||||||
|
|
||||||
# Creating extra VMs
|
|
||||||
# You can create an image of the current VM by following the instructions below.
|
|
||||||
# https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image
|
|
||||||
# Then create a new VM and choose "Custom images".
|
|
||||||
|
|
||||||
|
|
||||||
# Script proper.
|
|
||||||
|
|
||||||
# Get the token and tags from arguments.
|
|
||||||
param (
|
|
||||||
[Parameter(Mandatory=$true)][string]$token,
|
|
||||||
[string]$tags = ""
|
|
||||||
[Int]$agents = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
# Allow HTTPS
|
|
||||||
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
|
|
||||||
|
|
||||||
# Helper to add to PATH.
|
|
||||||
# Will take current PATH so avoid running it after anything to modifies only the powershell session path.
|
|
||||||
function Add-Path ([string]$newPathItem) {
|
|
||||||
$Env:Path+= ";" + $newPathItem + ";"
|
|
||||||
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::Machine)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install Git for Windows
|
|
||||||
Write-Host "Installing Git for Windows."
|
|
||||||
Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.19.1.windows.1/Git-2.19.1-64-bit.exe -OutFile git.exe
|
|
||||||
.\git.exe /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh" /DIR="C:\git"
|
|
||||||
Add-Path "C:\git\bin"
|
|
||||||
Remove-Item git.exe
|
|
||||||
|
|
||||||
# Download NSSM (https://nssm.cc/) to run the BuildKite agent as a service.
|
|
||||||
Write-Host "Downloading NSSM."
|
|
||||||
Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile nssm.zip
|
|
||||||
Expand-Archive -Path nssm.zip -DestinationPath C:\nssm
|
|
||||||
Add-Path "C:\nssm\nssm-2.24-101-g897c7ad\win64"
|
|
||||||
Remove-Item nssm.zip
|
|
||||||
|
|
||||||
# Run the BuildKite agent install script
|
|
||||||
Write-Host "Installing BuildKite agent."
|
|
||||||
$env:buildkiteAgentToken = $token
|
|
||||||
$env:buildkiteAgentTags = $tags
|
|
||||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
|
||||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/buildkite/agent/master/install.ps1'))
|
|
||||||
|
|
||||||
# Configure the BuildKite agent clone and timestamp behavior
|
|
||||||
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ngit-clone-flags=--config core.autocrlf=input --config core.eol=lf --config core.longpaths=true --config core.symlinks=true`n"
|
|
||||||
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ntimestamp-lines=true`n"
|
|
||||||
|
|
||||||
# Register the BuildKite agent service using NSSM, so that it persists through restarts and is
|
|
||||||
# restarted if the process dies.
|
|
||||||
for ($i=1; $i -le $agents; $i++)
|
|
||||||
{
|
|
||||||
$agentName = "buildkite-agent-$i"
|
|
||||||
Write-Host "Registering $agentName as a service."
|
|
||||||
nssm.exe install $agentName "C:\buildkite-agent\bin\buildkite-agent.exe" "start"
|
|
||||||
nssm.exe set $agentName AppStdout "C:\buildkite-agent\$agentName.log"
|
|
||||||
nssm.exe set $agentName AppStderr "C:\buildkite-agent\$agentName.log"
|
|
||||||
nssm.exe status $agentName
|
|
||||||
nssm.exe start $agentName
|
|
||||||
nssm.exe status $agentName
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restart the machine.
|
|
||||||
Restart-Computer
|
|
@ -13,7 +13,7 @@ a GitHub token that enables publishing snapshots.
|
|||||||
|
|
||||||
To create the github_token file, we take this approach:
|
To create the github_token file, we take this approach:
|
||||||
- Find the angular-builds:token in http://valentine
|
- Find the angular-builds:token in http://valentine
|
||||||
- Go inside the CircleCI default docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it circleci/node:10.12`
|
- Go inside the ngcontainer docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it angular/ngcontainer`
|
||||||
- echo "https://[token]:@github.com" > credentials
|
- echo "https://[token]:@github.com" > credentials
|
||||||
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY
|
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY
|
||||||
- If needed, base64-encode the result so you can copy-paste it out of docker: `base64 github_token`
|
- If needed, base64-encode the result so you can copy-paste it out of docker: `base64 github_token`
|
@ -1,6 +1,6 @@
|
|||||||
# These options are enabled when running on CI
|
# These options are enabled when running on CI
|
||||||
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
||||||
# See documentation in /docs/BAZEL.md
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
|
||||||
# Don't be spammy in the logs
|
# Don't be spammy in the logs
|
||||||
# TODO(gmagolan): Hide progress again once build performance improves
|
# TODO(gmagolan): Hide progress again once build performance improves
|
||||||
@ -8,11 +8,30 @@
|
|||||||
# error: Too long with no output (exceeded 10m0s)
|
# error: Too long with no output (exceeded 10m0s)
|
||||||
# build --noshow_progress
|
# build --noshow_progress
|
||||||
|
|
||||||
|
# Don't run manual tests
|
||||||
|
test --test_tag_filters=-manual
|
||||||
|
|
||||||
# Print all the options that apply to the build.
|
# Print all the options that apply to the build.
|
||||||
# This helps us diagnose which options override others
|
# This helps us diagnose which options override others
|
||||||
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
|
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
|
||||||
build --announce_rc
|
build --announce_rc
|
||||||
|
|
||||||
|
# Create dist/bin symlink to $(bazel info bazel-bin)
|
||||||
|
# We use this when uploading artifacts after the build finishes
|
||||||
|
build --symlink_prefix=dist/
|
||||||
|
|
||||||
|
# Enable experimental CircleCI bazel remote cache proxy
|
||||||
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
build --experimental_remote_spawn_cache --remote_rest_cache=http://localhost:7643
|
||||||
|
|
||||||
|
# Prevent unstable environment variables from tainting cache keys
|
||||||
|
build --experimental_strict_action_env
|
||||||
|
|
||||||
|
# Save downloaded repositories such as the go toolchain
|
||||||
|
# This directory can then be included in the CircleCI cache
|
||||||
|
# It should save time running the first build
|
||||||
|
build --experimental_repository_cache=/home/circleci/bazel_repository_cache
|
||||||
|
|
||||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||||
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
||||||
@ -21,6 +40,3 @@ build --local_resources=14336,8.0,1.0
|
|||||||
|
|
||||||
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
||||||
test --flaky_test_attempts=2
|
test --flaky_test_attempts=2
|
||||||
|
|
||||||
# More details on failures
|
|
||||||
build --verbose_failures=true
|
|
||||||
|
@ -7,50 +7,36 @@
|
|||||||
# To validate changes, use an online parser, eg.
|
# To validate changes, use an online parser, eg.
|
||||||
# http://yaml-online-parser.appspot.com/
|
# http://yaml-online-parser.appspot.com/
|
||||||
|
|
||||||
# Note that the browser docker image comes with Chrome and Firefox preinstalled. This is just
|
# Variables
|
||||||
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
|
|
||||||
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
|
## IMPORTANT
|
||||||
# docker image with browsers pre-installed.
|
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
|
||||||
# **NOTE**: If you change the version of the docker images, also change the `cache_key` suffix.
|
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
|
||||||
var_1: &default_docker_image circleci/node:10.12
|
var_1: &docker_image angular/ngcontainer:0.6.0
|
||||||
var_2: &browsers_docker_image circleci/node:10.12-browsers
|
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.6.0
|
||||||
var_3: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-node-10.12
|
|
||||||
|
|
||||||
# Define common ENV vars
|
# Define common ENV vars
|
||||||
var_4: &define_env_vars
|
var_3: &define_env_vars
|
||||||
run:
|
run: echo "export PROJECT_ROOT=$(pwd)" >> $BASH_ENV
|
||||||
name: Define environment variables
|
|
||||||
command: ./.circleci/env.sh
|
|
||||||
|
|
||||||
var_5: &setup_bazel_remote_execution
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
var_4: &setup-bazel-remote-cache
|
||||||
run:
|
run:
|
||||||
name: "Setup bazel RBE remote execution"
|
name: Start up bazel remote cache proxy
|
||||||
command: |
|
command: ~/bazel-remote-proxy -backend circleci://
|
||||||
openssl aes-256-cbc -d -in .circleci/gcp_token -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
background: true
|
||||||
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
|
||||||
sudo bash -c "echo 'build --config=remote' >> /etc/bazel.bazelrc"
|
|
||||||
|
|
||||||
# Settings common to each job
|
# Settings common to each job
|
||||||
var_6: &job_defaults
|
anchor_1: &job_defaults
|
||||||
working_directory: ~/ng
|
working_directory: ~/ng
|
||||||
docker:
|
docker:
|
||||||
- image: *default_docker_image
|
- image: *docker_image
|
||||||
|
|
||||||
# After checkout, rebase on top of master.
|
# After checkout, rebase on top of master.
|
||||||
# Similar to travis behavior, but not quite the same.
|
# Similar to travis behavior, but not quite the same.
|
||||||
# See https://discuss.circleci.com/t/1662
|
# See https://discuss.circleci.com/t/1662
|
||||||
var_7: &post_checkout
|
anchor_2: &post_checkout
|
||||||
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
post: git pull --ff-only origin "refs/pull/${CIRCLE_PULL_REQUEST//*pull\//}/merge"
|
||||||
|
|
||||||
var_8: &yarn_install
|
|
||||||
run:
|
|
||||||
name: Running Yarn install
|
|
||||||
command: yarn install --frozen-lockfile --non-interactive
|
|
||||||
|
|
||||||
var_9: &setup_circleci_bazel_config
|
|
||||||
run:
|
|
||||||
name: Setting up CircleCI bazel configuration
|
|
||||||
command: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
@ -59,69 +45,55 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
|
|
||||||
|
# Check BUILD.bazel formatting before we have a node_modules directory
|
||||||
|
# Then we don't need any exclude pattern to avoid checking those files
|
||||||
|
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
|
||||||
|
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
||||||
|
# Run the skylark linter to check our Bazel rules
|
||||||
|
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)
|
||||||
|
# which has no replacement, see https://github.com/bazelbuild/bazel/issues/4858
|
||||||
|
- run: 'find . -type f -name "*.bzl" |
|
||||||
|
xargs java -jar /usr/local/bin/Skylint_deploy.jar --disable-checks=deprecated-api ||
|
||||||
|
(echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)'
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
|
||||||
- *yarn_install
|
|
||||||
|
|
||||||
- run: 'yarn bazel:format -mode=check ||
|
|
||||||
(echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)'
|
|
||||||
# Run the skylark linter to check our Bazel rules
|
|
||||||
- run: 'yarn bazel:lint ||
|
|
||||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
|
|
||||||
|
|
||||||
|
- run: yarn install --frozen-lockfile --non-interactive
|
||||||
- run: ./node_modules/.bin/gulp lint
|
- run: ./node_modules/.bin/gulp lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
key: *cache_key
|
- run: .circleci/setup_cache.sh
|
||||||
- *define_env_vars
|
|
||||||
- *setup_circleci_bazel_config
|
|
||||||
- *yarn_install
|
|
||||||
|
|
||||||
# Setup remote execution and run RBE-compatible tests.
|
|
||||||
- *setup_bazel_remote_execution
|
|
||||||
- run: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-local
|
|
||||||
# Now run RBE incompatible tests locally.
|
|
||||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
- run: yarn bazel test //... --build_tag_filters=-ivy-only,local --test_tag_filters=-ivy-only,local
|
- *setup-bazel-remote-cache
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
key: *cache_key
|
|
||||||
paths:
|
|
||||||
- "node_modules"
|
|
||||||
- "~/bazel_repository_cache"
|
|
||||||
|
|
||||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
|
||||||
test_ivy_aot:
|
|
||||||
<<: *job_defaults
|
|
||||||
resource_class: xlarge
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
|
||||||
- *setup_circleci_bazel_config
|
|
||||||
- *yarn_install
|
|
||||||
- *setup_bazel_remote_execution
|
|
||||||
|
|
||||||
# We need to explicitly specify the --symlink_prefix option because otherwise we would
|
- run: ls /home/circleci/bazel_repository_cache || true
|
||||||
# not be able to easily find the output bin directory when uploading artifacts for size
|
- run: bazel info release
|
||||||
# measurements.
|
- run: bazel run @nodejs//:yarn
|
||||||
- run: yarn test-ivy-aot //... --symlink_prefix=dist/
|
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||||
|
# This avoids waiting for the slowest build target to finish before running the first test
|
||||||
|
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||||
|
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||||
|
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||||
|
|
||||||
# Publish bundle artifacts which will be used to calculate the size change. **Note**: Make
|
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||||
# sure that the size plugin from the Angular robot fetches the artifacts from this CircleCI
|
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||||
# job (see .github/angular-robot.yml). Additionally any artifacts need to be stored with the
|
# The destination keys need be format {projectName}/{context}/{fileName} so that the github-robot can process them for size calculations
|
||||||
# following path format: "{projectName}/{context}/{fileName}". This format is necessary
|
# projectName should remain consistant to group files
|
||||||
# because otherwise the bot is not able to pick up the artifacts from CircleCI. See:
|
# context and fileName can be almost anything (within usual URI rules)
|
||||||
# https://github.com/angular/github-robot/blob/master/functions/src/plugins/size.ts#L392-L394
|
# There should only be exactly 2 forward slashes in the path
|
||||||
|
# This is so they're backwards compatiable with the existing data we have on bundle sizes
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
||||||
destination: core/hello_world/bundle
|
destination: core/hello_world/bundle
|
||||||
@ -134,131 +106,48 @@ jobs:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
||||||
destination: core/todo/bundle.br
|
destination: core/todo/bundle.br
|
||||||
|
- save_cache:
|
||||||
test_aio:
|
key: *cache_key
|
||||||
|
paths:
|
||||||
|
- "node_modules"
|
||||||
|
- "~/bazel_repository_cache"
|
||||||
|
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||||
|
test_ivy_jit:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
docker:
|
resource_class: xlarge
|
||||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
- run: .circleci/setup_cache.sh
|
||||||
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
|
- *setup-bazel-remote-cache
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
|
||||||
# Build aio
|
|
||||||
- run: yarn --cwd aio build --progress=false
|
|
||||||
# Lint the code
|
|
||||||
- run: yarn --cwd aio lint
|
|
||||||
# Run PWA-score tests
|
|
||||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
|
||||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
|
||||||
# Check the bundle sizes.
|
|
||||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
|
||||||
- run: yarn --cwd aio payload-size
|
|
||||||
# Run unit tests
|
|
||||||
- run: yarn --cwd aio test --watch=false
|
|
||||||
# Run e2e tests
|
|
||||||
- run: yarn --cwd aio e2e
|
|
||||||
# Run unit tests for Firebase redirects
|
|
||||||
- run: yarn --cwd aio redirects-test
|
|
||||||
|
|
||||||
deploy_aio:
|
- run: bazel run @yarn//:yarn
|
||||||
|
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
|
||||||
|
|
||||||
|
test_ivy_aot:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
docker:
|
resource_class: xlarge
|
||||||
# Needed because before deploying the deploy-production script runs the PWA score tests.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
- run: .circleci/setup_cache.sh
|
||||||
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
|
- *setup-bazel-remote-cache
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
|
||||||
# Deploy angular.io to production (if necessary)
|
|
||||||
- run: setPublicVar CI_STABLE_BRANCH "$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')"
|
|
||||||
- run: yarn --cwd aio deploy-production
|
|
||||||
|
|
||||||
test_aio_local:
|
- run: bazel run @yarn//:yarn
|
||||||
<<: *job_defaults
|
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
||||||
docker:
|
|
||||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- attach_workspace:
|
|
||||||
at: dist
|
|
||||||
- *define_env_vars
|
|
||||||
# Build aio (with local Angular packages)
|
|
||||||
- run: yarn --cwd aio build-local --progress=false
|
|
||||||
# Run PWA-score tests
|
|
||||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
|
||||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
|
||||||
# Run unit tests
|
|
||||||
- run: yarn --cwd aio test --watch=false
|
|
||||||
# Run e2e tests
|
|
||||||
- run: yarn --cwd aio e2e
|
|
||||||
|
|
||||||
test_aio_tools:
|
|
||||||
<<: *job_defaults
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- attach_workspace:
|
|
||||||
at: dist
|
|
||||||
- *define_env_vars
|
|
||||||
# Install
|
|
||||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
|
||||||
- run: yarn --cwd aio extract-cli-command-docs
|
|
||||||
# Run tools tests
|
|
||||||
- run: yarn --cwd aio tools-test
|
|
||||||
- run: ./aio/aio-builds-setup/scripts/test.sh
|
|
||||||
|
|
||||||
test_docs_examples_0:
|
|
||||||
<<: *job_defaults
|
|
||||||
docker:
|
|
||||||
# Needed because the example e2e tests depend on Chrome.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- attach_workspace:
|
|
||||||
at: dist
|
|
||||||
- *define_env_vars
|
|
||||||
# Install root
|
|
||||||
- *yarn_install
|
|
||||||
# Install aio
|
|
||||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
|
||||||
# Run examples tests
|
|
||||||
- run: yarn --cwd aio example-e2e --setup --local --shard=0/2
|
|
||||||
|
|
||||||
test_docs_examples_1:
|
|
||||||
<<: *job_defaults
|
|
||||||
docker:
|
|
||||||
# Needed because the example e2e tests depend on Chrome.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- attach_workspace:
|
|
||||||
at: dist
|
|
||||||
- *define_env_vars
|
|
||||||
# Install root
|
|
||||||
- *yarn_install
|
|
||||||
# Install aio
|
|
||||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
|
||||||
# Run examples tests
|
|
||||||
- run: yarn --cwd aio example-e2e --setup --local --shard=1/2
|
|
||||||
|
|
||||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
|
||||||
aio_preview:
|
aio_preview:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
environment:
|
environment:
|
||||||
@ -268,32 +157,13 @@ jobs:
|
|||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
- run: yarn install --frozen-lockfile --non-interactive
|
||||||
- *yarn_install
|
- run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH
|
||||||
- run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH $CI_PULL_REQUEST $CI_COMMIT
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: *aio_preview_artifact_path
|
path: *aio_preview_artifact_path
|
||||||
# The `destination` needs to be kept in synch with the value of
|
# The `destination` needs to be kept in synch with the value of
|
||||||
# `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile`
|
# `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile`
|
||||||
destination: aio/dist/aio-snapshot.tgz
|
destination: aio/dist/aio-snapshot.tgz
|
||||||
- run: node ./aio/scripts/create-preview $CIRCLE_BUILD_NUM
|
|
||||||
|
|
||||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
|
||||||
test_aio_preview:
|
|
||||||
<<: *job_defaults
|
|
||||||
docker:
|
|
||||||
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- *define_env_vars
|
|
||||||
- run: yarn install --cwd aio --frozen-lockfile --non-interactive
|
|
||||||
- run:
|
|
||||||
name: Wait for preview and run tests
|
|
||||||
command: node aio/scripts/test-preview.js $CI_PULL_REQUEST $CI_COMMIT $CI_AIO_MIN_PWA_SCORE
|
|
||||||
|
|
||||||
# This job exists only for backwards-compatibility with old scripts and tests
|
# This job exists only for backwards-compatibility with old scripts and tests
|
||||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||||
@ -306,15 +176,15 @@ jobs:
|
|||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
key: *cache_key
|
- run: .circleci/setup_cache.sh
|
||||||
- *define_env_vars
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
- *setup_circleci_bazel_config
|
- *setup-bazel-remote-cache
|
||||||
- *yarn_install
|
|
||||||
- *setup_bazel_remote_execution
|
|
||||||
|
|
||||||
|
- run: bazel run @nodejs//:yarn
|
||||||
- run: scripts/build-packages-dist.sh
|
- run: scripts/build-packages-dist.sh
|
||||||
|
|
||||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||||
@ -324,7 +194,7 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- packages-dist
|
- packages-dist
|
||||||
- packages-dist-ivy-jit
|
- packages-dist-ivy-jit
|
||||||
- packages-dist-ivy-aot
|
- packages-dist-ivy-local
|
||||||
|
|
||||||
# We run the integration tests outside of Bazel for now.
|
# We run the integration tests outside of Bazel for now.
|
||||||
# They are a separate workflow job so that they can be easily re-run.
|
# They are a separate workflow job so that they can be easily re-run.
|
||||||
@ -334,41 +204,35 @@ jobs:
|
|||||||
# See comments inside the integration/run_tests.sh script.
|
# See comments inside the integration/run_tests.sh script.
|
||||||
integration_test:
|
integration_test:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
docker:
|
|
||||||
# Needed because the integration tests expect Chrome to be installed (e.g cli-hello-world)
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
||||||
# of memory. Together with the system under test, this can exhaust the RAM
|
# of memory. Together with the system under test, this can exhaust the RAM
|
||||||
# on a 4G worker so we use a larger machine here too.
|
# on a 4G worker so we use a larger machine here too.
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
|
||||||
key: *cache_key
|
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: dist
|
at: dist
|
||||||
- *define_env_vars
|
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
||||||
- run: ./integration/run_tests.sh
|
|
||||||
|
|
||||||
# This job updates the content of repos like github.com/angular/core-builds
|
# This job updates the content of repos like github.com/angular/core-builds
|
||||||
# for every green build on angular/angular.
|
# for every green build on angular/angular.
|
||||||
publish_snapshot:
|
publish_snapshot:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- *define_env_vars
|
|
||||||
# See below - ideally this job should not trigger for non-upstream builds.
|
# See below - ideally this job should not trigger for non-upstream builds.
|
||||||
# But since it does, we have to check this condition.
|
# But since it does, we have to check this condition.
|
||||||
- run:
|
- run:
|
||||||
name: Skip this job for Pull Requests and Fork builds
|
name: Skip this job for Pull Requests and Fork builds
|
||||||
# Note, `|| true` on the end makes this step always exit 0
|
# Note, `|| true` on the end makes this step always exit 0
|
||||||
command: '[[
|
command: '[[
|
||||||
"$CI_PULL_REQUEST" != "false"
|
-v CIRCLE_PR_NUMBER
|
||||||
|| "$CI_REPO_OWNER" != "angular"
|
|| "$CIRCLE_PROJECT_USERNAME" != "angular"
|
||||||
|| "$CI_REPO_NAME" != "angular"
|
|| "$CIRCLE_PROJECT_REPONAME" != "angular"
|
||||||
]] && circleci step halt || true'
|
]] && circleci step halt || true'
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: dist
|
at: dist
|
||||||
# CircleCI has a config setting to force SSH for all github connections
|
# CircleCI has a config setting to force SSH for all github connections
|
||||||
@ -382,23 +246,12 @@ jobs:
|
|||||||
|
|
||||||
aio_monitoring:
|
aio_monitoring:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
docker:
|
|
||||||
# This job needs Chrome to be globally installed because the tests run with Protractor
|
|
||||||
# which does not load the browser through the Bazel webtesting rules.
|
|
||||||
- image: *browsers_docker_image
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- *define_env_vars
|
- run: xvfb-run --auto-servernum ./aio/scripts/test-production.sh
|
||||||
- run:
|
|
||||||
name: Run tests against the deployed apps
|
|
||||||
command: ./aio/scripts/test-production.sh $CI_AIO_MIN_PWA_SCORE
|
|
||||||
- run:
|
|
||||||
name: Notify caretaker about failure
|
|
||||||
command: 'curl --request POST --header "Content-Type: application/json" --data "{\"text\":\":x: \`$CIRCLE_JOB\` job failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}" $CI_SECRET_SLACK_CARETAKER_WEBHOOK_URL'
|
|
||||||
when: on_fail
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
@ -406,31 +259,9 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
|
- test_ivy_jit
|
||||||
- test_ivy_aot
|
- test_ivy_aot
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
- test_aio
|
|
||||||
- deploy_aio:
|
|
||||||
requires:
|
|
||||||
- test_aio
|
|
||||||
- test_aio_local:
|
|
||||||
requires:
|
|
||||||
- build-packages-dist
|
|
||||||
- test_aio_tools:
|
|
||||||
requires:
|
|
||||||
- build-packages-dist
|
|
||||||
- test_docs_examples_0:
|
|
||||||
requires:
|
|
||||||
- build-packages-dist
|
|
||||||
- test_docs_examples_1:
|
|
||||||
requires:
|
|
||||||
- build-packages-dist
|
|
||||||
- aio_preview:
|
|
||||||
# Only run on PR builds. (There can be no previews for non-PR builds.)
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: /pull\/\d+/
|
|
||||||
- test_aio_preview:
|
|
||||||
requires:
|
|
||||||
- aio_preview
|
- aio_preview
|
||||||
- integration_test:
|
- integration_test:
|
||||||
requires:
|
requires:
|
||||||
@ -443,12 +274,9 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
# Only publish if tests and integration tests pass
|
# Only publish if tests and integration tests pass
|
||||||
- test
|
- test
|
||||||
|
- test_ivy_jit
|
||||||
- test_ivy_aot
|
- test_ivy_aot
|
||||||
- integration_test
|
- integration_test
|
||||||
# Only publish if `aio`/`docs` tests using the locally built Angular packages pass
|
|
||||||
- test_aio_local
|
|
||||||
- test_docs_examples_0
|
|
||||||
- test_docs_examples_1
|
|
||||||
# Get the artifacts to publish from the build-packages-dist job
|
# Get the artifacts to publish from the build-packages-dist job
|
||||||
# since the publishing script expects the legacy outputs layout.
|
# since the publishing script expects the legacy outputs layout.
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
@ -463,3 +291,6 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
notify:
|
||||||
|
webhooks:
|
||||||
|
- url: https://ngbuilds.io/circle-build
|
@ -1,38 +0,0 @@
|
|||||||
####################################################################################################
|
|
||||||
# Helpers for defining environment variables for CircleCI.
|
|
||||||
#
|
|
||||||
# In CircleCI, each step runs in a new shell. The way to share ENV variables across steps is to
|
|
||||||
# export them from `$BASH_ENV`, which is automatically sourced at the beginning of every step (for
|
|
||||||
# the default `bash` shell).
|
|
||||||
#
|
|
||||||
# See also https://circleci.com/docs/2.0/env-vars/#using-bash_env-to-set-environment-variables.
|
|
||||||
####################################################################################################
|
|
||||||
|
|
||||||
# Set and print an environment variable.
|
|
||||||
#
|
|
||||||
# Use this function for setting environment variables that are public, i.e. it is OK for them to be
|
|
||||||
# visible to anyone through the CI logs.
|
|
||||||
#
|
|
||||||
# Usage: `setPublicVar <name> <value>`
|
|
||||||
function setPublicVar() {
|
|
||||||
setSecretVar $1 $2;
|
|
||||||
echo "$1=$2";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set (without printing) an environment variable.
|
|
||||||
#
|
|
||||||
# Use this function for setting environment variables that are secret, i.e. should not be visible to
|
|
||||||
# everyone through the CI logs.
|
|
||||||
#
|
|
||||||
# Usage: `setSecretVar <name> <value>`
|
|
||||||
function setSecretVar() {
|
|
||||||
# WARNING: Secrets (e.g. passwords, access tokens) should NOT be printed.
|
|
||||||
# (Keep original shell options to restore at the end.)
|
|
||||||
local -r originalShellOptions=$(set +o);
|
|
||||||
set +x -eu -o pipefail;
|
|
||||||
|
|
||||||
echo "export $1=\"${2:-}\";" >> $BASH_ENV;
|
|
||||||
|
|
||||||
# Restore original shell options.
|
|
||||||
eval "$originalShellOptions";
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Load helpers and make them available everywhere (through `$BASH_ENV`).
|
|
||||||
readonly envHelpersPath="`dirname $0`/env-helpers.inc.sh";
|
|
||||||
source $envHelpersPath;
|
|
||||||
echo "source $envHelpersPath;" >> $BASH_ENV;
|
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
# Define PUBLIC environment variables for CircleCI.
|
|
||||||
####################################################################################################
|
|
||||||
setPublicVar PROJECT_ROOT "$(pwd)";
|
|
||||||
setPublicVar CI_AIO_MIN_PWA_SCORE "95";
|
|
||||||
# This is the branch being built; e.g. `pull/12345` for PR builds.
|
|
||||||
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
|
|
||||||
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
|
|
||||||
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available,
|
|
||||||
# i.e. on push builds (a.k.a. non-PR builds). That is fine, since we only need it in push builds.
|
|
||||||
setPublicVar CI_COMMIT_RANGE "$(sed -r 's|^.*/([0-9a-f]+\.\.\.[0-9a-f]+)$|\1|i' <<< ${CIRCLE_COMPARE_URL:-})";
|
|
||||||
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
|
|
||||||
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
|
|
||||||
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";
|
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
# Define SECRET environment variables for CircleCI.
|
|
||||||
####################################################################################################
|
|
||||||
setSecretVar CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN "$AIO_DEPLOY_TOKEN";
|
|
||||||
setSecretVar CI_SECRET_PAYLOAD_FIREBASE_TOKEN "$ANGULAR_PAYLOAD_TOKEN";
|
|
||||||
# Defined in https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
|
||||||
setSecretVar CI_SECRET_SLACK_CARETAKER_WEBHOOK_URL "$SLACK_CARETAKER_WEBHOOK_URL";
|
|
||||||
|
|
||||||
|
|
||||||
# Source `$BASH_ENV` to make the variables available immediately.
|
|
||||||
source $BASH_ENV;
|
|
Binary file not shown.
Binary file not shown.
@ -1,107 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Usage (cli):
|
|
||||||
* ```
|
|
||||||
* node create-preview <build-number> <job-name> <webhook-url>
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Usage (JS):
|
|
||||||
* ```js
|
|
||||||
* require('./trigger-webhook').
|
|
||||||
* triggerWebhook(buildNumber, jobName, webhookUrl).
|
|
||||||
* then(...);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Triggers a notification webhook with CircleCI specific info.
|
|
||||||
*
|
|
||||||
* It can be used for notifying external servers and trigger operations based on CircleCI job status
|
|
||||||
* (e.g. triggering the creation of a preview based on previously stored build atrifacts).
|
|
||||||
*
|
|
||||||
* The body of the sent payload is of the form:
|
|
||||||
* ```json
|
|
||||||
* {
|
|
||||||
* "payload": {
|
|
||||||
* "build_num": ${buildNumber}
|
|
||||||
* "build_parameters": {
|
|
||||||
* "CIRCLE_JOB": "${jobName}"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* When used from JS, it returns a promise which resolves to an object of the form:
|
|
||||||
* ```json
|
|
||||||
* {
|
|
||||||
* "statucCode": ${statusCode},
|
|
||||||
* "responseText": "${responseText}"
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* NOTE:
|
|
||||||
* - When used from the cli, the command will exit with an error code if the response's status code
|
|
||||||
* is outside the [200, 400) range.
|
|
||||||
* - When used from JS, the returned promise will be resolved, even if the response's status code is
|
|
||||||
* outside the [200, 400) range. It is up to the caller to decide how this should be handled.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
const {request} = require('https');
|
|
||||||
|
|
||||||
// Exports
|
|
||||||
module.exports = {
|
|
||||||
triggerWebhook,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run
|
|
||||||
if (require.resolve === module) {
|
|
||||||
_main(process.argv.slice(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
function _main(args) {
|
|
||||||
triggerWebhook(...args).
|
|
||||||
then(({statusCode, responseText}) => (200 <= statusCode && statusCode < 400) ?
|
|
||||||
console.log(`Status: ${statusCode}\n${responseText}`) :
|
|
||||||
Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))).
|
|
||||||
catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function postJson(url, data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const opts = {method: 'post', headers: {'Content-Type': 'application/json'}};
|
|
||||||
const onResponse = res => {
|
|
||||||
const statusCode = res.statusCode || -1;
|
|
||||||
let responseText = '';
|
|
||||||
|
|
||||||
res.
|
|
||||||
on('error', reject).
|
|
||||||
on('data', d => responseText += d).
|
|
||||||
on('end', () => resolve({statusCode, responseText}));
|
|
||||||
};
|
|
||||||
|
|
||||||
request(url, opts, onResponse).
|
|
||||||
on('error', reject).
|
|
||||||
end(JSON.stringify(data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerWebhook(buildNumber, jobName, webhookUrl) {
|
|
||||||
if (!buildNumber || !jobName || !webhookUrl || isNaN(buildNumber)) {
|
|
||||||
throw new Error(
|
|
||||||
'Missing or invalid arguments.\n' +
|
|
||||||
'Expected: buildNumber (number), jobName (string), webhookUrl (string)');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
payload: {
|
|
||||||
build_num: +buildNumber,
|
|
||||||
build_parameters: {CIRCLE_JOB: jobName},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return postJson(webhookUrl, data);
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
# https://editorconfig.org
|
# http://editorconfig.org
|
||||||
|
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
61
.github/ISSUE_TEMPLATE.md
vendored
61
.github/ISSUE_TEMPLATE.md
vendored
@ -1,10 +1,59 @@
|
|||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
<!--
|
||||||
|
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||||
|
|
||||||
Please help us process issues more efficiently by filing an
|
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
||||||
issue using one of the following templates:
|
-->
|
||||||
|
|
||||||
https://github.com/angular/angular/issues/new/choose
|
## 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 -->
|
||||||
|
[ ] Performance issue
|
||||||
|
[ ] 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
|
||||||
|
[ ] Other... Please describe:
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
Thank you!
|
## Current behavior
|
||||||
|
<!-- Describe how the issue manifests. -->
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
||||||
|
## Expected behavior
|
||||||
|
<!-- Describe what the desired behavior would be. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Minimal reproduction of the problem with instructions
|
||||||
|
<!--
|
||||||
|
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
|
||||||
|
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
|
||||||
|
-->
|
||||||
|
|
||||||
|
## What is the motivation / use case for changing the behavior?
|
||||||
|
<!-- Describe the motivation or the concrete use case. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
<pre><code>
|
||||||
|
Angular version: X.Y.Z
|
||||||
|
<!-- Check whether this is still an issue in the most recent Angular version -->
|
||||||
|
|
||||||
|
Browser:
|
||||||
|
- [ ] Chrome (desktop) version XX
|
||||||
|
- [ ] Chrome (Android) version XX
|
||||||
|
- [ ] Chrome (iOS) version XX
|
||||||
|
- [ ] Firefox version XX
|
||||||
|
- [ ] Safari (desktop) version XX
|
||||||
|
- [ ] Safari (iOS) version XX
|
||||||
|
- [ ] IE version XX
|
||||||
|
- [ ] Edge version XX
|
||||||
|
|
||||||
|
For Tooling issues:
|
||||||
|
- Node version: XX <!-- run `node --version` -->
|
||||||
|
- Platform: <!-- Mac, Linux, Windows -->
|
||||||
|
|
||||||
|
Others:
|
||||||
|
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->
|
||||||
|
</code></pre>
|
||||||
|
67
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
67
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
name: "\U0001F41EBug report"
|
|
||||||
about: Report a bug in the Angular Framework
|
|
||||||
---
|
|
||||||
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
|
||||||
|
|
||||||
Oh hi there! 😄
|
|
||||||
|
|
||||||
To expedite issue processing please search open and closed issues before submitting a new one.
|
|
||||||
Existing issues often contain information about workarounds, resolution, or progress updates.
|
|
||||||
|
|
||||||
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
|
||||||
|
|
||||||
|
|
||||||
# 🐞 bug report
|
|
||||||
|
|
||||||
### Affected Package
|
|
||||||
<!-- Can you pin-point one or more @angular/* packages as the source of the bug? -->
|
|
||||||
<!-- ✍️edit: --> The issue is caused by package @angular/....
|
|
||||||
|
|
||||||
|
|
||||||
### Is this a regression?
|
|
||||||
|
|
||||||
<!-- Did this behavior use to work in the previous version? -->
|
|
||||||
<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
|
|
||||||
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
<!-- ✍️--> A clear and concise description of the problem...
|
|
||||||
|
|
||||||
|
|
||||||
## 🔬 Minimal Reproduction
|
|
||||||
<!--
|
|
||||||
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-issue-repro2
|
|
||||||
-->
|
|
||||||
<!-- ✍️--> https://stackblitz.com/...
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue. Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
|
|
||||||
|
|
||||||
Issues that don't have enough info and can't be reproduced will be closed.
|
|
||||||
|
|
||||||
You can read more about issue submission guidelines here: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue
|
|
||||||
-->
|
|
||||||
|
|
||||||
## 🔥 Exception or Error
|
|
||||||
<pre><code>
|
|
||||||
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
|
||||||
<!-- ✍️-->
|
|
||||||
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
## 🌍 Your Environment
|
|
||||||
|
|
||||||
**Angular Version:**
|
|
||||||
<pre><code>
|
|
||||||
<!-- run `ng version` and paste output below -->
|
|
||||||
<!-- ✍️-->
|
|
||||||
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
**Anything else relevant?**
|
|
||||||
<!-- ✍️Is this a browser specific issue? If so, please specify the browser and version. -->
|
|
||||||
|
|
||||||
<!-- ✍️Do any of these matter: operating system, IDE, package manager, HTTP server, ...? If so, please mention it below. -->
|
|
32
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
32
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
name: "\U0001F680Feature request"
|
|
||||||
about: Suggest a feature for Angular Framework
|
|
||||||
|
|
||||||
---
|
|
||||||
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
|
||||||
|
|
||||||
Oh hi there! 😄
|
|
||||||
|
|
||||||
To expedite issue processing please search open and closed issues before submitting a new one.
|
|
||||||
Existing issues often contain information about workarounds, resolution, or progress updates.
|
|
||||||
|
|
||||||
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
|
||||||
|
|
||||||
|
|
||||||
# 🚀 feature request
|
|
||||||
|
|
||||||
### Relevant Package
|
|
||||||
<!-- Can you pin-point one or more @angular/* packages the are relevant for this feature request? -->
|
|
||||||
<!-- ✍️edit: --> This feature request is for @angular/....
|
|
||||||
|
|
||||||
|
|
||||||
### Description
|
|
||||||
<!-- ✍️--> A clear and concise description of the problem or missing capability...
|
|
||||||
|
|
||||||
|
|
||||||
### Describe the solution you'd like
|
|
||||||
<!-- ✍️--> If you have a solution in mind, please describe it.
|
|
||||||
|
|
||||||
|
|
||||||
### Describe alternatives you've considered
|
|
||||||
<!-- ✍️--> Have you considered any alternative solutions or workarounds?
|
|
55
.github/ISSUE_TEMPLATE/3-docs-bug.md
vendored
55
.github/ISSUE_TEMPLATE/3-docs-bug.md
vendored
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
name: "📚 Docs or angular.io issue report"
|
|
||||||
about: Report an issue in Angular's documentation or angular.io application
|
|
||||||
|
|
||||||
---
|
|
||||||
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
|
||||||
|
|
||||||
Oh hi there! 😄
|
|
||||||
|
|
||||||
To expedite issue processing please search open and closed issues before submitting a new one.
|
|
||||||
Existing issues often contain information about workarounds, resolution, or progress updates.
|
|
||||||
|
|
||||||
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
|
||||||
|
|
||||||
# 📚 Docs or angular.io bug report
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
<!-- ✍️edit:--> A clear and concise description of the problem...
|
|
||||||
|
|
||||||
|
|
||||||
## 🔬 Minimal Reproduction
|
|
||||||
|
|
||||||
### What's the affected URL?**
|
|
||||||
<!-- ✍️edit:--> https://angular.io/...
|
|
||||||
|
|
||||||
### Reproduction Steps**
|
|
||||||
<!-- If applicable please list the steps to take to reproduce the issue -->
|
|
||||||
<!-- ✍️edit:-->
|
|
||||||
|
|
||||||
### Expected vs Actual Behavior**
|
|
||||||
<!-- If applicable please describe the difference between the expected and actual behavior after following the repro steps. -->
|
|
||||||
<!-- ✍️edit:-->
|
|
||||||
|
|
||||||
|
|
||||||
## 📷Screenshot
|
|
||||||
<!-- Often a screenshot can help to capture the issue better than a long description. -->
|
|
||||||
<!-- ✍️upload a screenshot:-->
|
|
||||||
|
|
||||||
|
|
||||||
## 🔥 Exception or Error
|
|
||||||
<pre><code>
|
|
||||||
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
|
||||||
<!-- ✍️-->
|
|
||||||
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
## 🌍 Your Environment
|
|
||||||
|
|
||||||
### Browser info
|
|
||||||
<!-- ✍️Is this a browser specific issue? If so, please specify the device, browser, and version. -->
|
|
||||||
|
|
||||||
### Anything else relevant?
|
|
||||||
<!-- ✍️Please provide additional info if necessary. -->
|
|
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: ⚠️ Security issue disclosure
|
|
||||||
about: Report a security issue in Angular Framework, Material, or CLI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
||||||
|
|
||||||
Please read https://angular.io/guide/security#report-issues on how to disclose security related issues.
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
16
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
16
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: "❓Support request"
|
|
||||||
about: Questions and requests for support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
||||||
|
|
||||||
Please do not file questions or support requests on the GitHub issues tracker.
|
|
||||||
|
|
||||||
You can get your questions answered using other communication channels. Please see:
|
|
||||||
https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
|
||||||
|
|
||||||
Thank you!
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
13
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
13
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
name: "\U0001F6E0️Angular CLI"
|
|
||||||
about: Issues and feature requests for Angular CLI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
||||||
|
|
||||||
Please file any Angular CLI issues at: https://github.com/angular/angular-cli/issues/new
|
|
||||||
|
|
||||||
For the time being, we keep Angular CLI issues in a separate repository.
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
13
.github/ISSUE_TEMPLATE/7-angular-material.md
vendored
13
.github/ISSUE_TEMPLATE/7-angular-material.md
vendored
@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
name: "\U0001F48EAngular Material"
|
|
||||||
about: Issues and feature requests for Angular Material
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
||||||
|
|
||||||
Please file any Angular Material issues at: https://github.com/angular/material2/issues/new
|
|
||||||
|
|
||||||
For the time being, we keep Angular Material issues in a separate repository.
|
|
||||||
|
|
||||||
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
|
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -10,17 +10,17 @@ Please check if your PR fulfills the following requirements:
|
|||||||
What kind of change does this PR introduce?
|
What kind of change does this PR introduce?
|
||||||
|
|
||||||
<!-- Please check the one that applies to this PR using "x". -->
|
<!-- Please check the one that applies to this PR using "x". -->
|
||||||
|
```
|
||||||
- [ ] Bugfix
|
[ ] Bugfix
|
||||||
- [ ] Feature
|
[ ] Feature
|
||||||
- [ ] Code style update (formatting, local variables)
|
[ ] Code style update (formatting, local variables)
|
||||||
- [ ] Refactoring (no functional changes, no api changes)
|
[ ] Refactoring (no functional changes, no api changes)
|
||||||
- [ ] Build related changes
|
[ ] Build related changes
|
||||||
- [ ] CI related changes
|
[ ] CI related changes
|
||||||
- [ ] Documentation content changes
|
[ ] Documentation content changes
|
||||||
- [ ] angular.io application / infrastructure changes
|
[ ] angular.io application / infrastructure changes
|
||||||
- [ ] Other... Please describe:
|
[ ] Other... Please describe:
|
||||||
|
```
|
||||||
|
|
||||||
## What is the current behavior?
|
## What is the current behavior?
|
||||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||||
@ -32,10 +32,10 @@ Issue Number: N/A
|
|||||||
|
|
||||||
|
|
||||||
## Does this PR introduce a breaking change?
|
## Does this PR introduce a breaking change?
|
||||||
|
```
|
||||||
- [ ] Yes
|
[ ] Yes
|
||||||
- [ ] No
|
[ ] No
|
||||||
|
```
|
||||||
|
|
||||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||||
|
|
||||||
|
45
.github/angular-robot.yml
vendored
45
.github/angular-robot.yml
vendored
@ -3,8 +3,11 @@
|
|||||||
#options for the size plugin
|
#options for the size plugin
|
||||||
size:
|
size:
|
||||||
disabled: false
|
disabled: false
|
||||||
maxSizeIncrease: 2000
|
maxSizeIncrease: 1000
|
||||||
circleCiStatusName: "ci/circleci: test_ivy_aot"
|
circleCiStatusName: "ci/circleci: build-packages-dist"
|
||||||
|
status:
|
||||||
|
disabled: false
|
||||||
|
context: "ci/angular: size"
|
||||||
|
|
||||||
# options for the merge plugin
|
# options for the merge plugin
|
||||||
merge:
|
merge:
|
||||||
@ -39,28 +42,14 @@ merge:
|
|||||||
- "packages/**"
|
- "packages/**"
|
||||||
# list of patterns to ignore for the files changed by the PR
|
# list of patterns to ignore for the files changed by the PR
|
||||||
exclude:
|
exclude:
|
||||||
- "packages/*"
|
|
||||||
- "packages/bazel/*"
|
|
||||||
- "packages/bazel/src/builders/**"
|
|
||||||
- "packages/bazel/src/ng_package/**"
|
|
||||||
- "packages/bazel/src/protractor/**"
|
|
||||||
- "packages/bazel/src/schematics/**"
|
|
||||||
- "packages/compiler-cli/src/ngcc/**"
|
|
||||||
- "packages/docs/**"
|
|
||||||
- "packages/elements/schematics/**"
|
|
||||||
- "packages/examples/**"
|
|
||||||
- "packages/language-service/**"
|
- "packages/language-service/**"
|
||||||
- "packages/private/**"
|
|
||||||
- "packages/service-worker/**"
|
|
||||||
- "**/.gitignore"
|
- "**/.gitignore"
|
||||||
- "**/.gitkeep"
|
- "**/.gitkeep"
|
||||||
- "**/yarn.lock"
|
|
||||||
- "**/package.json"
|
- "**/package.json"
|
||||||
- "**/tsconfig-build.json"
|
- "**/tsconfig-build.json"
|
||||||
- "**/tsconfig.json"
|
- "**/tsconfig.json"
|
||||||
- "**/rollup.config.js"
|
- "**/rollup.config.js"
|
||||||
- "**/BUILD.bazel"
|
- "**/BUILD.bazel"
|
||||||
- "**/*.md"
|
|
||||||
- "packages/**/integrationtest/**"
|
- "packages/**/integrationtest/**"
|
||||||
- "packages/**/test/**"
|
- "packages/**/test/**"
|
||||||
|
|
||||||
@ -71,10 +60,6 @@ merge:
|
|||||||
# label to monitor
|
# label to monitor
|
||||||
mergeLabel: "PR action: merge"
|
mergeLabel: "PR action: merge"
|
||||||
|
|
||||||
# adding any of these labels will also add the merge label
|
|
||||||
mergeLinkedLabels:
|
|
||||||
- "PR action: merge-assistance"
|
|
||||||
|
|
||||||
# list of checks that will determine if the merge label can be added
|
# list of checks that will determine if the merge label can be added
|
||||||
checks:
|
checks:
|
||||||
|
|
||||||
@ -142,23 +127,3 @@ triage:
|
|||||||
-
|
-
|
||||||
- "type: RFC / Discussion / question"
|
- "type: RFC / Discussion / question"
|
||||||
- "comp: *"
|
- "comp: *"
|
||||||
|
|
||||||
# options for the triage PR plugin
|
|
||||||
triagePR:
|
|
||||||
# set to true to disable
|
|
||||||
disabled: false
|
|
||||||
# number of the milestone to apply when the PR has not been triaged yet
|
|
||||||
needsTriageMilestone: 83,
|
|
||||||
# number of the milestone to apply when the PR is triaged
|
|
||||||
defaultMilestone: 82,
|
|
||||||
# arrays of labels that determine if a PR has been triaged by the caretaker
|
|
||||||
l1TriageLabels:
|
|
||||||
-
|
|
||||||
- "comp: *"
|
|
||||||
# arrays of labels that determine if a PR has been fully triaged
|
|
||||||
l2TriageLabels:
|
|
||||||
-
|
|
||||||
- "type: *"
|
|
||||||
- "effort*"
|
|
||||||
- "risk*"
|
|
||||||
- "comp: *"
|
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,8 +1,7 @@
|
|||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
/bazel-*
|
bazel-*
|
||||||
/integration/bazel/bazel-*
|
|
||||||
e2e_test.*
|
e2e_test.*
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
@ -15,6 +14,7 @@ pubspec.lock
|
|||||||
.settings/
|
.settings/
|
||||||
*.swo
|
*.swo
|
||||||
modules/.settings
|
modules/.settings
|
||||||
|
.bazelrc
|
||||||
.vscode
|
.vscode
|
||||||
modules/.vscode
|
modules/.vscode
|
||||||
|
|
||||||
@ -30,7 +30,3 @@ yarn-error.log
|
|||||||
|
|
||||||
# rollup-test output
|
# rollup-test output
|
||||||
/modules/rollup-test/dist/
|
/modules/rollup-test/dist/
|
||||||
|
|
||||||
# User specific bazel settings
|
|
||||||
.bazelrc.user
|
|
||||||
|
|
||||||
|
@ -87,10 +87,10 @@ groups:
|
|||||||
files:
|
files:
|
||||||
include:
|
include:
|
||||||
- "WORKSPACE"
|
- "WORKSPACE"
|
||||||
- ".bazel*"
|
|
||||||
- "*.bazel"
|
- "*.bazel"
|
||||||
- "*.bzl"
|
- "*.bzl"
|
||||||
- "packages/bazel/*"
|
- "packages/bazel/*"
|
||||||
|
- "tools/bazel.rc"
|
||||||
- "/docs/BAZEL.md"
|
- "/docs/BAZEL.md"
|
||||||
users:
|
users:
|
||||||
- alexeagle #primary
|
- alexeagle #primary
|
||||||
@ -98,7 +98,6 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery
|
- mhevery
|
||||||
- vikerman #fallback
|
- vikerman #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
build-and-ci:
|
build-and-ci:
|
||||||
conditions:
|
conditions:
|
||||||
@ -109,9 +108,9 @@ groups:
|
|||||||
- "*.lock"
|
- "*.lock"
|
||||||
- "tools/*"
|
- "tools/*"
|
||||||
exclude:
|
exclude:
|
||||||
- "aio/*"
|
- "tools/bazel.rc"
|
||||||
- "packages/core/test/bundling/*"
|
|
||||||
- "tools/public_api_guard/*"
|
- "tools/public_api_guard/*"
|
||||||
|
- "aio/*"
|
||||||
users:
|
users:
|
||||||
- IgorMinar #primary
|
- IgorMinar #primary
|
||||||
- alexeagle
|
- alexeagle
|
||||||
@ -212,7 +211,6 @@ groups:
|
|||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
compiler/i18n:
|
compiler/i18n:
|
||||||
conditions:
|
conditions:
|
||||||
@ -225,7 +223,6 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
compiler:
|
compiler:
|
||||||
conditions:
|
conditions:
|
||||||
@ -237,7 +234,6 @@ groups:
|
|||||||
- mhevery
|
- mhevery
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
compiler-cli/ngtools:
|
compiler-cli/ngtools:
|
||||||
conditions:
|
conditions:
|
||||||
@ -247,13 +243,13 @@ groups:
|
|||||||
- hansl
|
- hansl
|
||||||
- filipesilva #fallback
|
- filipesilva #fallback
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
compiler-cli:
|
compiler-cli:
|
||||||
conditions:
|
conditions:
|
||||||
files:
|
files:
|
||||||
include:
|
include:
|
||||||
- "packages/compiler-cli/*"
|
- "packages/compiler-cli/*"
|
||||||
|
- "packages/bazel/*"
|
||||||
exclude:
|
exclude:
|
||||||
- "packages/compiler-cli/src/ngtools*"
|
- "packages/compiler-cli/src/ngtools*"
|
||||||
users:
|
users:
|
||||||
@ -261,7 +257,6 @@ groups:
|
|||||||
- alxhub
|
- alxhub
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
common:
|
common:
|
||||||
conditions:
|
conditions:
|
||||||
@ -274,7 +269,6 @@ groups:
|
|||||||
- pkozlowski-opensource #primary
|
- pkozlowski-opensource #primary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
forms:
|
forms:
|
||||||
conditions:
|
conditions:
|
||||||
@ -283,9 +277,6 @@ groups:
|
|||||||
- "aio/content/guide/forms.md"
|
- "aio/content/guide/forms.md"
|
||||||
- "aio/content/examples/forms/*"
|
- "aio/content/examples/forms/*"
|
||||||
- "aio/content/images/guide/forms/*"
|
- "aio/content/images/guide/forms/*"
|
||||||
- "aio/content/guide/forms-overview.md"
|
|
||||||
- "aio/content/examples/forms-overview/*"
|
|
||||||
- "aio/content/images/guide/forms-overview/*"
|
|
||||||
- "aio/content/guide/form-validation.md"
|
- "aio/content/guide/form-validation.md"
|
||||||
- "aio/content/examples/form-validation/*"
|
- "aio/content/examples/form-validation/*"
|
||||||
- "aio/content/images/guide/form-validation/*"
|
- "aio/content/images/guide/form-validation/*"
|
||||||
@ -340,7 +331,6 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
testing:
|
testing:
|
||||||
conditions:
|
conditions:
|
||||||
@ -374,7 +364,6 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
platform-browser:
|
platform-browser:
|
||||||
conditions:
|
conditions:
|
||||||
@ -384,7 +373,6 @@ groups:
|
|||||||
- mhevery #primary
|
- mhevery #primary
|
||||||
# needs secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
platform-server:
|
platform-server:
|
||||||
conditions:
|
conditions:
|
||||||
@ -407,7 +395,6 @@ groups:
|
|||||||
- mhevery #primary
|
- mhevery #primary
|
||||||
# needs secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- kara
|
|
||||||
|
|
||||||
service-worker:
|
service-worker:
|
||||||
conditions:
|
conditions:
|
||||||
@ -441,7 +428,6 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
- kara
|
|
||||||
|
|
||||||
benchpress:
|
benchpress:
|
||||||
conditions:
|
conditions:
|
||||||
|
22
.travis.yml
22
.travis.yml
@ -30,6 +30,14 @@ env:
|
|||||||
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
|
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
|
||||||
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
||||||
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
|
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
|
||||||
|
# FIREBASE_TOKEN
|
||||||
|
# This is needed for publishing builds to the "aio-staging" and "angular-io" firebase projects.
|
||||||
|
# This token was generated using the aio-deploy@angular.io account using `firebase login:ci` and password from valentine
|
||||||
|
- secure: "L5CyQmpwWtoR4Qi4xlWQh/cL1M6ZeJL4W4QAr4HdKFMgYt9h+Whqkymyh2NxwmCbPvWa7yUd+OiLQUDCY7L2VIg16hTwoe2CgYDyQA0BEwLzxtRrJXl93TfwMlrUx5JSIzAccD6D4sjtz8kSFMomK2Nls33xOXOukwyhVMjd0Cg="
|
||||||
|
# ANGULAR_PAYLOAD_FIREBASE_TOKEN
|
||||||
|
# This is for payload size data to "angular-payload-size" firebase project
|
||||||
|
# This token was generated using the payload@angular.io account using `firebase login:ci` and password from valentine
|
||||||
|
- secure: "SxotP/ymNy6uWAVbfwM9BlwETPEBpkRvU/F7fCtQDDic99WfQHzzUSQqHTk8eKk3GrGAOSL09vT0WfStQYEIGEoS5UHWNgOnelxhw+d5EnaoB8vQ0dKQBTK092hQg4feFprr+B/tCasyMV6mVwpUzZMbIJNn/Rx7H5g1bp+Gkfg="
|
||||||
matrix:
|
matrix:
|
||||||
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
||||||
- CI_MODE=e2e
|
- CI_MODE=e2e
|
||||||
@ -37,12 +45,12 @@ env:
|
|||||||
- CI_MODE=saucelabs_required
|
- CI_MODE=saucelabs_required
|
||||||
# deactivated, see #19768
|
# deactivated, see #19768
|
||||||
# - CI_MODE=browserstack_required
|
# - CI_MODE=browserstack_required
|
||||||
|
- CI_MODE=saucelabs_optional
|
||||||
# We disable these optional jobs because those acquire tunnel and browser instances which
|
- CI_MODE=browserstack_optional
|
||||||
# could lead to rate limit excess while those are failing most of the time and nobody pays
|
- CI_MODE=aio_tools_test
|
||||||
# attention anyway.
|
- CI_MODE=aio
|
||||||
# - CI_MODE=saucelabs_optional
|
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||||
# - CI_MODE=browserstack_optional
|
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
@ -60,6 +68,8 @@ install:
|
|||||||
script:
|
script:
|
||||||
- ./scripts/ci/build.sh
|
- ./scripts/ci/build.sh
|
||||||
- ./scripts/ci/test.sh
|
- ./scripts/ci/test.sh
|
||||||
|
# deploy is part of 'script' and not 'after_success' so that we fail the build if the deployment fails
|
||||||
|
- ./scripts/ci/deploy.sh
|
||||||
- ./scripts/ci/angular.sh
|
- ./scripts/ci/angular.sh
|
||||||
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
||||||
- ./scripts/ci/cleanup.sh
|
- ./scripts/ci/cleanup.sh
|
||||||
|
55
BUILD.bazel
55
BUILD.bazel
@ -8,14 +8,26 @@ exports_files([
|
|||||||
"protractor-perf.conf.js",
|
"protractor-perf.conf.js",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Developers should always run `bazel run :install`
|
||||||
|
# This ensures that package.json in subdirectories get installed as well.
|
||||||
|
alias(
|
||||||
|
name = "install",
|
||||||
|
actual = "@nodejs//:yarn",
|
||||||
|
)
|
||||||
|
|
||||||
|
alias(
|
||||||
|
name = "node_modules",
|
||||||
|
actual = "@angular_deps//:node_modules",
|
||||||
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "web_test_bootstrap_scripts",
|
name = "web_test_bootstrap_scripts",
|
||||||
# do not sort
|
# do not sort
|
||||||
srcs = [
|
srcs = [
|
||||||
"@ngdeps//node_modules/reflect-metadata:Reflect.js",
|
"@angular_deps//:node_modules/reflect-metadata/Reflect.js",
|
||||||
"@ngdeps//node_modules/zone.js:dist/zone.js",
|
"@angular_deps//:node_modules/zone.js/dist/zone.js",
|
||||||
"@ngdeps//node_modules/zone.js:dist/zone-testing.js",
|
"@angular_deps//:node_modules/zone.js/dist/zone-testing.js",
|
||||||
"@ngdeps//node_modules/zone.js:dist/task-tracking.js",
|
"@angular_deps//:node_modules/zone.js/dist/task-tracking.js",
|
||||||
"//:test-events.js",
|
"//:test-events.js",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -23,34 +35,11 @@ filegroup(
|
|||||||
filegroup(
|
filegroup(
|
||||||
name = "angularjs_scripts",
|
name = "angularjs_scripts",
|
||||||
srcs = [
|
srcs = [
|
||||||
# We also declare the unminfied AngularJS files since these can be used for
|
"@angular_deps//:node_modules/angular-1.5/angular.js",
|
||||||
# local debugging (e.g. see: packages/upgrade/test/common/test_helpers.ts)
|
"@angular_deps//:node_modules/angular-1.6/angular.js",
|
||||||
"@ngdeps//node_modules/angular:angular.js",
|
"@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
||||||
"@ngdeps//node_modules/angular:angular.min.js",
|
"@angular_deps//:node_modules/angular-mocks-1.6/angular-mocks.js",
|
||||||
"@ngdeps//node_modules/angular-1.5:angular.js",
|
"@angular_deps//:node_modules/angular-mocks/angular-mocks.js",
|
||||||
"@ngdeps//node_modules/angular-1.5:angular.min.js",
|
"@angular_deps//:node_modules/angular/angular.js",
|
||||||
"@ngdeps//node_modules/angular-1.6:angular.js",
|
|
||||||
"@ngdeps//node_modules/angular-1.6:angular.min.js",
|
|
||||||
"@ngdeps//node_modules/angular-mocks:angular-mocks.js",
|
|
||||||
"@ngdeps//node_modules/angular-mocks-1.5:angular-mocks.js",
|
|
||||||
"@ngdeps//node_modules/angular-mocks-1.6:angular-mocks.js",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
|
||||||
|
|
||||||
# A nodejs_binary for @angular/bazel/ngc-wrapped to use by default in
|
|
||||||
# ng_module that depends on @npm//@angular/bazel instead of the
|
|
||||||
# output of the //packages/bazel/src/ngc-wrapped ts_library rule. This
|
|
||||||
# default is for downstream users that depend on the @angular/bazel npm
|
|
||||||
# package. The generated @npm//@angular/bazel/ngc-wrapped target
|
|
||||||
# does not work because it does not have the node `--expose-gc` flag
|
|
||||||
# set which is required to support the call to `global.gc()`.
|
|
||||||
nodejs_binary(
|
|
||||||
name = "@angular/bazel/ngc-wrapped",
|
|
||||||
configuration_env_vars = ["compile"],
|
|
||||||
data = ["@npm//@angular/bazel"],
|
|
||||||
entry_point = "@angular/bazel/src/ngc-wrapped/index.js",
|
|
||||||
install_source_map_support = False,
|
|
||||||
templated_args = ["--node_options=--expose-gc"],
|
|
||||||
)
|
|
||||||
|
306
CHANGELOG.md
306
CHANGELOG.md
@ -1,302 +1,21 @@
|
|||||||
<a name="7.1.4"></a>
|
<a name="6.1.8"></a>
|
||||||
## [7.1.4](https://github.com/angular/angular/compare/7.1.3...7.1.4) (2018-12-18)
|
## [6.1.8](https://github.com/angular/angular/compare/6.1.7...6.1.8) (2018-09-19)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* **animations:** do not truncate decimals for delay ([#24455](https://github.com/angular/angular/issues/24455)) ([cd1e206](https://github.com/angular/angular/commit/cd1e206))
|
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([2d0e642](https://github.com/angular/angular/commit/2d0e642))
|
||||||
* **animations:** mark actual descendant node as disabled ([#26180](https://github.com/angular/angular/issues/26180)) ([453589f](https://github.com/angular/angular/commit/453589f))
|
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([91dd160](https://github.com/angular/angular/commit/91dd160))
|
||||||
* **bazel:** devserver entry_module should have underscore name ([#27719](https://github.com/angular/angular/issues/27719)) ([b108e9a](https://github.com/angular/angular/commit/b108e9a))
|
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([48d7f4e](https://github.com/angular/angular/commit/48d7f4e))
|
||||||
* **bazel:** emit full node stack traces when Angular compilation crashes ([#27678](https://github.com/angular/angular/issues/27678)) ([0d8528b](https://github.com/angular/angular/commit/0d8528b))
|
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([678b420](https://github.com/angular/angular/commit/678b420))
|
||||||
* **bazel:** fix major/minor semver check between [@angular](https://github.com/angular)/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([3ed1e84](https://github.com/angular/angular/commit/3ed1e84))
|
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([a31cfc5](https://github.com/angular/angular/commit/a31cfc5))
|
||||||
* **bazel:** Load http_archive and rules_nodejs dependencies ([#27609](https://github.com/angular/angular/issues/27609)) ([89ace1a](https://github.com/angular/angular/commit/89ace1a))
|
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([e117b1f](https://github.com/angular/angular/commit/e117b1f))
|
||||||
* **bazel:** ng_package writes unrelevant definitions to bazel out ([#27519](https://github.com/angular/angular/issues/27519)) ([ef056c5](https://github.com/angular/angular/commit/ef056c5)), closes [/github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts#L105-L124](https://github.com//github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts/issues/L105-L124)
|
|
||||||
* **bazel:** Read latest versions from latest-versions.ts & use semver check ([#27591](https://github.com/angular/angular/issues/27591)) ([93078e3](https://github.com/angular/angular/commit/93078e3))
|
|
||||||
* **bazel:** Set module_name and enable ng test ([#27715](https://github.com/angular/angular/issues/27715)) ([183f278](https://github.com/angular/angular/commit/183f278))
|
|
||||||
* **common:** KeyValuePipe should return empty array for empty objects ([#27258](https://github.com/angular/angular/issues/27258)) ([fa3af8b](https://github.com/angular/angular/commit/fa3af8b))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.3"></a>
|
|
||||||
## [7.1.3](https://github.com/angular/angular/compare/7.1.2...7.1.3) (2018-12-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** tsickle dependency not working with typescript 3.1.x ([#27402](https://github.com/angular/angular/issues/27402)) ([a9f39a4](https://github.com/angular/angular/commit/a9f39a4))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.2"></a>
|
|
||||||
## [7.1.2](https://github.com/angular/angular/compare/7.1.1...7.1.2) (2018-12-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** do not throw error when writing tsickle externs ([#27200](https://github.com/angular/angular/issues/27200)) ([079c4b3](https://github.com/angular/angular/commit/079c4b3))
|
|
||||||
* **bazel:** do not throw if ts compile action does not create esm5 outputs ([#27401](https://github.com/angular/angular/issues/27401)) ([9b4d959](https://github.com/angular/angular/commit/9b4d959))
|
|
||||||
* **bazel:** ng_package cannot be run multiple times without clean ([#27200](https://github.com/angular/angular/issues/27200)) ([1ca2923](https://github.com/angular/angular/commit/1ca2923))
|
|
||||||
* **bazel:** ng_package not generating UMD bundles on windows ([#27200](https://github.com/angular/angular/issues/27200)) ([e476c38](https://github.com/angular/angular/commit/e476c38))
|
|
||||||
* **bazel:** ng_package should correctly map to source maps in secondary entry-points ([#27313](https://github.com/angular/angular/issues/27313)) ([fc2c23e](https://github.com/angular/angular/commit/fc2c23e)), closes [#25510](https://github.com/angular/angular/issues/25510)
|
|
||||||
* **compiler-cli:** flatModuleIndex files not generated on windows with multiple input files ([#27200](https://github.com/angular/angular/issues/27200)) ([8087b6b](https://github.com/angular/angular/commit/8087b6b))
|
|
||||||
* **compiler-cli:** ngtsc shim files not being generated on case-insensitive platforms ([#27466](https://github.com/angular/angular/issues/27466)) ([84f2928](https://github.com/angular/angular/commit/84f2928)), closes [/github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts#L681-L682](https://github.com//github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts/issues/L681-L682)
|
|
||||||
* **platform-server:** add [@angular](https://github.com/angular)/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([236ac06](https://github.com/angular/angular/commit/236ac06)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.1"></a>
|
|
||||||
## [7.1.1](https://github.com/angular/angular/compare/7.1.0...7.1.1) (2018-11-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **core:** export a value for InjectFlags ([#27279](https://github.com/angular/angular/issues/27279)) ([bdf5f3e](https://github.com/angular/angular/commit/bdf5f3e)), closes [#27251](https://github.com/angular/angular/issues/27251)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.0"></a>
|
|
||||||
# [7.1.0](https://github.com/angular/angular/compare/7.1.0-rc.0...7.1.0) (2018-11-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
|
||||||
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
|
||||||
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([2326b9c](https://github.com/angular/angular/commit/2326b9c))
|
|
||||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
|
||||||
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
|
||||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
|
||||||
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
|
||||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
|
||||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
|
||||||
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
|
|
||||||
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
|
|
||||||
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([496372d](https://github.com/angular/angular/commit/496372d)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
|
||||||
* **service-worker:** add typing to public api guard and fix lint errors ([#25860](https://github.com/angular/angular/issues/25860)) ([1061875](https://github.com/angular/angular/commit/1061875))
|
|
||||||
* **upgrade:** improve downgrading-related error messages ([#26217](https://github.com/angular/angular/issues/26217)) ([7dbc103](https://github.com/angular/angular/commit/7dbc103))
|
|
||||||
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([64647af](https://github.com/angular/angular/commit/64647af)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
|
||||||
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
|
|
||||||
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
|
|
||||||
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **bazel:** Bazel workspace schematics ([#26971](https://github.com/angular/angular/issues/26971)) ([b07bd30](https://github.com/angular/angular/commit/b07bd30))
|
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([97ae7ae](https://github.com/angular/angular/commit/97ae7ae))
|
||||||
* **router:** add prioritizedGuardValue operator optimization and allowing UrlTree return from guard ([#26478](https://github.com/angular/angular/issues/26478)) ([fdfedce](https://github.com/angular/angular/commit/fdfedce))
|
* **ivy:** enable .ngfactory.js generation in g3 only ([#25392](https://github.com/angular/angular/issues/25392)) ([1c44b71](https://github.com/angular/angular/commit/1c44b71))
|
||||||
* **compiler:** ability to mark an InvokeFunctionExpr as pure ([#26860](https://github.com/angular/angular/issues/26860)) ([4dfa71f](https://github.com/angular/angular/commit/4dfa71f))
|
|
||||||
* **forms:** add updateOn option to FormBuilder ([#24599](https://github.com/angular/angular/issues/24599)) ([e9e804f](https://github.com/angular/angular/commit/e9e804f))
|
|
||||||
* **router:** allow guards to return UrlTree as well as boolean ([#26521](https://github.com/angular/angular/issues/26521)) ([081f95c](https://github.com/angular/angular/commit/081f95c))
|
|
||||||
* **router:** allow redirect from guards by returning UrlTree ([#26521](https://github.com/angular/angular/issues/26521)) ([152ca66](https://github.com/angular/angular/commit/152ca66))
|
|
||||||
* **router:** guard returning UrlTree cancels current navigation and redirects ([#26521](https://github.com/angular/angular/issues/26521)) ([4e9f2e5](https://github.com/angular/angular/commit/4e9f2e5)), closes [#24618](https://github.com/angular/angular/issues/24618)
|
|
||||||
* **service-worker:** add typing for messagesClicked in SwPush service ([#25860](https://github.com/angular/angular/issues/25860)) ([c78c221](https://github.com/angular/angular/commit/c78c221))
|
|
||||||
* **service-worker:** close notifications and focus window on click ([#25860](https://github.com/angular/angular/issues/25860)) ([f5d5a3d](https://github.com/angular/angular/commit/f5d5a3d))
|
|
||||||
* **service-worker:** handle 'notificationclick' events ([#25860](https://github.com/angular/angular/issues/25860)) ([cf6ea28](https://github.com/angular/angular/commit/cf6ea28)), closes [#20956](https://github.com/angular/angular/issues/20956) [#22311](https://github.com/angular/angular/issues/22311)
|
|
||||||
* **upgrade:** support downgrading multiple modules ([#26217](https://github.com/angular/angular/issues/26217)) ([93837e9](https://github.com/angular/angular/commit/93837e9)), closes [#26062](https://github.com/angular/angular/issues/26062)
|
|
||||||
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.0-rc.0"></a>
|
|
||||||
# [7.1.0-rc.0](https://github.com/angular/angular/compare/7.1.0-beta.2...7.1.0-rc.0) (2018-11-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
|
|
||||||
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
|
|
||||||
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.4"></a>
|
|
||||||
## [7.0.4](https://github.com/angular/angular/compare/7.0.3...7.0.4) (2018-11-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([4348c47](https://github.com/angular/angular/commit/4348c47))
|
|
||||||
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([188e9ce](https://github.com/angular/angular/commit/188e9ce))
|
|
||||||
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([d304427](https://github.com/angular/angular/commit/d304427)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.0-beta.2"></a>
|
|
||||||
# [7.1.0-beta.2](https://github.com/angular/angular/compare/7.1.0-beta.1...7.1.0-beta.2) (2018-11-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
|
|
||||||
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
|
|
||||||
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([496372d](https://github.com/angular/angular/commit/496372d)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
|
||||||
* **service-worker:** add typing to public api guard and fix lint errors ([#25860](https://github.com/angular/angular/issues/25860)) ([1061875](https://github.com/angular/angular/commit/1061875))
|
|
||||||
* **upgrade:** improve downgrading-related error messages ([#26217](https://github.com/angular/angular/issues/26217)) ([7dbc103](https://github.com/angular/angular/commit/7dbc103))
|
|
||||||
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([64647af](https://github.com/angular/angular/commit/64647af)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **compiler:** ability to mark an InvokeFunctionExpr as pure ([#26860](https://github.com/angular/angular/issues/26860)) ([4dfa71f](https://github.com/angular/angular/commit/4dfa71f))
|
|
||||||
* **forms:** add updateOn option to FormBuilder ([#24599](https://github.com/angular/angular/issues/24599)) ([e9e804f](https://github.com/angular/angular/commit/e9e804f))
|
|
||||||
* **router:** allow guards to return UrlTree as well as boolean ([#26521](https://github.com/angular/angular/issues/26521)) ([081f95c](https://github.com/angular/angular/commit/081f95c))
|
|
||||||
* **router:** allow redirect from guards by returning UrlTree ([#26521](https://github.com/angular/angular/issues/26521)) ([152ca66](https://github.com/angular/angular/commit/152ca66))
|
|
||||||
* **router:** guard returning UrlTree cancels current navigation and redirects ([#26521](https://github.com/angular/angular/issues/26521)) ([4e9f2e5](https://github.com/angular/angular/commit/4e9f2e5)), closes [#24618](https://github.com/angular/angular/issues/24618)
|
|
||||||
* **service-worker:** add typing for messagesClicked in SwPush service ([#25860](https://github.com/angular/angular/issues/25860)) ([c78c221](https://github.com/angular/angular/commit/c78c221))
|
|
||||||
* **service-worker:** close notifications and focus window on click ([#25860](https://github.com/angular/angular/issues/25860)) ([f5d5a3d](https://github.com/angular/angular/commit/f5d5a3d))
|
|
||||||
* **service-worker:** handle 'notificationclick' events ([#25860](https://github.com/angular/angular/issues/25860)) ([cf6ea28](https://github.com/angular/angular/commit/cf6ea28)), closes [#20956](https://github.com/angular/angular/issues/20956) [#22311](https://github.com/angular/angular/issues/22311)
|
|
||||||
* **upgrade:** support downgrading multiple modules ([#26217](https://github.com/angular/angular/issues/26217)) ([93837e9](https://github.com/angular/angular/commit/93837e9)), closes [#26062](https://github.com/angular/angular/issues/26062)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.3"></a>
|
|
||||||
## [7.0.3](https://github.com/angular/angular/compare/7.0.2...7.0.3) (2018-11-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([4d532df](https://github.com/angular/angular/commit/4d532df))
|
|
||||||
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([dc05385](https://github.com/angular/angular/commit/dc05385)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
|
||||||
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([315d95c](https://github.com/angular/angular/commit/315d95c)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.0-beta.1"></a>
|
|
||||||
# [7.1.0-beta.1](https://github.com/angular/angular/compare/7.1.0-beta.0...7.1.0-beta.1) (2018-10-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
|
||||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
|
||||||
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
|
||||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
|
||||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.2"></a>
|
|
||||||
## [7.0.2](https://github.com/angular/angular/compare/7.0.1...7.0.2) (2018-10-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([c01f340](https://github.com/angular/angular/commit/c01f340))
|
|
||||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([#26879](https://github.com/angular/angular/issues/26879)) ([257ac83](https://github.com/angular/angular/commit/257ac83))
|
|
||||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([b3c6409](https://github.com/angular/angular/commit/b3c6409))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.1.0-beta.0"></a>
|
|
||||||
# [7.1.0-beta.0](https://github.com/angular/angular/compare/7.0.0-rc.1...7.1.0-beta.0) (2018-10-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
|
||||||
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
|
||||||
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([2326b9c](https://github.com/angular/angular/commit/2326b9c))
|
|
||||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **router:** add prioritizedGuardValue operator optimization and allowing UrlTree return from guard ([#26478](https://github.com/angular/angular/issues/26478)) ([fdfedce](https://github.com/angular/angular/commit/fdfedce))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.1"></a>
|
|
||||||
## [7.0.1](https://github.com/angular/angular/compare/7.0.0...7.0.1) (2018-10-24)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.0"></a>
|
|
||||||
# [7.0.0](https://github.com/angular/angular/compare/7.0.0-rc.1...7.0.0) (2018-10-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Release Highlights & Update instructions
|
|
||||||
|
|
||||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v7 release announcement](https://blog.angular.io/version-7-of-angular-cli-prompts-virtual-scroll-drag-and-drop-and-more-c594e22e7b8c).
|
|
||||||
|
|
||||||
|
|
||||||
### Dependency updates
|
|
||||||
|
|
||||||
* @angular/core now depends on
|
|
||||||
* TypeScript 3.1
|
|
||||||
* RxJS 6.3
|
|
||||||
* @angular/platform-server now depends on Domino 2.1
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
|
|
||||||
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
|
|
||||||
* **compiler-cli:** add support to extend `angularCompilerOptions` ([#22717](https://github.com/angular/angular/issues/22717)) ([d7e5bbf](https://github.com/angular/angular/commit/d7e5bbf)), closes [#22684](https://github.com/angular/angular/issues/22684)
|
|
||||||
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([2a21ca0](https://github.com/angular/angular/commit/2a21ca0))
|
|
||||||
* **elements:** enable Shadow DOM v1 and slots ([#24861](https://github.com/angular/angular/issues/24861)) ([c9844a2](https://github.com/angular/angular/commit/c9844a2))
|
|
||||||
* **platform-server:** update domino to v2.1.0 ([#25564](https://github.com/angular/angular/issues/25564)) ([3fb0da2](https://github.com/angular/angular/commit/3fb0da2))
|
|
||||||
* **router:** warn if navigation triggered outside Angular zone ([#24959](https://github.com/angular/angular/issues/24959)) ([010e35d](https://github.com/angular/angular/commit/010e35d)), closes [#15770](https://github.com/angular/angular/issues/15770) [#15946](https://github.com/angular/angular/issues/15946) [#24728](https://github.com/angular/angular/issues/24728)
|
|
||||||
* **router:** add UrlSegment[] to CanLoad interface ([#13127](https://github.com/angular/angular/issues/13127)) ([07d8d39](https://github.com/angular/angular/commit/07d8d39)), closes [#12411](https://github.com/angular/angular/issues/12411)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
|
|
||||||
* **bazel:** Cache fileNameToModuleName lookups ([#25731](https://github.com/angular/angular/issues/25731)) ([f394ba0](https://github.com/angular/angular/commit/f394ba0))
|
|
||||||
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
|
|
||||||
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
|
|
||||||
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([6ed7993](https://github.com/angular/angular/commit/6ed7993))
|
|
||||||
* **bazel:** only lookup amd module-name tags in .d.ts files ([#25710](https://github.com/angular/angular/issues/25710)) ([42072c4](https://github.com/angular/angular/commit/42072c4))
|
|
||||||
* **bazel:** protractor rule should include *.e2e-spec.js ([#25701](https://github.com/angular/angular/issues/25701)) ([3809e0f](https://github.com/angular/angular/commit/3809e0f))
|
|
||||||
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([ddc1335](https://github.com/angular/angular/commit/ddc1335))
|
|
||||||
* **benchpress:** Use performance.mark() instead of console.time() ([#24114](https://github.com/angular/angular/issues/24114)) ([06d0400](https://github.com/angular/angular/commit/06d0400))
|
|
||||||
* **common:** register locale data for all equivalent closure locales ([#25867](https://github.com/angular/angular/issues/25867)) ([d83f9d4](https://github.com/angular/angular/commit/d83f9d4))
|
|
||||||
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
|
|
||||||
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
|
|
||||||
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([00d3666](https://github.com/angular/angular/commit/00d3666))
|
|
||||||
* **compiler:** add hostVars and support pure functions in host bindings ([#25626](https://github.com/angular/angular/issues/25626)) ([b424b31](https://github.com/angular/angular/commit/b424b31))
|
|
||||||
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
|
||||||
* **compiler:** update compiler to generate new slot allocations ([#25607](https://github.com/angular/angular/issues/25607)) ([27e2039](https://github.com/angular/angular/commit/27e2039))
|
|
||||||
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
|
||||||
* **core:** add missing `peerDependency ` to `[@angular](https://github.com/angular)/compiler` ([#26033](https://github.com/angular/angular/issues/26033)) ([549de1e](https://github.com/angular/angular/commit/549de1e)), closes [/github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e#diff-58563046c4439699f2e6a89187099a54](https://github.com//github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e/issues/diff-58563046c4439699f2e6a89187099a54)
|
|
||||||
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
|
||||||
* **core:** do not clear element content when using shadow dom ([#24861](https://github.com/angular/angular/issues/24861)) ([6e828bb](https://github.com/angular/angular/commit/6e828bb))
|
|
||||||
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([1f59f2f](https://github.com/angular/angular/commit/1f59f2f))
|
|
||||||
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
|
|
||||||
* **elements:** add compiler dependency ([#24861](https://github.com/angular/angular/issues/24861)) ([6143da6](https://github.com/angular/angular/commit/6143da6))
|
|
||||||
* **elements:** add compiler to integration ([#24861](https://github.com/angular/angular/issues/24861)) ([a080ffc](https://github.com/angular/angular/commit/a080ffc))
|
|
||||||
* **elements:** strict null checks ([#24861](https://github.com/angular/angular/issues/24861)) ([a8210d0](https://github.com/angular/angular/commit/a8210d0))
|
|
||||||
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
|
||||||
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([8dc2b11](https://github.com/angular/angular/commit/8dc2b11))
|
|
||||||
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
|
||||||
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([00b5c7b](https://github.com/angular/angular/commit/00b5c7b))
|
|
||||||
* **service-worker:** do not blow up when caches are unwritable ([#26042](https://github.com/angular/angular/issues/26042)) ([2bd767c](https://github.com/angular/angular/commit/2bd767c))
|
|
||||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
|
||||||
* **upgrade:** trigger `$destroy` event on upgraded component element ([#25357](https://github.com/angular/angular/issues/25357)) ([2a672a9](https://github.com/angular/angular/commit/2a672a9)), closes [#25334](https://github.com/angular/angular/issues/25334)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.10"></a>
|
|
||||||
## [6.1.10](https://github.com/angular/angular/compare/6.1.9...6.1.10) (2018-10-10)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **platform-browser:** fix [#22155](https://github.com/angular/angular/issues/22155), destroy hammer manager when `HammerInstance.off()` is run ([#22156](https://github.com/angular/angular/issues/22156)) ([3b4d9dc](https://github.com/angular/angular/commit/3b4d9dc))
|
|
||||||
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([623adbb](https://github.com/angular/angular/commit/623adbb)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.9"></a>
|
|
||||||
## [6.1.9](https://github.com/angular/angular/compare/6.1.8...6.1.9) (2018-09-26)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -326,8 +45,6 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
|||||||
|
|
||||||
Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6 instead. sorry! :-)
|
Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6 instead. sorry! :-)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.4"></a>
|
<a name="6.1.4"></a>
|
||||||
## [6.1.4](https://github.com/angular/angular/compare/6.1.3...6.1.4) (2018-08-22)
|
## [6.1.4](https://github.com/angular/angular/compare/6.1.3...6.1.4) (2018-08-22)
|
||||||
|
|
||||||
@ -362,6 +79,9 @@ Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6
|
|||||||
<a name="6.1.1"></a>
|
<a name="6.1.1"></a>
|
||||||
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
* **compiler-cli:** correct tsickle dependency version to fix typescript 2.9 compatibility ([fec29fa](https://github.com/angular/angular/commit/317c7087c56b72aa74cd6d6a8f719e6e7fec29fa))
|
* **compiler-cli:** correct tsickle dependency version to fix typescript 2.9 compatibility ([fec29fa](https://github.com/angular/angular/commit/317c7087c56b72aa74cd6d6a8f719e6e7fec29fa))
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,15 +51,19 @@ and help you to craft the change so that it is successfully accepted into the pr
|
|||||||
|
|
||||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||||
|
|
||||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction. Having a minimal reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions.
|
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
|
||||||
|
|
||||||
A minimal reproduction allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem.
|
- version of Angular used
|
||||||
|
- 3rd-party libraries and their versions
|
||||||
|
- and most importantly - a use-case that fails
|
||||||
|
|
||||||
We will be insisting on a minimal reproduction scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal reproduction. We understand that sometimes it might be hard to extract essential bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.
|
||||||
|
|
||||||
|
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||||
|
|
||||||
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
||||||
|
|
||||||
You can file new issues by selecting from our [new issue templates](https://github.com/angular/angular/issues/new/choose) and filling out the issue template.
|
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
|
||||||
|
|
||||||
|
|
||||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||||
@ -67,8 +71,6 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
|||||||
|
|
||||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
||||||
that relates to your submission. You don't want to duplicate effort.
|
that relates to your submission. You don't want to duplicate effort.
|
||||||
1. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add.
|
|
||||||
Discussing the design up front helps to ensure that we're ready to accept your work.
|
|
||||||
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||||
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
||||||
1. Fork the angular/angular repo.
|
1. Fork the angular/angular repo.
|
||||||
|
@ -13,10 +13,12 @@ Angular is a development platform for building mobile and desktop web applicatio
|
|||||||
|
|
||||||
[Get started in 5 minutes][quickstart].
|
[Get started in 5 minutes][quickstart].
|
||||||
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
[Learn about the latest improvements][changelog].
|
[Learn about the latest improvements][changelog].
|
||||||
|
|
||||||
|
|
||||||
## Want to help?
|
## Want to help?
|
||||||
|
|
||||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
||||||
|
128
WORKSPACE
128
WORKSPACE
@ -1,52 +1,89 @@
|
|||||||
workspace(name = "angular")
|
workspace(name = "angular")
|
||||||
|
|
||||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
#
|
||||||
load(
|
# Download Bazel toolchain dependencies as needed by build actions
|
||||||
"//packages/bazel:package.bzl",
|
#
|
||||||
"rules_angular_dependencies",
|
http_archive(
|
||||||
"rules_angular_dev_dependencies",
|
name = "build_bazel_rules_typescript",
|
||||||
|
url = "https://github.com/bazelbuild/rules_typescript/archive/0.17.0.zip",
|
||||||
|
strip_prefix = "rules_typescript-0.17.0",
|
||||||
|
sha256 = "1626ee2cc9770af6950bfc77dffa027f9aedf330fe2ea2ee7e504428927bd95d",
|
||||||
|
)
|
||||||
|
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
|
||||||
|
rules_typescript_dependencies()
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "bazel_toolchains",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||||
|
"https://github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
||||||
|
],
|
||||||
|
strip_prefix = "bazel-toolchains-5124557861ebf4c0b67f98180bff1f8551e0b421",
|
||||||
|
sha256 = "c3b08805602cd1d2b67ebe96407c1e8c6ed3d4ce55236ae2efe2f1948f38168d",
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "io_bazel_rules_go",
|
name = "io_bazel_rules_sass",
|
||||||
sha256 = "b7a62250a3a73277ade0ce306d22f122365b513f5402222403e507f2f997d421",
|
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
|
||||||
url = "https://github.com/bazelbuild/rules_go/releases/download/0.16.3/rules_go-0.16.3.tar.gz",
|
strip_prefix = "rules_sass-1.11.0",
|
||||||
|
sha256 = "dbe9fb97d5a7833b2a733eebc78c9c1e3880f676ac8af16e58ccf2139cbcad03",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Uncomment for local bazel rules development
|
# This commit matches the version of buildifier in angular/ngcontainer
|
||||||
#local_repository(
|
# If you change this, also check if it matches the version in the angular/ngcontainer
|
||||||
# name = "build_bazel_rules_nodejs",
|
# version in /.circleci/config.yml
|
||||||
# path = "../rules_nodejs",
|
BAZEL_BUILDTOOLS_VERSION = "49a6c199e3fbf5d94534b2771868677d3f9c6de9"
|
||||||
#)
|
|
||||||
#local_repository(
|
|
||||||
# name = "build_bazel_rules_typescript",
|
|
||||||
# path = "../rules_typescript",
|
|
||||||
#)
|
|
||||||
|
|
||||||
# Angular Bazel users will call this function
|
http_archive(
|
||||||
rules_angular_dependencies()
|
name = "com_github_bazelbuild_buildtools",
|
||||||
|
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||||
|
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||||
|
sha256 = "edf39af5fc257521e4af4c40829fffe8fba6d0ebff9f4dd69a6f8f1223ae047b",
|
||||||
|
)
|
||||||
|
|
||||||
# Install transitive deps of rules_nodejs
|
# Fetching the Bazel source code allows us to compile the Skylark linter
|
||||||
load("@build_bazel_rules_nodejs//:package.bzl", "rules_nodejs_dependencies")
|
http_archive(
|
||||||
|
name = "io_bazel",
|
||||||
|
url = "https://github.com/bazelbuild/bazel/archive/0.17.1.zip",
|
||||||
|
strip_prefix = "bazel-0.17.1",
|
||||||
|
sha256 = "ace8cced3b21e64a8fdad68508e9b0644201ec848ad583651719841d567fc66d",
|
||||||
|
)
|
||||||
|
|
||||||
rules_nodejs_dependencies()
|
http_archive(
|
||||||
|
name = "io_bazel_skydoc",
|
||||||
|
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
|
||||||
|
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
|
||||||
|
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
|
||||||
|
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
|
||||||
|
)
|
||||||
|
|
||||||
# These are the dependencies only for us
|
# We have a source dependency on the Devkit repository, because it's built with
|
||||||
rules_angular_dev_dependencies()
|
# Bazel.
|
||||||
|
# This allows us to edit sources and have the effect appear immediately without
|
||||||
|
# re-packaging or "npm link"ing.
|
||||||
|
# Even better, things like aspects will visit the entire graph including
|
||||||
|
# ts_library rules in the devkit repository.
|
||||||
|
http_archive(
|
||||||
|
name = "angular_cli",
|
||||||
|
url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip",
|
||||||
|
strip_prefix = "angular-cli-6.1.0-rc.0",
|
||||||
|
sha256 = "8cf320ea58c321e103f39087376feea502f20eaf79c61a4fdb05c7286c8684fd",
|
||||||
|
)
|
||||||
|
|
||||||
# Install transitive deps of rules_typescript
|
http_archive(
|
||||||
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
|
name = "org_brotli",
|
||||||
|
url = "https://github.com/google/brotli/archive/v1.0.5.zip",
|
||||||
rules_typescript_dependencies()
|
strip_prefix = "brotli-1.0.5",
|
||||||
|
sha256 = "774b893a0700b0692a76e2e5b7e7610dbbe330ffbe3fe864b4b52ca718061d5a",
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Point Bazel to WORKSPACEs that live in subdirectories
|
# Point Bazel to WORKSPACEs that live in subdirectories
|
||||||
#
|
#
|
||||||
http_archive(
|
|
||||||
|
local_repository(
|
||||||
name = "rxjs",
|
name = "rxjs",
|
||||||
sha256 = "72b0b4e517f43358f554c125e40e39f67688cd2738a8998b4a266981ed32f403",
|
path = "node_modules/rxjs/src",
|
||||||
strip_prefix = "package/src",
|
|
||||||
url = "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
||||||
@ -59,38 +96,29 @@ local_repository(
|
|||||||
#
|
#
|
||||||
# Load and install our dependencies downloaded above.
|
# Load and install our dependencies downloaded above.
|
||||||
#
|
#
|
||||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
|
||||||
|
|
||||||
check_bazel_version("0.20.0", """
|
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||||
You no longer need to install Bazel on your machine.
|
|
||||||
Angular has a dependency on the @bazel/bazel package which supplies it.
|
check_bazel_version("0.17.0", """
|
||||||
Try running `yarn bazel` instead.
|
If you are on a Mac and using Homebrew, there is a breaking change to the installation in Bazel 0.16
|
||||||
(If you did run that, check that you've got a fresh `yarn install`)
|
See https://blog.bazel.build/2018/08/22/bazel-homebrew.html
|
||||||
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
node_repositories(
|
node_repositories(
|
||||||
node_version = "10.9.0",
|
|
||||||
package_json = ["//:package.json"],
|
package_json = ["//:package.json"],
|
||||||
preserve_symlinks = True,
|
preserve_symlinks = True,
|
||||||
yarn_version = "1.12.1",
|
node_version = "10.9.0",
|
||||||
|
yarn_version = "1.9.2",
|
||||||
)
|
)
|
||||||
|
|
||||||
local_repository(
|
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||||
name = "npm",
|
|
||||||
path = "tools/npm_workspace",
|
|
||||||
)
|
|
||||||
|
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
|
|
||||||
|
|
||||||
go_rules_dependencies()
|
go_rules_dependencies()
|
||||||
|
|
||||||
go_register_toolchains()
|
go_register_toolchains()
|
||||||
|
|
||||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||||
|
|
||||||
web_test_repositories()
|
web_test_repositories()
|
||||||
|
|
||||||
browser_repositories(
|
browser_repositories(
|
||||||
chromium = True,
|
chromium = True,
|
||||||
firefox = True,
|
firefox = True,
|
||||||
@ -108,9 +136,7 @@ ng_setup_workspace()
|
|||||||
# Skylark documentation generation
|
# Skylark documentation generation
|
||||||
|
|
||||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||||
|
|
||||||
sass_repositories()
|
sass_repositories()
|
||||||
|
|
||||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
||||||
|
|
||||||
skydoc_repositories()
|
skydoc_repositories()
|
||||||
|
3
aio/.gitignore
vendored
3
aio/.gitignore
vendored
@ -44,3 +44,6 @@ protractor-results*.txt
|
|||||||
# System Files
|
# System Files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# copied dependencies
|
||||||
|
src/assets/js/lunr*
|
||||||
|
@ -22,8 +22,8 @@ Here are the most important tasks you might need to use:
|
|||||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||||
* `yarn lint` - check that the doc-viewer code follows our style rules.
|
* `yarn lint` - check that the doc-viewer code follows our style rules.
|
||||||
* `yarn test` - watch all the source files, for the doc-viewer, and run all the unit tests when any change.
|
* `yarn test` - run all the unit tests once.
|
||||||
* `yarn test --watch=false` - run all the unit tests once.
|
* `yarn test --watch` - watch all the source files, for the doc-viewer, and run all the unit tests when any change.
|
||||||
* `yarn e2e` - run all the e2e tests for the doc-viewer.
|
* `yarn e2e` - run all the e2e tests for the doc-viewer.
|
||||||
|
|
||||||
* `yarn docs` - generate all the docs from the source files.
|
* `yarn docs` - generate all the docs from the source files.
|
||||||
@ -41,6 +41,8 @@ Here are the most important tasks you might need to use:
|
|||||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
- `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 --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||||
|
|
||||||
|
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
||||||
|
|
||||||
## Developing on Windows
|
## Developing on Windows
|
||||||
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
|
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
|
||||||
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
|
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
|
||||||
@ -54,9 +56,14 @@ It's necessary to remove the temporary files, because otherwise they're displaye
|
|||||||
|
|
||||||
## Using ServiceWorker locally
|
## Using ServiceWorker locally
|
||||||
|
|
||||||
Running `yarn start` (even when explicitly targeting production mode) does not set up the
|
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
|
||||||
ServiceWorker. If you want to test the ServiceWorker locally, you can use `yarn build` and then
|
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
|
||||||
serve the files in `dist/` with `yarn http-server dist -p 4200`.
|
with webpack serving the files from memory).
|
||||||
|
|
||||||
|
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
|
||||||
|
with `yarn http-server dist -p 4200`.
|
||||||
|
|
||||||
|
For more details see #16745.
|
||||||
|
|
||||||
|
|
||||||
## Guide to authoring
|
## Guide to authoring
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
# Periodically clean up builds that do not correspond to currently open PRs
|
# Periodically clean up builds that do not correspond to currently open PRs
|
||||||
0 12 * * * /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
0 12 * * * root /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
||||||
|
@ -36,11 +36,6 @@ server {
|
|||||||
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
|
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
|
||||||
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
|
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
|
||||||
|
|
||||||
error_page 404 /404.html;
|
|
||||||
location "=/404.html" {
|
|
||||||
internal;
|
|
||||||
}
|
|
||||||
|
|
||||||
location "~/[^/]+\.[^/]+$" {
|
location "~/[^/]+\.[^/]+$" {
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
@ -71,21 +66,6 @@ server {
|
|||||||
return 200 '';
|
return 200 '';
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check PRs previewability
|
|
||||||
location "~^/can-have-public-preview/\d+/?$" {
|
|
||||||
if ($request_method != "GET") {
|
|
||||||
add_header Allow "GET";
|
|
||||||
return 405;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass_request_headers on;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_method GET;
|
|
||||||
proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri;
|
|
||||||
|
|
||||||
resolver 127.0.0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Notify about CircleCI builds
|
# Notify about CircleCI builds
|
||||||
location "~^/circle-build/?$" {
|
location "~^/circle-build/?$" {
|
||||||
if ($request_method != "POST") {
|
if ($request_method != "POST") {
|
||||||
|
@ -5,12 +5,12 @@ import * as shell from 'shelljs';
|
|||||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||||
import {GithubApi} from '../common/github-api';
|
import {GithubApi} from '../common/github-api';
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
import {assertNotMissingOrEmpty, getPrInfoFromDownloadPath, Logger} from '../common/utils';
|
import {assertNotMissingOrEmpty, createLogger, getPrInfoFromDownloadPath} from '../common/utils';
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class BuildCleaner {
|
export class BuildCleaner {
|
||||||
|
|
||||||
private logger = new Logger('BuildCleaner');
|
private logger = createLogger('BuildCleaner');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
|
constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
|
||||||
@ -122,6 +122,6 @@ export class BuildCleaner {
|
|||||||
this.logger.log(`Existing downloads: ${existingDownloads.length}`);
|
this.logger.log(`Existing downloads: ${existingDownloads.length}`);
|
||||||
this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
|
this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
|
||||||
|
|
||||||
toRemove.forEach(filePath => shell.rm(path.join(this.downloadsDir, filePath)));
|
toRemove.forEach(filePath => shell.rm(filePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export class CircleCiApi {
|
|||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`);
|
throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json<BuildInfo>();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`CircleCI build info request failed (${error.message})`);
|
throw new Error(`CircleCI build info request failed (${error.message})`);
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ export class CircleCiApi {
|
|||||||
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`);
|
const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`);
|
||||||
const artifacts = await response.json() as ArtifactResponse;
|
const artifacts = await response.json<ArtifactResponse>();
|
||||||
const artifact = artifacts.find(item => item.path === artifactPath);
|
const artifact = artifacts.find(item => item.path === artifactPath);
|
||||||
if (!artifact) {
|
if (!artifact) {
|
||||||
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
||||||
|
@ -38,8 +38,7 @@ export class GithubApi {
|
|||||||
return this.request<T>('post', path, data);
|
return this.request<T>('post', path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In GitHub API paginated requests, page numbering is 1-based. (https://developer.github.com/v3/#pagination)
|
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 0): Promise<T[]> {
|
||||||
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 1): Promise<T[]> {
|
|
||||||
const perPage = 100;
|
const perPage = 100;
|
||||||
const params = {
|
const params = {
|
||||||
...baseParams,
|
...baseParams,
|
||||||
|
@ -74,6 +74,6 @@ export class GithubPullRequests {
|
|||||||
*/
|
*/
|
||||||
public fetchFiles(pr: number): Promise<FileInfo[]> {
|
public fetchFiles(pr: number): Promise<FileInfo[]> {
|
||||||
assert(pr > 0, `Invalid PR number: ${pr}`);
|
assert(pr > 0, `Invalid PR number: ${pr}`);
|
||||||
return this.api.getPaginated<FileInfo>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
return this.api.get<FileInfo[]>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
// We can't use `import...from` here, because of the following mess:
|
export const runTests = (specFiles: string[], helpers?: string[]) => {
|
||||||
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
// We can't use `import` here, because of the following mess:
|
||||||
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
||||||
//
|
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
||||||
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
//
|
||||||
// `jasmine-core` module and the `jasmine` module).
|
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
||||||
import Jasmine = require('jasmine');
|
// `jasmine-core` module and the `jasmine` module).
|
||||||
import 'source-map-support/register';
|
// tslint:disable-next-line: no-var-requires variable-name
|
||||||
|
const Jasmine = require('jasmine');
|
||||||
export const runTests = (specFiles: string[]) => {
|
|
||||||
const config = {
|
const config = {
|
||||||
|
helpers,
|
||||||
random: true,
|
random: true,
|
||||||
spec_files: specFiles,
|
spec_files: specFiles,
|
||||||
stopSpecOnExpectationFailure: true,
|
stopSpecOnExpectationFailure: true,
|
||||||
@ -16,7 +16,7 @@ export const runTests = (specFiles: string[]) => {
|
|||||||
|
|
||||||
process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason));
|
process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason));
|
||||||
|
|
||||||
const runner = new Jasmine({});
|
const runner = new Jasmine();
|
||||||
runner.loadConfig(config);
|
runner.loadConfig(config);
|
||||||
runner.onComplete((passed: boolean) => process.exit(passed ? 0 : 1));
|
runner.onComplete((passed: boolean) => process.exit(passed ? 0 : 1));
|
||||||
runner.execute();
|
runner.execute();
|
||||||
|
@ -74,25 +74,12 @@ export const getEnvVar = (name: string, isOptional = false): string => {
|
|||||||
return value || '';
|
return value || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export function createLogger(scope: string) {
|
||||||
* A basic logger implementation.
|
const padding = ' '.repeat(20 - scope.length);
|
||||||
* Delegates to `console`, but prepends each message with the current date and specified scope (i.e caller).
|
return {
|
||||||
*/
|
error: (...args: any[]) => console.error(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||||
export class Logger {
|
info: (...args: any[]) => console.info(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||||
private padding = ' '.repeat(20 - this.scope.length);
|
log: (...args: any[]) => console.log(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||||
|
warn: (...args: any[]) => console.warn(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
||||||
/**
|
};
|
||||||
* Create a new `Logger` instance for the specified `scope`.
|
|
||||||
* @param scope The logger's scope (added to all messages).
|
|
||||||
*/
|
|
||||||
constructor(private scope: string) {}
|
|
||||||
|
|
||||||
public error(...args: any[]) { this.callMethod('error', args); }
|
|
||||||
public info(...args: any[]) { this.callMethod('info', args); }
|
|
||||||
public log(...args: any[]) { this.callMethod('log', args); }
|
|
||||||
public warn(...args: any[]) { this.callMethod('warn', args); }
|
|
||||||
|
|
||||||
private callMethod(method: 'error' | 'info' | 'log' | 'warn', args: any[]) {
|
|
||||||
console[method](`[${new Date()}]`, `${this.scope}:${this.padding}`, ...args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,14 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as shell from 'shelljs';
|
import * as shell from 'shelljs';
|
||||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||||
import {assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils';
|
import {assertNotMissingOrEmpty, computeShortSha, createLogger} from '../common/utils';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||||
import {PreviewServerError} from './preview-error';
|
import {PreviewServerError} from './preview-error';
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class BuildCreator extends EventEmitter {
|
export class BuildCreator extends EventEmitter {
|
||||||
|
|
||||||
private logger = new Logger('BuildCreator');
|
private logger = createLogger('BuildCreator');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor(protected buildsDir: string) {
|
constructor(protected buildsDir: string) {
|
||||||
|
@ -4,7 +4,7 @@ import {dirname} from 'path';
|
|||||||
import {mkdir} from 'shelljs';
|
import {mkdir} from 'shelljs';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {CircleCiApi} from '../common/circle-ci-api';
|
import {CircleCiApi} from '../common/circle-ci-api';
|
||||||
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, Logger} from '../common/utils';
|
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, createLogger} from '../common/utils';
|
||||||
import {PreviewServerError} from './preview-error';
|
import {PreviewServerError} from './preview-error';
|
||||||
|
|
||||||
export interface GithubInfo {
|
export interface GithubInfo {
|
||||||
@ -19,7 +19,7 @@ export interface GithubInfo {
|
|||||||
* A helper that can get information about builds and download build artifacts.
|
* A helper that can get information about builds and download build artifacts.
|
||||||
*/
|
*/
|
||||||
export class BuildRetriever {
|
export class BuildRetriever {
|
||||||
private logger = new Logger('BuildRetriever');
|
private logger = createLogger('BuildRetriever');
|
||||||
constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
|
constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
|
||||||
assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
|
assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
|
||||||
assertNotMissingOrEmpty('downloadDir', downloadDir);
|
assertNotMissingOrEmpty('downloadDir', downloadDir);
|
||||||
@ -34,7 +34,7 @@ export class BuildRetriever {
|
|||||||
const buildInfo = await this.api.getBuildInfo(buildNum);
|
const buildInfo = await this.api.getBuildInfo(buildNum);
|
||||||
const githubInfo: GithubInfo = {
|
const githubInfo: GithubInfo = {
|
||||||
org: buildInfo.username,
|
org: buildInfo.username,
|
||||||
pr: getPrFromBranch(buildInfo.branch),
|
pr: getPrfromBranch(buildInfo.branch),
|
||||||
repo: buildInfo.reponame,
|
repo: buildInfo.reponame,
|
||||||
sha: buildInfo.vcs_revision,
|
sha: buildInfo.vcs_revision,
|
||||||
success: !buildInfo.failed,
|
success: !buildInfo.failed,
|
||||||
@ -73,7 +73,7 @@ export class BuildRetriever {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrFromBranch(branch: string): number {
|
function getPrfromBranch(branch: string): number {
|
||||||
// CircleCI only exposes PR numbers via the `branch` field :-(
|
// CircleCI only exposes PR numbers via the `branch` field :-(
|
||||||
const match = /^pull\/(\d+)$/.exec(branch);
|
const match = /^pull\/(\d+)$/.exec(branch);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
import * as bodyParser from 'body-parser';
|
import * as bodyParser from 'body-parser';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import {AddressInfo} from 'net';
|
|
||||||
import {CircleCiApi} from '../common/circle-ci-api';
|
import {CircleCiApi} from '../common/circle-ci-api';
|
||||||
import {GithubApi} from '../common/github-api';
|
import {GithubApi} from '../common/github-api';
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
import {GithubTeams} from '../common/github-teams';
|
import {GithubTeams} from '../common/github-teams';
|
||||||
import {assert, assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils';
|
import {assert, assertNotMissingOrEmpty, createLogger} from '../common/utils';
|
||||||
import {BuildCreator} from './build-creator';
|
import {BuildCreator} from './build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||||
import {BuildRetriever} from './build-retriever';
|
import {BuildRetriever} from './build-retriever';
|
||||||
@ -32,7 +31,7 @@ export interface PreviewServerConfig {
|
|||||||
trustedPrLabel: string;
|
trustedPrLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = new Logger('PreviewServer');
|
const logger = createLogger('PreviewServer');
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class PreviewServerFactory {
|
export class PreviewServerFactory {
|
||||||
@ -53,7 +52,7 @@ export class PreviewServerFactory {
|
|||||||
const httpServer = http.createServer(middleware as any);
|
const httpServer = http.createServer(middleware as any);
|
||||||
|
|
||||||
httpServer.on('listening', () => {
|
httpServer.on('listening', () => {
|
||||||
const info = httpServer.address() as AddressInfo;
|
const info = httpServer.address();
|
||||||
logger.info(`Up and running (and listening on ${info.address}:${info.port})...`);
|
logger.info(`Up and running (and listening on ${info.address}:${info.port})...`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,36 +63,10 @@ export class PreviewServerFactory {
|
|||||||
buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
|
buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
|
||||||
const middleware = express();
|
const middleware = express();
|
||||||
const jsonParser = bodyParser.json();
|
const jsonParser = bodyParser.json();
|
||||||
const significantFilesRe = new RegExp(cfg.significantFilesPattern);
|
|
||||||
|
|
||||||
// RESPOND TO IS-ALIVE PING
|
// RESPOND TO IS-ALIVE PING
|
||||||
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
||||||
|
|
||||||
// RESPOND TO CAN-HAVE-PUBLIC-PREVIEW CHECK
|
|
||||||
const canHavePublicPreviewRe = /^\/can-have-public-preview\/(\d+)\/?$/;
|
|
||||||
middleware.get(canHavePublicPreviewRe, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const pr = +canHavePublicPreviewRe.exec(req.url)![1];
|
|
||||||
|
|
||||||
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
|
||||||
// Cannot have preview: PR did not touch relevant files: `aio/` or `packages/` (except for spec files).
|
|
||||||
res.send({canHavePublicPreview: false, reason: 'No significant files touched.'});
|
|
||||||
logger.log(`PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`);
|
|
||||||
} else if (!await buildVerifier.getPrIsTrusted(pr)) {
|
|
||||||
// Cannot have preview: PR not automatically verifiable as "trusted".
|
|
||||||
res.send({canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'});
|
|
||||||
logger.log(`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`);
|
|
||||||
} else {
|
|
||||||
// Can have preview.
|
|
||||||
res.send({canHavePublicPreview: true, reason: null});
|
|
||||||
logger.log(`PR:${pr} - Can have a public preview.`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Previewability check error', err);
|
|
||||||
respondWithError(res, err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// CIRCLE_CI BUILD COMPLETE WEBHOOK
|
// CIRCLE_CI BUILD COMPLETE WEBHOOK
|
||||||
middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => {
|
middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -134,7 +107,7 @@ export class PreviewServerFactory {
|
|||||||
`Invalid webhook: expected "githubRepo" property to equal "${cfg.githubRepo}" but got "${repo}".`);
|
`Invalid webhook: expected "githubRepo" property to equal "${cfg.githubRepo}" but got "${repo}".`);
|
||||||
|
|
||||||
// Do not deploy unless this PR has touched relevant files: `aio/` or `packages/` (except for spec files)
|
// Do not deploy unless this PR has touched relevant files: `aio/` or `packages/` (except for spec files)
|
||||||
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
if (!await buildVerifier.getSignificantFilesChanged(pr, new RegExp(cfg.significantFilesPattern))) {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
logger.log(`PR:${pr}, Build:${buildNum} - ` +
|
logger.log(`PR:${pr}, Build:${buildNum} - ` +
|
||||||
`Skipping preview processing because this PR did not touch any significant files.`);
|
`Skipping preview processing because this PR did not touch any significant files.`);
|
||||||
@ -144,10 +117,7 @@ export class PreviewServerFactory {
|
|||||||
const artifactPath = await buildRetriever.downloadBuildArtifact(buildNum, pr, sha, cfg.buildArtifactPath);
|
const artifactPath = await buildRetriever.downloadBuildArtifact(buildNum, pr, sha, cfg.buildArtifactPath);
|
||||||
const isPublic = await buildVerifier.getPrIsTrusted(pr);
|
const isPublic = await buildVerifier.getPrIsTrusted(pr);
|
||||||
await buildCreator.create(pr, sha, artifactPath, isPublic);
|
await buildCreator.create(pr, sha, artifactPath, isPublic);
|
||||||
|
|
||||||
res.sendStatus(isPublic ? 201 : 202);
|
res.sendStatus(isPublic ? 201 : 202);
|
||||||
logger.log(`PR:${pr}, SHA:${computeShortSha(sha)}, Build:${buildNum} - ` +
|
|
||||||
`Successfully created ${isPublic ? 'public' : 'non-public'} preview.`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('CircleCI webhook error', err);
|
logger.error('CircleCI webhook error', err);
|
||||||
respondWithError(res, err);
|
respondWithError(res, err);
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
AIO_NGINX_PORT_HTTPS,
|
AIO_NGINX_PORT_HTTPS,
|
||||||
AIO_WWW_USER,
|
AIO_WWW_USER,
|
||||||
} from '../common/env-variables';
|
} from '../common/env-variables';
|
||||||
import {computeShortSha, Logger} from '../common/utils';
|
import {computeShortSha, createLogger} from '../common/utils';
|
||||||
|
|
||||||
// Interfaces - Types
|
// Interfaces - Types
|
||||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||||
@ -31,7 +31,7 @@ class Helper {
|
|||||||
https: AIO_NGINX_PORT_HTTPS,
|
https: AIO_NGINX_PORT_HTTPS,
|
||||||
};
|
};
|
||||||
|
|
||||||
private logger = new Logger('TestHelper');
|
private logger = createLogger('TestHelper');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -105,7 +105,7 @@ class Helper {
|
|||||||
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
|
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public verifyResponse(status: number | [number, string], regex: string | RegExp = /^/): VerifyCmdResultFn {
|
public verifyResponse(status: number | [number, string], regex = /^/): VerifyCmdResultFn {
|
||||||
let statusCode: number;
|
let statusCode: number;
|
||||||
let statusText: string;
|
let statusText: string;
|
||||||
|
|
||||||
@ -180,42 +180,26 @@ class Helper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DefaultCurlOptions {
|
|
||||||
defaultMethod?: CurlOptions['method'];
|
|
||||||
defaultOptions?: CurlOptions['options'];
|
|
||||||
defaultHeaders?: CurlOptions['headers'];
|
|
||||||
defaultData?: CurlOptions['data'];
|
|
||||||
defaultExtraPath?: CurlOptions['extraPath'];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CurlOptions {
|
interface CurlOptions {
|
||||||
method?: string;
|
method?: string;
|
||||||
options?: string;
|
options?: string;
|
||||||
headers?: string[];
|
|
||||||
data?: any;
|
data?: any;
|
||||||
url?: string;
|
url?: string;
|
||||||
extraPath?: string;
|
extraPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeCurl(baseUrl: string, {
|
export function makeCurl(baseUrl: string) {
|
||||||
defaultMethod = 'POST',
|
|
||||||
defaultOptions = '',
|
|
||||||
defaultHeaders = ['Content-Type: application/json'],
|
|
||||||
defaultData = {},
|
|
||||||
defaultExtraPath = '',
|
|
||||||
}: DefaultCurlOptions = {}) {
|
|
||||||
return function curl({
|
return function curl({
|
||||||
method = defaultMethod,
|
method = 'POST',
|
||||||
options = defaultOptions,
|
options = '',
|
||||||
headers = defaultHeaders,
|
data = {},
|
||||||
data = defaultData,
|
|
||||||
url = baseUrl,
|
url = baseUrl,
|
||||||
extraPath = defaultExtraPath,
|
extraPath = '',
|
||||||
}: CurlOptions) {
|
}: CurlOptions) {
|
||||||
const dataString = data ? JSON.stringify(data) : '';
|
const dataString = data ? JSON.stringify(data) : '';
|
||||||
const cmd = `curl -iLX ${method} ` +
|
const cmd = `curl -iLX ${method} ` +
|
||||||
`${options} ` +
|
`${options} ` +
|
||||||
headers.map(header => `--header "${header}" `).join('') +
|
`--header "Content-Type: application/json" ` +
|
||||||
`--data '${dataString}' ` +
|
`--data '${dataString}' ` +
|
||||||
`${url}${extraPath}`;
|
`${url}${extraPath}`;
|
||||||
return helper.runCmd(cmd);
|
return helper.runCmd(cmd);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import * as nock from 'nock';
|
import * as nock from 'nock';
|
||||||
import * as tar from 'tar-stream';
|
import * as tar from 'tar-stream';
|
||||||
import {gzipSync} from 'zlib';
|
import {gzipSync} from 'zlib';
|
||||||
import {getEnvVar, Logger} from '../common/utils';
|
import {createLogger, getEnvVar} from '../common/utils';
|
||||||
import {BuildNums, PrNums, SHA} from './constants';
|
import {BuildNums, PrNums, SHA} from './constants';
|
||||||
|
|
||||||
// We are using the `nock` library to fake responses from REST requests, when testing.
|
// We are using the `nock` library to fake responses from REST requests, when testing.
|
||||||
@ -14,7 +14,7 @@ import {BuildNums, PrNums, SHA} from './constants';
|
|||||||
// below and return a suitable response. This is quite complicated to setup since the
|
// below and return a suitable response. This is quite complicated to setup since the
|
||||||
// response from, say, CircleCI will affect what request is made to, say, Github.
|
// response from, say, CircleCI will affect what request is made to, say, Github.
|
||||||
|
|
||||||
const logger = new Logger('mock-external-apis');
|
const logger = createLogger('NOCK');
|
||||||
|
|
||||||
const log = (...args: any[]) => {
|
const log = (...args: any[]) => {
|
||||||
// Filter out non-matching URL checks
|
// Filter out non-matching URL checks
|
||||||
@ -76,7 +76,7 @@ const GITHUB_PULLS_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/p
|
|||||||
const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`;
|
const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`;
|
||||||
|
|
||||||
const getIssueUrl = (prNum: number) => `${GITHUB_ISSUES_URL}/${prNum}`;
|
const getIssueUrl = (prNum: number) => `${GITHUB_ISSUES_URL}/${prNum}`;
|
||||||
const getFilesUrl = (prNum: number, pageNum = 1) => `${GITHUB_PULLS_URL}/${prNum}/files?page=${pageNum}&per_page=100`;
|
const getFilesUrl = (prNum: number) => `${GITHUB_PULLS_URL}/${prNum}/files`;
|
||||||
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
||||||
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ const githubApi = nock(GITHUB_API_HOST).log(log).persist().matchHeader('Authoriz
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
// GENERAL responses
|
// GENERAL responses
|
||||||
githubApi.get(GITHUB_TEAMS_URL + '?page=1&per_page=100').reply(200, TEST_TEAM_INFO);
|
githubApi.get(GITHUB_TEAMS_URL + '?page=0&per_page=100').reply(200, TEST_TEAM_INFO);
|
||||||
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
||||||
|
|
||||||
// BUILD_INFO errors
|
// BUILD_INFO errors
|
||||||
|
@ -3,7 +3,6 @@ import * as path from 'path';
|
|||||||
import {rm} from 'shelljs';
|
import {rm} from 'shelljs';
|
||||||
import {AIO_BUILDS_DIR, AIO_NGINX_HOSTNAME, AIO_NGINX_PORT_HTTP, AIO_NGINX_PORT_HTTPS} from '../common/env-variables';
|
import {AIO_BUILDS_DIR, AIO_NGINX_HOSTNAME, AIO_NGINX_PORT_HTTP, AIO_NGINX_PORT_HTTPS} from '../common/env-variables';
|
||||||
import {computeShortSha} from '../common/utils';
|
import {computeShortSha} from '../common/utils';
|
||||||
import {PrNums} from './constants';
|
|
||||||
import {helper as h} from './helper';
|
import {helper as h} from './helper';
|
||||||
import {customMatchers} from './jasmine-custom-matchers';
|
import {customMatchers} from './jasmine-custom-matchers';
|
||||||
|
|
||||||
@ -253,42 +252,6 @@ describe(`nginx`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/can-have-public-preview`, () => {
|
|
||||||
const baseUrl = `${scheme}://${host}/can-have-public-preview`;
|
|
||||||
|
|
||||||
|
|
||||||
it('should disallow non-GET requests', async () => {
|
|
||||||
await Promise.all([
|
|
||||||
h.runCmd(`curl -iLX POST ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
|
||||||
h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
|
||||||
h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
|
||||||
h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should pass requests through to the preview server', async () => {
|
|
||||||
await h.runCmd(`curl -iLX GET ${baseUrl}/${PrNums.CHANGED_FILES_ERROR}`).
|
|
||||||
then(h.verifyResponse(500, /CHANGED_FILES_ERROR/));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for unknown paths', async () => {
|
|
||||||
const cmdPrefix = `curl -iLX GET ${baseUrl}`;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
h.runCmd(`${cmdPrefix}/foo/42`).then(h.verifyResponse(404)),
|
|
||||||
h.runCmd(`${cmdPrefix}-foo/42`).then(h.verifyResponse(404)),
|
|
||||||
h.runCmd(`${cmdPrefix}nfoo/42`).then(h.verifyResponse(404)),
|
|
||||||
h.runCmd(`${cmdPrefix}/42/foo`).then(h.verifyResponse(404)),
|
|
||||||
h.runCmd(`${cmdPrefix}/f00`).then(h.verifyResponse(404)),
|
|
||||||
h.runCmd(`${cmdPrefix}/`).then(h.verifyResponse(404)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/circle-build`, () => {
|
describe(`${host}/circle-build`, () => {
|
||||||
|
|
||||||
it('should disallow non-POST requests', done => {
|
it('should disallow non-POST requests', done => {
|
||||||
@ -324,7 +287,6 @@ describe(`nginx`, () => {
|
|||||||
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
|
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
|
||||||
]).then(done);
|
]).then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,92 +18,6 @@ describe('preview-server', () => {
|
|||||||
afterEach(() => h.cleanUp());
|
afterEach(() => h.cleanUp());
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/can-have-public-preview`, () => {
|
|
||||||
const curl = makeCurl(`${host}/can-have-public-preview`, {
|
|
||||||
defaultData: null,
|
|
||||||
defaultExtraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`,
|
|
||||||
defaultHeaders: [],
|
|
||||||
defaultMethod: 'GET',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should disallow non-GET requests', async () => {
|
|
||||||
const bodyRegex = /^Unknown resource in request/;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
curl({method: 'POST'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({method: 'PUT'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({method: 'PATCH'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({method: 'DELETE'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for unknown paths', async () => {
|
|
||||||
const bodyRegex = /^Unknown resource in request/;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
curl({extraPath: `/foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({extraPath: `-foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({extraPath: `nfoo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}/foo`}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({extraPath: '/f00'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
curl({extraPath: '/'}).then(h.verifyResponse(404, bodyRegex)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 500 if checking for significant file changes fails', async () => {
|
|
||||||
await Promise.all([
|
|
||||||
curl({extraPath: `/${PrNums.CHANGED_FILES_404}`}).then(h.verifyResponse(500, /CHANGED_FILES_404/)),
|
|
||||||
curl({extraPath: `/${PrNums.CHANGED_FILES_ERROR}`}).then(h.verifyResponse(500, /CHANGED_FILES_ERROR/)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 200 (false) if no significant files were touched', async () => {
|
|
||||||
const expectedResponse = JSON.stringify({
|
|
||||||
canHavePublicPreview: false,
|
|
||||||
reason: 'No significant files touched.',
|
|
||||||
});
|
|
||||||
|
|
||||||
await curl({extraPath: `/${PrNums.CHANGED_FILES_NONE}`}).then(h.verifyResponse(200, expectedResponse));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 500 if checking "trusted" status fails', async () => {
|
|
||||||
await curl({extraPath: `/${PrNums.TRUST_CHECK_ERROR}`}).then(h.verifyResponse(500, 'TRUST_CHECK_ERROR'));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 200 (false) if the PR is not automatically verifiable as "trusted"', async () => {
|
|
||||||
const expectedResponse = JSON.stringify({
|
|
||||||
canHavePublicPreview: false,
|
|
||||||
reason: 'Not automatically verifiable as \\"trusted\\".',
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
curl({extraPath: `/${PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
|
||||||
curl({extraPath: `/${PrNums.TRUST_CHECK_UNTRUSTED}`}).then(h.verifyResponse(200, expectedResponse)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 200 (true) if the PR can have a public preview', async () => {
|
|
||||||
const expectedResponse = JSON.stringify({
|
|
||||||
canHavePublicPreview: true,
|
|
||||||
reason: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
|
||||||
curl({extraPath: `/${PrNums.TRUST_CHECK_TRUSTED_LABEL}`}).then(h.verifyResponse(200, expectedResponse)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/circle-build`, () => {
|
describe(`${host}/circle-build`, () => {
|
||||||
|
|
||||||
const curl = makeCurl(`${host}/circle-build`);
|
const curl = makeCurl(`${host}/circle-build`);
|
||||||
|
@ -7,49 +7,43 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "yarn clean-dist",
|
"prebuild": "yarn clean-dist",
|
||||||
"build": "yarn ~~build",
|
"build": "tsc",
|
||||||
"prebuild-watch": "yarn prebuild",
|
"build-watch": "yarn build --watch",
|
||||||
"build-watch": "yarn ~~build-watch",
|
|
||||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||||
"predev": "yarn build || true",
|
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||||
"dev": "run-p ~~build-watch ~~test-watch",
|
|
||||||
"lint": "tslint --project tsconfig.json",
|
"lint": "tslint --project tsconfig.json",
|
||||||
"pretest": "yarn build",
|
|
||||||
"test": "yarn ~~test-only",
|
|
||||||
"pretest-watch": "yarn pretest",
|
|
||||||
"test-watch": "yarn ~~test-watch",
|
|
||||||
"~~build": "tsc",
|
|
||||||
"~~build-watch": "yarn ~~build --watch",
|
|
||||||
"pre~~test-only": "yarn lint",
|
"pre~~test-only": "yarn lint",
|
||||||
"~~test-only": "node dist/test",
|
"~~test-only": "node dist/test",
|
||||||
"~~test-watch": "nodemon --delay 1 --exec \"yarn ~~test-only\" --watch dist"
|
"pretest": "yarn build",
|
||||||
|
"test": "yarn ~~test-only",
|
||||||
|
"pretest-watch": "yarn build",
|
||||||
|
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.2",
|
||||||
"delete-empty": "^2.0.0",
|
"delete-empty": "^2.0.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.15.4",
|
||||||
"jasmine": "^3.2.0",
|
"jasmine": "^2.8.0",
|
||||||
"nock": "^9.6.1",
|
"nock": "^9.2.5",
|
||||||
"node-fetch": "^2.2.0",
|
"node-fetch": "^2.1.2",
|
||||||
"shelljs": "^0.8.2",
|
"shelljs": "^0.8.1",
|
||||||
"source-map-support": "^0.5.9",
|
"tar-stream": "^1.6.0",
|
||||||
"tar-stream": "^1.6.1",
|
"tslib": "^1.7.1"
|
||||||
"tslib": "^1.9.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.17.0",
|
"@types/body-parser": "^1.16.5",
|
||||||
"@types/express": "^4.16.0",
|
"@types/express": "^4.0.37",
|
||||||
"@types/jasmine": "^2.8.8",
|
"@types/jasmine": "^2.6.0",
|
||||||
"@types/nock": "^9.3.0",
|
"@types/nock": "^9.1.3",
|
||||||
"@types/node": "^10.9.2",
|
"@types/node": "^8.0.30",
|
||||||
"@types/node-fetch": "^2.1.2",
|
"@types/node-fetch": "^1.6.8",
|
||||||
"@types/shelljs": "^0.8.0",
|
"@types/shelljs": "^0.8.0",
|
||||||
"@types/supertest": "^2.0.5",
|
"@types/supertest": "^2.0.3",
|
||||||
"nodemon": "^1.18.3",
|
"concurrently": "^3.5.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"nodemon": "^1.12.1",
|
||||||
"supertest": "^3.1.0",
|
"supertest": "^3.0.0",
|
||||||
"tslint": "^5.11.0",
|
"tslint": "^5.7.0",
|
||||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||||
"typescript": "^3.0.1"
|
"typescript": "^2.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,28 +5,25 @@ import * as shell from 'shelljs';
|
|||||||
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
|
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
|
||||||
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
|
||||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||||
import {Logger} from '../../lib/common/utils';
|
|
||||||
|
|
||||||
const EXISTING_BUILDS = [10, 20, 30, 40];
|
const EXISTING_BUILDS = [10, 20, 30, 40];
|
||||||
const EXISTING_DOWNLOADS = [
|
const EXISTING_DOWNLOADS = [
|
||||||
'10-ABCDEF0-build.zip',
|
'downloads/10-ABCDEF0-build.zip',
|
||||||
'10-1234567-build.zip',
|
'downloads/10-1234567-build.zip',
|
||||||
'20-ABCDEF0-build.zip',
|
'downloads/20-ABCDEF0-build.zip',
|
||||||
'20-1234567-build.zip',
|
'downloads/20-1234567-build.zip',
|
||||||
];
|
];
|
||||||
const OPEN_PRS = [10, 40];
|
const OPEN_PRS = [10, 40];
|
||||||
const ANY_DATE = jasmine.any(String);
|
const ANY_DATE = jasmine.any(String);
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
describe('BuildCleaner', () => {
|
describe('BuildCleaner', () => {
|
||||||
let loggerErrorSpy: jasmine.Spy;
|
|
||||||
let loggerLogSpy: jasmine.Spy;
|
|
||||||
let cleaner: BuildCleaner;
|
let cleaner: BuildCleaner;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
spyOn(console, 'error');
|
||||||
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
spyOn(console, 'log');
|
||||||
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '/downloads', 'build.zip');
|
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', 'build.zip');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor()', () => {
|
describe('constructor()', () => {
|
||||||
@ -54,13 +51,11 @@ describe('BuildCleaner', () => {
|
|||||||
toThrowError('Missing or empty required parameter \'githubToken\'!');
|
toThrowError('Missing or empty required parameter \'githubToken\'!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw if \'downloadsDir\' is empty', () => {
|
it('should throw if \'downloadsDir\' is empty', () => {
|
||||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')).
|
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')).
|
||||||
toThrowError('Missing or empty required parameter \'downloadsDir\'!');
|
toThrowError('Missing or empty required parameter \'downloadsDir\'!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw if \'artifactPath\' is empty', () => {
|
it('should throw if \'artifactPath\' is empty', () => {
|
||||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')).
|
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')).
|
||||||
toThrowError('Missing or empty required parameter \'artifactPath\'!');
|
toThrowError('Missing or empty required parameter \'artifactPath\'!');
|
||||||
@ -90,12 +85,9 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should return a promise', async () => {
|
it('should return a promise', () => {
|
||||||
const promise = cleaner.cleanUp();
|
const promise = cleaner.cleanUp();
|
||||||
expect(promise).toEqual(jasmine.any(Promise));
|
expect(promise).toEqual(jasmine.any(Promise));
|
||||||
|
|
||||||
// Do not complete the test and release the spies synchronously, to avoid running the actual implementations.
|
|
||||||
await promise;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -168,7 +160,6 @@ describe('BuildCleaner', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
|
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
|
||||||
try {
|
try {
|
||||||
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
|
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
|
||||||
@ -177,7 +168,6 @@ describe('BuildCleaner', () => {
|
|||||||
expect(err).toBe('Test');
|
expect(err).toBe('Test');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -287,14 +277,11 @@ describe('BuildCleaner', () => {
|
|||||||
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
|
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should log the number of open PRs', () => {
|
it('should log the number of open PRs', () => {
|
||||||
promise.then(prNumbers => {
|
promise.then(prNumbers => {
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
||||||
ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -314,9 +301,9 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should get the contents of the downloads directory', () => {
|
it('should get the contents of the builds directory', () => {
|
||||||
expect(fsReaddirSpy).toHaveBeenCalled();
|
expect(fsReaddirSpy).toHaveBeenCalled();
|
||||||
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/downloads');
|
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('downloads');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -330,7 +317,7 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should resolve with the returned file names', done => {
|
it('should resolve with the returned files (as numbers)', done => {
|
||||||
promise.then(result => {
|
promise.then(result => {
|
||||||
expect(result).toEqual(EXISTING_DOWNLOADS);
|
expect(result).toEqual(EXISTING_DOWNLOADS);
|
||||||
done();
|
done();
|
||||||
@ -396,7 +383,8 @@ describe('BuildCleaner', () => {
|
|||||||
|
|
||||||
cleaner.removeDir('/foo/bar');
|
cleaner.removeDir('/foo/bar');
|
||||||
|
|
||||||
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
expect(console.error).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(String), 'BuildCleaner: ', 'ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -413,8 +401,8 @@ describe('BuildCleaner', () => {
|
|||||||
it('should log the number of existing builds and builds to be removed', () => {
|
it('should log the number of existing builds and builds to be removed', () => {
|
||||||
cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]);
|
cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]);
|
||||||
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith('Existing builds: 3');
|
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing builds: 3');
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 build(s): 1, 2');
|
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Removing 2 build(s): 1, 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -466,36 +454,25 @@ describe('BuildCleaner', () => {
|
|||||||
|
|
||||||
|
|
||||||
describe('removeUnnecessaryDownloads()', () => {
|
describe('removeUnnecessaryDownloads()', () => {
|
||||||
let shellRmSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
shellRmSpy = spyOn(shell, 'rm');
|
spyOn(shell, 'rm');
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should log the number of existing downloads and downloads to be removed', () => {
|
|
||||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
|
||||||
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith('Existing downloads: 4');
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 download(s): 20-ABCDEF0-build.zip, 20-1234567-build.zip');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should construct full paths to directories (by prepending \'downloadsDir\')', () => {
|
|
||||||
cleaner.removeUnnecessaryDownloads(['dl-1', 'dl-2', 'dl-3'], []);
|
|
||||||
|
|
||||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-1'));
|
|
||||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-2'));
|
|
||||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-3'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should remove the downloads that do not correspond to open PRs', () => {
|
it('should remove the downloads that do not correspond to open PRs', () => {
|
||||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||||
expect(shellRmSpy).toHaveBeenCalledTimes(2);
|
expect(shell.rm).toHaveBeenCalledTimes(2);
|
||||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-ABCDEF0-build.zip'));
|
expect(shell.rm).toHaveBeenCalledWith('downloads/20-ABCDEF0-build.zip');
|
||||||
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-1234567-build.zip'));
|
expect(shell.rm).toHaveBeenCalledWith('downloads/20-1234567-build.zip');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should log the number of existing builds and builds to be removed', () => {
|
||||||
|
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||||
|
|
||||||
|
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing downloads: 4');
|
||||||
|
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ',
|
||||||
|
'Removing 2 download(s): downloads/20-ABCDEF0-build.zip, downloads/20-1234567-build.zip');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -126,8 +126,8 @@ describe('GithubApi', () => {
|
|||||||
(api as any).getPaginated('/foo/bar');
|
(api as any).getPaginated('/foo/bar');
|
||||||
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
||||||
|
|
||||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 1, per_page: 100});
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 0, per_page: 100});
|
||||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 1, per_page: 100});
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 0, per_page: 100});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -162,9 +162,9 @@ describe('GithubApi', () => {
|
|||||||
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
||||||
|
|
||||||
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
||||||
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
|
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(0)]);
|
||||||
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
|
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(1)]);
|
||||||
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
|
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(2)]);
|
||||||
|
|
||||||
expect(data).toEqual(allItems);
|
expect(data).toEqual(allItems);
|
||||||
|
|
||||||
|
@ -4,13 +4,13 @@ import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
|||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
describe('GithubPullRequests', () => {
|
describe('GithubPullRequests', () => {
|
||||||
|
|
||||||
let githubApi: jasmine.SpyObj<GithubApi>;
|
let githubApi: jasmine.SpyObj<GithubApi>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']);
|
githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('constructor()', () => {
|
describe('constructor()', () => {
|
||||||
|
|
||||||
it('should throw if \'githubOrg\' is missing or empty', () => {
|
it('should throw if \'githubOrg\' is missing or empty', () => {
|
||||||
@ -95,14 +95,16 @@ describe('GithubPullRequests', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('fetchAll()', () => {
|
describe('fetchAll()', () => {
|
||||||
let prs: GithubPullRequests;
|
let prs: GithubPullRequests;
|
||||||
|
|
||||||
beforeEach(() => prs = new GithubPullRequests(githubApi, 'foo', 'bar'));
|
beforeEach(() => {
|
||||||
|
prs = new GithubPullRequests(githubApi, 'foo', 'bar');
|
||||||
|
spyOn(console, 'log');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
||||||
@ -129,10 +131,8 @@ describe('GithubPullRequests', () => {
|
|||||||
githubApi.getPaginated.and.returnValue('Test');
|
githubApi.getPaginated.and.returnValue('Test');
|
||||||
expect(prs.fetchAll() as any).toBe('Test');
|
expect(prs.fetchAll() as any).toBe('Test');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('fetchFiles()', () => {
|
describe('fetchFiles()', () => {
|
||||||
let prs: GithubPullRequests;
|
let prs: GithubPullRequests;
|
||||||
|
|
||||||
@ -141,21 +141,21 @@ describe('GithubPullRequests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should make a paginated GET request to GitHub with the correct pathname', () => {
|
it('should make a GET request to GitHub with the correct pathname', () => {
|
||||||
prs.fetchFiles(42);
|
prs.fetchFiles(42);
|
||||||
expect(githubApi.getPaginated).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
expect(githubApi.get).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should resolve with the data returned from GitHub', done => {
|
it('should resolve with the data returned from GitHub', done => {
|
||||||
const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}];
|
const expected: any = [{ sha: 'ABCDE', filename: 'a/b/c'}, { sha: '12345', filename: 'x/y/z' }];
|
||||||
githubApi.getPaginated.and.callFake(() => Promise.resolve(expected));
|
githubApi.get.and.callFake(() => Promise.resolve(expected));
|
||||||
prs.fetchFiles(42).then(data => {
|
prs.fetch(42).then(data => {
|
||||||
expect(data).toEqual(expected);
|
expect(data).toEqual(expected);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
// Imports
|
// Imports
|
||||||
import {resolve as resolvePath} from 'path';
|
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
assertNotMissingOrEmpty,
|
assertNotMissingOrEmpty,
|
||||||
@ -7,7 +6,6 @@ import {
|
|||||||
computeShortSha,
|
computeShortSha,
|
||||||
getEnvVar,
|
getEnvVar,
|
||||||
getPrInfoFromDownloadPath,
|
getPrInfoFromDownloadPath,
|
||||||
Logger,
|
|
||||||
} from '../../lib/common/utils';
|
} from '../../lib/common/utils';
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@ -21,7 +19,6 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('assert', () => {
|
describe('assert', () => {
|
||||||
it('should throw if passed a false value', () => {
|
it('should throw if passed a false value', () => {
|
||||||
expect(() => assert(false, 'error message')).toThrowError('error message');
|
expect(() => assert(false, 'error message')).toThrowError('error message');
|
||||||
@ -32,7 +29,6 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('computeArtifactDownloadPath', () => {
|
describe('computeArtifactDownloadPath', () => {
|
||||||
it('should compute an absolute path based on the artifact info provided', () => {
|
it('should compute an absolute path based on the artifact info provided', () => {
|
||||||
const downloadDir = '/a/b/c';
|
const downloadDir = '/a/b/c';
|
||||||
@ -40,11 +36,10 @@ describe('utils', () => {
|
|||||||
const sha = 'ABCDEF1234567';
|
const sha = 'ABCDEF1234567';
|
||||||
const artifactPath = 'a/path/to/file.zip';
|
const artifactPath = 'a/path/to/file.zip';
|
||||||
const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath);
|
const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath);
|
||||||
expect(path).toBe(resolvePath('/a/b/c/123-ABCDEF1-file.zip'));
|
expect(path).toEqual('/a/b/c/123-ABCDEF1-file.zip');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('getPrInfoFromDownloadPath', () => {
|
describe('getPrInfoFromDownloadPath', () => {
|
||||||
it('should extract the PR and SHA from the file path', () => {
|
it('should extract the PR and SHA from the file path', () => {
|
||||||
const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip');
|
const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip');
|
||||||
@ -53,7 +48,6 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('assertNotMissingOrEmpty()', () => {
|
describe('assertNotMissingOrEmpty()', () => {
|
||||||
|
|
||||||
it('should throw if passed an empty value', () => {
|
it('should throw if passed an empty value', () => {
|
||||||
@ -128,79 +122,4 @@ describe('utils', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Logger', () => {
|
|
||||||
let consoleErrorSpy: jasmine.Spy;
|
|
||||||
let consoleInfoSpy: jasmine.Spy;
|
|
||||||
let consoleLogSpy: jasmine.Spy;
|
|
||||||
let consoleWarnSpy: jasmine.Spy;
|
|
||||||
let logger: Logger;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
consoleErrorSpy = spyOn(console, 'error');
|
|
||||||
consoleInfoSpy = spyOn(console, 'info');
|
|
||||||
consoleLogSpy = spyOn(console, 'log');
|
|
||||||
consoleWarnSpy = spyOn(console, 'warn');
|
|
||||||
|
|
||||||
logger = new Logger('TestScope');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should delegate to `console`', () => {
|
|
||||||
logger.error('foo');
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleErrorSpy.calls.argsFor(0)).toContain('foo');
|
|
||||||
|
|
||||||
logger.info('bar');
|
|
||||||
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleInfoSpy.calls.argsFor(0)).toContain('bar');
|
|
||||||
|
|
||||||
logger.log('baz');
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleLogSpy.calls.argsFor(0)).toContain('baz');
|
|
||||||
|
|
||||||
logger.warn('qux');
|
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleWarnSpy.calls.argsFor(0)).toContain('qux');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should prepend messages with the current date and logger\'s scope', () => {
|
|
||||||
const mockDate = new Date(1337);
|
|
||||||
const expectedDateStr = `[${mockDate}]`;
|
|
||||||
const expectedScopeStr = 'TestScope: ';
|
|
||||||
|
|
||||||
jasmine.clock().mockDate(mockDate);
|
|
||||||
jasmine.clock().withMock(() => {
|
|
||||||
logger.error();
|
|
||||||
logger.info();
|
|
||||||
logger.log();
|
|
||||||
logger.warn();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
|
||||||
expect(consoleInfoSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should pass all arguments to `console`', () => {
|
|
||||||
const someString = jasmine.any(String);
|
|
||||||
|
|
||||||
logger.error('foo1', 'foo2');
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(someString, someString, 'foo1', 'foo2');
|
|
||||||
|
|
||||||
logger.info('bar1', 'bar2');
|
|
||||||
expect(consoleInfoSpy).toHaveBeenCalledWith(someString, someString, 'bar1', 'bar2');
|
|
||||||
|
|
||||||
logger.log('baz1', 'baz2');
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledWith(someString, someString, 'baz1', 'baz2');
|
|
||||||
|
|
||||||
logger.warn('qux1', 'qux2');
|
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(someString, someString, 'qux1', 'qux2');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
declare namespace jasmine {
|
||||||
|
export interface DoneFn extends Function {
|
||||||
|
(): void;
|
||||||
|
fail: (message: Error | string) => void;
|
||||||
|
}
|
||||||
|
}
|
@ -3,4 +3,5 @@ import {runTests} from '../lib/common/run-tests';
|
|||||||
|
|
||||||
// Run
|
// Run
|
||||||
const specFiles = [`${__dirname}/**/*.spec.js`];
|
const specFiles = [`${__dirname}/**/*.spec.js`];
|
||||||
runTests(specFiles);
|
const helpers = [`${__dirname}/helpers.js`];
|
||||||
|
runTests(specFiles, helpers);
|
||||||
|
@ -5,7 +5,6 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as shell from 'shelljs';
|
import * as shell from 'shelljs';
|
||||||
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
||||||
import {Logger} from '../../lib/common/utils';
|
|
||||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||||
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
||||||
@ -492,7 +491,7 @@ describe('BuildCreator', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cpExecCbs = [];
|
cpExecCbs = [];
|
||||||
|
|
||||||
consoleWarnSpy = spyOn(Logger.prototype, 'warn');
|
consoleWarnSpy = spyOn(console, 'warn');
|
||||||
shellChmodSpy = spyOn(shell, 'chmod');
|
shellChmodSpy = spyOn(shell, 'chmod');
|
||||||
shellRmSpy = spyOn(shell, 'rm');
|
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: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||||
@ -514,7 +513,8 @@ describe('BuildCreator', () => {
|
|||||||
|
|
||||||
it('should log (as a warning) any stderr output if extracting succeeded', done => {
|
it('should log (as a warning) any stderr output if extracting succeeded', done => {
|
||||||
(bc as any).extractArchive('foo', 'bar').
|
(bc as any).extractArchive('foo', 'bar').
|
||||||
then(() => expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr')).
|
then(() => expect(consoleWarnSpy)
|
||||||
|
.toHaveBeenCalledWith(jasmine.any(String), 'BuildCreator: ', 'This is stderr')).
|
||||||
then(done);
|
then(done);
|
||||||
|
|
||||||
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as nock from 'nock';
|
import * as nock from 'nock';
|
||||||
import {resolve as resolvePath} from 'path';
|
|
||||||
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||||
import {Logger} from '../../lib/common/utils';
|
|
||||||
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
||||||
|
|
||||||
describe('BuildRetriever', () => {
|
describe('BuildRetriever', () => {
|
||||||
const MAX_DOWNLOAD_SIZE = 10000;
|
const MAX_DOWNLOAD_SIZE = 10000;
|
||||||
const DOWNLOAD_DIR = resolvePath('/DOWNLOAD/DIR');
|
const DOWNLOAD_DIR = '/DOWNLOAD/DIR';
|
||||||
const BASE_URL = 'http://test.com';
|
const BASE_URL = 'http://test.com';
|
||||||
const ARTIFACT_PATH = '/some/path/build.zip';
|
const ARTIFACT_PATH = '/some/path/build.zip';
|
||||||
|
|
||||||
@ -31,6 +29,10 @@ describe('BuildRetriever', () => {
|
|||||||
vcs_revision: 'COMMIT',
|
vcs_revision: 'COMMIT',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
spyOn(console, 'log');
|
||||||
|
spyOn(console, 'warn');
|
||||||
|
spyOn(console, 'error');
|
||||||
|
|
||||||
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
||||||
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
|
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
|
||||||
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
|
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
|
||||||
@ -89,7 +91,6 @@ describe('BuildRetriever', () => {
|
|||||||
let retriever: BuildRetriever;
|
let retriever: BuildRetriever;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(Logger.prototype, 'warn');
|
|
||||||
retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -132,14 +133,11 @@ describe('BuildRetriever', () => {
|
|||||||
|
|
||||||
it('should write the artifact file to disk', async () => {
|
it('should write the artifact file to disk', async () => {
|
||||||
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
|
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
|
||||||
const downloadPath = resolvePath(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`);
|
|
||||||
|
|
||||||
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
|
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
|
||||||
expect(writeFileSpy).toHaveBeenCalledWith(downloadPath, jasmine.any(Buffer), jasmine.any(Function));
|
expect(writeFileSpy)
|
||||||
|
.toHaveBeenCalledWith(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`, jasmine.any(Buffer), jasmine.any(Function));
|
||||||
const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1];
|
const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1];
|
||||||
expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS);
|
expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS);
|
||||||
|
|
||||||
artifactRequest.done();
|
artifactRequest.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as supertest from 'supertest';
|
import * as supertest from 'supertest';
|
||||||
|
import {promisify} from 'util';
|
||||||
import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||||
import {GithubApi} from '../../lib/common/github-api';
|
import {GithubApi} from '../../lib/common/github-api';
|
||||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||||
import {GithubTeams} from '../../lib/common/github-teams';
|
import {GithubTeams} from '../../lib/common/github-teams';
|
||||||
import {Logger} from '../../lib/common/utils';
|
|
||||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||||
import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
|
import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
|
||||||
@ -38,18 +38,15 @@ describe('PreviewServerFactory', () => {
|
|||||||
significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
|
significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
|
||||||
trustedPrLabel: 'trusted: pr-label',
|
trustedPrLabel: 'trusted: pr-label',
|
||||||
};
|
};
|
||||||
let loggerErrorSpy: jasmine.Spy;
|
|
||||||
let loggerInfoSpy: jasmine.Spy;
|
|
||||||
let loggerLogSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
|
const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
|
||||||
PreviewServerFactory.create({...defaultConfig, ...partialConfig});
|
PreviewServerFactory.create({...defaultConfig, ...partialConfig});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
spyOn(console, 'error');
|
||||||
loggerInfoSpy = spyOn(Logger.prototype, 'info');
|
spyOn(console, 'info');
|
||||||
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
spyOn(console, 'log');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create()', () => {
|
describe('create()', () => {
|
||||||
@ -143,10 +140,11 @@ describe('PreviewServerFactory', () => {
|
|||||||
const server = createPreviewServer();
|
const server = createPreviewServer();
|
||||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||||
|
|
||||||
expect(loggerInfoSpy).not.toHaveBeenCalled();
|
expect(console.info).not.toHaveBeenCalled();
|
||||||
|
|
||||||
server.emit('listening');
|
server.emit('listening');
|
||||||
expect(loggerInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...');
|
expect(console.info).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(String), 'PreviewServer: ', 'Up and running (and listening on foo:1337)...');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -243,6 +241,10 @@ describe('PreviewServerFactory', () => {
|
|||||||
let buildCreator: BuildCreator;
|
let buildCreator: BuildCreator;
|
||||||
let agent: supertest.SuperTest<supertest.Test>;
|
let agent: supertest.SuperTest<supertest.Test>;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const promisifyRequest = async (req: supertest.Request) => await promisify(req.end.bind(req))();
|
||||||
|
const verifyRequests = async (reqs: supertest.Request[]) => await Promise.all(reqs.map(promisifyRequest));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
|
const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
|
||||||
defaultConfig.circleCiToken);
|
defaultConfig.circleCiToken);
|
||||||
@ -259,11 +261,10 @@ describe('PreviewServerFactory', () => {
|
|||||||
agent = supertest.agent(middleware);
|
agent = supertest.agent(middleware);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('GET /health-check', () => {
|
describe('GET /health-check', () => {
|
||||||
|
|
||||||
it('should respond with 200', async () => {
|
it('should respond with 200', async () => {
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
agent.get('/health-check').expect(200),
|
agent.get('/health-check').expect(200),
|
||||||
agent.get('/health-check/').expect(200),
|
agent.get('/health-check/').expect(200),
|
||||||
]);
|
]);
|
||||||
@ -271,7 +272,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for non-GET requests', async () => {
|
it('should respond with 404 for non-GET requests', async () => {
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
agent.put('/health-check').expect(404),
|
agent.put('/health-check').expect(404),
|
||||||
agent.post('/health-check').expect(404),
|
agent.post('/health-check').expect(404),
|
||||||
agent.patch('/health-check').expect(404),
|
agent.patch('/health-check').expect(404),
|
||||||
@ -281,7 +282,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 if the path does not match exactly', async () => {
|
it('should respond with 404 if the path does not match exactly', async () => {
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
agent.get('/health-check/foo').expect(404),
|
agent.get('/health-check/foo').expect(404),
|
||||||
agent.get('/health-check-foo').expect(404),
|
agent.get('/health-check-foo').expect(404),
|
||||||
agent.get('/health-checknfoo').expect(404),
|
agent.get('/health-checknfoo').expect(404),
|
||||||
@ -293,104 +294,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('/circle-build', () => {
|
||||||
describe('GET /can-have-public-preview/<pr>', () => {
|
|
||||||
const baseUrl = '/can-have-public-preview';
|
|
||||||
const pr = 777;
|
|
||||||
const url = `${baseUrl}/${pr}`;
|
|
||||||
let bvGetPrIsTrustedSpy: jasmine.Spy;
|
|
||||||
let bvGetSignificantFilesChangedSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
|
|
||||||
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').
|
|
||||||
and.returnValue(Promise.resolve(true));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for non-GET requests', async () => {
|
|
||||||
await Promise.all([
|
|
||||||
agent.put(url).expect(404),
|
|
||||||
agent.post(url).expect(404),
|
|
||||||
agent.patch(url).expect(404),
|
|
||||||
agent.delete(url).expect(404),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 404 if the path does not match exactly', async () => {
|
|
||||||
await Promise.all([
|
|
||||||
agent.get('/can-have-public-preview/42/foo').expect(404),
|
|
||||||
agent.get('/can-have-public-preview-foo/42').expect(404),
|
|
||||||
agent.get('/can-have-public-previewnfoo/42').expect(404),
|
|
||||||
agent.get('/foo/can-have-public-preview/42').expect(404),
|
|
||||||
agent.get('/foo-can-have-public-preview/42').expect(404),
|
|
||||||
agent.get('/fooncan-have-public-preview/42').expect(404),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond appropriately if the PR did not touch any significant files', async () => {
|
|
||||||
bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false));
|
|
||||||
|
|
||||||
const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'};
|
|
||||||
const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`;
|
|
||||||
|
|
||||||
await agent.get(url).expect(200, expectedResponse);
|
|
||||||
|
|
||||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
|
||||||
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
|
|
||||||
bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false));
|
|
||||||
|
|
||||||
const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
|
|
||||||
const expectedLog =
|
|
||||||
`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`;
|
|
||||||
|
|
||||||
await agent.get(url).expect(200, expectedResponse);
|
|
||||||
|
|
||||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond appropriately if the PR can have a preview', async () => {
|
|
||||||
const expectedResponse = {canHavePublicPreview: true, reason: null};
|
|
||||||
const expectedLog = `PR:${pr} - Can have a public preview.`;
|
|
||||||
|
|
||||||
await agent.get(url).expect(200, expectedResponse);
|
|
||||||
|
|
||||||
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
|
|
||||||
bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error'));
|
|
||||||
|
|
||||||
await agent.get(url).expect(500, 'getSignificantFilesChanged error');
|
|
||||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should respond with error if `getPrIsTrusted()` fails', async () => {
|
|
||||||
const error = new Error('getPrIsTrusted error');
|
|
||||||
bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
|
|
||||||
|
|
||||||
await agent.get(url).expect(500, 'getPrIsTrusted error');
|
|
||||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('POST /circle-build', () => {
|
|
||||||
let getGithubInfoSpy: jasmine.Spy;
|
let getGithubInfoSpy: jasmine.Spy;
|
||||||
let getSignificantFilesChangedSpy: jasmine.Spy;
|
let getSignificantFilesChangedSpy: jasmine.Spy;
|
||||||
let downloadBuildArtifactSpy: jasmine.Spy;
|
let downloadBuildArtifactSpy: jasmine.Spy;
|
||||||
@ -455,7 +359,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||||
expect(getGithubInfoSpy).not.toHaveBeenCalled();
|
expect(getGithubInfoSpy).not.toHaveBeenCalled();
|
||||||
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
|
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
||||||
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
||||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
@ -467,7 +371,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||||
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
||||||
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
|
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
|
||||||
expect(loggerLogSpy).toHaveBeenCalledWith(
|
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
||||||
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
|
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
|
||||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
@ -563,7 +467,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for non-POST requests', async () => {
|
it('should respond with 404 for non-POST requests', async () => {
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
agent.get(url).expect(404),
|
agent.get(url).expect(404),
|
||||||
agent.put(url).expect(404),
|
agent.put(url).expect(404),
|
||||||
agent.patch(url).expect(404),
|
agent.patch(url).expect(404),
|
||||||
@ -578,7 +482,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
const request1 = agent.post(url);
|
const request1 = agent.post(url);
|
||||||
const request2 = agent.post(url).send();
|
const request2 = agent.post(url).send();
|
||||||
|
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
request1.expect(400, responseBody),
|
request1.expect(400, responseBody),
|
||||||
request2.expect(400, responseBody),
|
request2.expect(400, responseBody),
|
||||||
]);
|
]);
|
||||||
@ -591,7 +495,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
const request1 = agent.post(url).send({});
|
const request1 = agent.post(url).send({});
|
||||||
const request2 = agent.post(url).send({number: null});
|
const request2 = agent.post(url).send({number: null});
|
||||||
|
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
request1.expect(400, `${responseBodyPrefix} {}`),
|
request1.expect(400, `${responseBodyPrefix} {}`),
|
||||||
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
||||||
]);
|
]);
|
||||||
@ -599,7 +503,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
|
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
|
||||||
await createRequest(+pr);
|
await promisifyRequest(createRequest(+pr));
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -607,8 +511,9 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should propagate errors from BuildVerifier', async () => {
|
it('should propagate errors from BuildVerifier', async () => {
|
||||||
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
||||||
|
|
||||||
await createRequest(+pr).expect(500, 'Test');
|
const req = createRequest(+pr).expect(500, 'Test');
|
||||||
|
|
||||||
|
await promisifyRequest(req);
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||||
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -617,17 +522,19 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
|
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
|
||||||
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
||||||
|
|
||||||
await createRequest(24);
|
await promisifyRequest(createRequest(24));
|
||||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
|
||||||
|
|
||||||
await createRequest(42);
|
await promisifyRequest(createRequest(42));
|
||||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should propagate errors from BuildCreator', async () => {
|
it('should propagate errors from BuildCreator', async () => {
|
||||||
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
||||||
await createRequest(+pr).expect(500, 'Test');
|
|
||||||
|
const req = createRequest(+pr).expect(500, 'Test');
|
||||||
|
await verifyRequests([req]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -637,7 +544,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
||||||
await Promise.all(reqs);
|
await verifyRequests(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -645,7 +552,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
||||||
await Promise.all(reqs);
|
await verifyRequests(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -653,13 +560,14 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
||||||
await Promise.all(reqs);
|
await verifyRequests(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
|
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
|
||||||
const promises = ['foo', 'notlabeled'].
|
const promises = ['foo', 'notlabeled'].
|
||||||
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200]));
|
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
|
||||||
|
map(promisifyRequest);
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
@ -676,7 +584,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should respond with 404', async () => {
|
it('should respond with 404', async () => {
|
||||||
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
||||||
|
|
||||||
await Promise.all([
|
await verifyRequests([
|
||||||
agent.get('/some/url').expect(404, responseFor('get')),
|
agent.get('/some/url').expect(404, responseFor('get')),
|
||||||
agent.put('/some/url').expect(404, responseFor('put')),
|
agent.put('/some/url').expect(404, responseFor('put')),
|
||||||
agent.post('/some/url').expect(404, responseFor('post')),
|
agent.post('/some/url').expect(404, responseFor('post')),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,6 @@
|
|||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# Set up env variables
|
# Set up env variables
|
||||||
export AIO_CIRCLE_CI_TOKEN=UNUSED_CIRCLE_CI_TOKEN
|
|
||||||
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
|
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
|
||||||
|
|
||||||
# Run the clean-up
|
# Run the clean-up
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
TODO (gkalpak): Add docs. Mention:
|
TODO (gkalpak): Add docs. Mention:
|
||||||
- Testing on CI.
|
- Testing on CI.
|
||||||
Relevant files: `aio/aio-builds-setup/scripts/test.sh`
|
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
||||||
- Deploying from CI.
|
- Deploying from CI.
|
||||||
Relevant files: `.circleci/config.yml`, `scripts/ci/deploy.sh`, `aio/scripts/build-artifacts.sh`,
|
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-to-firebase.sh`
|
||||||
`aio/scripts/deploy-to-firebase.sh`
|
|
||||||
|
@ -34,31 +34,34 @@ container:
|
|||||||
|
|
||||||
|
|
||||||
### On CI (CircleCI)
|
### On CI (CircleCI)
|
||||||
- The CI script builds the angular.io project.
|
- Build job completes successfully.
|
||||||
|
- The CI script checks whether the build job was initiated by a PR against the angular/angular
|
||||||
|
master branch.
|
||||||
|
- The CI script checks whether the PR has touched any files that might affect the angular.io app
|
||||||
|
(currently the `aio/` or `packages/` directories, ignoring spec files).
|
||||||
- The CI script gzips and stores the build artifacts in the CI infrastructure.
|
- The CI script gzips and stores the build artifacts in the CI infrastructure.
|
||||||
- When the build completes, CircleCI triggers a webhook on the preview-server.
|
- When the build completes CircleCI triggers a webhook on the preview-server.
|
||||||
|
|
||||||
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
|
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
|
||||||
|
|
||||||
|
|
||||||
### Hosting build artifacts
|
### Hosting build artifacts
|
||||||
|
|
||||||
- nginx receives the webhook trigger and passes it through to the preview server.
|
- nginx receives the webhook trigger and passes it through to the preview server.
|
||||||
- The preview-server runs several preliminary checks to determine whether the request is valid and
|
|
||||||
whether the corresponding PR can have a (public or non-public) preview (more details can be found
|
|
||||||
[here](overview--security-model.md)).
|
|
||||||
- The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
|
- The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
|
||||||
- The preview-server makes a request to this URL to receive the artifact - failing if the size
|
- The preview-server makes a request to this URL to receive the artifact - failing if the size
|
||||||
exceeds the specified max file size - and stores it in a temporary location.
|
exceeds the specified max file size - and stores it in a temporary location.
|
||||||
- The preview-server runs more checks to determine whether the preview should be publicly accessible
|
- The preview-server runs several checks to determine whether the request should be accepted and
|
||||||
or stored for later verification (more details can be found [here](overview--security-model.md)).
|
whether it should be publicly accessible or stored for later verification (more details can be
|
||||||
|
found [here](overview--security-model.md)).
|
||||||
- The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
|
- The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
|
||||||
builds for the same PR had been previously deployed as non-public and the current build has been
|
builds for the same PR had been previously deployed as non-public and the current build has been
|
||||||
automatically verified, all previous builds are made public as well.
|
automatically verified, all previous builds are made public as well.
|
||||||
If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
|
If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
|
||||||
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
|
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
|
||||||
- The preview-server verifies that it is not trying to overwrite an existing build.
|
- The preview-server verifies that it is not trying to overwrite an existing build.
|
||||||
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the
|
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the first
|
||||||
first few characters of the SHA: `<PR>/<SHA>/`
|
few characters of the SHA: `<PR>/<SHA>/`
|
||||||
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
|
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
|
||||||
number and SHA.)
|
number and SHA.)
|
||||||
- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
|
- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
|
||||||
@ -98,8 +101,8 @@ More info on the possible HTTP status codes and their meaning can be found
|
|||||||
|
|
||||||
### Removing obsolete artifacts
|
### Removing obsolete artifacts
|
||||||
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a
|
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a
|
||||||
clean-up task once a day. The task retrieves all open PRs from GitHub and removes all directories
|
clean-up tasks once a day. The task retrieves all open PRs from GitHub and removes all directories
|
||||||
that do not correspond to an open PR.
|
that do not correspond with an open PR.
|
||||||
|
|
||||||
|
|
||||||
### Health-check
|
### Health-check
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Overview - HTTP Status Codes
|
# Overview - HTTP Status Codes
|
||||||
|
|
||||||
|
|
||||||
This is a list of all the possible HTTP status codes returned by the nginx and preview servers,
|
This is a list of all the possible HTTP status codes returned by the nginx and preview servers, along
|
||||||
along with a brief explanation of what they mean:
|
with a brief explanation of what they mean:
|
||||||
|
|
||||||
|
|
||||||
## `http://*.ngbuilds.io/*`
|
## `http://*.ngbuilds.io/*`
|
||||||
@ -25,23 +25,6 @@ along with a brief explanation of what they mean:
|
|||||||
File not found.
|
File not found.
|
||||||
|
|
||||||
|
|
||||||
## `https://ngbuilds.io/can-have-public-preview/<pr>`
|
|
||||||
|
|
||||||
- **200 (OK)**:
|
|
||||||
Whether the PR can have a public preview (based on its author, label, changed files).
|
|
||||||
_Response type:_ JSON
|
|
||||||
_Response format:_
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
canHavePublicPreview: boolean,
|
|
||||||
reason: string | null,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- **405 (Method Not Allowed)**:
|
|
||||||
Request method other than GET.
|
|
||||||
|
|
||||||
|
|
||||||
## `https://ngbuilds.io/circle-build`
|
## `https://ngbuilds.io/circle-build`
|
||||||
|
|
||||||
- **201 (Created)**:
|
- **201 (Created)**:
|
||||||
|
@ -11,8 +11,8 @@ part of the CI process and serving them publicly.
|
|||||||
|
|
||||||
## Security objectives
|
## Security objectives
|
||||||
|
|
||||||
- **Prevent hosting arbitrary content on our servers.**
|
- **Prevent hosting arbitrary content to on servers.**
|
||||||
Since there is no restriction on who can submit a PR, we cannot allow arbitrary, untrusted PRs'
|
Since there is no restriction on who can submit a PR, we cannot allow arbitrary untrusted PRs'
|
||||||
build artifacts to be hosted.
|
build artifacts to be hosted.
|
||||||
|
|
||||||
- **Prevent overwriting other people's hosted build artifacts.**
|
- **Prevent overwriting other people's hosted build artifacts.**
|
||||||
@ -40,49 +40,40 @@ part of the CI process and serving them publicly.
|
|||||||
### In a nutshell
|
### In a nutshell
|
||||||
The implemented approach can be broken up to the following sub-tasks:
|
The implemented approach can be broken up to the following sub-tasks:
|
||||||
|
|
||||||
1. Receive notification from CircleCI of a completed build.
|
0. Receive notification from CircleCI of a completed build.
|
||||||
2. Verify that the build is valid and can have a preview.
|
1. Verify that the build is valid and download the artifact.
|
||||||
3. Download the build artifact.
|
2. Fetch the PR's metadata, including author and labels.
|
||||||
4. Fetch the PR's metadata, including author and labels.
|
3. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
||||||
5. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
4. If necessary, update the corresponding PR's verification status.
|
||||||
6. If necessary, update the corresponding PR's verification status.
|
5. Deploy the artifacts to the corresponding PR's directory.
|
||||||
7. Deploy the artifacts to the corresponding PR's directory.
|
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
||||||
8. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
|
||||||
during deployment will remain valid until the artifacts are removed).
|
during deployment will remain valid until the artifacts are removed).
|
||||||
9. Prevent hosted preview files from accessing anything outside their directory.
|
7. Prevent hosted preview files from accessing anything outside their directory.
|
||||||
|
|
||||||
|
|
||||||
### Implementation details
|
### Implementation details
|
||||||
This section describes how each of the aforementioned sub-tasks is accomplished:
|
This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||||
|
|
||||||
1. **Receive notification from CircleCI of a completed build**
|
0. **Receive notification from CircleCI of a completed build**
|
||||||
|
|
||||||
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
||||||
The payload contains the number of the build that completed.
|
The payload contains the number of the build that completed.
|
||||||
|
|
||||||
2. **Verify that the build is valid and can have a preview.**
|
1. **Verify that the build is valid and download the artifact.**
|
||||||
|
|
||||||
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
||||||
number and then run a direct query against the CircleCI API to get hold of the real data for
|
number and then run a direct query against the CircleCI API to get hold of the real data for
|
||||||
the given build number.
|
the given build number.
|
||||||
|
|
||||||
We perform a number of preliminary checks:
|
If the build was not successful then we ignore this trigger. Otherwise we check that the
|
||||||
- Was the webhook triggered by the designated CircleCI job (currently `aio_preview`)?
|
associated github organisation and repository are what we expect (e.g. angular/angular).
|
||||||
- Was the build successful?
|
|
||||||
- Are the associated GitHub organisation and repository what we expect (e.g. `angular/angular`)?
|
|
||||||
- Has the PR touched any files that might affect the angular.io app (currently the `aio/` or
|
|
||||||
`packages/` directories, ignoring spec files)?
|
|
||||||
|
|
||||||
If any of the preliminary checks fails, the process is aborted and not preview is generated.
|
Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that
|
||||||
|
|
||||||
3. **Download the build artifact.**
|
|
||||||
|
|
||||||
Next we make another call to the CircleCI API to get a list of the URLs for artifacts of that
|
|
||||||
build. If there is one that matches the configured artifact path, we download the contents of the
|
build. If there is one that matches the configured artifact path, we download the contents of the
|
||||||
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
||||||
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
||||||
|
|
||||||
4. **Fetch the PR's metadata, including author and labels**.
|
2. **Fetch the PR's metadata, including author and labels**.
|
||||||
|
|
||||||
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
||||||
including the author's username and the labels - using the
|
including the author's username and the labels - using the
|
||||||
@ -90,7 +81,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
||||||
[@mary-poppins](https://github.com/mary-poppins)).
|
[@mary-poppins](https://github.com/mary-poppins)).
|
||||||
|
|
||||||
5. **Check whether the PR can be automatically verified as "trusted"**.
|
3. **Check whether the PR can be automatically verified as "trusted"**.
|
||||||
|
|
||||||
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
||||||
and publicly accessible on the preview server. There are two ways to check that:
|
and publicly accessible on the preview server. There are two ways to check that:
|
||||||
@ -102,32 +93,31 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
||||||
Here too, we use the token by @mary-poppins.
|
Here too, we use the token by @mary-poppins.
|
||||||
|
|
||||||
6. **If necessary update the corresponding PR's verification status**.
|
4. **If necessary update the corresponding PR's verification status**.
|
||||||
|
|
||||||
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
||||||
whether it is publicly accessible or not), based on the new verification status. For example, if
|
whether it is publicly accessible or not), based on the new verification status. For example, if
|
||||||
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
||||||
otherwise, the PR (and all the previously downloaded previews) are made public. It works the same
|
otherwise, the PR (and all the previously hosted previews) are made public. It works the same
|
||||||
way if a PR has gone from "trusted" to "not trusted".
|
way if a PR has gone from "trusted" to "not trusted".
|
||||||
|
|
||||||
7. **Deploy the artifacts to the corresponding PR's directory.**
|
5. **Deploy the artifacts to the corresponding PR's directory.**
|
||||||
|
|
||||||
With the preceding steps, we have verified that the build artifacts are valid. Additionally, we
|
With the preceding steps, we have verified that the build artifacts are valid.
|
||||||
have determined whether the PR can be trusted to have its previews publicly accessible or whether
|
Additionally, we have determined whether the PR can be trusted to have its previews
|
||||||
further verification is necessary.
|
publicly accessible or whether further verification is necessary. The artifacts will be stored to
|
||||||
|
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
||||||
|
Essentially, as long as sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to
|
||||||
|
"project" the trust we have in a team's members through the PR to the build artifacts.
|
||||||
|
|
||||||
The artifacts will be stored to the PR's directory, but will not be publicly accessible unless
|
6. **Prevent overwriting previously deployed artifacts**.
|
||||||
the PR has been verified. Essentially, as long as sub-tasks 2, 3, 4 and 5 can be securely
|
|
||||||
accomplished, it is possible to "project" the trust we have in a team's members through the PR to
|
|
||||||
the build artifacts.
|
|
||||||
|
|
||||||
8. **Prevent overwriting previously deployed artifacts**.
|
|
||||||
|
|
||||||
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
||||||
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js Express server) rejects builds that have already been handled.
|
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js
|
||||||
|
Express server) rejects builds that have already been handled.
|
||||||
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
||||||
|
|
||||||
9. **Prevent hosted preview files from accessing anything outside their directory.**
|
7. **Prevent hosted preview files from accessing anything outside their directory.**
|
||||||
|
|
||||||
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
||||||
outside of the directory where the preview files are stored.
|
outside of the directory where the preview files are stored.
|
||||||
@ -140,10 +130,10 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
This means that any secret access keys need only be stored on the preview-server and not on any of
|
This means that any secret access keys need only be stored on the preview-server and not on any of
|
||||||
the CI build infrastructure (e.g. CircleCI).
|
the CI build infrastructure (e.g. CircleCI).
|
||||||
|
|
||||||
- Each trusted PR author has full control over the content that is hosted as a preview for their
|
- Each trusted PR author has full control over the content that is hosted as a preview for their PRs.
|
||||||
PRs. Part of the security model relies on the trustworthiness of these authors.
|
Part of the security model relies on the trustworthiness of these authors.
|
||||||
|
|
||||||
- Adding the specified label on a PR to mark it as trusted, gives the author full control over the
|
- Adding the specified label on a PR to mark it as trusted, gives the author full control over
|
||||||
content that is hosted for the specific PR preview (e.g. by pushing more commits to it). The user
|
the content that is hosted for the specific PR preview (e.g. by pushing more commits to it).
|
||||||
adding the label is responsible for ensuring that this control is not abused and that the PR is
|
The user adding the label is responsible for ensuring that this control is not abused and that
|
||||||
either closed (one way of another) or the access is revoked.
|
the PR is either closed (one way of another) or the access is revoked.
|
||||||
|
@ -8,7 +8,7 @@ Necessary secrets:
|
|||||||
1. `GITHUB_TOKEN`
|
1. `GITHUB_TOKEN`
|
||||||
- Used for:
|
- Used for:
|
||||||
- Retrieving open PRs without rate-limiting.
|
- Retrieving open PRs without rate-limiting.
|
||||||
- Retrieving PR info, such as author, labels, changed files.
|
- Retrieving PR author.
|
||||||
- Retrieving members of the trusted GitHub teams.
|
- Retrieving members of the trusted GitHub teams.
|
||||||
- Posting comments with preview links on PRs.
|
- Posting comments with preview links on PRs.
|
||||||
|
|
||||||
@ -25,9 +25,8 @@ Necessary secrets:
|
|||||||
- Generate new token with the `public_repo` scope.
|
- Generate new token with the `public_repo` scope.
|
||||||
|
|
||||||
2. `CIRCLE_CI_TOKEN`
|
2. `CIRCLE_CI_TOKEN`
|
||||||
- Visit https://circleci.com/gh/angular/angular/edit#api.
|
- Visit https://circleci.com/gh/angular/angular/edit#api
|
||||||
- Create an API token with `Build Artifacts` scope.
|
- Create an API token with `Build Artifacts` scope
|
||||||
|
|
||||||
|
|
||||||
## Save secrets on the VM
|
## Save secrets on the VM
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"src/assets",
|
"src/assets",
|
||||||
"src/generated",
|
"src/generated",
|
||||||
"src/app/search/search-worker.js",
|
"src/app/search/search-worker.js",
|
||||||
|
"src/favicon.ico",
|
||||||
"src/pwa-manifest.json",
|
"src/pwa-manifest.json",
|
||||||
"src/google385281288605d160.html",
|
"src/google385281288605d160.html",
|
||||||
{
|
{
|
||||||
@ -61,8 +62,7 @@
|
|||||||
"src": "src/environments/environment.ts",
|
"src": "src/environments/environment.ts",
|
||||||
"replaceWith": "src/environments/environment.next.ts"
|
"replaceWith": "src/environments/environment.next.ts"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"serviceWorker": true
|
|
||||||
},
|
},
|
||||||
"stable": {
|
"stable": {
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
@ -70,8 +70,7 @@
|
|||||||
"src": "src/environments/environment.ts",
|
"src": "src/environments/environment.ts",
|
||||||
"replaceWith": "src/environments/environment.stable.ts"
|
"replaceWith": "src/environments/environment.stable.ts"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"serviceWorker": true
|
|
||||||
},
|
},
|
||||||
"archive": {
|
"archive": {
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
@ -79,8 +78,7 @@
|
|||||||
"src": "src/environments/environment.ts",
|
"src": "src/environments/environment.ts",
|
||||||
"replaceWith": "src/environments/environment.archive.ts"
|
"replaceWith": "src/environments/environment.archive.ts"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"serviceWorker": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -125,6 +123,7 @@
|
|||||||
"src/assets",
|
"src/assets",
|
||||||
"src/generated",
|
"src/generated",
|
||||||
"src/app/search/search-worker.js",
|
"src/app/search/search-worker.js",
|
||||||
|
"src/favicon.ico",
|
||||||
"src/pwa-manifest.json",
|
"src/pwa-manifest.json",
|
||||||
"src/google385281288605d160.html",
|
"src/google385281288605d160.html",
|
||||||
{
|
{
|
||||||
|
3
aio/content/cli-src/.gitignore
vendored
3
aio/content/cli-src/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
/node_modules
|
|
||||||
package.json
|
|
||||||
yarn.lock
|
|
@ -1,100 +0,0 @@
|
|||||||
<h1 class="no-toc">CLI Command Reference</h1>
|
|
||||||
|
|
||||||
The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications. You can use the tool directly in a command shell, or indirectly through an interactive UI such as [Angular Console](https://angularconsole.com).
|
|
||||||
|
|
||||||
## Installing Angular CLI
|
|
||||||
|
|
||||||
Major versions of Angular CLI follow the supported major version of Angular, but minor versions can be released separately.
|
|
||||||
|
|
||||||
Install the CLI using the `npm` package manager:
|
|
||||||
<code-example format="." language="bash">
|
|
||||||
npm install -g @angular/cli
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
For details about changes between versions, and information about updating from previous releases,
|
|
||||||
see the Releases tab on GitHub: https://github.com/angular/angular-cli/releases
|
|
||||||
|
|
||||||
## Basic workflow
|
|
||||||
|
|
||||||
Invoke the tool on the command line through the `ng` executable.
|
|
||||||
Online help is available on the command line.
|
|
||||||
Enter the following to list commands or options for a given command (such as [generate](cli/generate)) with a short description.
|
|
||||||
|
|
||||||
<code-example format="." language="bash">
|
|
||||||
ng help
|
|
||||||
ng generate --help
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
To create, build, and serve a new, basic Angular project on a development server, go to the parent directory of your new workspace use the following commands:
|
|
||||||
|
|
||||||
<code-example format="." language="bash">
|
|
||||||
ng new my-first-project
|
|
||||||
cd my-first-project
|
|
||||||
ng serve
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
In your browser, open http://localhost:4200/ to see the new app run.
|
|
||||||
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
|
|
||||||
|
|
||||||
## Workspaces and project files
|
|
||||||
|
|
||||||
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
|
|
||||||
A workspace can contain multiple apps and libraries.
|
|
||||||
The initial app created by the [ng new](cli/new) command is at the top level of the workspace.
|
|
||||||
When you generate an additional app or library in a workspace, it goes into a `projects/` subfolder.
|
|
||||||
|
|
||||||
A newly generated app contains the source files for a root module, with a root component and template.
|
|
||||||
Each app has a `src` folder that contains the logic, data, and assets.
|
|
||||||
|
|
||||||
You can edit the generated files directly, or add to and modify them using CLI commands.
|
|
||||||
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
|
|
||||||
Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on apps and libraries, must be executed from within a workspace or project folder.
|
|
||||||
|
|
||||||
* See more about the [Workspace file structure](guide/file-structure).
|
|
||||||
|
|
||||||
### Workspace and project configuration
|
|
||||||
|
|
||||||
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
|
|
||||||
This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets.
|
|
||||||
|
|
||||||
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
|
|
||||||
Note that option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands can use either camelCase or dash-case.
|
|
||||||
|
|
||||||
* See more about [Workspace Configuration](guide/workspace-config).
|
|
||||||
* See the [complete schema](https://github.com/angular/angular-cli/wiki/angular-workspace) for `angular.json`.
|
|
||||||
|
|
||||||
## CLI command-language syntax
|
|
||||||
|
|
||||||
Command syntax is shown as follows:
|
|
||||||
|
|
||||||
`ng` *commandNameOrAlias* *requiredArg* [*optionalArg*] `[options]`
|
|
||||||
|
|
||||||
* Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command.
|
|
||||||
|
|
||||||
* Option names are prefixed with a double dash (--).
|
|
||||||
Option aliases are prefixed with a single dash (-).
|
|
||||||
Arguments are not prefixed.
|
|
||||||
For example: `ng build my-app -c production`
|
|
||||||
|
|
||||||
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
|
|
||||||
|
|
||||||
* Argument and option names can be given in either
|
|
||||||
[camelCase or dash-case](guide/glossary#case-types).
|
|
||||||
`--myOptionName` is equivalent to `--my-option-name`.
|
|
||||||
|
|
||||||
### Boolean and enumerated options
|
|
||||||
|
|
||||||
Boolean options have two forms: `--thisOption` sets the flag, `--noThisOption` clears it.
|
|
||||||
If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.
|
|
||||||
|
|
||||||
Allowed values are given with each enumerated option description, with the default value in **bold**.
|
|
||||||
|
|
||||||
### Relative paths
|
|
||||||
|
|
||||||
Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root.
|
|
||||||
|
|
||||||
### Schematics
|
|
||||||
|
|
||||||
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
|
|
||||||
In addition to any general options, each artifact or library defines its own options in a *schematic*.
|
|
||||||
Schematic options are supplied to the command in the same format as immediate command options.
|
|
@ -1,6 +1,6 @@
|
|||||||
'use strict'; // necessary for es6 output in node
|
'use strict'; // necessary for es6 output in node
|
||||||
|
|
||||||
import { browser, ExpectedConditions as EC } from 'protractor';
|
import { browser } from 'protractor';
|
||||||
import { logging } from 'selenium-webdriver';
|
import { logging } from 'selenium-webdriver';
|
||||||
import * as openClose from './open-close.po';
|
import * as openClose from './open-close.po';
|
||||||
import * as statusSlider from './status-slider.po';
|
import * as statusSlider from './status-slider.po';
|
||||||
@ -25,8 +25,6 @@ describe('Animation Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Open/Close Component', () => {
|
describe('Open/Close Component', () => {
|
||||||
const closedHeight = '100px';
|
|
||||||
const openHeight = '200px';
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await openCloseHref.click();
|
await openCloseHref.click();
|
||||||
@ -34,37 +32,37 @@ describe('Animation Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be open', async () => {
|
it('should be open', async () => {
|
||||||
|
let text = await openClose.getComponentText();
|
||||||
const toggleButton = openClose.getToggleButton();
|
const toggleButton = openClose.getToggleButton();
|
||||||
const container = openClose.getComponentContainer();
|
const container = openClose.getComponentContainer();
|
||||||
let text = await container.getText();
|
|
||||||
|
|
||||||
if (text.includes('Closed')) {
|
if (text.includes('Closed')) {
|
||||||
await toggleButton.click();
|
await toggleButton.click();
|
||||||
await browser.wait(async () => await container.getCssValue('height') === openHeight, 2000);
|
sleepFor();
|
||||||
}
|
}
|
||||||
|
|
||||||
text = await container.getText();
|
text = await openClose.getComponentText();
|
||||||
const containerHeight = await container.getCssValue('height');
|
const containerHeight = await container.getCssValue('height');
|
||||||
|
|
||||||
expect(text).toContain('The box is now Open!');
|
expect(text).toContain('The box is now Open!');
|
||||||
expect(containerHeight).toBe(openHeight);
|
expect(containerHeight).toBe('200px');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be closed', async () => {
|
it('should be closed', async () => {
|
||||||
|
let text = await openClose.getComponentText();
|
||||||
const toggleButton = openClose.getToggleButton();
|
const toggleButton = openClose.getToggleButton();
|
||||||
const container = openClose.getComponentContainer();
|
const container = openClose.getComponentContainer();
|
||||||
let text = await container.getText();
|
|
||||||
|
|
||||||
if (text.includes('Open')) {
|
if (text.includes('Open')) {
|
||||||
await toggleButton.click();
|
await toggleButton.click();
|
||||||
await browser.wait(async () => await container.getCssValue('height') === closedHeight, 2000);
|
sleepFor();
|
||||||
}
|
}
|
||||||
|
|
||||||
text = await container.getText();
|
text = await openClose.getComponentText();
|
||||||
const containerHeight = await container.getCssValue('height');
|
const containerHeight = await container.getCssValue('height');
|
||||||
|
|
||||||
expect(text).toContain('The box is now Closed!');
|
expect(text).toContain('The box is now Closed!');
|
||||||
expect(containerHeight).toBe(closedHeight);
|
expect(containerHeight).toBe('100px');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log animation events', async () => {
|
it('should log animation events', async () => {
|
||||||
@ -74,7 +72,8 @@ describe('Animation Tests', () => {
|
|||||||
await toggleButton.click();
|
await toggleButton.click();
|
||||||
|
|
||||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||||
const animationMessages = logs.filter(({ message }) => message.includes('Animation'));
|
|
||||||
|
const animationMessages = logs.filter(({ message }) => message.indexOf('Animation') !== -1 ? true : false);
|
||||||
|
|
||||||
expect(animationMessages.length).toBeGreaterThan(0);
|
expect(animationMessages.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
@ -90,16 +89,16 @@ describe('Animation Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be inactive with an orange background', async () => {
|
it('should be inactive with an orange background', async () => {
|
||||||
|
let text = await statusSlider.getComponentText();
|
||||||
const toggleButton = statusSlider.getToggleButton();
|
const toggleButton = statusSlider.getToggleButton();
|
||||||
const container = statusSlider.getComponentContainer();
|
const container = statusSlider.getComponentContainer();
|
||||||
let text = await container.getText();
|
|
||||||
|
|
||||||
if (text === 'Active') {
|
if (text === 'Active') {
|
||||||
await toggleButton.click();
|
await toggleButton.click();
|
||||||
await browser.wait(async () => await container.getCssValue('backgroundColor') === inactiveColor, 2000);
|
sleepFor(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
text = await container.getText();
|
text = await statusSlider.getComponentText();
|
||||||
const bgColor = await container.getCssValue('backgroundColor');
|
const bgColor = await container.getCssValue('backgroundColor');
|
||||||
|
|
||||||
expect(text).toBe('Inactive');
|
expect(text).toBe('Inactive');
|
||||||
@ -107,16 +106,16 @@ describe('Animation Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be active with a blue background', async () => {
|
it('should be active with a blue background', async () => {
|
||||||
|
let text = await statusSlider.getComponentText();
|
||||||
const toggleButton = statusSlider.getToggleButton();
|
const toggleButton = statusSlider.getToggleButton();
|
||||||
const container = statusSlider.getComponentContainer();
|
const container = statusSlider.getComponentContainer();
|
||||||
let text = await container.getText();
|
|
||||||
|
|
||||||
if (text === 'Inactive') {
|
if (text === 'Inactive') {
|
||||||
await toggleButton.click();
|
await toggleButton.click();
|
||||||
await browser.wait(async () => await container.getCssValue('backgroundColor') === activeColor, 2000);
|
sleepFor(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
text = await container.getText();
|
text = await statusSlider.getComponentText();
|
||||||
const bgColor = await container.getCssValue('backgroundColor');
|
const bgColor = await container.getCssValue('backgroundColor');
|
||||||
|
|
||||||
expect(text).toBe('Active');
|
expect(text).toBe('Active');
|
||||||
@ -164,7 +163,10 @@ describe('Animation Tests', () => {
|
|||||||
const hero = heroesList.get(0);
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
await hero.click();
|
await hero.click();
|
||||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
await sleepFor(100);
|
||||||
|
const newTotal = await heroesList.count();
|
||||||
|
|
||||||
|
expect(newTotal).toBeLessThan(total);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -188,7 +190,10 @@ describe('Animation Tests', () => {
|
|||||||
const hero = heroesList.get(0);
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
await hero.click();
|
await hero.click();
|
||||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
await sleepFor(250);
|
||||||
|
const newTotal = await heroesList.count();
|
||||||
|
|
||||||
|
expect(newTotal).toBeLessThan(total);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -208,14 +213,14 @@ describe('Animation Tests', () => {
|
|||||||
it('should filter down the list when a search is performed', async () => {
|
it('should filter down the list when a search is performed', async () => {
|
||||||
const heroesList = filterStagger.getHeroesList();
|
const heroesList = filterStagger.getHeroesList();
|
||||||
const total = await heroesList.count();
|
const total = await heroesList.count();
|
||||||
|
|
||||||
const formInput = filterStagger.getFormInput();
|
const formInput = filterStagger.getFormInput();
|
||||||
|
|
||||||
await formInput.sendKeys('Mag');
|
await formInput.sendKeys('Mag');
|
||||||
|
await sleepFor(500);
|
||||||
await browser.wait(async () => await heroesList.count() === 2, 2000);
|
|
||||||
|
|
||||||
const newTotal = await heroesList.count();
|
const newTotal = await heroesList.count();
|
||||||
|
|
||||||
expect(newTotal).toBeLessThan(total);
|
expect(newTotal).toBeLessThan(total);
|
||||||
|
expect(newTotal).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,7 +248,10 @@ describe('Animation Tests', () => {
|
|||||||
const hero = heroesList.get(0);
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
await hero.click();
|
await hero.click();
|
||||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
await sleepFor(300);
|
||||||
|
const newTotal = await heroesList.count();
|
||||||
|
|
||||||
|
expect(newTotal).toBeLessThan(total);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,3 +23,11 @@ export function getComponentContainer() {
|
|||||||
const findContainer = () => by.css('div');
|
const findContainer = () => by.css('div');
|
||||||
return locate(getComponent(), findContainer());
|
return locate(getComponent(), findContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getComponentText() {
|
||||||
|
const findContainerText = () => by.css('div');
|
||||||
|
const contents = locate(getComponent(), findContainerText());
|
||||||
|
const componentText = await contents.getText();
|
||||||
|
|
||||||
|
return componentText;
|
||||||
|
}
|
||||||
|
@ -18,3 +18,11 @@ export function getComponentContainer() {
|
|||||||
const findContainer = () => by.css('div');
|
const findContainer = () => by.css('div');
|
||||||
return locate(getComponent(), findContainer());
|
return locate(getComponent(), findContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getComponentText() {
|
||||||
|
const findContainerText = () => by.css('div');
|
||||||
|
const contents = locate(getComponent(), findContainerText());
|
||||||
|
const componentText = await contents.getText();
|
||||||
|
|
||||||
|
return componentText;
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
// #enddocregion import
|
// #enddocregion import
|
||||||
|
|
||||||
// #docregion metadata, component
|
// #docregion metadata
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
@ -13,4 +13,4 @@ import { Component } from '@angular/core';
|
|||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'My First Angular App!';
|
title = 'My First Angular App!';
|
||||||
}
|
}
|
||||||
// #enddocregion title, class, component
|
// #enddocregion title, class
|
||||||
|
@ -9,7 +9,7 @@ import { AdComponent } from './ad.component';
|
|||||||
selector: 'app-ad-banner',
|
selector: 'app-ad-banner',
|
||||||
// #docregion ad-host
|
// #docregion ad-host
|
||||||
template: `
|
template: `
|
||||||
<div class="ad-banner-example">
|
<div class="ad-banner">
|
||||||
<h3>Advertisements</h3>
|
<h3>Advertisements</h3>
|
||||||
<ng-template ad-host></ng-template>
|
<ng-template ad-host></ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,6 @@
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ad-banner-example {
|
.ad-banner {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
@ -1,23 +1,12 @@
|
|||||||
'use strict'; // necessary for es6 output in node
|
'use strict'; // necessary for es6 output in node
|
||||||
|
|
||||||
import { browser, by, element, ElementFinder, ExpectedConditions as EC } from 'protractor';
|
import { browser, by, element } from 'protractor';
|
||||||
|
|
||||||
/* tslint:disable:quotemark */
|
/* tslint:disable:quotemark */
|
||||||
describe('Elements', () => {
|
describe('Elements', () => {
|
||||||
const messageInput = element(by.css('input'));
|
const messageInput = element(by.css('input'));
|
||||||
const popupButtons = element.all(by.css('button'));
|
const popupButtons = element.all(by.css('button'));
|
||||||
|
|
||||||
// Helpers
|
|
||||||
const click = (elem: ElementFinder) => {
|
|
||||||
// Waiting for the element to be clickable, makes the tests less flaky.
|
|
||||||
browser.wait(EC.elementToBeClickable(elem), 5000);
|
|
||||||
elem.click();
|
|
||||||
};
|
|
||||||
const waitForText = (elem: ElementFinder) => {
|
|
||||||
// Waiting for the element to have some text, makes the tests less flaky.
|
|
||||||
browser.wait(async () => /\S/.test(await elem.getText()), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => browser.get(''));
|
beforeEach(() => browser.get(''));
|
||||||
|
|
||||||
describe('popup component', () => {
|
describe('popup component', () => {
|
||||||
@ -28,7 +17,7 @@ describe('Elements', () => {
|
|||||||
it('should be displayed on button click', () => {
|
it('should be displayed on button click', () => {
|
||||||
expect(popupComponent.isPresent()).toBe(false);
|
expect(popupComponent.isPresent()).toBe(false);
|
||||||
|
|
||||||
click(popupComponentButton);
|
popupComponentButton.click();
|
||||||
expect(popupComponent.isPresent()).toBe(true);
|
expect(popupComponent.isPresent()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,9 +25,7 @@ describe('Elements', () => {
|
|||||||
messageInput.clear();
|
messageInput.clear();
|
||||||
messageInput.sendKeys('Angular rocks!');
|
messageInput.sendKeys('Angular rocks!');
|
||||||
|
|
||||||
click(popupComponentButton);
|
popupComponentButton.click();
|
||||||
waitForText(popupComponent);
|
|
||||||
|
|
||||||
expect(popupComponent.getText()).toContain('Popup: Angular rocks!');
|
expect(popupComponent.getText()).toContain('Popup: Angular rocks!');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,7 +33,7 @@ describe('Elements', () => {
|
|||||||
popupComponentButton.click();
|
popupComponentButton.click();
|
||||||
expect(popupComponent.isPresent()).toBe(true);
|
expect(popupComponent.isPresent()).toBe(true);
|
||||||
|
|
||||||
click(closeButton);
|
closeButton.click();
|
||||||
expect(popupComponent.isPresent()).toBe(false);
|
expect(popupComponent.isPresent()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -59,7 +46,7 @@ describe('Elements', () => {
|
|||||||
it('should be displayed on button click', () => {
|
it('should be displayed on button click', () => {
|
||||||
expect(popupElement.isPresent()).toBe(false);
|
expect(popupElement.isPresent()).toBe(false);
|
||||||
|
|
||||||
click(popupElementButton);
|
popupElementButton.click();
|
||||||
expect(popupElement.isPresent()).toBe(true);
|
expect(popupElement.isPresent()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,9 +54,7 @@ describe('Elements', () => {
|
|||||||
messageInput.clear();
|
messageInput.clear();
|
||||||
messageInput.sendKeys('Angular rocks!');
|
messageInput.sendKeys('Angular rocks!');
|
||||||
|
|
||||||
click(popupElementButton);
|
popupElementButton.click();
|
||||||
waitForText(popupElement);
|
|
||||||
|
|
||||||
expect(popupElement.getText()).toContain('Popup: Angular rocks!');
|
expect(popupElement.getText()).toContain('Popup: Angular rocks!');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +62,7 @@ describe('Elements', () => {
|
|||||||
popupElementButton.click();
|
popupElementButton.click();
|
||||||
expect(popupElement.isPresent()).toBe(true);
|
expect(popupElement.isPresent()).toBe(true);
|
||||||
|
|
||||||
click(closeButton);
|
closeButton.click();
|
||||||
expect(popupElement.isPresent()).toBe(false);
|
expect(popupElement.isPresent()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { browser, element, by } from 'protractor';
|
|
||||||
|
|
||||||
describe('Forms Overview Tests', function () {
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
browser.get('');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
<!--The content below is only a placeholder and can be replaced.-->
|
|
||||||
<h1>Forms Overview</h1>
|
|
||||||
|
|
||||||
<h2>Reactive</h2>
|
|
||||||
|
|
||||||
<app-reactive-favorite-color></app-reactive-favorite-color>
|
|
||||||
|
|
||||||
<h2>Template-Driven</h2>
|
|
||||||
|
|
||||||
<app-template-favorite-color></app-template-favorite-color>
|
|
@ -1,31 +0,0 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { TemplateModule } from './template/template.module';
|
|
||||||
import { ReactiveModule } from './reactive/reactive.module';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [ReactiveModule, TemplateModule],
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should create the app', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should render title in a h1 tag', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('Forms Overview');
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-root',
|
|
||||||
templateUrl: './app.component.html',
|
|
||||||
styleUrls: ['./app.component.css']
|
|
||||||
})
|
|
||||||
export class AppComponent {
|
|
||||||
title = 'forms-intro';
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { ReactiveModule } from './reactive/reactive.module';
|
|
||||||
import { TemplateModule } from './template/template.module';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
ReactiveModule,
|
|
||||||
TemplateModule
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule { }
|
|
@ -1,50 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
import { FavoriteColorComponent } from './favorite-color.component';
|
|
||||||
import { createNewEvent } from '../../shared/utils';
|
|
||||||
|
|
||||||
describe('Favorite Color Component', () => {
|
|
||||||
let component: FavoriteColorComponent;
|
|
||||||
let fixture: ComponentFixture<FavoriteColorComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [ ReactiveFormsModule ],
|
|
||||||
declarations: [ FavoriteColorComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(FavoriteColorComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
// #docregion view-to-model
|
|
||||||
it('should update the value of the input field', () => {
|
|
||||||
const input = fixture.nativeElement.querySelector('input');
|
|
||||||
const event = createNewEvent('input');
|
|
||||||
|
|
||||||
input.value = 'Red';
|
|
||||||
input.dispatchEvent(event);
|
|
||||||
|
|
||||||
expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red');
|
|
||||||
});
|
|
||||||
// #enddocregion view-to-model
|
|
||||||
|
|
||||||
// #docregion model-to-view
|
|
||||||
it('should update the value in the control', () => {
|
|
||||||
component.favoriteColorControl.setValue('Blue');
|
|
||||||
|
|
||||||
const input = fixture.nativeElement.querySelector('input');
|
|
||||||
|
|
||||||
expect(input.value).toBe('Blue');
|
|
||||||
});
|
|
||||||
// #enddocregion model-to-view
|
|
||||||
});
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormControl } from '@angular/forms';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-reactive-favorite-color',
|
|
||||||
template: `
|
|
||||||
Favorite Color: <input type="text" [formControl]="favoriteColorControl">
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class FavoriteColorComponent {
|
|
||||||
favoriteColorControl = new FormControl('');
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { ReactiveModule } from './reactive.module';
|
|
||||||
|
|
||||||
describe('ReactiveModule', () => {
|
|
||||||
let reactiveModule: ReactiveModule;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
reactiveModule = new ReactiveModule();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(reactiveModule).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,14 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { FavoriteColorComponent } from './favorite-color/favorite-color.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
|
||||||
declarations: [FavoriteColorComponent],
|
|
||||||
exports: [FavoriteColorComponent],
|
|
||||||
})
|
|
||||||
export class ReactiveModule { }
|
|
@ -1,5 +0,0 @@
|
|||||||
export function createNewEvent(eventName: string, bubbles = false, cancelable = false) {
|
|
||||||
let evt = document.createEvent('CustomEvent');
|
|
||||||
evt.initCustomEvent(eventName, bubbles, cancelable, null);
|
|
||||||
return evt;
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
import { FavoriteColorComponent } from './favorite-color.component';
|
|
||||||
import { createNewEvent } from '../../shared/utils';
|
|
||||||
|
|
||||||
describe('FavoriteColorComponent', () => {
|
|
||||||
let component: FavoriteColorComponent;
|
|
||||||
let fixture: ComponentFixture<FavoriteColorComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [ FormsModule ],
|
|
||||||
declarations: [ FavoriteColorComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(FavoriteColorComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
// #docregion model-to-view
|
|
||||||
it('should update the favorite color on the input field', fakeAsync(() => {
|
|
||||||
component.favoriteColor = 'Blue';
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
tick();
|
|
||||||
|
|
||||||
const input = fixture.nativeElement.querySelector('input');
|
|
||||||
|
|
||||||
expect(input.value).toBe('Blue');
|
|
||||||
}));
|
|
||||||
// #enddocregion model-to-view
|
|
||||||
|
|
||||||
// #docregion view-to-model
|
|
||||||
it('should update the favorite color in the component', fakeAsync(() => {
|
|
||||||
const input = fixture.nativeElement.querySelector('input');
|
|
||||||
const event = createNewEvent('input');
|
|
||||||
|
|
||||||
input.value = 'Red';
|
|
||||||
input.dispatchEvent(event);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.favoriteColor).toEqual('Red');
|
|
||||||
}));
|
|
||||||
// #enddocregion view-to-model
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-template-favorite-color',
|
|
||||||
template: `
|
|
||||||
Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class FavoriteColorComponent {
|
|
||||||
favoriteColor = '';
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { TemplateModule } from './template.module';
|
|
||||||
|
|
||||||
describe('TemplateModule', () => {
|
|
||||||
let templateModule: TemplateModule;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
templateModule = new TemplateModule();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(templateModule).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,14 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { FavoriteColorComponent } from './favorite-color/favorite-color.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule
|
|
||||||
],
|
|
||||||
declarations: [FavoriteColorComponent],
|
|
||||||
exports: [FavoriteColorComponent]
|
|
||||||
})
|
|
||||||
export class TemplateModule { }
|
|
@ -1,14 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Forms Overview</title>
|
|
||||||
<base href="/">
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||||||
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)
|
|
||||||
.catch(err => console.log(err));
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "Forms Overview",
|
|
||||||
"files":[
|
|
||||||
"!**/*.d.ts",
|
|
||||||
"!**/*.js"
|
|
||||||
]
|
|
||||||
}
|
|
@ -72,15 +72,15 @@
|
|||||||
<h2>You submitted the following:</h2>
|
<h2>You submitted the following:</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3">Name</div>
|
<div class="col-xs-3">Name</div>
|
||||||
<div class="col-xs-9">{{ model.name }}</div>
|
<div class="col-xs-9 pull-left">{{ model.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3">Alter Ego</div>
|
<div class="col-xs-3">Alter Ego</div>
|
||||||
<div class="col-xs-9">{{ model.alterEgo }}</div>
|
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3">Power</div>
|
<div class="col-xs-3">Power</div>
|
||||||
<div class="col-xs-9">{{ model.power }}</div>
|
<div class="col-xs-9 pull-left">{{ model.power }}</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<button class="btn btn-primary" (click)="submitted=false">Edit</button>
|
<button class="btn btn-primary" (click)="submitted=false">Edit</button>
|
||||||
|
@ -8,11 +8,11 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'customers',
|
path: 'customers',
|
||||||
loadChildren: './customers/customers.module#CustomersModule'
|
loadChildren: 'app/customers/customers.module#CustomersModule'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'orders',
|
path: 'orders',
|
||||||
loadChildren: './orders/orders.module#OrdersModule'
|
loadChildren: 'app/orders/orders.module#OrdersModule'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
@ -5,8 +5,8 @@ import { ContactModule } from './contact/contact.module.3';
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||||
{ path: 'heroes', loadChildren: './hero/hero.module.3#HeroModule' }
|
{ path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -8,8 +8,8 @@ import { ContactModule } from './contact/contact.module';
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||||
// #docregion lazy-routes
|
// #docregion lazy-routes
|
||||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||||
{ path: 'heroes', loadChildren: './hero/hero.module#HeroModule' }
|
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
|
||||||
// #enddocregion lazy-routes
|
// #enddocregion lazy-routes
|
||||||
];
|
];
|
||||||
// #enddocregion routes
|
// #enddocregion routes
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user