diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 000000000000..8691a8f11929 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "docs-angularjs-org-9p2" + } +} diff --git a/.gitignore b/.gitignore index e897180b89d1..42c5e13b4421 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ performance/temp*.html *~ *.swp angular.js.tmproj -/node_modules/ +node_modules/ bower_components/ angular.xcodeproj .idea diff --git a/.travis.yml b/.travis.yml index 478de09a5484..617083342a15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ cache: branches: except: - - /^g3_.*$/ + - "/^g3_.*$/" env: matrix: @@ -20,14 +20,15 @@ env: - JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs - JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs global: - - CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler + # node 4 likes the G++ v4.8 compiler + # see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements + - CXX=g++-4.8 - SAUCE_USERNAME=angular-ci - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - LOGS_DIR=/tmp/angular-build/logs - BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready + - secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks= -# node 4 likes the G++ v4.8 compiler -# see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements addons: apt: sources: @@ -37,20 +38,61 @@ addons: before_script: - du -sh ./node_modules ./bower_components/ || true - - ./scripts/travis/before_build.sh - + - "./scripts/travis/before_build.sh" script: - - ./scripts/travis/build.sh + - "./scripts/travis/build.sh" after_script: - - ./scripts/travis/tear_down_browser_provider.sh - - ./scripts/travis/print_logs.sh + - "./scripts/travis/tear_down_browser_provider.sh" + - "./scripts/travis/print_logs.sh" notifications: webhooks: urls: - https://webhooks.gitter.im/e/d2120f3f2bb39a4531b2 - http://104.197.9.155:8484/hubot/travis/activity #hubot-server - on_success: always # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: always # default: false + on_success: always # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: always # default: false + +jobs: + include: + - stage: deploy + env: + - JOB=deploy + before_script: skip + script: + - "./scripts/travis/build.sh" + # Work around the 10min Travis timeout so the code.angularjs firebase+gcs code deploy can complete + before_deploy: | + function keep_alive() { + while true; do + echo -en "\a" + sleep 5 + done + } + keep_alive & + deploy: + - provider: firebase + skip_cleanup: true + token: + secure: $FIREBASE_TOKEN + on: + repo: angular/angular.js + all_branches: true + # deploy a new docs version when the commit is tagged on the "latest" npm version + condition: $TRAVIS_TAG != '' && $( jq ".distTag" "package.json" | tr -d "\"[:space:]" ) = latest + - provider: gcs + skip_cleanup: true + access_key_id: GOOGLDB7W2J3LFHICF3R + secret_access_key: + secure: tHIFdSq55qkyZf9zT/3+VkhUrTvOTMuswxXU3KyWaBrSieZqG0UnUDyNm+n3lSfX95zEl/+rJAWbfvhVSxZi13ndOtvRF+MdI1cvow2JynP0aDSiPffEvVrZOmihD6mt2SlMfhskr5FTduQ69kZG6DfLcve1PPDaIwnbOv3phb8= + bucket: code-angularjs-org-338b8.appspot.com + local-dir: upload + detect_encoding: true # detects gzip compression + on: + repo: angular/angular.js + all_branches: true + # upload the build when the commit is tagged or the branch is "master" + condition: $TRAVIS_TAG != '' || ($TRAVIS_PULL_REQUEST = false && $TRAVIS_BRANCH = master) + diff --git a/Gruntfile.js b/Gruntfile.js index 5f602bed8fd7..8de52bca501f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -49,7 +49,6 @@ if (!process.env.TRAVIS && !process.env.JENKINS_HOME) { } } - module.exports = function(grunt) { // this loads all the node_modules that start with `grunt-` as plugins @@ -64,6 +63,8 @@ module.exports = function(grunt) { NG_VERSION.cdn = versionInfo.cdnVersion; var dist = 'angular-' + NG_VERSION.full; + var deployVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.full; + if (versionInfo.cdnVersion == null) { throw new Error('Unable to read CDN version, are you offline or has the CDN not been properly pushed?\n' + 'Perhaps you want to set the NG1_BUILD_NO_REMOTE_VERSION_REQUESTS environment variable?'); @@ -324,6 +325,15 @@ module.exports = function(grunt) { expand: true, dot: true, dest: dist + '/' + }, + firebaseCodeDeploy: { + options: { + mode: 'gzip' + }, + src: ['**'], + cwd: 'build', + expand: true, + dest: 'upload/' + deployVersion + '/' } }, @@ -418,7 +428,7 @@ module.exports = function(grunt) { 'write', 'docs', 'copy', - 'compress' + 'compress:build' ]); grunt.registerTask('ci-checks', [ 'ddescribe-iit', diff --git a/firebase.json b/firebase.json new file mode 100644 index 000000000000..3427962f39bd --- /dev/null +++ b/firebase.json @@ -0,0 +1,24 @@ +{ + "hosting": { + "public": "build/docs", + "ignore": [ + "/index.html", + "/index-debug.html", + "/index-jquery.html" + ], + "rewrites": [ + { + "source": "/", + "destination": "/index-production.html" + }, + { + "source": "/index.html", + "destination": "/index-production.html" + }, + { + "source": "**/*!(.jpg|.jpeg|.gif|.png|.html|.js|.json|.css|.svg|.ttf|.woff|.woff2|.eot)", + "destination": "/index-production.html" + } + ] + } +} \ No newline at end of file diff --git a/readme.firebase.docs.md b/readme.firebase.docs.md new file mode 100644 index 000000000000..d73e9a74f5d2 --- /dev/null +++ b/readme.firebase.docs.md @@ -0,0 +1,10 @@ +Firebase for docs.angularjs.org +=============================== + +The docs are deployed to Google Firebase hosting via Travis deployment config, which expects +firebase.json and .firebaserc in the repository root. + +See travis.yml for the complete deployment config. + +See /scripts/code.angularjs.org-firebase/readme.firebase.code.md for the firebase deployment to +code.angularjs.org \ No newline at end of file diff --git a/scripts/code.angularjs.org-firebase/.eslintrc.json b/scripts/code.angularjs.org-firebase/.eslintrc.json new file mode 100644 index 000000000000..22abc7fb2b7a --- /dev/null +++ b/scripts/code.angularjs.org-firebase/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "es6": true + } +} diff --git a/scripts/code.angularjs.org-firebase/.firebaserc b/scripts/code.angularjs.org-firebase/.firebaserc new file mode 100644 index 000000000000..5ae9ae1e0f91 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "code-angularjs-org-338b8" + } +} diff --git a/scripts/code.angularjs.org-firebase/firebase.json b/scripts/code.angularjs.org-firebase/firebase.json new file mode 100644 index 000000000000..a4d299f9a105 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/firebase.json @@ -0,0 +1,21 @@ +{ + "hosting": { + "public": "public", + "redirects": [ + { + "source": "/:version/docs", + "destination": "/:version/docs/index.html", + "type": 301 + } + ], + "rewrites": [ + { + "source": "/**", + "function": "sendStoredFile" + } + ] + }, + "storage": { + "rules": "storage.rules" + } +} diff --git a/scripts/code.angularjs.org-firebase/functions/index.js b/scripts/code.angularjs.org-firebase/functions/index.js new file mode 100644 index 000000000000..18fdd6c7a44a --- /dev/null +++ b/scripts/code.angularjs.org-firebase/functions/index.js @@ -0,0 +1,75 @@ +'use strict'; + +const functions = require('firebase-functions'); +const gcs = require('@google-cloud/storage')(); +const path = require('path'); + +const gcsBucketId = `${process.env.GCLOUD_PROJECT}.appspot.com`; +const LOCAL_TMP_FOLDER = '/tmp/'; + +const BROWSER_CACHE_DURATION = 300; +const CDN_CACHE_DURATION = 600; + +function sendStoredFile(request, response) { + let filePathSegments = request.path.split('/').filter((segment) => { + // Remove empty leading or trailing path parts + return segment !== ''; + }); + + const version = filePathSegments[0]; + const isDocsPath = filePathSegments[1] === 'docs'; + const lastSegment = filePathSegments[filePathSegments.length - 1]; + const bucket = gcs.bucket(gcsBucketId); + + let downloadSource; + let downloadDestination; + let fileName; + + if (isDocsPath && filePathSegments.length === 2) { + fileName = 'index.html'; + filePathSegments = [version, 'docs', fileName]; + } else { + fileName = lastSegment; + } + + downloadSource = path.join.apply(null, filePathSegments); + downloadDestination = `${LOCAL_TMP_FOLDER}${fileName}`; + + downloadAndSend(downloadSource, downloadDestination).catch(error => { + if (isDocsPath && error.code === 404) { + fileName = 'index.html'; + filePathSegments = [version, 'docs', fileName]; + downloadSource = path.join.apply(null, filePathSegments); + downloadDestination = `${LOCAL_TMP_FOLDER}${fileName}`; + + return downloadAndSend(downloadSource, downloadDestination); + } + + return Promise.reject(error); + }).catch(error => { + let message = 'General error'; + if (error.code === 404) { + if (fileName.split('.').length === 1) { + message = 'Directory listing is not supported'; + } else { + message = 'File not found'; + } + } + + return response.status(error.code).send(message); + }); + + function downloadAndSend(downloadSource, downloadDestination) { + return bucket.file(downloadSource).download({ + destination: downloadDestination + }).then(() => { + return response.status(200) + .set({ + 'Cache-Control': `public, max-age=${BROWSER_CACHE_DURATION}, s-maxage=${CDN_CACHE_DURATION}` + }) + .sendFile(downloadDestination); + }); + } +} + +exports.sendStoredFile = functions.https.onRequest(sendStoredFile); diff --git a/scripts/code.angularjs.org-firebase/functions/package.json b/scripts/code.angularjs.org-firebase/functions/package.json new file mode 100644 index 000000000000..71a68bd6d34a --- /dev/null +++ b/scripts/code.angularjs.org-firebase/functions/package.json @@ -0,0 +1,10 @@ +{ + "name": "functions-firebase-code.angularjs.org", + "description": "Cloud Functions to serve files from gcs to code.angularjs.org", + "dependencies": { + "@google-cloud/storage": "^1.1.1", + "firebase-admin": "^4.2.1", + "firebase-functions": "^0.5.9" + }, + "private": true +} diff --git a/scripts/code.angularjs.org-firebase/public/favicon.ico b/scripts/code.angularjs.org-firebase/public/favicon.ico new file mode 100644 index 000000000000..fe24a63a6ba4 Binary files /dev/null and b/scripts/code.angularjs.org-firebase/public/favicon.ico differ diff --git a/scripts/code.angularjs.org-firebase/public/googleb96cceae5888d79f.html b/scripts/code.angularjs.org-firebase/public/googleb96cceae5888d79f.html new file mode 100644 index 000000000000..c6a8e278d543 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/public/googleb96cceae5888d79f.html @@ -0,0 +1 @@ +google-site-verification: googleb96cceae5888d79f.html \ No newline at end of file diff --git a/scripts/code.angularjs.org-firebase/public/index.html b/scripts/code.angularjs.org-firebase/public/index.html new file mode 100644 index 000000000000..fe8e94ca62c8 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/public/index.html @@ -0,0 +1,10 @@ + + + + + + AngularJS + + + + diff --git a/scripts/code.angularjs.org-firebase/public/robots.txt b/scripts/code.angularjs.org-firebase/public/robots.txt new file mode 100644 index 000000000000..480082428fa1 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * + +Disallow: /*docs/ +Disallow: /*i18n/ +Disallow: /*.zip$ diff --git a/scripts/code.angularjs.org-firebase/readme.firebase.code.md b/scripts/code.angularjs.org-firebase/readme.firebase.code.md new file mode 100644 index 000000000000..58a5d25835d4 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/readme.firebase.code.md @@ -0,0 +1,12 @@ +Firebase for code.angularjs.org +=============================== + +This folder contains the Google Firebase scripts for the code.angularjs.org setup. + +firebase.json contains the rewrite rules that route every subdirectory request to the cloud function +in functions/index.js that serves the docs from the Firebase Google Cloud Storage bucket. + +The deployment to the Google Cloud Storage bucket happens automatically via Travis. See the travis.yml +file in the repository root. + +See /readme.firebase.docs.md for the firebase deployment to docs.angularjs.org \ No newline at end of file diff --git a/scripts/code.angularjs.org-firebase/storage.rules b/scripts/code.angularjs.org-firebase/storage.rules new file mode 100644 index 000000000000..d494542e9b28 --- /dev/null +++ b/scripts/code.angularjs.org-firebase/storage.rules @@ -0,0 +1,7 @@ +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if request.auth!=null; + } + } +} diff --git a/scripts/code.angularjs.org/publish.sh b/scripts/code.angularjs.org/publish.sh index a9780a6de4a4..13420e725318 100755 --- a/scripts/code.angularjs.org/publish.sh +++ b/scripts/code.angularjs.org/publish.sh @@ -59,23 +59,12 @@ function _update_code() { echo "-- Pushing code.angularjs.org" git push origin master - - for backend in "$@" ; do - echo "-- Refreshing code.angularjs.org: backend=$backend" - - # FIXME: We gave up publishing to code.angularjs.org because the GCE automatically removes firewall - # rules that allow access to port 8003. - - # curl http://$backend:8003/gitFetchSite.php - done } function publish { - # The TXT record for backends.angularjs.org is a CSV of the IP addresses for - # the currently serving Compute Engine backends. - # code.angularjs.org is served out of port 8003 on these backends. - backends=("$(dig backends.angularjs.org +short TXT | python -c 'print raw_input()[1:-1].replace(",", "\n")')") - _update_code ${backends[@]} + # publish updates the code.angularjs.org Github repository + # the deployment to Firebase happens via Travis + _update_code } source $(dirname $0)/../utils.inc diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index bf9be1724550..bc71d1d836be 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -5,39 +5,51 @@ set -e export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev` export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev` -if [ "$JOB" == "ci-checks" ]; then - grunt ci-checks - if [[ $TRAVIS_PULL_REQUEST != 'false' ]]; then - # validate commit messages of all commits in the PR - # convert commit range to 2 dots, as commitplease uses `git log`. - # See https://github.com/travis-ci/travis-ci/issues/4596 for more info - echo "Validate commit messages in PR." - yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}" - fi -elif [ "$JOB" == "unit" ]; then - if [ "$BROWSER_PROVIDER" == "browserstack" ]; then - BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_EDGE,BS_iOS_8,BS_iOS_9" - else - BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_EDGE,SL_iOS" - fi - - grunt test:promises-aplus - grunt test:unit --browsers="$BROWSERS" --reporters=spec - grunt tests:docs --browsers="$BROWSERS" --reporters=spec -elif [ "$JOB" == "docs-e2e" ]; then - grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js" -elif [ "$JOB" == "e2e" ]; then - if [[ $TEST_TARGET == jquery* ]]; then - export USE_JQUERY=1 - fi - - export TARGET_SPECS="build/docs/ptore2e/**/default_test.js" - if [[ "$TEST_TARGET" == jquery* ]]; then - TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js" - fi - - export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS" - grunt test:travis-protractor --specs="$TARGET_SPECS" -else - echo "Unknown job type. Please set JOB=ci-checks, JOB=unit or JOB=e2e-*." -fi +case "$JOB" in + "ci-checks") + grunt ci-checks + + if [[ $TRAVIS_PULL_REQUEST != 'false' ]]; then + # validate commit messages of all commits in the PR + # convert commit range to 2 dots, as commitplease uses `git log`. + # See https://github.com/travis-ci/travis-ci/issues/4596 for more info + echo "Validate commit messages in PR:" + yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}" + fi + ;; + "unit") + if [ "$BROWSER_PROVIDER" == "browserstack" ]; then + BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_EDGE,BS_iOS_8,BS_iOS_9" + else + BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_EDGE,SL_iOS" + fi + + grunt test:promises-aplus + grunt test:unit --browsers="$BROWSERS" --reporters=spec + grunt tests:docs --browsers="$BROWSERS" --reporters=spec + ;; + "docs-e2e") + grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js" + ;; + "e2e") + if [[ $TEST_TARGET == jquery* ]]; then + export USE_JQUERY=1 + fi + + export TARGET_SPECS="build/docs/ptore2e/**/default_test.js" + + if [[ "$TEST_TARGET" == jquery* ]]; then + TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js" + fi + + export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS" + grunt test:travis-protractor --specs="$TARGET_SPECS" + ;; + "deploy") + grunt package + grunt compress:firebaseCodeDeploy + ;; + *) + echo "Unknown job type. Please set JOB=ci-checks, JOB=unit, JOB=deploy or JOB=e2e-*." + ;; +esac