From 400c1b364e04495d62d09ddf44774356278acf07 Mon Sep 17 00:00:00 2001 From: PAMulligan Date: Sun, 21 Jun 2026 00:46:25 -0400 Subject: [PATCH] feat(gui): release CI, packaged smoke test, and release-please versioning (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the packaging/distribution/documentation sub-issue. - Versioning: release-please now syncs packages/gui/package.json $.version (added as an extra-file in release-please-config.json), so the GUI version tracks the git-tag-driven release and is never hand-bumped. - Release CI: .github/workflows/gui-release.yml builds installers for macOS (.dmg), Windows (.exe), and Linux (.AppImage) on a published release (matrix), smoke-tests each, and uploads them to the release via gh. - Packaged smoke test: tests/smoke/smoke.mjs launches the built Electron app with Playwright's _electron and asserts it reaches the setup view; wired into a new smoke job in gui.yml (Xvfb) so it runs on every GUI PR, and into the release build before upload. Verified locally. - Docs: docs/GUI.md gains a first-run → activated-theme walkthrough and the versioning/release-artifact notes, and corrects the now-shipped "Open project folder" picker description. electron-builder packaging config + README reference shipped earlier; this adds multi-OS artifact builds, the release-flow integration, and the launch smoke test. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/gui-release.yml | 71 ++++++++++++++++++++++++++++++ .github/workflows/gui.yml | 31 +++++++++++++ docs/GUI.md | 36 ++++++++++++--- packages/gui/eslint.config.mjs | 18 ++++++++ packages/gui/package.json | 2 + packages/gui/tests/smoke/smoke.mjs | 33 ++++++++++++++ pnpm-lock.yaml | 3 ++ release-please-config.json | 5 +++ 8 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/gui-release.yml create mode 100644 packages/gui/tests/smoke/smoke.mjs diff --git a/.github/workflows/gui-release.yml b/.github/workflows/gui-release.yml new file mode 100644 index 0000000..6578707 --- /dev/null +++ b/.github/workflows/gui-release.yml @@ -0,0 +1,71 @@ +name: GUI Release Build + +# Builds installable GUI artifacts for macOS / Windows / Linux and attaches them to +# the GitHub Release that release-please publishes. The app version is whatever +# release-please wrote into packages/gui/package.json (an extra-file in +# release-please-config.json) — never hand-bumped. + +on: + release: + types: [published] + workflow_dispatch: # allow a manual dry run (builds, but skips upload) + +permissions: + contents: write + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + artifacts: 'packages/gui/dist/*.dmg' + - os: windows-latest + artifacts: 'packages/gui/dist/*.exe' + - os: ubuntu-latest + artifacts: 'packages/gui/dist/*.AppImage' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack (pnpm) + run: corepack enable + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Electron runtime libraries + Xvfb (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y xvfb + pnpm --filter @flavian/gui exec playwright install-deps chromium + + - name: Build GUI + run: pnpm --filter @flavian/gui build + + - name: Smoke test (app launches and reaches the setup view) + shell: bash + run: | + if [ "$RUNNER_OS" = "Linux" ]; then + xvfb-run --auto-servernum pnpm --filter @flavian/gui smoke + else + pnpm --filter @flavian/gui smoke + fi + + - name: Package installer + # Unsigned (no certs in CI) — see electron-builder.yml signAndEditExecutable. + run: pnpm --filter @flavian/gui exec electron-builder --publish never + + - name: Upload artifacts to the release + if: github.event_name == 'release' + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload "${{ github.event.release.tag_name }}" ${{ matrix.artifacts }} --clobber diff --git a/.github/workflows/gui.yml b/.github/workflows/gui.yml index e8028e9..a717266 100644 --- a/.github/workflows/gui.yml +++ b/.github/workflows/gui.yml @@ -50,3 +50,34 @@ jobs: - name: Run GUI unit tests run: pnpm --filter @flavian/gui test + + smoke: + # Launches the built Electron app headlessly (Xvfb) and asserts it reaches the + # setup view. Separate job because it needs the Electron binary + a display. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack (pnpm) + run: corepack enable + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + # No ELECTRON_SKIP here — the smoke needs the real Electron binary. + run: pnpm install --frozen-lockfile + + - name: Install Electron runtime libraries + Xvfb + run: | + sudo apt-get update + sudo apt-get install -y xvfb + pnpm --filter @flavian/gui exec playwright install-deps chromium + + - name: Build GUI + run: pnpm --filter @flavian/gui build + + - name: Smoke test (app launches and reaches the setup view) + run: xvfb-run --auto-servernum pnpm --filter @flavian/gui smoke diff --git a/docs/GUI.md b/docs/GUI.md index df90ca1..6578643 100644 --- a/docs/GUI.md +++ b/docs/GUI.md @@ -109,16 +109,40 @@ an elevated shell, so it 404s the build — disabling signing sidesteps it entir `signAndEditExecutable: false`, and build on a host with signing privileges (Developer Mode / elevated shell, or CI). -The GUI **operates on a Flavian project directory on disk** — it writes themes, `.env`, -and conversion output there, so those files must stay writable. The app bundle therefore -ships only the GUI itself and does **not** embed the template or its scripts. At startup it -locates the project by walking up from where it runs (so launching from inside a checkout -"just works"); pointing it at an arbitrary folder is the planned **Open project folder…** -follow-up (the `resolveProjectRef`/`locateRepoRoot` seam already supports it). +**Versioning & release artifacts.** The GUI version is written by release-please +(`packages/gui/package.json` is an `extra-files` entry in `release-please-config.json`), so +it tracks the repo's git-tag-driven version and is **never hand-bumped**. When release-please +publishes a GitHub Release, the **GUI Release Build** workflow +(`.github/workflows/gui-release.yml`) builds the macOS / Windows / Linux installers, +smoke-tests each, and attaches them to the release. A packaged-launch smoke test also runs on +every GUI PR (`.github/workflows/gui.yml`, via Xvfb). + +The GUI **operates on a Flavian project directory on disk** — it writes themes, `.env`, and +conversion output there, so those files must stay writable. The app bundle therefore ships +only the GUI itself and does **not** embed the template or its scripts. At startup it locates +the project by walking up from where it runs (launching from inside a checkout "just works"); +the **Open project folder…** action (sidebar "Choose project…" / "Change…") lets you point it +at any checkout, and the choice is remembered between launches. The distribution model: a user installs the app and runs it against a Flavian project on disk, driving setup → Docker → conversion → QA entirely through the UI — no terminal. +## First run & walkthrough + +1. **Install & launch** — run the installer for your OS (or `pnpm gui:dev` from a checkout). +2. **Open a project** — on first launch, click **Choose folder…** and pick your Flavian + checkout (the folder with `wordpress-local.sh`). It's remembered next time. +3. **Prerequisites** — confirm Claude Code, Docker, Git, and Node are detected; fix anything + flagged. +4. **Setup wizard** — scaffold a project (slug, theme starter, options) — runs the same + `apply()`/`resolveDefaults()` as `pnpm run init`. +5. **WordPress** — **Build → Start → Install**, then open the site / wp-admin from the quick + links. +6. **Convert design** — pick Figma / Canva / InDesign, provide the input, launch, and watch + the streamed progress; on success, click **Activate theme**. +7. **Visual QA** — run visual-diff + Lighthouse and review the diffs, scores, and any + design-vs-result comparison. + ## Requirements Running the GUI requires the same tools Flavian itself needs — Claude Code, Docker, Git, diff --git a/packages/gui/eslint.config.mjs b/packages/gui/eslint.config.mjs index 433c410..0fa780e 100644 --- a/packages/gui/eslint.config.mjs +++ b/packages/gui/eslint.config.mjs @@ -9,6 +9,24 @@ export default tseslint.config( { ignores: ['out/**', 'dist/**', 'node_modules/**'] }, js.configs.recommended, ...tseslint.configs.recommended, + { + // Plain JS/MJS scripts (e.g. the smoke test) run on Node — declare its globals. + files: ['**/*.{js,cjs,mjs}'], + languageOptions: { + globals: { + console: 'readonly', + process: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', + Buffer: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + queueMicrotask: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + }, + }, + }, { files: ['**/*.{ts,tsx,mts}'], rules: { diff --git a/packages/gui/package.json b/packages/gui/package.json index 0007db5..6a880c0 100644 --- a/packages/gui/package.json +++ b/packages/gui/package.json @@ -17,6 +17,7 @@ "typecheck": "tsc -p tsconfig.node.json --noEmit && tsc -p tsconfig.web.json --noEmit", "lint": "eslint .", "test": "node --import tsx --test \"tests/**/*.test.mts\"", + "smoke": "node tests/smoke/smoke.mjs", "package": "electron-vite build && electron-builder" }, "dependencies": { @@ -35,6 +36,7 @@ "eslint": "^9.39.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.1.1", + "playwright": "1.60.0", "tsx": "^4.19.0", "typescript": "^5.6.0", "typescript-eslint": "^8.61.1", diff --git a/packages/gui/tests/smoke/smoke.mjs b/packages/gui/tests/smoke/smoke.mjs new file mode 100644 index 0000000..917e35e --- /dev/null +++ b/packages/gui/tests/smoke/smoke.mjs @@ -0,0 +1,33 @@ +// Packaged-build smoke test: launch the built Electron app and assert it boots to +// the shell and reaches a usable view (the Prerequisites/setup view or the project +// gate). Requires `electron-vite build` to have produced out/ first. +// +// Run: pnpm --filter @flavian/gui build && pnpm --filter @flavian/gui smoke +// In headless CI (Linux), wrap with `xvfb-run`. +import { _electron as electron } from 'playwright'; +import { strict as assert } from 'node:assert'; +import { fileURLToPath } from 'node:url'; + +const mainPath = fileURLToPath(new URL('../../out/main/index.js', import.meta.url)); + +const app = await electron.launch({ args: [mainPath] }); +try { + const win = await app.firstWindow(); + await win.waitForLoadState('domcontentloaded'); + + // The shell always renders the "Flavian" brand; content is either the default + // Prerequisites view (when a project is found) or the "Open a Flavian project" gate. + await win.waitForSelector('.brand', { timeout: 30_000 }); + const brand = ((await win.textContent('.brand')) ?? '').trim(); + assert.equal(brand, 'Flavian', 'app shell (brand) did not render'); + + const body = (await win.textContent('body')) ?? ''; + assert.ok( + /Prerequisites|Open a Flavian project/.test(body), + 'expected the setup view (Prerequisites) or the project gate', + ); + + console.log('smoke: GUI launched and reached the setup view'); +} finally { + await app.close(); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c56879c..5a8c1f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: eslint-plugin-react-hooks: specifier: ^7.1.1 version: 7.1.1(eslint@9.39.4(jiti@2.6.1)) + playwright: + specifier: 1.60.0 + version: 1.60.0 tsx: specifier: ^4.19.0 version: 4.22.4 diff --git a/release-please-config.json b/release-please-config.json index 55ae282..3560082 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -14,6 +14,11 @@ "type": "json", "path": "package.json", "jsonpath": "$.version" + }, + { + "type": "json", + "path": "packages/gui/package.json", + "jsonpath": "$.version" } ] }