Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ cookbooks
/node_modules
# /public/packs
/public/packs-test
# The frontend is built inside the image, never taken from a build on the host
/build
/public/assets
/node_modules

# Stuff that's sometimes lying around in dev machines
Expand Down
216 changes: 93 additions & 123 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ jobs:
run: docker tag ghcr.io/neinteractiveliterature/intercode-assets:${{ github.sha }} ghcr.io/neinteractiveliterature/intercode-assets:latest
- name: Push to registry
run: docker push ghcr.io/neinteractiveliterature/intercode-assets:latest && docker push ghcr.io/neinteractiveliterature/intercode-assets:${{ github.event.release.name }}
# And for the framework-mode assets image, which is only built when BUILD_FRAMEWORK is set (see ci.yml)
- name: Tag and push the framework-mode assets image
if: vars.BUILD_FRAMEWORK != ''
run: |
image=ghcr.io/neinteractiveliterature/intercode-assets-framework
docker pull $image:${{ github.sha }}
docker tag $image:${{ github.sha }} $image:${{ github.event.release.name }}
docker tag $image:${{ github.sha }} $image:latest
docker push $image:latest
docker push $image:${{ github.event.release.name }}
# Keeps each release's built assets in an S3 bucket, without deleting earlier releases' files, so that stale
# tabs and cached pages can still load their chunks. Skipped unless ASSETS_BUCKET is set; see
# terraform/modules/assets_bucket_intercode and upload-assets-to-bucket.yml.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
with:
cache: yarn
- run: yarn install
- run: yarn build:web
- run: yarn build:framework
- run: yarn check-graphql-documents
eslint:
name: eslint
Expand Down
30 changes: 20 additions & 10 deletions .github/workflows/upload-assets-to-bucket.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Upload assets to bucket

# Uploads a commit's built assets (from its intercode-assets image) to the assets bucket, and marks the files that
# commit's release dropped as obsolete. See terraform/modules/assets_bucket_intercode. Called by the release workflow,
# Uploads a commit's built frontend (from its intercode-assets image) to the assets bucket, which is what ASSETS_HOST
# serves to the pages that Rails serves, and marks the files that commit's release dropped as obsolete. See terraform/modules/assets_bucket_intercode. Called by the release workflow,
# and can be run by hand to upload an already-built commit.
#
# The role it assumes only trusts the refs listed in the module's github_oidc_subjects (release tags by default), so a
Expand Down Expand Up @@ -45,8 +45,8 @@ jobs:
steps:
- name: Check that the bucket is configured
run: |
if [ -z "${{ vars.ASSETS_BUCKET }}" ] || [ -z "${{ vars.ASSETS_UPLOAD_ROLE_ARN }}" ]; then
echo "ASSETS_BUCKET and ASSETS_UPLOAD_ROLE_ARN must be set as repository variables" >&2
if [ -z "${{ vars.ASSETS_BUCKET }}" ] || [ -z "${{ vars.ASSETS_UPLOAD_ROLE_ARN }}" ] || [ -z "${{ vars.ASSETS_HOST }}" ]; then
echo "ASSETS_BUCKET, ASSETS_UPLOAD_ROLE_ARN and ASSETS_HOST must be set as repository variables" >&2
exit 1
fi
- name: Check the commit
Expand All @@ -58,21 +58,31 @@ jobs:
- uses: actions/checkout@v7
- name: Log in to registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract assets from image
- name: Extract the build from the image
run: |
docker pull ghcr.io/neinteractiveliterature/intercode-assets:$ASSETS_SHA
container=$(docker create ghcr.io/neinteractiveliterature/intercode-assets:$ASSETS_SHA)
docker cp "$container:/usr/share/nginx/html/packs" packs
docker cp "$container:/usr/share/intercode-assets" build
docker cp "$container:/etc/intercode-assets/current-files.txt" current-files.txt
docker rm "$container"
# The image also carries over earlier releases' files (see Dockerfile.assets). Only this release's own
# files count as current, so the rest can be marked obsolete in the bucket.
(cd packs && find . -type f | sort) | comm -23 - current-files.txt | (cd packs && xargs -r rm)
# The image also carries over earlier releases' files (see the assets stage of the Dockerfile). Only this
# release's own files count as current, so the rest can be marked obsolete in the bucket.
(cd build/assets && find . -type f | sort) | comm -23 - current-files.txt | (cd build/assets && xargs -r rm)
- name: Point the build at the assets host
# The build is made against a placeholder asset host. Pages that Rails serves load these files from
# ASSETS_HOST, which is the value the app containers have, so the URLs have to name it.
run: script/substituteAssetsHost.sh build "${{ vars.ASSETS_HOST }}"
- name: Install boto3 (used to mark obsolete files)
run: python3 -m pip install --user 'boto3>=1.35,<2'
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.ASSETS_UPLOAD_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION || 'us-east-1' }}
- name: Upload the assets (all of them, or the phase asked for)
run: script/uploadReleaseAssets.sh "${{ vars.ASSETS_BUCKET }}" packs "$PHASE"
run: UPLOAD_INDEX=false script/uploadFrameworkAssets.sh "${{ vars.ASSETS_BUCKET }}" build "$PHASE"
# The bucket still holds the files of the releases before v9.0.0, under packs/. The frontend no longer has any, so
# all of them are obsolete: this starts their countdown to expiry (the bucket's retention period), and tabs open
# on an old release can load their files until then. It does nothing once they are all marked.
- name: Mark the files of earlier releases' packs/ obsolete
if: env.PHASE != 'hashed'
run: python3 script/mark_obsolete_assets.py "${{ vars.ASSETS_BUCKET }}" packs/ < /dev/null
18 changes: 7 additions & 11 deletions .github/workflows/upload-framework-assets-to-bucket.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
name: Upload framework-mode build to bucket

# Uploads a commit's framework-mode build (from its intercode-assets-framework image) to the static shell bucket, and
# Uploads a commit's framework-mode build (from its intercode-assets image) to the static shell bucket, and
# marks the files that commit's release dropped as obsolete. See terraform/modules/assets_bucket_intercode and the static
# shell mode of terraform/modules/cloudfront_intercode. Called by the release workflow, and can be run by hand to upload
# an already-built commit. The image is only built (and pushed, on main) when the repository variable BUILD_FRAMEWORK
# is set; see the build-framework job in ci.yml.
# an already-built commit.
#
# The role it assumes only trusts the refs listed in the module's github_oidc_subjects (release tags by default), so a
# manual run has to be started from a ref that role trusts.
Expand All @@ -13,7 +12,7 @@ on:
workflow_call:
inputs:
sha:
description: Commit whose intercode-assets-framework image to upload
description: Commit whose intercode-assets image to upload
required: true
type: string
phase:
Expand All @@ -24,7 +23,7 @@ on:
workflow_dispatch:
inputs:
sha:
description: Commit whose intercode-assets-framework image to upload (full SHA; defaults to the commit this runs from)
description: Commit whose intercode-assets image to upload (full SHA; defaults to the commit this runs from)
required: false
type: string
phase:
Expand Down Expand Up @@ -62,15 +61,12 @@ jobs:
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract the build from the image
run: |
if ! docker pull ghcr.io/neinteractiveliterature/intercode-assets-framework:$ASSETS_SHA; then
echo "No intercode-assets-framework image for $ASSETS_SHA. It is only built when BUILD_FRAMEWORK is set." >&2
exit 1
fi
container=$(docker create ghcr.io/neinteractiveliterature/intercode-assets-framework:$ASSETS_SHA)
docker pull ghcr.io/neinteractiveliterature/intercode-assets:$ASSETS_SHA
container=$(docker create ghcr.io/neinteractiveliterature/intercode-assets:$ASSETS_SHA)
docker cp "$container:/usr/share/intercode-assets" build
docker cp "$container:/etc/intercode-assets/current-files.txt" current-files.txt
docker rm "$container"
# The image also carries over earlier releases' files (see Dockerfile.assets-framework). Only this release's
# The image also carries over earlier releases' files (see the assets stage of the Dockerfile). Only this release's
# own files count as current, so the rest can be marked obsolete in the bucket.
(cd build/assets && find . -type f | sort) | comm -23 - current-files.txt | (cd build/assets && xargs -r rm)
- name: Point the build at the same origin
Expand Down
53 changes: 53 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
ARG RUBY_VERSION=4
ARG NODE_VERSION=24.11.1
ARG CHAMBER_VERSION=3.1.5
# Optional, for the assets target: an earlier intercode-assets image (for example the last release's) whose hashed files
# are carried over, so that tabs and cached pages from the previous release can still load their chunks. The default,
# an empty image, carries nothing over.
ARG PREVIOUS_ASSETS_IMAGE=scratch
# Carried-over files are pruned this many days after the release that dropped them. Files that are part of this
# release are never pruned, however old.
ARG PREVIOUS_ASSETS_RETENTION_DAYS=30

### dev

Expand Down Expand Up @@ -30,6 +37,48 @@ RUN bundle config set without 'development test' \

COPY --chown=www:www . /usr/src/intercode

### frontend (the framework-mode build, which the Rails app serves as its shell and the assets image serves from nginx)

FROM node:${NODE_VERSION}-slim AS frontend
ARG REVISION
ENV REVISION=${REVISION}
# husky, which wants a git repository
ENV HUSKY=0
WORKDIR /usr/src/intercode
RUN apt-get update && apt-get install -y --no-install-recommends python3 build-essential && rm -rf /var/lib/apt/lists/*
COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn .yarn
COPY doc-site/package.json doc-site/package.json
RUN yarn install --immutable
COPY . .
RUN yarn run build:framework

### assets (docker build --target assets): the frontend served by nginx, as the intercode-assets image
#
# The build is made against a placeholder asset host. When the container starts it takes a fresh copy of the build and
# swaps in $ASSETS_HOST (or nothing, for same-origin assets), so one image serves any deployment. The app container
# needs the same ASSETS_HOST. With RAILS_UPSTREAM set, the container also proxies the Rails routes and crawlers to Rails.
# See https://intercode.interactiveliterature.org/docs/deploying

FROM ${PREVIOUS_ASSETS_IMAGE} AS previous

FROM nginx AS assets
ARG PREVIOUS_ASSETS_RETENTION_DAYS
# The pristine build. The entrypoint copies it into the web root on every start, before substituting ASSETS_HOST.
COPY --from=frontend /usr/src/intercode/build/framework/client /usr/share/intercode-assets
RUN rm /usr/share/nginx/html/index.*
RUN --mount=type=bind,from=previous,target=/previous \
--mount=type=bind,source=script/mergePreviousAssets.sh,target=/mergePreviousAssets.sh \
/mergePreviousAssets.sh /previous ${PREVIOUS_ASSETS_RETENTION_DAYS} /usr/share/intercode-assets assets
COPY script/substituteAssetsHost.sh /usr/local/bin/substituteAssetsHost.sh
COPY docker-assets/40-substitute-assets-host.sh /docker-entrypoint.d/40-substitute-assets-host.sh
COPY docker-assets/nginx.conf /etc/nginx/conf.d/default.conf
# The reference front server configuration, used when RAILS_UPSTREAM is set. The crawler map and the Rails locations are
# generated from the same route list as the CloudFront module.
COPY docker-assets/front-proxy.conf.template /etc/nginx/intercode/front-proxy.conf.template
COPY docker-assets/proxy_params.conf /etc/nginx/proxy_params.conf
COPY terraform/modules/cloudfront_intercode/nginx/crawler-map.conf terraform/modules/cloudfront_intercode/nginx/rails-locations.conf /etc/nginx/intercode/

### build

FROM dev AS build
Expand All @@ -41,6 +90,10 @@ RUN bundle exec bootsnap precompile --gemfile app/ lib/
RUN DATABASE_URL=postgresql://fakehost/not_a_real_database AWS_S3_BUCKET=fakebucket AWS_ACCESS_KEY_ID=dummy AWS_SECRET_ACCESS_KEY=dummy bundle exec rails assets:precompile
RUN rm -r doc-site

# The pristine framework-mode build. It is made against a placeholder asset host, because ASSETS_HOST is only known when
# the container starts; bin/with_static_shell makes the copy that Rails serves, with the real host substituted in.
COPY --from=frontend --chown=www:www /usr/src/intercode/build/framework/client /usr/src/intercode/build/framework/client

### ld_preload trickery

FROM ruby:${RUBY_VERSION}-slim AS amd64_jemalloc
Expand Down
39 changes: 0 additions & 39 deletions Dockerfile.assets

This file was deleted.

44 changes: 0 additions & 44 deletions Dockerfile.assets-framework

This file was deleted.

4 changes: 2 additions & 2 deletions agent-docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ PARALLEL_WORKERS=4 bin/rails test

## System tests against the framework-mode build

While the migration to React Router framework mode (#12219) is in progress, the system tests also run against the framework-mode build, in a real browser (Cuprite) with Rails serving its prebuilt shell (`StaticSpaShell`) when `STATIC_SHELL_DIR` is set. `test/system/framework_shell_test.rb` only runs in that mode and checks that the build boots (it uses the placeholder `/framework-check` page); the other system tests run in both.
The system tests run in a real browser (Cuprite), and CI runs them against the framework-mode build with Rails serving its prebuilt shell (`StaticSpaShell`) when `STATIC_SHELL_DIR` is set, which is how production runs. Locally they use the data-mode build unless you set that up (below). `test/system/framework_shell_test.rb` only runs in the framework mode and checks that the build boots (it uses the placeholder `/framework-check` page); the other system tests run in both.

```sh
yarn build:framework
script/prepareStaticShell.sh # prints STATIC_SHELL_DIR=...; also fills public/assets
STATIC_SHELL_DIR=$PWD/tmp/static-shell bin/rails test:system
```

Run `script/prepareStaticShell.sh` again after each build. CI does the same in the `Minitest system tests` job when the repository variable `BUILD_FRAMEWORK` is set. The framework build's routes are generated from `AppRouter.tsx` by `script/genRoutes.mts` (`yarn routes:generate`; CI runs `yarn routes:check`), so change routes in `AppRouter.tsx` and regenerate until the cutover (#12260) deletes both.
Run `script/prepareStaticShell.sh` again after each build. CI does the same in the `Minitest system tests` job. The framework build's routes are generated from `AppRouter.tsx` by `script/genRoutes.mts` (`yarn routes:generate`; CI runs `yarn routes:check`), so change routes in `AppRouter.tsx` and regenerate until the cutover (#12260) deletes both.

## End-to-End Testing with Playwright

Expand Down
Loading
Loading