mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
* refactor(attachments): generate text preview whitelist Co-authored-by: Cursor <[email protected]> * fix(attachments): gate text previews on shared whitelist (MUL-8248) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]>
955 lines
46 KiB
YAML
955 lines
46 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
schedule:
|
|
- cron: "23 3 * * *"
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# PRs and main pushes use the same paths. Scheduled/manual runs validate all
|
|
# scopes, including platform stress tests, without charging every merge for them.
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
frontend: ${{ steps.decide.outputs.frontend }}
|
|
backend: ${{ steps.decide.outputs.backend }}
|
|
sqlc: ${{ steps.decide.outputs.sqlc }}
|
|
runtime: ${{ steps.decide.outputs.runtime }}
|
|
quality_only: ${{ steps.decide.outputs.quality_only }}
|
|
scripts: ${{ steps.decide.outputs.scripts }}
|
|
installer: ${{ steps.decide.outputs.installer }}
|
|
images: ${{ steps.decide.outputs.images }}
|
|
full: ${{ steps.decide.outputs.full }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Test CI selection and gates
|
|
run: node --test scripts/ci-scope.test.mjs scripts/check-image-budget.test.mjs
|
|
|
|
- name: Filter paths
|
|
id: filter
|
|
if: github.event_name == 'pull_request' || github.event_name == 'push'
|
|
uses: dorny/paths-filter@v3
|
|
with:
|
|
# Pushes compare before/head SHAs. PRs use the pull-request file API;
|
|
# paths-filter ignores base/ref for pull_request events.
|
|
base: ${{ github.event_name == 'push' && github.event.before || github.base_ref }}
|
|
ref: ${{ github.sha }}
|
|
filters: .github/ci-paths.json
|
|
|
|
- name: Decide
|
|
id: decide
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
FILTER_RESULTS: ${{ toJSON(steps.filter.outputs) }}
|
|
run: node scripts/ci-scope.mjs decide
|
|
|
|
sqlc-check:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.sqlc == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Verify generated SQL is up to date
|
|
run: |
|
|
make sqlc
|
|
git diff --exit-code -- server/pkg/db/generated
|
|
untracked="$(git ls-files --others --exclude-standard -- server/pkg/db/generated)"
|
|
if [ -n "$untracked" ]; then
|
|
printf 'Untracked generated SQL files:\n%s\n' "$untracked"
|
|
exit 1
|
|
fi
|
|
|
|
# The frontend validation is split across four runners on purpose. A prior
|
|
# split isolated build/typecheck/lint from the test suites; on a full test
|
|
# cache miss, however, web/core/desktop/views still competed for the four
|
|
# vCPUs on the test runner and stretched the job to ~8 minutes. Giving views
|
|
# its own runner only reduced that to 7m23s because the suite itself remained
|
|
# the long pole, so two Vitest shards now split it across two runners while
|
|
# the remaining suites continue in parallel on another. The extra setup
|
|
# increases total runner-minutes but reduces wall-clock feedback time.
|
|
frontend-build:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
# Pin turbo's filesystem cache somewhere actions/cache can address.
|
|
TURBO_CACHE_DIR: .turbo/cache
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install
|
|
|
|
# `node-version: 22` above floats across patch releases, and turbo's
|
|
# global hash does not include the interpreter at all (`engines` is null
|
|
# in its dry-run cache inputs). Without the resolved version in the key,
|
|
# a runner silently moving to another 22.x would restore a cache built by
|
|
# the old interpreter and report green without executing anything.
|
|
- name: Resolve runtime for cache key
|
|
id: runtime
|
|
run: echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
|
|
|
|
# Cache entries are immutable, so the key carries the commit SHA to make
|
|
# every run publish a fresh one and `restore-keys` falls back to the most
|
|
# recent prefix match. Each frontend worker runs a disjoint task set, so
|
|
# it gets its own prefix rather than racing to save one key.
|
|
# GitHub scopes caches by branch: a PR reads main's entries (so unchanged
|
|
# tasks hit on the first push) and writes its own (so re-pushes hit too).
|
|
- name: Restore turbo cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: .turbo/cache
|
|
key: turbo-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-${{ github.sha }}
|
|
restore-keys: |
|
|
turbo-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-
|
|
|
|
- name: Build, type check, and lint
|
|
# Mobile lives in a parallel mobile-verify workflow (path-filtered
|
|
# to apps/mobile/** + packages/core/**) so it doesn't add
|
|
# ~50s of expo-lint + tsc to every web/desktop PR. Keep this
|
|
# filter in sync with the root package.json scripts, which also
|
|
# exclude @multica/mobile.
|
|
run: pnpm exec turbo build typecheck lint --filter='!@multica/docs' --filter='!@multica/mobile'
|
|
|
|
- name: Check frontend quality
|
|
uses: ./.github/actions/frontend-quality
|
|
|
|
frontend-test:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
TURBO_CACHE_DIR: .turbo/cache
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install
|
|
|
|
- name: Resolve runtime for cache key
|
|
id: runtime
|
|
run: echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
|
|
|
|
# See frontend-build for the key strategy. These entries are tiny (~60KB
|
|
# measured): `test` declares no outputs, so turbo caches exit codes and
|
|
# logs rather than artifacts -- yet a hit still skips the whole suite,
|
|
# which is the single most expensive task in the graph.
|
|
- name: Restore turbo cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: .turbo/cache
|
|
key: turbo-test-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-${{ github.sha }}
|
|
restore-keys: |
|
|
turbo-test-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-
|
|
|
|
- name: Test web, core, and desktop
|
|
# Same filter rationale as frontend-build. Type errors are not this
|
|
# job's responsibility -- frontend-build owns the `typecheck` task.
|
|
# `test` reaches dependency sources through the hash-only
|
|
# `^cache-inputs` edge (see turbo.json), so no `tsc` runs here.
|
|
run: pnpm exec turbo test --filter='!@multica/docs' --filter='!@multica/mobile' --filter='!@multica/views'
|
|
|
|
# Split views across two runners. GitHub reports the matrix job's combined
|
|
# result, so the aggregate gate can continue depending on this one job ID.
|
|
frontend-views-test:
|
|
name: frontend-views-test (${{ matrix.index }}/${{ matrix.total }})
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- index: 1
|
|
total: 2
|
|
- index: 2
|
|
total: 2
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
TURBO_CACHE_DIR: .turbo/cache
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install
|
|
|
|
- name: Resolve runtime for cache key
|
|
id: runtime
|
|
run: echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
|
|
|
|
# Each shard has its own prefix so concurrent jobs never race to save the
|
|
# same immutable cache key. Turbo also includes the passthrough `--shard`
|
|
# argument in the task hash, while dependency sources still flow through
|
|
# turbo.json's hash-only `^cache-inputs` edge.
|
|
- name: Restore turbo cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: .turbo/cache
|
|
key: turbo-views-test-${{ matrix.index }}-of-${{ matrix.total }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-${{ github.sha }}
|
|
restore-keys: |
|
|
turbo-views-test-${{ matrix.index }}-of-${{ matrix.total }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-
|
|
|
|
- name: Test views shard ${{ matrix.index }}/${{ matrix.total }}
|
|
run: pnpm exec turbo test --filter='@multica/views' -- --shard=${{ matrix.index }}/${{ matrix.total }}
|
|
|
|
# Preserve the aggregate check names while validating intentional skips.
|
|
frontend:
|
|
needs: [changes, frontend-build, frontend-test, frontend-views-test, frontend-quality]
|
|
if: ${{ !cancelled() }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Check frontend job results
|
|
env:
|
|
NEEDS_JSON: ${{ toJSON(needs) }}
|
|
JOB_SCOPES: '{"frontend-build":"frontend","frontend-test":"frontend","frontend-views-test":"frontend","frontend-quality":"quality_only"}'
|
|
run: node scripts/ci-scope.mjs gate
|
|
|
|
# The Go caches are handled explicitly here rather than through setup-go's
|
|
# `cache: true`. setup-go keys its entry on go.sum alone and shares that key
|
|
# with every Linux job in this workflow, so whichever job finished first,
|
|
# go-vulnerability-scan in under a minute, saved a ~95 MB entry holding
|
|
# little beyond module downloads; this job then "hit" that key on every run
|
|
# and never saved its own. The result was a from-scratch race-instrumented
|
|
# compile of the whole module on every backend run, measured at roughly
|
|
# half of the job's ~8 minutes (MUL-7280).
|
|
#
|
|
# Two entries now. The module cache is keyed by go.sum: immutable, saved on
|
|
# a miss, never grows. The build cache is keyed by commit SHA with a prefix
|
|
# fallback, so each run starts from the newest entry and recompiles only
|
|
# what its change touched. Only pushes to main publish a build-cache entry;
|
|
# PR runs restore and leave, which keeps storage churn at one entry per
|
|
# merge, the same shape as the turbo caches above. The key carries the
|
|
# resolved Go version because `check-latest` floats across patch releases,
|
|
# and the runner image (ImageOS) because cached cgo and race objects are
|
|
# linked against that image's glibc: when ubuntu-latest moves to a new
|
|
# release, an entry built on the old one must not be restored. setup-go
|
|
# keys on ImageOS for the same reason (actions/setup-go#368).
|
|
#
|
|
# A rolling entry only grows on its own: each save is the restored entry
|
|
# plus this commit's objects, and Go trims only what has sat unused for
|
|
# five days, so the entry carried every recent main commit's race objects.
|
|
# It reached 3.5 GB in ten days, and restoring and saving it cost more than
|
|
# the compile it saved (MUL-7547). Before saving, the job therefore drops
|
|
# every entry this run did not use, leaving the next run the objects of the
|
|
# tree it is most likely to build.
|
|
#
|
|
# This job is the only writer of either entry. backend-agent-tests restores
|
|
# both, but it downloads just pkg/agent's slice of the module graph and
|
|
# finishes first, so letting it save would repeat the first-writer-wins
|
|
# problem above: an immutable go.sum key pinned to a partial module cache.
|
|
#
|
|
# Every compile below runs in race mode so build, vet and test share one set
|
|
# of objects. A plain `go build` fed nothing to the -race test binaries and
|
|
# was pure duplicate work; nothing in server/ carries a `race` build tag, so
|
|
# both modes accept exactly the same code.
|
|
#
|
|
# `GOFLAGS=-count=1` because a rolling cache would otherwise let `go test`
|
|
# replay cached results. Migrations are read from disk at run time rather
|
|
# than embedded, so a schema change is invisible to a test binary's hash and
|
|
# a cached "ok" from an untouched package could mask a real failure.
|
|
backend-tests:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
# Job-level filtering skips both services on unrelated changes.
|
|
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg17
|
|
env:
|
|
POSTGRES_DB: multica
|
|
POSTGRES_USER: multica
|
|
POSTGRES_PASSWORD: multica
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U multica -d multica"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 20
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
env:
|
|
DATABASE_URL: postgres://multica:multica@localhost:5432/multica?sslmode=disable
|
|
# Wires up the RedisLocalSkill*_test.go suite. Distinct from REDIS_URL
|
|
# (which would flip the server binary itself onto the Redis-backed
|
|
# realtime relay + request stores); the tests talk to this Redis
|
|
# directly so they run alongside the Postgres-backed suite.
|
|
REDIS_TEST_URL: redis://localhost:6379/1
|
|
GOFLAGS: -count=1
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
id: go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
# Keep the security floor even when the setup-go manifest lags;
|
|
# continue accepting newer patches within Go 1.27.
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache: false
|
|
|
|
- name: Restore Go module cache
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: ~/go/pkg/mod
|
|
key: go-mod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('server/go.sum') }}
|
|
|
|
# ImageOS is a runner environment variable, not a context value, so a
|
|
# shell step spells the build-cache key prefix once for restore and save.
|
|
- name: Resolve Go build cache key
|
|
id: gocache
|
|
run: echo "prefix=go-build-race-${{ runner.os }}-${{ runner.arch }}-$ImageOS-${{ steps.go.outputs.go-version }}-" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Restore Go build cache
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
path: ~/.cache/go-build
|
|
key: ${{ steps.gocache.outputs.prefix }}${{ github.sha }}
|
|
restore-keys: ${{ steps.gocache.outputs.prefix }}
|
|
|
|
# Go refreshes a cache entry's mtime when it uses it, but only once that
|
|
# mtime is an hour old. So after the run, an entry no newer than an hour
|
|
# before the first compile is one this run did not use. The prune step
|
|
# before the save compares against this marker.
|
|
- name: Mark Go build cache epoch
|
|
id: goepoch
|
|
run: touch -d '-1 hour' "$RUNNER_TEMP/go-build-epoch"
|
|
|
|
- name: Build
|
|
run: cd server && go build -race ./...
|
|
|
|
# The `agentintegration` files execute real agent CLIs, so no CI job runs
|
|
# them. Nothing compiled them either, which let them rot silently: a
|
|
# helper renamed in an ordinary test file, or two branches each defining
|
|
# the same helper in one package, breaks the tagged build with every
|
|
# untagged job still green. vet type-checks test files, so this is the
|
|
# cheapest gate that keeps them honest without needing an agent CLI.
|
|
- name: Vet tag-gated agent integration tests
|
|
run: cd server && go vet -race -tags agentintegration ./...
|
|
|
|
- name: Run migrations
|
|
run: cd server && go run ./cmd/migrate up
|
|
|
|
- name: Verify Go test wrapper
|
|
run: bash scripts/test-go.test.sh
|
|
|
|
- name: Test
|
|
# pkg/agent runs on its own runner: backend-agent-tests below.
|
|
run: bash scripts/test-go.sh --race --only regular
|
|
|
|
# Go's own trim (cmd/go/internal/cache) with this run as the window
|
|
# instead of five days: only `-a`/`-d` entries, and an executable entry
|
|
# is a directory, so it goes whole. See the job comment.
|
|
- name: Prune Go build cache to this run's entries
|
|
if: ${{ !cancelled() && github.event_name == 'push' && steps.goepoch.outcome == 'success' }}
|
|
run: |
|
|
du -sh ~/.cache/go-build
|
|
find ~/.cache/go-build -mindepth 2 -maxdepth 2 \( -name '*-a' -o -name '*-d' \) \
|
|
! -newer "$RUNNER_TEMP/go-build-epoch" -exec rm -rf {} +
|
|
du -sh ~/.cache/go-build
|
|
|
|
# Publishes from main only; see the job comment. `!cancelled()` so a red
|
|
# main still refreshes the objects for the next run, and the key step's
|
|
# outcome so a run that never reached Go does not save an empty entry
|
|
# under a half-formed key.
|
|
- name: Save Go build cache
|
|
if: ${{ !cancelled() && github.event_name == 'push' && steps.gocache.outcome == 'success' }}
|
|
uses: actions/cache/save@v6
|
|
with:
|
|
path: ~/.cache/go-build
|
|
key: ${{ steps.gocache.outputs.prefix }}${{ github.sha }}
|
|
|
|
# pkg/agent used to run as the tail of backend-tests: ~2m20s for a single
|
|
# package, throttled to -parallel 2 because its subprocess-backed tests carry
|
|
# hard deadlines and starve when a race build competes for the runner (see
|
|
# scripts/test-go.sh). Those tests are timer-bound rather than CPU-bound, so
|
|
# the throttle stays and the package simply gets its own runner; it reads no
|
|
# database, so no service containers either. Same trade as the frontend
|
|
# split: more runner-minutes, less wall clock. Both Go caches are restored
|
|
# but never saved here: backend-tests owns publishing (see its comment), and
|
|
# this job's only unique output is the pkg/agent test binary, which is cheap
|
|
# to relink.
|
|
backend-agent-tests:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
# Same policy as backend-tests: CI always executes the tests it reports.
|
|
GOFLAGS: -count=1
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
id: go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache: false
|
|
|
|
- name: Restore Go module cache
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
path: ~/go/pkg/mod
|
|
key: go-mod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('server/go.sum') }}
|
|
|
|
# Must match backend-tests' key step exactly.
|
|
- name: Resolve Go build cache key
|
|
id: gocache
|
|
run: echo "prefix=go-build-race-${{ runner.os }}-${{ runner.arch }}-$ImageOS-${{ steps.go.outputs.go-version }}-" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Restore Go build cache
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
path: ~/.cache/go-build
|
|
key: ${{ steps.gocache.outputs.prefix }}${{ github.sha }}
|
|
restore-keys: ${{ steps.gocache.outputs.prefix }}
|
|
|
|
- name: Test agent package
|
|
run: bash scripts/test-go.sh --race --only agent
|
|
|
|
# Keep the live vulnerability database from hiding build and test results.
|
|
# The aggregate backend job below still treats both jobs as one merge gate.
|
|
go-vulnerability-scan:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
# Keep the security floor even when the setup-go manifest lags;
|
|
# continue accepting newer patches within Go 1.27.
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Scan Go vulnerabilities
|
|
working-directory: server
|
|
run: go tool govulncheck ./...
|
|
|
|
# Preserve the existing required-check name while allowing tests, generated
|
|
# SQL verification, and the vulnerability scan to run independently.
|
|
backend:
|
|
needs: [changes, backend-tests, backend-agent-tests, sqlc-check, go-vulnerability-scan, macos-runtime, windows-execenv, script-checks, installer]
|
|
if: ${{ !cancelled() }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Check backend job results
|
|
env:
|
|
NEEDS_JSON: ${{ toJSON(needs) }}
|
|
JOB_SCOPES: '{"backend-tests":"backend","backend-agent-tests":"backend","sqlc-check":"sqlc","go-vulnerability-scan":"backend","macos-runtime":"full","windows-execenv":"runtime","script-checks":"scripts","installer":"installer"}'
|
|
run: node scripts/ci-scope.mjs gate
|
|
|
|
windows-execenv:
|
|
# The environment-preparation deadline owns a process tree, not just a Go
|
|
# process. This Windows runtime test verifies Job Object cancellation kills
|
|
# a delayed descendant before an immediate retry can reuse the same root.
|
|
# The runtime scope includes daemon/agent sources and their Go dependencies.
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.runtime == 'true' }}
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
# Keep the security floor even when the setup-go manifest lags;
|
|
# continue accepting newer patches within Go 1.27.
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Test Windows WeCom media streaming and plaintext cleanup
|
|
working-directory: server
|
|
# These tests need no database. Windows must prove private file access,
|
|
# streaming without buffered fallback, and cleanup after process exit.
|
|
run: go test ./internal/integrations/wecom -v -run '^(TestStreamingDecrypt|TestMediaTempFile|TestPeekFile|TestIngest|TestTheStreamingPath)' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows execution-environment isolation
|
|
working-directory: server
|
|
# Keep this job scoped to the runtime regression it exists to prove.
|
|
# The package's legacy OpenClaw HOME tests are not Windows-safe and are
|
|
# outside this PR; the normal backend job still runs the full package.
|
|
run: go test ./internal/daemon/execenv -run '^TestPrepareIsolated_WindowsKillsDescendantBeforeRetry$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows daemon local-skill discovery
|
|
working-directory: server
|
|
# These fixtures must redirect both the Windows user profile and the
|
|
# platform-native Hermes home; otherwise discovery scans the runner's
|
|
# real skills instead of the test directories.
|
|
run: go test ./internal/daemon -v -run '^(TestListRuntimeLocalSkills_HermesFollowsTaskHome|TestLocalSkills_DiscoversACPProviderRoots)$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows Git Bash temp lifecycle
|
|
working-directory: server
|
|
run: |
|
|
$env:MULTICA_TEST_GIT_BASH = "$env:ProgramFiles\Git\usr\bin\bash.exe"
|
|
if (-not (Test-Path -LiteralPath $env:MULTICA_TEST_GIT_BASH)) { throw "Git for Windows is required" }
|
|
go test -tags=gitbashintegration ./internal/daemon -v -run '^(TestWindowsGitBashTaskTempLifecycle|TestTaskMulticaEnvironment.*|TestTaskTempBaseDir)$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows agent launcher argv/stdin handling
|
|
working-directory: server
|
|
# Agent prompts must never reach a Windows launcher through argv: the
|
|
# official cursor-agent.ps1, pi.ps1, and qwen.ps1 launch native children with
|
|
# `$args`, and PowerShell re-serialises them onto the child command
|
|
# line. Under
|
|
# Legacy native argument passing (powershell.exe 5.1, pwsh <= 7.2) a
|
|
# prompt holding embedded quotes is re-tokenised and fragments like
|
|
# `-X` become flags (#5649). Only a real PowerShell host proves this,
|
|
# so it cannot live in the ubuntu backend job. Scoped to the launcher
|
|
# tests, which are windows-tagged and therefore run nowhere else today;
|
|
# the backend job still runs the full package on Linux.
|
|
# -v so a silent skip (no PowerShell host resolved, or a -run pattern
|
|
# that stops matching) is visible in the log instead of passing as "ok".
|
|
run: go test ./pkg/agent -v -run '^(TestCursorExecutePromptSurvivesPowerShellShim|TestPiExecutePromptSurvivesPowerShellShim|TestQwenExecutePromptSurvivesPowerShellShim|TestPlatformCursorInvocation|TestPlatformCopilotInvocation|TestPlatformPiInvocation|TestPlatformQwenInvocation)' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows OpenCode oversized prompt reaches stdin
|
|
working-directory: server
|
|
# #6538: the daemon inlined the whole task prompt as an argv element,
|
|
# so every OpenCode task whose prompt cleared CreateProcess's 32,767
|
|
# character lpCommandLine limit failed to start at all, with Go
|
|
# reporting ERROR_FILENAME_EXCED_RANGE as the misleading "The filename
|
|
# or extension is too long". Only a real CreateProcess enforces that
|
|
# ceiling, so this cannot run in the ubuntu backend job. The test
|
|
# spawns a native .exe directly (a Chocolatey-installed opencode.exe is
|
|
# a real PE binary, not a .cmd shim) with an oversized prompt and pins
|
|
# that the process starts, argv stays free of the prompt, and the full
|
|
# payload arrives on stdin.
|
|
# -v so a silent skip or a -run pattern that stops matching is visible
|
|
# in the log instead of passing as "ok".
|
|
run: go test ./pkg/agent -v -run '^TestOpencodeExecuteOversizedPromptStartsOnWindows$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows agent process-tree ownership
|
|
working-directory: server
|
|
# A Job Object is the only way to prove whole-tree termination on
|
|
# Windows, and only a real Windows runner can exercise it: that a
|
|
# grandchild dies with the tree that owns it, that an unowned process
|
|
# still reports cleanup as unconfirmed, and that a descendant holding
|
|
# inherited stdout neither keeps Result blocked nor survives cleanup.
|
|
# -v makes RUN/PASS evidence explicit in CI logs.
|
|
run: go test ./pkg/agent -v -run '^(TestStartOwnedProcessTreeCapturesImmediateDescendants|TestStartOwnedProcessTreeLeavesNoSuspendedChild|TestWaitProcessGroupGoneWithoutOwnershipReportsUnconfirmed|TestCodexInitializeRetrySupportedWithOwnedProcessTree|TestCodexWindowsDescendantsDieWithTheOwnedProcessTree|TestCodeArtsWindowsCancellationTerminatesDescendants)$' -count=1 -timeout=5m
|
|
|
|
- name: Test bounded CLI output collection on Windows
|
|
working-directory: server
|
|
# MUL-5467: RunCollect / RunCollectQuiet own the pipes as well as the
|
|
# process tree on the OpenClaw CLI paths, which outputOwned cannot. A real
|
|
# Windows host proves its Job Object owns and
|
|
# terminates descendants, a CLI that prints its answer and then refuses
|
|
# to exit still yields that answer, a response still streaming at the
|
|
# deadline is NOT reported as success, and no collector goroutine
|
|
# outlives the call. Unix uses process groups, so it cannot exercise this
|
|
# platform lifecycle.
|
|
# -v so a skip (a -run pattern that stops matching) is visible in the log
|
|
# instead of passing as "ok".
|
|
run: go test ./pkg/agent -v -run '^TestWindowsRunCollect' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows Pi session lock leaves the transcript readable
|
|
working-directory: server
|
|
# #7840: the session lock claimed the transcript itself, and Windows
|
|
# byte-range locks are mandatory. Pi's own read of the file we hand it
|
|
# through --session failed with EBUSY, so every Pi/omp task on Windows
|
|
# died ~0.5s after launch. Unix flock is advisory and never reproduces
|
|
# it, which is exactly how the regression shipped; the tests are
|
|
# windows-tagged and therefore run nowhere else today.
|
|
# -v so a skip (a -run pattern that stops matching) is visible in the
|
|
# log instead of passing as "ok".
|
|
run: go test ./pkg/agent -v -run '^TestPiSessionFileLock' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows OpenClaw npm shim interpreter resolution
|
|
working-directory: server
|
|
# #6061: every OpenClaw task failed execenv prep on a Windows host with
|
|
# a bare `exit status 1` and no stderr. A batch shim resolves and runs
|
|
# fine while the `node` it re-execs is unreachable, and npm's real
|
|
# template prefers a co-located node.exe over PATH — none of which can
|
|
# be proven without a real cmd.exe host. These tests pin: the positive
|
|
# control (node on PATH → success), that a missing node surfaces
|
|
# cmd.exe's own stderr (the first run of this job disproved #6061's
|
|
# premise that it does not), that a genuinely silent shim DOES reach the
|
|
# new diagnostic, that a co-located interpreter is credited, that a
|
|
# context timeout is not misdiagnosed as a missing interpreter, and that
|
|
# TEMP/TMP are NOT load-bearing (the originally reported root cause,
|
|
# since retracted upstream).
|
|
# Scoped to the windows-tagged shim tests — the package's legacy
|
|
# OpenClaw HOME tests are not Windows-safe; the backend job still runs
|
|
# the full package plus the cross-platform half on Linux.
|
|
# -v so a skip (no node on the runner) is visible instead of passing
|
|
# silently as "ok".
|
|
run: go test ./internal/daemon/execenv -v -run '^TestWindowsOpenclawShim' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows workdir containment guard in the CLI
|
|
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
working-directory: server
|
|
# The CLI's --content-file / --attachment guard decides whether a path is
|
|
# inside the task workdir by canonicalizing both sides. Two of the things
|
|
# that decision rests on are Windows-only and covered nowhere else: a
|
|
# candidate on another volume, where filepath.Rel returns an error rather
|
|
# than a "..", so the guard has to read that as "outside" instead of
|
|
# surfacing `Rel: can't make Z:\... relative to C:\...` as a resolve
|
|
# failure; and a directory junction — the containment-relevant link shape
|
|
# that needs no elevation, so the one real hosts have — which resolves to
|
|
# itself rather than to its target under this module's go directive. The
|
|
# junction test pins that gap deliberately, records the observations it
|
|
# rests on, and fails with instructions if the platform ever starts
|
|
# resolving them.
|
|
# cmd/multica has no Windows job otherwise, so these windows-tagged
|
|
# tests run nowhere else. It matters twice over on a non-admin host,
|
|
# where the package's cross-platform cases skip for want of the symlink
|
|
# privilege; the ubuntu backend job covers those shapes instead.
|
|
# -v so a silent skip (or a -run pattern that stops matching) is visible
|
|
# in the log instead of passing as "ok".
|
|
run: go test ./cmd/multica -v -run '^TestFileWithinWorkingDirWindows' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows link-target classification in internal/util
|
|
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
working-directory: server
|
|
# The guard's Windows correctness rests on internal/util's link-target
|
|
# grammar (classifyTarget), whose branch ordering is load-bearing:
|
|
# VolumeName is non-empty for drive-absolute paths too, so asking it
|
|
# before IsAbs misreads a junction's absolute target as drive-relative
|
|
# and resolves it against the working directory — exactly how a
|
|
# junction escape read as inside the workdir for one CI run. The
|
|
# fixture-free table pins every branch without needing a filesystem,
|
|
# but windows-tagged util tests run nowhere else. Scoped to the table
|
|
# by name; the package's symlinked-workdir cases belong to the ubuntu
|
|
# backend job, which runs the whole package.
|
|
# -v so a silent skip or a pattern that stops matching is visible.
|
|
run: go test ./internal/util -v -run '^TestClassifyTarget' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows isolated repo checkout is committable
|
|
working-directory: server
|
|
# #6449: on Windows the daemon now hands Codex tasks a checkout whose
|
|
# .git lives inside the task workdir, because a linked worktree's
|
|
# external gitdir stays read-only under the native sandbox and breaks
|
|
# `git add` / `git commit` at the end of a task. Two of the guarantees
|
|
# are claims about Windows itself and cannot be made on ubuntu: that
|
|
# Git puts the gitdir inside the task directory, and that the clone's
|
|
# objects are private copies rather than NTFS hard links sharing one
|
|
# file and one security descriptor with the daemon-owned cache. The
|
|
# cross-volume test covers what a hard link cannot express at all.
|
|
# Scoped to the two windows-tagged tests by exact name. A prefix match
|
|
# also caught the package's cross-platform isolated-checkout test,
|
|
# which cannot run here: repocache derives a cache directory name from
|
|
# the full source repo path, so a t.TempDir() path that already embeds
|
|
# a long test name doubles and blows past MAX_PATH. That test belongs
|
|
# to the ubuntu backend job, which runs the whole package.
|
|
# -v so a skip (single-volume runner) is visible instead of passing
|
|
# silently as "ok".
|
|
run: go test ./internal/daemon/repocache -v -run '^(TestIsolatedCheckoutIsCommittableOnWindows|TestIsolatedCheckoutAcrossVolumesOnWindows)$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows directory-junction link safety
|
|
working-directory: server
|
|
# MUL-6000: the per-task codex-home links the user's real skills into
|
|
# the task directory instead of copying them. On Windows that link is a
|
|
# directory junction whenever os.Symlink is denied (no Developer Mode),
|
|
# and a junction is the one link shape a ModeSymlink check misses:
|
|
# since Go 1.23 os.Lstat reports it as ModeDir|ModeIrregular with no
|
|
# ModeSymlink bit, while its DirEntry still answers IsDir() == true, so
|
|
# filepath.WalkDir descends into the target. Two claims about Windows
|
|
# itself cannot be made on ubuntu: that the per-task skills wipe
|
|
# (os.RemoveAll) drops the junction rather than the user's files, and
|
|
# that the GC's artifact sweep and size accounting refuse to walk
|
|
# through one. The tests call mklink /J directly so the junction shape
|
|
# is exercised even on a runner where symlinks are permitted.
|
|
# -v so a skip is visible instead of passing silently as "ok".
|
|
run: |
|
|
go test ./internal/daemon/execenv -v -run '^(TestSeedUserCodexSkills|TestHydrateCodexSkills)' -count=1 -timeout=5m
|
|
go test ./internal/daemon -v -run '^(TestCleanTaskArtifacts_DoesNotFollowDirectoryJunction|TestTaskSize_DoesNotCountDirectoryJunction)$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows task temp dir sweep survives a sharing violation
|
|
working-directory: server
|
|
# The GC's task temp sweep has to cope with a file it cannot delete: a
|
|
# leftover child process holding one open is what left 174 directories
|
|
# behind in #7364. Only a real Windows filesystem produces that sharing
|
|
# violation — on unix an open file unlinks fine — so this is the only
|
|
# place we can prove the two things that matter: the failed cleanup
|
|
# keeps .task_lock (without it the directory reads as a pre-lock
|
|
# leftover and stops being reclaimable on liveness), and the next cycle
|
|
# removes the directory once the handle closes.
|
|
# -v so a skip is visible instead of passing silently as "ok".
|
|
run: go test ./internal/daemon/execenv -v -run '^TestPruneTaskTempDirsSurvivesRealSharingViolation$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows agent executable junction resolution
|
|
working-directory: server
|
|
# The standalone Codex installer exposes bin as a directory junction.
|
|
# filepath.EvalSymlinks cannot traverse that reparse-point shape, so
|
|
# only a real Windows filesystem proves both PATH discovery and an
|
|
# explicitly configured path reach the release executable. The same job
|
|
# covers the npm shape, where the entry point is a `.cmd` shim that only
|
|
# the command interpreter can run.
|
|
run: go test ./internal/daemon -v -run '^(TestCanonicalExecutablePath|TestTrimExtendedLengthPrefix|TestResolveAgentExecutablePathKeeps|TestResolveAgentExecutablePath_ProfileOverride|TestResolveAgentEntry(FollowsRetargetedInstallerJunction|CanonicalizesRediscoveredJunction|ForLaunchKeepsCmdShimLaunchable|ForLaunchRejectsUnverifiedInitialJunctionTarget|ForLaunchRejectsRediscoveredJunctionWhenFinalPathResolutionFails|DoesNotSharePreRetargetSingleflightResult|ForLaunchFailsWhenJunctionKeepsRetargeting)|TestHandleTaskReportsWindowsCodexProcessStartFailure)' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows task workdirs under a junctioned workspaces root
|
|
working-directory: server
|
|
# #8946: a workspaces root moved to another drive and left behind as a
|
|
# directory junction puts a junction in the middle of every task path.
|
|
# filepath.EvalSymlinks fails with ENOTDIR there, so every repo checkout
|
|
# was refused as "not owned by the active task" and every follow-up
|
|
# silently lost its workdir. The strict resolver also has to walk
|
|
# extended-length (`\\?\`) spellings instead of comparing them as
|
|
# strings, and refuse volume-GUID paths it cannot walk. Only a real
|
|
# Windows filesystem has junctions; the ubuntu backend job covers the
|
|
# symlink shapes.
|
|
# -v so a skip or a pattern that stops matching is visible.
|
|
run: |
|
|
go test ./internal/util -v -run '^(TestResolveSymlinksFollowsJunctions|TestResolveSymlinksExtendedLengthPaths)$' -count=1 -timeout=5m
|
|
go test ./internal/daemon -v -run '^(TestAuthorizeRepoCheckoutWorkDirThroughJunctionedRoot|TestAuthorizeRepoCheckoutWorkDirFollowsJunctionsInsideTheWorkdir|TestAuthorizeRepoCheckoutWorkDirExtendedLengthSpelling|TestShouldReusePriorWorkdirThroughJunctionedRoot)$' -count=1 -timeout=5m
|
|
|
|
- name: Build Windows CLI helper entrypoint
|
|
working-directory: server
|
|
run: go build ./cmd/multica
|
|
|
|
- name: Test Windows Cursor background ownership
|
|
working-directory: server
|
|
# Cursor's background shell is captured through a second Job Object, and
|
|
# TestCaptureCursorBackgroundProcessRejectsForeignJob is windows-tagged,
|
|
# so it builds nowhere else. This used to be its own windows-latest job
|
|
# on the same runtime scope as this one; same runner, same package, same
|
|
# concern, so it is a step here instead of a second runner per merge.
|
|
# Last on purpose: absorbing another job's steps means this job's own
|
|
# failure modes now truncate that job's coverage too, and Cursor
|
|
# ownership is the newest arrival, not the load-bearing one. The ubuntu
|
|
# backend job still covers the untagged and Unix arms.
|
|
run: go test -race ./pkg/agent -v -run 'TestCursorBackground|TestCaptureCursorBackground' -count=1 -timeout=5m
|
|
|
|
# The only macOS runner here, and the only place cursor_background_process_darwin.go
|
|
# is ever built: it signals a process group through XNU's per-process unique
|
|
# identity so a recycled PID cannot redirect a kill, and the two tests pinning
|
|
# that are `//go:build darwin`. Windows keeps its arm as a step in
|
|
# windows-execenv, but macOS has no sibling job to join, and this covers one
|
|
# integration's background shell rather than the shared launch path every
|
|
# agent uses. So the daily full run carries it instead of charging every
|
|
# backend merge for a macOS runner; `full` also means the stress counts below
|
|
# are now the only finalization pass, replacing the single-count merge variant.
|
|
macos-runtime:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.full == 'true' }}
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "~1.27.3"
|
|
check-latest: true
|
|
cache-dependency-path: server/go.sum
|
|
- name: Verify Cursor background lifecycle and watchdog races
|
|
shell: bash
|
|
working-directory: server
|
|
# Explicit, verbose selection prevents the Unix regression from silently
|
|
# disappearing behind a build tag.
|
|
run: |
|
|
go test -race ./pkg/agent -v -run 'TestCursorBackground|TestCaptureCursorBackground' -count=1
|
|
go test -race ./internal/daemon -v -run 'Test.*Background.*Watchdog|Test.*IdleWatchdog' -count=1
|
|
|
|
- name: Stress macOS finalization
|
|
shell: bash
|
|
working-directory: server
|
|
run: |
|
|
go test ./pkg/agent -run '^TestCursorBackgroundLifecycle/finish$' -count=5
|
|
go test -race ./pkg/agent -run '^TestCursorBackgroundLifecycle/finish$' -count=5
|
|
|
|
- name: Verify cgo-free macOS ownership
|
|
shell: bash
|
|
working-directory: server
|
|
run: CGO_ENABLED=0 go test ./pkg/agent -run '^TestCaptureCursorBackground(KernelRejectsStaleIdentity|DetachedSession|LateDescendant)$' -count=1
|
|
|
|
image-budget:
|
|
# Soft gate against the 21.7MB of raw PNG/JPG that MUL-6352 cleared out:
|
|
# a bitmap added or grown past 300KB fails until the PR description says
|
|
# why. Pull requests only — a push to main has no description to read,
|
|
# and the branch it came from was already checked.
|
|
needs: changes
|
|
if: ${{ github.event_name == 'pull_request' && needs.changes.outputs.images == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Fetch comparison base
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
run: git fetch --no-tags --depth=1 origin "$BASE_SHA"
|
|
|
|
- name: Check image budget
|
|
env:
|
|
PR_BODY: ${{ github.event.pull_request.body }}
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
run: node scripts/check-image-budget.mjs --base "$BASE_SHA"
|
|
|
|
installer:
|
|
# Stub-driven shell tests for scripts/install.sh and scripts/install.ps1.
|
|
# Kept off the heavy backend job so installer regressions surface
|
|
# independently, and exercised on macOS too because the installer targets
|
|
# macOS/Homebrew and `tar` / `sed` / `mktemp` differ between BSD and GNU
|
|
# userlands. Windows runs the PowerShell installer's own suite: the two
|
|
# installers share one port contract, and neither is covered by the
|
|
# frontend job.
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.installer == 'true' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Test shell installers
|
|
if: runner.os != 'Windows'
|
|
run: bash scripts/install.test.sh
|
|
|
|
- name: Test installer with Windows PowerShell 5.1
|
|
if: runner.os == 'Windows'
|
|
shell: powershell
|
|
run: ./scripts/install.ps1.test.ps1
|
|
|
|
- name: Test installer with PowerShell 7
|
|
if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
run: ./scripts/install.ps1.test.ps1
|
|
|
|
script-checks:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.scripts == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Test self-host env derivation
|
|
run: bash scripts/selfhost-config.test.sh
|
|
|
|
- name: Test check script
|
|
run: bash scripts/check.test.sh
|
|
|
|
- name: Test worktree database cleanup
|
|
run: bash scripts/worktree-db.test.sh
|
|
|
|
- name: Test development environment registry
|
|
run: bash scripts/dev-env.test.sh
|
|
|
|
- name: Test UI performance comparison runner
|
|
run: bash scripts/perf-compare.test.sh
|
|
|
|
- name: Verify reserved-slugs.ts is up to date
|
|
# Re-runs the generator and fails on any drift from the
|
|
# checked-in TypeScript output. The Go side embeds the JSON
|
|
# source directly, so a passing diff here proves both sides
|
|
# share one source of truth.
|
|
run: |
|
|
node scripts/generate-reserved-slugs.mjs
|
|
git diff --exit-code -- packages/core/paths/reserved-slugs.ts
|
|
|
|
- name: Verify attachment text-preview whitelist is up to date
|
|
# The Go handler embeds the JSON source directly while the shared
|
|
# web/desktop view consumes the checked-in generated TypeScript file.
|
|
run: |
|
|
node scripts/generate-attachment-text-preview.mjs
|
|
git diff --exit-code -- packages/views/editor/utils/text-preview-whitelist.generated.ts
|
|
|
|
- name: Setup Helm
|
|
uses: azure/setup-helm@v4
|
|
|
|
- name: Test Helm chart
|
|
run: bash scripts/helm-config.test.sh
|
|
|
|
- name: Test backend entrypoint signals
|
|
run: bash scripts/entrypoint.test.sh
|
|
|
|
- name: Test build output naming
|
|
run: bash scripts/makefile-build.test.sh
|
|
|
|
frontend-quality:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.quality_only == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: pnpm/action-setup@v4
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Check frontend quality
|
|
uses: ./.github/actions/frontend-quality
|