Local development

Developing Emet means running the Vite dev server against a Hugo site. Most of the time that’s a site on your disk, with no GitHub involved at all; when you’re working on something GitHub-specific there’s a mode for that too. This page covers both, plus the full list of npm scripts.

What you’ll need#

  • Node.js 22.12 or later, with npm.
  • A Hugo site to point at. Any site will do, though one with a few sections, some shortcodes and a theme will exercise more of the code. If you don’t have one, Emet will fall back to some built-in demo content.

Getting it running#

git clone https://github.com/dmyates/emetcms.git
cd emetcms
npm install
HUGO_ROOT=/path/to/your/hugo/site npm run dev

Open http://localhost:5173 and you should see the site’s sections in the sidebar. There’s no sign-in; the dev server signs you in as a made-up “Development User” and goes straight to the editor. If the site has no admin/site.yaml, the dev server scans it and writes one (plus admin/cms.yaml) first, so the very first load can take a second longer.

HUGO_ROOT must be an absolute path. Without it, the dev server assumes Emet has been checked out at static/emetcms/ inside a Hugo site and looks two directories up, which is the layout the project started with and not one you’re likely to be using.

The three dev modes#

Which backend the running app uses depends on what it finds at startup. The order is in src/content/ContentProvider.tsx, and the three possibilities in a browser are:

Local. vite-plugin-local-cms.ts adds a JSON API to the dev server under /api/local/*: list, read, write, rename and delete files, serve images, look up archetypes and shortcode configs, read Hugo’s config and permalinks and generate the CMS config. vite-plugin-static.ts serves the site’s static/ directory so image previews work. The app checks /api/local/check, and if the plugin reports a Hugo site at HUGO_ROOT (it looks for hugo.toml, hugo.yaml, hugo.yml, hugo.json, config.toml, config.yaml, config.yml, config.json or a config/ directory), you’re in local mode. Saves write to disk immediately and there’s nothing to push.

Mock. If /api/local/check says there’s no Hugo site (wrong HUGO_ROOT, or none at all), the app falls back to src/content/backends/mock.ts, which wraps an in-memory site from src/github/mock.ts with a couple of demo posts. Edits work but vanish on refresh. This is handy for UI work that doesn’t care about content and for a quick look at the app without setting anything up.

GitHub. To run the real GitHub backend, OAuth flow, sync service and publication workflow in development, set VITE_FORCE_AUTH along with a client ID:

VITE_FORCE_AUTH=1 VITE_GITHUB_CLIENT_ID=Iv23.your_client_id npm run dev

The app then behaves like a production build: sign-in button, repository picker, setup wizard and all. The dev server proxies /api to http://localhost:8788, which is where wrangler pages dev listens by default, so in a second terminal run the Pages Functions:

npm run build
npx wrangler pages dev dist

Put GITHUB_CLIENT_SECRET=... in a .dev.vars file in the project root first; wrangler reads it and the file is gitignored. You’ll also need http://localhost:5173/ added as a callback URL on your GitHub App. (npm run preview:cloudflare is the shortcut for the build-and-serve step, but it serves the built app on 8788 rather than the hot-reloading one on 5173, so it’s better for checking a build than for developing.)

Note that in local and mock mode the GitHub-only parts of the UI, i.e. the branch switcher, sync indicator and Sync with Production button, are hidden rather than stubbed, so you won’t see them until you switch to GitHub mode.

The desktop app#

npm run dev:desktop runs the Electron build in development with the same HUGO_ROOT convention, and uses yet another backend, over IPC. See Desktop app.

Scripts#

Command What it does
npm run dev Vite dev server on port 5173, in local, mock or GitHub mode as above.
npm run build tsc then vite build, into dist/. Needs VITE_GITHUB_CLIENT_ID to produce something people can sign in to.
npm run preview Builds and serves dist/ with Vite’s static server. No functions, so no sign-in.
npm run preview:cloudflare Builds with DEPLOY_TARGET=cloudflare-pages and serves dist/ plus functions/ with wrangler pages dev on port 8788.
npm run deploy Same build, then wrangler pages deploy dist. Alias for deploy:cloudflare.
npm run typecheck tsc --noEmit. Doesn’t cover tests or the Electron main and preload code.
npm run lint ESLint over src/.
npm test Vitest, once. See Testing.
npm run test:watch Vitest in watch mode.
npm run dev:desktop Electron in development.
npm run build:desktop electron-vite build into out/.
npm run package:desktop The above, then electron-builder into build/.

CI (.github/workflows/ci.yml) runs lint, typecheck, test and build on every push to master and every pull request, so it’s worth running the first three locally before you push.

Where next?#