Skip to content

Release build: host the UI on GitHub Pages, one folder per release - #76

Merged
samuelstroschein merged 3 commits into
devfrom
feat/hosted-release-pipeline
Oct 13, 2026
Merged

samuelstroschein merged 3 commits into
devfrom
feat/hosted-release-pipeline

Conversation

@samuelstroschein

@samuelstroschein samuelstroschein commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

What

The new Parrot (SDK 4 + Inlang editor) needs its UI hosted: it uses module workers and WebAssembly, so the plugin sandbox loads it from a URL whose origin is in networkAccess.allowedDomains. Nothing was hosted (opral.github.io/parrot-figma/ returned 404, parrot-global.github.io is dead). This PR hosts every release on GitHub Pages of this repository, one folder per release, and never on a merge.

  • Release build (.github/workflows/publish.yml, manual): the free-form ui_url input is replaced by version. The workflow derives https://opral.github.io/parrot-figma/releases/<version>/, builds the sandbox with that VITE_CUSTOM_URL, builds release/manifest.json (still fails when the origin is not in allowedDomains), uploads the parrot-figma-plugin artifact as before and publishes dist/ui to releases/<version>/ on the gh-pages branch.
    • version is validated (^[0-9A-Za-z]([0-9A-Za-z._-]{0,62}[0-9A-Za-z])?$: no ./.., no leading dash or trailing dot, no slash) before anything uses it and only ever reaches scripts through environment variables. An existing version (case-insensitive) is refused, early in the build job and again under the lock in the publish job. Versions without a - suffix can only be built from dev (other branches build pre-releases such as 1.2.3-beta1).
    • Three jobs: build has contents: read, runs all repository and dependency code and checks the release files (entry page and script, a .wasm, the UI URL baked into dist/plugin.js, no symlinks) before anything is published; publish is the only job with contents: write, runs no repository code (it downloads the artifact, copies it into releases/<version>/, checks that nothing else changed, commits as github-actions[bot] and pushes without force; serialized by the release-pages concurrency group; creates the gh-pages branch with a README and .nojekyll if missing); verify waits until Pages serves the release and checks status, Content-Type and Access-Control-Allow-Origin of the page, its entry script and the WebAssembly file, and prints the URL, the checks and the exact manual Figma publish steps in the job summary. It is its own job so it can be re-run alone.
    • A merge into dev publishes nothing (no push trigger, CI does not touch gh-pages).
  • Manifest (manifest.json, kept in sync with manifest.figma.ts by a new test): https://opral.github.io added; https://parrot-global.github.io and https://api.parrot.global removed; reasoning rewritten (hosted UI on GitHub Pages, anonymous usage counts via inlang.com/relay, s3-alpha.figma.com = Figma profile pictures). Plugin id unchanged.
    • parrot-global.github.io: only the checkVersion() in src/ui/ui.tsx, whose call is commented out. api.parrot.global: only ParrotApi, unreachable (SYNC_CREDITS = false, activateLicense has no caller, payment feature flag is off). Both were absent from the network log of the live run.
    • s3-alpha.figma.com is kept: it serves figma.currentUser.photoUrl, whose only consumer is the (currently hidden) Users view. It can go when that view is removed.
  • docs/releasing.md: the release flow (version, releases/<version>/ on gh-pages, how to test a release, manual publish steps in the Figma desktop app). README untouched.
  • QA harness (.qa/): tools find the hosted UI iframe by PARROT_UI_ORIGIN, QA_FRONT=<file id> refuses real input unless that file's tab is in front, new hosted-probe.mjs (console, CSP/CORS/network of the hosted UI) and net-all.mjs (requests of a file tab), documented in .qa/README.md.

Repo setting changed (done with gh api, not part of the diff): Pages source is now branch gh-pages / root (it was build type workflow, source dev). Publishing to the Figma Community stays manual.

Verified live (Release build runs from this branch, real Figma Desktop)

Released files (release/manifest.json + dist/plugin.js of the artifact) run in Figma with the UI from https://opral.github.io/parrot-figma/releases/0.0.0-test1/:

  • the UI iframe origin is https://opral.github.io (CDP target + location.origin), its module worker assets/entry.browser-*.js and both WASM files load from there; no console errors, CSP, CORS or failed requests (a fresh UI load makes only requests to opral.github.io and inlang.com/relay)
  • import i18next JSON and Android XML, generate keys for a frame, link a text to an imported message, switch the frame to German and back (bold and italic ranges identical afterwards), apply translations to the canvas, export i18next JSON, Android XML and next-intl JSON (content checked), machine translation dialog opens (the "More info" button was not clicked on purpose), plugin close + run again, and run again after reloading the file tab
  • production telemetry only goes to https://inlang.com/relay/i/v0/e/ (counts and hashed ids, no texts or keys); nothing else leaves
  • negative runs: an existing version (also a case variant), ../evil and a non-pre-release version from this branch fail in the build job before anything is built; a final run with the three-job workflow published and verified

Pages serves application/wasm, application/javascript; charset=utf-8 and Access-Control-Allow-Origin: *; a push of the workflow triggers the Pages build (served in under a minute).

Notes

  • Each release is about 96 MB (23 MB compressed; the Lix SDK WASM is 81 MB, GitHub rejects single files over 100 MB) and Pages sites are limited to 1 GB.
  • The test releases 0.0.0-test* were removed from gh-pages again.
  • Suggested follow-up outside this PR: protect gh-pages against force pushes and deletion.

pnpm tsc && pnpm lint && pnpm test && pnpm build pass.

🤖 Generated with Claude Code

samuelstroschein and others added 3 commits October 13, 2026 09:54
The Release build workflow takes a version, publishes dist/ui to
releases/<version>/ on the gh-pages branch (never overwriting a release)
and builds the sandbox against that URL. The manifest allows
opral.github.io and drops the retired parrot-global and api.parrot.global
hosts.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ifact copy

docs/releasing.md describes the release flow (version, releases/<version>/ on
gh-pages, manual Figma publish, how to test a release). The QA harness finds
the hosted UI by PARROT_UI_ORIGIN, can refuse input unless the file tab is
in front (QA_FRONT) and has a probe for console, CSP/CORS and network of the
hosted UI. The publish job refuses symlinks and git metadata in the UI build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…al versions

Review findings: the Pages verification runs in its own job so it can be
re-run alone, the UI URL baked into the sandbox, the entry script and the
WASM file are checked before anything is published, symlinks are refused
in the whole release, versions without a pre-release suffix can only be
released from dev, the existing-release check is case-insensitive in the
build job too, the Pages README is created when missing, jobs have timeouts
and the manifest reasoning says hashed ids instead of anonymous.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@samuelstroschein
samuelstroschein merged commit 6c6b29e into dev Oct 13, 2026
8 of 10 checks passed
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.

1 participant