@vttforge/cli 0.0.0 → 0.5.1

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 +364 -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,168 @@
1
+ {{!--
2
+ Character sheet — markup mirrors the canonical Figma reference
3
+ (character-sheet.jsx + Theme Explorations .sh-* classes).
4
+
5
+ Tabs nav uses `data-tab`/`data-group` + `data-action="vttforgeTab"` so
6
+ BaseActorSheet's default tab action routes clicks. `context.tabs.primary`
7
+ is auto-populated by BaseActorSheet's _prepareContext.
8
+
9
+ `<img data-edit="img">` is handled by DocumentSheetV2's built-in editImage
10
+ action. `<prose-mirror>` is the v13 rich text element for biography.
11
+ --}}
12
+ <form autocomplete="off">
13
+
14
+ {{!-- ════════ HEAD (.sh-head) ════════ --}}
15
+ <div class="sh-head">
16
+ <img class="sh-portrait" src="{{actor.img}}" alt="{{actor.name}}" data-edit="img" />
17
+ <div class="sh-id">
18
+ <div class="sh-id-top">
19
+ <input class="sh-name" type="text" name="name" value="{{actor.name}}"
20
+ placeholder="{{localize '{{LOCALE_PREFIX}}.Sheet.NamePlaceholder'}}" />
21
+ <div class="sh-sub">
22
+ <span>{{localize "{{LOCALE_PREFIX}}.Sheet.Level"}}</span>
23
+ <input type="number" name="system.level" value="{{system.level}}" min="1" max="20" />
24
+ </div>
25
+ </div>
26
+ <div class="sh-quick">
27
+ <div class="q">
28
+ <div class="qv">
29
+ <input type="number" name="system.health.value" value="{{system.health.value}}" min="0" />
30
+ <span class="qv-sep">/</span>
31
+ <span class="qv-max">{{system.health.max}}</span>
32
+ </div>
33
+ <div class="qk">{{localize "{{LOCALE_PREFIX}}.Sheet.Quick.HP"}}</div>
34
+ </div>
35
+ <div class="q">
36
+ <div class="qv">{{system.armorClass}}</div>
37
+ <div class="qk">{{localize "{{LOCALE_PREFIX}}.Sheet.Quick.AC"}}</div>
38
+ </div>
39
+ <div class="q">
40
+ <div class="qv">
41
+ <input type="number" name="system.speed" value="{{system.speed}}" min="0" />
42
+ </div>
43
+ <div class="qk">{{localize "{{LOCALE_PREFIX}}.Sheet.Quick.SPD"}}</div>
44
+ </div>
45
+ <div class="q">
46
+ <div class="qv">{{numberFormat system.initiative sign=true}}</div>
47
+ <div class="qk">{{localize "{{LOCALE_PREFIX}}.Sheet.Quick.INIT"}}</div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ {{!-- ════════ TABS (.sh-tabs) ════════ --}}
54
+ <nav class="sh-tabs" data-group="primary">
55
+ {{#each tabs as |tab|}}
56
+ <button type="button" class="sh-tab {{#if tab.active}}on{{/if}}"
57
+ data-tab="{{tab.id}}"
58
+ data-group="primary"
59
+ data-action="vttforgeTab"
60
+ title="{{localize tab.label}}">
61
+ {{#if tab.icon}}<i class="{{tab.icon}}"></i>{{/if}}
62
+ <span>{{localize tab.label}}</span>
63
+ </button>
64
+ {{/each}}
65
+ </nav>
66
+
67
+ {{!-- ════════ BODY (.sh-body) ════════ --}}
68
+ <section class="sh-body">
69
+
70
+ {{!-- ABILITIES --}}
71
+ <section class="tab abilities {{tabs.abilities.cssClass}}"
72
+ data-tab="abilities" data-group="primary">
73
+ <h3 class="sh-section-title">{{localize "{{LOCALE_PREFIX}}.Sheet.AbilityScores"}}</h3>
74
+ <div class="sh-abilities">
75
+ {{#each abilities as |ability|}}
76
+ <div class="sh-abil">
77
+ <div class="lbl" title="{{ability.label}}">{{ability.key}}</div>
78
+ <input class="val" type="number"
79
+ name="system.abilities.{{ability.key}}"
80
+ value="{{ability.value}}" min="1" max="30" />
81
+ <button type="button" class="mod sh-abil-roll"
82
+ data-action="rollAbility" data-ability="{{ability.key}}"
83
+ title="{{localize '{{LOCALE_PREFIX}}.Sheet.Roll.label'}}">
84
+ {{numberFormat ability.mod sign=true}}
85
+ </button>
86
+ </div>
87
+ {{/each}}
88
+ </div>
89
+ </section>
90
+
91
+ {{!-- INVENTORY --}}
92
+ <section class="tab inventory {{tabs.inventory.cssClass}}"
93
+ data-tab="inventory" data-group="primary">
94
+ <header class="sh-section-head">
95
+ <h3 class="sh-section-title">{{localize "{{LOCALE_PREFIX}}.Sheet.Inventory"}}</h3>
96
+ <button type="button" class="sh-action" data-action="createGear"
97
+ title="{{localize '{{LOCALE_PREFIX}}.Sheet.NewGear'}}">
98
+ <i class="fa-solid fa-plus"></i>
99
+ <span>{{localize "{{LOCALE_PREFIX}}.Sheet.NewGear"}}</span>
100
+ </button>
101
+ </header>
102
+ <div class="sh-items">
103
+ {{#each gear as |item|}}
104
+ <div class="sh-item" data-item-id="{{item.id}}" draggable="true">
105
+ <div class="sh-item-ico">
106
+ {{#if item.img}}
107
+ <img src="{{item.img}}" alt="{{item.name}}" />
108
+ {{else}}
109
+ <i class="fa-solid fa-box"></i>
110
+ {{/if}}
111
+ </div>
112
+ <div>
113
+ <div class="sh-item-name">{{item.name}}</div>
114
+ <div class="sh-item-meta">{{numberFormat item.weight}} lb</div>
115
+ </div>
116
+ <span class="sh-tag {{#if (eq item.kind "stowed")}}muted{{/if}}">
117
+ {{localize (concat "{{LOCALE_PREFIX}}.Gear.Kind." item.kind)}}
118
+ </span>
119
+ <span class="sh-qty">×{{item.quantity}}</span>
120
+ <button type="button" class="sh-item-delete" data-action="deleteItem"
121
+ title="{{localize '{{LOCALE_PREFIX}}.Sheet.Delete'}}">
122
+ <i class="fa-solid fa-trash"></i>
123
+ </button>
124
+ </div>
125
+ {{else}}
126
+ <div class="sh-items-empty">{{localize "{{LOCALE_PREFIX}}.Sheet.NoGear"}}</div>
127
+ {{/each}}
128
+ </div>
129
+
130
+ <div class="sh-drop">
131
+ <div class="lbl">{{localize "{{LOCALE_PREFIX}}.Sheet.Drop.label"}}</div>
132
+ <div class="sub">onDropItem(item, event)</div>
133
+ </div>
134
+ </section>
135
+
136
+ {{!-- SPELLS (placeholder) --}}
137
+ <section class="tab spells {{tabs.spells.cssClass}}"
138
+ data-tab="spells" data-group="primary">
139
+ <h3 class="sh-section-title">{{localize "{{LOCALE_PREFIX}}.Sheet.Spells"}}</h3>
140
+ <div class="sh-empty">{{localize "{{LOCALE_PREFIX}}.Sheet.SpellsEmpty"}}</div>
141
+ </section>
142
+
143
+ {{!-- BIOGRAPHY --}}
144
+ <section class="tab biography {{tabs.biography.cssClass}}"
145
+ data-tab="biography" data-group="primary">
146
+ <h3 class="sh-section-title">{{localize "{{LOCALE_PREFIX}}.Sheet.Biography"}}</h3>
147
+ {{#if isEditable}}
148
+ <prose-mirror name="system.biography"
149
+ button="true"
150
+ editable="{{isEditable}}"
151
+ toggled="false"
152
+ value="{{system.biography}}">
153
+ {{{enrichedBiography}}}
154
+ </prose-mirror>
155
+ {{else}}
156
+ <div class="sh-biography-readonly">{{{enrichedBiography}}}</div>
157
+ {{/if}}
158
+ </section>
159
+
160
+ </section>
161
+
162
+ {{!-- ════════ FOOT (.sh-foot) ════════ --}}
163
+ <footer class="sh-foot">
164
+ <span class="nm">VTTFORGE · FORGE</span>
165
+ <span>@vttforge/styles · v0.2</span>
166
+ </footer>
167
+
168
+ </form>
@@ -0,0 +1,54 @@
1
+ {{!-- Gear sheet — minimal demo of BaseItemSheet on ItemSheetV2. --}}
2
+ <form autocomplete="off">
3
+
4
+ <header class="sheet-header">
5
+ <img class="portrait" src="{{item.img}}" alt="{{item.name}}" data-edit="img" />
6
+ <input type="text" name="name" value="{{item.name}}"
7
+ placeholder="{{localize '{{LOCALE_PREFIX}}.Sheet.NamePlaceholder'}}" />
8
+ </header>
9
+
10
+ <nav class="sheet-tabs" data-group="primary">
11
+ {{#each tabs as |tab|}}
12
+ <button type="button" class="item {{tab.cssClass}}"
13
+ data-tab="{{tab.id}}"
14
+ data-group="primary"
15
+ data-action="vttforgeTab"
16
+ title="{{localize tab.label}}">
17
+ {{#if tab.icon}}<i class="{{tab.icon}}"></i>{{/if}}
18
+ <span>{{localize tab.label}}</span>
19
+ </button>
20
+ {{/each}}
21
+ </nav>
22
+
23
+ <section class="sheet-body">
24
+ {{!-- DETAILS --}}
25
+ <section class="tab details {{tabs.details.cssClass}}"
26
+ data-tab="details" data-group="primary">
27
+ <div class="field">
28
+ <label>{{localize "{{LOCALE_PREFIX}}.Gear.Quantity"}}</label>
29
+ <input type="number" name="system.quantity" value="{{system.quantity}}" min="0" />
30
+ </div>
31
+ <div class="field">
32
+ <label>{{localize "{{LOCALE_PREFIX}}.Gear.Weight"}}</label>
33
+ <input type="number" name="system.weight" value="{{system.weight}}" min="0" step="0.1" />
34
+ </div>
35
+ </section>
36
+
37
+ {{!-- DESCRIPTION --}}
38
+ <section class="tab description {{tabs.description.cssClass}}"
39
+ data-tab="description" data-group="primary">
40
+ {{#if isEditable}}
41
+ <prose-mirror name="system.description"
42
+ button="true"
43
+ editable="{{isEditable}}"
44
+ toggled="false"
45
+ value="{{system.description}}">
46
+ {{{enrichedDescription}}}
47
+ </prose-mirror>
48
+ {{else}}
49
+ <div class="description-readonly">{{{enrichedDescription}}}</div>
50
+ {{/if}}
51
+ </section>
52
+ </section>
53
+
54
+ </form>
@@ -0,0 +1,16 @@
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 — it bundles
8
+ * `scripts/main.mjs` into `dist/main.mjs` (no hash, no bare specifiers),
9
+ * resolves `@import "@vttforge/styles"` at build time, copies the manifest +
10
+ * `lang/` + `templates/` to `dist/`, and syncs the manifest version from
11
+ * `package.json` on every build. The result under `dist/` is the deployable
12
+ * artefact you symlink into Foundry's `Data/systems/{{ID}}/`.
13
+ */
14
+ export default defineConfig({
15
+ plugins: [vttforge({ id: '{{ID}}' })],
16
+ });
@@ -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/system.json
52
+ # 2. Patch manifest/download URLs into dist/system.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/system.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/system.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/system.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/system.json
92
+ ```
@@ -0,0 +1,57 @@
1
+ # {{TITLE}}
2
+
3
+ {{DESCRIPTION}}
4
+
5
+ Built on [VTTForge](https://github.com/vttforge/vttforge) — typed data models, declarative sheet boilerplate, migration runner, error catalogue, and a Vite-based build pipeline.
6
+
7
+ > **Note** — VTTForge is currently in pre-release and not yet published to npm. The dependencies in `package.json` (`@vttforge/core`, `@vttforge/styles`, `@vttforge/vite-plugin`) will resolve once VTTForge ships its first public release. Until then, work from a local checkout: `pnpm link --global` from each VTTForge package, then `pnpm link --global @vttforge/core @vttforge/styles @vttforge/vite-plugin` here.
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` so saved files reload the world without a page
23
+ refresh — `dev` watches `dist/` for changes, and Foundry's built-in dispatcher
24
+ swaps CSS / Handlebars / JSON on the fly.
25
+
26
+ Then launch Foundry, create a world that uses **{{TITLE}}**, and open a character — the sheet is the `CharacterSheet` shipped in `scripts/sheets/`.
27
+
28
+ ## What's inside
29
+
30
+ | Path | Purpose |
31
+ |---|---|
32
+ | `system.json` | Manifest (id, compatibility, document types, manifest-side sanitization paths) |
33
+ | `template.json` | Foundry document type declarations (Actor + Item subtypes) |
34
+ | `scripts/main.ts` | Entry point — one call to `registerSystem` from `@vttforge/core` |
35
+ | `scripts/data/*.ts` | Typed data models (`BaseTypeDataModel()` from `@vttforge/core`) |
36
+ | `scripts/sheets/*.ts` | Sheet boilerplate eliminators (`BaseActorSheet()` / `BaseItemSheet()`) |
37
+ | `scripts/migrations.ts` | `createMigrationRunner()` — versioned data migrations |
38
+ | `templates/` | Handlebars templates for sheets |
39
+ | `styles/main.css` | Stylesheet — imports `@vttforge/styles` as the design system base |
40
+ | `lang/en.json` | Localization strings |
41
+
42
+ ## Releasing to Foundry
43
+
44
+ Tag-push triggers `.github/workflows/release.yml`, which builds the system, zips `dist/`, and attaches both `{{ID}}-<version>.zip` and `dist/system.json` to a GitHub Release.
45
+
46
+ ```bash
47
+ git tag v0.1.0
48
+ git push --tags
49
+ ```
50
+
51
+ Foundry installs from the `system.json` URL on the Release — point users at the `latest/download/system.json` URL in the release notes so they auto-update on every tag.
52
+
53
+ For foundryvtt.com submissions, submit the same manifest URL pattern.
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,55 @@
1
+ {
2
+ "TYPES.Actor.character": "Character",
3
+ "TYPES.Item.gear": "Gear",
4
+
5
+ "{{LOCALE_PREFIX}}.System.title": "{{TITLE}}",
6
+ "{{LOCALE_PREFIX}}.System.description": "{{DESCRIPTION}}",
7
+
8
+ "{{LOCALE_PREFIX}}.Sheet.Character.title": "Character",
9
+ "{{LOCALE_PREFIX}}.Sheet.Gear.title": "Gear",
10
+ "{{LOCALE_PREFIX}}.Sheet.NamePlaceholder": "Name…",
11
+ "{{LOCALE_PREFIX}}.Sheet.Level": "LV",
12
+ "{{LOCALE_PREFIX}}.Sheet.AbilityScores": "Ability scores",
13
+ "{{LOCALE_PREFIX}}.Sheet.Inventory": "Inventory",
14
+ "{{LOCALE_PREFIX}}.Sheet.Biography": "Biography",
15
+ "{{LOCALE_PREFIX}}.Sheet.NewGear": "New gear",
16
+ "{{LOCALE_PREFIX}}.Sheet.NewGearName": "New Gear",
17
+ "{{LOCALE_PREFIX}}.Sheet.NoGear": "No gear yet — drag items here or click New gear.",
18
+ "{{LOCALE_PREFIX}}.Sheet.Delete": "Delete",
19
+
20
+ "{{LOCALE_PREFIX}}.Sheet.Quick.HP": "HP",
21
+ "{{LOCALE_PREFIX}}.Sheet.Quick.AC": "AC",
22
+ "{{LOCALE_PREFIX}}.Sheet.Quick.SPD": "SPD",
23
+ "{{LOCALE_PREFIX}}.Sheet.Quick.INIT": "INIT",
24
+
25
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.abilities": "Abilities",
26
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.inventory": "Inventory",
27
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.spells": "Spells",
28
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.biography": "Biography",
29
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.details": "Details",
30
+ "{{LOCALE_PREFIX}}.Sheet.Tabs.description": "Description",
31
+ "{{LOCALE_PREFIX}}.Sheet.Spells": "Spells",
32
+ "{{LOCALE_PREFIX}}.Sheet.SpellsEmpty": "Spells slot reserved for a follow-up release.",
33
+
34
+ "{{LOCALE_PREFIX}}.Sheet.Roll.label": "Roll",
35
+ "{{LOCALE_PREFIX}}.Sheet.Roll.flavor": "{ability} check",
36
+ "{{LOCALE_PREFIX}}.Sheet.Drop.label": "Drop item to add",
37
+ "{{LOCALE_PREFIX}}.Sheet.Drop.rejected": "Cannot drop {type} items here — only gear is accepted.",
38
+
39
+ "{{LOCALE_PREFIX}}.Ability.str": "Strength",
40
+ "{{LOCALE_PREFIX}}.Ability.dex": "Dexterity",
41
+ "{{LOCALE_PREFIX}}.Ability.con": "Constitution",
42
+ "{{LOCALE_PREFIX}}.Ability.int": "Intelligence",
43
+ "{{LOCALE_PREFIX}}.Ability.wis": "Wisdom",
44
+ "{{LOCALE_PREFIX}}.Ability.cha": "Charisma",
45
+
46
+ "{{LOCALE_PREFIX}}.Gear.Quantity": "Quantity",
47
+ "{{LOCALE_PREFIX}}.Gear.Weight": "Weight (lb)",
48
+ "{{LOCALE_PREFIX}}.Gear.Kind": "Kind",
49
+ "{{LOCALE_PREFIX}}.Gear.Kind.equipped": "equipped",
50
+ "{{LOCALE_PREFIX}}.Gear.Kind.valued": "valued",
51
+ "{{LOCALE_PREFIX}}.Gear.Kind.stowed": "stowed",
52
+
53
+ "{{LOCALE_PREFIX}}.Settings.showTutorial.name": "Show tutorial",
54
+ "{{LOCALE_PREFIX}}.Settings.showTutorial.hint": "Display the welcome banner on world load."
55
+ }
@@ -0,0 +1,27 @@
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.10.0",
16
+ "@vttforge/styles": "^0.3.0"
17
+ },
18
+ "devDependencies": {
19
+ "@vttforge/cli": "^0.5.0",
20
+ "@vttforge/vite-plugin": "^0.4.0",
21
+ "typescript": "^5.4.0",
22
+ "vite": "^8.2.2"
23
+ },
24
+ "engines": {
25
+ "node": ">=26"
26
+ }
27
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * CharacterData — typed schema for the `character` Actor type.
3
+ *
4
+ * Quick stats are HP / AC / SPD / INIT, abilities are the six classic
5
+ * scores (str/dex/con/int/wis/cha). Derived state (modifiers, max HP,
6
+ * armor class, initiative) is computed in-memory in `prepareDerivedData`.
7
+ *
8
+ * `prepareBaseData` initialises fields that Active Effects need to mutate
9
+ * (base max HP before any AE bonus); `prepareDerivedData` then computes
10
+ * the AE-aware values (modifiers, percentages, totals). Never write to the
11
+ * database in either hook — they're purely in-memory derivations.
12
+ */
13
+ import { BaseTypeDataModel, fields } from '@vttforge/core';
14
+
15
+ export class CharacterData extends BaseTypeDataModel() {
16
+ static defineSchema() {
17
+ const f = fields();
18
+ // The six ability scores are written once, as a factory rather than a
19
+ // shared options object: a field keeps the options it was handed, and
20
+ // some field classes write back into them.
21
+ const score = () =>
22
+ new f.NumberField({
23
+ required: true,
24
+ nullable: false,
25
+ integer: true,
26
+ min: 1,
27
+ max: 30,
28
+ initial: 10,
29
+ });
30
+ return {
31
+ level: new f.NumberField({
32
+ required: true,
33
+ nullable: false,
34
+ integer: true,
35
+ min: 1,
36
+ max: 20,
37
+ initial: 1,
38
+ }),
39
+ speed: new f.NumberField({
40
+ required: true,
41
+ nullable: false,
42
+ integer: true,
43
+ min: 0,
44
+ initial: 30,
45
+ }),
46
+ abilities: new f.SchemaField({
47
+ str: score(),
48
+ dex: score(),
49
+ con: score(),
50
+ int: score(),
51
+ wis: score(),
52
+ cha: score(),
53
+ }),
54
+ health: new f.SchemaField({
55
+ value: new f.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 10 }),
56
+ max: new f.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 10 }),
57
+ }),
58
+ power: new f.SchemaField({
59
+ value: new f.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 5 }),
60
+ max: new f.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 5 }),
61
+ }),
62
+ biography: new f.HTMLField(),
63
+ };
64
+ }
65
+
66
+ prepareDerivedData() {
67
+ // biome-ignore lint/suspicious/noExplicitAny: schema-driven types ship in a later @vttforge/types release
68
+ const self = this as any;
69
+ for (const [key, value] of Object.entries(self.abilities)) {
70
+ const score = typeof value === 'number' ? value : ((value as { value?: number })?.value ?? 10);
71
+ const mod = Math.floor((score - 10) / 2);
72
+ self.abilities[key] = { value: score, mod };
73
+ }
74
+
75
+ const conMod = self.abilities.con.mod;
76
+ self.health.max = 10 + self.level + conMod;
77
+
78
+ self.armorClass = 10 + self.abilities.dex.mod;
79
+ self.initiative = self.abilities.dex.mod;
80
+ }
81
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GearData — typed schema for the `gear` Item type.
3
+ *
4
+ * `kind` is an enum that drives the inventory pill on the character sheet:
5
+ * `equipped` and `valued` render with the accent pill, `stowed` renders
6
+ * muted.
7
+ */
8
+ import { BaseTypeDataModel, fields } from '@vttforge/core';
9
+
10
+ const GEAR_KINDS = ['equipped', 'valued', 'stowed'] as const;
11
+
12
+ export class GearData extends BaseTypeDataModel() {
13
+ static defineSchema() {
14
+ const f = fields();
15
+ return {
16
+ quantity: new f.NumberField({
17
+ required: true,
18
+ nullable: false,
19
+ integer: true,
20
+ min: 0,
21
+ initial: 1,
22
+ }),
23
+ weight: new f.NumberField({
24
+ required: true,
25
+ nullable: false,
26
+ min: 0,
27
+ initial: 0,
28
+ }),
29
+ kind: new f.StringField({
30
+ required: true,
31
+ choices: GEAR_KINDS as unknown as string[],
32
+ initial: 'stowed',
33
+ }),
34
+ description: new f.HTMLField(),
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,33 @@
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
+ * pnpm add -D 'github:League-of-Foundry-Developers/foundry-vtt-types#main'
10
+ *
11
+ * Then declare `types: ["fvtt-types"]` in `tsconfig.json` and delete this
12
+ * file.
13
+ */
14
+ /* eslint-disable @typescript-eslint/no-explicit-any */
15
+
16
+ declare global {
17
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
18
+ const game: any;
19
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
20
+ const CONFIG: any;
21
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
22
+ const Hooks: any;
23
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
24
+ const ui: any;
25
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
26
+ const ChatMessage: any;
27
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
28
+ const Roll: any;
29
+ // biome-ignore lint/suspicious/noExplicitAny: stub declarations only
30
+ const foundry: any;
31
+ }
32
+
33
+ export {};