Skip to content

fix(web): a release works on Cloudflare Pages — the terminal opens (node_modules is served), the sign-in page keeps its headers - #111

Merged
ndemianc merged 2 commits into
developfrom
fix/web-cloudflare-pages
Oct 19, 2026
Merged

ndemianc merged 2 commits into
developfrom
fix/web-cloudflare-pages

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

What

On the first deploy to Cloudflare Pages (app.levelcode.ai), the terminal did not open. The browser console showed:

  • Refused to execute script from …/static/node_modules/@xterm/xterm/lib/xterm.js because its MIME type ('text/html') is not executable (and the same for vscode-textmate and vscode-oniguruma);
  • an error for every lookup of extensions/levelcode-themes/package.nls.json.

The cause. wrangler pages deploy never uploads a folder named node_modules: **/node_modules is in its ignore list, in packages/wrangler/src/pages/validate.ts, and no option keeps it. The editor's web client loads its terminal library, its syntax colouring and more from static/node_modules. Pages answers an address it has no file for with index.html and status 200, so each of those scripts came back as the page.

Commits

  1. test(web): serve.mjs --dist <release> --pages answers the way Pages does (web/lib/pages.mjs):

    • only what wrangler uploads is served;
    • _redirects rules apply first;
    • .html addresses are redirected to the address without it;
    • an address with no file gets the page, with 200.

    e2e.mjs, e2e-look.mjs and e2e-signin-tab.mjs take --pages. e2e.mjs checks that a terminal opens and that no script, JSON or WebAssembly response was HTML. Against the live release (rebuilt from the same client: same build id, 5bfb4df6caa1) it fails both, 30/32, as production did.

  2. fix(web):

    • node_modules: the build stages static/node_modules again as static/node-modules, and writes _redirects with a single rewrite rule, /_/:id/static/node_modules/* /_/:id/static/node-modules/:splat 200. Other hosts ignore _redirects and serve node_modules directly. The build refuses a node_modules folder anywhere else in a release.
    • /callback: Pages serves callback.html at /callback, and matches _headers rules by the address requested. Every page's header rule is now also written without .html, so the sign-in page keeps its strict policy and no-store.
    • levelcode-themes: it is no longer staged a second time when the client already has it baked in. That stops the translation-lookup errors.
    • docs/WEB.md: a new section, "On Cloudflare Pages".

Verified

With the client the live release was built from:

Check Result
e2e.mjs --pages 32/32
e2e-look.mjs --pages 26/26
e2e-signin-tab.mjs --pages 20/20, the sign-in page reached through Pages' .html redirect
e2e.mjs, without --pages 32/32
Unit gate, Node 24 and Node 18 127 files pass

The release is 1,840 uploaded files, of Pages' 20,000 (+959 files, +35 MB for the copy). The largest file is still 24.6 MiB, under the 25 MiB limit.

Not run

On Cloudflare itself: everything above runs against the local emulation of Pages.

Left as it is

The baked levelcode-themes still declares the desktop's default theme. In practice the browser edition's themes win, because the editor follows the system's light or dark setting (e2e-look confirms it). They would lose only if a user turned that off.

🤖 Generated with Claude Code

…e sees a terminal and no script answered with HTML

The first deploy to Cloudflare Pages (app.levelcode.ai, 2026-10-19) went live without its terminal:
the client loads xterm, vscode-textmate and vscode-oniguruma from static/node_modules, wrangler
never uploads a folder by that name, and Pages answered each of those scripts with index.html
(200, text/html), so the browser refused to run them. Every check had passed against serve.mjs,
a plain file server.

web/lib/pages.mjs is what Pages does differently, and serve.mjs --dist <release> --pages answers
that way: only what `wrangler pages deploy` uploads (its IGNORE_LIST: **/node_modules, **/.git,
**/.DS_Store, its own files at the root; symbolic links skipped), _redirects first (200 rewrites,
3xx redirects, :name and * placeholders, first match wins), .html redirected to the address
without it and served there, and index.html with 200 for an address that has no file. e2e.mjs,
e2e-look.mjs and e2e-signin-tab.mjs pass --pages through.

e2e.mjs gains two checks: in a release, LevelCode Shell: New Terminal opens a terminal (its
library loaded); and no script, JSON or WebAssembly response was HTML (cdp.mjs now keeps each
response's content type). Against the release that is live — same build id, 5bfb4df6caa1,
rebuilt from the same client — `e2e.mjs --pages` fails both, as production did (30/32). The fix
is the next commit.

pages.mjs also holds the release's answer to the folder (NODE_MODULES_COPY, toRedirectsFile),
which the next commit's build writes; pages.test.js runs that rule against every kind of path.
…les is served, the sign-in page keeps its headers

Three things a release did on Pages that it does not do on a plain file server (the previous
commit shows them):

- node_modules. The build stages static/node_modules a second time as static/node-modules, and
  writes a _redirects with one rule, /_/:id/static/node_modules/* -> /_/:id/static/node-modules/:splat
  (200, a rewrite), so Pages serves the client's files at the address it asks for, for this build
  and for a previous one kept online. Other hosts serve node_modules and ignore _redirects. The
  build refuses a node_modules folder anywhere else in a release. (+959 files, +35 MB: 1,840
  files are uploaded, of Pages' 20,000.)
- /callback. Pages serves callback.html at /callback and matches _headers by the address asked
  for: the sign-in page went without its default-src 'none' policy and no-store there. Every
  page's rule is now also written without `.html` (index.html's is `/`, which has its own).
- levelcode-themes. A client built from a checkout that has LevelCode's extensions bakes it in;
  the build still staged a second copy (without a package.nls.json), which the workbench skipped
  with an error per translation lookup. It is staged only when the client does not have it.

Checked with the client the live release was built from: e2e.mjs --pages 32/32 (the terminal
opens; no script, JSON or WebAssembly answered with HTML), e2e-look.mjs --pages 26/26 (the web
themes are in use: the baked theme's default is outranked by following the system),
e2e-signin-tab.mjs --pages 20/20 (the sign-in page reached through Pages' .html redirect), e2e.mjs
without --pages 32/32. Unit gate 127 files on Node 24 and Node 18. Not run on Cloudflare itself.
@ndemianc
ndemianc marked this pull request as ready for review October 19, 2026 05:33
Copilot AI balanced review requested due to automatic review settings October 19, 2026 05:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified in the focused build, routing, header, and regression-test changes.

No open issues 🎉

What changed in this PR

Fixes LevelCode web releases on Cloudflare Pages so terminal dependencies load and the sign-in callback retains its security headers.

Changes:

  • Copies client dependencies to an uploadable path and adds a rewrite rule.
  • Preserves extensionless page headers and avoids staging duplicate themes.
  • Adds Pages emulation and regression checks.
File Description
web/​test/​unit/​pages.test.js Tests upload filtering, rewrites, and page routing.
web/​test/​unit/​headers.test.js Checks headers for /callback.
web/​test/​e2e.mjs Checks terminal loading and unexpected HTML responses.
web/​test/​e2e-signin-tab.mjs Enables Pages-mode sign-in checks.
web/​test/​e2e-look.mjs Enables Pages-mode appearance checks.
web/​test/​cdp.mjs Records response MIME types.
web/​serve.mjs Adds Pages-compatible serving mode.
web/​lib/​pages.mjs Defines Pages routing and upload behavior.
web/​lib/​headers.mjs Adds extensionless page header rules.
scripts/​build-web.mjs Stages dependencies, writes rewrites, and avoids duplicate themes.
docs/​WEB.md Documents Pages deployment and verification.

Review effort: Balanced

@ndemianc
ndemianc merged commit 08a2cc8 into develop Oct 19, 2026
3 checks passed
@ndemianc
ndemianc deleted the fix/web-cloudflare-pages branch October 19, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants