Build and deployment

Overview

  git push
    │
    ▼
AWS CodePipeline  (one pipeline per environment: internal-client-v2-<env>)
    │
    ├── buildspec.yml            build stage
    │     npm ci
    │     npm run lint
    │     # npm run test:ci      ← currently commented out
    │     ./deployment/build.sh $ENV_NAME $ENV_TYPE "$PWD/deployment"
    │        ├─ npm install
    │        ├─ . ./deployment/setenv.sh <env> <domain> <config-dir>
    │        └─ ng build --configuration=$THIS_NODE_ENV
    │              with --max_old_space_size=12288
    │     artifacts: ./dist/**/*
    │
    └── buildspec-deploy.yml     deploy stage
          ./deployment/deploy.sh
            aws s3 sync $DIST → s3://$BUCKET_NAME/$BUCKET_PREFIX
            (plus a __v<version> copy on release)

Separate specs exist for CI-only checks (buildspec-ci.yml) and Cypress (buildspec-cypress.yml).

Angular build configurations

From angular.json. Common options: aot: true, outputHashing: 'all', vendorChunk: false, output to dist/, src/index.html as index, plus the eight global stylesheets, three global scripts and the TinyMCE asset copy.

Configuration Settings

development (default)

optimization: false, sourceMap: true, namedChunks: true, buildOptimizer: false. No file replacement.

staging

optimization: true, sourceMap: false, namedChunks: false, extractLicenses: true, buildOptimizer: true. No file replacement — uses environment.ts as generated.

production

As staging, plus a file replacement of src/environments/environment.ts → environment.prod.ts.

THIS_NODE_ENV, set by setenv.sh, selects which one build.sh passes to ng build: production for ENV_NAME=release, staging for ENV_NAME=staging, development otherwise. See environments.

Custom webpack

angular.json uses @angular-builders/custom-webpack:browser with custom-webpack.config.js:

module.exports = {
  plugins: [new webpack.DefinePlugin({ BUILD_TIMESTAMP: +new Date() })],
  optimization: { sideEffects: false },
};

sideEffects: false disables webpack’s side-effect-based tree shaking. Given the number of modules with import-time side effects (reducers reading localStorage, window.$ assignment, the Segment snippet), this is load-bearing — do not enable it.

BUILD_TIMESTAMP is defined globally but not referenced by application code.

Memory

The AOT build of ~420 components exceeds Node’s default heap:

Where Setting

npm run ng-high-mem

--max_old_space_size=8192 — used by build-prod and build-test.

deployment/build.sh

--max_old_space_size=12288 — the CI build.

A local npm run build uses the plain ng binary and can run out of memory on large changes; use npm run build-prod or build-test instead.

allowedCommonJsDependencies suppresses CommonJS warnings for ag-grid-enterprise, jwt-decode, angular2-moment, elasticsearch, rxjs and payment.

deployment/build.sh

./deployment/build.sh ENV_NAME ENV_TYPE CONFIG_DIR [ENV_FILE_NAME]

ENV_TYPE chooses the domain:

if [[ "$ENV_TYPE" == "dev" ]]; then DOMAIN=dev.payability.com
else                                DOMAIN=payability.com; fi

It then runs npm install, sources setenv.sh (note: sourced, so the exported variables persist), prints THIS_NODE_ENV, and builds. set -eux means the script echoes every command and aborts on the first failure.

Buildspecs

buildspec.yml — the environment build

Phase Commands

install

Node 12 + Python 3.8 runtimes. Fetches an SSH key pair from Secrets Manager (production/files/ssh-keys/github-zip), installs it into ~/.ssh, adds github.com to known_hosts, then npm ci.

build

npm run lint, then ./deployment/build.sh $ENV_NAME $ENV_TYPE "$PWD/deployment".

artifacts

./dist/*/

npm run test:ci is present but commented out in this spec:

- npm run lint
- # npm run test:ci
- ./deployment/build.sh …

So the deploy path runs lint only. Unit tests run in buildspec-ci.yml, which is a separate pipeline — do not assume a deployed build had its tests run.

The SSH key is needed because npm ci resolves at least one dependency over SSH.

buildspec-ci.yml — checks only

install: nodejs 12; npm ci
build:   npm run lint && npm run test:ci
reports:
  karma-junit-report: TESTS.xml            (JUNITXML)
  cobertura-report:   coverage/cobertura-coverage.xml
artifacts: coverage/lcov-report/**/*

No build, no deploy. This is the gate that actually runs the unit suite.

buildspec-cypress.yml — E2E

Pulls NPM_TOKEN and CYPRESS_QA_TEAM_PASSWORD from Secrets Manager (internal-client-v2/cypress:QA_TEAM_PASSWORD), configures the GitHub npm registry, then either runs against a locally served app or a deployed one:

if [ $CYPRESS_LOCAL = "true" ]; then
  ./deployment/setenv.sh $CYPRESS_ENV_NAME "dev.payability.com" "$PWD/deployment"
  npm run cy:run-local-no-watch
else
  npm run cy:run
fi

post_build merges the JUnit reports. Artifacts include reports, screenshots and videos. The suite is marked not maintained — see testing.

buildspec-deploy.yml

One line: ./deployment/deploy.sh.

deployment/deploy.sh

DIST_FOLDER=$CODEBUILD_SRC_DIR_BuildArtifact/dist
S3_LOCATION=s3://$BUCKET_NAME/$BUCKET_PREFIX

# deploy as the default (latest) version
aws s3 sync $DIST_FOLDER $S3_LOCATION --acl public-read --delete \
            --exclude "*__v*" --region us-east-1

# OPS-131: also deploy to a versioned subfolder for release
if [[ "$ENV_NAME" == "release" ]]; then
  CURRENT_VERSION=$(jq -r '.version' ./package.json)
  aws s3 sync $DIST_FOLDER $S3_LOCATION/__v${CURRENT_VERSION} \
              --acl public-read --delete --region us-east-1
fi

Two things to note:

  • --delete removes anything in the bucket prefix that is not in the artifact, but --exclude "__v" protects the versioned release folders from being swept away.

  • On release, the same assets are also published to __v<package.json version>, which is what allows switching between released versions without a rebuild (ticket OPS-131). Bumping package.json is therefore meaningful on release.

--acl public-read: the built assets are public. Nothing secret may be in the bundle — which is worth remembering given the hardcoded event-logger token noted in observability.

Environments and pipelines

From the repository README:

  • All dev environments deploy from develop by default.

  • Production is released by opening a pull request to release.

  • To point an environment at your branch: AWS Console → CodePipeline (dev account) → the pipeline named internal-client-v2-<env> → Edit → Edit stage (source) → the GitHub block → set Branch name → Done → Save. Every new HEAD on that branch then triggers a deployment; Release change forces one.

  • Dev assets land in the dev-payability-www bucket under cdk-<env>/admin.

deployment/reset-branches.sh force-recreates dev-1 … dev-10 from develop and force-pushes them — the per-developer environment branches.

reset-branches.sh runs git branch -D and git push --force in a loop over ten branches. Do not run it casually; anyone with work on a dev-N branch loses it.

Docker (not the deployment path)

A Dockerfile and docker-compose.yml exist for local containerised runs, and they are stale:

  • FROM node:8-alpine — Node 8, not the pinned 12.21.0.

  • ng build --env=$environment "dist-$environment" — the pre-Angular-6 CLI flag, which the current CLI does not accept.

  • Stage 2 serves dist from nginx:1.13.3-alpine with nginx/default.conf.

nginx/default.conf is still informative about the hosting contract: gzip on, root /usr/share/nginx/html, and

location / { try_files $uri $uri/ /index.html =404; }

an SPA fallback to index.html. Note that hash routing (useHash: true) means deep links do not actually depend on this rewrite — see routing.

Release checklist

  1. Merge to develop; confirm the environment pipeline is green.

  2. Bump version in package.json — it becomes the __v<version> S3 folder.

  3. Open a pull request to release using .github/PULL_REQUEST_TEMPLATE.md.

  4. Confirm the release pipeline used setup_template-production.cfg (ENV_NAME=release) and therefore the production build configuration.

  5. Verify the deployed build: the sidebar footer shows the front-end and back-end versions.

Gotchas

Issue Detail

Deploy build does not run tests

test:ci is commented out of buildspec.yml.

npm install, not npm ci, inside build.sh

The buildspec runs npm ci and then build.sh runs npm install again.

Heap size differs local vs CI

8192 locally, 12288 in CI.

Docker files are stale

Node 8 and a removed CLI flag.

--acl public-read

Built assets are world-readable.

Only release gets versioned folders

And only if package.json was bumped.

setenv.sh is sourced

Its exports leak into the rest of build.sh deliberately.