@vttforge/cli 0.0.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +350 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -5
  4. package/dist/bin.d.mts +1 -0
  5. package/dist/bin.mjs +201 -0
  6. package/dist/bin.mjs.map +1 -0
  7. package/dist/index.d.mts +496 -0
  8. package/dist/index.mjs +2 -0
  9. package/dist/src-CWhUNM__.mjs +2443 -0
  10. package/dist/src-CWhUNM__.mjs.map +1 -0
  11. package/package.json +62 -9
  12. package/templates/module-js/.github/workflows/release.yml +92 -0
  13. package/templates/module-js/README.md +57 -0
  14. package/templates/module-js/_gitignore +12 -0
  15. package/templates/module-js/lang/en.json +11 -0
  16. package/templates/module-js/module.json +20 -0
  17. package/templates/module-js/package.json +24 -0
  18. package/templates/module-js/scripts/main.mjs +65 -0
  19. package/templates/module-js/styles/main.css +16 -0
  20. package/templates/module-js/vite.config.mjs +13 -0
  21. package/templates/module-ts/.github/workflows/release.yml +92 -0
  22. package/templates/module-ts/README.md +57 -0
  23. package/templates/module-ts/_gitignore +12 -0
  24. package/templates/module-ts/lang/en.json +11 -0
  25. package/templates/module-ts/module.json +20 -0
  26. package/templates/module-ts/package.json +26 -0
  27. package/templates/module-ts/scripts/foundry-globals.ts +23 -0
  28. package/templates/module-ts/scripts/main.ts +71 -0
  29. package/templates/module-ts/styles/main.css +16 -0
  30. package/templates/module-ts/tsconfig.json +18 -0
  31. package/templates/module-ts/vite.config.mjs +13 -0
  32. package/templates/system-js/.github/workflows/release.yml +92 -0
  33. package/templates/system-js/README.md +57 -0
  34. package/templates/system-js/_gitignore +12 -0
  35. package/templates/system-js/lang/en.json +55 -0
  36. package/templates/system-js/package.json +25 -0
  37. package/templates/system-js/scripts/data/character-data.mjs +79 -0
  38. package/templates/system-js/scripts/data/gear-data.mjs +37 -0
  39. package/templates/system-js/scripts/main.mjs +79 -0
  40. package/templates/system-js/scripts/migrations.mjs +43 -0
  41. package/templates/system-js/scripts/sheets/character-sheet.mjs +164 -0
  42. package/templates/system-js/scripts/sheets/gear-sheet.mjs +65 -0
  43. package/templates/system-js/styles/main.css +495 -0
  44. package/templates/system-js/system.json +44 -0
  45. package/templates/system-js/template.json +8 -0
  46. package/templates/system-js/templates/actor/character-sheet.hbs +168 -0
  47. package/templates/system-js/templates/item/gear-sheet.hbs +54 -0
  48. package/templates/system-js/vite.config.mjs +16 -0
  49. package/templates/system-ts/.github/workflows/release.yml +92 -0
  50. package/templates/system-ts/README.md +57 -0
  51. package/templates/system-ts/_gitignore +12 -0
  52. package/templates/system-ts/lang/en.json +55 -0
  53. package/templates/system-ts/package.json +27 -0
  54. package/templates/system-ts/scripts/data/character-data.ts +81 -0
  55. package/templates/system-ts/scripts/data/gear-data.ts +37 -0
  56. package/templates/system-ts/scripts/foundry-globals.ts +33 -0
  57. package/templates/system-ts/scripts/main.ts +82 -0
  58. package/templates/system-ts/scripts/migrations.ts +43 -0
  59. package/templates/system-ts/scripts/sheets/character-sheet.ts +202 -0
  60. package/templates/system-ts/scripts/sheets/gear-sheet.ts +68 -0
  61. package/templates/system-ts/styles/main.css +495 -0
  62. package/templates/system-ts/system.json +44 -0
  63. package/templates/system-ts/template.json +8 -0
  64. package/templates/system-ts/templates/actor/character-sheet.hbs +168 -0
  65. package/templates/system-ts/templates/item/gear-sheet.hbs +54 -0
  66. package/templates/system-ts/tsconfig.json +18 -0
  67. package/templates/system-ts/vite.config.mjs +16 -0
@@ -0,0 +1,57 @@
1
+ # {{TITLE}}
2
+
3
+ {{DESCRIPTION}}
4
+
5
+ A Foundry VTT module built on [VTTForge](https://github.com/vttforge/vttforge) — uses `SystemConfig` from `@vttforge/core` for typed settings, ships an example hook listener, and exposes a small public API on `game.modules.get("{{ID}}").api`.
6
+
7
+ > **Note** — VTTForge is currently in pre-release and not yet published to npm. Dependencies in `package.json` will resolve once VTTForge ships its first public release.
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ pnpm install
13
+ pnpm dev # vite build --watch + auto-symlinks dist/ into Foundry's Data
14
+ pnpm build # one-shot build + emits {{ID}}-<version>.zip for foundryvtt.com
15
+ ```
16
+
17
+ `pnpm dev` (`vttforge dev` under the hood) auto-detects your Foundry user-data
18
+ directory on the first run, prompts you to confirm it, and saves the choice to
19
+ `.vttforge/config.json` for next time. Override with `--data-dir <path>` or set
20
+ `FOUNDRY_DATA_DIR` in your environment.
21
+
22
+ Run Foundry with `--hotReload` to pick up changes without refreshing the page.
23
+ Then enable **{{TITLE}}** in any world.
24
+
25
+ ## What's inside
26
+
27
+ | Path | Purpose |
28
+ |---|---|
29
+ | `module.json` | Manifest |
30
+ | `scripts/main.mjs` | Entry point — settings registration, hook listeners, public API |
31
+ | `styles/main.css` | Stylesheet, scoped under `.{{ID}}` |
32
+ | `lang/en.json` | Localization strings |
33
+
34
+ ## Public API
35
+
36
+ The module exposes its API on `game.modules.get("{{ID}}").api`:
37
+
38
+ ```js
39
+ const api = game.modules.get("{{ID}}").api;
40
+ api.greet("world"); // returns "Hello, world!"
41
+ api.getSetting("showWelcome"); // returns the current value
42
+ ```
43
+
44
+ Extend `scripts/main.mjs` to add real methods — the example exists to show the registration pattern.
45
+
46
+ ## Releasing to Foundry
47
+
48
+ Tag-push triggers `.github/workflows/release.yml` to build, zip, and attach `{{ID}}-<version>.zip` + `dist/module.json` to a GitHub Release. Point Foundry at the `latest/download/module.json` URL.
49
+
50
+ ```bash
51
+ git tag v0.1.0
52
+ git push --tags
53
+ ```
54
+
55
+ ## License
56
+
57
+ {{LICENSE}} © {{YEAR}} {{AUTHOR}}
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ .vscode
5
+ .DS_Store
6
+ *.log
7
+
8
+ # vttforge dev — saved Foundry data-dir path
9
+ .vttforge/
10
+
11
+ # vttforge build — release artifact
12
+ *.zip
@@ -0,0 +1,11 @@
1
+ {
2
+ "{{LOCALE_PREFIX}}": {
3
+ "Settings": {
4
+ "showWelcome": {
5
+ "name": "Show welcome notification",
6
+ "hint": "Display a one-shot toast when {{TITLE}} loads in a world."
7
+ }
8
+ },
9
+ "Welcome": "{{TITLE}} loaded — try `game.modules.get(\"{{ID}}\").api.greet(\"GM\")` in the console."
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "{{ID}}",
3
+ "title": "{{TITLE}}",
4
+ "description": "{{DESCRIPTION}}",
5
+ "version": "0.1.0",
6
+ "compatibility": {
7
+ "minimum": "{{FOUNDRY_MIN_VERSION}}",
8
+ "verified": "{{FOUNDRY_VERIFIED_VERSION}}"
9
+ },
10
+ "authors": [{ "name": "{{AUTHOR}}" }],
11
+ "esmodules": ["main.mjs"],
12
+ "styles": [{ "src": "styles/main.css" }],
13
+ "languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
14
+ "flags": {
15
+ "hotReload": {
16
+ "extensions": ["css", "json"],
17
+ "paths": ["styles", "lang"]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "{{ID}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "{{DESCRIPTION}}",
6
+ "type": "module",
7
+ "license": "{{LICENSE}}",
8
+ "author": "{{AUTHOR}}",
9
+ "scripts": {
10
+ "build": "vttforge build",
11
+ "dev": "vttforge dev"
12
+ },
13
+ "dependencies": {
14
+ "@vttforge/core": "^0.6.0"
15
+ },
16
+ "devDependencies": {
17
+ "@vttforge/cli": "^0.5.0",
18
+ "@vttforge/vite-plugin": "^0.3.0",
19
+ "vite": "^8.2.2"
20
+ },
21
+ "engines": {
22
+ "node": ">=26"
23
+ }
24
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * {{TITLE}} — entry point.
3
+ *
4
+ * Demonstrates the typical Foundry module lifecycle:
5
+ *
6
+ * - `Hooks.once("init")` — register settings (here via `SystemConfig` from
7
+ * `@vttforge/core` for typed access) and expose the public API on
8
+ * `game.modules.get(MODULE_ID).api`.
9
+ * - `Hooks.once("ready")` — surface a one-shot notification if the user
10
+ * opted in via settings.
11
+ * - `Hooks.on("renderActorSheetV2", ...)` — example hook listener that
12
+ * tags any actor sheet with a discreet `.{{ID}}-badge` so you can verify
13
+ * the module is actually attached without hunting through devtools.
14
+ */
15
+ import { SystemConfig } from '@vttforge/core';
16
+
17
+ const MODULE_ID = '{{ID}}';
18
+ const settings = new SystemConfig(MODULE_ID);
19
+
20
+ Hooks.once('init', () => {
21
+ settings.register('showWelcome', {
22
+ name: '{{LOCALE_PREFIX}}.Settings.showWelcome.name',
23
+ hint: '{{LOCALE_PREFIX}}.Settings.showWelcome.hint',
24
+ scope: 'client',
25
+ config: true,
26
+ type: Boolean,
27
+ default: true,
28
+ });
29
+
30
+ const api = {
31
+ greet(name) {
32
+ return `Hello, ${name}!`;
33
+ },
34
+ getSetting(key) {
35
+ return settings.get(key);
36
+ },
37
+ };
38
+
39
+ const moduleHandle = game.modules.get(MODULE_ID);
40
+ if (moduleHandle) {
41
+ moduleHandle.api = api;
42
+ }
43
+ });
44
+
45
+ Hooks.once('ready', () => {
46
+ const shouldWelcome = settings.get('showWelcome');
47
+ if (shouldWelcome) {
48
+ ui.notifications?.info(game.i18n.localize('{{LOCALE_PREFIX}}.Welcome'));
49
+ }
50
+ });
51
+
52
+ Hooks.on('renderActorSheetV2', (_app, element) => {
53
+ // Decorative: tag the rendered sheet so it's obvious the module is
54
+ // active. Remove or replace with real behaviour once you start shipping
55
+ // module features.
56
+ const header = element.querySelector('.window-header .window-title');
57
+ if (header && !header.querySelector(`.{{ID}}-badge`)) {
58
+ const badge = document.createElement('span');
59
+ badge.className = '{{ID}}-badge';
60
+ // Double-quoted because `vttforge init` rejects `"` and `\` in the
61
+ // title — apostrophes in titles like "Daisy's Module" stay intact.
62
+ badge.textContent = "{{TITLE}}";
63
+ header.appendChild(badge);
64
+ }
65
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * {{TITLE}} stylesheet.
3
+ *
4
+ * Scope all rules under the `.{{ID}}` namespace so they don't leak into
5
+ * other modules or the host system's sheets. Foundry's CSS cascade is
6
+ * already layered, but namespacing makes intent explicit.
7
+ */
8
+ .{{ID}}-badge {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 0.4em;
12
+ padding: 0.15em 0.5em;
13
+ border-radius: 4px;
14
+ background: rgba(0, 0, 0, 0.08);
15
+ font-size: 0.8em;
16
+ }
@@ -0,0 +1,13 @@
1
+ import vttforge from '@vttforge/vite-plugin';
2
+ import { defineConfig } from 'vite';
3
+
4
+ /**
5
+ * Vite config for {{TITLE}}.
6
+ *
7
+ * `@vttforge/vite-plugin` owns the build contract for both systems and
8
+ * modules — `kind: 'module'` tells the plugin to write `dist/module.json`
9
+ * (instead of `system.json`) and to base chunk URLs at `/modules/{{ID}}/`.
10
+ */
11
+ export default defineConfig({
12
+ plugins: [vttforge({ id: '{{ID}}', kind: 'module' })],
13
+ });
@@ -0,0 +1,92 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Resolve version from tag
18
+ id: version
19
+ env:
20
+ REF: ${{ github.ref_name }}
21
+ run: |
22
+ version="${REF#v}"
23
+ # Git allows surprising characters in tag names. Reject anything
24
+ # that isn't a strict semver-ish identifier so downstream node -e
25
+ # / shell interpolations can't be turned into command injection
26
+ # by a malicious tag push.
27
+ if ! [[ "$version" =~ ^[0-9A-Za-z._+-]+$ ]]; then
28
+ echo "Refusing tag with unsafe characters: $version" >&2
29
+ exit 1
30
+ fi
31
+ echo "version=$version" >> "$GITHUB_OUTPUT"
32
+
33
+ - uses: pnpm/action-setup@v4
34
+ with:
35
+ version: 10
36
+
37
+ - uses: actions/setup-node@v4
38
+ with:
39
+ node-version: 22
40
+
41
+ - run: pnpm install
42
+
43
+ - name: Sync manifest version
44
+ env:
45
+ VERSION: ${{ steps.version.outputs.version }}
46
+ run: |
47
+ node -e "const fs=require('node:fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.version=process.env.VERSION;fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');"
48
+
49
+ # We invoke vite directly (not `pnpm build` → `vttforge build`) because
50
+ # the release workflow needs to:
51
+ # 1. Run vite build → emit dist/module.json
52
+ # 2. Patch manifest/download URLs into dist/module.json
53
+ # 3. Zip dist/ → release artifact (so the zipped manifest carries URLs)
54
+ # `vttforge build` zips at step 1, which would freeze the un-patched
55
+ # manifest. Eventually the vite-plugin will support env-driven URL
56
+ # injection and this dance disappears.
57
+ - run: pnpm exec vite build
58
+
59
+ - name: Inject release URLs into manifest
60
+ env:
61
+ VERSION: ${{ steps.version.outputs.version }}
62
+ REPO: ${{ github.repository }}
63
+ PKG_ID: '{{ID}}'
64
+ run: |
65
+ node -e "const fs=require('node:fs');const path='dist/module.json';const m=JSON.parse(fs.readFileSync(path,'utf8'));const r=process.env.REPO;const v=process.env.VERSION;const id=process.env.PKG_ID;m.manifest=\`https://github.com/\${r}/releases/latest/download/module.json\`;m.download=\`https://github.com/\${r}/releases/download/v\${v}/\${id}-\${v}.zip\`;fs.writeFileSync(path,JSON.stringify(m,null,2)+'\n');"
66
+
67
+ - name: Build release zip
68
+ env:
69
+ VERSION: ${{ steps.version.outputs.version }}
70
+ PKG_ID: '{{ID}}'
71
+ run: |
72
+ cd dist
73
+ zip -r "../${PKG_ID}-${VERSION}.zip" .
74
+
75
+ - name: Create GitHub Release
76
+ env:
77
+ VERSION: ${{ steps.version.outputs.version }}
78
+ PKG_ID: '{{ID}}'
79
+ uses: softprops/action-gh-release@v2
80
+ with:
81
+ files: |
82
+ ${{ env.PKG_ID }}-${{ env.VERSION }}.zip
83
+ dist/module.json
84
+ generate_release_notes: true
85
+ body: |
86
+ ## Foundry installation
87
+
88
+ Install via Foundry's setup wizard with this manifest URL:
89
+
90
+ ```
91
+ https://github.com/${{ github.repository }}/releases/latest/download/module.json
92
+ ```
@@ -0,0 +1,57 @@
1
+ # {{TITLE}}
2
+
3
+ {{DESCRIPTION}}
4
+
5
+ A Foundry VTT module built on [VTTForge](https://github.com/vttforge/vttforge) — uses `SystemConfig` from `@vttforge/core` for typed settings, ships an example hook listener, and exposes a small public API on `game.modules.get("{{ID}}").api`.
6
+
7
+ > **Note** — VTTForge is currently in pre-release and not yet published to npm. Dependencies in `package.json` will resolve once VTTForge ships its first public release.
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ pnpm install
13
+ pnpm dev # vite build --watch + auto-symlinks dist/ into Foundry's Data
14
+ pnpm build # one-shot build + emits {{ID}}-<version>.zip for foundryvtt.com
15
+ ```
16
+
17
+ `pnpm dev` (`vttforge dev` under the hood) auto-detects your Foundry user-data
18
+ directory on the first run, prompts you to confirm it, and saves the choice to
19
+ `.vttforge/config.json` for next time. Override with `--data-dir <path>` or set
20
+ `FOUNDRY_DATA_DIR` in your environment.
21
+
22
+ Run Foundry with `--hotReload` to pick up changes without refreshing the page.
23
+ Then enable **{{TITLE}}** in any world.
24
+
25
+ ## What's inside
26
+
27
+ | Path | Purpose |
28
+ |---|---|
29
+ | `module.json` | Manifest |
30
+ | `scripts/main.ts` | Entry point — settings registration, hook listeners, public API |
31
+ | `styles/main.css` | Stylesheet, scoped under `.{{ID}}` |
32
+ | `lang/en.json` | Localization strings |
33
+
34
+ ## Public API
35
+
36
+ The module exposes its API on `game.modules.get("{{ID}}").api`:
37
+
38
+ ```ts
39
+ const api = game.modules.get("{{ID}}").api;
40
+ api.greet("world"); // returns "Hello, world!"
41
+ api.getSetting("showWelcome"); // returns the current value
42
+ ```
43
+
44
+ Extend `scripts/main.ts` to add real methods — the example exists to show the registration pattern.
45
+
46
+ ## Releasing to Foundry
47
+
48
+ Tag-push triggers `.github/workflows/release.yml` to build, zip, and attach `{{ID}}-<version>.zip` + `dist/module.json` to a GitHub Release. Point Foundry at the `latest/download/module.json` URL.
49
+
50
+ ```bash
51
+ git tag v0.1.0
52
+ git push --tags
53
+ ```
54
+
55
+ ## License
56
+
57
+ {{LICENSE}} © {{YEAR}} {{AUTHOR}}
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ .vscode
5
+ .DS_Store
6
+ *.log
7
+
8
+ # vttforge dev — saved Foundry data-dir path
9
+ .vttforge/
10
+
11
+ # vttforge build — release artifact
12
+ *.zip
@@ -0,0 +1,11 @@
1
+ {
2
+ "{{LOCALE_PREFIX}}": {
3
+ "Settings": {
4
+ "showWelcome": {
5
+ "name": "Show welcome notification",
6
+ "hint": "Display a one-shot toast when {{TITLE}} loads in a world."
7
+ }
8
+ },
9
+ "Welcome": "{{TITLE}} loaded — try `game.modules.get(\"{{ID}}\").api.greet(\"GM\")` in the console."
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "{{ID}}",
3
+ "title": "{{TITLE}}",
4
+ "description": "{{DESCRIPTION}}",
5
+ "version": "0.1.0",
6
+ "compatibility": {
7
+ "minimum": "{{FOUNDRY_MIN_VERSION}}",
8
+ "verified": "{{FOUNDRY_VERIFIED_VERSION}}"
9
+ },
10
+ "authors": [{ "name": "{{AUTHOR}}" }],
11
+ "esmodules": ["main.mjs"],
12
+ "styles": [{ "src": "styles/main.css" }],
13
+ "languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
14
+ "flags": {
15
+ "hotReload": {
16
+ "extensions": ["css", "json"],
17
+ "paths": ["styles", "lang"]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{ID}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "{{DESCRIPTION}}",
6
+ "type": "module",
7
+ "license": "{{LICENSE}}",
8
+ "author": "{{AUTHOR}}",
9
+ "scripts": {
10
+ "build": "vttforge build",
11
+ "dev": "vttforge dev",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@vttforge/core": "^0.6.0"
16
+ },
17
+ "devDependencies": {
18
+ "@vttforge/cli": "^0.5.0",
19
+ "@vttforge/vite-plugin": "^0.3.0",
20
+ "typescript": "^5.4.0",
21
+ "vite": "^8.2.2"
22
+ },
23
+ "engines": {
24
+ "node": ">=26"
25
+ }
26
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal Foundry runtime globals.
3
+ *
4
+ * These declarations let the scaffold compile cleanly without an external
5
+ * type package. Replace with `fvtt-types` (community-maintained, pinned to
6
+ * a known-working commit SHA) or `@vttforge/types` (coming in a later
7
+ * VTTForge release) for full type coverage.
8
+ */
9
+
10
+ declare global {
11
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
12
+ const game: any;
13
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
14
+ const CONFIG: any;
15
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
16
+ const Hooks: any;
17
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
18
+ const ui: any;
19
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
20
+ const foundry: any;
21
+ }
22
+
23
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * {{TITLE}} — entry point.
3
+ *
4
+ * Demonstrates the typical Foundry module lifecycle:
5
+ *
6
+ * - `Hooks.once("init")` — register settings (here via `SystemConfig` from
7
+ * `@vttforge/core` for typed access) and expose the public API on
8
+ * `game.modules.get(MODULE_ID).api`.
9
+ * - `Hooks.once("ready")` — surface a one-shot notification if the user
10
+ * opted in via settings.
11
+ * - `Hooks.on("renderActorSheetV2", ...)` — example hook listener that
12
+ * tags any actor sheet with a discreet `.{{ID}}-badge` so you can verify
13
+ * the module is actually attached without hunting through devtools.
14
+ */
15
+ import './foundry-globals.js';
16
+ import { SystemConfig } from '@vttforge/core';
17
+
18
+ const MODULE_ID = '{{ID}}';
19
+ const settings = new SystemConfig(MODULE_ID);
20
+
21
+ interface ModuleApi {
22
+ greet(name: string): string;
23
+ getSetting<T>(key: string): T;
24
+ }
25
+
26
+ Hooks.once('init', () => {
27
+ settings.register('showWelcome', {
28
+ name: '{{LOCALE_PREFIX}}.Settings.showWelcome.name',
29
+ hint: '{{LOCALE_PREFIX}}.Settings.showWelcome.hint',
30
+ scope: 'client',
31
+ config: true,
32
+ type: Boolean,
33
+ default: true,
34
+ });
35
+
36
+ const api: ModuleApi = {
37
+ greet(name: string): string {
38
+ return `Hello, ${name}!`;
39
+ },
40
+ getSetting<T>(key: string): T {
41
+ return settings.get(key) as T;
42
+ },
43
+ };
44
+
45
+ const moduleHandle = game.modules.get(MODULE_ID);
46
+ if (moduleHandle) {
47
+ moduleHandle.api = api;
48
+ }
49
+ });
50
+
51
+ Hooks.once('ready', () => {
52
+ const shouldWelcome = settings.get<boolean>('showWelcome');
53
+ if (shouldWelcome) {
54
+ ui.notifications?.info(game.i18n.localize('{{LOCALE_PREFIX}}.Welcome'));
55
+ }
56
+ });
57
+
58
+ Hooks.on('renderActorSheetV2', (_app: unknown, element: HTMLElement) => {
59
+ // Decorative: tag the rendered sheet so it's obvious the module is
60
+ // active. Remove or replace with real behaviour once you start shipping
61
+ // module features.
62
+ const header = element.querySelector('.window-header .window-title');
63
+ if (header && !header.querySelector(`.{{ID}}-badge`)) {
64
+ const badge = document.createElement('span');
65
+ badge.className = '{{ID}}-badge';
66
+ // Double-quoted because `vttforge init` rejects `"` and `\` in the
67
+ // title — apostrophes in titles like "Daisy's Module" stay intact.
68
+ badge.textContent = "{{TITLE}}";
69
+ header.appendChild(badge);
70
+ }
71
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * {{TITLE}} stylesheet.
3
+ *
4
+ * Scope all rules under the `.{{ID}}` namespace so they don't leak into
5
+ * other modules or the host system's sheets. Foundry's CSS cascade is
6
+ * already layered, but namespacing makes intent explicit.
7
+ */
8
+ .{{ID}}-badge {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 0.4em;
12
+ padding: 0.15em 0.5em;
13
+ border-radius: 4px;
14
+ background: rgba(0, 0, 0, 0.08);
15
+ font-size: 0.8em;
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "isolatedModules": true,
10
+ "verbatimModuleSyntax": true,
11
+ "noEmit": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "allowImportingTsExtensions": false
15
+ },
16
+ "include": ["scripts/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,13 @@
1
+ import vttforge from '@vttforge/vite-plugin';
2
+ import { defineConfig } from 'vite';
3
+
4
+ /**
5
+ * Vite config for {{TITLE}}.
6
+ *
7
+ * `@vttforge/vite-plugin` owns the build contract for both systems and
8
+ * modules — `kind: 'module'` tells the plugin to write `dist/module.json`
9
+ * (instead of `system.json`) and to base chunk URLs at `/modules/{{ID}}/`.
10
+ */
11
+ export default defineConfig({
12
+ plugins: [vttforge({ id: '{{ID}}', kind: 'module', entry: 'scripts/main.ts' })],
13
+ });