foundryvtt-module-scaffold

v2026.09.24

Scaffold a new FoundryVTT v13 module repo (Vite + TS + bun + biome, CI, release-please) with basic/app/libwrapper variants. Use when bootstrapping or init-ing a foundry module.

GitHub
安装命令
npx skhub add laurigates/foundryvtt-module-scaffold
Markdown
SKILL.md

foundryvtt-module-scaffold

Bootstrap a new FoundryVTT v13 module repo built with Vite + TypeScript + bun + biome, leaving only the actual module logic to implement. The generated repo passes just check (typecheck + build + lint + test) from the first commit and distributes via the GitHub-release manifest-URL convention.

When to Use This Skill

Use this skill when...Use the alternative when...
Starting a new FoundryVTT module repo — CI-green TS toolchain, release-please, and a basic/app/libwrapper skeleton before writing module logicYou want the full pipeline (repo created + seeded + gitops-adopted) → foundryvtt-module
Spinning up a FoundryVTT module backlog ideaAdding a feature to an existing module — this creates a new repo

The architecture it scaffolds

TypeScript source in src/ (entry src/module.ts), built to dist/<id>.mjs via Vite library mode. vite-plugin-static-copy places module.json, lang/, styles/ (and templates/ for the app variant) into dist/, which Foundry serves as the module root. tsc --noEmit type-checks; Vite emits — decoupled.

  • Type gate: bun run typecheck → tsc --noEmit. Foundry globals are typed by loose local ambient shims (src/foundry-shims.d.ts), so the build is self-contained and CI-green without the (beta, git-only) fvtt-types. Verify the real Foundry API before relying on a shape — the shims are deliberately loose. Opt into fvtt-types later if you want richer types.
  • Build: bun run build → vite build → dist/<id>.mjs + copied assets.
  • Dev: bun run dev → Vite dev server on :30001 proxying everything to Foundry on :30000 except the module's own files (served with HMR).
  • Distribute via GitHub release: module.json manifest → releases/latest/download/module.json, download → releases/latest/download/<id>.zip. release-please bumps $.version in both package.json and module.json; the release job builds, zips dist/, and attaches the assets. No foundryvtt.com submission is needed to install by URL (only to be listed in the in-app package browser).

Three variants

VariantUse whenAdds on top of basic
basic (default)Settings + lifecycle behavior — the minimal well-formed module.init/ready hooks, a registered world setting, i18n, scoped CSS.
appThe module has a UI panel.An ApplicationV2/HandlebarsApplicationMixin window (src/app.ts + templates/app.hbs) and a game.settings.registerMenu button that opens it.
libwrapperThe module patches a core/system method.src/patches.ts with a libWrapper.register(...) call and a manual monkey-patch fallback when lib-wrapper is absent; a relationships.recommends entry for lib-wrapper.

Decision rule: basic for behavior driven by hooks/settings; app when the module surfaces a window or dialog; libwrapper when it overrides a core method (the conflict-safe way to do that on Foundry). Variants compose conceptually — start from the closest one and add the rest by hand.

How to run

scaffold.py is stdlib-only. Run from the workspace where the module should land (e.g. repos/laurigates/foundryvtt-dev/).

Basic settings+hooks module:

python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-initiative-tweaks --display "Initiative Tweaks" --desc "Small quality-of-life tweaks to the combat initiative tracker."

Module with an ApplicationV2 UI panel:

python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-party-overview --display "Party Overview" --desc "A dockable party status panel for GMs." --variant app

Module that patches a core method via libWrapper:

python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-token-vision-tweak --display "Token Vision Tweak" --desc "Adjusts token vision drawing via a libWrapper-guarded patch." --variant libwrapper

Flags: --name (GitHub repo, e.g. foundryvtt-x), --id (Foundry module id; default = --name minus the leading foundryvtt-), --display (title), --desc, --variant {basic,app,libwrapper}, --fvtt-min / --fvtt-verified (compatibility, default 12 / 13), --publisher (default laurigates), --author, --dir (parent dir, default cwd).

It refuses to overwrite an existing directory.

Verifying a module (--verify)

python3 ${CLAUDE_SKILL_DIR}/scaffold.py --verify foundryvtt-initiative-tweaks

Re-runs the finishing-pass audit against an existing module and emits a machine verdict (MODULE_ID_MATCH=, ESMODULES=, MANIFEST_URL=, DOWNLOAD_URL=, RELEASE_ZIP=, DECLARED_ASSETS=, LOCKFILE=, then ISSUE_COUNT= and STATUS=). Exit 1 on ERROR.

STATUS=Meaning
ERRORFoundry cannot install, load, or update the published module — the id disagrees across module.json / vite.config.ts / src/constants.ts, esmodules names a file the build never emits, or an install URL resolves to a release asset nobody uploads.
WARNIt loads, but something is unfinished (a declared asset that 404s, no bun.lock yet). A fresh scaffold is WARN until bun install runs.
OKNothing outstanding.

The generated repo asserts the same invariants in tests/manifest.test.ts, which its own test CI job runs on every PR — so a rename that drifts one of the five places the module id appears fails a PR, not a user's install.

Alternative: the cargo-generate template (pilot)

templates/foundryvtt-module/ is a cargo-generate port of scaffold.py whose emitted files are real files (so tsc/biome/actionlint can check them) rather than Python strings. Output is byte-identical, enforced by scripts/tests/test-template-parity.sh.

scaffold.py remains the default. Reach for the template to edit the scaffold itself, or to try the flow before it is promoted:

cargo generate --path ${CLAUDE_SKILL_DIR}/../../templates/foundryvtt-module --name foundryvtt-initiative-tweaks --vcs none --define 'display_name=Initiative Tweaks' --define 'description=…' --define variant=basic

Needs cargo-generate locally — not in the base image, and the main cost of the port. CI installs it from the release tarball so the parity gate actually runs (#2221). See templates/README.md for the comparison, the one deliberate divergence (a non-kebab-case name), the Liquid brace-collision fixes, and what promoting the template would take.

What you get

A repo where just check passes from the first commit: a real module.json manifest, package.json (bun scripts), vite.config.ts, strict tsconfig.json, biome.json, vitest.config.ts + a green Vitest smoke test (Foundry globals stubbed in tests/setup.ts) and tests/manifest.test.ts (the manifest-vs-build gate above), .github/workflows/ (ci.yml, release-please.yml), release-please-config.json + manifest, renovate.json, a justfile, src/module.ts + src/settings.ts + src/constants.ts + src/foundry-shims.d.ts, lang/en.json, styles/<id>.css, CLAUDE.md, README.md, LICENSE, and an ADR recording the toolchain decision. The app variant adds src/app.ts + templates/app.hbs; libwrapper adds src/patches.ts.

After scaffolding

The generator prints the exact next steps. In order:

cd foundryvtt-<name>
git init -b main
bun install
just check

Seed main directly (the repo is unprotected until gitops adopts it) — pushing a feature branch first would leave main missing on origin and force a rename + default-branch fixup later. bun install writes bun.lock, which the seed commit must include (CI uses --frozen-lockfile).

Then implement, and wire up infra:

  1. Implement the module — for basic, src/module.ts + src/settings.ts; for app, src/app.ts + templates/app.hbs; for libwrapper, replace the Token._draw example in src/patches.ts with the real target.
  2. Add the repo to gitops/repositories.tf with release_please = true and a foundryvtt topic (mirror the foundryvtt-mcp entry). On apply, gitops pushes the release-please App credentials.

Or skip steps entirely: run the /foundryvtt-module orchestrator, which chains scaffold → gh repo create → seed main → the gitops PR.

Hard rules baked into the output

  • id is the single source of truth. module.json id, the install folder, and the release zip name all derive from --id. Lowercase kebab-case only.
  • ESM-only, paths byte-match the manifest. esmodules references <id>.mjs; the Vite output filename is pinned to match. A mismatch is a silent load failure — tests/manifest.test.ts fails the PR that introduces it.
  • Target the harness-pinned Foundry version. Keep module.json compatibility.{minimum,verified} in sync with what you test against. Verify the Foundry API against https://foundryvtt.com/api/ or the live console — not memory.
  • Do not commit dist/. It is git-ignored and rebuilt; CI builds it for releases.
  • Scoped CSS. Every selector is prefixed with the module id — keep it that way so styles never clobber core or other modules.
  • Never hand-edit CHANGELOG.md or the version fields — release-please owns them (it bumps both package.json and module.json).

Agentic Optimizations

ContextCommand
Scaffold a basic modulepython3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-X --display "X" --desc "…"
Scaffold an app modulepython3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-X --display "X" --desc "…" --variant app
Verify a generated modulecd foundryvtt-X && bun install && just check
Gate the finishing pass (machine verdict)python3 ${CLAUDE_SKILL_DIR}/scaffold.py --verify foundryvtt-X
Check the pilot still matches scaffold.pybash ${CLAUDE_SKILL_DIR}/scripts/tests/test-template-parity.sh
Prove the finishing-pass gate still firesbash ${CLAUDE_SKILL_DIR}/scripts/tests/test-manifest-invariants.sh

Notes & deferrals

  • The biome pin is single-sourced in scaffold.py's BIOME_VERSION constant so biome.json and the CI setup-biome step never drift.
  • Action/tool versions in the generated workflows are current as of scaffolding; the account-wide gitops Renovate App reads the emitted renovate.json and bumps them. No repo-local renovate.yml is emitted: a second runner kept a second dependency dashboard under a second bot identity (#2708).
  • The generated module uses local ambient shims, not fvtt-types. This keeps the build green and self-contained; switch tsconfig types to fvtt-types (github:League-of-Foundry-Developers/foundry-vtt-types#main) for full API types once you need them.
  • Quench (in-Foundry Mocha runner) and Playwright integration tests against the harness are not scaffolded — add them when the module warrants runtime coverage.
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

foundryvtt-plugin/skills/foundryvtt-module-scaffold

默认分支

main

最新提交

1668324

Tree SHA

b2d4cc3