diff --git a/.github/workflows/common-ci.yml b/.github/workflows/common-ci.yml index ad6b4ad9cf..58fe7bdec6 100644 --- a/.github/workflows/common-ci.yml +++ b/.github/workflows/common-ci.yml @@ -163,6 +163,15 @@ jobs: npm run typecheck npm test + # The console job installs ui/'s packages from this cache but does not + # save it, so on main this job fetches them too, and a copy it saves + # holds them. + - name: Fetch the console's packages + if: >- + ${{ steps.scope.outputs.needed == 'true' && (github.event_name == 'push' && github.ref == 'refs/heads/main') }} + working-directory: ui + run: npm ci --ignore-scripts --no-fund --no-audit + - name: Report the npm cache id: report-npm-cache if: ${{ steps.scope.outputs.needed == 'true' && (always()) }} @@ -183,7 +192,7 @@ jobs: PACKAGE_CACHE: ${{ runner.temp }}/space-ci-helpers/package-cache.py run: >- python3 -I "${PACKAGE_CACHE}" trim npm "${HOME}/.npm" - docs/api/package-lock.json scripts/console-loop/package-lock.json + docs/api/package-lock.json scripts/console-loop/package-lock.json ui/package-lock.json - name: Save the npm cache if: ${{ steps.scope.outputs.needed == 'true' && (steps.prepare-npm-upload.outcome == 'success') }} @@ -192,6 +201,90 @@ jobs: path: ~/.npm key: ${{ steps.npm-cache-key.outputs.key }} + # The admin console's unit tests, type check and production build. Two + # console changes can each pass alone and break the console together; this + # catches that before the merge. The build needs no network or secrets: its + # API rewrites default to local addresses. + console: + if: github.repository == 'perplexityai/SPACE' + name: Admin console + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + env: + NEXT_TELEMETRY_DISABLED: '1' + steps: + - name: Check out SPACE + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # Pull CI and the merge queue skip a check the change cannot affect, + # and Main CI, which passes no base, runs it (ci/check-scopes/README.md). + - name: Decide whether this change needs the check + id: scope + shell: bash + env: + SCOPE: console + SCOPE_BASE: ${{ inputs.scope_base }} + # Set only for pull requests, whose change is the synthetic merge + # against its first parent. + PULL_REQUEST_HEAD: ${{ github.event.pull_request.head.sha }} + # Only an explicit answer reaches the gates: anything else fails. + run: | + set -euo pipefail + decision="$(bash ci/scripts/check-needed.sh "${SCOPE}" "${SCOPE_BASE}" "${PULL_REQUEST_HEAD}")" + if [[ ! "${decision}" =~ ^needed=(true|false)($'\n'base=[0-9a-f]{40})?$ ]]; then + echo "check-needed.sh answered '${decision}'" >&2 + exit 1 + fi + printf '%s\n' "${decision}" >>"${GITHUB_OUTPUT}" + + - *copy-package-cache-helper + + # The Node version the console image builds with (docker/Dockerfile.ui). + # The SPACE Guide saves the npm cache this job restores. + - name: Set up Node + if: steps.scope.outputs.needed == 'true' + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: '22.23.3' + + - *name-npm-cache + + - *restore-npm-cache + + - *prepare-npm-cache + + - name: Install the console's packages + if: steps.scope.outputs.needed == 'true' + working-directory: ui + run: npm ci --ignore-scripts --no-fund --no-audit + + - name: Run the console's unit tests + if: steps.scope.outputs.needed == 'true' + working-directory: ui + run: npm test + + # --no-install: without the installed TypeScript, npx would fetch the + # unrelated `tsc` package instead of failing. + - name: Type-check the console + if: steps.scope.outputs.needed == 'true' + working-directory: ui + run: npx --no-install tsc --noEmit -p . + + - name: Build the console + if: steps.scope.outputs.needed == 'true' + working-directory: ui + run: npm run build + + - name: Report the npm cache + if: ${{ steps.scope.outputs.needed == 'true' && (always()) }} + env: + PACKAGE_CACHE: ${{ runner.temp }}/space-ci-helpers/package-cache.py + run: python3 -I "${PACKAGE_CACHE}" report npm "${HOME}/.npm" + api_reference: if: github.repository == 'perplexityai/SPACE' name: Documentation site diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e528f98e7f..f30f311cf0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,9 +133,9 @@ Pull CI and the merge queue also skip any other check the change cannot affect: a console-only change runs no Rust or Python checks, and a documentation change no Rust checks. Each check's scope in [ci/check-scopes/](ci/check-scopes/README.md) lists the paths it never reads -(for the Go shim, the only paths it reads). A skipped check passes, and its -log says why. Main CI runs every check after the merge, except archcheck, -which runs only for pull requests and merge groups. +(for the Go shim and the admin console, the only paths they read). A skipped +check passes, and its log says why. Main CI runs every check after the merge, +except archcheck, which runs only for pull requests and merge groups. Run `cargo arch` to check the repository structure rules in [ARCHITECTURE.toml](ARCHITECTURE.toml); [archcheck](crates/tools/archcheck/README.md) diff --git a/ci/check-scopes/README.md b/ci/check-scopes/README.md index c7a7143fcf..201304c1a7 100644 --- a/ci/check-scopes/README.md +++ b/ci/check-scopes/README.md @@ -25,7 +25,7 @@ What counts as the change: makes the check run, so an omission costs time, never coverage. - `.run` lists the only paths the check reads, and the check runs when a changed path matches one of them. Use it only for a check whose inputs - are self-contained: today, the Go shim. + are self-contained: today, the Go shim and the admin console. - `run-all.txt` lists paths that make every check run: the workflows, the CI scripts, these files and the CI image's inputs. - The scope `always`, used by the two documentation jobs, has no file: those diff --git a/ci/check-scopes/console.run b/ci/check-scopes/console.run new file mode 100644 index 0000000000..44e2aecd69 --- /dev/null +++ b/ci/check-scopes/console.run @@ -0,0 +1,5 @@ +# The admin console check installs, tests, type-checks and builds ui/ from +# its own package.json and package-lock.json. Nothing under ui/ imports, +# reads or generates from outside it, and the Node version it runs on is in +# the workflow, which run-all.txt covers. +ui/** diff --git a/ci/scripts/package-cache.py b/ci/scripts/package-cache.py index 557a7fed4d..1112abd814 100644 --- a/ci/scripts/package-cache.py +++ b/ci/scripts/package-cache.py @@ -76,9 +76,14 @@ FAMILIES = { "cargo": ("Cargo.lock",), "go": ("shim/go.mod", "shim/go.sum"), - # The SPACE Guide job installs both: the docs' Mermaid bundle and the - # console loop's checks. - "npm": ("docs/api/package-lock.json", "scripts/console-loop/package-lock.json"), + # The SPACE Guide job, which saves this cache, installs all three: the + # docs' Mermaid bundle, the console loop's checks and, on main, the admin + # console's packages, which the console job installs on every run. + "npm": ( + "docs/api/package-lock.json", + "scripts/console-loop/package-lock.json", + "ui/package-lock.json", + ), "uv-python": ("uv.lock", "pyproject.toml", "ci/scripts/run-python-tests.sh"), "uv-shell": ( "uv.lock", diff --git a/ci/scripts/test-check-needed.sh b/ci/scripts/test-check-needed.sh index d19fca9c09..902616514a 100755 --- a/ci/scripts/test-check-needed.sh +++ b/ci/scripts/test-check-needed.sh @@ -67,6 +67,11 @@ assert_needed true rust shim/env_contract_generated.go # The shim's commands run without GOWORK=off. assert_needed true go go.work +# The admin console needs only ui/. +assert_needed true console ui/src/lib/api.ts +assert_needed true console ui/package-lock.json +assert_needed false console crates/l40_api/api/src/lib.rs docs/api/openapi.json scripts/console-loop/src/loop.ts + # Anything not listed as unread runs the check: Rust reads infra/, db/, # templates/ and scripts/. assert_needed true rust infra/space/identity/node-identities.sh @@ -96,11 +101,12 @@ assert_needed false rust "ui/src/app/café.tsx" assert_needed false rust "docs/release notes.md" # A change to CI itself, or to the CI image's inputs, runs every check: the -# Go scope would otherwise skip all of these. +# Go and console scopes would otherwise skip all of these. assert_needed true go .github/workflows/pull-ci.yml assert_needed true go ci/scripts/run-shim-checks.sh assert_needed true go rust-toolchain.toml assert_needed true go docker/build-btrfs-progs.sh +assert_needed true console .github/workflows/common-ci.yml # The change is tree to tree: a later console-only commit does not hide an # earlier Rust one.