276 lines
10 KiB
YAML
276 lines
10 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
# Integration branches are listed here as well as below: a pull_request run
|
|
# only ever proves one part merged into the branch, so without a push
|
|
# trigger the accumulated state of the branch that eventually reaches main
|
|
# is never built on its own.
|
|
branches: [main, integration/kv-asset-server-backend]
|
|
pull_request:
|
|
branches:
|
|
[
|
|
main,
|
|
feat/maic-editor-v0,
|
|
feat/maic-editor-v1,
|
|
runtime-server-backend,
|
|
integration/kv-asset-server-backend,
|
|
]
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
# Never cancel a main run. Each main run validates only its own push range, so
|
|
# a superseded run takes the range that contained a package change with it and
|
|
# the replacement, comparing against the cancelled tip, sees nothing.
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
jobs:
|
|
check:
|
|
name: Lint, Typecheck & Unit Tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Package version bumps
|
|
if: github.event_name == 'pull_request'
|
|
run: node scripts/check-package-version-bumps.mjs "$BASE_SHA"
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
|
|
# A branch creation or a force push reports an unusable `before`. Fail
|
|
# rather than substituting HEAD^: a force push can replace many commits at
|
|
# once, so the previous commit is not the range that needs checking, and
|
|
# publishing requires this run to be green.
|
|
- name: Package version bumps (main push)
|
|
if: github.event_name == 'push'
|
|
run: |
|
|
set -euo pipefail
|
|
base="$BEFORE_SHA"
|
|
if [ -z "$base" ] || ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then
|
|
echo "::error::Push range base ${base:-<empty>} is unavailable, so this push cannot be checked."
|
|
echo "Re-run after the branch has an ordinary push range, or verify the versions by hand."
|
|
exit 1
|
|
fi
|
|
node scripts/check-package-version-bumps.mjs "$base"
|
|
env:
|
|
BEFORE_SHA: ${{ github.event.before }}
|
|
|
|
# The tarball smoke test proves this about the bytes that would actually
|
|
# be published, but it installs five package tarballs, so it runs only on
|
|
# the release path. This is the cheap source-level form, here so that a
|
|
# pull request restoring `workspace:*` fails at review time rather than at
|
|
# release time, after a version number has already been spent.
|
|
- name: Internal dependency ranges
|
|
run: node scripts/check-internal-dependency-ranges.mjs
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
|
|
# `postinstall` builds all five @openmaic packages, and some of them
|
|
# generate tracked, publishable files (renderer's fonts.css and its KaTeX
|
|
# font snapshot). The publish workflow refuses to release when the build
|
|
# rewrites one; catching it here means a stale generated file is a pull
|
|
# request failure instead of a release failure.
|
|
#
|
|
# Against GITHUB_SHA, not the index or HEAD: a bare `git diff` misses
|
|
# staged changes, while install code can create a commit and move HEAD.
|
|
# This check exists precisely because code ran before it, so its reference
|
|
# point must be something that code cannot move. Disabling replacement
|
|
# objects also prevents a local replace ref from redirecting the commit.
|
|
- name: Verify the install did not rewrite tracked package files
|
|
run: |
|
|
set -euo pipefail
|
|
actual_head="$(git --no-replace-objects rev-parse HEAD)"
|
|
if [ "$actual_head" != "$GITHUB_SHA" ]; then
|
|
echo "::error::HEAD moved from $GITHUB_SHA to $actual_head during installation."
|
|
exit 1
|
|
fi
|
|
if ! git --no-replace-objects diff --quiet "$GITHUB_SHA" -- packages/@openmaic; then
|
|
echo "::error::Installing rewrote tracked files under packages/@openmaic."
|
|
echo "Regenerate them and commit the result."
|
|
git --no-replace-objects --no-pager diff --stat "$GITHUB_SHA" -- packages/@openmaic
|
|
exit 1
|
|
fi
|
|
echo "Tracked package files match the commit under test."
|
|
|
|
- name: Prettier
|
|
run: pnpm check
|
|
|
|
- name: ESLint
|
|
run: pnpm lint
|
|
|
|
- name: TypeScript
|
|
run: npx tsc --noEmit
|
|
|
|
- name: i18n Key Alignment
|
|
run: pnpm check:i18n-keys
|
|
|
|
- name: Unit Tests
|
|
run: pnpm test
|
|
|
|
- name: Unit Tests (importer)
|
|
run: pnpm --filter @openmaic/importer test
|
|
|
|
- name: Unit Tests (dsl)
|
|
run: pnpm --filter @openmaic/dsl test
|
|
|
|
- name: TypeScript (generation)
|
|
run: pnpm --filter @openmaic/generation run typecheck
|
|
|
|
- name: Unit Tests (generation)
|
|
run: pnpm --filter @openmaic/generation test
|
|
|
|
- name: Node consumer smoke (generation scene)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
port=43127
|
|
node scripts/generation-node-smoke-server.mjs --port "$port" >"$RUNNER_TEMP/generation-smoke-server.log" 2>&1 &
|
|
server_pid=$!
|
|
cleanup() {
|
|
kill "$server_pid" 2>/dev/null || true
|
|
wait "$server_pid" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
for attempt in {1..50}; do
|
|
if node -e "fetch('http://127.0.0.1:$port/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"; then
|
|
break
|
|
fi
|
|
if [ "$attempt" -eq 50 ]; then
|
|
cat "$RUNNER_TEMP/generation-smoke-server.log"
|
|
exit 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
output="$(node scripts/generation-node-smoke.mjs \
|
|
--requirement "Explain dependency injection" \
|
|
--endpoint "http://127.0.0.1:$port" \
|
|
--model smoke-model)"
|
|
printf '%s\n' "$output"
|
|
node -e 'const value = JSON.parse(process.argv[1]); if (!Array.isArray(value.outlines) || value.outlines.length === 0 || !value.scene || value.sceneValidation?.valid !== true) process.exit(1)' "$output"
|
|
|
|
# Covers the package's own test tree, which the root tsc reaches only by an
|
|
# incidental glob. The device-scope guard is written as `@ts-expect-error`
|
|
# probes in those tests, and a probe nothing type-checks proves nothing.
|
|
- name: TypeScript (storage, incl. tests)
|
|
run: pnpm --filter @openmaic/storage run typecheck
|
|
|
|
- name: Unit Tests (storage)
|
|
run: pnpm --filter @openmaic/storage test
|
|
|
|
render-service:
|
|
name: Render Service (typecheck + tests)
|
|
runs-on: ubuntu-latest
|
|
# The producer dep bundles puppeteer; skip its Chromium download — the unit
|
|
# tests exercise the service's boundaries (unzip, admission, body caps) and
|
|
# never launch a browser.
|
|
env:
|
|
PUPPETEER_SKIP_DOWNLOAD: "true"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
# render-service is its own npm package (own lockfile, Node-only deps),
|
|
# excluded from the root eslint/tsc/pnpm workspace — so it gets its own
|
|
# install + quality gate here rather than riding on the `check` job.
|
|
cache: npm
|
|
cache-dependency-path: render-service/package-lock.json
|
|
|
|
- name: Install
|
|
working-directory: render-service
|
|
run: npm ci
|
|
|
|
- name: TypeScript
|
|
working-directory: render-service
|
|
run: npm run typecheck
|
|
|
|
- name: Unit Tests
|
|
working-directory: render-service
|
|
run: npm test
|
|
|
|
- name: Build container image
|
|
run: docker build --tag openmaic-render-service:ci render-service
|
|
|
|
e2e:
|
|
name: E2E Tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
|
|
- name: Install Playwright browsers
|
|
run: pnpm exec playwright install chromium --with-deps
|
|
|
|
# Measures the emitted cover cards in Chromium. Lives with the unit tests
|
|
# (it needs the compiler), but only this job has a browser, so it runs
|
|
# here with the flag that turns a missing one into a failure.
|
|
- name: Cover-card layout guardrail
|
|
run: pnpm exec vitest run tests/video-export/cover-card-layout.browser.test.ts
|
|
env:
|
|
COVER_LAYOUT_BROWSER: '1'
|
|
|
|
- name: Interactive static HTML Chromium smoke
|
|
run: pnpm exec vitest run tests/video-export/interactive-static-html.browser.test.ts
|
|
env:
|
|
INTERACTIVE_STATIC_BROWSER: '1'
|
|
|
|
# runner.* is only valid in step contexts (not job-level env).
|
|
- name: Materialize Hyperframes lint samples
|
|
run: pnpm exec vitest run tests/video-export/e2e-materialize.test.ts
|
|
env:
|
|
HF_E2E_DIR: ${{ runner.temp }}/openmaic-hyperframes-samples
|
|
|
|
# Hyperframes 0.7.60 reports warning-only lint with exit status zero, so
|
|
# require its plain clean summary as well as a successful process.
|
|
- name: Lint Hyperframes samples
|
|
shell: bash
|
|
env:
|
|
HF_E2E_DIR: ${{ runner.temp }}/openmaic-hyperframes-samples
|
|
run: |
|
|
set -euo pipefail
|
|
for sample in quiz pbl-v2 pbl-legacy pbl-dense mixed arabic interactive-static; do
|
|
dir="$HF_E2E_DIR/$sample"
|
|
if output="$(pnpm exec hyperframes lint "$dir" 2>&1)"; then
|
|
status=0
|
|
else
|
|
status=$?
|
|
fi
|
|
printf '%s\n' "$output"
|
|
if [ "$status" -ne 0 ]; then
|
|
echo "::error::Hyperframes lint failed for $sample (exit $status)."
|
|
exit "$status"
|
|
fi
|
|
if ! grep -Fq -- '0 errors, 0 warnings' <<<"$output"; then
|
|
echo "::error::Hyperframes lint for $sample did not report zero errors and zero warnings."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Run e2e tests
|
|
run: pnpm exec playwright test
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
if: failure()
|
|
with:
|
|
name: playwright-report
|
|
path: playwright-report/
|
|
retention-days: 7
|