Repository navigation
192 lines (165 loc) · 7.92 KB
/
Copy pathci.yml
File metadata and controls
192 lines (165 loc) · 7.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv and Python 3.14
uses: astral-sh/setup-uv@v5
with:
python-version: "3.14"
enable-cache: true
# `--extra serve` installs OpenEnv, which the `seahaven.openenv` module and
# its tests need. Without it the type check and the transport tests would
# be skipped here and run nowhere. `--extra mcp` cannot join it: the two
# extras are declared as conflicting in `pyproject.toml`, because OpenEnv
# pins the MCP SDK to 1.x and `seahaven.mcp` needs 2.x. The `mcp` job
# below is that extra's environment.
- name: Install the project
run: uv sync --locked --extra serve
# The suites guard the OpenEnv modules with `pytest.importorskip`, so an
# extra that is installed but does not import would skip them and leave
# CI green having tested none of `seahaven serve`, the environment or the
# client. This is the one step that fails instead.
- name: The serve extra imports
run: uv run python -c "import seahaven.openenv"
- name: Format
run: uv run ruff format --check
- name: Lint
run: uv run ruff check
# `ty` resolves imports against the environment it runs in, so neither
# environment can check the whole tree: `seahaven.mcp` does not resolve
# here, and `seahaven.openenv` does not resolve in the `mcp` job. The two
# jobs split the tree between them -- what this run excludes is exactly
# what the other one includes -- and `tests/test_ci_workflow.py` reads
# both lines out of this file and fails when a path falls between them.
- name: Types
run: uv run ty check -c 'src.exclude=["src/seahaven/mcp", "tests/test_mcp_server.py", "tests/test_mcp_process.py"]'
- name: Tests
run: uv run pytest
# The reference world is its own package with its own suite, and its own
# pytest rootdir: a world is tested the way a world's author tests one.
- name: Reference world tests
run: uv run pytest worlds/projecttracker
# The example extension is its own package with its own suite too, and its
# world is a copy of ProjectTracker under its `tests/`. What it proves is
# the extension contract: that a protocol the framework knows nothing about
# rides on the published seams.
- name: Example extension tests
run: uv run pytest extensions/seahaven-xmlrpc
# Seahaven is vendored into other people's products: nothing it installs
# at runtime is copyleft. The gate is named the extra this job synced, and
# reads the base closure plus that extra -- an extra whose licences cannot
# be read is not cleared, it fails. Every declared extra is named by one
# job or another, which `tests/test_licence_check.py` asserts against this
# file.
- name: Dependency licences
run: uv run python scripts/check_licences.py serve
# The second environment. `serve` and `mcp` are conflicting extras and no
# environment can hold both, so the MCP extra gets a job of its own: the same
# framework suite, which skips what needs OpenEnv, and the licence gate for
# the tree this extra ships.
mcp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv and Python 3.14
uses: astral-sh/setup-uv@v5
with:
python-version: "3.14"
enable-cache: true
- name: Install the project
run: uv sync --locked --extra mcp
# Both modules, because they are two different claims. The suites skip on
# `mcp.server.context`, which only the 2.x SDK has, so that is the
# predicate the job has to assert; `seahaven.mcp` is the thing this
# project ships, and what it happens to import is an implementation
# detail rather than a promise. `tests/conftest.py` holds the guard, and
# `tests/test_ci_workflow.py` holds this line to the same module so the
# two cannot drift into a green run that tested none of this.
- name: The mcp extra imports
run: uv run python -c "import mcp.server.context, seahaven.mcp"
# The other half of the type check: the paths the `check` job excludes,
# named as arguments, because only a path argument narrows what `ty`
# walks. They arrive in phases 2 and 3 and `ty` exits 2 on a path that is
# not there yet, so the step checks the ones that exist and says so when
# there are none. Phase 2 then needs no edit here.
- name: Types
run: |
paths=$(ls -d src/seahaven/mcp tests/test_mcp_server.py tests/test_mcp_process.py 2>/dev/null || true)
if [ -z "$paths" ]; then
echo "nothing on the mcp side of the split yet"
else
uv run ty check $paths
fi
- name: Tests
run: uv run pytest
- name: Dependency licences
run: uv run python scripts/check_licences.py mcp
# The web console is a separate toolchain in `ui/`, and what ships is the
# built file vendored at `src/seahaven/openenv/console/index.html`. Without
# this job nothing type-checks the console, nothing runs its browser test, and
# nothing notices when someone edits `ui/src` and forgets to re-vendor the
# build.
console:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ui
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Install
run: npm ci
# `npm run build` is `tsc --noEmit && vite build`, so the types are
# checked here and a type error fails the job before anything is built.
- name: Build
run: npm run build
# The vendored copy is what `seahaven serve` answers `/console` with, and
# it is a build artefact: a commit that changes `ui/src` without rebuilding
# it ships the old page. The build is reproducible, so the copy and a fresh
# build are the same bytes or the copy is stale.
- name: The vendored console is the current build
run: |
cmp dist/index.html ../src/seahaven/openenv/console/index.html || {
echo "::error::src/seahaven/openenv/console/index.html is not the current build of ui/."
echo "Run: cd ui && npm run build && cp dist/index.html ../src/seahaven/openenv/console/index.html"
exit 1
}
# The build inlines its dependencies into the file that ships in the
# wheel, so an npm dependency reaches a user the same way a PyPI one does.
# `scripts/check_licences.py` is the no-copyleft rule for the Python
# closure and cannot see this one.
- name: Bundled licences
run: npm run licences
- name: Install Chromium
run: npx playwright install --with-deps chromium
# Two mock environments, because the console has two modes: a tool
# environment and one whose actions are not tools. The test drives the
# built file in a real browser and fails on any unexpected console error.
- name: Browser test
run: |
node mock/server.mjs --port 8000 &
node mock/server.mjs --port 8001 --plain &
# Wait for both to answer rather than sleeping at them. A mock that
# dies on startup otherwise surfaces as a locator timeout somewhere
# in the middle of the run instead of as "the mock did not start".
for port in 8000 8001; do
for _ in $(seq 50); do
curl -sf "http://127.0.0.1:$port/health" >/dev/null && break
sleep 0.2
done
curl -sf "http://127.0.0.1:$port/health" >/dev/null || {
echo "::error::the mock on $port never came up"
exit 1
}
done
node e2e/smoke.mjs