Classy is two REST services (portal/backend and autotest) plus a frontend that the backend serves as
static content. All of them run from an IDE or the command line without building containers.
| Dependency | Notes |
|---|---|
Node 22 (>= 22.0.0 < 24) |
nvm install picks the version from .nvmrc (lts/jod); CI builds on cimg/node:22.23.2. Node 24 does not work: restify pulls in spdy, which calls the http_parser binding that Node 24 removed. |
| Yarn 1 (classic) | .yarnrc pins the repo's own copy, so whichever Yarn 1.x launcher you have defers to it. |
| Docker | Runs MongoDB, and the AutoTest specs that build grading images. |
| MongoDB 5 | docker run --name classy-mongo -p 27017:27017 -d mongo:5.0, then docker start classy-mongo on later sessions. |
Mongo must be running before you start AutoTest, Portal, or the test suite.
On macOS the AutoTest Docker specs need /var/run/docker.sock, which Docker Desktop does not create by
default: enable Settings → Advanced → "Allow the default Docker socket to be used".
Copy .env.sample to .env in the repo root and fill it in. The sample documents each variable inline
and packages/common/src/Config.ts is the authoritative list. Never commit .env. Config loads
it by a path relative to itself, so it resolves no matter which directory you launch from.
Two settings matter specifically for local development:
DB_URL— your local Mongo, e.g.mongodb://localhost:27017.BACKEND_URL— must be http for local test runs. The specs start their own backend, andBackendServeronly listens over https whenCIis set; withhttps://localhostthe AutoTestClassPortalandGitHub Event Parserspecs cannot reach it.
Classy manages administrators through GitHub teams, so the org needs staff and admin teams, and the
bot user should be an owner of the organization.
nvm install # selects the node version named in .nvmrc
yarn install
yarn run build
tsc emits each .js beside its .ts, and both the daemons and the test suite run that emitted
JavaScript. Re-run yarn run build after every source change or you will be running stale code.
To run a service:
- backend:
yarn run backendfrompackages/portal/backend/ - autotest:
yarn run autotestfrompackages/autotest/ - frontend: see
packages/portal/frontend/README.md
packages/portal/backend/src-util/ holds batch utilities. Several of them modify the database or GitHub
irreversibly, so read them carefully before running any of these.
From the repo root, after yarn run build:
yarn run test:backend
yarn run test:autotest
yarn run test
- Run from the repo root. The AutoTest specs resolve their fixtures relative to the root when
CIis unset, so running frompackages/autotest/fails withENOENT ... test/githubEvents/*.json. - Set
LOG_LEVEL=WARNfor a readable run; the defaultTRACEburies the Mocha summary under thousands of lines. - Specs that hit live GitHub skip themselves unless
CIis set, so a local run leaves ~100 pending. To exercise them, setCI=true— which also switches the backend to https, so generate certs first:mkdir -p packages/portal/backend/ssl && openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \ -keyout packages/portal/backend/ssl/privkey.pem \ -out packages/portal/backend/ssl/fullchain.pem -subj "/CN=localhost" yarn run coverwrites coverage totestOutput/coverage/index.html. Locally it under-reports, because mocks stand in for much of what CI exercises for real.
In WebStorm, create a Mocha target with node options -r tsconfig-paths/register, mocha options
--exit, and the repo root as the working directory.
- There is no
baseUrl. Path mappings are relative to thetsconfig.jsonthey appear in.tsconfig-pathsresolves the@common/@backendaliases at runtime and still needs one, so the scripts exportTS_NODE_BASEURL=.(the Dockerfiles set it as anENV). Running mocha or a daemon by hand without it fails withCannot find module '@common/Log'. strictis pinned tofalsein eachtsconfig.jsonto keep pre-7 semantics. Turning it on surfaces roughly 1250 null/undefined diagnostics, so treat that as its own project.esModuleInteropis always on and cannot be disabled. CommonJS packages that are called or constructed (supertest,dockerode,csv-parse,client-oauth2,moment,parse-link-header) must useimport x from "y", notimport * as x from "y".- The compiler is a native binary with no JavaScript API, so tools built on it do not work:
ts-loader,tsconfig-paths-webpack-plugin,tslint, andts-node. Linting and formatting run through Biome (yarn lint,yarn lint:fix); the frontend runstscfirst and points webpack at the emitted.js, withresolve.aliasstanding in for the path mappings.
Containers need different configuration than host-local development, because inside a container
localhost is that container rather than its neighbour. Keep a second .env.docker (also gitignored)
that differs only in:
| Setting | Host-local | Containers |
|---|---|---|
DB_URL |
mongodb://localhost:27017 |
mongodb://USER:PASS@db:27017/?authMechanism=DEFAULT |
AUTOTEST_URL |
http://localhost |
http://autotest |
BACKEND_URL |
http://localhost |
https://portal |
PERSIST_DIR |
relative, e.g. persist |
absolute, e.g. /output — a relative value fails with mount path must be absolute |
docker-compose.yml hardcodes env_file: .env and the Dockerfiles COPY .env, so .env.docker cannot
be passed with --env-file; it has to be swapped into place for the run:
cp .env .env.bak && cp .env.docker .env
./helper-scripts/bootstrap-plugin.sh
docker compose -f docker-compose.yml up -d --build
curl -k https://localhost/portal/config # exercises nginx -> portal -> Mongo
docker compose -f docker-compose.yml down && cp .env.bak .env
-f docker-compose.ymlis deliberate. It skipsdocker-compose.override.yml, whichbootstrap-plugin.shcopies from the plugin and which hardcodes the db volume at/var/opt/classy/db. On a dev machine that path is created root-owned while the container runs as${UID}, so Mongo dies withread-only directory: /data/db. Include the override only when you are specifically testing plugin behaviour, and create that directory yourself first. (CI includes it:build_onlyruns as root, where the path is writable.)- Stop any other Mongo bound to 27017 first; the
dbservice publishes that port. restart: alwaysmeans a crashed service comes straight back, so a failure can look like a 502 from nginx rather than a crash. Checkdocker compose logs portalbefore blaming the proxy.- The built images contain a copy of your
.env. Fine locally; do not push them anywhere.
CircleCI runs two jobs per push:
build_and_test— compiles every package, checks formatting and lint rules with Biome, then runs the Portal and AutoTest suites.build_only— builds the images, starts the stack, and polls/portal/configuntil it answers.
CI decrypts its own .env from .circleci/env.enc, so your local .env is never involved.
When a build fails, the test results are in the build's artifacts as
testOutput/backend/test/test-results.xml — including on red builds. Read that rather than the step
output: CircleCI truncates step output at 400KB and this suite exceeds it, so the earliest specs scroll off.
A nightly scheduled build runs the same job against live GitHub. It catches breakage caused by external state drift (a changed template repo, an expired token) rather than by commits, so a nightly failure on unchanged code is a real signal, not a flake.
- Portal backend, Portal frontend, and AutoTest all compile
-
yarn lintis clean (yarn lint:fixto apply) - Portal and AutoTest suites pass
- Containers build and come up — only when changing something they depend on
Items 1–3 are exactly what build_and_test checks. Some specs behave differently locally than on CI
(the AutoTest Docker ones, and anything gated on live GitHub), so a green local run is not proof of a
green build, nor the reverse.