Skip to content

Commit 3acb36c

Browse files
authored
fix(ci): give the web build a heap ceiling and repair stale typecheck filters (#150)
## Related Issue No filed issue. Three release jobs failed on `main` at `ae4211f1f`, and the failure then walked past two gates that should have caught it. ## Problem **1. `apps/pythinker-web` outgrew Node's default heap on macOS runners.** The workspace file editor pulled Monaco into the production module graph (1 source file importing it before #145, 4 after), and the vite build now needs a little over 2 GB of old space. macOS runners default to a ~2053 MB ceiling, so three jobs died with `FATAL ERROR: ... JavaScript heap out of memory` and exit 134: | Workflow | Job | |---|---| | Desktop Release | `mac` — "Build workspace" | | Release | Native bundle `darwin-x64` | | Release | Native bundle `darwin-arm64` | Linux and Windows runners default higher and stayed green, so `CI` and `Nix Build` never saw it. **2. A half-shipped release passed as a good one.** `publish-native-assets` refused to publish a partial native set, exactly as designed. But a job whose dependency failed reports `skipped`, not `failure`, and `redeploy-cdn` only checked for `failure` — so it deployed 1.0.0 to the CDN while the GitHub release `@pymodel/pythinker-code@1.0.0` held **zero assets**. Every native installer would have been sent to a release that has none. `verify-cdn-release` had no `always()`, so the same skip propagated through the graph and silently disabled the one gate that exists to catch this. Both went quiet in precisely the run that needed them. **3. Three typecheck gates in CI had gone quietly dead.** `pnpm --filter <name>` exits 0 when the filter matches nothing, so a rename turns a gate into a no-op with nothing red to show for it: - `@pymodel/dashboard-server` and `@pymodel/dashboard-web` matched nothing after the packages became `@pymodel/vis-server` / `@pymodel/vis-web`. - `pythinker-code` matched the CLI *directory* (`apps/pythinker-code`), not the VS Code extension, whose package name is `pythinker`. The extension was never typechecked in CI, and the CLI was already covered by the tsgo step above it. ## What changed **The heap ceiling lives in `apps/pythinker-web`'s own `build` script.** That is the one point every consumer shares — CI, Nix, both release workflows, and local builds — and it stays portable: a `NODE_OPTIONS=` prefix would break the Windows desktop job, which runs the same script under `cmd.exe`. Measured on this repo rather than guessed: 2048 MB fails, 2560 MB passes, and the build peaks near 2.2 GB. 4096 leaves headroom without reserving anything, since V8 only grows to what it needs. Rebuilding the committed bundle produced a byte-identical `dist-web` — only the input fingerprint moved, which confirms the change is behaviour-neutral. **`redeploy-cdn` now requires `publish-native-assets` to have succeeded** whenever the release has native artifacts at all, so a release with no assets leaves the CDN on the last installable version instead of advertising one nobody can install. **`verify-cdn-release` now runs on its own merits** and reports a stale CDN rather than disappearing with it. **The CI typecheck steps become one loop that resolves each filter to a real workspace package before running it,** so a stale name fails instead of passing silently. All five targets pass locally, including the VS Code extension, which this turns on for the first time. ## Checklist - [x] I have read the [CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md) document. - [x] I have linked a related issue (external PRs: the issue must have a maintainer's `/approve`). - [x] I have added tests that prove my feature works. — mutation-tested: a bogus filter name exits 1, and 2048 MB reproduces the OOM locally. - [x] Ran `gen-changesets` skill, or this PR needs no changeset. - [x] Ran `gen-docs` skill, or this PR needs no doc update. [skip changeset] — build and CI infrastructure only; the shipped bundle is byte-identical. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved release verification so incomplete native asset releases are detected instead of being silently redeployed. * CDN release checks now run consistently when applicable, helping identify deployment issues earlier. * **Chores** * Improved build reliability for larger web projects. * Strengthened automated type checking across all current packages. * Refreshed web bundle metadata to keep deployed assets synchronized. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ae4211f commit 3acb36c

4 files changed

Lines changed: 38 additions & 16 deletions

File tree

.github/workflows/ci.yml

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,21 @@ jobs:
136136
echo "Typechecking ${config}"
137137
pnpm dlx --package @typescript/native-preview@beta tsgo -p "${config}" --noEmit
138138
done
139-
- name: Typecheck VS Code extension
140-
run: pnpm --filter pythinker-code run typecheck
141-
- name: Typecheck pythinker-web (vue-tsc)
142-
run: pnpm --filter @pymodel/pythinker-web run typecheck
143-
- name: Typecheck dashboard-server
144-
run: pnpm --filter @pymodel/dashboard-server run typecheck
145-
- name: Typecheck dashboard-web
146-
run: pnpm --filter @pymodel/dashboard-web run typecheck
147-
- name: Typecheck desktop
148-
run: pnpm --filter @pymodel/pythinker-desktop run typecheck
139+
# `pnpm --filter <name>` exits 0 when nothing matches, so a renamed
140+
# package silently turns its gate into a no-op. Three of these filters
141+
# had already gone stale that way: `@pymodel/dashboard-server` and
142+
# `@pymodel/dashboard-web` matched nothing after the vis rename, and
143+
# `pythinker-code` matched the CLI directory instead of the VS Code
144+
# extension (`pythinker`), so the extension was never typechecked here.
145+
# Resolving each filter first turns a stale name back into a failure.
146+
- name: Typecheck workspace apps
147+
run: |
148+
set -euo pipefail
149+
for package in pythinker @pymodel/pythinker-web @pymodel/vis-server @pymodel/vis-web @pymodel/pythinker-desktop; do
150+
if [ -z "$(pnpm ls --filter "$package" --depth -1 --parseable)" ]; then
151+
echo "No workspace package matches the filter '${package}'" >&2
152+
exit 1
153+
fi
154+
echo "Typechecking ${package}"
155+
pnpm --filter "$package" run typecheck
156+
done

.github/workflows/release.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,18 @@ jobs:
240240
needs:
241241
- release
242242
- publish-native-assets
243+
# A native release whose zips never reached the GitHub release must not
244+
# reach the CDN either. `publish-native-assets` refuses to publish a
245+
# partial set, and a job whose needs failed reports `skipped`, not
246+
# `failure` — so checking only for failure let a release with zero assets
247+
# through and pointed every native installer at a release that has none.
248+
# Requiring success (only when this release has native artifacts at all)
249+
# leaves the CDN on the last installable version instead.
243250
if: >-
244251
always()
245252
&& needs.release.result == 'success'
246-
&& needs.publish-native-assets.result != 'failure'
247-
&& needs.publish-native-assets.result != 'cancelled'
253+
&& (needs.release.outputs.pythinker_native_release != 'true'
254+
|| needs.publish-native-assets.result == 'success')
248255
&& (needs.release.outputs.packages_published == 'true'
249256
|| startsWith(github.event.head_commit.message, 'ci: release packages'))
250257
runs-on: ubuntu-latest
@@ -308,9 +315,16 @@ jobs:
308315
needs:
309316
- release
310317
- redeploy-cdn
318+
# Without `always()` a skip anywhere upstream skips this job too, and the
319+
# gate that exists to catch a half-shipped release goes quiet in exactly
320+
# the runs that need it. It stays out of `redeploy-cdn`'s result on
321+
# purpose: a CDN that never redeployed is the failure this asserts, so it
322+
# has to run and report it rather than disappear with it.
311323
if: >-
312-
needs.release.outputs.packages_published == 'true'
313-
|| startsWith(github.event.head_commit.message, 'ci: release packages')
324+
always()
325+
&& needs.release.result == 'success'
326+
&& (needs.release.outputs.packages_published == 'true'
327+
|| startsWith(github.event.head_commit.message, 'ci: release packages'))
314328
runs-on: ubuntu-latest
315329
steps:
316330
- name: Checkout
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"sourceHash": "dcb2227d96cf476915942656bc127bb1d7801258b01daa6995ee76e915ff199b",
2+
"sourceHash": "39adcc1ccb551ac03f06fe2152c6a69004701da14fd2535f8b26202cb9d27c14",
33
"sourceFileCount": 389
44
}

apps/pythinker-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "module",
77
"scripts": {
88
"dev": "vite",
9-
"build": "vite build",
9+
"build": "node --max-old-space-size=4096 ./node_modules/vite/bin/vite.js build",
1010
"typecheck": "vue-tsc --noEmit",
1111
"test": "vitest run",
1212
"check:style": "node scripts/check-style.mjs",

0 commit comments

Comments
 (0)