Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/gui-release.yml
Original file line number Diff line number Diff line change
@@ -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
31 changes: 31 additions & 0 deletions .github/workflows/gui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 30 additions & 6 deletions docs/GUI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions packages/gui/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 2 additions & 0 deletions packages/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand Down
33 changes: 33 additions & 0 deletions packages/gui/tests/smoke/smoke.mjs
Original file line number Diff line number Diff line change
@@ -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();
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"type": "json",
"path": "package.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": "packages/gui/package.json",
"jsonpath": "$.version"
}
]
}
Expand Down
Loading