From 3f9cfae8a42b928496c5cdb07ddf378149cb7692 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Wed, 30 Nov 2022 15:43:23 +0000 Subject: [PATCH] build: remove docker-based AIO preview deploy setup (#48345) The docker-based AIO preview deploy setup is replaced with significantly simpler setup using GitHub actions provided by dev-infra (also something dev-infra can maintain better then). The actions under the hood leverage Firebase preview channels. PR Close #48345 --- .circleci/config.yml | 37 - .circleci/trigger-webhook.js | 105 - .../dockerbuild/.dockerignore | 3 - aio/aio-builds-setup/dockerbuild/Dockerfile | 179 -- .../dockerbuild/cronjobs/aio-builds-cleanup | 2 - .../dockerbuild/dnsmasq/dnsmasq.conf | 16 - .../dockerbuild/logrotate/aio-misc | 9 - .../dockerbuild/logrotate/aio-nginx | 13 - .../dockerbuild/logrotate/aio-preview-server | 9 - .../dockerbuild/nginx/aio-builds.conf | 123 -- .../nginx/create-selfsigned-cert.sh | 20 - .../dockerbuild/scripts-js/.gitignore | 2 - .../scripts-js/lib/clean-up/build-cleaner.ts | 128 -- .../scripts-js/lib/clean-up/index.ts | 26 - .../scripts-js/lib/common/circle-ci-api.ts | 151 -- .../scripts-js/lib/common/constants.ts | 4 - .../scripts-js/lib/common/env-variables.ts | 19 - .../scripts-js/lib/common/github-api.ts | 111 -- .../lib/common/github-pull-requests.ts | 79 - .../scripts-js/lib/common/github-teams.ts | 72 - .../scripts-js/lib/common/run-tests.ts | 22 - .../scripts-js/lib/common/utils.ts | 109 - .../lib/preview-server/build-creator.ts | 144 -- .../lib/preview-server/build-events.ts | 16 - .../lib/preview-server/build-retriever.ts | 77 - .../lib/preview-server/build-verifier.ts | 46 - .../scripts-js/lib/preview-server/index.ts | 41 - .../lib/preview-server/preview-error.ts | 8 - .../preview-server/preview-server-factory.ts | 207 -- .../scripts-js/lib/preview-server/utils.ts | 28 - .../scripts-js/lib/verify-setup/constants.ts | 49 - .../lib/verify-setup/delete-empty.d.ts | 10 - .../scripts-js/lib/verify-setup/helper.ts | 237 --- .../scripts-js/lib/verify-setup/index.ts | 6 - .../jasmine-custom-matchers-types.d.ts | 7 - .../verify-setup/jasmine-custom-matchers.ts | 88 - .../lib/verify-setup/mock-external-apis.ts | 187 -- .../scripts-js/lib/verify-setup/nginx.e2e.ts | 395 ---- .../lib/verify-setup/preview-server.e2e.ts | 566 ------ .../verify-setup/server-integration.e2e.ts | 269 --- .../verify-setup/start-test-preview-server.ts | 2 - .../lib/verify-setup/tar-stream.d.ts | 30 - .../dockerbuild/scripts-js/package.json | 54 - .../test/clean-up/build-cleaner.spec.ts | 433 ---- .../test/common/circleci-api.spec.ts | 154 -- .../scripts-js/test/common/github-api.spec.ts | 327 --- .../test/common/github-pull-requests.spec.ts | 156 -- .../test/common/github-teams.spec.ts | 203 -- .../scripts-js/test/common/utils.spec.ts | 206 -- .../dockerbuild/scripts-js/test/index.ts | 6 - .../test/preview-server/build-creator.spec.ts | 635 ------ .../test/preview-server/build-events.spec.ts | 58 - .../preview-server/build-retriever.spec.ts | 178 -- .../preview-server/build-verifier.spec.ts | 158 -- .../jasmine-custom-async-matchers-types.d.ts | 5 - .../jasmine-custom-async-matchers.ts | 59 - .../test/preview-server/preview-error.spec.ts | 39 - .../preview-server-factory.spec.ts | 687 ------- .../test/preview-server/utils.spec.ts | 51 - .../dockerbuild/scripts-js/tsconfig.json | 63 - .../dockerbuild/scripts-js/tslint.json | 24 - .../dockerbuild/scripts-js/yarn.lock | 1758 ----------------- .../dockerbuild/scripts-sh/clean-up.sh | 9 - .../dockerbuild/scripts-sh/dev-mode.sh | 12 - .../dockerbuild/scripts-sh/health-check.sh | 86 - .../dockerbuild/scripts-sh/init.sh | 18 - .../scripts-sh/preview-server-prod.sh | 14 - .../scripts-sh/preview-server-test.sh | 16 - .../dockerbuild/scripts-sh/test-env.sh | 19 - .../scripts-sh/verify-setup-and-log.sh | 2 - .../dockerbuild/scripts-sh/verify-setup.sh | 44 - aio/aio-builds-setup/docs/_TOC.md | 34 - .../image-config--environment-variables.md | 67 - .../docs/misc--debug-docker-container.md | 48 - .../docs/misc--integrate-with-ci.md | 9 - .../docs/misc--use-lets-encrypt-certs.md | 80 - .../docs/overview--general.md | 108 - .../docs/overview--http-status-codes.md | 99 - .../docs/overview--scripts-and-commands.md | 66 - .../docs/overview--security-model.md | 149 -- .../docs/vm-setup--attach-persistent-disk.md | 20 - .../docs/vm-setup--create-docker-image.md | 43 - .../vm-setup--create-host-dirs-and-files.md | 80 - .../docs/vm-setup--set-up-docker.md | 28 - .../docs/vm-setup--set-up-secrets.md | 39 - .../docs/vm-setup--start-docker-container.md | 110 -- .../docs/vm-setup--update-docker-container.md | 62 - aio/aio-builds-setup/scripts/_env.sh | 5 - aio/aio-builds-setup/scripts/create-image.sh | 18 - aio/aio-builds-setup/scripts/test.sh | 12 - .../scripts/update-preview-server.sh | 75 - aio/scripts/create-preview.js | 37 - aio/scripts/test-preview.js | 122 -- aio/tsconfig.json | 1 - 94 files changed, 10438 deletions(-) delete mode 100644 .circleci/trigger-webhook.js delete mode 100644 aio/aio-builds-setup/dockerbuild/.dockerignore delete mode 100644 aio/aio-builds-setup/dockerbuild/Dockerfile delete mode 100644 aio/aio-builds-setup/dockerbuild/cronjobs/aio-builds-cleanup delete mode 100644 aio/aio-builds-setup/dockerbuild/dnsmasq/dnsmasq.conf delete mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-misc delete mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx delete mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-preview-server delete mode 100644 aio/aio-builds-setup/dockerbuild/nginx/aio-builds.conf delete mode 100755 aio/aio-builds-setup/dockerbuild/nginx/create-selfsigned-cert.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/.gitignore delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/build-cleaner.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/index.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/constants.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/env-variables.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-api.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-pull-requests.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-teams.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/run-tests.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/utils.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-creator.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-events.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-verifier.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/index.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-error.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-server-factory.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/utils.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/constants.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/delete-empty.d.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/helper.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/index.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers-types.d.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/mock-external-apis.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/nginx.e2e.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/preview-server.e2e.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/start-test-preview-server.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/tar-stream.d.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/package.json delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/clean-up/build-cleaner.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-api.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-pull-requests.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-teams.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/common/utils.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/index.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-creator.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-events.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-verifier.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers-types.d.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-error.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-server-factory.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/utils.spec.ts delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/tsconfig.json delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-js/yarn.lock delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/clean-up.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/dev-mode.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/health-check.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/init.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-prod.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-test.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/test-env.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup-and-log.sh delete mode 100644 aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup.sh delete mode 100644 aio/aio-builds-setup/docs/_TOC.md delete mode 100644 aio/aio-builds-setup/docs/image-config--environment-variables.md delete mode 100644 aio/aio-builds-setup/docs/misc--debug-docker-container.md delete mode 100644 aio/aio-builds-setup/docs/misc--integrate-with-ci.md delete mode 100644 aio/aio-builds-setup/docs/misc--use-lets-encrypt-certs.md delete mode 100644 aio/aio-builds-setup/docs/overview--general.md delete mode 100644 aio/aio-builds-setup/docs/overview--http-status-codes.md delete mode 100644 aio/aio-builds-setup/docs/overview--scripts-and-commands.md delete mode 100644 aio/aio-builds-setup/docs/overview--security-model.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--attach-persistent-disk.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--create-docker-image.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--set-up-docker.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--start-docker-container.md delete mode 100644 aio/aio-builds-setup/docs/vm-setup--update-docker-container.md delete mode 100755 aio/aio-builds-setup/scripts/_env.sh delete mode 100755 aio/aio-builds-setup/scripts/create-image.sh delete mode 100755 aio/aio-builds-setup/scripts/test.sh delete mode 100755 aio/aio-builds-setup/scripts/update-preview-server.sh delete mode 100644 aio/scripts/create-preview.js delete mode 100644 aio/scripts/test-preview.js diff --git a/.circleci/config.yml b/.circleci/config.yml index cd18bed6044..9bf2636cb35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -363,35 +363,6 @@ jobs: # Check the bundle sizes. - run: yarn --cwd aio payload-size aio-local - # This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`. - aio_preview: - executor: - name: default-executor - resource_class: 2xlarge+ - environment: - AIO_SNAPSHOT_ARTIFACT_PATH: &aio_preview_artifact_path 'aio/tmp/snapshot.tgz' - steps: - - custom_attach_workspace - - init_environment - - run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH $CI_PULL_REQUEST $CI_COMMIT - - store_artifacts: - path: *aio_preview_artifact_path - # The `destination` needs to be kept in sync with the value of - # `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile` - 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: - executor: test-browser-executor - steps: - - custom_attach_workspace - - init_environment - - run: yarn --cwd aio install --frozen-lockfile --non-interactive --cache-folder ~/.cache/yarn - - 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 - # The `build-npm-packages` tasks exist for backwards-compatibility with old scripts and # tests that rely on the pre-Bazel `dist/packages-dist` output structure (build.sh). # Having multiple jobs that independently build in this manner duplicates some work; we build @@ -752,14 +723,6 @@ workflows: - test_aio_local: requires: - setup - - aio_preview: - # Only run on PR builds. (There can be no previews for non-PR builds.) - <<: *only_on_pull_requests - requires: - - setup - - test_aio_preview: - requires: - - aio_preview - publish_packages_as_artifacts: requires: - build-npm-packages diff --git a/.circleci/trigger-webhook.js b/.circleci/trigger-webhook.js deleted file mode 100644 index 88c1b700dc2..00000000000 --- a/.circleci/trigger-webhook.js +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env node - -/** - * Usage (cli): - * ``` - * node create-preview - * ``` - * - * 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); -} diff --git a/aio/aio-builds-setup/dockerbuild/.dockerignore b/aio/aio-builds-setup/dockerbuild/.dockerignore deleted file mode 100644 index a53938b615e..00000000000 --- a/aio/aio-builds-setup/dockerbuild/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -scripts-js/lib -scripts-js/node_modules -scripts-js/**/test diff --git a/aio/aio-builds-setup/dockerbuild/Dockerfile b/aio/aio-builds-setup/dockerbuild/Dockerfile deleted file mode 100644 index 6cb1ab5a4d7..00000000000 --- a/aio/aio-builds-setup/dockerbuild/Dockerfile +++ /dev/null @@ -1,179 +0,0 @@ -# Image metadata and config -FROM debian:bullseye - -LABEL name="angular.io PR preview" \ - description="This image implements the PR preview functionality for angular.io." \ - vendor="Angular" \ - version="1.0" - -VOLUME /aio-secrets -VOLUME /var/www/aio-builds -VOLUME /dockerbuild - -EXPOSE 80 443 - - -# Build-time args and env vars -# The AIO_ARTIFACT_PATH path needs to be kept in synch with the value of -# `aio_preview->steps->store_artifacts->destination` property in `.circleci/config.yml` -ARG AIO_ARTIFACT_PATH=aio/dist/aio-snapshot.tgz -ARG TEST_AIO_ARTIFACT_PATH=$AIO_ARTIFACT_PATH -ARG AIO_BUILDS_DIR=/var/www/aio-builds -ARG TEST_AIO_BUILDS_DIR=/tmp/aio-builds -ARG AIO_DOMAIN_NAME=ngbuilds.io -ARG TEST_AIO_DOMAIN_NAME=$AIO_DOMAIN_NAME.localhost -ARG AIO_GITHUB_ORGANIZATION=angular -ARG TEST_AIO_GITHUB_ORGANIZATION=test-org -ARG AIO_GITHUB_REPO=angular -ARG TEST_AIO_GITHUB_REPO=test-repo -ARG AIO_GITHUB_TEAM_SLUGS=aio-auto-previews,aio-contributors -ARG TEST_AIO_GITHUB_TEAM_SLUGS=test-team-1,test-team-2 -ARG AIO_NGINX_HOSTNAME=$AIO_DOMAIN_NAME -ARG TEST_AIO_NGINX_HOSTNAME=$TEST_AIO_DOMAIN_NAME -ARG AIO_NGINX_PORT_HTTP=80 -ARG TEST_AIO_NGINX_PORT_HTTP=8080 -ARG AIO_NGINX_PORT_HTTPS=443 -ARG TEST_AIO_NGINX_PORT_HTTPS=4433 -ARG AIO_SIGNIFICANT_FILES_PATTERN='^(?:aio|packages)/(?!.*[._]spec\\.[jt]s$)' -ARG TEST_AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN -ARG AIO_TRUSTED_PR_LABEL="aio: preview" -ARG TEST_AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL -ARG AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost -ARG TEST_AIO_PREVIEW_SERVER_HOSTNAME=$AIO_PREVIEW_SERVER_HOSTNAME -ARG AIO_ARTIFACT_MAX_SIZE=36700160 -ARG TEST_AIO_ARTIFACT_MAX_SIZE=200 -ARG AIO_PREVIEW_SERVER_PORT=3000 -ARG TEST_AIO_PREVIEW_SERVER_PORT=3001 - -ENV AIO_ARTIFACT_PATH=$AIO_ARTIFACT_PATH TEST_AIO_ARTIFACT_PATH=$TEST_AIO_ARTIFACT_PATH \ - AIO_BUILDS_DIR=$AIO_BUILDS_DIR TEST_AIO_BUILDS_DIR=$TEST_AIO_BUILDS_DIR \ - AIO_DOMAIN_NAME=$AIO_DOMAIN_NAME TEST_AIO_DOMAIN_NAME=$TEST_AIO_DOMAIN_NAME \ - AIO_GITHUB_ORGANIZATION=$AIO_GITHUB_ORGANIZATION TEST_AIO_GITHUB_ORGANIZATION=$TEST_AIO_GITHUB_ORGANIZATION \ - AIO_GITHUB_REPO=$AIO_GITHUB_REPO TEST_AIO_GITHUB_REPO=$TEST_AIO_GITHUB_REPO \ - AIO_GITHUB_TEAM_SLUGS=$AIO_GITHUB_TEAM_SLUGS TEST_AIO_GITHUB_TEAM_SLUGS=$TEST_AIO_GITHUB_TEAM_SLUGS \ - AIO_LOCALCERTS_DIR=/etc/ssl/localcerts TEST_AIO_LOCALCERTS_DIR=/etc/ssl/localcerts-test \ - AIO_NGINX_HOSTNAME=$AIO_NGINX_HOSTNAME TEST_AIO_NGINX_HOSTNAME=$TEST_AIO_NGINX_HOSTNAME \ - AIO_NGINX_LOGS_DIR=/var/log/aio/nginx TEST_AIO_NGINX_LOGS_DIR=/var/log/aio/nginx-test \ - AIO_NGINX_PORT_HTTP=$AIO_NGINX_PORT_HTTP TEST_AIO_NGINX_PORT_HTTP=$TEST_AIO_NGINX_PORT_HTTP \ - AIO_NGINX_PORT_HTTPS=$AIO_NGINX_PORT_HTTPS TEST_AIO_NGINX_PORT_HTTPS=$TEST_AIO_NGINX_PORT_HTTPS \ - AIO_SCRIPTS_JS_DIR=/usr/share/aio-scripts-js \ - AIO_SCRIPTS_SH_DIR=/usr/share/aio-scripts-sh \ - AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN TEST_AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN \ - AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL TEST_AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL \ - AIO_PREVIEW_SERVER_HOSTNAME=$AIO_PREVIEW_SERVER_HOSTNAME TEST_AIO_PREVIEW_SERVER_HOSTNAME=$TEST_AIO_PREVIEW_SERVER_HOSTNAME \ - AIO_ARTIFACT_MAX_SIZE=$AIO_ARTIFACT_MAX_SIZE TEST_AIO_ARTIFACT_MAX_SIZE=$TEST_AIO_ARTIFACT_MAX_SIZE \ - AIO_PREVIEW_SERVER_PORT=$AIO_PREVIEW_SERVER_PORT TEST_AIO_PREVIEW_SERVER_PORT=$TEST_AIO_PREVIEW_SERVER_PORT \ - AIO_WWW_USER=www-data \ - NODE_ENV=production - - -# Create directory for logs -RUN mkdir /var/log/aio - - -# Add extra package sources -RUN apt-get update -y && apt-get install -y curl -RUN curl --silent --show-error --location https://deb.nodesource.com/setup_14.x | bash - -RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - -RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list - - -# Install packages -# NOTE: Many packages make older versions unavailable on the repositories, so we cannot pin to -# specific versions :( -# See, for example: -# - https://github.com/nodesource/distributions/issues/33 -# - https://askubuntu.com/questions/715104/how-can-i-downgrade-openssl-via-apt-get -RUN apt-get update -y && apt-get install -y \ - cron \ - dnsmasq \ - nano \ - nginx \ - nodejs \ - openssl \ - rsyslog \ - vim \ - yarn=1.22.18-1 -RUN yarn global add pm2@5.2.0 - - -# Set up log rotation -COPY logrotate/* /etc/logrotate.d/ -RUN chmod 0644 /etc/logrotate.d/* - - -# Set up cronjobs -COPY cronjobs/aio-builds-cleanup /etc/cron.d/ -RUN chmod 0744 /etc/cron.d/aio-builds-cleanup -RUN crontab /etc/cron.d/aio-builds-cleanup -RUN printenv | grep AIO_ >> /etc/environment - - -# Set up dnsmasq -COPY dnsmasq/dnsmasq.conf /etc/ -RUN sed -i "s|{{\$AIO_NGINX_HOSTNAME}}|$AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf -RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/dnsmasq.conf -RUN sed -i "s|{{\$TEST_AIO_NGINX_HOSTNAME}}|$TEST_AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf -RUN sed -i "s|{{\$TEST_AIO_PREVIEW_SERVER_HOSTNAME}}|$TEST_AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/dnsmasq.conf - - -# Set up SSL/TLS certificates -COPY nginx/create-selfsigned-cert.sh /tmp/ -RUN chmod a+x /tmp/create-selfsigned-cert.sh -RUN /tmp/create-selfsigned-cert.sh "selfcert-prod" "$AIO_NGINX_HOSTNAME" "$AIO_LOCALCERTS_DIR" -RUN /tmp/create-selfsigned-cert.sh "selfcert-test" "$TEST_AIO_NGINX_HOSTNAME" "$TEST_AIO_LOCALCERTS_DIR" -RUN rm /tmp/create-selfsigned-cert.sh -RUN update-ca-certificates - - -# Set up nginx (for production and testing) -RUN sed -i -E "s|^user\s+\S+;|user $AIO_WWW_USER;|" /etc/nginx/nginx.conf -RUN rm -f /etc/nginx/conf.d/* -RUN rm -f /etc/nginx/sites-enabled/* - -COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_ARTIFACT_MAX_SIZE}}|$AIO_ARTIFACT_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-prod.conf -RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_PORT}}|$AIO_PREVIEW_SERVER_PORT|g" /etc/nginx/conf.d/aio-builds-prod.conf - -COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$TEST_AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$TEST_AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_ARTIFACT_MAX_SIZE}}|$TEST_AIO_ARTIFACT_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-test.conf -RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_PORT}}|$TEST_AIO_PREVIEW_SERVER_PORT|g" /etc/nginx/conf.d/aio-builds-test.conf - - -# Set up pm2 -RUN pm2 startup --user root > /dev/null - - -# Set up the shell scripts -COPY scripts-sh/ $AIO_SCRIPTS_SH_DIR/ -RUN chmod a+x $AIO_SCRIPTS_SH_DIR/* -RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \ - | while read file; do ln -s $AIO_SCRIPTS_SH_DIR/$file /usr/local/bin/aio-${file%.*}; done - - -# Set up the Node.js scripts -COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/ -RUN yarn --cwd="$AIO_SCRIPTS_JS_DIR/" install --production --frozen-lockfile - - -# Set up health check -HEALTHCHECK --interval=5m CMD /usr/local/bin/aio-health-check - - -# Go! -WORKDIR / -CMD aio-init && tail -f /dev/null diff --git a/aio/aio-builds-setup/dockerbuild/cronjobs/aio-builds-cleanup b/aio/aio-builds-setup/dockerbuild/cronjobs/aio-builds-cleanup deleted file mode 100644 index f820811ab24..00000000000 --- a/aio/aio-builds-setup/dockerbuild/cronjobs/aio-builds-cleanup +++ /dev/null @@ -1,2 +0,0 @@ -# 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 diff --git a/aio/aio-builds-setup/dockerbuild/dnsmasq/dnsmasq.conf b/aio/aio-builds-setup/dockerbuild/dnsmasq/dnsmasq.conf deleted file mode 100644 index af43feeb2ef..00000000000 --- a/aio/aio-builds-setup/dockerbuild/dnsmasq/dnsmasq.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Do not read /etc/resolv.conf. Get servers from this file instead. -no-resolv -server=8.8.8.8 -server=8.8.4.4 - -# Listen for DHCP and DNS requests only on this address. -listen-address=127.0.0.1 - -# Force an IP address for these domains. -address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1 -address=/{{$AIO_PREVIEW_SERVER_HOSTNAME}}/127.0.0.1 -address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1 -address=/{{$TEST_AIO_PREVIEW_SERVER_HOSTNAME}}/127.0.0.1 - -# Run as root (required from inside docker container). -user=root diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc b/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc deleted file mode 100644 index 165009cd551..00000000000 --- a/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc +++ /dev/null @@ -1,9 +0,0 @@ -/var/log/aio/clean-up.log /var/log/aio/init.log /var/log/aio/verify-setup.log { - compress - create - delaycompress - missingok - monthly - notifempty - rotate 6 -} diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx b/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx deleted file mode 100644 index 59d434243ec..00000000000 --- a/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx +++ /dev/null @@ -1,13 +0,0 @@ -/var/log/aio/nginx/*.log /var/log/aio/nginx-test/*.log { - compress - create - delaycompress - missingok - monthly - notifempty - rotate 6 - sharedscripts - postrotate - service nginx rotate >/dev/null 2>&1 - endscript -} diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-preview-server b/aio/aio-builds-setup/dockerbuild/logrotate/aio-preview-server deleted file mode 100644 index 3fe5d31f889..00000000000 --- a/aio/aio-builds-setup/dockerbuild/logrotate/aio-preview-server +++ /dev/null @@ -1,9 +0,0 @@ -/var/log/aio/preview-server-*.log { - compress - copytruncate - delaycompress - missingok - monthly - notifempty - rotate 6 -} diff --git a/aio/aio-builds-setup/dockerbuild/nginx/aio-builds.conf b/aio/aio-builds-setup/dockerbuild/nginx/aio-builds.conf deleted file mode 100644 index c9aeec032da..00000000000 --- a/aio/aio-builds-setup/dockerbuild/nginx/aio-builds.conf +++ /dev/null @@ -1,123 +0,0 @@ -# Redirect all HTTP traffic to HTTPS -server { - server_name _; - - listen {{$AIO_NGINX_PORT_HTTP}} default_server; - listen [::]:{{$AIO_NGINX_PORT_HTTP}}; - - access_log {{$AIO_NGINX_LOGS_DIR}}/access.log; - error_log {{$AIO_NGINX_LOGS_DIR}}/error.log; - - # Ideally we want 308 (permanent + keep original method), - # but it is relatively new and not supported by some clients (e.g. cURL). - return 307 https://$host:{{$AIO_NGINX_PORT_HTTPS}}$request_uri; -} - -# Serve PR-preview requests -server { - server_name "~^pr(?[1-9][0-9]*)-(?[0-9a-f]{7,40})\."; - - listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2; - listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2; - - ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt; - ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key; - ssl_prefer_server_ciphers on; - ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; - - root {{$AIO_BUILDS_DIR}}/$pr/$sha; - disable_symlinks on from=$document_root; - index index.html; - - gzip on; - gzip_comp_level 7; - gzip_types *; - - access_log {{$AIO_NGINX_LOGS_DIR}}/access.log; - error_log {{$AIO_NGINX_LOGS_DIR}}/error.log; - - error_page 404 /404.html; - location "=/404.html" { - internal; - } - - location "~/[^/]+\.[^/]+$" { - try_files $uri $uri/ =404; - } - - location / { - try_files $uri $uri/ /index.html =404; - } -} - -# Handle all other requests -server { - server_name _; - - listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2 default_server; - listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2; - - ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt; - ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key; - ssl_prefer_server_ciphers on; - ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; - - access_log {{$AIO_NGINX_LOGS_DIR}}/access.log; - error_log {{$AIO_NGINX_LOGS_DIR}}/error.log; - - # Health check - location "~^/health-check/?$" { - add_header Content-Type text/plain; - 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 - location "~^/circle-build/?$" { - if ($request_method != "POST") { - add_header Allow "POST"; - return 405; - } - - proxy_pass_request_headers on; - proxy_redirect off; - proxy_method POST; - proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri; - - resolver 127.0.0.1; - } - - # Notify about PR changes - location "~^/pr-updated/?$" { - if ($request_method != "POST") { - add_header Allow "POST"; - return 405; - } - - proxy_pass_request_headers on; - proxy_redirect off; - proxy_method POST; - proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri; - - resolver 127.0.0.1; - } - - # Everything else - location / { - return 404; - } -} diff --git a/aio/aio-builds-setup/dockerbuild/nginx/create-selfsigned-cert.sh b/aio/aio-builds-setup/dockerbuild/nginx/create-selfsigned-cert.sh deleted file mode 100755 index 286639e8801..00000000000 --- a/aio/aio-builds-setup/dockerbuild/nginx/create-selfsigned-cert.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - - -# Variables -confFile=/tmp/$1.conf -domainName=$2 -outDir=$3 - - -# Create certificate -cp /etc/ssl/openssl.cnf "$confFile" -echo "[subjectAltName]" >> "$confFile" -echo "subjectAltName = DNS:$domainName, DNS:*.$domainName" >> "$confFile" -mkdir -p $outDir -openssl req -days 365 -newkey rsa:2048 -nodes -sha256 -x509 \ - -config "$confFile" -extensions subjectAltName -subj "/CN=$domainName" \ - -out "$outDir/$domainName.crt" -keyout "$outDir/$domainName.key" -chmod -R 400 "$outDir" -cp "$outDir/$domainName.crt" /usr/local/share/ca-certificates diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/.gitignore b/aio/aio-builds-setup/dockerbuild/scripts-js/.gitignore deleted file mode 100644 index c925c21d56c..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist -/node_modules diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/build-cleaner.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/build-cleaner.ts deleted file mode 100644 index 99b4ddadcfd..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/build-cleaner.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Imports -import * as fs from 'fs'; -import * as path from 'path'; -import * as shell from 'shelljs'; -import {HIDDEN_DIR_PREFIX} from '../common/constants'; -import {GithubApi} from '../common/github-api'; -import {GithubPullRequests} from '../common/github-pull-requests'; -import {assertNotMissingOrEmpty, getPrInfoFromDownloadPath, Logger} from '../common/utils'; - -// Classes -export class BuildCleaner { - - private logger = new Logger('BuildCleaner'); - - // Constructor - constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string, - protected githubToken: string, protected downloadsDir: string, protected artifactPath: string) { - assertNotMissingOrEmpty('buildsDir', buildsDir); - assertNotMissingOrEmpty('githubOrg', githubOrg); - assertNotMissingOrEmpty('githubRepo', githubRepo); - assertNotMissingOrEmpty('githubToken', githubToken); - assertNotMissingOrEmpty('downloadsDir', downloadsDir); - assertNotMissingOrEmpty('artifactPath', artifactPath); - } - - // Methods - Public - public async cleanUp(): Promise { - try { - this.logger.log('Cleaning up builds and downloads'); - const openPrs = await this.getOpenPrNumbers(); - this.logger.log(`Open pull requests: ${openPrs.length}`); - await Promise.all([ - this.cleanBuilds(openPrs), - this.cleanDownloads(openPrs), - ]); - } catch (error) { - this.logger.error('ERROR:', error); - throw error; - } - } - - public async cleanBuilds(openPrs: number[]): Promise { - const existingBuilds = await this.getExistingBuildNumbers(); - await this.removeUnnecessaryBuilds(existingBuilds, openPrs); - } - - public async cleanDownloads(openPrs: number[]): Promise { - const existingDownloads = await this.getExistingDownloads(); - await this.removeUnnecessaryDownloads(existingDownloads, openPrs); - } - - public getExistingBuildNumbers(): Promise { - return new Promise((resolve, reject) => { - fs.readdir(this.buildsDir, (err, files) => { - if (err) { - return reject(err); - } - - const buildNumbers = files. - map(name => name.replace(HIDDEN_DIR_PREFIX, '')). // Remove the "hidden dir" prefix - map(Number). // Convert string to number - filter(Boolean); // Ignore NaN (or 0), because they are not builds - - resolve(buildNumbers); - }); - }); - } - - public async getOpenPrNumbers(): Promise { - const api = new GithubApi(this.githubToken); - const githubPullRequests = new GithubPullRequests(api, this.githubOrg, this.githubRepo); - const prs = await githubPullRequests.fetchAll('open'); - return prs.map(pr => pr.number); - } - - public removeDir(dir: string): void { - try { - if (shell.test('-d', dir)) { - shell.chmod('-R', 'a+w', dir); - shell.rm('-rf', dir); - } - } catch (err) { - this.logger.error(`ERROR: Unable to remove '${dir}' due to:`, err); - } - } - - public removeUnnecessaryBuilds(existingBuildNumbers: number[], openPrNumbers: number[]): void { - const toRemove = existingBuildNumbers.filter(num => !openPrNumbers.includes(num)); - - this.logger.log(`Existing builds: ${existingBuildNumbers.length}`); - this.logger.log(`Removing ${toRemove.length} build(s): ${toRemove.join(', ')}`); - - // Try removing public dirs. - toRemove. - map(num => path.join(this.buildsDir, String(num))). - forEach(dir => this.removeDir(dir)); - - // Try removing hidden dirs. - toRemove. - map(num => path.join(this.buildsDir, HIDDEN_DIR_PREFIX + String(num))). - forEach(dir => this.removeDir(dir)); - } - - public getExistingDownloads(): Promise { - const artifactFile = path.basename(this.artifactPath); - return new Promise((resolve, reject) => { - fs.readdir(this.downloadsDir, (err, files) => { - if (err) { - return reject(err); - } - files = files.filter(file => file.endsWith(artifactFile)); - resolve(files); - }); - }); - } - - public removeUnnecessaryDownloads(existingDownloads: string[], openPrNumbers: number[]): void { - const toRemove = existingDownloads.filter(filePath => { - const {pr} = getPrInfoFromDownloadPath(filePath); - return !openPrNumbers.includes(pr); - }); - - this.logger.log(`Existing downloads: ${existingDownloads.length}`); - this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`); - - toRemove.forEach(filePath => shell.rm(path.join(this.downloadsDir, filePath))); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/index.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/index.ts deleted file mode 100644 index 5fb6bbec706..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/clean-up/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Imports -import {AIO_DOWNLOADS_DIR} from '../common/constants'; -import { - AIO_ARTIFACT_PATH, - AIO_BUILDS_DIR, - AIO_GITHUB_ORGANIZATION, - AIO_GITHUB_REPO, - AIO_GITHUB_TOKEN, -} from '../common/env-variables'; -import {BuildCleaner} from './build-cleaner'; - -// Run -_main(); - -// Functions -function _main(): void { - const buildCleaner = new BuildCleaner( - AIO_BUILDS_DIR, - AIO_GITHUB_ORGANIZATION, - AIO_GITHUB_REPO, - AIO_GITHUB_TOKEN, - AIO_DOWNLOADS_DIR, - AIO_ARTIFACT_PATH); - - buildCleaner.cleanUp().catch(() => process.exit(1)); -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts deleted file mode 100644 index 860795e1b68..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Imports -import fetch, {RequestInit} from 'node-fetch'; - -import {assertNotMissingOrEmpty} from './utils'; - -// Constants -const CIRCLE_CI_BASE_API_URL = 'https://circleci.com/api/v2'; -const CIRCLE_CI_BUILD_API_URL = `${CIRCLE_CI_BASE_API_URL}/project/gh`; -const CIRCLE_CI_PIPELINE_API_URL = `${CIRCLE_CI_BASE_API_URL}/pipeline`; - -// Interfaces - Types - -// API docs: https://circleci.com/docs/api/v2#operation/getJobArtifacts -// Example: https://circleci.com/api/v2/project/gh/angular/angular/1163941/artifacts -export interface ArtifactInfo { - /** The path of this build artifacts. */ - path: string; - - /** The full URL where this artifact can be downloaded from. */ - url: string; - - // There are other fields but they are not used in this code. -} - -export type ArtifactResponse = {items: ArtifactInfo[]}; - -// API docs: https://circleci.com/docs/api/v2#operation/getJobDetails -// Example: https://circleci.com/api/v2/project/gh/angular/angular/job/1163941 -export interface BuildInfo { - /** The job number. */ - number: number; - - /** The job name (e.g. `'aio_preview'`). */ - name: string; - - /** Info about the organization which the project related to this job belongs to. */ - organization: { - name: string; - }; - - /** Info about the project related to this job. */ - project: { - name: string; - }; - - /** Info about the [pipeline](https://circleci.com/docs/2.0/pipelines/) that this job is part of. */ - pipeline: { - id: string; - }; - - // There are other fields but they are not used in this code. -} - -// API docs: https://circleci.com/docs/api/v2#operation/getPipelineById -// https://circleci.com/api/v2/pipeline/356227c0-32f6-4f99-bfc2-3938db90a147 -export interface PipelineInfo { - /** The pipeline ID. */ - id: string; - - /** Info related to/retrieved from the version control system provider (e.g. GitHub). */ - vcs: { - /** The PR number. */ - review_id: string; - - /** The HEAD SHA. */ - revision: string; - }; - - // There are other fields but they are not used in this code. -} - -/** - * A Helper that can interact with the CircleCI API. - */ -export class CircleCiApi { - /** - * Construct a helper that can interact with the CircleCI REST API. - * @param githubOrg The Github organisation whose repos we want to access in CircleCI (e.g. - * angular). - * @param githubRepo The Github repo whose builds we want to access in CircleCI (e.g. angular). - * @param circleCiToken The CircleCI API access token (secret). - */ - constructor( - private githubOrg: string, - private githubRepo: string, - private circleCiToken: string, - ) { - assertNotMissingOrEmpty('githubOrg', githubOrg); - assertNotMissingOrEmpty('githubRepo', githubRepo); - assertNotMissingOrEmpty('circleCiToken', circleCiToken); - } - - public async fetchFromCircleCi(url: string, params: RequestInit = {}) { - params.headers = {...params.headers, 'Circle-Token': this.circleCiToken}; - const response = await fetch(url, params); - if (response.status !== 200) { - throw new Error(`${url}: ${response.status} - ${response.statusText}`); - } - return response; - } - - /** - * Get the info for a build (aka job) from the CircleCI API. - * @param buildNumber The CircleCI build number that generated the artifact. - * @returns A promise to the info about the build. - */ - public async getBuildInfo(buildNumber: number): Promise { - try { - const url = `${CIRCLE_CI_BUILD_API_URL}/${this.githubOrg}/${this.githubRepo}/job/${buildNumber}`; - const response = await this.fetchFromCircleCi(url); - return response.json(); - } catch (error) { - throw new Error(`CircleCI build info request failed (${(error as Error).message})`); - } - } - - /** - * Query the CircleCI API to get a URL for a specified artifact from a specified build. - * @param artifactPath The path, within the build to the artifact. - * @returns A promise to the URL that can be requested to download the actual build artifact file. - */ - public async getBuildArtifactUrl(buildNumber: number, artifactPath: string): Promise { - const baseUrl = `${CIRCLE_CI_BUILD_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`; - try { - const response = await this.fetchFromCircleCi(`${baseUrl}/artifacts`); - const artifacts = await response.json() as ArtifactResponse; - const artifact = artifacts.items.find(item => item.path === artifactPath); - if (!artifact) { - throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`); - } - return artifact.url; - } catch (error) { - throw new Error(`CircleCI artifact URL request failed (${(error as Error).message})`); - } - } - - /** - * Get the info for a [pipeline](https://circleci.com/docs/2.0/pipelines/) from the CircleCI API. - * @param pipelineId The CircleCI pipeline ID that generated the artifact. - * @returns A promise to the info about the pipeline. - */ - public async getPipelineInfo(pipelineId: string): Promise { - try { - const url = `${CIRCLE_CI_PIPELINE_API_URL}/${pipelineId}`; - const response = await this.fetchFromCircleCi(url); - return response.json(); - } catch (error) { - throw new Error(`CircleCI pipeline info request failed (${(error as Error).message})`); - } - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/constants.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/constants.ts deleted file mode 100644 index 186f75cae63..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Constants -export const AIO_DOWNLOADS_DIR = '/tmp/aio-downloads'; -export const HIDDEN_DIR_PREFIX = 'hidden--'; -export const SHORT_SHA_LEN = 7; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/env-variables.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/env-variables.ts deleted file mode 100644 index 1383836d1ef..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/env-variables.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {getEnvVar} from './utils'; - -export const AIO_ARTIFACT_PATH = getEnvVar('AIO_ARTIFACT_PATH'); -export const AIO_BUILDS_DIR = getEnvVar('AIO_BUILDS_DIR'); -export const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN'); -export const AIO_CIRCLE_CI_TOKEN = getEnvVar('AIO_CIRCLE_CI_TOKEN'); -export const AIO_DOMAIN_NAME = getEnvVar('AIO_DOMAIN_NAME'); -export const AIO_GITHUB_ORGANIZATION = getEnvVar('AIO_GITHUB_ORGANIZATION'); -export const AIO_GITHUB_REPO = getEnvVar('AIO_GITHUB_REPO'); -export const AIO_GITHUB_TEAM_SLUGS = getEnvVar('AIO_GITHUB_TEAM_SLUGS'); -export const AIO_NGINX_HOSTNAME = getEnvVar('AIO_NGINX_HOSTNAME'); -export const AIO_NGINX_PORT_HTTP = +getEnvVar('AIO_NGINX_PORT_HTTP'); -export const AIO_NGINX_PORT_HTTPS = +getEnvVar('AIO_NGINX_PORT_HTTPS'); -export const AIO_SIGNIFICANT_FILES_PATTERN = getEnvVar('AIO_SIGNIFICANT_FILES_PATTERN'); -export const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL'); -export const AIO_PREVIEW_SERVER_HOSTNAME = getEnvVar('AIO_PREVIEW_SERVER_HOSTNAME'); -export const AIO_PREVIEW_SERVER_PORT = +getEnvVar('AIO_PREVIEW_SERVER_PORT'); -export const AIO_ARTIFACT_MAX_SIZE = +getEnvVar('AIO_ARTIFACT_MAX_SIZE'); -export const AIO_WWW_USER = getEnvVar('AIO_WWW_USER'); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-api.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-api.ts deleted file mode 100644 index fc259cbdb77..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-api.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Imports -import {IncomingMessage} from 'http'; -import * as https from 'https'; -import {assertNotMissingOrEmpty} from './utils'; - -// Constants -const GITHUB_HOSTNAME = 'api.github.com'; - -// Interfaces - Types -interface RequestParams { - [key: string]: string | number; -} - -type RequestParamsOrNull = RequestParams | null; - -// Classes -export class GithubApi { - protected requestHeaders: {[key: string]: string}; - - // Constructor - constructor(githubToken: string) { - assertNotMissingOrEmpty('githubToken', githubToken); - - this.requestHeaders = { - 'Authorization': `token ${githubToken}`, - 'User-Agent': `Node/${process.versions.node}`, - }; - } - - // Methods - Public - public get(pathname: string, params?: RequestParamsOrNull): Promise { - const path = this.buildPath(pathname, params); - return this.request('get', path); - } - - public post(pathname: string, params?: RequestParamsOrNull, data?: any): Promise { - const path = this.buildPath(pathname, params); - return this.request('post', path, data); - } - - // In GitHub API paginated requests, page numbering is 1-based. (https://developer.github.com/v3/#pagination) - public getPaginated(pathname: string, baseParams: RequestParams = {}, currentPage: number = 1): Promise { - const perPage = 100; - const params = { - ...baseParams, - page: currentPage, - per_page: perPage, - }; - - return this.get(pathname, params).then(items => { - if (items.length < perPage) { - return items; - } - - return this.getPaginated(pathname, baseParams, currentPage + 1).then(moreItems => [...items, ...moreItems]); - }); - } - - // Methods - Protected - protected buildPath(pathname: string, params?: RequestParamsOrNull): string { - if (params == null) { - return pathname; - } - - const search = (params === null) ? '' : this.serializeSearchParams(params); - const joiner = search && '?'; - - return `${pathname}${joiner}${search}`; - } - - protected request(method: string, path: string, data: any = null): Promise { - return new Promise((resolve, reject) => { - const options = { - headers: {...this.requestHeaders}, - host: GITHUB_HOSTNAME, - method, - path, - }; - - const onError = (statusCode: number, responseText: string) => { - const url = `https://${GITHUB_HOSTNAME}${path}`; - reject(`Request to '${url}' failed (status: ${statusCode}): ${responseText}`); - }; - const onSuccess = (responseText: string) => { - try { resolve(responseText && JSON.parse(responseText)); } catch (err) { reject(err); } - }; - const onResponse = (res: IncomingMessage) => { - const statusCode = res.statusCode || -1; - const isSuccess = (200 <= statusCode) && (statusCode < 400); - let responseText = ''; - - res. - on('data', d => responseText += d). - on('end', () => isSuccess ? onSuccess(responseText) : onError(statusCode, responseText)). - on('error', reject); - }; - - https. - request(options, onResponse). - on('error', reject). - end(data && JSON.stringify(data)); - }); - } - - protected serializeSearchParams(params: RequestParams): string { - return Object.keys(params). - filter(key => params[key] != null). - map(key => `${key}=${encodeURIComponent(String(params[key]))}`). - join('&'); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-pull-requests.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-pull-requests.ts deleted file mode 100644 index f429a3c2c3c..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-pull-requests.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {GithubApi} from './github-api'; -import {assert, assertNotMissingOrEmpty} from './utils'; - -export interface PullRequest { - number: number; - user: {login: string}; - labels: {name: string}[]; -} - -export interface FileInfo { - sha: string; - filename: string; -} - -export type PullRequestState = 'all' | 'closed' | 'open'; - -/** - * Access pull requests on GitHub. - */ -export class GithubPullRequests { - public repoSlug: string; - - /** - * Create an instance of this helper - * @param api An instance of the Github API helper. - * @param githubOrg The organisation on GitHub whose repo we will interrogate. - * @param githubRepo The repository on Github with whose PRs we will interact. - */ - constructor(private api: GithubApi, githubOrg: string, githubRepo: string) { - assertNotMissingOrEmpty('githubOrg', githubOrg); - assertNotMissingOrEmpty('githubRepo', githubRepo); - this.repoSlug = `${githubOrg}/${githubRepo}`; - } - - /** - * Post a comment on a PR. - * @param pr The number of the PR on which to comment. - * @param body The body of the comment to post. - * @returns A promise that resolves when the comment has been posted. - */ - public addComment(pr: number, body: string): Promise { - assert(pr > 0, `Invalid PR number: ${pr}`); - assert(!!body, `Invalid or empty comment body: ${body}`); - return this.api.post(`/repos/${this.repoSlug}/issues/${pr}/comments`, null, {body}); - } - - /** - * Request information about a PR. - * @param pr The number of the PR for which to request info. - * @returns A promise that is resolves with information about the specified PR. - */ - public fetch(pr: number): Promise { - assert(pr > 0, `Invalid PR number: ${pr}`); - // Using the `/issues/` URL, because the `/pulls/` one does not provide labels. - return this.api.get(`/repos/${this.repoSlug}/issues/${pr}`); - } - - /** - * Request information about all PRs that match the given state. - * @param state Only retrieve PRs that have this state. - * @returns A promise that is resolved with information about the requested PRs. - */ - public fetchAll(state: PullRequestState = 'all'): Promise { - const pathname = `/repos/${this.repoSlug}/pulls`; - const params = {state}; - - return this.api.getPaginated(pathname, params); - } - - /** - * Request a list of files for the given PR. - * @param pr The number of the PR for which to request files. - * @returns A promise that resolves to an array of file information - */ - public fetchFiles(pr: number): Promise { - assert(pr > 0, `Invalid PR number: ${pr}`); - return this.api.getPaginated(`/repos/${this.repoSlug}/pulls/${pr}/files`); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-teams.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-teams.ts deleted file mode 100644 index d04a2995765..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/github-teams.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {GithubApi} from './github-api'; -import {assertNotMissingOrEmpty} from './utils'; - -export interface Team { - id: number; - slug: string; -} - -export interface TeamMembership { - state: string; -} - -export class GithubTeams { - /** - * Create an instance of this helper - * @param api An instance of the Github API helper. - * @param githubOrg The organisation on GitHub whose repo we will interrogate. - */ - constructor(private api: GithubApi, protected githubOrg: string) { - assertNotMissingOrEmpty('githubOrg', githubOrg); - } - - /** - * Request information about all the organisation's teams in GitHub. - * @returns A promise that is resolved with information about the teams. - */ - public fetchAll(): Promise { - return this.api.getPaginated(`/orgs/${this.githubOrg}/teams`); - } - - /** - * Check whether the specified username is a member of the specified team. - * @param username The usernane to check for in the team. - * @param teamIds The team to check for the username. - * @returns a Promise that resolves to `true` if the username is a member of the team. - */ - public async isMemberById(username: string, teamIds: number[]): Promise { - - const getMembership = async (teamId: number) => { - try { - const {state} = await this.api.get(`/teams/${teamId}/memberships/${username}`); - return state === 'active'; - } catch (error) { - return false; - } - }; - - for (const teamId of teamIds) { - if (await getMembership(teamId)) { - return true; - } - } - - return false; - } - - /** - * Check whether the given username is a member of the teams specified by the team slugs. - * @param username The username to check for in the teams. - * @param teamSlugs A collection of slugs that represent the teams to check for the username. - * @returns a Promise that resolves to `true` if the usernane is a member of at least one of the specified teams. - */ - public async isMemberBySlug(username: string, teamSlugs: string[]): Promise { - try { - const teams = await this.fetchAll(); - const teamIds = teams.filter(team => teamSlugs.includes(team.slug)).map(team => team.id); - return await this.isMemberById(username, teamIds); - } catch (error) { - return false; - } - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/run-tests.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/run-tests.ts deleted file mode 100644 index faecca99a1f..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/run-tests.ts +++ /dev/null @@ -1,22 +0,0 @@ -// We can't use `import...from` here, because of the following mess: -// - 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). -import Jasmine = require('jasmine'); -import 'source-map-support/register'; - -export const runTests = (specFiles: string[]) => { - const config = { - random: true, - spec_files: specFiles, - stopSpecOnExpectationFailure: true, - }; - - process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason)); - - const runner = new Jasmine({}); - runner.loadConfig(config); - runner.execute(); -}; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/utils.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/utils.ts deleted file mode 100644 index 5a3d3b65f1d..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/utils.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {basename, resolve as resolvePath} from 'path'; - -import {SHORT_SHA_LEN} from './constants'; - -/** - * Shorten a SHA to make it more readable - * @param sha The SHA to shorten. - */ -export function computeShortSha(sha: string) { - return sha.slice(0, SHORT_SHA_LEN); -} - -/** - * Compute the path for a downloaded artifact file. - * @param downloadsDir The directory where artifacts are downloaded - * @param pr The PR associated with this artifact. - * @param sha The SHA associated with the build for this artifact. - * @param artifactPath The path to the artifact on CircleCI. - * @returns The fully resolved location for the specified downloaded artifact. - */ -export function computeArtifactDownloadPath( - downloadsDir: string, pr: number, sha: string, artifactPath: string) { - return resolvePath(downloadsDir, `${pr}-${computeShortSha(sha)}-${basename(artifactPath)}`); -} - -/** - * Extract the PR number and latest commit SHA from a downloaded file path. - * @param downloadPath the path to the downloaded file. - * @returns An object whose keys are the PR and SHA extracted from the file path. - */ -export function getPrInfoFromDownloadPath(downloadPath: string) { - const file = basename(downloadPath); - const [pr, sha] = file.split('-'); - return {pr: +pr, sha}; -} - -/** - * Assert that a value is true. - * @param value The value to assert. - * @param message The message if the value is not true. - */ -export function assert(value: boolean, message: string) { - if (!value) { - throw new Error(message); - } -} - -/** - * Assert that a parameter is not equal to "". - * @param name The name of the parameter. - * @param value The value of the parameter. - */ -export const assertNotMissingOrEmpty = (name: string, value: string|null|undefined) => { - assert(!!value, `Missing or empty required parameter '${name}'!`); -}; - -/** - * Get an environment variable. - * @param name The name of the environment variable. - * @param isOptional True if the variable is optional. - * @returns The value of the variable or "" if it is optional and falsy. - * @throws `Error` if the variable is falsy and not optional. - */ -export const getEnvVar = (name: string, isOptional = false): string => { - const value = process.env[name]; - - if (!isOptional && !value) { - try { - throw new Error(`ERROR: Missing required environment variable '${name}'!`); - } catch (error) { - console.error((error as Error).stack); - process.exit(1); - } - } - - return value || ''; -}; - -/** - * A basic logger implementation. - * Delegates to `console`, but prepends each message with the current date and specified scope (i.e - * caller). - */ -export class Logger { - private padding = ' '.repeat(20 - this.scope.length); - - /** - * 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); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-creator.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-creator.ts deleted file mode 100644 index f6c0771b124..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-creator.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Imports -import * as cp from 'child_process'; -import {EventEmitter} from 'events'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as shell from 'shelljs'; -import {HIDDEN_DIR_PREFIX} from '../common/constants'; -import {assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils'; -import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events'; -import {PreviewServerError} from './preview-error'; - -// Classes -export class BuildCreator extends EventEmitter { - - private logger = new Logger('BuildCreator'); - - // Constructor - constructor(protected buildsDir: string) { - super(); - assertNotMissingOrEmpty('buildsDir', buildsDir); - } - - // Methods - Public - public create(pr: number, sha: string, archivePath: string, isPublic: boolean): Promise { - // Use only part of the SHA for more readable URLs. - sha = computeShortSha(sha); - - const {newPrDir: prDir} = this.getCandidatePrDirs(pr, isPublic); - const shaDir = path.join(prDir, sha); - let dirToRemoveOnError: string; - - return Promise.resolve(). - // If the same PR exists with different visibility, update the visibility first. - then(() => this.updatePrVisibility(pr, isPublic)). - then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])). - then(([prDirExisted, shaDirExisted]) => { - if (shaDirExisted) { - const publicOrNot = isPublic ? 'public' : 'non-public'; - throw new PreviewServerError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); - } - - dirToRemoveOnError = prDirExisted ? shaDir : prDir; - - return Promise.resolve(). - then(() => shell.mkdir('-p', shaDir)). - then(() => this.extractArchive(archivePath, shaDir)). - then(() => this.emit(CreatedBuildEvent.type, new CreatedBuildEvent(+pr, sha, isPublic))). - then(() => undefined); - }). - catch(err => { - if (dirToRemoveOnError) { - shell.rm('-rf', dirToRemoveOnError); - } - - if (!(err instanceof PreviewServerError)) { - err = new PreviewServerError(500, `Error while creating preview at: ${shaDir}\n${err}`); - } - - throw err; - }); - } - - public updatePrVisibility(pr: number, makePublic: boolean): Promise { - const {oldPrDir: otherVisPrDir, newPrDir: targetVisPrDir} = this.getCandidatePrDirs(pr, makePublic); - - return Promise. - all([this.exists(otherVisPrDir), this.exists(targetVisPrDir)]). - then(([otherVisPrDirExisted, targetVisPrDirExisted]) => { - if (!otherVisPrDirExisted) { - // No visibility change: Either the visibility is up-to-date or the PR does not exist. - return false; - } else if (targetVisPrDirExisted) { - // Error: Directories for both visibilities exist. - throw new PreviewServerError(409, - `Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`); - } - - // Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`. - return Promise.resolve(). - then(() => shell.mv(otherVisPrDir, targetVisPrDir)). - then(() => this.listShasByDate(targetVisPrDir)). - then(shas => this.emit(ChangedPrVisibilityEvent.type, new ChangedPrVisibilityEvent(+pr, shas, makePublic))). - then(() => true); - }). - catch(err => { - if (!(err instanceof PreviewServerError)) { - err = new PreviewServerError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`); - } - - throw err; - }); - } - - // Methods - Protected - protected exists(fileOrDir: string): Promise { - return new Promise(resolve => fs.access(fileOrDir, err => resolve(!err))); - } - - protected extractArchive(inputFile: string, outputDir: string): Promise { - return new Promise((resolve, reject) => { - const cmd = `tar --extract --gzip --directory "${outputDir}" --file "${inputFile}"`; - - cp.exec(cmd, (err, _stdout, stderr) => { - if (err) { - return reject(err); - } - - if (stderr) { - this.logger.warn(stderr); - } - - try { - shell.chmod('-R', 'a-w', outputDir); - shell.rm('-f', inputFile); - resolve(); - } catch (err) { - reject(err); - } - }); - }); - } - - protected getCandidatePrDirs(pr: number, isPublic: boolean): {oldPrDir: string, newPrDir: string} { - const hiddenPrDir = path.join(this.buildsDir, HIDDEN_DIR_PREFIX + pr); - const publicPrDir = path.join(this.buildsDir, `${pr}`); - - const oldPrDir = isPublic ? hiddenPrDir : publicPrDir; - const newPrDir = isPublic ? publicPrDir : hiddenPrDir; - - return {oldPrDir, newPrDir}; - } - - protected listShasByDate(inputDir: string): Promise { - return Promise.resolve(). - then(() => shell.ls('-l', inputDir) as any as Promise<(fs.Stats & {name: string})[]>). - // Keep directories only. - // (Also, convert to standard Array - ShellJS provides custom `sort()` method for sorting file contents.) - then(items => items.filter(item => item.isDirectory())). - // Sort by modification date. - then(items => items.sort((a, b) => a.mtime.getTime() - b.mtime.getTime())). - // Return directory names. - then(items => items.map(item => item.name)); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-events.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-events.ts deleted file mode 100644 index 39dcbf10eb5..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-events.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Classes -export class ChangedPrVisibilityEvent { - // Properties - Public, Static - public static type = 'pr.changedVisibility'; - - // Constructor - constructor(public pr: number, public shas: string[], public isPublic: boolean) {} -} - -export class CreatedBuildEvent { - // Properties - Public, Static - public static type = 'build.created'; - - // Constructor - constructor(public pr: number, public sha: string, public isPublic: boolean) {} -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts deleted file mode 100644 index 9f187104288..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as fs from 'fs'; -import {dirname} from 'path'; -import {mkdir} from 'shelljs'; -import {promisify} from 'util'; - -import {CircleCiApi} from '../common/circle-ci-api'; -import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, Logger} from '../common/utils'; - -import {PreviewServerError} from './preview-error'; - -export interface GithubInfo { - org: string; - pr: number; - repo: string; - sha: string; -} - -/** - * A helper that can get information about builds and download build artifacts. - */ -export class BuildRetriever { - private logger = new Logger('BuildRetriever'); - constructor( - private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) { - assert( - downloadSizeLimit > 0, - 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.'); - assertNotMissingOrEmpty('downloadDir', downloadDir); - } - - /** - * Get GitHub information about a build - * @param buildNum The number of the build for which to retrieve the info. - * @returns The Github org, repo, PR and latest SHA for the specified build. - */ - public async getGithubInfo(buildNum: number): Promise { - const buildInfo = await this.api.getBuildInfo(buildNum); - const pipelineInfo = await this.api.getPipelineInfo(buildInfo.pipeline.id); - const githubInfo: GithubInfo = { - org: buildInfo.organization.name, - pr: +pipelineInfo.vcs.review_id, - repo: buildInfo.project.name, - sha: pipelineInfo.vcs.revision, - }; - return githubInfo; - } - - /** - * Make a request to the given URL for a build artifact and store it locally. - * @param buildNum the number of the CircleCI build whose artifact we want to download. - * @param pr the number of the PR that triggered the CircleCI build. - * @param sha the commit in the PR that triggered the CircleCI build. - * @param artifactPath the path on CircleCI where the artifact was stored. - * @returns A promise to the file path where the downloaded file was stored. - */ - public async downloadBuildArtifact( - buildNum: number, pr: number, sha: string, artifactPath: string): Promise { - try { - const outPath = computeArtifactDownloadPath(this.downloadDir, pr, sha, artifactPath); - const downloadExists = - await new Promise(resolve => fs.exists(outPath, exists => resolve(exists))); - if (!downloadExists) { - const url = await this.api.getBuildArtifactUrl(buildNum, artifactPath); - const response = await this.api.fetchFromCircleCi(url, {size: this.downloadSizeLimit}); - const buffer = await response.buffer(); - mkdir('-p', dirname(outPath)); - await promisify(fs.writeFile)(outPath, buffer); - } - return outPath; - } catch (error: any) { - this.logger.warn(error); - const status = (error.type === 'max-size') ? 413 : 500; - throw new PreviewServerError( - status, `CircleCI artifact download failed (${error.message || error})`); - } - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-verifier.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-verifier.ts deleted file mode 100644 index 42a56344d78..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-verifier.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {GithubPullRequests, PullRequest} from '../common/github-pull-requests'; -import {GithubTeams} from '../common/github-teams'; -import {assertNotMissingOrEmpty} from '../common/utils'; - -/** - * A helper to verify whether builds are trusted. - */ -export class BuildVerifier { - /** - * Construct a new BuildVerifier instance. - * @param prs A helper to access PR information. - * @param teams A helper to access Github team information. - * @param allowedTeamSlugs The teams that are trusted. - * @param trustedPrLabel The github label that indicates that a PR is trusted. - */ - constructor(protected prs: GithubPullRequests, protected teams: GithubTeams, - protected allowedTeamSlugs: string[], protected trustedPrLabel: string) { - assertNotMissingOrEmpty('allowedTeamSlugs', allowedTeamSlugs && allowedTeamSlugs.join('')); - assertNotMissingOrEmpty('trustedPrLabel', trustedPrLabel); - } - - /** - * Check whether a PR contains files that are significant to the build. - * @param pr The number of the PR to check - * @param significantFilePattern A regex that selects files that are significant. - */ - public async getSignificantFilesChanged(pr: number, significantFilePattern: RegExp): Promise { - const files = await this.prs.fetchFiles(pr); - return files.some(file => significantFilePattern.test(file.filename)); - } - - /** - * Check whether a PR is trusted. - * @param pr The number of the PR to check. - * @returns true if the PR is trusted. - */ - public async getPrIsTrusted(pr: number): Promise { - const prInfo = await this.prs.fetch(pr); - return this.hasLabel(prInfo, this.trustedPrLabel) || - (await this.teams.isMemberBySlug(prInfo.user.login, this.allowedTeamSlugs)); - } - - protected hasLabel(prInfo: PullRequest, label: string): boolean { - return prInfo.labels.some(labelObj => labelObj.name === label); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/index.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/index.ts deleted file mode 100644 index 90780bc8de8..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Imports -import {AIO_DOWNLOADS_DIR} from '../common/constants'; -import { - AIO_ARTIFACT_MAX_SIZE, - AIO_ARTIFACT_PATH, - AIO_BUILDS_DIR, - AIO_CIRCLE_CI_TOKEN, - AIO_DOMAIN_NAME, - AIO_GITHUB_ORGANIZATION, - AIO_GITHUB_REPO, - AIO_GITHUB_TEAM_SLUGS, - AIO_GITHUB_TOKEN, - AIO_PREVIEW_SERVER_HOSTNAME, - AIO_PREVIEW_SERVER_PORT, - AIO_SIGNIFICANT_FILES_PATTERN, - AIO_TRUSTED_PR_LABEL, -} from '../common/env-variables'; -import {PreviewServerFactory} from './preview-server-factory'; - -// Run -_main(); - -// Functions -function _main(): void { - PreviewServerFactory - .create({ - buildArtifactPath: AIO_ARTIFACT_PATH, - buildsDir: AIO_BUILDS_DIR, - circleCiToken: AIO_CIRCLE_CI_TOKEN, - domainName: AIO_DOMAIN_NAME, - downloadSizeLimit: AIO_ARTIFACT_MAX_SIZE, - downloadsDir: AIO_DOWNLOADS_DIR, - githubOrg: AIO_GITHUB_ORGANIZATION, - githubRepo: AIO_GITHUB_REPO, - githubTeamSlugs: AIO_GITHUB_TEAM_SLUGS.split(','), - githubToken: AIO_GITHUB_TOKEN, - significantFilesPattern: AIO_SIGNIFICANT_FILES_PATTERN, - trustedPrLabel: AIO_TRUSTED_PR_LABEL, - }) - .listen(AIO_PREVIEW_SERVER_PORT, AIO_PREVIEW_SERVER_HOSTNAME); -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-error.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-error.ts deleted file mode 100644 index af23b26cd07..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-error.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Classes -export class PreviewServerError extends Error { - // Constructor - constructor(public status: number = 500, message?: string) { - super(message); - Object.setPrototypeOf(this, PreviewServerError.prototype); - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-server-factory.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-server-factory.ts deleted file mode 100644 index 24175a61b6a..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/preview-server-factory.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Imports -import * as bodyParser from 'body-parser'; -import * as express from 'express'; -import * as http from 'http'; -import {AddressInfo} from 'net'; -import {CircleCiApi} from '../common/circle-ci-api'; -import {GithubApi} from '../common/github-api'; -import {GithubPullRequests} from '../common/github-pull-requests'; -import {GithubTeams} from '../common/github-teams'; -import {assert, assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils'; -import {BuildCreator} from './build-creator'; -import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events'; -import {BuildRetriever} from './build-retriever'; -import {BuildVerifier} from './build-verifier'; -import {respondWithError, throwRequestError} from './utils'; - -const AIO_PREVIEW_JOB = 'aio_preview'; - -// Interfaces - Types -export interface PreviewServerConfig { - downloadsDir: string; - downloadSizeLimit: number; - buildArtifactPath: string; - buildsDir: string; - domainName: string; - githubOrg: string; - githubRepo: string; - githubTeamSlugs: string[]; - circleCiToken: string; - githubToken: string; - significantFilesPattern: string; - trustedPrLabel: string; -} - -const logger = new Logger('PreviewServer'); - -// Classes -export class PreviewServerFactory { - // Methods - Public - public static create(cfg: PreviewServerConfig): http.Server { - assertNotMissingOrEmpty('domainName', cfg.domainName); - - const circleCiApi = new CircleCiApi(cfg.githubOrg, cfg.githubRepo, cfg.circleCiToken); - const githubApi = new GithubApi(cfg.githubToken); - const prs = new GithubPullRequests(githubApi, cfg.githubOrg, cfg.githubRepo); - const teams = new GithubTeams(githubApi, cfg.githubOrg); - - const buildRetriever = new BuildRetriever(circleCiApi, cfg.downloadSizeLimit, cfg.downloadsDir); - const buildVerifier = new BuildVerifier(prs, teams, cfg.githubTeamSlugs, cfg.trustedPrLabel); - const buildCreator = PreviewServerFactory.createBuildCreator(prs, cfg.buildsDir, cfg.domainName); - - const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator, cfg); - const httpServer = http.createServer(middleware as any); - - httpServer.on('listening', () => { - const info = httpServer.address() as AddressInfo; - logger.info(`Up and running (and listening on ${info.address}:${info.port})...`); - }); - - return httpServer; - } - - public static createMiddleware(buildRetriever: BuildRetriever, buildVerifier: BuildVerifier, - buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express { - const middleware = express(); - const jsonParser = bodyParser.json(); - const significantFilesRe = new RegExp(cfg.significantFilesPattern); - - // RESPOND TO IS-ALIVE PING - 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 - middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => { - try { - if (!( - req.is('json') && - req.body && - req.body.payload && - req.body.payload.build_num > 0 && - req.body.payload.build_parameters && - req.body.payload.build_parameters.CIRCLE_JOB - )) { - throwRequestError(400, `Incorrect body content. Expected JSON`, req); - } - - const job = req.body.payload.build_parameters.CIRCLE_JOB; - const buildNum = req.body.payload.build_num; - - logger.log(`Build:${buildNum}, Job:${job} - processing web-hook trigger`); - - if (job !== AIO_PREVIEW_JOB) { - res.sendStatus(204); - logger.log(`Build:${buildNum}, Job:${job} -`, - `Skipping preview processing because this is not the "${AIO_PREVIEW_JOB}" job.`); - return; - } - - const { pr, sha, org, repo } = await buildRetriever.getGithubInfo(buildNum); - - assert(cfg.githubOrg === org, - `Invalid webhook: expected "githubOrg" property to equal "${cfg.githubOrg}" but got "${org}".`); - assert(cfg.githubRepo === 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) - if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) { - res.sendStatus(204); - logger.log(`PR:${pr}, Build:${buildNum} - ` + - `Skipping preview processing because this PR did not touch any significant files.`); - return; - } - - const artifactPath = await buildRetriever.downloadBuildArtifact(buildNum, pr, sha, cfg.buildArtifactPath); - const isPublic = await buildVerifier.getPrIsTrusted(pr); - await buildCreator.create(pr, sha, artifactPath, isPublic); - - res.sendStatus(isPublic ? 201 : 202); - logger.log(`PR:${pr}, SHA:${computeShortSha(sha)}, Build:${buildNum} - ` + - `Successfully created ${isPublic ? 'public' : 'non-public'} preview.`); - } catch (err) { - logger.error('CircleCI webhook error', err); - respondWithError(res, err); - } - }); - - // GITHUB PR UPDATED WEBHOOK - middleware.post(/^\/pr-updated\/?$/, jsonParser, async (req, res) => { - const { action, number: prNo }: { action?: string, number?: number } = req.body; - const visMayHaveChanged = !action || (action === 'labeled') || (action === 'unlabeled'); - - try { - if (!visMayHaveChanged) { - res.sendStatus(200); - } else if (!prNo) { - throwRequestError(400, `Missing or empty 'number' field`, req); - } else { - const isPublic = await buildVerifier.getPrIsTrusted(prNo); - await buildCreator.updatePrVisibility(prNo, isPublic); - res.sendStatus(200); - } - } catch (err) { - logger.error('PR update hook error', err); - respondWithError(res, err); - } - }); - - // ALL OTHER REQUESTS - middleware.all('*', req => throwRequestError(404, 'Unknown resource', req)); - middleware.use((err: any, _req: any, res: express.Response, _next: any) => { - const statusText = http.STATUS_CODES[err.status] || '???'; - logger.error(`Preview server error: ${err.status} - ${statusText}:`, err.message); - respondWithError(res, err); - }); - - return middleware; - } - - public static createBuildCreator(prs: GithubPullRequests, buildsDir: string, domainName: string): BuildCreator { - const buildCreator = new BuildCreator(buildsDir); - const postPreviewsComment = (pr: number, shas: string[]) => { - const body = shas. - map(sha => `You can preview ${sha} at https://pr${pr}-${sha}.${domainName}/.`). - join('\n'); - - return prs.addComment(pr, body); - }; - - buildCreator.on(CreatedBuildEvent.type, ({pr, sha, isPublic}: CreatedBuildEvent) => { - if (isPublic) { - postPreviewsComment(pr, [sha]); - } - }); - - buildCreator.on(ChangedPrVisibilityEvent.type, ({pr, shas, isPublic}: ChangedPrVisibilityEvent) => { - if (isPublic && shas.length) { - postPreviewsComment(pr, shas); - } - }); - - return buildCreator; - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/utils.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/utils.ts deleted file mode 100644 index bf845e18c08..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as express from 'express'; -import {PreviewServerError} from './preview-error'; - -/** - * Update the response to report that an error has occurred. - * @param res The response to configure as an error. - * @param err The error that needs to be reported. - */ -export async function respondWithError(res: express.Response, err: any): Promise { - if (!(err instanceof PreviewServerError)) { - err = new PreviewServerError(500, String((err && err.message) || err)); - } - - res.status(err.status); - return new Promise(resolve => res.end(err.message, resolve)); -} - -/** - * Throw an exception that describes the given error information. - * @param status The HTTP status code include in the error. - * @param error The error message to include in the error. - * @param req The request that triggered this error. - */ -export function throwRequestError(status: number, error: string, req: express.Request): never { - const message = `${error} in request: ${req.method} ${req.originalUrl}` + - (!req.body ? '' : ` ${JSON.stringify(req.body)}`); - throw new PreviewServerError(status, message); -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/constants.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/constants.ts deleted file mode 100644 index c0a528044d2..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/constants.ts +++ /dev/null @@ -1,49 +0,0 @@ -export const enum BuildNums { - BUILD_INFO_ERROR = 1, - BUILD_INFO_404, - BUILD_INFO_INVALID_GH_ORG, - BUILD_INFO_INVALID_GH_REPO, - PIPELINE_INFO_ERROR, - PIPELINE_INFO_404, - CHANGED_FILES_ERROR, - CHANGED_FILES_404, - CHANGED_FILES_NONE, - BUILD_ARTIFACTS_ERROR, - BUILD_ARTIFACTS_404, - BUILD_ARTIFACTS_EMPTY, - BUILD_ARTIFACTS_MISSING, - DOWNLOAD_ARTIFACT_ERROR, - DOWNLOAD_ARTIFACT_404, - DOWNLOAD_ARTIFACT_TOO_BIG, - TRUST_CHECK_ERROR, - TRUST_CHECK_UNTRUSTED, - TRUST_CHECK_TRUSTED_LABEL, - TRUST_CHECK_ACTIVE_TRUSTED_USER, - TRUST_CHECK_INACTIVE_TRUSTED_USER, -} - -export const enum PipelineIds { - PIPELINE_INFO_ERROR = 'pipeline-error', - PIPELINE_INFO_404 = 'pipeline-404', - CHANGED_FILES_ERROR = 'pipeline-cfe', - CHANGED_FILES_404 = 'pipeline-cf404', - CHANGED_FILES_NONE = 'pipeline-cfn', - TRUST_CHECK_ERROR = 'pipeline-tce', - TRUST_CHECK_UNTRUSTED = 'pipeline-tcu', - PIPELINE_INFO_OK = 'pipeline-ok', -} - -export const enum PrNums { - CHANGED_FILES_ERROR = 1, - CHANGED_FILES_404, - CHANGED_FILES_NONE, - TRUST_CHECK_ERROR, - TRUST_CHECK_UNTRUSTED, - TRUST_CHECK_TRUSTED_LABEL, - TRUST_CHECK_ACTIVE_TRUSTED_USER, - TRUST_CHECK_INACTIVE_TRUSTED_USER, -} - -export const SHA = '1234567890'.repeat(4); -export const ALT_SHA = 'abcde'.repeat(8); -export const SIMILAR_SHA = SHA.slice(0, -1) + 'A'; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/delete-empty.d.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/delete-empty.d.ts deleted file mode 100644 index 19b96d72558..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/delete-empty.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare module 'delete-empty' { - interface Options { - dryRun: boolean; - verbose: boolean; - filter: (filePath: string) => boolean; - } - export default function deleteEmpty(cwd: string, options?: Options): Promise; - export default function deleteEmpty(cwd: string, options?: Options, callback?: (err: any, deleted: string[]) => void): void; - export function sync(cwd: string, options?: Options): string[]; -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/helper.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/helper.ts deleted file mode 100644 index da1df765002..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/helper.ts +++ /dev/null @@ -1,237 +0,0 @@ -// Imports -import * as cp from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as shell from 'shelljs'; -import {AIO_DOWNLOADS_DIR, HIDDEN_DIR_PREFIX} from '../common/constants'; -import { - AIO_BUILDS_DIR, - AIO_NGINX_PORT_HTTP, - AIO_NGINX_PORT_HTTPS, - AIO_WWW_USER, -} from '../common/env-variables'; -import {computeShortSha, Logger} from '../common/utils'; - -// Interfaces - Types -export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; } -export interface FileSpecs { content?: string; size?: number; } - -export type CleanUpFn = () => void; -export type TestSuiteFactory = (scheme: string, port: number) => void; -export type VerifyCmdResultFn = (result: CmdResult) => void; - -// Classes -class Helper { - - // Properties - Protected - protected cleanUpFns: CleanUpFn[] = []; - protected portPerScheme: {[scheme: string]: number} = { - http: AIO_NGINX_PORT_HTTP, - https: AIO_NGINX_PORT_HTTPS, - }; - - private logger = new Logger('TestHelper'); - - // Constructor - constructor() { - shell.mkdir('-p', AIO_BUILDS_DIR); - shell.exec(`chown -R ${AIO_WWW_USER} ${AIO_BUILDS_DIR}`); - shell.mkdir('-p', AIO_DOWNLOADS_DIR); - shell.exec(`chown -R ${AIO_WWW_USER} ${AIO_DOWNLOADS_DIR}`); - } - - // Methods - Public - public cleanUp(): void { - while (this.cleanUpFns.length) { - // Clean-up fns remove themselves from the list. - this.cleanUpFns[0](); - } - - const leftoverDownloads = fs.readdirSync(AIO_DOWNLOADS_DIR); - const leftoverBuilds = fs.readdirSync(AIO_BUILDS_DIR); - - if (leftoverDownloads.length) { - this.logger.log(`Downloads directory '${AIO_DOWNLOADS_DIR}' is not empty after clean-up.`, leftoverDownloads); - shell.rm('-rf', `${AIO_DOWNLOADS_DIR}/*`); - } - - if (leftoverBuilds.length) { - this.logger.log(`Builds directory '${AIO_BUILDS_DIR}' is not empty after clean-up.`, leftoverBuilds); - shell.rm('-rf', `${AIO_BUILDS_DIR}/*`); - } - - if (leftoverBuilds.length || leftoverDownloads.length) { - throw new Error(`Unexpected test files not cleaned up.`); - } - } - - public createDummyBuild(pr: number, sha: string, isPublic = true, force = false, legacy = false): CleanUpFn { - const prDir = this.getPrDir(pr, isPublic); - const shaDir = this.getShaDir(prDir, sha, legacy); - const idxPath = path.join(shaDir, 'index.html'); - const barPath = path.join(shaDir, 'foo', 'bar.js'); - - this.writeFile(idxPath, {content: `PR: ${pr} | SHA: ${sha} | File: /index.html`}, force); - this.writeFile(barPath, {content: `PR: ${pr} | SHA: ${sha} | File: /foo/bar.js`}, force); - shell.exec(`chown -R ${AIO_WWW_USER} ${prDir}`); - - return this.createCleanUpFn(() => shell.rm('-rf', prDir)); - } - - public getPrDir(pr: number, isPublic: boolean): string { - const prDirName = isPublic ? '' + pr : HIDDEN_DIR_PREFIX + pr; - return path.join(AIO_BUILDS_DIR, prDirName); - } - - public getShaDir(prDir: string, sha: string, legacy = false): string { - return path.join(prDir, legacy ? sha : computeShortSha(sha)); - } - - public readBuildFile(pr: number, sha: string, relFilePath: string, isPublic = true, legacy = false): string { - const shaDir = this.getShaDir(this.getPrDir(pr, isPublic), sha, legacy); - const absFilePath = path.join(shaDir, relFilePath); - return fs.readFileSync(absFilePath, 'utf8'); - } - - public runCmd(cmd: string, opts: cp.ExecOptions = {}): Promise { - return new Promise(resolve => { - const proc = cp.exec(cmd, opts, (err, stdout, stderr) => resolve({success: !err, err, stdout, stderr})); - this.createCleanUpFn(() => proc.kill()); - }); - } - - public runForAllSupportedSchemes(suiteFactory: TestSuiteFactory): void { - Object.entries(this.portPerScheme).forEach(([scheme, port]) => suiteFactory(scheme, port)); - } - - public verifyResponse(status: number, regex: string | RegExp = /^/): VerifyCmdResultFn { - return (result: CmdResult) => { - const [headers, body] = result.stdout. - split(/(?:\r?\n){2,}/). - map(s => s.trim()). - slice(-2); // In case of redirect, discard the previous headers. - // Only keep the last to sections (final headers and body). - - if (!result.success) { - this.logger.log('Stdout:', result.stdout); - this.logger.error('Stderr:', result.stderr); - this.logger.error('Error:', result.err); - } - - expect(result.success).toBe(true); - expect(headers).toMatch(new RegExp(`HTTP/(?:1\\.1|2) ${status} `)); - expect(body).toMatch(regex); - }; - } - - public writeBuildFile(pr: number, sha: string, relFilePath: string, content: string, isPublic = true, - legacy = false): void { - const shaDir = this.getShaDir(this.getPrDir(pr, isPublic), sha, legacy); - const absFilePath = path.join(shaDir, relFilePath); - this.writeFile(absFilePath, {content}, true); - } - - public writeFile(filePath: string, {content, size}: FileSpecs, force = false): void { - if (!force && fs.existsSync(filePath)) { - throw new Error(`Refusing to overwrite existing file '${filePath}'.`); - } - - let cleanUpTarget = filePath; - while (!fs.existsSync(path.dirname(cleanUpTarget))) { - cleanUpTarget = path.dirname(cleanUpTarget); - } - - shell.mkdir('-p', path.dirname(filePath)); - if (size) { - // Create a file of the specified size. - cp.execSync(`fallocate -l ${size} ${filePath}`); - } else { - // Create a file with the specified content. - fs.writeFileSync(filePath, content || ''); - } - shell.exec(`chown ${AIO_WWW_USER} ${filePath}`); - } - - // Methods - Protected - protected createCleanUpFn(fn: () => void): CleanUpFn { - const cleanUpFn = () => { - const idx = this.cleanUpFns.indexOf(cleanUpFn); - if (idx !== -1) { - this.cleanUpFns.splice(idx, 1); - fn(); - } - }; - - this.cleanUpFns.push(cleanUpFn); - - return cleanUpFn; - } -} - -interface DefaultCurlOptions { - defaultMethod?: CurlOptions['method']; - defaultOptions?: CurlOptions['options']; - defaultHeaders?: CurlOptions['headers']; - defaultData?: CurlOptions['data']; - defaultExtraPath?: CurlOptions['extraPath']; -} - -interface CurlOptions { - method?: string; - options?: string; - headers?: string[]; - data?: any; - url?: string; - extraPath?: string; -} - -export function makeCurl(baseUrl: string, { - defaultMethod = 'POST', - defaultOptions = '', - defaultHeaders = ['Content-Type: application/json'], - defaultData = {}, - defaultExtraPath = '', -}: DefaultCurlOptions = {}) { - return function curl({ - method = defaultMethod, - options = defaultOptions, - headers = defaultHeaders, - data = defaultData, - url = baseUrl, - extraPath = defaultExtraPath, - }: CurlOptions) { - const dataString = data ? JSON.stringify(data) : ''; - const cmd = `curl -iLX ${method} ` + - `${options} ` + - headers.map(header => `--header "${header}" `).join('') + - `--data '${dataString}' ` + - `${url}${extraPath}`; - return helper.runCmd(cmd); - }; -} - -export interface PayloadData { - data: { - payload: { - build_num: number, - build_parameters: { - CIRCLE_JOB: string; - }; - }; - }; -} - -export function payload(buildNum: number): PayloadData { - return { - data: { - payload: { - build_num: buildNum, - build_parameters: { CIRCLE_JOB: 'aio_preview' }, - }, - }, - }; -} - - -// Exports -export const helper = new Helper(); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/index.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/index.ts deleted file mode 100644 index 37e6d19a530..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Imports -import {runTests} from '../common/run-tests'; - -// Run -const specFiles = [`${__dirname}/**/*.e2e.js`]; -runTests(specFiles); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers-types.d.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers-types.d.ts deleted file mode 100644 index 8788325666a..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers-types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module jasmine { - interface Matchers { - toExistAsAFile(remove = true): boolean; - toExistAsABuild(remove = true): boolean; - toExistAsAnArtifact(remove = true): boolean; - } -} \ No newline at end of file diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers.ts deleted file mode 100644 index 4767254b2de..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/jasmine-custom-matchers.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {sync as deleteEmpty} from 'delete-empty'; -import {existsSync, unlinkSync} from 'fs'; -import {join} from 'path'; -import {AIO_DOWNLOADS_DIR} from '../common/constants'; -import {computeShortSha} from '../common/utils'; -import {SHA} from './constants'; -import {helper} from './helper'; - -function checkFile(filePath: string, remove: boolean): boolean { - const exists = existsSync(filePath); - if (exists && remove) { - // if we expected the file to exist then we remove it to prevent leftover file errors - unlinkSync(filePath); - } - return exists; -} - -function getArtifactPath(prNum: number, sha: string = SHA): string { - return `${AIO_DOWNLOADS_DIR}/${prNum}-${computeShortSha(sha)}-aio-snapshot.tgz`; -} - -function checkFiles(prNum: number, isPublic: boolean, sha: string, isLegacy: boolean, remove: boolean) { - const files = ['/index.html', '/foo/bar.js']; - const prPath = helper.getPrDir(prNum, isPublic); - const shaPath = helper.getShaDir(prPath, sha, isLegacy); - - const existingFiles: string[] = []; - const missingFiles: string[] = []; - files - .map(file => join(shaPath, file)) - .forEach(file => (checkFile(file, remove) ? existingFiles : missingFiles).push(file)); - - deleteEmpty(prPath); - - return { existingFiles, missingFiles }; -} - -class ToExistAsAFile implements jasmine.CustomMatcher { - public compare(actual: string, remove = true): jasmine.CustomMatcherResult { - const pass = checkFile(actual, remove); - return { - message: `Expected file at "${actual}" ${pass ? 'not' : ''} to exist`, - pass, - }; - } -} - -class ToExistAsAnArtifact implements jasmine.CustomMatcher { - public compare(actual: {prNum: number, sha?: string}, remove = true): jasmine.CustomMatcherResult { - const { prNum, sha = SHA } = actual; - const filePath = getArtifactPath(prNum, sha); - const pass = checkFile(filePath, remove); - return { - message: `Expected artifact "PR:${prNum}, SHA:${sha}, FILE:${filePath}" ${pass ? 'not' : '\b'} to exist`, - pass, - }; - } -} - -class ToExistAsABuild implements jasmine.CustomMatcher { - public compare(actual: {prNum: number, isPublic?: boolean, sha?: string, isLegacy?: boolean}, remove = true): - jasmine.CustomMatcherResult { - const {prNum, isPublic = true, sha = SHA, isLegacy = false} = actual; - const {missingFiles} = checkFiles(prNum, isPublic, sha, isLegacy, remove); - return { - message: `Expected files for build "PR:${prNum}, SHA:${sha}" to exist:\n` + - missingFiles.map(file => ` - ${file}`).join('\n'), - pass: missingFiles.length === 0, - }; - } - public negativeCompare(actual: {prNum: number, isPublic?: boolean, sha?: string, isLegacy?: boolean}): - jasmine.CustomMatcherResult { - const {prNum, isPublic = true, sha = SHA, isLegacy = false} = actual; - const { existingFiles } = checkFiles(prNum, isPublic, sha, isLegacy, false); - return { - message: `Expected files for build "PR:${prNum}, SHA:${sha}" not to exist:\n` + - existingFiles.map(file => ` - ${file}`).join('\n'), - pass: existingFiles.length === 0, - }; - } - -} - -export const customMatchers = { - toExistAsABuild: () => new ToExistAsABuild(), - toExistAsAFile: () => new ToExistAsAFile(), - toExistAsAnArtifact: () => new ToExistAsAnArtifact(), -}; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/mock-external-apis.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/mock-external-apis.ts deleted file mode 100644 index 4481f1ce2c8..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/mock-external-apis.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* tslint:disable:max-line-length */ -import * as nock from 'nock'; -import * as tar from 'tar-stream'; -import {gzipSync} from 'zlib'; -import {getEnvVar, Logger} from '../common/utils'; -import {BuildNums, PipelineIds, PrNums, SHA} from './constants'; - -// We are using the `nock` library to fake responses from REST requests, when testing. -// This is necessary, because the test preview-server runs as a separate node process to -// the test harness, so we do not have direct access to the code (e.g. for mocking). -// (See also 'lib/verify-setup/start-test-preview-server.ts'.) - -// Each of the potential requests to an external API (e.g. Github or CircleCI) are mocked -// 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. - -const logger = new Logger('mock-external-apis'); - -const AIO_CIRCLE_CI_TOKEN = getEnvVar('AIO_CIRCLE_CI_TOKEN'); -const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN'); - -const AIO_ARTIFACT_PATH = getEnvVar('AIO_ARTIFACT_PATH'); -const AIO_GITHUB_ORGANIZATION = getEnvVar('AIO_GITHUB_ORGANIZATION'); -const AIO_GITHUB_REPO = getEnvVar('AIO_GITHUB_REPO'); -const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL'); -const AIO_GITHUB_TEAM_SLUGS = getEnvVar('AIO_GITHUB_TEAM_SLUGS').split(','); - -const ACTIVE_TRUSTED_USER = 'active-trusted-user'; -const INACTIVE_TRUSTED_USER = 'inactive-trusted-user'; -const UNTRUSTED_USER = 'untrusted-user'; - -const BASIC_BUILD_INFO = { - name: 'test_job', - organization: {name: AIO_GITHUB_ORGANIZATION}, - project: {name: AIO_GITHUB_REPO}, - pipeline: {id: PipelineIds.PIPELINE_INFO_OK}, -}; -const BASIC_PIPELINE_INFO = { - id: PipelineIds.PIPELINE_INFO_OK, - vcs: { - review_id: `${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`, - revision: SHA, - }, -}; - -const ISSUE_INFO_TRUSTED_LABEL = { labels: [{ name: AIO_TRUSTED_PR_LABEL }], user: { login: UNTRUSTED_USER } }; -const ISSUE_INFO_ACTIVE_TRUSTED_USER = { labels: [], user: { login: ACTIVE_TRUSTED_USER } }; -const ISSUE_INFO_INACTIVE_TRUSTED_USER = { labels: [], user: { login: INACTIVE_TRUSTED_USER } }; -const ISSUE_INFO_UNTRUSTED = { labels: [], user: { login: UNTRUSTED_USER } }; -const ACTIVE_STATE = { state: 'active' }; -const INACTIVE_STATE = { state: 'inactive' }; - -const TEST_TEAM_INFO = AIO_GITHUB_TEAM_SLUGS.map((slug, index) => ({ slug, id: index })); - -const CIRCLE_CI_API_HOST = 'https://circleci.com'; -const ARTIFACT_1 = { path: 'artifact-1', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-1`, _urlPath: '/artifacts/artifact-1' }; -const ARTIFACT_2 = { path: 'artifact-2', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-2`, _urlPath: '/artifacts/artifact-2' }; -const ARTIFACT_3 = { path: 'artifact-3', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-3`, _urlPath: '/artifacts/artifact-3' }; -const ARTIFACT_ERROR = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/error`, _urlPath: '/artifacts/error' }; -const ARTIFACT_404 = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/404`, _urlPath: '/artifacts/404' }; -const ARTIFACT_VALID_TRUSTED_USER = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/valid/user`, _urlPath: '/artifacts/valid/user' }; -const ARTIFACT_VALID_TRUSTED_LABEL = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/valid/label`, _urlPath: '/artifacts/valid/label' }; -const ARTIFACT_VALID_UNTRUSTED = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/valid/untrusted`, _urlPath: '/artifacts/valid/untrusted' }; - -const CIRCLE_CI_BUILD_INFO_URL = `/api/v2/project/gh/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}`; -const CIRCLE_CI_PIPELINE_INFO_URL = '/api/v2/pipeline'; - -const buildInfoUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/job/${buildNum}`; -const buildArtifactsUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/${buildNum}/artifacts`; -const pipelineInfoUrl = (pipelineId: string) => `${CIRCLE_CI_PIPELINE_INFO_URL}/${pipelineId}`; -const buildInfo = (pipelineId: string) => ({ ...BASIC_BUILD_INFO, pipeline: { id: pipelineId } }); - -const GITHUB_API_HOST = 'https://api.github.com'; -const GITHUB_ISSUES_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/issues`; -const GITHUB_PULLS_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/pulls`; -const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`; - -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 getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`; -const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`; - -const setUpCircleCiApiForBuild = - (buildNum: number, pipelineId: string, prNum = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, sha = SHA) => { - circleCiApi.get(buildInfoUrl(buildNum)).reply(200, buildInfo(pipelineId)); - circleCiApi.get(pipelineInfoUrl(pipelineId)).reply(200, { - id: pipelineId, - vcs: { review_id: `${prNum}`, revision: sha }, - }); - }; - -const createArchive = (buildNum: number, prNum: number, sha: string) => { - logger.log('createArchive', buildNum, prNum, sha); - const pack = tar.pack(); - pack.entry({name: 'index.html'}, `BUILD: ${buildNum} | PR: ${prNum} | SHA: ${sha} | File: /index.html`); - pack.entry({name: 'foo/bar.js'}, `BUILD: ${buildNum} | PR: ${prNum} | SHA: ${sha} | File: /foo/bar.js`); - pack.finalize(); - const zip = gzipSync(pack.read()); - return zip; -}; - -// Create request scopes -const circleCiApi = nock(CIRCLE_CI_API_HOST).persist().matchHeader('Circle-Token', AIO_CIRCLE_CI_TOKEN); -const githubApi = nock(GITHUB_API_HOST).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`); - -////////////////////////////// - -// GENERAL responses -githubApi.get(GITHUB_TEAMS_URL + '?page=1&per_page=100').reply(200, TEST_TEAM_INFO); -githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200); -circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_OK)).reply(200, BASIC_PIPELINE_INFO); - -// BUILD INFO errors -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_ERROR)).replyWithError('BUILD_INFO_ERROR'); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_404)).reply(404, 'BUILD_INFO_404'); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_ORG)).reply(200, { ...BASIC_BUILD_INFO, organization: { name: 'bad' } }); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_REPO)).reply(200, { ...BASIC_BUILD_INFO, project: { name: 'bad' } }); - -// PIPELINE INFO errors -circleCiApi.get(buildInfoUrl(BuildNums.PIPELINE_INFO_ERROR)).reply(200, { ...BASIC_BUILD_INFO, pipeline: { id: PipelineIds.PIPELINE_INFO_ERROR } }); -circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_ERROR)).replyWithError('PIPELINE_INFO_ERROR'); - -circleCiApi.get(buildInfoUrl(BuildNums.PIPELINE_INFO_404)).reply(200, { ...BASIC_BUILD_INFO, pipeline: { id: PipelineIds.PIPELINE_INFO_404 } }); -circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_ERROR)).reply(404, 'PIPELINE_INFO_404'); - -// CHANGED FILE errors -setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_ERROR, PipelineIds.CHANGED_FILES_ERROR, PrNums.CHANGED_FILES_ERROR); -githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_ERROR)).replyWithError('CHANGED_FILES_ERROR'); -setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_404, PipelineIds.CHANGED_FILES_404, PrNums.CHANGED_FILES_404); -githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_404)).reply(404, 'CHANGED_FILES_404'); -setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_NONE, PipelineIds.CHANGED_FILES_NONE, PrNums.CHANGED_FILES_NONE); -githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_NONE)).reply(200, []); - -// ARTIFACT URL errors -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_ERROR)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_ERROR)).replyWithError('BUILD_ARTIFACTS_ERROR'); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_404)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_404)).reply(404, 'BUILD_ARTIFACTS_ERROR'); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, { items: [] }); -circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, { items: [ARTIFACT_1, ARTIFACT_2, ARTIFACT_3] }); - -// ARTIFACT DOWNLOAD errors -circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, { items: [ARTIFACT_ERROR] }); -circleCiApi.get(ARTIFACT_ERROR._urlPath).replyWithError(ARTIFACT_ERROR._urlPath); -circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_404)).reply(200, BASIC_BUILD_INFO); -circleCiApi.get(buildArtifactsUrl(BuildNums.DOWNLOAD_ARTIFACT_404)).reply(200, [ARTIFACT_404]); -circleCiApi.get(ARTIFACT_ERROR._urlPath).reply(404, ARTIFACT_ERROR._urlPath); - -// TRUST CHECK errors -setUpCircleCiApiForBuild(BuildNums.TRUST_CHECK_ERROR, PipelineIds.TRUST_CHECK_ERROR, PrNums.TRUST_CHECK_ERROR); -githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_ERROR)).reply(200, [{ filename: 'aio/a' }]); -circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ERROR)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] }); -githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_ERROR)).replyWithError('TRUST_CHECK_ERROR'); - -// ACTIVE TRUSTED USER response -circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, BASIC_BUILD_INFO); -githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, [{ filename: 'aio/a' }]); -circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] }); -circleCiApi.get(ARTIFACT_VALID_TRUSTED_USER._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA)); -githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, ISSUE_INFO_ACTIVE_TRUSTED_USER); -githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_STATE); - -// TRUSTED LABEL response -circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, BASIC_BUILD_INFO); -githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, [{ filename: 'aio/a' }]); -circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_LABEL] }); -circleCiApi.get(ARTIFACT_VALID_TRUSTED_LABEL._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_TRUSTED_LABEL, PrNums.TRUST_CHECK_TRUSTED_LABEL, SHA)); -githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, ISSUE_INFO_TRUSTED_LABEL); -githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_STATE); - -// INACTIVE TRUSTED USER response -circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, BASIC_BUILD_INFO); -githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, [{ filename: 'aio/a' }]); -circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] }); -githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, ISSUE_INFO_INACTIVE_TRUSTED_USER); -githubApi.get(getTeamMembershipUrl(0, INACTIVE_TRUSTED_USER)).reply(200, INACTIVE_STATE); - -// UNTRUSTED reponse -setUpCircleCiApiForBuild(BuildNums.TRUST_CHECK_UNTRUSTED, PipelineIds.TRUST_CHECK_UNTRUSTED, PrNums.TRUST_CHECK_UNTRUSTED); -githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_UNTRUSTED)).reply(200, [{ filename: 'aio/a' }]); -circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_UNTRUSTED)).reply(200, { items: [ARTIFACT_VALID_UNTRUSTED] }); -circleCiApi.get(ARTIFACT_VALID_UNTRUSTED._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_UNTRUSTED, PrNums.TRUST_CHECK_UNTRUSTED, SHA)); -githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_UNTRUSTED)).reply(200, ISSUE_INFO_UNTRUSTED); -githubApi.get(getTeamMembershipUrl(0, UNTRUSTED_USER)).reply(404); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/nginx.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/nginx.e2e.ts deleted file mode 100644 index 8fe35eb81e9..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/nginx.e2e.ts +++ /dev/null @@ -1,395 +0,0 @@ -// Imports -import * as path from 'path'; -import {rm} from 'shelljs'; -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 {PrNums} from './constants'; -import {helper as h} from './helper'; -import {customMatchers} from './jasmine-custom-matchers'; - -// Tests -describe(`nginx`, () => { - - beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000); - beforeEach(() => jasmine.addMatchers(customMatchers)); - afterEach(() => h.cleanUp()); - - - it('should redirect HTTP to HTTPS', async () => { - const httpHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTP}`; - const httpsHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTPS}`; - const urlMap = { - [`http://${httpHost}/`]: `https://${httpsHost}/`, - [`http://${httpHost}/foo`]: `https://${httpsHost}/foo`, - [`http://foo.${httpHost}/`]: `https://foo.${httpsHost}/`, - }; - - const verifyRedirection = async (fromUrl: string, toUrl: string) => { - const result = await h.runCmd(`curl -i ${fromUrl}`); - h.verifyResponse(307)(result); - - const headers = result.stdout.split(/(?:\r?\n){2,}/)[0]; - expect(headers).toContain(`Location: ${toUrl}`); - }; - - await Promise.all(Object.entries(urlMap).map(urls => verifyRedirection(...urls))); - }); - - - h.runForAllSupportedSchemes((scheme, port) => describe(`(on ${scheme.toUpperCase()})`, () => { - const hostname = AIO_NGINX_HOSTNAME; - const host = `${hostname}:${port}`; - const pr = 9; - const sha9 = '9'.repeat(40); - const sha0 = '0'.repeat(40); - const shortSha9 = computeShortSha(sha9); - const shortSha0 = computeShortSha(sha0); - - - describe(`pr-.${host}/*`, () => { - - describe('(for public builds)', () => { - - beforeEach(() => { - h.createDummyBuild(pr, sha9); - h.createDummyBuild(pr, sha0); - }); - - afterEach(() => { - expect({ prNum: pr, sha: sha9 }).toExistAsABuild(); - expect({ prNum: pr, sha: sha0 }).toExistAsABuild(); - }); - - - it('should return /index.html', async () => { - const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`; - const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); - - await Promise.all([ - h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)), - h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)), - h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)), - ]); - }); - - - it('should return /index.html (for legacy builds)', async () => { - const origin = `${scheme}://pr${pr}-${sha9}.${host}`; - const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); - - h.createDummyBuild(pr, sha9, true, false, true); - - await Promise.all([ - h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)), - h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)), - h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)), - ]); - - expect({ prNum: pr, sha: sha9, isLegacy: true }).toExistAsABuild(); - }); - - - it('should return /foo/bar.js', async () => { - const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`); - - await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`). - then(h.verifyResponse(200, bodyRegex)); - }); - - - it('should return /foo/bar.js (for legacy builds)', async () => { - const origin = `${scheme}://pr${pr}-${sha9}.${host}`; - const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`); - - h.createDummyBuild(pr, sha9, true, false, true); - - await h.runCmd(`curl -iL ${origin}/foo/bar.js`).then(h.verifyResponse(200, bodyRegex)); - - expect({ prNum: pr, sha: sha9, isLegacy: true }).toExistAsABuild(); - }); - - - it('should respond with 403 for directories', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/`).then(h.verifyResponse(403)), - h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo`).then(h.verifyResponse(403)), - ]); - }); - - - it('should respond with 404 for unknown paths to files', async () => { - await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`). - then(h.verifyResponse(404)); - }); - - - it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', async () => { - const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`; - const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); - - await Promise.all([ - h.runCmd(`curl -iL ${origin}/foo/baz`).then(h.verifyResponse(200, bodyRegex)), - h.runCmd(`curl -iL ${origin}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)), - ]); - }); - - - it('should respond with 404 for unknown PRs/SHAs', async () => { - const otherPr = 54321; - const otherShortSha = computeShortSha('8'.repeat(40)); - - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://pr${pr}9-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}9.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherShortSha}.${host}`).then(h.verifyResponse(404)), - ]); - }); - - - it('should respond with 404 if the subdomain format is wrong', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://xpr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://prx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://xx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://p${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://r${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${pr}${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${pr}_${shortSha9}.${host}`).then(h.verifyResponse(404)), - ]); - }); - - - it('should reject PRs with leading zeros', async () => { - await h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`). - then(h.verifyResponse(404)); - }); - - - it('should accept SHAs with leading zeros (but not trim the zeros)', async () => { - const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); - const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`); - - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://pr${pr}-0${shortSha9}.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)), - h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)), - ]); - }); - - }); - - - describe('(for hidden builds)', () => { - - it('should respond with 404 for any file or directory', async () => { - const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`; - const assert404 = h.verifyResponse(404); - - h.createDummyBuild(pr, sha9, false); - - await Promise.all([ - h.runCmd(`curl -iL ${origin}/index.html`).then(assert404), - h.runCmd(`curl -iL ${origin}/`).then(assert404), - h.runCmd(`curl -iL ${origin}`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo/bar.js`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo/`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo`).then(assert404), - ]); - - expect({ prNum: pr, sha: sha9, isPublic: false }).toExistAsABuild(); - }); - - - it('should respond with 404 for any file or directory (for legacy builds)', async () => { - const origin = `${scheme}://pr${pr}-${sha9}.${host}`; - const assert404 = h.verifyResponse(404); - - h.createDummyBuild(pr, sha9, false, false, true); - - await Promise.all([ - h.runCmd(`curl -iL ${origin}/index.html`).then(assert404), - h.runCmd(`curl -iL ${origin}/`).then(assert404), - h.runCmd(`curl -iL ${origin}`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo/bar.js`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo/`).then(assert404), - h.runCmd(`curl -iL ${origin}/foo`).then(assert404), - ]); - - expect({ prNum: pr, sha: sha9, isPublic: false, isLegacy: true }).toExistAsABuild(); - }); - - }); - - }); - - - describe(`${host}/health-check`, () => { - - it('should respond with 200', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://${host}/health-check`).then(h.verifyResponse(200)), - h.runCmd(`curl -iL ${scheme}://${host}/health-check/`).then(h.verifyResponse(200)), - ]); - }); - - - it('should respond with 404 if the path does not match exactly', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://${host}/health-check/foo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/health-check-foo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/health-checknfoo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/foo/health-check`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/foo-health-check`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/foonhealth-check`).then(h.verifyResponse(404)), - ]); - }); - - }); - - - 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)), - h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse(405)), - ]); - }); - - - 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`, () => { - - it('should disallow non-POST requests', async () => { - const url = `${scheme}://${host}/circle-build`; - - await Promise.all([ - h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)), - ]); - }); - - - it('should pass requests through to the preview server', async () => { - await h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`). - then(h.verifyResponse(400, /Incorrect body content. Expected JSON/)); - }); - - - it('should respond with 404 for unknown paths', async () => { - const cmdPrefix = `curl -iLX POST ${scheme}://${host}`; - - await Promise.all([ - h.runCmd(`${cmdPrefix}/foo/circle-build/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/foo-circle-build/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/fooncircle-build/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/circle-build/foo/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/circle-build-foo/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/circle-buildnfoo/`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/circle-build/pr`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)), - ]); - }); - - }); - - - describe(`${host}/pr-updated`, () => { - const url = `${scheme}://${host}/pr-updated`; - - - it('should disallow non-POST requests', async () => { - await Promise.all([ - h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)), - h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)), - ]); - }); - - - it('should pass requests through to the preview server', async () => { - await h.runCmd(`curl -iLX POST --header "Content-Type: application/json" ${url}`). - then(h.verifyResponse(400, /Missing or empty 'number' field/)); - }); - - - it('should respond with 404 for unknown paths', async () => { - const cmdPrefix = `curl -iLX POST ${scheme}://${host}`; - - await Promise.all([ - h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)), - ]); - }); - - }); - - - describe(`${host}/*`, () => { - - beforeEach(() => { - ['index.html', 'foo.js', 'foo/index.html'].forEach(relFilePath => { - const absFilePath = path.join(AIO_BUILDS_DIR, relFilePath); - h.writeFile(absFilePath, {content: `File: /${relFilePath}`}); - }); - }); - - it('should respond with 404 for unknown URLs (even if the resource exists)', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${scheme}://${host}/index.html`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://foo.${host}/index.html`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://foo.${host}/`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://foo.${host}`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/foo.js`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${scheme}://${host}/foo/index.html`).then(h.verifyResponse(404)), - ]); - }); - - afterEach(() => { - ['index.html', 'foo.js', 'foo/index.html', 'foo'].forEach(relFilePath => { - const absFilePath = path.join(AIO_BUILDS_DIR, relFilePath); - rm('-r', absFilePath); - }); - }); - - }); - - })); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/preview-server.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/preview-server.e2e.ts deleted file mode 100644 index 7237707cc30..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/preview-server.e2e.ts +++ /dev/null @@ -1,566 +0,0 @@ -// Imports -import * as fs from 'fs'; -import {join} from 'path'; -import {AIO_PREVIEW_SERVER_HOSTNAME, AIO_PREVIEW_SERVER_PORT, AIO_WWW_USER} from '../common/env-variables'; -import {computeShortSha} from '../common/utils'; -import {ALT_SHA, BuildNums, PrNums, SHA, SIMILAR_SHA} from './constants'; -import {helper as h, makeCurl, payload} from './helper'; -import {customMatchers} from './jasmine-custom-matchers'; - -// Tests -describe('preview-server', () => { - const hostname = AIO_PREVIEW_SERVER_HOSTNAME; - const port = AIO_PREVIEW_SERVER_PORT; - const host = `http://${hostname}:${port}`; - - beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000); - beforeEach(() => jasmine.addMatchers(customMatchers)); - 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`, () => { - const curl = makeCurl(`${host}/circle-build`); - - - it('should disallow non-POST requests', async () => { - const bodyRegex = /^Unknown resource/; - - await Promise.all([ - curl({method: 'GET'}).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 () => { - await Promise.all([ - curl({url: `${host}/foo/circle-build`}).then(h.verifyResponse(404)), - curl({url: `${host}/foo-circle-build`}).then(h.verifyResponse(404)), - curl({url: `${host}/fooncircle-build`}).then(h.verifyResponse(404)), - curl({url: `${host}/circle-build/foo`}).then(h.verifyResponse(404)), - curl({url: `${host}/circle-build-foo`}).then(h.verifyResponse(404)), - curl({url: `${host}/circle-buildnfoo`}).then(h.verifyResponse(404)), - curl({url: `${host}/circle-build/pr`}).then(h.verifyResponse(404)), - curl({url: `${host}/circle-build42`}).then(h.verifyResponse(404)), - ]); - }); - - it('should respond with 400 if the body is not valid', async () => { - await Promise.all([ - curl({ data: '' }).then(h.verifyResponse(400)), - curl({ data: {} }).then(h.verifyResponse(400)), - curl({ data: { payload: {} } }).then(h.verifyResponse(400)), - curl({ data: { payload: { build_num: 1 } } }).then(h.verifyResponse(400)), - curl({ data: { payload: { build_num: 1, build_parameters: {} } } }).then(h.verifyResponse(400)), - curl(payload(0)).then(h.verifyResponse(400)), - curl(payload(-1)).then(h.verifyResponse(400)), - ]); - }); - - it('should respond with 500 if any of the CircleCI API requests errors', async () => { - await curl(payload(BuildNums.BUILD_INFO_ERROR)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.BUILD_INFO_404)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.PIPELINE_INFO_ERROR)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.PIPELINE_INFO_404)).then(h.verifyResponse(500)); - }); - - it('should respond with 500 if the github org from CircleCI does not match what is configured', async () => { - await curl(payload(BuildNums.BUILD_INFO_INVALID_GH_ORG)).then(h.verifyResponse(500)); - }); - - it('should respond with 500 if the github repo from CircleCI does not match what is configured', async () => { - await curl(payload(BuildNums.BUILD_INFO_INVALID_GH_REPO)).then(h.verifyResponse(500)); - }); - - it('should respond with 500 if the github files API errors', async () => { - await curl(payload(BuildNums.CHANGED_FILES_ERROR)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.CHANGED_FILES_404)).then(h.verifyResponse(500)); - }); - - it('should respond with 204 if no significant files are changed by the PR', async () => { - await curl(payload(BuildNums.CHANGED_FILES_NONE)).then(h.verifyResponse(204)); - }); - - it('should respond with 500 if the CircleCI artifact API fails', async () => { - await curl(payload(BuildNums.BUILD_ARTIFACTS_ERROR)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.BUILD_ARTIFACTS_404)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.BUILD_ARTIFACTS_EMPTY)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.BUILD_ARTIFACTS_MISSING)).then(h.verifyResponse(500)); - }); - - it('should respond with 500 if fetching the artifact errors', async () => { - await curl(payload(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).then(h.verifyResponse(500)); - await curl(payload(BuildNums.DOWNLOAD_ARTIFACT_404)).then(h.verifyResponse(500)); - }); - - it('should respond with 500 if the GH trusted API fails', async () => { - await curl(payload(BuildNums.TRUST_CHECK_ERROR)).then(h.verifyResponse(500)); - expect({ prNum: PrNums.TRUST_CHECK_ERROR }).toExistAsAnArtifact(); - }); - - it('should respond with 201 if a new public build is created', async () => { - await curl(payload(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).then(h.verifyResponse(201)); - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER }).toExistAsABuild(); - }); - - it('should respond with 202 if a new private build is created', async () => { - await curl(payload(BuildNums.TRUST_CHECK_UNTRUSTED)).then(h.verifyResponse(202)); - expect({ prNum: PrNums.TRUST_CHECK_UNTRUSTED, isPublic: false }).toExistAsABuild(); - }); - - [true, false].forEach(isPublic => { - const build = isPublic ? BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER : BuildNums.TRUST_CHECK_UNTRUSTED; - const prNum = isPublic ? PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER : PrNums.TRUST_CHECK_UNTRUSTED; - const label = isPublic ? 'public' : 'non-public'; - const overwriteRe = RegExp(`^Request to overwrite existing ${label} directory`); - const statusCode = isPublic ? 201 : 202; - - describe(`for ${label} builds`, () => { - - it('should extract the contents of the build artifact', async () => { - await curl(payload(build)) - .then(h.verifyResponse(statusCode)); - expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic)) - .toContain(`PR: ${prNum} | SHA: ${SHA} | File: /index.html`); - expect(h.readBuildFile(prNum, SHA, 'foo/bar.js', isPublic)) - .toContain(`PR: ${prNum} | SHA: ${SHA} | File: /foo/bar.js`); - expect({ prNum, isPublic }).toExistAsABuild(); - }); - - it(`should create files/directories owned by '${AIO_WWW_USER}'`, async () => { - await curl(payload(build)) - .then(h.verifyResponse(statusCode)); - - const shaDir = h.getShaDir(h.getPrDir(prNum, isPublic), SHA); - const { stdout: allFiles } = await h.runCmd(`find ${shaDir}`); - const { stdout: userFiles } = await h.runCmd(`find ${shaDir} -user ${AIO_WWW_USER}`); - - expect(userFiles).toBe(allFiles); - expect(userFiles).toContain(shaDir); - expect(userFiles).toContain(join(shaDir, 'index.html')); - expect(userFiles).toContain(join(shaDir, 'foo', 'bar.js')); - - expect({ prNum, isPublic }).toExistAsABuild(); - }); - - it('should delete the build artifact file', async () => { - await curl(payload(build)) - .then(h.verifyResponse(statusCode)); - expect({ prNum, SHA }).not.toExistAsAnArtifact(); - expect({ prNum, isPublic }).toExistAsABuild(); - }); - - it('should make the build directory non-writable', async () => { - await curl(payload(build)) - .then(h.verifyResponse(statusCode)); - - // See https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588. - const isNotWritable = (fileOrDir: string) => { - const mode = fs.statSync(fileOrDir).mode; - // tslint:disable-next-line: no-bitwise - return !(mode & parseInt('222', 8)); - }; - - const shaDir = h.getShaDir(h.getPrDir(prNum, isPublic), SHA); - expect(isNotWritable(shaDir)).toBe(true); - expect(isNotWritable(join(shaDir, 'index.html'))).toBe(true); - expect(isNotWritable(join(shaDir, 'foo', 'bar.js'))).toBe(true); - - expect({ prNum, isPublic }).toExistAsABuild(); - }); - - it('should ignore a legacy 40-chars long build directory (even if it starts with the same chars)', - async () => { - // It is possible that 40-chars long build directories exist, if they had been deployed - // before implementing the shorter build directory names. In that case, we don't want the - // second (shorter) name to be considered the same as the old one (even if they originate - // from the same SHA). - - h.createDummyBuild(prNum, SHA, isPublic, false, true); - h.writeBuildFile(prNum, SHA, 'index.html', 'My content', isPublic, true); - expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic, true)).toBe('My content'); - - await curl(payload(build)) - .then(h.verifyResponse(statusCode)); - - expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic, false)).toContain('index.html'); - expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic, true)).toBe('My content'); - - expect({ prNum, isPublic, sha: SHA, isLegacy: false }).toExistAsABuild(); - expect({ prNum, isPublic, sha: SHA, isLegacy: true }).toExistAsABuild(); - }); - - it(`should not overwrite existing builds`, async () => { - // setup a build already in place - h.createDummyBuild(prNum, SHA, isPublic); - // distinguish this build from the downloaded one - h.writeBuildFile(prNum, SHA, 'index.html', 'My content', isPublic); - await curl(payload(build)).then(h.verifyResponse(409, overwriteRe)); - expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic)).toBe('My content'); - expect({ prNum, isPublic }).toExistAsABuild(); - expect({ prNum }).toExistAsAnArtifact(); - }); - - it(`should not overwrite existing builds (even if the SHA is different)`, async () => { - // Since only the first few characters of the SHA are used, it is possible for two different - // SHAs to correspond to the same directory. In that case, we don't want the second SHA to - // overwrite the first. - expect(SIMILAR_SHA).not.toEqual(SHA); - expect(computeShortSha(SIMILAR_SHA)).toEqual(computeShortSha(SHA)); - h.createDummyBuild(prNum, SIMILAR_SHA, isPublic); - expect(h.readBuildFile(prNum, SIMILAR_SHA, 'index.html', isPublic)).toContain('index.html'); - h.writeBuildFile(prNum, SIMILAR_SHA, 'index.html', 'My content', isPublic); - expect(h.readBuildFile(prNum, SIMILAR_SHA, 'index.html', isPublic)).toBe('My content'); - - await curl(payload(build)).then(h.verifyResponse(409, overwriteRe)); - expect(h.readBuildFile(prNum, SIMILAR_SHA, 'index.html', isPublic)).toBe('My content'); - expect({ prNum, isPublic, sha: SIMILAR_SHA }).toExistAsABuild(); - expect({ prNum, sha: SIMILAR_SHA }).toExistAsAnArtifact(); - }); - - it('should only delete the SHA directory on error (for existing PR)', async () => { - h.createDummyBuild(prNum, ALT_SHA, isPublic); - await curl(payload(BuildNums.TRUST_CHECK_ERROR)).then(h.verifyResponse(500)); - expect({ prNum: PrNums.TRUST_CHECK_ERROR }).toExistAsAnArtifact(); - expect({ prNum, isPublic, sha: SHA }).not.toExistAsABuild(); - expect({ prNum, isPublic, sha: ALT_SHA }).toExistAsABuild(); - }); - - describe('when the PR\'s visibility has changed', () => { - - it('should update the PR\'s visibility', async () => { - h.createDummyBuild(prNum, ALT_SHA, !isPublic); - await curl(payload(build)).then(h.verifyResponse(statusCode)); - expect({ prNum, isPublic }).toExistAsABuild(); - expect({ prNum, isPublic, sha: ALT_SHA }).toExistAsABuild(); - }); - - - it('should not overwrite existing builds (but keep the updated visibility)', async () => { - h.createDummyBuild(prNum, SHA, !isPublic); - await curl(payload(build)).then(h.verifyResponse(409)); - expect({ prNum, isPublic }).toExistAsABuild(); - expect({ prNum, isPublic: !isPublic }).not.toExistAsABuild(); - // since it errored we didn't clear up the downloaded artifact - perhaps we should? - expect({ prNum }).toExistAsAnArtifact(); - }); - - - it('should reject the request if it fails to update the PR\'s visibility', async () => { - // One way to cause an error is to have both a public and a hidden directory for the same PR. - h.createDummyBuild(prNum, ALT_SHA, isPublic); - h.createDummyBuild(prNum, ALT_SHA, !isPublic); - - const errorRegex = new RegExp(`^Request to move '${h.getPrDir(prNum, !isPublic)}' ` + - `to existing directory '${h.getPrDir(prNum, isPublic)}'.`); - - await curl(payload(build)).then(h.verifyResponse(409, errorRegex)); - - expect({ prNum, isPublic }).not.toExistAsABuild(); - - // The bad folders should have been deleted - expect({ prNum, sha: ALT_SHA, isPublic }).toExistAsABuild(); - expect({ prNum, sha: ALT_SHA, isPublic: !isPublic }).toExistAsABuild(); - - // since it errored we didn't clear up the downloaded artifact - perhaps we should? - expect({ prNum }).toExistAsAnArtifact(); - }); - }); - }); - }); - }); - - - describe(`${host}/health-check`, () => { - - it('should respond with 200', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${host}/health-check`).then(h.verifyResponse(200)), - h.runCmd(`curl -iL ${host}/health-check/`).then(h.verifyResponse(200)), - ]); - }); - - - it('should respond with 404 if the path does not match exactly', async () => { - await Promise.all([ - h.runCmd(`curl -iL ${host}/health-check/foo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${host}/health-check-foo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${host}/health-checknfoo`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${host}/foo/health-check`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${host}/foo-health-check`).then(h.verifyResponse(404)), - h.runCmd(`curl -iL ${host}/foonhealth-check`).then(h.verifyResponse(404)), - ]); - }); - - }); - - - describe(`${host}/pr-updated`, () => { - const curl = makeCurl(`${host}/pr-updated`); - - it('should disallow non-POST requests', async () => { - const bodyRegex = /^Unknown resource in request/; - - await Promise.all([ - curl({method: 'GET'}).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 400 for requests without a payload', async () => { - const bodyRegex = /^Missing or empty 'number' field in request/; - - await Promise.all([ - curl({ data: '' }).then(h.verifyResponse(400, bodyRegex)), - curl({ data: {} }).then(h.verifyResponse(400, bodyRegex)), - ]); - }); - - - it('should respond with 400 for requests without a \'number\' field', async () => { - const bodyRegex = /^Missing or empty 'number' field in request/; - - await Promise.all([ - curl({ data: {} }).then(h.verifyResponse(400, bodyRegex)), - curl({ data: { number: null} }).then(h.verifyResponse(400, bodyRegex)), - ]); - }); - - - it('should reject requests for which checking the PR visibility fails', async () => { - await curl({ data: { number: PrNums.TRUST_CHECK_ERROR } }).then(h.verifyResponse(500, /TRUST_CHECK_ERROR/)); - }); - - - it('should respond with 404 for unknown paths', async () => { - const mockPayload = JSON.stringify({number: 1}); // MockExternalApiFlags.TRUST_CHECK_ACTIVE_TRUSTED_USER }); - const cmdPrefix = `curl -iLX POST --data "${mockPayload}" ${host}`; - - await Promise.all([ - h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)), - h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)), - ]); - }); - - - it('should do nothing if PR\'s visibility is already up-to-date', async () => { - const publicPr = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const hiddenPr = PrNums.TRUST_CHECK_UNTRUSTED; - - const checkVisibilities = (remove: boolean) => { - // Public build is already public. - expect({ prNum: publicPr, isPublic: false }).not.toExistAsABuild(remove); - expect({ prNum: publicPr, isPublic: true }).toExistAsABuild(remove); - // Hidden build is already hidden. - expect({ prNum: hiddenPr, isPublic: false }).toExistAsABuild(remove); - expect({ prNum: hiddenPr, isPublic: true }).not.toExistAsABuild(remove); - }; - - h.createDummyBuild(publicPr, SHA, true); - h.createDummyBuild(hiddenPr, SHA, false); - checkVisibilities(false); - - await Promise.all([ - curl({ data: {number: +publicPr, action: 'foo' } }).then(h.verifyResponse(200)), - curl({ data: {number: +hiddenPr, action: 'foo' } }).then(h.verifyResponse(200)), - ]); - - // Visibilities should not have changed, because the specified action could not have triggered a change. - checkVisibilities(true); - }); - - - it('should do nothing if \'action\' implies no visibility change', async () => { - const publicPr = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const hiddenPr = PrNums.TRUST_CHECK_UNTRUSTED; - - const checkVisibilities = (remove: boolean) => { - // Public build is hidden atm. - expect({ prNum: publicPr, isPublic: false }).toExistAsABuild(remove); - expect({ prNum: publicPr, isPublic: true }).not.toExistAsABuild(remove); - // Hidden build is public atm. - expect({ prNum: hiddenPr, isPublic: false }).not.toExistAsABuild(remove); - expect({ prNum: hiddenPr, isPublic: true }).toExistAsABuild(remove); - }; - - h.createDummyBuild(publicPr, SHA, false); - h.createDummyBuild(hiddenPr, SHA, true); - checkVisibilities(false); - - await Promise.all([ - curl({ data: {number: +publicPr, action: 'foo' } }).then(h.verifyResponse(200)), - curl({ data: {number: +hiddenPr, action: 'foo' } }).then(h.verifyResponse(200)), - ]); - // Visibilities should not have changed, because the specified action could not have triggered a change. - checkVisibilities(true); - }); - - - describe('when the visiblity has changed', () => { - const publicPr = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const hiddenPr = PrNums.TRUST_CHECK_UNTRUSTED; - - beforeEach(() => { - // Create initial PR builds with opposite visibilities as the ones that will be reported: - // - The now public PR was previously hidden. - // - The now hidden PR was previously public. - h.createDummyBuild(publicPr, SHA, false); - h.createDummyBuild(hiddenPr, SHA, true); - - expect({ prNum: publicPr, isPublic: false }).toExistAsABuild(false); - expect({ prNum: publicPr, isPublic: true }).not.toExistAsABuild(false); - expect({ prNum: hiddenPr, isPublic: false }).not.toExistAsABuild(false); - expect({ prNum: hiddenPr, isPublic: true }).toExistAsABuild(false); - }); - afterEach(() => { - // Expect PRs' visibility to have been updated: - // - The public PR should be actually public (previously it was hidden). - // - The hidden PR should be actually hidden (previously it was public). - expect({ prNum: publicPr, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: publicPr, isPublic: true }).toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: false }).toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: true }).not.toExistAsABuild(); - }); - - - it('should update the PR\'s visibility (action: undefined)', async () => { - await Promise.all([ - curl({ data: {number: +publicPr } }).then(h.verifyResponse(200)), - curl({ data: {number: +hiddenPr } }).then(h.verifyResponse(200)), - ]); - }); - - - it('should update the PR\'s visibility (action: labeled)', async () => { - await Promise.all([ - curl({ data: {number: +publicPr, action: 'labeled' } }).then(h.verifyResponse(200)), - curl({ data: {number: +hiddenPr, action: 'labeled' } }).then(h.verifyResponse(200)), - ]); - }); - - - it('should update the PR\'s visibility (action: unlabeled)', async () => { - await Promise.all([ - curl({ data: {number: +publicPr, action: 'unlabeled' } }).then(h.verifyResponse(200)), - curl({ data: {number: +hiddenPr, action: 'unlabeled' } }).then(h.verifyResponse(200)), - ]); - }); - - }); - - }); - - - describe(`${host}/*`, () => { - - it('should respond with 404 for requests to unknown URLs', async () => { - const bodyRegex = /^Unknown resource/; - - await Promise.all([ - h.runCmd(`curl -iL ${host}/index.html`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iL ${host}/`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iL ${host}`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iLX PUT ${host}`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iLX POST ${host}`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iLX PATCH ${host}`).then(h.verifyResponse(404, bodyRegex)), - h.runCmd(`curl -iLX DELETE ${host}`).then(h.verifyResponse(404, bodyRegex)), - ]); - }); - - }); -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts deleted file mode 100644 index 83755dda3a4..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts +++ /dev/null @@ -1,269 +0,0 @@ -// Imports -import {AIO_NGINX_HOSTNAME} from '../common/env-variables'; -import {computeShortSha} from '../common/utils'; -import {ALT_SHA, BuildNums, PrNums, SHA} from './constants'; -import {helper as h, makeCurl, payload} from './helper'; -import {customMatchers} from './jasmine-custom-matchers'; - -// Tests -h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme.toUpperCase()})`, () => { - const hostname = AIO_NGINX_HOSTNAME; - const host = `${hostname}:${port}`; - const curlPrUpdated = makeCurl(`${scheme}://${host}/pr-updated`); - - const getFile = (pr: number, sha: string, file: string) => - h.runCmd(`curl -iL ${scheme}://pr${pr}-${computeShortSha(sha)}.${host}/${file}`); - const prUpdated = (prNum: number, action?: string) => curlPrUpdated({ data: { number: prNum, action } }); - const circleBuild = makeCurl(`${scheme}://${host}/circle-build`); - - beforeEach(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - jasmine.addMatchers(customMatchers); - }); - afterEach(() => h.cleanUp()); - - - describe('for a new/non-existing PR', () => { - - it('should be able to create and serve a public preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - - const regexPrefix = `^BUILD: ${BUILD} \\| PR: ${PR} \\| SHA: ${SHA} \\| File:`; - const idxContentRegex = new RegExp(`${regexPrefix} \\/index\\.html$`); - const barContentRegex = new RegExp(`${regexPrefix} \\/foo\\/bar\\.js$`); - - await circleBuild(payload(BUILD)).then(h.verifyResponse(201)); - await Promise.all([ - getFile(PR, SHA, 'index.html').then(h.verifyResponse(200, idxContentRegex)), - getFile(PR, SHA, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex)), - ]); - - expect({ prNum: PR }).toExistAsABuild(); - expect({ prNum: PR, isPublic: false }).not.toExistAsABuild(); - }); - - - it('should be able to create but not serve a hidden preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; - const PR = PrNums.TRUST_CHECK_UNTRUSTED; - - await circleBuild(payload(BUILD)).then(h.verifyResponse(202)); - await Promise.all([ - getFile(PR, SHA, 'index.html').then(h.verifyResponse(404)), - getFile(PR, SHA, 'foo/bar.js').then(h.verifyResponse(404)), - ]); - - expect({ prNum: PR }).not.toExistAsABuild(); - expect({ prNum: PR, isPublic: false }).toExistAsABuild(); - }); - - - it('should reject if verification fails', async () => { - const BUILD = BuildNums.TRUST_CHECK_ERROR; - const PR = PrNums.TRUST_CHECK_ERROR; - - await circleBuild(payload(BUILD)).then(h.verifyResponse(500)); - expect({ prNum: PR }).toExistAsAnArtifact(); - expect({ prNum: PR }).not.toExistAsABuild(); - expect({ prNum: PR, isPublic: false }).not.toExistAsABuild(); - }); - - - it('should be able to notify that a PR has been updated (and do nothing)', async () => { - await prUpdated(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER).then(h.verifyResponse(200)); - // The PR should still not exist. - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: true }).not.toExistAsABuild(); - }); - - }); - - - describe('for an existing PR', () => { - - it('should be able to create and serve a public preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - - const regexPrefix1 = `^PR: ${PR} \\| SHA: ${ALT_SHA} \\| File:`; - const idxContentRegex1 = new RegExp(`${regexPrefix1} \\/index\\.html$`); - const barContentRegex1 = new RegExp(`${regexPrefix1} \\/foo\\/bar\\.js$`); - - const regexPrefix2 = `^BUILD: ${BUILD} \\| PR: ${PR} \\| SHA: ${SHA} \\| File:`; - const idxContentRegex2 = new RegExp(`${regexPrefix2} \\/index\\.html$`); - const barContentRegex2 = new RegExp(`${regexPrefix2} \\/foo\\/bar\\.js$`); - - h.createDummyBuild(PR, ALT_SHA); - await circleBuild(payload(BUILD)).then(h.verifyResponse(201)); - await Promise.all([ - getFile(PR, ALT_SHA, 'index.html').then(h.verifyResponse(200, idxContentRegex1)), - getFile(PR, ALT_SHA, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex1)), - getFile(PR, SHA, 'index.html').then(h.verifyResponse(200, idxContentRegex2)), - getFile(PR, SHA, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex2)), - ]); - - expect({ prNum: PR, sha: SHA }).toExistAsABuild(); - expect({ prNum: PR, sha: ALT_SHA }).toExistAsABuild(); - }); - - - it('should be able to create but not serve a hidden preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; - const PR = PrNums.TRUST_CHECK_UNTRUSTED; - - h.createDummyBuild(PR, ALT_SHA, false); - await circleBuild(payload(BUILD)).then(h.verifyResponse(202)); - - await Promise.all([ - getFile(PR, ALT_SHA, 'index.html').then(h.verifyResponse(404)), - getFile(PR, ALT_SHA, 'foo/bar.js').then(h.verifyResponse(404)), - getFile(PR, SHA, 'index.html').then(h.verifyResponse(404)), - getFile(PR, SHA, 'foo/bar.js').then(h.verifyResponse(404)), - ]); - - expect({ prNum: PR, sha: SHA }).not.toExistAsABuild(); - expect({ prNum: PR, sha: SHA, isPublic: false }).toExistAsABuild(); - expect({ prNum: PR, sha: ALT_SHA }).not.toExistAsABuild(); - expect({ prNum: PR, sha: ALT_SHA, isPublic: false }).toExistAsABuild(); - }); - - - it('should reject if verification fails', async () => { - const BUILD = BuildNums.TRUST_CHECK_ERROR; - const PR = PrNums.TRUST_CHECK_ERROR; - - h.createDummyBuild(PR, ALT_SHA, false); - - await circleBuild(payload(BUILD)).then(h.verifyResponse(500)); - - expect({ prNum: PR }).toExistAsAnArtifact(); - expect({ prNum: PR }).not.toExistAsABuild(); - expect({ prNum: PR, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: PR, sha: ALT_SHA, isPublic: false }).toExistAsABuild(); - }); - - - it('should not be able to overwrite an existing public preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - - const regexPrefix = `^PR: ${PR} \\| SHA: ${SHA} \\| File:`; - const idxContentRegex = new RegExp(`${regexPrefix} \\/index\\.html$`); - const barContentRegex = new RegExp(`${regexPrefix} \\/foo\\/bar\\.js$`); - - h.createDummyBuild(PR, SHA); - - await circleBuild(payload(BUILD)).then(h.verifyResponse(409)); - await Promise.all([ - getFile(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA, 'index.html').then(h.verifyResponse(200, idxContentRegex)), - getFile(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex)), - ]); - - expect({ prNum: PR }).toExistAsAnArtifact(); - expect({ prNum: PR }).toExistAsABuild(); - }); - - - it('should not be able to overwrite an existing hidden preview', async () => { - const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; - const PR = PrNums.TRUST_CHECK_UNTRUSTED; - h.createDummyBuild(PR, SHA, false); - - await circleBuild(payload(BUILD)).then(h.verifyResponse(409)); - - expect({ prNum: PR }).toExistAsAnArtifact(); - expect({ prNum: PR, isPublic: false }).toExistAsABuild(); - }); - - - it('should be able to request re-checking visibility (if outdated)', async () => { - const publicPr = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const hiddenPr = PrNums.TRUST_CHECK_UNTRUSTED; - - h.createDummyBuild(publicPr, SHA, false); - h.createDummyBuild(hiddenPr, SHA, true); - - // PR visibilities are outdated (i.e. the opposte of what the should). - expect({ prNum: publicPr, sha: SHA, isPublic: false }).toExistAsABuild(false); - expect({ prNum: publicPr, sha: SHA, isPublic: true }).not.toExistAsABuild(false); - expect({ prNum: hiddenPr, sha: SHA, isPublic: false }).not.toExistAsABuild(false); - expect({ prNum: hiddenPr, sha: SHA, isPublic: true }).toExistAsABuild(false); - - await Promise.all([ - prUpdated(publicPr).then(h.verifyResponse(200)), - prUpdated(hiddenPr).then(h.verifyResponse(200)), - ]); - - // PR visibilities should have been updated. - expect({ prNum: publicPr, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: publicPr, isPublic: true }).toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: false }).toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: true }).not.toExistAsABuild(); - }); - - - it('should be able to request re-checking visibility (if up-to-date)', async () => { - const publicPr = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; - const hiddenPr = PrNums.TRUST_CHECK_UNTRUSTED; - - h.createDummyBuild(publicPr, SHA, true); - h.createDummyBuild(hiddenPr, SHA, false); - - // PR visibilities are already up-to-date. - expect({ prNum: publicPr, sha: SHA, isPublic: false }).not.toExistAsABuild(false); - expect({ prNum: publicPr, sha: SHA, isPublic: true }).toExistAsABuild(false); - expect({ prNum: hiddenPr, sha: SHA, isPublic: false }).toExistAsABuild(false); - expect({ prNum: hiddenPr, sha: SHA, isPublic: true }).not.toExistAsABuild(false); - - await Promise.all([ - prUpdated(publicPr).then(h.verifyResponse(200)), - prUpdated(hiddenPr).then(h.verifyResponse(200)), - ]); - - // PR visibilities are still up-to-date. - expect({ prNum: publicPr, isPublic: true }).toExistAsABuild(); - expect({ prNum: publicPr, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: true }).not.toExistAsABuild(); - expect({ prNum: hiddenPr, isPublic: false }).toExistAsABuild(); - }); - - - it('should reject a request if re-checking visibility fails', async () => { - const errorPr = PrNums.TRUST_CHECK_ERROR; - - h.createDummyBuild(errorPr, SHA, true); - - expect({ prNum: errorPr, isPublic: false }).not.toExistAsABuild(false); - expect({ prNum: errorPr, isPublic: true }).toExistAsABuild(false); - - await prUpdated(errorPr).then(h.verifyResponse(500, /TRUST_CHECK_ERROR/)); - - // PR visibility should not have been updated. - expect({ prNum: errorPr, isPublic: false }).not.toExistAsABuild(); - expect({ prNum: errorPr, isPublic: true }).toExistAsABuild(); - }); - - - it('should reject a request if updating visibility fails', async () => { - // One way to cause an error is to have both a public and a hidden directory for the same PR. - h.createDummyBuild(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA, false); - h.createDummyBuild(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA, true); - - const hiddenPrDir = h.getPrDir(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, false); - const publicPrDir = h.getPrDir(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, true); - const bodyRegex = new RegExp(`Request to move '${hiddenPrDir}' to existing directory '${publicPrDir}'`); - - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: false }).toExistAsABuild(false); - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: true }).toExistAsABuild(false); - - await prUpdated(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER).then(h.verifyResponse(409, bodyRegex)); - - // PR visibility should not have been updated. - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: false }).toExistAsABuild(); - expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, isPublic: true }).toExistAsABuild(); - }); - - }); - -})); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/start-test-preview-server.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/start-test-preview-server.ts deleted file mode 100644 index cc1cfd19de2..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/start-test-preview-server.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '../preview-server'; -import './mock-external-apis'; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/tar-stream.d.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/tar-stream.d.ts deleted file mode 100644 index 99a50ab35d0..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/tar-stream.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -declare module 'tar-stream' { - - import {Readable, Writable} from 'stream'; - - export interface Pack extends Readable { - entry(header: Header, callback?: (err?: any) => {}): Writable; - entry(header: Header, contents: string, callback?: (err?: any) => {}): Writable; - entry(header: Header, buffer: Buffer, callback?: (err?: any) => {}): Writable; - entry(header: Header, buffer: string|Buffer, callback?: (err?: any) => {}): Writable; - finalize(); - destroy(err: any); - } - - export interface Header { - name: string; - mode?: number; - uid?: number; - gid?: number; - size?: number; - mtime?: Date; - type?: type; - linkname?: string; - uname?: string; - gname?: string; - devmajor?: number; - devminor?: number; - } - - export function pack(): Pack; -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/package.json b/aio/aio-builds-setup/dockerbuild/scripts-js/package.json deleted file mode 100644 index ac96ae924a4..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "aio-scripts-js", - "version": "1.0.0", - "description": "Performing various tasks on PR build artifacts for angular.io.", - "repository": "https://github.com/angular/angular.git", - "author": "Angular", - "license": "MIT", - "scripts": { - "prebuild": "yarn clean-dist", - "build": "yarn ~~build", - "prebuild-watch": "yarn prebuild", - "build-watch": "yarn ~~build-watch", - "clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"", - "predev": "yarn build || true", - "dev": "run-p ~~build-watch ~~test-watch", - "lint": "tslint --project tsconfig.json", - "pretest": "run-s build lint", - "test": "yarn ~~test-only", - "pretest-watch": "yarn pretest", - "test-watch": "yarn ~~test-watch", - "~~build": "tsc", - "~~build-watch": "yarn ~~build --watch", - "~~test-only": "node dist/test", - "~~test-watch": "nodemon --delay 1 --exec \"yarn ~~test-only\" --watch dist" - }, - "dependencies": { - "body-parser": "^1.19.0", - "delete-empty": "^3.0.0", - "express": "^4.17.1", - "jasmine": "^4.0.0", - "nock": "^13.0.4", - "node-fetch": "^2.6.1", - "shelljs": "^0.8.5", - "source-map-support": "^0.5.19", - "tar-stream": "^2.1.3", - "tslib": "^2.3.0" - }, - "devDependencies": { - "@types/body-parser": "^1.19.0", - "@types/express": "^4.17.8", - "@types/jasmine": "^4.0.0", - "@types/nock": "^11.1.0", - "@types/node": "^14.6.4", - "@types/node-fetch": "^2.5.7", - "@types/shelljs": "^0.8.8", - "@types/supertest": "^2.0.10", - "nodemon": "^2.0.4", - "npm-run-all": "^4.1.5", - "supertest": "^6.0.0", - "tslint": "^6.1.3", - "tslint-jasmine-noSkipOrFocus": "^1.0.9", - "typescript": "~4.8.0" - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/clean-up/build-cleaner.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/clean-up/build-cleaner.spec.ts deleted file mode 100644 index df3a4d969dd..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/clean-up/build-cleaner.spec.ts +++ /dev/null @@ -1,433 +0,0 @@ -// Imports -import * as fs from 'fs'; -import {normalize} from 'path'; -import * as shell from 'shelljs'; -import {BuildCleaner} from '../../lib/clean-up/build-cleaner'; -import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants'; -import {GithubPullRequests} from '../../lib/common/github-pull-requests'; -import {Logger} from '../../lib/common/utils'; - -const EXISTING_BUILDS = [10, 20, 30, 40]; -const EXISTING_DOWNLOADS = [ - '10-ABCDEF0-build.zip', - '10-1234567-build.zip', - '20-ABCDEF0-build.zip', - '20-1234567-build.zip', -]; -const OPEN_PRS = [10, 40]; - -// Tests -describe('BuildCleaner', () => { - let loggerErrorSpy: jasmine.Spy; - let loggerLogSpy: jasmine.Spy; - let cleaner: BuildCleaner; - - beforeEach(() => { - loggerErrorSpy = spyOn(Logger.prototype, 'error'); - loggerLogSpy = spyOn(Logger.prototype, 'log'); - cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '/downloads', 'build.zip'); - }); - - describe('constructor()', () => { - - it('should throw if \'buildsDir\' is empty', () => { - expect(() => new BuildCleaner('', 'baz', 'qux', '12345', 'downloads', 'build.zip')). - toThrowError('Missing or empty required parameter \'buildsDir\'!'); - }); - - - it('should throw if \'githubOrg\' is empty', () => { - expect(() => new BuildCleaner('/foo/bar', '', 'qux', '12345', 'downloads', 'build.zip')). - toThrowError('Missing or empty required parameter \'githubOrg\'!'); - }); - - - it('should throw if \'githubRepo\' is empty', () => { - expect(() => new BuildCleaner('/foo/bar', 'baz', '', '12345', 'downloads', 'build.zip')). - toThrowError('Missing or empty required parameter \'githubRepo\'!'); - }); - - - it('should throw if \'githubToken\' is empty', () => { - expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '', 'downloads', 'build.zip')). - toThrowError('Missing or empty required parameter \'githubToken\'!'); - }); - - - it('should throw if \'downloadsDir\' is empty', () => { - expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')). - toThrowError('Missing or empty required parameter \'downloadsDir\'!'); - }); - - - it('should throw if \'artifactPath\' is empty', () => { - expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')). - toThrowError('Missing or empty required parameter \'artifactPath\'!'); - }); - - }); - - - describe('cleanUp()', () => { - let cleanerGetExistingBuildNumbersSpy: jasmine.Spy; - let cleanerGetOpenPrNumbersSpy: jasmine.Spy; - let cleanerGetExistingDownloadsSpy: jasmine.Spy; - let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy; - let cleanerRemoveUnnecessaryDownloadsSpy: jasmine.Spy; - - beforeEach(() => { - cleanerGetExistingBuildNumbersSpy = spyOn(cleaner, 'getExistingBuildNumbers').and.resolveTo(EXISTING_BUILDS); - cleanerGetOpenPrNumbersSpy = spyOn(cleaner, 'getOpenPrNumbers').and.resolveTo(OPEN_PRS); - cleanerGetExistingDownloadsSpy = spyOn(cleaner, 'getExistingDownloads').and.resolveTo(EXISTING_DOWNLOADS); - - cleanerRemoveUnnecessaryBuildsSpy = spyOn(cleaner, 'removeUnnecessaryBuilds'); - cleanerRemoveUnnecessaryDownloadsSpy = spyOn(cleaner, 'removeUnnecessaryDownloads'); - }); - - - it('should return a promise', async () => { - const promise = cleaner.cleanUp(); - expect(promise).toBeInstanceOf(Promise); - - // Do not complete the test and release the spies synchronously, to avoid running the actual implementations. - await promise; - }); - - - it('should get the open PRs', async () => { - await cleaner.cleanUp(); - expect(cleanerGetOpenPrNumbersSpy).toHaveBeenCalled(); - }); - - - it('should get the existing builds', async () => { - await cleaner.cleanUp(); - expect(cleanerGetExistingBuildNumbersSpy).toHaveBeenCalled(); - }); - - - it('should get the existing downloads', async () => { - await cleaner.cleanUp(); - expect(cleanerGetExistingDownloadsSpy).toHaveBeenCalled(); - }); - - - it('should pass existing builds and open PRs to \'removeUnnecessaryBuilds()\'', async () => { - await cleaner.cleanUp(); - expect(cleanerRemoveUnnecessaryBuildsSpy).toHaveBeenCalledWith(EXISTING_BUILDS, OPEN_PRS); - }); - - - it('should pass existing downloads and open PRs to \'removeUnnecessaryDownloads()\'', async () => { - await cleaner.cleanUp(); - expect(cleanerRemoveUnnecessaryDownloadsSpy).toHaveBeenCalledWith(EXISTING_DOWNLOADS, OPEN_PRS); - }); - - - it('should reject if \'getOpenPrNumbers()\' rejects', async () => { - cleanerGetOpenPrNumbersSpy.and.rejectWith('Test'); - await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test'); - }); - - - it('should reject if \'getExistingBuildNumbers()\' rejects', async () => { - cleanerGetExistingBuildNumbersSpy.and.rejectWith('Test'); - await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test'); - }); - - - it('should reject if \'getExistingDownloads()\' rejects', async () => { - cleanerGetExistingDownloadsSpy.and.rejectWith('Test'); - await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test'); - }); - - - it('should reject if \'removeUnnecessaryBuilds()\' rejects', async () => { - cleanerRemoveUnnecessaryBuildsSpy.and.rejectWith('Test'); - await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test'); - }); - - - it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => { - cleanerRemoveUnnecessaryDownloadsSpy.and.rejectWith('Test'); - await expectAsync(cleaner.cleanUp()).toBeRejectedWith('Test'); - }); - - }); - - - describe('getExistingBuildNumbers()', () => { - let fsReaddirSpy: jasmine.Spy; - let readdirCb: (err: any, files?: string[]) => void; - let promise: Promise; - - beforeEach(() => { - fsReaddirSpy = spyOn(fs, 'readdir').and.callFake( - ((_: string, cb: typeof readdirCb) => readdirCb = cb) as unknown as typeof fs.readdir, - ); - promise = cleaner.getExistingBuildNumbers(); - }); - - - it('should return a promise', () => { - expect(promise).toBeInstanceOf(Promise); - }); - - - it('should get the contents of the builds directory', () => { - expect(fsReaddirSpy).toHaveBeenCalled(); - expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/foo/bar'); - }); - - - it('should reject if an error occurs while getting the files', async () => { - readdirCb('Test'); - await expectAsync(promise).toBeRejectedWith('Test'); - }); - - - it('should resolve with the returned files (as numbers)', async () => { - readdirCb(null, ['12', '34', '56']); - await expectAsync(promise).toBeResolvedTo([12, 34, 56]); - }); - - - it('should remove `HIDDEN_DIR_PREFIX` from the filenames', async () => { - readdirCb(null, [`${HIDDEN_DIR_PREFIX}12`, '34', `${HIDDEN_DIR_PREFIX}56`]); - await expectAsync(promise).toBeResolvedTo([12, 34, 56]); - }); - - - it('should ignore files with non-numeric (or zero) names', async () => { - readdirCb(null, ['12', 'foo', '34', 'bar', '56', '000']); - await expectAsync(promise).toBeResolvedTo([12, 34, 56]); - }); - - }); - - - describe('getOpenPrNumbers()', () => { - let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void}; - let promise: Promise; - - beforeEach(() => { - spyOn(GithubPullRequests.prototype, 'fetchAll').and.callFake(() => { - return new Promise((resolve, reject) => prDeferred = {resolve, reject}); - }); - - promise = cleaner.getOpenPrNumbers(); - }); - - - it('should return a promise', () => { - expect(promise).toBeInstanceOf(Promise); - }); - - - it('should fetch open PRs via \'GithubPullRequests\'', () => { - expect(GithubPullRequests.prototype.fetchAll).toHaveBeenCalledWith('open'); - }); - - - it('should reject if an error occurs while fetching PRs', async () => { - prDeferred.reject('Test'); - await expectAsync(promise).toBeRejectedWith('Test'); - }); - - - it('should resolve with the numbers of the fetched PRs', async () => { - prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]); - await expectAsync(promise).toBeResolvedTo([1, 2, 3]); - }); - - }); - - - describe('getExistingDownloads()', () => { - let fsReaddirSpy: jasmine.Spy; - let readdirCb: (err: any, files?: string[]) => void; - let promise: Promise; - - beforeEach(() => { - fsReaddirSpy = spyOn(fs, 'readdir').and.callFake( - ((_: string, cb: typeof readdirCb) => readdirCb = cb) as unknown as typeof fs.readdir, - ); - promise = cleaner.getExistingDownloads(); - }); - - - it('should return a promise', () => { - expect(promise).toBeInstanceOf(Promise); - }); - - - it('should get the contents of the downloads directory', () => { - expect(fsReaddirSpy).toHaveBeenCalled(); - expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/downloads'); - }); - - - it('should reject if an error occurs while getting the files', async () => { - readdirCb('Test'); - await expectAsync(promise).toBeRejectedWith('Test'); - }); - - - it('should resolve with the returned file names', async () => { - readdirCb(null, EXISTING_DOWNLOADS); - await expectAsync(promise).toBeResolvedTo(EXISTING_DOWNLOADS); - }); - - - it('should ignore files that do not match the artifactPath', async () => { - readdirCb(null, ['10-ABCDEF-build.zip', '20-AAAAAAA-otherfile.zip', '30-FFFFFFF-build.zip']); - await expectAsync(promise).toBeResolvedTo(['10-ABCDEF-build.zip', '30-FFFFFFF-build.zip']); - }); - - }); - - - describe('removeDir()', () => { - let shellChmodSpy: jasmine.Spy; - let shellRmSpy: jasmine.Spy; - let shellTestSpy: jasmine.Spy; - - beforeEach(() => { - shellChmodSpy = spyOn(shell, 'chmod'); - shellRmSpy = spyOn(shell, 'rm'); - shellTestSpy = spyOn(shell, 'test').and.returnValue(true); - }); - - - it('should test if the directory exists (and return if it does not)', () => { - shellTestSpy.and.returnValue(false); - cleaner.removeDir('/foo/bar'); - - expect(shellTestSpy).toHaveBeenCalledWith('-d', '/foo/bar'); - expect(shellChmodSpy).not.toHaveBeenCalled(); - expect(shellRmSpy).not.toHaveBeenCalled(); - }); - - - it('should remove the specified directory and its content', () => { - cleaner.removeDir('/foo/bar'); - expect(shellRmSpy).toHaveBeenCalledWith('-rf', '/foo/bar'); - }); - - - it('should make the directory and its content writable before removing', () => { - cleaner.removeDir('/foo/bar'); - - expect(shellChmodSpy).toHaveBeenCalledBefore(shellRmSpy); - expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar'); - expect(shellRmSpy).toHaveBeenCalled(); - }); - - - it('should catch errors and log them', () => { - shellRmSpy.and.throwError('Test'); - cleaner.removeDir('/foo/bar'); - - expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', new Error('Test')); - }); - - }); - - - describe('removeUnnecessaryBuilds()', () => { - let cleanerRemoveDirSpy: jasmine.Spy; - - beforeEach(() => { - cleanerRemoveDirSpy = spyOn(cleaner, 'removeDir'); - }); - - - it('should log the number of existing builds and builds to be removed', () => { - cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]); - - expect(loggerLogSpy).toHaveBeenCalledWith('Existing builds: 3'); - expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 build(s): 1, 2'); - }); - - - it('should construct full paths to directories (by prepending \'buildsDir\')', () => { - cleaner.removeUnnecessaryBuilds([1, 2, 3], []); - - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/2')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/3')); - }); - - - it('should try removing hidden directories as well', () => { - cleaner.removeUnnecessaryBuilds([1, 2, 3], []); - - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}2`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`)); - }); - - - it('should remove the builds that do not correspond to open PRs', () => { - cleaner.removeUnnecessaryBuilds([1, 2, 3, 4], [2, 4]); - expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(4); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/3')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`)); - cleanerRemoveDirSpy.calls.reset(); - - cleaner.removeUnnecessaryBuilds([1, 2, 3, 4], [1, 2, 3, 4]); - expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(0); - cleanerRemoveDirSpy.calls.reset(); - - cleaner.removeUnnecessaryBuilds([1, 2, 3, 4], []); - expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(8); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/2')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/3')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/4')); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}1`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}2`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}3`)); - expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize(`/foo/bar/${HIDDEN_DIR_PREFIX}4`)); - cleanerRemoveDirSpy.calls.reset(); - }); - - }); - - - describe('removeUnnecessaryDownloads()', () => { - let shellRmSpy: jasmine.Spy; - - beforeEach(() => { - shellRmSpy = 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', () => { - cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS); - expect(shellRmSpy).toHaveBeenCalledTimes(2); - expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-ABCDEF0-build.zip')); - expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-1234567-build.zip')); - }); - - }); -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts deleted file mode 100644 index ff337a4d13d..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as nock from 'nock'; -import {CircleCiApi} from '../../lib/common/circle-ci-api'; - -const ORG = 'testorg'; -const REPO = 'testrepo'; -const TOKEN = 'xxxx'; -const BASE_BUILD_URL = `https://circleci.com/api/v2/project/gh/${ORG}/${REPO}`; -const BASE_PIPELINE_URL = 'https://circleci.com/api/v2/pipeline'; - -describe('CircleCIApi', () => { - describe('constructor()', () => { - it('should throw if \'githubOrg\' is missing or empty', () => { - expect(() => new CircleCiApi('', REPO, TOKEN)). - toThrowError('Missing or empty required parameter \'githubOrg\'!'); - }); - - it('should throw if \'githubRepo\' is missing or empty', () => { - expect(() => new CircleCiApi(ORG, '', TOKEN)). - toThrowError('Missing or empty required parameter \'githubRepo\'!'); - }); - - it('should throw if \'circleCiToken\' is missing or empty', () => { - expect(() => new CircleCiApi(ORG, REPO, '')). - toThrowError('Missing or empty required parameter \'circleCiToken\'!'); - }); - }); - - describe('fetchFromCircleCI', () => { - it('should include the authentication token in the headers on every request', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const request = nock(BASE_BUILD_URL) - .get('/') - .matchHeader('Circle-Token', TOKEN) - .reply(200); - await api.fetchFromCircleCi(`${BASE_BUILD_URL}/`); - request.done(); - }) - }) - - describe('getBuildInfo', () => { - it('should make a request to the CircleCI API for the given build number', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const buildNum = 12345; - const expectedBuildInfo: any = { org: ORG, repo: REPO, build_num: buildNum }; - - const request = nock(BASE_BUILD_URL) - .get(`/job/${buildNum}`) - .reply(200, expectedBuildInfo); - - const buildInfo = await api.getBuildInfo(buildNum); - expect(buildInfo).toEqual(expectedBuildInfo); - request.done(); - }); - - it('should throw an error if the request fails', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const buildNum = 12345; - const errorMessage = 'Invalid request'; - const request = nock(BASE_BUILD_URL).get(`/job/${buildNum}`); - - request.replyWithError(errorMessage); - await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError( - `CircleCI build info request failed ` + - `(request to ${BASE_BUILD_URL}/job/${buildNum} failed, reason: ${errorMessage})`); - - request.reply(404, errorMessage); - await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError( - `CircleCI build info request failed ` + - `(request to ${BASE_BUILD_URL}/job/${buildNum} failed, reason: ${errorMessage})`); - }); - }); - - describe('getBuildArtifactUrl', () => { - it('should make a request to the CircleCI API for the given build number', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const buildNum = 12345; - const artifact0: any = { path: 'some/path/0', url: 'https://url/0' }; - const artifact1: any = { path: 'some/path/1', url: 'https://url/1' }; - const artifact2: any = { path: 'some/path/2', url: 'https://url/2' }; - const request = nock(BASE_BUILD_URL) - .get(`/${buildNum}/artifacts`) - .reply(200, {items: [artifact0, artifact1, artifact2]}); - - await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeResolvedTo('https://url/1'); - request.done(); - }); - - - it('should throw an error if the request fails', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const buildNum = 12345; - const errorMessage = 'Invalid request'; - const request = nock(BASE_BUILD_URL).get(`/${buildNum}/artifacts`); - - request.replyWithError(errorMessage); - await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError( - `CircleCI artifact URL request failed ` + - `(request to ${BASE_BUILD_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`); - - request.reply(404, errorMessage); - await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError( - `CircleCI artifact URL request failed ` + - `(request to ${BASE_BUILD_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`); - }); - - it('should throw an error if the response does not contain the specified artifact', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const buildNum = 12345; - const artifact0: any = { path: 'some/path/0', url: 'https://url/0' }; - const artifact1: any = { path: 'some/path/1', url: 'https://url/1' }; - const artifact2: any = { path: 'some/path/2', url: 'https://url/2' }; - nock(BASE_BUILD_URL) - .get(`/${buildNum}/artifacts`) - .reply(200, {items: [artifact0, artifact1, artifact2]}); - - await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/3')).toBeRejectedWithError( - `CircleCI artifact URL request failed ` + - `(Missing artifact (some/path/3) for CircleCI build: ${buildNum})`); - }); - }); - - describe('getPipelineInfo', () => { - it('should make a request to the CircleCI API for the given pipeline ID', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const pipelineId = 'a1-b2-c3'; - const expectedPipelineInfo: any = { org: ORG, repo: REPO, pipeline_id: pipelineId }; - - const request = nock(BASE_PIPELINE_URL) - .get(`/${pipelineId}`) - .reply(200, expectedPipelineInfo); - - const pipelineInfo = await api.getPipelineInfo(pipelineId); - expect(pipelineInfo).toEqual(expectedPipelineInfo); - request.done(); - }); - - it('should throw an error if the request fails', async () => { - const api = new CircleCiApi(ORG, REPO, TOKEN); - const pipelineId = 'a1-b2-c3'; - const errorMessage = 'Invalid request'; - const request = nock(BASE_PIPELINE_URL).get(`/${pipelineId}`); - - request.replyWithError(errorMessage); - await expectAsync(api.getPipelineInfo(pipelineId)).toBeRejectedWithError( - `CircleCI pipeline info request failed ` + - `(request to ${BASE_PIPELINE_URL}/${pipelineId} failed, reason: ${errorMessage})`); - - request.reply(404, errorMessage); - await expectAsync(api.getPipelineInfo(pipelineId)).toBeRejectedWithError( - `CircleCI pipeline info request failed ` + - `(request to ${BASE_PIPELINE_URL}/${pipelineId} failed, reason: ${errorMessage})`); - }); - }); -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-api.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-api.spec.ts deleted file mode 100644 index 71af2f379a4..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-api.spec.ts +++ /dev/null @@ -1,327 +0,0 @@ -// Imports -import * as nock from 'nock'; -import {GithubApi} from '../../lib/common/github-api'; - -// Tests -describe('GithubApi', () => { - let api: GithubApi; - - beforeEach(() => api = new GithubApi('12345')); - - - describe('constructor()', () => { - - it('should throw if \'githubToken\' is missing or empty', () => { - expect(() => new GithubApi('')).toThrowError('Missing or empty required parameter \'githubToken\'!'); - }); - - }); - - - describe('get()', () => { - let apiBuildPathSpy: jasmine.Spy; - let apiRequestSpy: jasmine.Spy; - - beforeEach(() => { - apiBuildPathSpy = spyOn(api as any, 'buildPath'); - apiRequestSpy = spyOn(api as any, 'request'); - }); - - - it('should call \'buildPath()\' with the pathname and params', () => { - api.get('/foo', {bar: 'baz'}); - - expect(apiBuildPathSpy).toHaveBeenCalled(); - expect(apiBuildPathSpy.calls.argsFor(0)).toEqual(['/foo', {bar: 'baz'}]); - }); - - - it('should call \'request()\' with the correct method', () => { - api.get('/foo'); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[0]).toBe('get'); - }); - - - it('should call \'request()\' with the correct path', () => { - apiBuildPathSpy.and.returnValue('/foo/bar'); - api.get('foo'); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[1]).toBe('/foo/bar'); - }); - - - it('should not pass data to \'request()\'', () => { - (api.get as any)('foo', {}, {}); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined(); - }); - - }); - - - describe('post()', () => { - let apiBuildPathSpy: jasmine.Spy; - let apiRequestSpy: jasmine.Spy; - - beforeEach(() => { - apiBuildPathSpy = spyOn(api as any, 'buildPath'); - apiRequestSpy = spyOn(api as any, 'request'); - }); - - - it('should call \'buildPath()\' with the pathname and params', () => { - api.post('/foo', {bar: 'baz'}); - - expect(apiBuildPathSpy).toHaveBeenCalled(); - expect(apiBuildPathSpy.calls.argsFor(0)).toEqual(['/foo', {bar: 'baz'}]); - }); - - - it('should call \'request()\' with the correct method', () => { - api.post('/foo'); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[0]).toBe('post'); - }); - - - it('should call \'request()\' with the correct path', () => { - apiBuildPathSpy.and.returnValue('/foo/bar'); - api.post('/foo'); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[1]).toBe('/foo/bar'); - }); - - - it('should pass the data to \'request()\'', () => { - api.post('/foo', {}, {bar: 'baz'}); - - expect(apiRequestSpy).toHaveBeenCalled(); - expect(apiRequestSpy.calls.argsFor(0)[2]).toEqual({bar: 'baz'}); - }); - - }); - - - describe('getPaginated()', () => { - let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[]; - - beforeEach(() => { - deferreds = []; - spyOn(api, 'get').and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject}))); - }); - - - it('should return a promise', () => { - expect((api as any).getPaginated()).toBeInstanceOf(Promise); - }); - - - it('should call \'get()\' with the correct pathname and params', () => { - (api as any).getPaginated('/foo/bar'); - (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', {baz: 'qux', page: 1, per_page: 100}); - }); - - - it('should reject if the request fails', async () => { - const responsePromise = (api as any).getPaginated('/foo/bar'); - deferreds[0].reject('Test'); - - await expectAsync(responsePromise).toBeRejectedWith('Test'); - }); - - - it('should resolve with the returned items', async () => { - const items = [{id: 1}, {id: 2}]; - const responsePromise = (api as any).getPaginated('/foo/bar'); - deferreds[0].resolve(items); - - await expectAsync(responsePromise).toBeResolvedTo(items); - }); - - - it('should iteratively call \'get()\' to fetch all items', async () => { - // Create an array or 250 objects. - const allItems = new Array(250).fill(null).map((_, i) => ({id: i})); - const apiGetSpy = api.get as jasmine.Spy; - const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100}); - - const responsePromise = (api as any).getPaginated('/foo/bar', {baz: 'qux'}); - - deferreds[0].resolve(allItems.slice(0, 100)); - setTimeout(() => { - deferreds[1].resolve(allItems.slice(100, 200)); - setTimeout(() => { - deferreds[2].resolve(allItems.slice(200)); - }, 0); - }, 0); - - await expectAsync(responsePromise).toBeResolvedTo(allItems); - - expect(apiGetSpy).toHaveBeenCalledTimes(3); - expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]); - expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]); - expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]); - }); - - }); - - - // Protected methods - - describe('buildPath()', () => { - - it('should return the pathname if no params', () => { - expect((api as any).buildPath('/foo')).toBe('/foo'); - expect((api as any).buildPath('/foo', undefined)).toBe('/foo'); - expect((api as any).buildPath('/foo', null)).toBe('/foo'); - }); - - - it('should append the params to the pathname', () => { - expect((api as any).buildPath('/foo', {bar: 'baz'})).toBe('/foo?bar=baz'); - }); - - - it('should join the params with \'&\'', () => { - expect((api as any).buildPath('/foo', {bar: 1, baz: 2})).toBe('/foo?bar=1&baz=2'); - }); - - - it('should ignore undefined/null params', () => { - expect((api as any).buildPath('/foo', {bar: undefined, baz: null})).toBe('/foo'); - }); - - - it('should encode param values as URI components', () => { - expect((api as any).buildPath('/foo', {bar: 'b a&z'})).toBe('/foo?bar=b%20a%26z'); - }); - - }); - - describe('request()', () => { - it('should return a promise', () => { - nock('https://api.github.com').get('/').reply(200); - expect((api as any).request()).toBeInstanceOf(Promise); - }); - - - it('should call \'https.request()\' with the correct options', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(200); - - await (api as any).request('method', '/path'); - requestHandler.done(); - }); - - - it('should add the \'Authorization\' header containing the \'githubToken\'', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method', undefined, { - reqheaders: {Authorization: 'token 12345'}, - }) - .reply(200); - - await (api as any).request('method', '/path'); - requestHandler.done(); - }); - - - it('should reject on request error', async () => { - nock('https://api.github.com') - .intercept('/path', 'method') - .replyWithError('Test'); - - await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError('Test'); - }); - - - it('should \'JSON.stringify\' and send the data along with the request', async () => { - const data = {key: 'value'}; - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method', JSON.stringify(data)) - .reply(200); - - await (api as any).request('method', '/path', data); - requestHandler.done(); - }); - - - it('should reject if response statusCode is <200', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(199); - const responsePromise = (api as any).request('method', '/path'); - - await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed')); - await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 199')); - - requestHandler.done(); - }); - - - it('should reject if response statusCode is >=400', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(400); - const responsePromise = (api as any).request('method', '/path'); - - await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed')); - await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 400')); - - requestHandler.done(); - }); - - - it('should include the response text in the rejection message', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(500, 'Test'); - const responsePromise = (api as any).request('method', '/path'); - - await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('Test')); - - requestHandler.done(); - }); - - - it('should resolve if returned statusCode is >=200 and <400', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(200); - - await expectAsync((api as any).request('method', '/path')).toBeResolved(); - requestHandler.done(); - }); - - - it('should parse the response body into an object using \'JSON.parse\'', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(300, '{"foo": "bar"}'); - - await expectAsync((api as any).request('method', '/path')).toBeResolvedTo({foo: 'bar'}); - requestHandler.done(); - }); - - it('should reject if the response text is malformed JSON', async () => { - const requestHandler = nock('https://api.github.com') - .intercept('/path', 'method') - .reply(300, '}'); - - await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError(SyntaxError); - requestHandler.done(); - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-pull-requests.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-pull-requests.spec.ts deleted file mode 100644 index c79218a0cb4..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-pull-requests.spec.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Imports -import {GithubApi} from '../../lib/common/github-api'; -import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests'; - -// Tests -describe('GithubPullRequests', () => { - let githubApi: jasmine.SpyObj; - - beforeEach(() => { - githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']); - }); - - - describe('constructor()', () => { - - it('should throw if \'githubOrg\' is missing or empty', () => { - expect(() => new GithubPullRequests(githubApi, '', 'bar')). - toThrowError('Missing or empty required parameter \'githubOrg\'!'); - }); - - - it('should throw if \'githubRepo\' is missing or empty', () => { - expect(() => new GithubPullRequests(githubApi, 'foo', '')). - toThrowError('Missing or empty required parameter \'githubRepo\'!'); - }); - - }); - - - describe('addComment()', () => { - let prs: GithubPullRequests; - - beforeEach(() => { - prs = new GithubPullRequests(githubApi, 'foo', 'bar'); - }); - - - it('should throw if the PR number is invalid', () => { - expect(() => prs.addComment(-1337, 'body')).toThrowError(`Invalid PR number: -1337`); - expect(() => prs.addComment(NaN, 'body')).toThrowError(`Invalid PR number: NaN`); - }); - - - it('should throw if the comment body is invalid or empty', () => { - expect(() => prs.addComment(42, '')).toThrowError(`Invalid or empty comment body: `); - }); - - - it('should make a POST request to Github with the correct pathname, params and data', () => { - githubApi.post.and.resolveTo(); - prs.addComment(42, 'body'); - expect(githubApi.post).toHaveBeenCalledWith('/repos/foo/bar/issues/42/comments', null, {body: 'body'}); - }); - - - it('should reject if the request fails', async () => { - githubApi.post.and.rejectWith('Test'); - await expectAsync(prs.addComment(42, 'body')).toBeRejectedWith('Test'); - }); - - - it('should resolve with the data from the Github POST', async () => { - githubApi.post.and.resolveTo('Test'); - await expectAsync(prs.addComment(42, 'body')).toBeResolvedTo('Test'); - }); - - }); - - - describe('fetch()', () => { - let prs: GithubPullRequests; - - beforeEach(() => { - prs = new GithubPullRequests(githubApi, 'foo', 'bar'); - }); - - - it('should make a GET request to GitHub with the correct pathname', () => { - prs.fetch(42); - expect(githubApi.get).toHaveBeenCalledWith('/repos/foo/bar/issues/42'); - }); - - - it('should resolve with the data returned from GitHub', async () => { - const expected: any = {number: 42}; - githubApi.get.and.resolveTo(expected); - - await expectAsync(prs.fetch(42)).toBeResolvedTo(expected); - }); - - }); - - - describe('fetchAll()', () => { - let prs: GithubPullRequests; - - beforeEach(() => prs = new GithubPullRequests(githubApi, 'foo', 'bar')); - - - it('should call \'getPaginated()\' with the correct pathname and params', () => { - const expectedPathname = '/repos/foo/bar/pulls'; - - prs.fetchAll('all'); - prs.fetchAll('closed'); - prs.fetchAll('open'); - - expect(githubApi.getPaginated).toHaveBeenCalledTimes(3); - expect(githubApi.getPaginated.calls.argsFor(0)).toEqual([expectedPathname, {state: 'all'}]); - expect(githubApi.getPaginated.calls.argsFor(1)).toEqual([expectedPathname, {state: 'closed'}]); - expect(githubApi.getPaginated.calls.argsFor(2)).toEqual([expectedPathname, {state: 'open'}]); - }); - - - it('should default to \'all\' if no state is specified', () => { - prs.fetchAll(); - expect(githubApi.getPaginated).toHaveBeenCalledWith('/repos/foo/bar/pulls', {state: 'all'}); - }); - - - it('should forward the value returned by \'getPaginated()\'', async () => { - const mockPrs: PullRequest[] = [ - {number: 1, user: {login: 'foo'}, labels: []}, - {number: 2, user: {login: 'bar'}, labels: []}, - ]; - - githubApi.getPaginated.and.resolveTo(mockPrs); - expect(await prs.fetchAll()).toBe(mockPrs); - }); - - }); - - - describe('fetchFiles()', () => { - let prs: GithubPullRequests; - - beforeEach(() => { - prs = new GithubPullRequests(githubApi, 'foo', 'bar'); - }); - - - it('should make a paginated GET request to GitHub with the correct pathname', () => { - prs.fetchFiles(42); - expect(githubApi.getPaginated).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files'); - }); - - - it('should resolve with the data returned from GitHub', async () => { - const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}]; - githubApi.getPaginated.and.resolveTo(expected); - - await expectAsync(prs.fetchFiles(42)).toBeResolvedTo(expected); - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-teams.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-teams.spec.ts deleted file mode 100644 index d8fa2a4d7b4..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/github-teams.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import {GithubApi} from '../../lib/common/github-api'; -import {GithubTeams, Team} from '../../lib/common/github-teams'; - -// Tests -describe('GithubTeams', () => { - - let githubApi: jasmine.SpyObj; - - beforeEach(() => { - githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']); - }); - - describe('constructor()', () => { - - it('should throw if \'githubOrg\' is missing or empty', () => { - expect(() => new GithubTeams(githubApi, '')). - toThrowError('Missing or empty required parameter \'githubOrg\'!'); - }); - - }); - - - describe('fetchAll()', () => { - let teams: GithubTeams; - - beforeEach(() => { - teams = new GithubTeams(githubApi, 'foo'); - }); - - - it('should call \'getPaginated()\' with the correct pathname and params', () => { - teams.fetchAll(); - expect(githubApi.getPaginated).toHaveBeenCalledWith('/orgs/foo/teams'); - }); - - - it('should forward the value returned by \'getPaginated()\'', async () => { - const mockTeams: Team[] = [ - {id: 1, slug: 'foo'}, - {id: 2, slug: 'bar'}, - ]; - - githubApi.getPaginated.and.resolveTo(mockTeams); - expect(await teams.fetchAll()).toBe(mockTeams); - }); - - }); - - - describe('isMemberById()', () => { - let teams: GithubTeams; - - beforeEach(() => { - teams = new GithubTeams(githubApi, 'foo'); - }); - - - it('should return a promise', () => { - githubApi.get.and.resolveTo(); - const promise = teams.isMemberById('user', [1]); - expect(promise).toBeInstanceOf(Promise); - }); - - - it('should resolve with false if called with an empty array', async () => { - await expectAsync(teams.isMemberById('user', [])).toBeResolvedTo(false); - expect(githubApi.get).not.toHaveBeenCalled(); - }); - - - it('should call \'get()\' with the correct pathname', async () => { - githubApi.get.and.resolveTo(); - await teams.isMemberById('user', [1]); - - expect(githubApi.get).toHaveBeenCalledWith('/teams/1/memberships/user'); - }); - - - it('should resolve with false if \'get()\' rejects', async () => { - githubApi.get.and.rejectWith(null); - - await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false); - expect(githubApi.get).toHaveBeenCalled(); - }); - - - it('should resolve with false if the membership is not active', async () => { - githubApi.get.and.resolveTo({state: 'pending'}); - - await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false); - expect(githubApi.get).toHaveBeenCalled(); - }); - - - it('should resolve with true if the membership is active', async () => { - githubApi.get.and.resolveTo({state: 'active'}); - await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(true); - }); - - - it('should sequentially call \'get()\' until an active membership is found', async () => { - githubApi.get. - withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}). - withArgs('/teams/2/memberships/user').and.rejectWith(null). - withArgs('/teams/3/memberships/user').and.resolveTo({state: 'active'}); - - await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(true); - - expect(githubApi.get).toHaveBeenCalledTimes(3); - expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user'); - expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user'); - expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user'); - }); - - - it('should resolve with false if no active membership is found', async () => { - githubApi.get. - withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}). - withArgs('/teams/2/memberships/user').and.rejectWith(null). - withArgs('/teams/3/memberships/user').and.resolveTo({state: 'not active'}). - withArgs('/teams/4/memberships/user').and.rejectWith(null); - - await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(false); - - expect(githubApi.get).toHaveBeenCalledTimes(4); - expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user'); - expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user'); - expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user'); - expect(githubApi.get.calls.argsFor(3)[0]).toBe('/teams/4/memberships/user'); - }); - - }); - - - describe('isMemberBySlug()', () => { - let teams: GithubTeams; - let teamsFetchAllSpy: jasmine.Spy; - let teamsIsMemberByIdSpy: jasmine.Spy; - - beforeEach(() => { - teams = new GithubTeams(githubApi, 'foo'); - - teamsFetchAllSpy = spyOn(teams, 'fetchAll').and.resolveTo([{id: 1, slug: 'team1'}, {id: 2, slug: 'team2'}]); - teamsIsMemberByIdSpy = spyOn(teams, 'isMemberById'); - }); - - - it('should return a promise', () => { - expect(teams.isMemberBySlug('user', ['team-slug'])).toBeInstanceOf(Promise); - }); - - - it('should call \'fetchAll()\'', () => { - teams.isMemberBySlug('user', ['team-slug']); - expect(teamsFetchAllSpy).toHaveBeenCalled(); - }); - - - it('should resolve with false if \'fetchAll()\' rejects', async () => { - teamsFetchAllSpy.and.rejectWith(null); - await expectAsync(teams.isMemberBySlug('user', ['team-slug'])).toBeResolvedTo(false); - }); - - - it('should call \'isMemberById()\' with the correct params if no team is found', async () => { - await teams.isMemberBySlug('user', ['no-match']); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', []); - }); - - - it('should call \'isMemberById()\' with the correct params if teams are found', async () => { - await teams.isMemberBySlug('userA', ['team1']); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]); - - await teams.isMemberBySlug('userB', ['team2']); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [2]); - - await teams.isMemberBySlug('userC', ['team1', 'team2']); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userC', [1, 2]); - }); - - - it('should resolve with false if \'isMemberById()\' rejects', async () => { - teamsIsMemberByIdSpy.and.rejectWith(null); - - await expectAsync(teams.isMemberBySlug('user', ['team1'])).toBeResolvedTo(false); - expect(teamsIsMemberByIdSpy).toHaveBeenCalled(); - }); - - - it('should resolve with the value \'isMemberById()\' resolves with', async () => { - teamsIsMemberByIdSpy.and.resolveTo(true); - await expectAsync(teams.isMemberBySlug('userA', ['team1'])).toBeResolvedTo(true); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]); - - teamsIsMemberByIdSpy.and.resolveTo(false); - await expectAsync(teams.isMemberBySlug('userB', ['team1'])).toBeResolvedTo(false); - expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [1]); - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/utils.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/utils.spec.ts deleted file mode 100644 index 063268cce5a..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/utils.spec.ts +++ /dev/null @@ -1,206 +0,0 @@ -// Imports -import {resolve as resolvePath} from 'path'; -import { - assert, - assertNotMissingOrEmpty, - computeArtifactDownloadPath, - computeShortSha, - getEnvVar, - getPrInfoFromDownloadPath, - Logger, -} from '../../lib/common/utils'; - -// Tests -describe('utils', () => { - - describe('computeShortSha', () => { - it('should return only the first SHORT_SHA_LEN characters of the SHA', () => { - expect(computeShortSha('0123456789')).toEqual('0123456'); - expect(computeShortSha('ABC')).toEqual('ABC'); - expect(computeShortSha('')).toEqual(''); - }); - }); - - - describe('assert', () => { - it('should throw if passed a false value', () => { - expect(() => assert(false, 'error message')).toThrowError('error message'); - }); - - it('should not throw if passed a true value', () => { - expect(() => assert(true, 'error message')).not.toThrow(); - }); - }); - - - describe('computeArtifactDownloadPath', () => { - it('should compute an absolute path based on the artifact info provided', () => { - const downloadDir = '/a/b/c'; - const pr = 123; - const sha = 'ABCDEF1234567'; - const artifactPath = 'a/path/to/file.zip'; - const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath); - expect(path).toBe(resolvePath('/a/b/c/123-ABCDEF1-file.zip')); - }); - }); - - - describe('getPrInfoFromDownloadPath', () => { - it('should extract the PR and SHA from the file path', () => { - const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip'); - expect(pr).toEqual(12345); - expect(sha).toEqual('ABCDE'); - }); - }); - - - describe('assertNotMissingOrEmpty()', () => { - - it('should throw if passed an empty value', () => { - expect(() => assertNotMissingOrEmpty('foo', undefined)). - toThrowError('Missing or empty required parameter \'foo\'!'); - expect(() => assertNotMissingOrEmpty('bar', null)).toThrowError('Missing or empty required parameter \'bar\'!'); - expect(() => assertNotMissingOrEmpty('baz', '')).toThrowError('Missing or empty required parameter \'baz\'!'); - }); - - - it('should not throw if passed a non-empty value', () => { - expect(() => assertNotMissingOrEmpty('foo', ' ')).not.toThrow(); - expect(() => assertNotMissingOrEmpty('bar', 'bar')).not.toThrow(); - expect(() => assertNotMissingOrEmpty('baz', 'b a z')).not.toThrow(); - }); - - }); - - - describe('getEnvVar()', () => { - const emptyVar = '$$test_utils_getEnvVar_empty$$'; - const nonEmptyVar = '$$test_utils_getEnvVar_nonEmpty$$'; - const undefinedVar = '$$test_utils_getEnvVar_undefined$$'; - - beforeEach(() => { - process.env[emptyVar] = ''; - process.env[nonEmptyVar] = 'foo'; - }); - afterEach(() => { - delete process.env[emptyVar]; - delete process.env[nonEmptyVar]; - }); - - - it('should return an environment variable', () => { - expect(getEnvVar(nonEmptyVar)).toBe('foo'); - }); - - - it('should exit with an error if the environment variable is not defined', () => { - const consoleErrorSpy = spyOn(console, 'error'); - const processExitSpy = spyOn(process, 'exit'); - - getEnvVar(undefinedVar); - - expect(consoleErrorSpy).toHaveBeenCalled(); - expect(consoleErrorSpy.calls.argsFor(0)[0]).toContain(undefinedVar); - expect(processExitSpy).toHaveBeenCalledWith(1); - }); - - - it('should exit with an error if the environment variable is empty', () => { - const consoleErrorSpy = spyOn(console, 'error'); - const processExitSpy = spyOn(process, 'exit'); - - getEnvVar(emptyVar); - - expect(consoleErrorSpy).toHaveBeenCalled(); - expect(consoleErrorSpy.calls.argsFor(0)[0]).toContain(emptyVar); - expect(processExitSpy).toHaveBeenCalledWith(1); - }); - - - it('should return an empty string if an undefined variable is optional', () => { - expect(getEnvVar(undefinedVar, true)).toBe(''); - }); - - - it('should return an empty string if an empty variable is optional', () => { - expect(getEnvVar(emptyVar, true)).toBe(''); - }); - - }); - - - 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'); - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/index.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/index.ts deleted file mode 100644 index 3470b5541bd..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Imports -import {runTests} from '../lib/common/run-tests'; - -// Run -const specFiles = [`${__dirname}/**/*.spec.js`]; -runTests(specFiles); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-creator.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-creator.spec.ts deleted file mode 100644 index 53fc839d43a..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-creator.spec.ts +++ /dev/null @@ -1,635 +0,0 @@ -// Imports -import * as cp from 'child_process'; -import {EventEmitter} from 'events'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as shell from 'shelljs'; -import {SHORT_SHA_LEN} from '../../lib/common/constants'; -import {Logger} from '../../lib/common/utils'; -import {BuildCreator} from '../../lib/preview-server/build-creator'; -import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events'; -import {PreviewServerError} from '../../lib/preview-server/preview-error'; -import {customAsyncMatchers} from './jasmine-custom-async-matchers'; - - -// Tests -describe('BuildCreator', () => { - const pr = 9; - const sha = '9'.repeat(40); - const shortSha = sha.slice(0, SHORT_SHA_LEN); - const archive = 'snapshot.tar.gz'; - const buildsDir = 'builds/dir'; - const hiddenPrDir = path.join(buildsDir, `hidden--${pr}`); - const publicPrDir = path.join(buildsDir, `${pr}`); - const hiddenShaDir = path.join(hiddenPrDir, shortSha); - const publicShaDir = path.join(publicPrDir, shortSha); - let bc: BuildCreator; - - beforeEach(() => jasmine.addAsyncMatchers(customAsyncMatchers)); - beforeEach(() => bc = new BuildCreator(buildsDir)); - - - describe('constructor()', () => { - - it('should throw if \'buildsDir\' is missing or empty', () => { - expect(() => new BuildCreator('')).toThrowError('Missing or empty required parameter \'buildsDir\'!'); - }); - - - it('should extend EventEmitter', () => { - expect(bc).toBeInstanceOf(BuildCreator); - expect(bc).toBeInstanceOf(EventEmitter); - - expect(Object.getPrototypeOf(bc)).toBe(BuildCreator.prototype); - }); - - }); - - - describe('create()', () => { - let bcEmitSpy: jasmine.Spy; - let bcExistsSpy: jasmine.Spy; - let bcExtractArchiveSpy: jasmine.Spy; - let bcUpdatePrVisibilitySpy: jasmine.Spy; - let shellMkdirSpy: jasmine.Spy; - let shellRmSpy: jasmine.Spy; - - beforeEach(() => { - bcEmitSpy = spyOn(bc, 'emit'); - bcExistsSpy = spyOn(bc as any, 'exists'); - bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive'); - bcUpdatePrVisibilitySpy = spyOn(bc, 'updatePrVisibility'); - shellMkdirSpy = spyOn(shell, 'mkdir'); - shellRmSpy = spyOn(shell, 'rm'); - }); - - - [true, false].forEach(isPublic => { - const prDir = isPublic ? publicPrDir : hiddenPrDir; - const shaDir = isPublic ? publicShaDir : hiddenShaDir; - - - it('should return a promise', async () => { - const promise = bc.create(pr, sha, archive, isPublic); - expect(promise).toBeInstanceOf(Promise); - - // Do not complete the test (and release the spies) synchronously to avoid running the actual - // `extractArchive()`. - await promise; - }); - - - it('should update the PR\'s visibility first if necessary', async () => { - await bc.create(pr, sha, archive, isPublic); - - expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledBefore(shellMkdirSpy); - expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic); - expect(shellMkdirSpy).toHaveBeenCalled(); - }); - - - it('should create the build directory (and any parent directories)', async () => { - await bc.create(pr, sha, archive, isPublic); - expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir); - }); - - - it('should extract the archive contents into the build directory', async () => { - await bc.create(pr, sha, archive, isPublic); - expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir); - }); - - - it('should emit a CreatedBuildEvent on success', async () => { - let emitted = false; - - bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => { - expect(type).toBe(CreatedBuildEvent.type); - expect(evt).toBeInstanceOf(CreatedBuildEvent); - expect(evt.pr).toBe(+pr); - expect(evt.sha).toBe(shortSha); - expect(evt.isPublic).toBe(isPublic); - - emitted = true; - }); - - await bc.create(pr, sha, archive, isPublic); - expect(emitted).toBe(true); - }); - - - describe('on error', () => { - beforeEach(() => { - bcExistsSpy.and.returnValue(false); - }); - - - it('should abort and skip further operations if changing the PR\'s visibility fails', async () => { - const mockError = new PreviewServerError(543, 'Test'); - bcUpdatePrVisibilitySpy.and.rejectWith(mockError); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWith(mockError); - - expect(bcExistsSpy).not.toHaveBeenCalled(); - expect(shellMkdirSpy).not.toHaveBeenCalled(); - expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if the build does already exist', async () => { - bcExistsSpy.withArgs(shaDir).and.returnValue(true); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError( - 409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`); - - expect(shellMkdirSpy).not.toHaveBeenCalled(); - expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should detect existing build directory after visibility change', async () => { - bcUpdatePrVisibilitySpy.and.callFake(() => bcExistsSpy.and.returnValue(true)); - - expect(bcExistsSpy(prDir)).toBe(false); - expect(bcExistsSpy(shaDir)).toBe(false); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError( - 409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`); - - expect(shellMkdirSpy).not.toHaveBeenCalled(); - expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if it fails to create the directories', async () => { - shellMkdirSpy.and.throwError(''); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected(); - - expect(shellMkdirSpy).toHaveBeenCalled(); - expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if it fails to extract the archive', async () => { - bcExtractArchiveSpy.and.throwError(''); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected(); - - expect(shellMkdirSpy).toHaveBeenCalled(); - expect(bcExtractArchiveSpy).toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should delete the PR directory (for new PR)', async () => { - bcExtractArchiveSpy.and.throwError(''); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected(); - expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir); - }); - - - it('should delete the SHA directory (for existing PR)', async () => { - bcExistsSpy.withArgs(prDir).and.returnValue(true); - bcExtractArchiveSpy.and.throwError(''); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected(); - expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir); - }); - - - it('should reject with an PreviewServerError', async () => { - // tslint:disable-next-line: no-string-throw - shellMkdirSpy.and.callFake(() => { throw 'Test'; }); - - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError( - 500, `Error while creating preview at: ${shaDir}\nTest`); - }); - - - it('should pass PreviewServerError instances unmodified', async () => { - shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); }); - await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(543, 'Test'); - }); - - }); - - }); - - }); - - - describe('updatePrVisibility()', () => { - let bcEmitSpy: jasmine.Spy; - let bcExistsSpy: jasmine.Spy; - let bcListShasByDate: jasmine.Spy; - let shellMvSpy: jasmine.Spy; - - beforeEach(() => { - bcEmitSpy = spyOn(bc, 'emit'); - bcExistsSpy = spyOn(bc as any, 'exists'); - bcListShasByDate = spyOn(bc as any, 'listShasByDate'); - shellMvSpy = spyOn(shell, 'mv'); - - bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false)); - bcListShasByDate.and.returnValue([]); - }); - - - it('should return a promise', async () => { - const promise = bc.updatePrVisibility(pr, true); - expect(promise).toBeInstanceOf(Promise); - - // Do not complete the test (and release the spies) synchronously to avoid running the actual `extractArchive()`. - await promise; - }); - - - [true, false].forEach(makePublic => { - const oldPrDir = makePublic ? hiddenPrDir : publicPrDir; - const newPrDir = makePublic ? publicPrDir : hiddenPrDir; - - - it('should rename the directory', async () => { - await bc.updatePrVisibility(pr, makePublic); - expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir); - }); - - - describe('when the visibility is updated', () => { - - it('should resolve to true', async () => { - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(true); - }); - - - it('should rename the directory', async () => { - await bc.updatePrVisibility(pr, makePublic); - expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir); - }); - - - it('should emit a ChangedPrVisibilityEvent on success', async () => { - let emitted = false; - - bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => { - expect(type).toBe(ChangedPrVisibilityEvent.type); - expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent); - expect(evt.pr).toBe(+pr); - expect(evt.shas).toBeInstanceOf(Array); - expect(evt.isPublic).toBe(makePublic); - - emitted = true; - }); - - await bc.updatePrVisibility(pr, makePublic); - expect(emitted).toBe(true); - }); - - - it('should include all shas in the emitted event', async () => { - const shas = ['foo', 'bar', 'baz']; - let emitted = false; - - bcListShasByDate.and.resolveTo(shas); - bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => { - expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir); - - expect(type).toBe(ChangedPrVisibilityEvent.type); - expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent); - expect(evt.pr).toBe(+pr); - expect(evt.shas).toBe(shas); - expect(evt.isPublic).toBe(makePublic); - - emitted = true; - }); - - await bc.updatePrVisibility(pr, makePublic); - expect(emitted).toBe(true); - }); - - }); - - - it('should do nothing if the visibility is already up-to-date', async () => { - bcExistsSpy.and.callFake((dir: string) => dir === newPrDir); - - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false); - - expect(shellMvSpy).not.toHaveBeenCalled(); - expect(bcListShasByDate).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should do nothing if the PR directory does not exist', async () => { - bcExistsSpy.and.returnValue(false); - - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false); - - expect(shellMvSpy).not.toHaveBeenCalled(); - expect(bcListShasByDate).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - describe('on error', () => { - - it('should abort and skip further operations if both directories exist', async () => { - bcExistsSpy.and.returnValue(true); - - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError( - 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`); - - expect(shellMvSpy).not.toHaveBeenCalled(); - expect(bcListShasByDate).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if it fails to rename the directory', async () => { - shellMvSpy.and.throwError(''); - - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected(); - - expect(shellMvSpy).toHaveBeenCalled(); - expect(bcListShasByDate).not.toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if it fails to list the SHAs', async () => { - bcListShasByDate.and.throwError(''); - - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected(); - - expect(shellMvSpy).toHaveBeenCalled(); - expect(bcListShasByDate).toHaveBeenCalled(); - expect(bcEmitSpy).not.toHaveBeenCalled(); - }); - - - it('should reject with an PreviewServerError', async () => { - // tslint:disable-next-line: no-string-throw - shellMvSpy.and.callFake(() => { throw 'Test'; }); - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError( - 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`); - }); - - - it('should pass PreviewServerError instances unmodified', async () => { - shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); }); - await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(543, 'Test'); - }); - - }); - - }); - - }); - - - // Protected methods - - describe('exists()', () => { - let fsAccessSpy: jasmine.Spy; - let fsAccessCbs: ((v?: any) => void)[]; - - beforeEach(() => { - fsAccessCbs = []; - fsAccessSpy = spyOn(fs, 'access').and.callFake( - ((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb)) as unknown as typeof fs.access, - ); - }); - - - it('should return a promise', () => { - expect((bc as any).exists('foo')).toBeInstanceOf(Promise); - }); - - - it('should call \'fs.access()\' with the specified argument', () => { - (bc as any).exists('foo'); - expect(fsAccessSpy).toHaveBeenCalledWith('foo', jasmine.any(Function)); - }); - - - it('should resolve with \'true\' if \'fs.access()\' succeeds', async () => { - const existsPromises = [ - (bc as any).exists('foo'), - (bc as any).exists('bar'), - ]; - - fsAccessCbs[0](); - fsAccessCbs[1](null); - - await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([true, true]); - }); - - - it('should resolve with \'false\' if \'fs.access()\' errors', async () => { - const existsPromises = [ - (bc as any).exists('foo'), - (bc as any).exists('bar'), - ]; - - fsAccessCbs[0]('Error'); - fsAccessCbs[1](new Error()); - - await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([false, false]); - }); - - }); - - - describe('extractArchive()', () => { - let consoleWarnSpy: jasmine.Spy; - let shellChmodSpy: jasmine.Spy; - let shellRmSpy: jasmine.Spy; - let cpExecSpy: jasmine.Spy; - let cpExecCbs: ((...args: any[]) => void)[]; - - beforeEach(() => { - cpExecCbs = []; - - consoleWarnSpy = spyOn(Logger.prototype, 'warn'); - shellChmodSpy = spyOn(shell, 'chmod'); - shellRmSpy = spyOn(shell, 'rm'); - cpExecSpy = spyOn(cp, 'exec').and.callFake( - ((_: string, cb: (...args: any[]) => void) => - cpExecCbs.push(cb)) as unknown as typeof cp.exec, - ); - }); - - - it('should return a promise', () => { - expect((bc as any).extractArchive('foo', 'bar')).toBeInstanceOf(Promise); - }); - - - it('should "gunzip" and "untar" the input file into the output directory', () => { - const cmd = 'tar --extract --gzip --directory "output/dir" --file "input/file"'; - - (bc as any).extractArchive('input/file', 'output/dir'); - expect(cpExecSpy).toHaveBeenCalledWith(cmd, jasmine.any(Function)); - }); - - - it('should log (as a warning) any stderr output if extracting succeeded', async () => { - const extractPromise = (bc as any).extractArchive('foo', 'bar'); - cpExecCbs[0](null, 'This is stdout', 'This is stderr'); - - await expectAsync(extractPromise).toBeResolved(); - expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr'); - }); - - - it('should make the build directory non-writable', async () => { - const extractPromise = (bc as any).extractArchive('foo', 'bar'); - cpExecCbs[0](); - - await expectAsync(extractPromise).toBeResolved(); - expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar'); - }); - - - it('should delete the build artifact file on success', async () => { - const extractPromise = (bc as any).extractArchive('input/file', 'output/dir'); - cpExecCbs[0](); - - await expectAsync(extractPromise).toBeResolved(); - expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file'); - }); - - - describe('on error', () => { - - it('should abort and skip further operations if it fails to extract the archive', async () => { - const extractPromise = (bc as any).extractArchive('foo', 'bar'); - cpExecCbs[0]('Test'); - - await expectAsync(extractPromise).toBeRejectedWith('Test'); - expect(shellChmodSpy).not.toHaveBeenCalled(); - expect(shellRmSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and skip further operations if it fails to make non-writable', async () => { - // tslint:disable-next-line: no-string-throw - shellChmodSpy.and.callFake(() => { throw 'Test'; }); - - const extractPromise = (bc as any).extractArchive('foo', 'bar'); - cpExecCbs[0](); - - await expectAsync(extractPromise).toBeRejectedWith('Test'); - expect(shellChmodSpy).toHaveBeenCalled(); - expect(shellRmSpy).not.toHaveBeenCalled(); - }); - - - it('should abort and reject if it fails to remove the build artifact file', async () => { - // tslint:disable-next-line: no-string-throw - shellRmSpy.and.callFake(() => { throw 'Test'; }); - - const extractPromise = (bc as any).extractArchive('foo', 'bar'); - cpExecCbs[0](); - - await expectAsync(extractPromise).toBeRejectedWith('Test'); - expect(shellChmodSpy).toHaveBeenCalled(); - expect(shellRmSpy).toHaveBeenCalled(); - }); - - }); - - }); - - - describe('listShasByDate()', () => { - let shellLsSpy: jasmine.Spy; - const lsResult = (name: string, mtimeMs: number, isDirectory = true) => ({ - isDirectory: () => isDirectory, - mtime: new Date(mtimeMs), - name, - }); - - beforeEach(() => { - shellLsSpy = spyOn(shell, 'ls').and.returnValue([] as unknown as shell.ShellArray); - }); - - - it('should return a promise', async () => { - const promise = (bc as any).listShasByDate('input/dir'); - expect(promise).toBeInstanceOf(Promise); - - // Do not complete the test (and release the spies) synchronously to avoid running the actual `ls()`. - await promise; - }); - - - it('should `ls()` files with their metadata', async () => { - await (bc as any).listShasByDate('input/dir'); - expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir'); - }); - - - it('should reject if listing files fails', async () => { - shellLsSpy.and.rejectWith('Test'); - await expectAsync((bc as any).listShasByDate('input/dir')).toBeRejectedWith('Test'); - }); - - - it('should return the filenames', async () => { - shellLsSpy.and.resolveTo([ - lsResult('foo', 100), - lsResult('bar', 200), - lsResult('baz', 300), - ]); - - await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'bar', 'baz']); - }); - - - it('should sort by date', async () => { - shellLsSpy.and.resolveTo([ - lsResult('foo', 300), - lsResult('bar', 100), - lsResult('baz', 200), - ]); - - await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']); - }); - - - it('should not break with ShellJS\' custom `sort()` method', async () => { - const mockArray = [ - lsResult('foo', 300), - lsResult('bar', 100), - lsResult('baz', 200), - ]; - mockArray.sort = jasmine.createSpy('sort'); - - shellLsSpy.and.resolveTo(mockArray); - - await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']); - expect(mockArray.sort).not.toHaveBeenCalled(); - }); - - - it('should only include directories', async () => { - shellLsSpy.and.resolveTo([ - lsResult('foo', 100), - lsResult('bar', 200, false), - lsResult('baz', 300), - ]); - - await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'baz']); - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-events.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-events.spec.ts deleted file mode 100644 index 7c6af3fcad7..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-events.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Imports -import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events'; - -// Tests -describe('ChangedPrVisibilityEvent', () => { - let evt: ChangedPrVisibilityEvent; - - beforeEach(() => evt = new ChangedPrVisibilityEvent(42, ['foo', 'bar'], true)); - - - it('should have a static \'type\' property', () => { - expect(ChangedPrVisibilityEvent.type).toBe('pr.changedVisibility'); - }); - - - it('should have a \'pr\' property', () => { - expect(evt.pr).toBe(42); - }); - - - it('should have a \'shas\' property', () => { - expect(evt.shas).toEqual(['foo', 'bar']); - }); - - - it('should have an \'isPublic\' property', () => { - expect(evt.isPublic).toBe(true); - }); - -}); - - -describe('CreatedBuildEvent', () => { - let evt: CreatedBuildEvent; - - beforeEach(() => evt = new CreatedBuildEvent(42, 'bar', true)); - - - it('should have a static \'type\' property', () => { - expect(CreatedBuildEvent.type).toBe('build.created'); - }); - - - it('should have a \'pr\' property', () => { - expect(evt.pr).toBe(42); - }); - - - it('should have a \'sha\' property', () => { - expect(evt.sha).toBe('bar'); - }); - - - it('should have an \'isPublic\' property', () => { - expect(evt.isPublic).toBe(true); - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts deleted file mode 100644 index d980ea876ea..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as fs from 'fs'; -import * as nock from 'nock'; -import {resolve as resolvePath} from 'path'; -import {BuildInfo, CircleCiApi, PipelineInfo} from '../../lib/common/circle-ci-api'; -import {Logger} from '../../lib/common/utils'; -import {BuildRetriever} from '../../lib/preview-server/build-retriever'; - -describe('BuildRetriever', () => { - const MAX_DOWNLOAD_SIZE = 10000; - const DOWNLOAD_DIR = resolvePath('/DOWNLOAD/DIR'); - const BASE_URL = 'http://test.com'; - const ARTIFACT_PATH = '/some/path/build.zip'; - - let api: CircleCiApi; - let BUILD_INFO: BuildInfo; - let PIPELINE_INFO: PipelineInfo; - let WRITEFILE_RESULT: any; - let writeFileSpy: jasmine.Spy; - let EXISTS_RESULT: boolean; - let existsSpy: jasmine.Spy; - let getBuildArtifactUrlSpy: jasmine.Spy; - - beforeEach(() => { - BUILD_INFO = { - number: 12345, - name: 'test_job', - organization: {name: 'ORG'}, - project: {name: 'REPO'}, - pipeline: {id: 'test_pipeline'}, - }; - PIPELINE_INFO = { - id: 'test_pipeline', - vcs: { - review_id: '777', - revision: 'COMMIT', - }, - }; - - api = new CircleCiApi('ORG', 'REPO', 'TOKEN'); - spyOn(api, 'getBuildInfo').and.resolveTo(BUILD_INFO); - spyOn(api, 'getPipelineInfo').and.resolveTo(PIPELINE_INFO); - getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl').and.resolveTo(BASE_URL + ARTIFACT_PATH); - - WRITEFILE_RESULT = undefined; - writeFileSpy = spyOn(fs, 'writeFile').and.callFake( - ((_path: string, _buffer: Buffer, callback: fs.NoParamCallback) => - callback(WRITEFILE_RESULT)) as typeof fs.writeFile, - ); - - EXISTS_RESULT = false; - existsSpy = spyOn(fs, 'exists').and.callFake( - ((_path, callback) => callback(EXISTS_RESULT)) as typeof fs.exists, - ); - }); - - describe('constructor', () => { - it('should fail if the "downloadSizeLimit" is invalid', () => { - expect(() => new BuildRetriever(api, NaN, DOWNLOAD_DIR)) - .toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`); - expect(() => new BuildRetriever(api, 0, DOWNLOAD_DIR)) - .toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`); - expect(() => new BuildRetriever(api, -1, DOWNLOAD_DIR)) - .toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`); - }); - - it('should fail if the "downloadDir" is missing', () => { - expect(() => new BuildRetriever(api, MAX_DOWNLOAD_SIZE, '')) - .toThrowError(`Missing or empty required parameter 'downloadDir'!`); - }); - }); - - - describe('getGithubInfo', () => { - it('should request the build and pipeline info from CircleCI', async () => { - const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR); - const info = await retriever.getGithubInfo(12345); - expect(api.getBuildInfo).toHaveBeenCalledWith(12345); - expect(api.getPipelineInfo).toHaveBeenCalledWith('test_pipeline'); - expect(info).toEqual({org: 'ORG', pr: 777, repo: 'REPO', sha: 'COMMIT'}); - }); - }); - - - describe('downloadBuildArtifact', () => { - const ARTIFACT_CONTENTS = 'ARTIFACT CONTENTS'; - let retriever: BuildRetriever; - - beforeEach(() => { - spyOn(Logger.prototype, 'warn'); - retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR); - }); - - it('should get the artifact URL from the CircleCI API', async () => { - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); - await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); - expect(api.getBuildArtifactUrl).toHaveBeenCalledWith(12345, ARTIFACT_PATH); - artifactRequest.done(); - }); - - it('should download the artifact from its URL', async () => { - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); - await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); - // The following line proves that the artifact URL fetch occurred. - artifactRequest.done(); - }); - - it('should fail if the artifact is too large', async () => { - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); - retriever = new BuildRetriever(api, 10, DOWNLOAD_DIR); - - await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)). - toBeRejectedWith(jasmine.objectContaining({status: 413})); - - artifactRequest.done(); - }); - - it('should not download the artifact if it already exists', async () => { - const artifactRequestInterceptor = nock(BASE_URL).get(ARTIFACT_PATH); - const artifactRequest = artifactRequestInterceptor.reply(200, ARTIFACT_CONTENTS); - EXISTS_RESULT = true; - await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); - expect(existsSpy).toHaveBeenCalled(); - expect(getBuildArtifactUrlSpy).not.toHaveBeenCalled(); - expect(artifactRequest.isDone()).toEqual(false); - nock.removeInterceptor(artifactRequestInterceptor); - }); - - it('should write the artifact file to disk', async () => { - 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); - expect(writeFileSpy).toHaveBeenCalledWith(downloadPath, jasmine.any(Buffer), jasmine.any(Function)); - - const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1]; - expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS); - - artifactRequest.done(); - }); - - it('should fail if the CircleCI API fails', async () => { - getBuildArtifactUrlSpy.and.rejectWith('getBuildArtifactUrl failed'); - await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)). - toBeRejectedWithError('CircleCI artifact download failed (getBuildArtifactUrl failed)'); - }); - - it('should fail if the URL fetch errors', async () => { - // create a new handler that errors - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).replyWithError('Artifact Request Failed'); - - await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).toBeRejectedWithError( - 'CircleCI artifact download failed ' + - '(request to http://test.com/some/path/build.zip failed, reason: Artifact Request Failed)'); - - artifactRequest.done(); - }); - - it('should fail if the URL fetch 404s', async () => { - // create a new handler that errors - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(404, 'No such artifact'); - - await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)). - toBeRejectedWithError(`CircleCI artifact download failed (${BASE_URL}/some/path/build.zip: 404 - Not Found)`); - - artifactRequest.done(); - }); - - it('should fail if file write fails', async () => { - const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); - WRITEFILE_RESULT = 'Test Error'; - - await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)). - toBeRejectedWithError('CircleCI artifact download failed (Test Error)'); - - artifactRequest.done(); - }); - }); -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-verifier.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-verifier.spec.ts deleted file mode 100644 index a977ecb99ea..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-verifier.spec.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Imports -import {GithubApi} from '../../lib/common/github-api'; -import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests'; -import {GithubTeams} from '../../lib/common/github-teams'; -import {BuildVerifier} from '../../lib/preview-server/build-verifier'; - -// Tests -describe('BuildVerifier', () => { - const defaultConfig = { - allowedTeamSlugs: ['team1', 'team2'], - githubOrg: 'organization', - githubRepo: 'repo', - githubToken: 'githubToken', - secret: 'secret', - trustedPrLabel: 'trusted: pr-label', - }; - let prs: GithubPullRequests; - let bv: BuildVerifier; - - // Helpers - const createBuildVerifier = (partialConfig: Partial = {}) => { - const cfg = {...defaultConfig, ...partialConfig} as typeof defaultConfig; - const api = new GithubApi(cfg.githubToken); - prs = new GithubPullRequests(api, cfg.githubOrg, cfg.githubRepo); - const teams = new GithubTeams(api, cfg.githubOrg); - return new BuildVerifier(prs, teams, cfg.allowedTeamSlugs, cfg.trustedPrLabel); - }; - - beforeEach(() => bv = createBuildVerifier()); - - - describe('constructor()', () => { - - ['githubToken', 'githubRepo', 'githubOrg', 'allowedTeamSlugs', 'trustedPrLabel']. - forEach(param => { - it(`should throw if '${param}' is missing or empty`, () => { - expect(() => createBuildVerifier({[param]: ''})). - toThrowError(`Missing or empty required parameter '${param}'!`); - }); - }); - - - it('should throw if \'allowedTeamSlugs\' is an empty array', () => { - expect(() => createBuildVerifier({allowedTeamSlugs: []})). - toThrowError('Missing or empty required parameter \'allowedTeamSlugs\'!'); - }); - - }); - - - describe('getSignificantFilesChanged', () => { - it('should return false if none of the fetched files match the given pattern', async () => { - const fetchFilesSpy = spyOn(prs, 'fetchFiles'); - fetchFilesSpy.and.resolveTo([ - {filename: 'a/b/c', sha: 'a1'}, - {filename: 'd/e/f', sha: 'b2'}, - ]); - expect(await bv.getSignificantFilesChanged(777, /^x/)).toEqual(false); - expect(fetchFilesSpy).toHaveBeenCalledWith(777); - - fetchFilesSpy.calls.reset(); - expect(await bv.getSignificantFilesChanged(777, /^a/)).toEqual(true); - expect(fetchFilesSpy).toHaveBeenCalledWith(777); - }); - }); - - - describe('getPrIsTrusted()', () => { - const pr = 9; - let mockPrInfo: PullRequest; - let prsFetchSpy: jasmine.Spy; - let teamsIsMemberBySlugSpy: jasmine.Spy; - - beforeEach(() => { - mockPrInfo = { - labels: [ - {name: 'foo'}, - {name: 'bar'}, - ], - number: 9, - user: {login: 'username'}, - }; - - prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').and.resolveTo(mockPrInfo); - teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').and.resolveTo(true); - }); - - - it('should return a promise', async () => { - const promise = bv.getPrIsTrusted(pr); - expect(promise).toBeInstanceOf(Promise); - - // Do not complete the test (and release the spies) synchronously to avoid running the actual - // `GithubTeams#isMemberBySlug()`. - await promise; - }); - - - it('should fetch the corresponding PR', async () => { - await bv.getPrIsTrusted(pr); - expect(prsFetchSpy).toHaveBeenCalledWith(pr); - }); - - - it('should fail if fetching the PR errors', async () => { - prsFetchSpy.and.rejectWith('Test'); - await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test'); - }); - - - describe('when the PR has the "trusted PR" label', () => { - - beforeEach(() => mockPrInfo.labels.push({name: 'trusted: pr-label'})); - - - it('should resolve to true', async () => { - await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true); - }); - - - it('should not try to verify the author\'s membership status', async () => { - await expectAsync(bv.getPrIsTrusted(pr)); - expect(teamsIsMemberBySlugSpy).not.toHaveBeenCalled(); - }); - - }); - - - describe('when the PR does not have the "trusted PR" label', () => { - - it('should verify the PR author\'s membership in the specified teams', async () => { - await bv.getPrIsTrusted(pr); - expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']); - }); - - - it('should fail if verifying membership errors', async () => { - teamsIsMemberBySlugSpy.and.rejectWith('Test'); - await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test'); - }); - - - it('should resolve to true if the PR\'s author is a member', async () => { - teamsIsMemberBySlugSpy.and.resolveTo(true); - await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true); - }); - - - it('should resolve to false if the PR\'s author is not a member', async () => { - teamsIsMemberBySlugSpy.and.resolveTo(false); - await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(false); - }); - - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers-types.d.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers-types.d.ts deleted file mode 100644 index 8ea1e7105dc..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers-types.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module jasmine { - interface AsyncMatchers { - toBeRejectedWithPreviewServerError(status: number, message?: string | RegExp): Promise; - } -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers.ts deleted file mode 100644 index 29aea53d2ba..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/jasmine-custom-async-matchers.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {PreviewServerError} from '../../lib/preview-server/preview-error'; - - -// Matchers -const toBeRejectedWithPreviewServerError: jasmine.CustomAsyncMatcherFactory = matchersUtil => { - return { - async compare(actualPromise: Promise, expectedStatus: number, expectedMessage?: string | RegExp) { - if (!(actualPromise instanceof Promise)) { - throw new Error(`Expected '${toBeRejectedWithPreviewServerError.name}()' to be called on a promise.`); - } - - try { - await actualPromise; - - return { - pass: false, - message: `Expected a promise to be rejected with a '${PreviewServerError.name}', but it was resolved.`, - }; - } catch (actualError) { - const actualPrintValue = stringify(actualError); - const expectedPrintValue = - stringify(new PreviewServerError(expectedStatus, expectedMessage && `${expectedMessage}`)); - - const pass = errorMatches(actualError, expectedStatus, expectedMessage); - const message = - `Expected a promise ${pass ? 'not ' : ''}to be rejected with ${expectedPrintValue}, but is was` + - `${pass ? '' : ` rejected with ${actualPrintValue}`}.`; - - return {pass, message}; - } - }, - }; - - // Helpers - function errorMatches(actualErr: unknown, expectedStatus: number, expectedMsg?: string | RegExp): boolean { - if (!(actualErr instanceof PreviewServerError)) return false; - if (actualErr.status !== expectedStatus) return false; - return messageMatches(actualErr.message, expectedMsg); - } - - function messageMatches(actualMsg: string, expectedMsg?: string | RegExp): boolean { - if (typeof expectedMsg === 'undefined') return true; - if (typeof expectedMsg === 'string') return actualMsg === expectedMsg; - return expectedMsg.test(actualMsg); - } - - function stringify(value: unknown): string { - if (value instanceof PreviewServerError) { - return `${PreviewServerError.name}(${value.status}${value.message ? `, ${value.message}` : ''})`; - } - - return matchersUtil.pp(value); - } -}; - -// Exports -export const customAsyncMatchers: jasmine.CustomAsyncMatcherFactories = { - toBeRejectedWithPreviewServerError, -}; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-error.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-error.spec.ts deleted file mode 100644 index 61ea1a0c9b9..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-error.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Imports -import {PreviewServerError} from '../../lib/preview-server/preview-error'; - -// Tests -describe('PreviewServerError', () => { - let err: PreviewServerError; - - beforeEach(() => err = new PreviewServerError(999, 'message')); - - - it('should extend Error', () => { - expect(err).toBeInstanceOf(PreviewServerError); - expect(err).toBeInstanceOf(Error); - - expect(Object.getPrototypeOf(err)).toBe(PreviewServerError.prototype); - }); - - - it('should have a \'status\' property', () => { - expect(err.status).toBe(999); - }); - - - it('should have a \'message\' property', () => { - expect(err.message).toBe('message'); - }); - - - it('should have a 500 \'status\' by default', () => { - expect(new PreviewServerError().status).toBe(500); - }); - - - it('should have an empty \'message\' by default', () => { - expect(new PreviewServerError().message).toBe(''); - expect(new PreviewServerError(999).message).toBe(''); - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-server-factory.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-server-factory.spec.ts deleted file mode 100644 index 8d0664e6093..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/preview-server-factory.spec.ts +++ /dev/null @@ -1,687 +0,0 @@ -// Imports -import * as http from 'http'; -import * as supertest from 'supertest'; -import {CircleCiApi} from '../../lib/common/circle-ci-api'; -import {GithubApi} from '../../lib/common/github-api'; -import {GithubPullRequests} from '../../lib/common/github-pull-requests'; -import {GithubTeams} from '../../lib/common/github-teams'; -import {Logger} from '../../lib/common/utils'; -import {BuildCreator} from '../../lib/preview-server/build-creator'; -import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events'; -import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever'; -import {BuildVerifier} from '../../lib/preview-server/build-verifier'; -import {PreviewServerConfig, PreviewServerFactory} from '../../lib/preview-server/preview-server-factory'; - -interface CircleCiWebHookPayload { - payload: { - build_num: number; - build_parameters: { - CIRCLE_JOB: string; - } - }; -} - -// Tests -describe('PreviewServerFactory', () => { - const defaultConfig: PreviewServerConfig = { - buildArtifactPath: 'artifact/path.zip', - buildsDir: 'builds/dir', - circleCiToken: 'CIRCLE_CI_TOKEN', - domainName: 'domain.name', - downloadSizeLimit: 999, - downloadsDir: '/tmp/aio-create-builds', - githubOrg: 'organisation', - githubRepo: 'repo', - githubTeamSlugs: ['team1', 'team2'], - githubToken: '12345', - significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)', - trustedPrLabel: 'trusted: pr-label', - }; - let loggerErrorSpy: jasmine.Spy; - let loggerInfoSpy: jasmine.Spy; - let loggerLogSpy: jasmine.Spy; - - // Helpers - const createPreviewServer = (partialConfig: Partial = {}) => - PreviewServerFactory.create({...defaultConfig, ...partialConfig}); - - beforeEach(() => { - loggerErrorSpy = spyOn(Logger.prototype, 'error'); - loggerInfoSpy = spyOn(Logger.prototype, 'info'); - loggerLogSpy = spyOn(Logger.prototype, 'log'); - }); - - describe('create()', () => { - let usfCreateMiddlewareSpy: jasmine.Spy; - - beforeEach(() => { - usfCreateMiddlewareSpy = spyOn(PreviewServerFactory, 'createMiddleware').and.callThrough(); - }); - - - it('should throw if \'buildsDir\' is missing or empty', () => { - expect(() => createPreviewServer({buildsDir: ''})). - toThrowError('Missing or empty required parameter \'buildsDir\'!'); - }); - - - it('should throw if \'domainName\' is missing or empty', () => { - expect(() => createPreviewServer({domainName: ''})). - toThrowError('Missing or empty required parameter \'domainName\'!'); - }); - - - it('should throw if \'githubToken\' is missing or empty', () => { - expect(() => createPreviewServer({githubToken: ''})). - toThrowError('Missing or empty required parameter \'githubToken\'!'); - }); - - - it('should throw if \'githubOrg\' is missing or empty', () => { - expect(() => createPreviewServer({githubOrg: ''})). - toThrowError('Missing or empty required parameter \'githubOrg\'!'); - }); - - - it('should throw if \'githubTeamSlugs\' is missing or empty', () => { - expect(() => createPreviewServer({githubTeamSlugs: []})). - toThrowError('Missing or empty required parameter \'allowedTeamSlugs\'!'); - }); - - - it('should throw if \'githubRepo\' is missing or empty', () => { - expect(() => createPreviewServer({githubRepo: ''})). - toThrowError('Missing or empty required parameter \'githubRepo\'!'); - }); - - - it('should throw if \'trustedPrLabel\' is missing or empty', () => { - expect(() => createPreviewServer({trustedPrLabel: ''})). - toThrowError('Missing or empty required parameter \'trustedPrLabel\'!'); - }); - - - it('should return an http.Server', () => { - const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough(); - const server = createPreviewServer(); - - expect(server).toBe(httpCreateServerSpy.calls.mostRecent().returnValue); - }); - - - it('should create and use an appropriate BuildCreator', () => { - const usfCreateBuildCreatorSpy = spyOn(PreviewServerFactory, 'createBuildCreator').and.callThrough(); - - createPreviewServer(); - const buildRetriever = jasmine.any(BuildRetriever); - const buildVerifier = jasmine.any(BuildVerifier); - const prs = jasmine.any(GithubPullRequests); - const buildCreator: BuildCreator = usfCreateBuildCreatorSpy.calls.mostRecent().returnValue; - - expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(buildRetriever, buildVerifier, buildCreator, defaultConfig); - expect(usfCreateBuildCreatorSpy).toHaveBeenCalledWith(prs, 'builds/dir', 'domain.name'); - }); - - - it('should create and use an appropriate middleware', () => { - const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough(); - - createPreviewServer(); - - const buildRetriever = jasmine.any(BuildRetriever); - const buildVerifier = jasmine.any(BuildVerifier); - const buildCreator = jasmine.any(BuildCreator); - expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(buildRetriever, buildVerifier, buildCreator, defaultConfig); - - const middleware = usfCreateMiddlewareSpy.calls.mostRecent().returnValue; - expect(httpCreateServerSpy).toHaveBeenCalledWith(middleware); - }); - - - it('should log the server address info on \'listening\'', () => { - const server = createPreviewServer(); - server.address = () => ({address: 'foo', family: '', port: 1337}); - - expect(loggerInfoSpy).not.toHaveBeenCalled(); - - server.emit('listening'); - expect(loggerInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...'); - }); - - }); - - - // Protected methods - - describe('createBuildCreator()', () => { - let buildCreator: BuildCreator; - - beforeEach(() => { - const api = new GithubApi(defaultConfig.githubToken); - const prs = new GithubPullRequests(api, defaultConfig.githubOrg, defaultConfig.githubRepo); - buildCreator = PreviewServerFactory.createBuildCreator(prs, defaultConfig.buildsDir, defaultConfig.domainName); - }); - - it('should pass the \'buildsDir\' to the BuildCreator', () => { - expect((buildCreator as any).buildsDir).toBe('builds/dir'); - }); - - - describe('on \'build.created\'', () => { - let prsAddCommentSpy: jasmine.Spy; - - beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment')); - - - it('should post a comment on GitHub for public previews', () => { - const commentBody = 'You can preview 1234567890 at https://pr42-1234567890.domain.name/.'; - - buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true}); - expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody); - }); - - - it('should not post a comment on GitHub for non-public previews', () => { - buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: false}); - expect(prsAddCommentSpy).not.toHaveBeenCalled(); - }); - - }); - - - describe('on \'pr.changedVisibility\'', () => { - let prsAddCommentSpy: jasmine.Spy; - - beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment')); - - - it('should post a comment on GitHub (for all SHAs) for PRs made public', () => { - const commentBody = 'You can preview 12345 at https://pr42-12345.domain.name/.\n' + - 'You can preview 67890 at https://pr42-67890.domain.name/.'; - - buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true}); - expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody); - }); - - - it('should not post a comment on GitHub if no SHAs were affected', () => { - buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: [], isPublic: true}); - expect(prsAddCommentSpy).not.toHaveBeenCalled(); - }); - - - it('should not post a comment on GitHub for PRs made non-public', () => { - buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: false}); - expect(prsAddCommentSpy).not.toHaveBeenCalled(); - }); - - }); - - - it('should pass the correct parameters to GithubPullRequests', () => { - const prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'); - - buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true}); - buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true}); - - const allCalls = prsAddCommentSpy.calls.all(); - const prs = allCalls[0].object as GithubPullRequests; - - expect(prsAddCommentSpy).toHaveBeenCalledTimes(2); - expect(prs).toBe(allCalls[1].object as GithubPullRequests); - expect(prs).toBeInstanceOf(GithubPullRequests); - expect(prs.repoSlug).toBe('organisation/repo'); - }); - - }); - - - describe('createMiddleware()', () => { - let buildRetriever: BuildRetriever; - let buildVerifier: BuildVerifier; - let buildCreator: BuildCreator; - let agent: supertest.SuperTest; - - beforeEach(() => { - const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo, - defaultConfig.circleCiToken); - const githubApi = new GithubApi(defaultConfig.githubToken); - const prs = new GithubPullRequests(githubApi, defaultConfig.githubOrg, defaultConfig.githubRepo); - const teams = new GithubTeams(githubApi, defaultConfig.githubOrg); - - buildRetriever = new BuildRetriever(circleCiApi, defaultConfig.downloadSizeLimit, defaultConfig.downloadsDir); - buildVerifier = new BuildVerifier(prs, teams, defaultConfig.githubTeamSlugs, defaultConfig.trustedPrLabel); - buildCreator = new BuildCreator(defaultConfig.buildsDir); - - const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator, - defaultConfig); - agent = supertest.agent(middleware); - }); - - - describe('GET /health-check', () => { - - it('should respond with 200', async () => { - await Promise.all([ - agent.get('/health-check').expect(200), - agent.get('/health-check/').expect(200), - ]); - }); - - - it('should respond with 404 for non-GET requests', async () => { - await Promise.all([ - agent.put('/health-check').expect(404), - agent.post('/health-check').expect(404), - agent.patch('/health-check').expect(404), - agent.delete('/health-check').expect(404), - ]); - }); - - - it('should respond with 404 if the path does not match exactly', async () => { - await Promise.all([ - agent.get('/health-check/foo').expect(404), - agent.get('/health-check-foo').expect(404), - agent.get('/health-checknfoo').expect(404), - agent.get('/foo/health-check').expect(404), - agent.get('/foo-health-check').expect(404), - agent.get('/foonhealth-check').expect(404), - ]); - }); - - }); - - - describe('GET /can-have-public-preview/', () => { - 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.resolveTo(true); - bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').and.resolveTo(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.resolveTo(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.resolveTo(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.rejectWith('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 () => { - bvGetPrIsTrustedSpy.and.throwError('getPrIsTrusted error'); - - await agent.get(url).expect(500, 'getPrIsTrusted error'); - expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', new Error('getPrIsTrusted error')); - }); - - }); - - - describe('POST /circle-build', () => { - let getGithubInfoSpy: jasmine.Spy; - let getSignificantFilesChangedSpy: jasmine.Spy; - let downloadBuildArtifactSpy: jasmine.Spy; - let getPrIsTrustedSpy: jasmine.Spy; - let createBuildSpy: jasmine.Spy; - let IS_PUBLIC: boolean; - let BUILD_INFO: GithubInfo; - let AFFECTS_SIGNIFICANT_FILES: boolean; - let BASIC_PAYLOAD: CircleCiWebHookPayload; - const URL = '/circle-build'; - const BUILD_NUM = 12345; - const PR = 777; - const SHA = 'COMMIT'; - const DOWNLOADED_ARTIFACT_PATH = 'downloads/777-COMMIT-build.zip'; - - beforeEach(() => { - IS_PUBLIC = true; - BUILD_INFO = { - org: defaultConfig.githubOrg, - pr: PR, - repo: defaultConfig.githubRepo, - sha: SHA, - }; - BASIC_PAYLOAD = { payload: { build_num: BUILD_NUM, build_parameters: { CIRCLE_JOB: 'aio_preview' } } }; - AFFECTS_SIGNIFICANT_FILES = true; - getGithubInfoSpy = spyOn(buildRetriever, 'getGithubInfo') - .and.callFake(() => Promise.resolve(BUILD_INFO)); - getSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged') - .and.callFake(() => Promise.resolve(AFFECTS_SIGNIFICANT_FILES)); - downloadBuildArtifactSpy = spyOn(buildRetriever, 'downloadBuildArtifact') - .and.callFake(() => Promise.resolve(DOWNLOADED_ARTIFACT_PATH)); - getPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted') - .and.callFake(() => Promise.resolve(IS_PUBLIC)); - createBuildSpy = spyOn(buildCreator, 'create'); - }); - - it('should respond with 400 if the request body is not in the correct format', async () => { - await Promise.all([ - agent.post(URL).expect(400), - agent.post(URL).send().expect(400), - agent.post(URL).send({}).expect(400), - agent.post(URL).send({ payload: {} }).expect(400), - agent.post(URL).send({ payload: { build_num: -1 } }).expect(400), - agent.post(URL).send({ payload: { build_num: 4000 } }).expect(400), - agent.post(URL).send({ payload: { build_num: 4000, build_parameters: { } } }).expect(400), - agent.post(URL).send({ payload: { build_num: 4000, build_parameters: { CIRCLE_JOB: '' } } }).expect(400), - ]); - }); - - it('should create a preview if everything is good and the build succeeded', async () => { - await agent.post(URL).send(BASIC_PAYLOAD).expect(201); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp)); - expect(downloadBuildArtifactSpy).toHaveBeenCalledWith(BUILD_NUM, PR, SHA, defaultConfig.buildArtifactPath); - expect(getPrIsTrustedSpy).toHaveBeenCalledWith(PR); - expect(createBuildSpy).toHaveBeenCalledWith(PR, SHA, DOWNLOADED_ARTIFACT_PATH, IS_PUBLIC); - }); - - it('should respond with 204 if the reported build is not the "AIO preview" job', async () => { - BASIC_PAYLOAD.payload.build_parameters.CIRCLE_JOB = 'lint'; - await agent.post(URL).send(BASIC_PAYLOAD).expect(204); - expect(getGithubInfoSpy).not.toHaveBeenCalled(); - expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled(); - expect(loggerLogSpy).toHaveBeenCalledWith( - 'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.'); - expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); - expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); - expect(createBuildSpy).not.toHaveBeenCalled(); - }); - - it('should respond with 204 if the build did not affect any significant files', async () => { - AFFECTS_SIGNIFICANT_FILES = false; - await agent.post(URL).send(BASIC_PAYLOAD).expect(204); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp)); - expect(loggerLogSpy).toHaveBeenCalledWith( - 'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.'); - expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); - expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); - expect(createBuildSpy).not.toHaveBeenCalled(); - }); - - it('should respond with 201 if the build is trusted', async () => { - IS_PUBLIC = true; - await agent.post(URL).send(BASIC_PAYLOAD).expect(201); - }); - - it('should respond with 202 if the build is not trusted', async () => { - IS_PUBLIC = false; - await agent.post(URL).send(BASIC_PAYLOAD).expect(202); - }); - - it('should fail if the CircleCI request fails', async () => { - // Note it is important to put the `reject` into `and.callFake`; - // If you just `and.returnValue` the rejected promise - // then you get an "unhandled rejection" message in the console. - getGithubInfoSpy.and.rejectWith('Test Error'); - await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); - expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); - expect(createBuildSpy).not.toHaveBeenCalled(); - }); - - it('should fail if the Github organisation of the build does not match the configured organisation', async () => { - BUILD_INFO.org = 'bad'; - await agent.post(URL).send(BASIC_PAYLOAD) - .expect(500, `Invalid webhook: expected "githubOrg" property to equal "organisation" but got "bad".`); - }); - - it('should fail if the Github repo of the build does not match the configured repo', async () => { - BUILD_INFO.repo = 'bad'; - await agent.post(URL).send(BASIC_PAYLOAD) - .expect(500, `Invalid webhook: expected "githubRepo" property to equal "repo" but got "bad".`); - }); - - it('should fail if the artifact fetch request fails', async () => { - downloadBuildArtifactSpy.and.rejectWith('Test Error'); - await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(downloadBuildArtifactSpy).toHaveBeenCalled(); - expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); - expect(createBuildSpy).not.toHaveBeenCalled(); - }); - - it('should fail if verifying the PR fails', async () => { - getPrIsTrustedSpy.and.rejectWith('Test Error'); - await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(downloadBuildArtifactSpy).toHaveBeenCalled(); - expect(getPrIsTrustedSpy).toHaveBeenCalled(); - expect(createBuildSpy).not.toHaveBeenCalled(); - }); - - it('should fail if creating the preview build fails', async () => { - createBuildSpy.and.rejectWith('Test Error'); - await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); - expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); - expect(downloadBuildArtifactSpy).toHaveBeenCalled(); - expect(getPrIsTrustedSpy).toHaveBeenCalled(); - expect(createBuildSpy).toHaveBeenCalled(); - }); - }); - - - describe('POST /pr-updated', () => { - const pr = '9'; - const url = '/pr-updated'; - let bvGetPrIsTrustedSpy: jasmine.Spy; - let bcUpdatePrVisibilitySpy: jasmine.Spy; - - // Helpers - const createRequest = (num: number, action?: string) => - agent.post(url).send({number: num, action}); - - beforeEach(() => { - bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted'); - bcUpdatePrVisibilitySpy = spyOn(buildCreator, 'updatePrVisibility'); - }); - - - it('should respond with 404 for non-POST requests', async () => { - await Promise.all([ - agent.get(url).expect(404), - agent.put(url).expect(404), - agent.patch(url).expect(404), - agent.delete(url).expect(404), - ]); - }); - - - it('should respond with 400 for requests without a payload', async () => { - const responseBody = `Missing or empty 'number' field in request: POST ${url} {}`; - - const request1 = agent.post(url); - const request2 = agent.post(url).send(); - - await Promise.all([ - request1.expect(400, responseBody), - request2.expect(400, responseBody), - ]); - }); - - - it('should respond with 400 for requests without a \'number\' field', async () => { - const responseBodyPrefix = `Missing or empty 'number' field in request: POST ${url}`; - - const request1 = agent.post(url).send({}); - const request2 = agent.post(url).send({number: null}); - - await Promise.all([ - request1.expect(400, `${responseBodyPrefix} {}`), - request2.expect(400, `${responseBodyPrefix} {"number":null}`), - ]); - }); - - - it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => { - await createRequest(+pr); - expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9); - }); - - - it('should propagate errors from BuildVerifier', async () => { - bvGetPrIsTrustedSpy.and.rejectWith('Test'); - - await createRequest(+pr).expect(500, 'Test'); - - expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9); - expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled(); - }); - - - it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => { - bvGetPrIsTrustedSpy. - withArgs(24).and.resolveTo(false). - withArgs(42).and.resolveTo(true); - - await createRequest(24); - expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false); - - await createRequest(42); - expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true); - }); - - - it('should propagate errors from BuildCreator', async () => { - bcUpdatePrVisibilitySpy.and.rejectWith('Test'); - await createRequest(+pr).expect(500, 'Test'); - }); - - - describe('on success', () => { - - it('should respond with 200 (action: undefined)', async () => { - bvGetPrIsTrustedSpy. - withArgs(2).and.resolveTo(false). - withArgs(4).and.resolveTo(true); - - const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200])); - await Promise.all(reqs); - }); - - - it('should respond with 200 (action: labeled)', async () => { - bvGetPrIsTrustedSpy. - withArgs(2).and.resolveTo(false). - withArgs(4).and.resolveTo(true); - - const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200])); - await Promise.all(reqs); - }); - - - it('should respond with 200 (action: unlabeled)', async () => { - bvGetPrIsTrustedSpy. - withArgs(2).and.resolveTo(false). - withArgs(4).and.resolveTo(true); - - const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200])); - await Promise.all(reqs); - }); - - - it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => { - const promises = ['foo', 'notlabeled']. - map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])); - - await Promise.all(promises); - expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled(); - expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled(); - }); - - }); - - }); - - - describe('ALL *', () => { - - it('should respond with 404', async () => { - const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`; - - await Promise.all([ - agent.get('/some/url').expect(404, responseFor('get')), - agent.put('/some/url').expect(404, responseFor('put')), - agent.post('/some/url').expect(404, responseFor('post')), - agent.patch('/some/url').expect(404, responseFor('patch')), - agent.delete('/some/url').expect(404, responseFor('delete')), - ]); - }); - - }); - - }); - -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/utils.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/utils.spec.ts deleted file mode 100644 index 5f0f36beaa2..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/utils.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as express from 'express'; - -import {PreviewServerError} from '../../lib/preview-server/preview-error'; -import {respondWithError, throwRequestError} from '../../lib/preview-server/utils'; - -describe('preview-server/utils', () => { - describe('respondWithError', () => { - let endSpy: jasmine.Spy; - let statusSpy: jasmine.Spy; - let response: express.Response; - - beforeEach(() => { - endSpy = jasmine.createSpy('end'); - statusSpy = jasmine.createSpy('status').and.callFake(() => response); - response = {status: statusSpy, end: endSpy} as any; - }); - - it('should set the status on the response', () => { - respondWithError(response, new PreviewServerError(505, 'TEST MESSAGE')); - expect(statusSpy).toHaveBeenCalledWith(505); - expect(endSpy).toHaveBeenCalledWith('TEST MESSAGE', jasmine.any(Function)); - }); - - it('should convert non-PreviewServerError errors to 500 PreviewServerErrors', () => { - respondWithError(response, new Error('OTHER MESSAGE')); - expect(statusSpy).toHaveBeenCalledWith(500); - expect(endSpy).toHaveBeenCalledWith('OTHER MESSAGE', jasmine.any(Function)); - }); - }); - - describe('throwRequestError', () => { - it('should throw a suitable error', () => { - let caught = false; - try { - const request = { - body: 'The request body', - method: 'POST', - originalUrl: 'some.domain.com/path', - } as express.Request; - throwRequestError(505, 'ERROR MESSAGE', request); - } catch (error: any) { - caught = true; - expect(error).toBeInstanceOf(PreviewServerError); - expect(error.status).toEqual(505); - expect(error.message) - .toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`); - } - expect(caught).toEqual(true); - }); - }); -}); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/tsconfig.json b/aio/aio-builds-setup/dockerbuild/scripts-js/tsconfig.json deleted file mode 100644 index 27847c75ae6..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/tsconfig.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "compilerOptions": { - /* Basic Options */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": [ - "es2015", - "es2016.array.include" - ], /* Specify library files to be included in the compilation: */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "dist", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - "noUnusedLocals": true, /* Report errors on unused locals. */ - "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - "typeRoots": [ - "node_modules/@types" - ], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - /* Source Map Options */ - // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ - "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Other */ - "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ - "newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */ - "pretty": true, /* Stylize errors and messages using color and context. */ - "skipLibCheck": true /* Skip type checking of all declaration files (*.d.ts). */ - }, - "include": [ - "lib/**/*", - "test/**/*" - ] -} \ No newline at end of file diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json b/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json deleted file mode 100644 index 76c3758506e..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": { - "array-type": [true, "array"], - "arrow-parens": [true, "ban-single-arg-parens"], - "interface-name": [true, "never-prefix"], - "max-classes-per-file": [true, 4], - "no-consecutive-blank-lines": [true, 2], - "no-console": [false], - "no-focused-test": true, - "no-namespace": [true, "allow-declarations"], - "no-skipped-test": true, - "no-string-literal": false, - "quotemark": [true, "single"], - "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"] - }, - "rulesDirectory": [ - "node_modules/tslint-jasmine-noSkipOrFocus" - ] -} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/yarn.lock b/aio/aio-builds-setup/dockerbuild/scripts-js/yarn.lock deleted file mode 100644 index 58f750cf73c..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/yarn.lock +++ /dev/null @@ -1,1758 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@types/body-parser@*", "@types/body-parser@^1.19.0": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/cookiejar@*": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" - integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== - -"@types/express-serve-static-core@^4.17.18": - version "4.17.29" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c" - integrity sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.8": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/glob@*": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/jasmine@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.0.3.tgz#097ce710d70eb7f3662e96c1f75824dd22c27d5c" - integrity sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg== - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/minimatch@*": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/nock@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@types/nock/-/nock-11.1.0.tgz#0a8c1056a31ba32a959843abccf99626dd90a538" - integrity sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw== - dependencies: - nock "*" - -"@types/node-fetch@^2.5.7": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" - integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node@*": - version "18.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199" - integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ== - -"@types/node@^14.6.4": - version "14.18.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41" - integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" - integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/shelljs@^0.8.8": - version "0.8.11" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.11.tgz#17a5696c825974e96828e96e89585d685646fcb8" - integrity sha512-x9yaMvEh5BEaZKeVQC4vp3l+QoFj3BXcd4aYfuKSzIIyihjdVARAadYy3SMNIz0WCCdS2vB9JL/U6GQk5PaxQw== - dependencies: - "@types/glob" "*" - "@types/node" "*" - -"@types/superagent@*": - version "4.1.15" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a" - integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ== - dependencies: - "@types/cookiejar" "*" - "@types/node" "*" - -"@types/supertest@^2.0.10": - version "2.0.12" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.12.tgz#ddb4a0568597c9aadff8dbec5b2e8fddbe8692fc" - integrity sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ== - dependencies: - "@types/superagent" "*" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -ansi-colors@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -asap@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -body-parser@1.20.0, body-parser@^1.19.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chokidar@^3.5.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.12.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -component-emitter@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -cookiejar@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" - integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== - -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -delete-empty@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/delete-empty/-/delete-empty-3.0.0.tgz#f8040f2669f26fa7060bc2304e9859c593b685e8" - integrity sha512-ZUyiwo76W+DYnKsL3Kim6M/UOavPdBJgDYWOmuQhYaZvJH0AXAHbUNyEDtRbBra8wqqr686+63/0azfEk1ebUQ== - dependencies: - ansi-colors "^4.1.0" - minimist "^1.2.0" - path-starts-with "^2.0.0" - rimraf "^2.6.2" - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -dezalgo@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ== - dependencies: - asap "^2.0.0" - wrappy "1" - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5: - version "1.20.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" - integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.1" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -express@^4.17.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.0" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.10.3" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-safe-stringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -formidable@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" - integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ== - dependencies: - dezalgo "1.0.3" - hexoid "1.0.0" - once "1.4.0" - qs "6.9.3" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -functions-have-names@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.2: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hexoid@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -jasmine-core@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-4.2.0.tgz#0605bea284d6d78276f43c47de2532ecd4a73b00" - integrity sha512-OcFpBrIhnbmb9wfI8cqPSJ50pv3Wg4/NSgoZIqHzIwO/2a9qivJWzv8hUvaREIMYYJBas6AvfXATFdVuzzCqVw== - -jasmine@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-4.2.1.tgz#55f81ab9d5d32f2645aa598d1356b6858407f4a9" - integrity sha512-LNZEKcScnjPRj5J92I1P515bxTvaHMRAERTyCoaGnWr87eOT6zv+b3M+kxKdH/06Gz4TnnXyHbXLiPtMHZ0ncw== - dependencies: - glob "^7.1.6" - jasmine-core "^4.2.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -methods@^1.1.2, methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mkdirp@^0.5.3: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -nock@*, nock@^13.0.4: - version "13.2.8" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.8.tgz#e2043ccaa8e285508274575e090a7fe1e46b90f1" - integrity sha512-JT42FrXfQRpfyL4cnbBEJdf4nmBpVP0yoCcSBr+xkT8Q1y3pgtaCKHGAAOIFcEJ3O3t0QbVAmid0S0f2bj3Wpg== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.21" - propagate "^2.0.0" - -node-fetch@^2.6.1: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -nodemon@^2.0.4: - version "2.0.19" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" - integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== - dependencies: - chokidar "^3.5.2" - debug "^3.2.7" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.8" - semver "^5.7.1" - simple-update-notifier "^1.0.7" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.5" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== - dependencies: - abbrev "1" - -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-all@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - -object-inspect@^1.12.0, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@1.4.0, once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-starts-with@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-starts-with/-/path-starts-with-2.0.0.tgz#ffd6d51926cd497022b44d392196033d5451892f" - integrity sha512-3UHTHbJz5+NLkPafFR+2ycJOjoc4WV2e9qCZCnm71zHiWaFrm1XniLVTkZXvaRgxr1xFh9JsTdicpH2yM03nLA== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pidtree@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" - integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - -propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@6.9.3: - version "6.9.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" - integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== - -qs@^6.10.3: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rimraf@^2.6.1, rimraf@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@~7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - -shelljs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -simple-update-notifier@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" - integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== - dependencies: - semver "~7.0.0" - -source-map-support@^0.5.19: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -string.prototype.padend@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz#997a6de12c92c7cb34dc8a201a6c53d9bd88a5f1" - integrity sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -superagent@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-8.0.0.tgz#2ea4587df4b81ef023ec01ebc6e1bcb9e2344cb6" - integrity sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg== - dependencies: - component-emitter "^1.3.0" - cookiejar "^2.1.3" - debug "^4.3.4" - fast-safe-stringify "^2.1.1" - form-data "^4.0.0" - formidable "^2.0.1" - methods "^1.1.2" - mime "2.6.0" - qs "^6.10.3" - readable-stream "^3.6.0" - semver "^7.3.7" - -supertest@^6.0.0: - version "6.2.4" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.4.tgz#3dcebe42f7fd6f28dd7ac74c6cba881f7101b2f0" - integrity sha512-M8xVnCNv+q2T2WXVzxDECvL2695Uv2uUj2O0utxsld/HRyJvOU8W9f1gvsYxSNU4wmIe0/L/ItnpU4iKq0emDA== - dependencies: - methods "^1.1.2" - superagent "^8.0.0" - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tar-stream@^2.1.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tslib@^1.13.0, tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tslint-jasmine-noSkipOrFocus@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/tslint-jasmine-noSkipOrFocus/-/tslint-jasmine-noSkipOrFocus-1.0.9.tgz#a562d3a6b3aa5c51e5ad29211a6eeb06ad1a082f" - integrity sha512-+jIR/7lmm96mw5pDBqBAs5U9ygbpCw5d70mXeuPRKoR7pFL2cAgsRYk2nTnsuktt9pucQxA0DimTZlq6qM5Kng== - dependencies: - rimraf "^2.6.1" - -tslint@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" - integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.13.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@~4.8.0: - version "4.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790" - integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/clean-up.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/clean-up.sh deleted file mode 100644 index 49525ea46db..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/clean-up.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -# 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) - -# Run the clean-up -node $AIO_SCRIPTS_JS_DIR/dist/lib/clean-up >> /var/log/aio/clean-up.log 2>&1 diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/dev-mode.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/dev-mode.sh deleted file mode 100644 index 64ed6b73778..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/dev-mode.sh +++ /dev/null @@ -1,12 +0,0 @@ -# Link the scripts on the host to the scripts in the container -# - the host scripts are mounted as a volume at `/dockerbuild`) -# - the original scripts are moved to `..._prod` in case they are needed later -# See `aio/aio-builds-setup/docs/misc--debug-docker-container.md` for more info - -mv $AIO_SCRIPTS_SH_DIR ${AIO_SCRIPTS_SH_DIR}_prod -ln -s /dockerbuild/scripts-sh $AIO_SCRIPTS_SH_DIR -chmod a+x $AIO_SCRIPTS_SH_DIR/* - -mv $AIO_SCRIPTS_JS_DIR ${AIO_SCRIPTS_JS_DIR}_prod -ln -s /dockerbuild/scripts-js $AIO_SCRIPTS_JS_DIR - diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/health-check.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/health-check.sh deleted file mode 100644 index 1cb8ba0a903..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/health-check.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -# Using `+e` so that all checks are run and we get a complete report (even if some checks failed). -set +e -u -o pipefail - - -# Variables -exitCode=0 - - -# Helpers -function checkCert { - local certPath=$1 - - if [[ ! -f "$certPath" ]]; then - echo "Certificate '$certPath' does not exist. Skipping expiration check..." - return - fi - - openssl x509 -checkend 0 -in "$certPath" -noout > /dev/null - reportStatus "Certificate '$certPath'" - - if [[ $? -ne 0 ]]; then - echo " [WARN]" - echo " If you did not provide the certificate explicitly, try running the" - echo " 'docker build' command again with the '--no-cache' option to generate" - echo " a new self-signed certificate." - fi -} - -function reportStatus { - local lastExitCode=$? - - echo "$1: $([[ $lastExitCode -eq 0 ]] && echo OK || echo NOT OK)" - [[ $lastExitCode -eq 0 ]] || exitCode=1 - - return $lastExitCode -} - - -# Check services -services=( - rsyslog - cron - nginx - pm2-root -) -for s in ${services[@]}; do - service $s status > /dev/null - reportStatus "Service '$s'" -done - - -# Check SSL/TLS certificates expiration -certs=( - "$AIO_LOCALCERTS_DIR/$AIO_DOMAIN_NAME.crt" - "$TEST_AIO_LOCALCERTS_DIR/$TEST_AIO_DOMAIN_NAME.crt" -) -for c in ${certs[@]}; do - checkCert $c -done - - -# Check servers -origins=( - http://$AIO_PREVIEW_SERVER_HOSTNAME:$AIO_PREVIEW_SERVER_PORT - http://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTP - https://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTPS -) -for o in ${origins[@]}; do - curl --fail --silent $o/health-check > /dev/null - reportStatus "Server '$o'" -done - - -# Check resolution of external URLs -origins=( - https://google.com -) -for o in ${origins[@]}; do - curl --fail --silent $o > /dev/null - reportStatus "External URL '$o'" -done - - -# Exit -exit $exitCode diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/init.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/init.sh deleted file mode 100644 index b9f949e7f0c..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/init.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -exec >> /var/log/aio/init.log -exec 2>&1 - -# Start the services -echo [`date`] - Starting services... -mkdir -p $AIO_NGINX_LOGS_DIR -mkdir -p $TEST_AIO_NGINX_LOGS_DIR - -service rsyslog start -service cron start -service dnsmasq start -service nginx start -service pm2-root start -aio-preview-server-prod start -echo [`date`] - Services started successfully. diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-prod.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-prod.sh deleted file mode 100644 index 091384ee483..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-prod.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -# Set up env variables for production -export AIO_CIRCLE_CI_TOKEN=$(head -c -1 /aio-secrets/CIRCLE_CI_TOKEN 2>/dev/null || echo "MISSING_CIRCLE_CI_TOKEN") -export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || echo "MISSING_GITHUB_TOKEN") - -# Start the preview-server instance -action=$([ "$1" == "stop" ] && echo "stop" || echo "start") -pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/preview-server \ - --uid $AIO_WWW_USER \ - --log /var/log/aio/preview-server-prod.log \ - --name aio-preview-server-prod \ - ${@:2} diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-test.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-test.sh deleted file mode 100644 index 8a945504411..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/preview-server-test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -# Start the preview-server instance -appName=aio-preview-server-test -if [[ "$1" == "stop" ]]; then - pm2 delete $appName -else - source aio-test-env - pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-preview-server.js \ - --uid $AIO_WWW_USER \ - --log /var/log/aio/preview-server-test.log \ - --name $appName \ - --no-autorestart \ - ${@:2} -fi diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/test-env.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/test-env.sh deleted file mode 100644 index 00826bab4d2..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/test-env.sh +++ /dev/null @@ -1,19 +0,0 @@ - # Set up env variables for testing - export AIO_NGINX_HOSTNAME=$TEST_AIO_NGINX_HOSTNAME - export AIO_NGINX_PORT_HTTP=$TEST_AIO_NGINX_PORT_HTTP - export AIO_NGINX_PORT_HTTPS=$TEST_AIO_NGINX_PORT_HTTPS - - export AIO_ARTIFACT_PATH=$TEST_AIO_ARTIFACT_PATH - export AIO_BUILDS_DIR=$TEST_AIO_BUILDS_DIR - export AIO_DOMAIN_NAME=$TEST_AIO_DOMAIN_NAME - export AIO_GITHUB_ORGANIZATION=$TEST_AIO_GITHUB_ORGANIZATION - export AIO_GITHUB_REPO=$TEST_AIO_GITHUB_REPO - export AIO_GITHUB_TEAM_SLUGS=$TEST_AIO_GITHUB_TEAM_SLUGS - export AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN - export AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL - export AIO_PREVIEW_SERVER_HOSTNAME=$TEST_AIO_PREVIEW_SERVER_HOSTNAME - export AIO_PREVIEW_SERVER_PORT=$TEST_AIO_PREVIEW_SERVER_PORT - export AIO_ARTIFACT_MAX_SIZE=$TEST_AIO_ARTIFACT_MAX_SIZE - - export AIO_CIRCLE_CI_TOKEN=TEST_CIRCLE_CI_TOKEN - export AIO_GITHUB_TOKEN=TEST_GITHUB_TOKEN diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup-and-log.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup-and-log.sh deleted file mode 100644 index af8f876eca0..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup-and-log.sh +++ /dev/null @@ -1,2 +0,0 @@ -aio-verify-setup -ls -t /var/log/aio/preview-server-verify* | head -1 | xargs cat diff --git a/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup.sh b/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup.sh deleted file mode 100644 index a83fcca6937..00000000000 --- a/aio/aio-builds-setup/dockerbuild/scripts-sh/verify-setup.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -logFile=/var/log/aio/verify-setup.log -previewServerLogFile=/var/log/aio/preview-server-verify-setup.log - -exec 3>&1 -exec >> $logFile -exec 2>&1 - -echo "[`date`] - Starting verification..." - -# Helpers -function countdown { - message=$1 - secs=$2 - while [ $secs -gt 0 ]; do - echo -ne "$message in $secs...\033[0K\r" - sleep 1 - : $((secs--)) - done - echo -ne "\033[0K\r" -} - -function onExit { - echo -e "Stopping Test Server" - aio-preview-server-test stop - echo -e "Full logs in '$logFile'.\n" > /dev/fd/3 -} - -# Setup EXIT trap -trap 'onExit' EXIT - -# Start an preview-server instance for testing -echo -e "Starting Test Server" -aio-preview-server-test start --log $previewServerLogFile - -# Give the preview-server some time to start :( -countdown "Starting" 5 > /dev/fd/3 - -# Run the tests -echo Running the tests -source aio-test-env -node $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup | tee /dev/fd/3 diff --git a/aio/aio-builds-setup/docs/_TOC.md b/aio/aio-builds-setup/docs/_TOC.md deleted file mode 100644 index 4859c2b2642..00000000000 --- a/aio/aio-builds-setup/docs/_TOC.md +++ /dev/null @@ -1,34 +0,0 @@ -# VM Setup Instructions - - -## Overview -- [General overview](overview--general.md) -- [Security model](overview--security-model.md) -- [Available scripts and commands](overview--scripts-and-commands.md) -- [HTTP status codes](overview--http-status-codes.md) - - -## Setting up the VM -- [Set up secrets](vm-setup--set-up-secrets.md) -- [Set up docker](vm-setup--set-up-docker.md) -- [Attach persistent disk](vm-setup--attach-persistent-disk.md) -- [Create host directories and files](vm-setup--create-host-dirs-and-files.md) -- [Create docker image](vm-setup--create-docker-image.md) - - -## Configuring the docker image -- [Available environment variables](image-config--environment-variables.md) - - -## Starting the docker container -- [Start docker container](vm-setup--start-docker-container.md) - - -## Updating the docker container -- [Update docker container](vm-setup--update-docker-container.md) - - -## Miscellaneous -- [Debug docker container](misc--debug-docker-container.md) -- [Use Let's Encrypt certificates](misc--use-lets-encrypt-certs.md) -- [Integrate with CI](misc--integrate-with-ci.md) diff --git a/aio/aio-builds-setup/docs/image-config--environment-variables.md b/aio/aio-builds-setup/docs/image-config--environment-variables.md deleted file mode 100644 index e0ab89edab6..00000000000 --- a/aio/aio-builds-setup/docs/image-config--environment-variables.md +++ /dev/null @@ -1,67 +0,0 @@ -# Image config - Environment variables - - -Below is a list of environment variables that can be configured when creating the docker image (as -described [here](vm-setup--create-docker-image.md)). An up-to-date list of the configurable -environment variables and their default values can be found in the -[Dockerfile](../dockerbuild/Dockerfile). - -**Note:** -Each variable has a `TEST_` prefixed counterpart, which is used for testing purposes. In most cases -you don't need to specify values for those. - -- `AIO_ARTIFACT_PATH`: - The path used to identify the AIO build artifact on the CircleCI servers. This should be equal to - the path given in the `.circleci/config.yml` file for the - `aio_preview->steps->store_artifacts->destination` key. - -- `AIO_BUILDS_DIR`: - The directory (inside the container) where the hosted build artifacts are kept. - -- `AIO_DOMAIN_NAME`: - The domain name of the server. - -- `AIO_GITHUB_ORGANIZATION`: - The GitHub organization whose teams are trusted for accepting build artifacts. - See also `AIO_GITHUB_TEAM_SLUGS`. - -- `AIO_GITHUB_REPO`: - The GitHub repository for which PRs will be hosted. - -- `AIO_GITHUB_TEAM_SLUGS`: - A comma-separated list of teams, whose authors are allowed to preview PRs. - See also `AIO_GITHUB_ORGANIZATION`. - -- `AIO_NGINX_HOSTNAME`: - The internal hostname for accessing the nginx server. This is mostly used for performing a - periodic health-check. - -- `AIO_NGINX_PORT_HTTP`: - The port number on which nginx listens for HTTP connections. This should be mapped to the - corresponding port on the host VM (as described [here](vm-setup--start-docker-container.md)). - -- `AIO_NGINX_PORT_HTTPS`: - The port number on which nginx listens for HTTPS connections. This should be mapped to the - corresponding port on the host VM (as described [here](vm-setup--start-docker-container.md)). - -- `AIO_SIGNIFICANT_FILES_PATTERN`: - The RegExp that determines whether a changed file indicates that a new preview needs to - be deployed. For example, if there is a changed file in the `/packages` directory then - some of the API docs might have changed, so we need to create a new preview. - -- `AIO_TRUSTED_PR_LABEL`: - The PR whose presence indicates the PR has been manually verified and is allowed to have its - build artifacts publicly served. This is useful for enabling previews for any PR (not only those - from trusted authors). - -- `AIO_PREVIEW_SERVER_HOSTNAME`: - The internal hostname for accessing the Node.js preview-server. This is used by nginx for - delegating web-hook requests and also for performing a periodic health-check. - -- `AIO_ARTIFACT_MAX_SIZE`: - The maximum allowed size for the gzip archive containing the build artifacts. - Files larger than this will be rejected. - -- `AIO_PREVIEW_SERVER_PORT`: - The port number on which the Node.js preview-server listens for HTTP connections. This is used by - nginx for delegating web-hook requests and also for performing a periodic health-check. diff --git a/aio/aio-builds-setup/docs/misc--debug-docker-container.md b/aio/aio-builds-setup/docs/misc--debug-docker-container.md deleted file mode 100644 index 9f024bca227..00000000000 --- a/aio/aio-builds-setup/docs/misc--debug-docker-container.md +++ /dev/null @@ -1,48 +0,0 @@ -# Miscellaneous - Debug docker container - - -TODO (gkalpak): Add docs. Mention: -- `aio-health-check` -- `aio-verify-setup` -- Test nginx accessible at: - - `http://$TEST_AIO_NGINX_HOSTNAME:$TEST_AIO_NGINX_PORT_HTTP` - - `https://$TEST_AIO_NGINX_HOSTNAME:$TEST_AIO_NGINX_PORT_HTTPS` -- Test preview-server accessible at: - - `http://$TEST_AIO_PREVIEW_SERVER_HOSTNAME:$TEST_AIO_PREVIEW_SERVER_PORT` -- Local DNS (via dnsmasq) maps the above hostnames to 127.0.0.1 - - -## Developing the preview server TypeScript files - -If you are running Docker on OS/X then you can benefit from linking the built TypeScript -files (i.e. `script-js/dist`) to the JavaScript files inside the Docker container. - -First start watching and building the TypeScript files (in the host): - -```bash -yarn build-watch -``` - -Now build, start and attach to the Docker container. See "Setting up the VM" -section in [TOC](_TOC.md). Then link the JavaScript folders (in the container): - -```bash -aio-dev-mode -``` - -Now whenever you make changes to the TypeScript, it will be automatically built -in the host, and the changes are automatically available in the container. -You can then run the unit tests (in the container): - -```bash -aio-verify-setup -``` - -Sometimes, the errors in the unit test log are not enough to tell you what went wrong. -In that case you can also look at the log of the preview-server itself. -A helper script that runs the unit tests (i.e. `aio-verify-setup`) and displays the -last relevant test-preview-server log is: - -```bash -aio-verify-setup-and-log -``` diff --git a/aio/aio-builds-setup/docs/misc--integrate-with-ci.md b/aio/aio-builds-setup/docs/misc--integrate-with-ci.md deleted file mode 100644 index a22f2fc0174..00000000000 --- a/aio/aio-builds-setup/docs/misc--integrate-with-ci.md +++ /dev/null @@ -1,9 +0,0 @@ -# Miscellaneous - Integrate with CI - - -TODO (gkalpak): Add docs. Mention: -- Testing on CI. - Relevant files: `aio/aio-builds-setup/scripts/test.sh` -- Deploying from CI. - Relevant files: `.circleci/config.yml`, `scripts/ci/deploy.sh`, `aio/scripts/build-artifacts.sh`, - `aio/scripts/deploy-to-firebase/index.mjs` diff --git a/aio/aio-builds-setup/docs/misc--use-lets-encrypt-certs.md b/aio/aio-builds-setup/docs/misc--use-lets-encrypt-certs.md deleted file mode 100644 index a88a8916521..00000000000 --- a/aio/aio-builds-setup/docs/misc--use-lets-encrypt-certs.md +++ /dev/null @@ -1,80 +0,0 @@ -# Miscellaneous - Use Let's Encrypt TLS certificates - - -[Let's Encrypt](https://letsencrypt.org/) is a free, automated, and open certificate authority (CA), -provided by the [Internet Security Research Group (ISRG)](https://www.abetterinternet.org/). It can -be used for issuing the certificates needed by the preview server. See the "Create TLS certificates" -section in [this doc](vm-setup--create-host-dirs-and-files.md) for more details. - -Let's Encrypt supports issuing -[wildcard certificates](https://letsencrypt.org/docs/faq/#does-let-s-encrypt-issue-wildcard-certificates), -but they have to be renewed every 3 months using a -[DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). - -Below are instructions for the creation of certificates using Let's Encrypt. - -**Note 1:** - It is assumed that the commands are executed on the VM that hosts the preview server. You can run - the commands on any machine and adjust accordingly the steps for transfering the generated - certificate and private key to the preview server VM. - -**Note 2:** - In order to complete the DNS-01 challenge and generate the certificate, one needs to have access - to update the DNS records associated with the `ngbuilds.io` domain and subdomains. - - -## Prerequisites -- Install [certbot](https://certbot.eff.org/): - - Old method: `sudo apt-get install certbot` - - New method: Follow the instructions [here](https://certbot.eff.org/instructions). - - -## Create new certificate -- Run the following command and follow the on-screen instructions: - ```sh - sudo certbot certonly \ - -d "ngbuilds.io,*.ngbuilds.io" \ - -m "devops@angular.io" \ - --agree-tos \ - --manual \ - --manual-public-ip-logging-ok \ - --preferred-challenges="dns" - ``` - - **Hint:** - You can use `dig -t txt _acme-challenge.ngbuilds.io` (on Linux/macOS) or something like - [DNSChecker.org](https://dnschecker.org/all-dns-records-of-domain.php?query=_acme-challenge.ngbuilds.io&rtype=TXT) to verify that the DNS updates have been successfully deployed and propagated. - -- [Optional] Remove the DNS TXT records added in the previous step. - (They are no longer needed, unless you want to create more certificates now.) - - -## Deploy new certificate -- Copy files to the host machine's `/etc/ssl/localcerts` directory, replacing `YYYY-MM` in the file - names with the current year and month (for example, `2022-08`): - ```sh - # Copy certificate. - sudo cp /etc/letsencrypt/live/ngbuilds.io/fullchain.pem /etc/ssl/localcerts/ngbuilds.io.crt.YYYY-MM - sudo cp /etc/letsencrypt/live/ngbuilds.io/fullchain.pem /etc/ssl/localcerts/ngbuilds.io.crt - - # Copy private key. - sudo cp /etc/letsencrypt/live/ngbuilds.io/privkey.pem /etc/ssl/localcerts/ngbuilds.io.key.YYYY-MM - sudo cp /etc/letsencrypt/live/ngbuilds.io/privkey.pem /etc/ssl/localcerts/ngbuilds.io.key - - # Update permissions. - sudo find /etc/ssl/localcerts -type f -exec chmod 400 {} \; - ``` - - **Note:** - The `.YYYY-MM`-suffixed copies are not needed/used. They are just kept for reference and backup - purposes. - -- Reload `nginx`: - ```sh - # Connect to the Docker container and reload reload `nginx`. - sudo docker exec -it aio service nginx reload - ``` - -- [Optional] Verify that the certificate has been successfully deployed by visiting - https://ngbuilds.io/ in a browser and inspecting the TLS certificate details. See, for example, - [how to view certificate details in Chrome](https://www.howtogeek.com/292076/how-do-you-view-ssl-certificate-details-in-google-chrome/). diff --git a/aio/aio-builds-setup/docs/overview--general.md b/aio/aio-builds-setup/docs/overview--general.md deleted file mode 100644 index bd03bdd1049..00000000000 --- a/aio/aio-builds-setup/docs/overview--general.md +++ /dev/null @@ -1,108 +0,0 @@ -# Overview - General - - -## Objective -Whenever a PR job is run on the CI infrastructure (e.g. CircleCI), we want to build `angular.io` -and host the build artifacts on a publicly accessible server so that collaborators, such as the -developers, designers, or authors, can preview the changes without having to check out and build -the app locally. - - -## Source code -In order to make it easier to administer the server and version-control the setup, we are using -[docker](https://www.docker.com) to run a container on a VM. The Dockerfile and all other files -necessary for creating the docker container are stored (and versioned) along with the angular.io -project's source code (currently part of the angular/angular repo) in the `aio-builds-setup/` -directory. - - -## Setup -The VM is hosted on [Google Compute Engine](https://cloud.google.com/compute/). The host OS is -debian:jessie. For more info how to set up the host VM take a look at the "Setting up the VM" -section in [TOC](_TOC.md). - - -## Security model -Since we are managing a public server, it is important to take appropriate measures in order to -prevent abuse. For more details on the challenges and the chosen approach take a look at the -[security model](overview--security-model.md). - - -## The 10000 feet view -This section gives a brief summary of the several operations performed on CI and by the docker -container: - - -### On CI (CircleCI) -- The CI script builds the angular.io project. -- 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. - -More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md). - - -### Hosting build artifacts -- 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 this URL to receive the artifact - failing if the size - 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 - 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 - builds for the same PR had been previously deployed as non-public and the current build has been - automatically verified, all previous builds are made public as well. - If the PR transitions from "non-public" to "public", the preview-server posts a comment on the - 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 deploys the artifacts to a subdirectory named after the PR number and the - first few characters of the SHA: `//` - (Non-publicly accessible PRs will be stored in a different location, but again derived from the PR - number and SHA.) -- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on - GitHub mentioning the SHA and the link where the preview can be found. - -More info on the possible HTTP status codes and their meaning can be found -[here](overview--http-status-codes.md). - - -### Updating PR visibility -- nginx receives a notification that a PR has been updated and passes it through to the - preview-server. This could, for example, be sent by a GitHub webhook every time a PR's labels - change. - E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":,"action":"labeled"}`) -- The request contains the PR number (as `number`) and optionally the action that triggered the - request (as `action`) in the payload. -- The preview-server verifies the payload and determines whether the `action` (if specified) could - have led to PR visibility changes. Only requests that omit the `action` field altogether or - specify an action that can affect visibility are further processed. - (Currently, the only actions that are considered capable of affecting visibility are `labeled` and - `unlabeled`.) -- The preview-server re-checks and if necessary updates the PR's visibility. - -More info on the possible HTTP status codes and their meaning can be found -[here](overview--http-status-codes.md). - - -### Serving build artifacts -- nginx receives a request for a hosted preview resource on a subdomain corresponding to the PR and SHA. - E.g.: `pr-.ngbuilds.io/path/to/resource` -- nginx maps the subdomain to the correct subdirectory and serves the resource. - E.g.: `///path/to/resource` - -More info on the possible HTTP status codes and their meaning can be found -[here](overview--http-status-codes.md). - - -### Removing obsolete artifacts -In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a -clean-up task once a day. The task retrieves all open PRs from GitHub and removes all directories -that do not correspond to an open PR. - - -### Health-check -The docker service runs a periodic health-check that verifies the running conditions of the -container. This includes verifying the status of specific system services, the responsiveness of -nginx and the preview-server and internet connectivity. diff --git a/aio/aio-builds-setup/docs/overview--http-status-codes.md b/aio/aio-builds-setup/docs/overview--http-status-codes.md deleted file mode 100644 index f04c2e53d8b..00000000000 --- a/aio/aio-builds-setup/docs/overview--http-status-codes.md +++ /dev/null @@ -1,99 +0,0 @@ -# Overview - HTTP Status Codes - - -This is a list of all the possible HTTP status codes returned by the nginx and preview servers, -along with a brief explanation of what they mean: - - -## `http://*.ngbuilds.io/*` - -- **307 (Temporary Redirect)**: - All non-HTTPS requests. 308 (Permanent Redirect) would be more appropriate, but is not supported - by all agents (e.g. cURL). - - -## `https://pr-.ngbuilds.io/*` - -- **200 (OK)**: - File was found or URL was rewritten to `/index.html` (i.e. all paths that have no `.` in final - segment). - -- **403 (Forbidden)**: - Trying to access a subdirectory. - -- **404 (Not Found)**: - File not found. - - -## `https://ngbuilds.io/can-have-public-preview/` - -- **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` - -- **201 (Created)**: - Build deployed successfully and is publicly available. - -- **202 (Accepted)**: - Build not automatically verifiable. Stored for later deployment (after re-verification). - -- **204 (No Content)**: - Build was not successful, so no further action is being taken. - -- **400 (Bad Request)**: - Invalid payload. - -- **403 (Forbidden)**: - Unable to talk to 3rd-party APIs. - -- **405 (Method Not Allowed)**: - Request method other than POST. - -- **409 (Conflict)**: - Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or - change PR visibility when the destination directory does already exist). - - -## `https://ngbuilds.io/health-check` - -- **200 (OK)**: - The server is healthy (i.e. up and running and processing requests). - - -## `https://ngbuilds.io/pr-updated` - -- **200 (OK)**: - Request processed successfully. Processing may or may not have resulted in further actions. - -- **400 (Bad Request)**: - No payload or no `number` field in payload. - -- **405 (Method Not Allowed)**: - Request method other than POST. - -- **409 (Conflict)**: - Request to overwrite existing (public or non-public) directory (i.e. directories for both - visibilities exist). - (Normally, this should not happen.) - - -## `https://*.ngbuilds.io/*` - -- **404 (Not Found)**: - Request not matched by the above rules. - -- **500 (Internal Server Error)**: - Error while processing a request matched by the above rules. diff --git a/aio/aio-builds-setup/docs/overview--scripts-and-commands.md b/aio/aio-builds-setup/docs/overview--scripts-and-commands.md deleted file mode 100644 index 0bdf4c50205..00000000000 --- a/aio/aio-builds-setup/docs/overview--scripts-and-commands.md +++ /dev/null @@ -1,66 +0,0 @@ -# Overview - Scripts and Commands - - -This is an overview of the available scripts and commands. - - -## Scripts -The scripts are located inside `/scripts/`. The following scripts are -available: - -- `create-image.sh`: - Can be used for creating a preconfigured docker image. - See [here](vm-setup--create-docker-image.md) for more info. - -- `test.sh`: - Can be used for running the tests for `/dockerbuild/scripts-js/`. This is - useful for CI integration. See [here](misc--integrate-with-ci.md) for more info. - -- `update-preview-server.sh`: - Can be used for updating the docker container (and image) based on the latest changes checked out - from a git repository. See [here](vm-setup--update-docker-container.md) for more info. - - -## Production Commands -The following commands are available globally from inside the docker container. They are either used -by the container to perform its various operations or can be used ad-hoc, mainly for testing -purposes. Each command is backed by a corresponding script inside -`/dockerbuild/scripts-sh/`. - -- `aio-clean-up`: - Cleans up the builds directory by removing the artifacts that do not correspond to an open PR. - _It is run as a daily cronjob._ - -- `aio-health-check`: - Runs a basic health-check, verifying that the necessary services are running, the servers are - responding and there is a working internet connection. - _It is used periodically by docker for determining the container's health status._ - -- `aio-init`: - Initializes the container (mainly by starting the necessary services). - _It is run (by default) when starting the container._ - -- `aio-preview-server-prod`: - Spins up a Node.js preview-server instance. - _It is used in `aio-init` (see above) during initialization._ - - -## Developer Commands - -- `aio-preview-server-test`: - Spins up a Node.js preview-server instance for tests. - _It is used in `aio-verify-setup` (see below) for running tests._ - -- `aio-verify-setup`: - Runs a suite of e2e-like tests, mainly verifying the correct (inter)operation of nginx and the - Node.js preview-server. - -- `aio-verify-setup-and-log`: - Runs the `aio-verify-setup` command but also then dumps the logs from the preview server, which - gives additional useful debugging information. See the [debugging docs](misc--debug-docker-container.md) - for more info. - -- `aio-dev-mode`: - Links external source files (from the Docker host) to internal source files (in the Docker - container). This makes it easier to use an IDE to edit files in the host that are then - tested in the container. See the [debugging docs](misc--debug-docker-container.md) for more info. \ No newline at end of file diff --git a/aio/aio-builds-setup/docs/overview--security-model.md b/aio/aio-builds-setup/docs/overview--security-model.md deleted file mode 100644 index 81cd7a9a7da..00000000000 --- a/aio/aio-builds-setup/docs/overview--security-model.md +++ /dev/null @@ -1,149 +0,0 @@ -# Overview - Security model - - -Whenever a PR job is run on CircleCI, we want to build `angular.io` and host the build artifacts on -a publicly accessible server so that collaborators, such as the developers, designers, or authors, -can preview the changes without having to check out and build the app locally. - -This document discusses the security considerations associated with moving build artifacts as -part of the CI process and serving them publicly. - - -## Security objectives - -- **Prevent hosting arbitrary content on our servers.** - Since there is no restriction on who can submit a PR, we cannot allow arbitrary, untrusted PRs' - build artifacts to be hosted. - -- **Prevent overwriting other people's hosted build artifacts.** - There needs to be a mechanism in place to ensure that the hosted content does indeed correspond - to the PR indicated by its URL. - -- **Prevent arbitrary access on the server.** - Since the PR author has full access over the build artifacts that would be hosted, we must - ensure that the build artifacts will not have arbitrary access to the server or expose sensitive - info. - - -## Issues / Caveats - -- Because the PR author can change the scripts run on CI, any security mechanisms must be immune to - such changes. - -- For security reasons, encrypted CircleCI variables are not available to PRs, so we can't rely on - them to implement security. - - -## Implemented approach - - -### In a nutshell -The implemented approach can be broken up to the following sub-tasks: - -1. Receive notification from CircleCI of a completed build. -2. Verify that the build is valid and can have a preview. -3. Download the build artifact. -4. Fetch the PR's metadata, including author and labels. -5. Check whether the PR can be automatically verified as "trusted" (based on its author or labels). -6. If necessary, update the corresponding PR's verification status. -7. Deploy the artifacts to the corresponding PR's directory. -8. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established - during deployment will remain valid until the artifacts are removed). -9. Prevent hosted preview files from accessing anything outside their directory. - - -### Implementation details -This section describes how each of the aforementioned sub-tasks is accomplished: - -1. **Receive notification from CircleCI of a completed build** - - 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. - -2. **Verify that the build is valid and can have a preview.** - - 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 - the given build number. - - We perform a number of preliminary checks: - - Was the webhook triggered by the designated CircleCI job (currently `aio_preview`)? - - Was the build successful? - - Are the associated GitHub organization 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. - -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 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. - -4. **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 - - including the author's username and the labels - using the - [GitHub API](https://developer.github.com/v3/). - To avoid rate-limit restrictions, we use a Personal Access Token (issued by - [@mary-poppins](https://github.com/mary-poppins)). - -5. **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 - and publicly accessible on the preview server. There are two ways to check that: - 1. We can verify that the PR has a pre-determined label, which marks it as "safe for preview". - Such a label can only have been added by a maintainer (with the necessary rights) and - designates that they have manually verified the PR contents. - 2. We can verify (again using the GitHub API) the author's membership in one of the - trusted GitHub teams. For this operation, we need a Personal Access Token with the - `read:org` scope issued by a user that can "see" the specified GitHub organization. - Here too, we use the token by @mary-poppins. - -6. **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. - whether it is publicly accessible or not), based on the new verification status. For example, if - a PR was initially considered "not trusted" but the check triggered by a new build determined - otherwise, the PR (and all the previously downloaded previews) are made public. It works the same - way if a PR has gone from "trusted" to "not trusted". - -7. **Deploy the artifacts to the corresponding PR's directory.** - - With the preceding steps, we have verified that the build artifacts are valid. Additionally, we - have determined whether the PR can be trusted to have its previews publicly accessible or whether - further verification is necessary. - - The artifacts will be stored to the PR's directory, but will not be publicly accessible unless - the PR has been verified. Essentially, as long as sub-tasks 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 - 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._ - -9. **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 - outside of the directory where the preview files are stored. - - -## Assumptions / Things to keep in mind - -- Other than the initial webhook trigger, which provides a build number, all requests for data come - from the preview-server making requests to well-defined API endpoints (e.g. CircleCI and Github). - 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). - -- Each trusted PR author has full control over the content that is hosted as a preview for their - PRs. 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 - content that is hosted for the specific PR preview (e.g. by pushing more commits to it). The user - adding the label is responsible for ensuring that this control is not abused and that the PR is - either closed (one way of another) or the access is revoked. diff --git a/aio/aio-builds-setup/docs/vm-setup--attach-persistent-disk.md b/aio/aio-builds-setup/docs/vm-setup--attach-persistent-disk.md deleted file mode 100644 index 256239d2f7b..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--attach-persistent-disk.md +++ /dev/null @@ -1,20 +0,0 @@ -# VM setup - Attach persistent disk - - -## Create `aio-builds` persistent disk (if not already exists) -- Follow instructions [here](https://cloud.google.com/compute/docs/disks/add-persistent-disk#create_disk). -- `sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/disk/by-id/google-aio-builds` - - -## Mount disk -- `sudo mkdir -p /mnt/disks/aio-builds` -- `sudo mount -o discard,defaults /dev/disk/by-id/google-aio-builds /mnt/disks/aio-builds` -- `sudo chmod a+w /mnt/disks/aio-builds` - - -## Mount disk on boot -- Run: - ```sh - echo UUID=`sudo blkid -s UUID -o value /dev/disk/by-id/google-aio-builds` \ - /mnt/disks/aio-builds ext4 defaults,discard,nofail 0 2 | sudo tee -a /etc/fstab - ``` diff --git a/aio/aio-builds-setup/docs/vm-setup--create-docker-image.md b/aio/aio-builds-setup/docs/vm-setup--create-docker-image.md deleted file mode 100644 index 7b778596725..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--create-docker-image.md +++ /dev/null @@ -1,43 +0,0 @@ -# VM setup - Create docker image - - -## Install git, Node.js and yarn -- `sudo apt-get update` -- `sudo apt-get install -y git` -- Install the latest stable version of [Node.js](https://nodejs.org/en/download). -- Install the latest stable version of [yarn](https://classic.yarnpkg.com/en/docs/install). - - -## Checkout repository -- `git clone ` - - -## Build docker image -- `/scripts/create-image.sh [[:] [--build-arg = ...]]` -- You can overwrite the default environment variables inside the image, by passing new values using - `--build-arg`. - -**Note 1:** The script has to execute docker commands with `sudo`. - -**Note 2:** -The script has to execute `yarn` commands, so make sure `yarn` is on the `PATH` when invoking the -script. - - -## Example -The following commands would create a docker image from GitHub repo `foo/bar` to be deployed on the -`foobar-builds.io` domain and accepting PR deployments from authors that are members of the -`bar-core` and `bar-docs-authors` teams of organization `foo`: - -- `git clone https://github.com/foo/bar.git foobar` -- Run: - ```sh - ./foobar/aio-builds-setup/scripts/create-image.sh foobar-builds \ - --build-arg AIO_REPO_SLUG=foo/bar \ - --build-arg AIO_DOMAIN_NAME=foobar-builds.io \ - --build-arg AIO_GITHUB_ORGANIZATION=foo \ - --build-arg AIO_GITHUB_TEAM_SLUGS=bar-core,bar-docs-authors - ``` - -A full list of the available environment variables can be found -[here](image-config--environment-variables.md). diff --git a/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md b/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md deleted file mode 100644 index 6e1b204f8c6..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md +++ /dev/null @@ -1,80 +0,0 @@ -# VM setup - Create host directories and files - - -## Create directory with secrets -For security reasons, sensitive info (such as tokens and passwords) are not hardcoded into the -docker image, nor passed as environment variables at runtime. They are passed to the docker -container from the host VM as files inside a directory. Each file's name is the name of the variable -and the file content is the value. These are read from inside the running container when necessary. - -More info on how to create `secrets` directory and files can be found -[here](vm-setup--set-up-secrets.md). - - -## Create directory for build artifacts -The build artifacts should be kept on a directory outside the docker container, so it is -easier to replace the container without losing the builds. For portability across VMs a -persistent disk can be used (as described [here](vm-setup--attach-persistent-disk.md)). - -**Note:** The directories created inside that directory will be owned by user `www-data`. - - -## Create TLS certificates (Optional for dev) -The host VM can attach a directory containing the TLS certificate and key to be used by the nginx -server for serving the hosted previews. More info on how to attach the directory when -starting the container can be found [here](vm-setup--start-docker-container.md). - -In order for the container to be able to find the certificate and key, they should be named -`.crt` and `.key` respectively. For example, for a domain name -`ngbuild.io`, nginx will look for files `ngbuilds.io.crt` and `ngbuilds.io.key`. More info on how to -specify the domain name see [here](vm-setup--create-docker-image.md). - -If no directory is attached, nginx will use an internal self-signed certificate. This is convenient -during development, but is not suitable for production. - -Certificates from any certificate authority can be used. As an example, see -[here](misc--use-lets-encrypt-certs) for instructions on using certificates from -[Let's Encrypt](https://letsencrypt.org/). - -**Note:** -Since nginx needs to be able to serve requests for both the main domain as well as any subdomain -(e.g. `ngbuilds.io/` and `foo-bar.ngbuilds.io/`), the provided certificate needs to be a -[wildcard certificate](https://en.wikipedia.org/wiki/Wildcard_certificate) covering both the domain -and all subdomains. - - -## Create directory for logs (Optional) -Optionally, a logs directory can pe passed to the docker container for storing non-system-related -logs. If not provided, the logs are kept locally on the container and will be lost whenever the -container is replaced (e.g. when updating to use a newer version of the docker image). Log files are -rotated and retained for 6 months. - -The following log files are kept in this directory: - -- `clean-up.log`: - Output of the `aio-clean-up` command, run as a cronjob for cleaning up the build artifacts of - closed PRs. - -- `init.log`: - Output of the `aio-init` command, run (by default) when starting the container. - -- `nginx/{access,error}.log`: - The access and error logs produced by the nginx server while serving "production" files. - -- `nginx-test/{access,error}.log`: - The access and error logs produced by the nginx server while serving "test" files. This is only - used when running tests locally from inside the container, e.g. with the `aio-verify-setup` - command. (See [here](overview--scripts-and-commands.md) for more info.) - -- `preview-server-{prod,test,verify-setup}-*.log`: - The logs produced by the Node.js preview-server while serving either: - - `-prod`: "Production" files (g.g during normal operation). - - `-test`: "Test" files (e.g. when a test instance is started with the `aio-preview-server-test` - command). - - `-verify-setup`: "Test" files, but while running `aio-verify-setup`. - - (See [here](overview--scripts-and-commands.md) for more info the commands mentioned above.) - -- `verify-setup.log`: - The output of the `aio-verify-setup` command (e.g. Jasmine output), except for preview-server - output which is logged to `preview-server-verify-setup-*.log` (see above). diff --git a/aio/aio-builds-setup/docs/vm-setup--set-up-docker.md b/aio/aio-builds-setup/docs/vm-setup--set-up-docker.md deleted file mode 100644 index 3156f6290a6..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--set-up-docker.md +++ /dev/null @@ -1,28 +0,0 @@ -# VM Setup - Set up docker - - -## Install docker - -Official installation instructions: https://docs.docker.com/engine/install -Example: - -_Debian (buster):_ -- `sudo apt-get update` -- `sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common` -- `curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -` -- `sudo apt-key fingerprint 0EBFCD88` -- `sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"` -- `sudo apt-get update` -- `sudo apt-get -y install docker-ce docker-ce-cli containerd.io` - - -## Start the docker -- `sudo service docker start` - - -## Test docker -- `sudo docker run hello-world` - - -## Start docker on boot -- `sudo systemctl enable docker` diff --git a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md b/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md deleted file mode 100644 index 346966c0360..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md +++ /dev/null @@ -1,39 +0,0 @@ -# VM Setup - Set up secrets - - -## Overview - -Necessary secrets: - -1. `GITHUB_TOKEN` - - Used for: - - Retrieving open PRs without rate-limiting. - - Retrieving PR info, such as author, labels, changed files. - - Retrieving members of the trusted GitHub teams. - - Posting comments with preview links on PRs. - -2. `CIRCLE_CI_TOKEN` - - Used for: - - Retrieving build information. - - Downloading build artifacts. - - -## Create secrets - -1. `GITHUB_TOKEN` - - Visit https://github.com/settings/tokens. - - Generate new token with the `public_repo` scope. - -2. `CIRCLE_CI_TOKEN` - - Visit https://app.circleci.com/settings/user/tokens. - - Create a user API token. - - -## Save secrets on the VM - -- `sudo mkdir /aio-secrets` -- `sudo touch /aio-secrets/GITHUB_TOKEN` -- Insert `` into `/aio-secrets/GITHUB_TOKEN`. -- `sudo touch /aio-secrets/CIRCLE_CI_TOKEN` -- Insert `` into `/aio-secrets/CIRCLE_CI_TOKEN`. -- `sudo chmod 400 /aio-secrets/*` diff --git a/aio/aio-builds-setup/docs/vm-setup--start-docker-container.md b/aio/aio-builds-setup/docs/vm-setup--start-docker-container.md deleted file mode 100644 index 91e3aa5f307..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--start-docker-container.md +++ /dev/null @@ -1,110 +0,0 @@ -# VM setup - Start docker container - - -## The `docker run` command -Once everything has been set up and configured, a docker container can be started with the following -command: - -``` -sudo docker run \ - --detach \ - --dns 127.0.0.1 \ - --name \ - --publish 80:80 \ - --publish 443:443 \ - --restart unless-stopped \ - --volume :/aio-secrets:ro \ - --volume :/var/www/aio-builds \ - [--volume :/etc/ssl/localcerts:ro] \ - [--volume :/var/log/aio] \ - [--volume :/dockerbuild] \ - [:] -``` - -Below is the same command with inline comments explaining each option. The API docs for `docker run` -can be found [here](https://docs.docker.com/engine/reference/run/). - -``` -sudo docker run \ - - # Start as a daemon. - --detach \ - - # Use the local DNS server. - # (This is necessary for mapping internal URLs, e.g. for the Node.js preview-server.) - --dns 127.0.0.1 \ - - # USe `` as an alias for the container. - # Useful for running `docker` commands, e.g.: `docker stop ` - --name \ - - # Map ports of the host VM (left) to ports of the docker container (right) - --publish 80:80 \ - --publish 443:443 \ - - # Automatically restart the container (unless it was explicitly stopped by the user). - # (This ensures that the container will be automatically started on boot.) - --restart unless-stopped \ - - # The directory the contains the secrets (e.g. GitHub token, JWT secret, etc). - # (See [here](vm-setup--set-up-secrets.md) for more info.) - # - # NOTE: - # Omitting this is OK during development/debugging, when you don't care to interact with real - # external services. - --volume :/aio-secrets:ro \ - - # The build artifacts and hosted previews will stored to and served from this directory. - # (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) - - # this will be a directory inside the disk.) - # - # NOTE: - # Omitting this is OK during development/debugging, when you don't care to interact with real - # external services. - --volume :/var/www/aio-builds \ - - # The directory the contains the TLS certificates. - # (See [here](vm-setup--create-host-dirs-and-files.md) for more info.) - # If not provided, the container will use self-signed certificates. - [--volume :/etc/ssl/localcerts:ro] \ - - # The directory where the logs are being kept. - # (See [here](vm-setup--create-host-dirs-and-files.md) for more info.) - # If not provided, the logs will be kept inside the container, which means they will be lost - # whenever a new container is created. - [--volume :/var/log/aio] \ - - # This directory allows you to share the source scripts between the host and the container when - # debugging. (See [here](misc--debug-docker-container.md) for how to set this up.) - [--volume :/dockerbuild] \ - - # The name of the docker image to use (and an optional tag; defaults to `latest`). - # (See [here](vm-setup--create-docker-image.md) for instructions on how to create the image.) - [:] -``` - - -## Example -The following command would do the following: -1. Start a docker container based on the previously created `foobar-builds` docker image. -2. Alias the docker container as `foobar-builds-1` (so it can be references in subsequent docker - commands). -3. Map predefined directories on the host VM to be used by the container for accessing secrets and - TLS certificates and keeping the build artifacts and logs. -4. Map the source scripts from the host to the container (for debugging purposes). - -``` -sudo docker run \ - --detach \ - --dns 127.0.0.1 \ - --name foobar-builds-1 \ - --publish 80:80 \ - --publish 443:443 \ - --restart unless-stopped \ - --volume /foobar-secrets:/aio-secrets:ro \ - --volume /mnt/disks/foobar-builds:/var/www/aio-builds \ - --volume /etc/ssl/localcerts:/etc/ssl/localcerts:ro \ - --volume /foobar-logs:/var/log/aio \ - --volume ~/angular/aio/aio-builds-setup/dockerbuild:/dockerbuild \ - foobar-builds -``` diff --git a/aio/aio-builds-setup/docs/vm-setup--update-docker-container.md b/aio/aio-builds-setup/docs/vm-setup--update-docker-container.md deleted file mode 100644 index 4b3c35b22d4..00000000000 --- a/aio/aio-builds-setup/docs/vm-setup--update-docker-container.md +++ /dev/null @@ -1,62 +0,0 @@ -# VM setup - Update docker container - - -## Overview -Assuming you have cloned the repository containing the preview server code (as described -[here](vm-setup--create-docker-image.md)), you can use the `update-preview-server.sh` script on the -VM host to update the preview server based on changes in the source code. - -The script will pull the latest changes from the origin's main branch and examine if there have -been any changes in files inside the preview server source code directory (see below). If there are, -it will create a new image and verify that it works as expected. Finally, it will stop and remove -the old docker container and image, create a new container based on the new image and start it. - -The script assumes that the preview server source code is in the repository's -`aio/aio-builds-setup/` directory and expects the following inputs: - -- **$1**: `HOST_REPO_DIR` -- **$2**: `HOST_SECRETS_DIR` -- **$3**: `HOST_BUILDS_DIR` -- **$4**: `HOST_LOCALCERTS_DIR` -- **$5**: `HOST_LOGS_DIR` - -See [here](vm-setup--create-host-dirs-and-files.md) for more info on what each input directory is -used for. - -**Note 1:** The script has to execute docker commands with `sudo`. - -**Note 2:** -The script has to execute `yarn` commands, so make sure `yarn` is on the `PATH` when invoking the -script. - -**Note 3:** Make sure the user that executes the script has access to update the repository. - - -## Run the script manually -You may choose to manually run the script, when necessary. Example: - -```sh -update-preview-server.sh \ - /path/to/repo \ - /path/to/secrets \ - /path/to/builds \ - /path/to/localcerts \ - /path/to/logs -``` - - -## Run the script automatically -You may choose to automatically trigger the script, e.g. using a cronjob. For example, the following -cronjob entry would run the script every 30 minutes, update the preview server (if necessary) and -log its output to `update-preview-server.log` (assuming the user has the necessary permissions): - -``` -# Periodically check for changes and update the preview server (if necessary) -*/30 * * * * /path/to/update-preview-server.sh /path/to/repo /path/to/secrets /path/to/builds /path/to/localcerts /path/to/logs >> /path/to/update-preview-server.log 2>&1 -``` - -**Note:** -Keep in mind that cron jobs run in non-interactive, non-login shells. This means that the execution -context might be different compared to when running the same commands from an interactive, login -shell. For example, `.bashrc` files are normally _not_ sourced automatically in cron jobs. See -[here](https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) for more info. diff --git a/aio/aio-builds-setup/scripts/_env.sh b/aio/aio-builds-setup/scripts/_env.sh deleted file mode 100755 index e0d5ef4c37d..00000000000 --- a/aio/aio-builds-setup/scripts/_env.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -readonly THIS_DIR=$(cd $(dirname $0); pwd) -readonly DOCKERBUILD_DIR="$THIS_DIR/../dockerbuild" -readonly SCRIPTS_JS_DIR="$DOCKERBUILD_DIR/scripts-js" diff --git a/aio/aio-builds-setup/scripts/create-image.sh b/aio/aio-builds-setup/scripts/create-image.sh deleted file mode 100755 index 553b426210f..00000000000 --- a/aio/aio-builds-setup/scripts/create-image.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -eux -o pipefail - -# Set up env -source "`dirname $0`/_env.sh" -readonly defaultImageNameAndTag="aio-builds:latest" - -# Build `scripts-js/` -# (Necessary, because only `scripts-js/dist/` is copied to the docker image.) -( - cd "$SCRIPTS_JS_DIR" - yarn install --frozen-lockfile --non-interactive - yarn build -) - -# Create docker image -readonly nameAndOptionalTag=${1:-$defaultImageNameAndTag} -sudo docker build --tag $nameAndOptionalTag ${@:2} $DOCKERBUILD_DIR diff --git a/aio/aio-builds-setup/scripts/test.sh b/aio/aio-builds-setup/scripts/test.sh deleted file mode 100755 index 7bc25f94790..00000000000 --- a/aio/aio-builds-setup/scripts/test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -eux -o pipefail - -# Set up env -source "`dirname $0`/_env.sh" - -# Test `scripts-js/` -( - cd "$SCRIPTS_JS_DIR" - yarn install --frozen-lockfile --non-interactive - yarn test -) diff --git a/aio/aio-builds-setup/scripts/update-preview-server.sh b/aio/aio-builds-setup/scripts/update-preview-server.sh deleted file mode 100755 index 711ae62a14f..00000000000 --- a/aio/aio-builds-setup/scripts/update-preview-server.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash - -set -eux -o pipefail -exec 3>&1 - -echo -e "\n\n[`date`] - Updating the preview server..." - -# Input -readonly HOST_REPO_DIR=$1 -readonly HOST_SECRETS_DIR=$2 -readonly HOST_BUILDS_DIR=$3 -readonly HOST_LOCALCERTS_DIR=$4 -readonly HOST_LOGS_DIR=$5 - -# Constants -readonly PROVISIONAL_TAG=provisional -readonly PROVISIONAL_IMAGE_NAME=aio-builds:$PROVISIONAL_TAG -readonly LATEST_IMAGE_NAME=aio-builds:latest -readonly CONTAINER_NAME=aio - -# Run -( - cd "$HOST_REPO_DIR" - - readonly lastDeployedCommit=$(git rev-parse HEAD) - echo "Currently at commit $lastDeployedCommit." - - # Pull latest main from origin. - git pull origin main - - # Do not update the server unless files inside `aio-builds-setup/` have changed - # or the last attempt failed (identified by the provisional image still being around). - readonly relevantChangedFilesCount=$(git diff --name-only $lastDeployedCommit...HEAD | grep -P "^aio/aio-builds-setup/" | wc -l) - readonly lastAttemptFailed=$(sudo docker image ls | grep "$PROVISIONAL_TAG" >> /dev/fd/3 && echo "true" || echo "false") - if [[ $relevantChangedFilesCount -eq 0 ]] && [[ "$lastAttemptFailed" != "true" ]]; then - echo "Skipping update because no relevant files have been touched." - exit 0 - fi - - # Create and verify a new docker image. - aio/aio-builds-setup/scripts/create-image.sh "$PROVISIONAL_IMAGE_NAME" --no-cache - readonly imageVerified=$(sudo docker run --dns 127.0.0.1 --rm --volume $HOST_SECRETS_DIR:/aio-secrets:ro "$PROVISIONAL_IMAGE_NAME" /bin/bash -c "aio-init && aio-health-check && aio-verify-setup" >> /dev/fd/3 && echo "true" || echo "false") - - if [[ "$imageVerified" != "true" ]]; then - echo "Failed to verify new docker image. Aborting update!" - exit 1 - fi - - # Remove the old container and replace the docker image. - sudo docker stop "$CONTAINER_NAME" || true - sudo docker rm "$CONTAINER_NAME" || true - sudo docker rmi "$LATEST_IMAGE_NAME" || true - sudo docker tag "$PROVISIONAL_IMAGE_NAME" "$LATEST_IMAGE_NAME" - sudo docker rmi "$PROVISIONAL_IMAGE_NAME" - - # Create and start a docker container based on the new image. - sudo docker run \ - --detach \ - --dns 127.0.0.1 \ - --name "$CONTAINER_NAME" \ - --publish 80:80 \ - --publish 443:443 \ - --restart unless-stopped \ - --volume $HOST_SECRETS_DIR:/aio-secrets:ro \ - --volume $HOST_BUILDS_DIR:/var/www/aio-builds \ - --volume $HOST_LOCALCERTS_DIR:/etc/ssl/localcerts:ro \ - --volume $HOST_LOGS_DIR:/var/log/aio \ - "$LATEST_IMAGE_NAME" - - # Clean up unused docker containers and images (to reclaim space). - sudo docker container prune --force - sudo docker image prune --all --force - - echo "The new docker image has been successfully deployed." -) diff --git a/aio/scripts/create-preview.js b/aio/scripts/create-preview.js deleted file mode 100644 index a9e26ce4eb8..00000000000 --- a/aio/scripts/create-preview.js +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env node - -/** - * Usage: - * node create-preview - * - * Triggers the preview server to initiate the preview creation process for the specified CircleCI - * build number. It must be called _after_ the build artifacts have been created and stored on - * CircleCI. - */ - -// Imports -const {triggerWebhook} = require('../../.circleci/trigger-webhook'); - -// Constants -const JOB_NAME = 'aio_preview'; -const WEBHOOK_URL = 'https://ngbuilds.io/circle-build'; - -// Input -const buildNumber = process.argv[2]; - -// Run -triggerWebhook(buildNumber, JOB_NAME, WEBHOOK_URL). - then(({statusCode, responseText}) => isSuccess(statusCode) ? - console.log(`Status: ${statusCode}\n${responseText}`) : - Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))). - catch(err => { - console.error(err); - process.exit(1); - }); - -// Helpers -function isSuccess(statusCode) { - // Getting a 409 response from the preview server means that the preview has already been created - // for the corresponding PR/SHA, so our objective has been accomplished. - return (200 <= statusCode && statusCode < 400) || (statusCode === 409); -} diff --git a/aio/scripts/test-preview.js b/aio/scripts/test-preview.js deleted file mode 100644 index d68e42d75d3..00000000000 --- a/aio/scripts/test-preview.js +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env node - -/** - * Usage: - * node scripts/test-preview - * - * Checks whether a PR will (eventually) have a (public) preview, waits for the preview to be - * available, and runs PWA tests against the preview. - * - * For PRs that are expected to have a preview, this script will fail if the preview is still not - * available after a pre-defined waiting period or if the PWA tests fail. - */ - -// Imports -const {spawn} = require('child_process'); -const {get: httpsGet} = require('https'); -const {relative} = require('path'); - -// Input -const [prNumber, prLastSha, minPwaScore] = validateArgs(process.argv.slice(2)); - -// Variables -const aioBuildsDomain = 'ngbuilds.io'; -const previewCheckInterval = 30000; -const previewCheckAttempts = 10; - -const shortSha = prLastSha && prLastSha.slice(0, 7); -const previewabilityCheckUrl = `https://${aioBuildsDomain}/can-have-public-preview/${prNumber}`; -const previewUrl = `https://pr${prNumber}-${shortSha}.${aioBuildsDomain}/`; - -// Check whether the PR can have a (public) preview. -get(previewabilityCheckUrl). - then(response => JSON.parse(response)). - then(({canHavePublicPreview, reason}) => { - // Nothing to do, if this PR can have no (public) preview. - if (canHavePublicPreview === false) { - reportNoPreview(reason); - return; - } - - // There should be a preview. Wait for it to be available. - return poll(previewCheckInterval, previewCheckAttempts, () => get(previewUrl)). - // The preview is still not available after the specified waiting period. - catch(() => { - const totalSecs = Math.round((previewCheckInterval * previewCheckAttempts) / 1000); - throw new Error(`Preview still not available after ${totalSecs}s.`); - }). - // The preview is now available. Run the tests. - then(() => yarnRun('smoke-tests', previewUrl)). - then(() => yarnRun('test-pwa-score', previewUrl, minPwaScore)); - }). - catch(onError); - -// Helpers -function get(url) { - console.log(`GET ${url}`); - return new Promise((resolve, reject) => { - const onResponse = res => { - const statusCode = res.statusCode || -1; - const isSuccess = (200 <= statusCode) && (statusCode < 400); - let responseText = ''; - - res. - on('error', reject). - on('data', d => responseText += d). - on('end', () => isSuccess ? - resolve(responseText) : - reject(`Request to '${url}' failed (status: ${statusCode}): ${responseText}`)); - }; - - httpsGet(url, onResponse). - on('error', reject); - }); -} - -function onError(err) { - console.error(err); - process.exit(1); -} - -function poll(interval, attempts, checkCondition) { - return new Promise((resolve, reject) => { - if (!attempts) return reject(); - - checkCondition(). - then(() => resolve()). - catch(() => wait(interval). - then(() => poll(interval, attempts - 1, checkCondition)). - then(resolve, reject)); - }); -} - -function reportNoPreview(reason) { - console.log(`No (public) preview available. (Reason: ${reason})`); -} - -function validateArgs(args) { - if (args.length !== 3) { - const relativeScriptPath = relative('.', __filename.replace(/\.js$/, '')); - const usageCmd = `node ${relativeScriptPath} `; - - return onError( - `Invalid number of arguments (expected 3, found ${args.length}).\n` + - `Usage: ${usageCmd}`); - } - - return args; -} - -function wait(delay) { - console.log(`Waiting ${delay}ms...`); - return new Promise(resolve => setTimeout(resolve, delay)); -} - -function yarnRun(script, ...args) { - return new Promise((resolve, reject) => { - const spawnOptions = {cwd: __dirname, stdio: 'inherit'}; - spawn('yarn', [script, ...args], spawnOptions). - on('error', reject). - on('exit', code => (code === 0 ? resolve : reject)()); - }); -} diff --git a/aio/tsconfig.json b/aio/tsconfig.json index cc8f7906ba6..9da01ee0f62 100644 --- a/aio/tsconfig.json +++ b/aio/tsconfig.json @@ -29,7 +29,6 @@ "useDefineForClassFields": false }, "exclude": [ - "aio-builds-setup", "content", "dist", "node_modules",