@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,82 @@
1
+ /**
2
+ * {{TITLE}} — entry point.
3
+ *
4
+ * `registerSystem` from `@vttforge/core` replaces the ~60-line
5
+ * `Hooks.once("init", ...)` block most systems copy-paste from each other:
6
+ * data model registration, document class swap, initiative formula, sheet
7
+ * registration, settings, and the `Hooks.once("ready", ...)` migration
8
+ * gate are all wired by one call.
9
+ */
10
+ import './foundry-globals.js';
11
+ import { registerSystem, SystemConfig, VttfError } from '@vttforge/core';
12
+ import { CharacterData } from './data/character-data.js';
13
+ import { GearData } from './data/gear-data.js';
14
+ import { migrations } from './migrations.js';
15
+ import { CharacterSheet } from './sheets/character-sheet.js';
16
+ import { GearSheet } from './sheets/gear-sheet.js';
17
+
18
+ const SYSTEM_ID = '{{ID}}';
19
+
20
+ const settings = new SystemConfig(SYSTEM_ID);
21
+
22
+ try {
23
+ registerSystem({
24
+ id: SYSTEM_ID,
25
+ actorDataModels: { character: CharacterData },
26
+ itemDataModels: { gear: GearData },
27
+ combat: {
28
+ initiative: { formula: '1d20 + @abilities.dex.mod', decimals: 2 },
29
+ },
30
+ onAfterInit: () => {
31
+ settings.register('showTutorial', {
32
+ name: '{{LOCALE_PREFIX}}.Settings.showTutorial.name',
33
+ hint: '{{LOCALE_PREFIX}}.Settings.showTutorial.hint',
34
+ scope: 'client',
35
+ config: true,
36
+ type: Boolean,
37
+ default: true,
38
+ });
39
+
40
+ // Register the schemaVersion setting so migrations.run() has somewhere
41
+ // to read and write the version flag.
42
+ migrations.register();
43
+
44
+ const { Actors, Items } = foundry.documents.collections;
45
+ Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
46
+ Actors.registerSheet(SYSTEM_ID, CharacterSheet, {
47
+ types: ['character'],
48
+ makeDefault: true,
49
+ label: '{{LOCALE_PREFIX}}.Sheet.Character.title',
50
+ });
51
+ Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
52
+ Items.registerSheet(SYSTEM_ID, GearSheet, {
53
+ types: ['gear'],
54
+ makeDefault: true,
55
+ label: '{{LOCALE_PREFIX}}.Sheet.Gear.title',
56
+ });
57
+ },
58
+ onReady: async () => {
59
+ if (!game.user?.isGM) return;
60
+ try {
61
+ const ran = await migrations.run();
62
+ if (ran.length > 0) {
63
+ // biome-ignore lint/suspicious/noConsole: console.info is the only Foundry-portable info logger
64
+ console.info(`[${SYSTEM_ID}] applied migrations:`, ran.join(', '));
65
+ }
66
+ } catch (err) {
67
+ if (err instanceof VttfError) {
68
+ ui.notifications?.error(`[${err.code}] ${err.message} — see ${err.docsUrl}`, {
69
+ permanent: true,
70
+ });
71
+ }
72
+ throw err;
73
+ }
74
+ },
75
+ });
76
+ } catch (err) {
77
+ if (err instanceof VttfError) {
78
+ // biome-ignore lint/suspicious/noConsole: bootstrap-time failures must surface
79
+ console.error(`[${err.code}] ${err.message} — see ${err.docsUrl}`);
80
+ }
81
+ throw err;
82
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Migration registry for {{TITLE}}.
3
+ *
4
+ * Each entry is idempotent — safe to re-run if a prior pass failed
5
+ * mid-sequence. `createMigrationRunner` reads the version flag declared in
6
+ * `system.json` (`flags.{{ID}}.needsMigrationVersion`), runs every
7
+ * migration whose version is newer than what's stored in the world's
8
+ * `schemaVersion` setting, and persists the new high-water mark on success.
9
+ */
10
+ import { createMigrationRunner } from '@vttforge/core';
11
+
12
+ const SYSTEM_ID = '{{ID}}';
13
+
14
+ /**
15
+ * v0.1.0 — first real schema. Add real migration logic here as soon as the
16
+ * first breaking schema change ships. Until then, this is a no-op example
17
+ * that proves the runner end-to-end.
18
+ */
19
+ async function migrateToV0_1_0(): Promise<void> {
20
+ // Iterate world documents and rewrite legacy fields here. Example:
21
+ //
22
+ // const actors = game.actors?.filter((a) => a.type === 'character') ?? [];
23
+ // for (const actor of actors) {
24
+ // const legacy = actor.system?.bio;
25
+ // if (typeof legacy !== 'string') continue;
26
+ // await actor.update({
27
+ // 'system.biography': legacy,
28
+ // '-=system.bio': null,
29
+ // });
30
+ // }
31
+ }
32
+
33
+ export const migrations = createMigrationRunner({
34
+ systemId: SYSTEM_ID,
35
+ compatibleVersion: '0.0.0',
36
+ migrations: [
37
+ {
38
+ version: '0.1.0',
39
+ description: 'initial schema — placeholder for first breaking change',
40
+ fn: migrateToV0_1_0,
41
+ },
42
+ ],
43
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * CharacterSheet — tabbed character sheet built on `BaseActorSheet()` from
3
+ * `@vttforge/core`. Demonstrates the full boilerplate-elimination surface:
4
+ *
5
+ * - `static TABS` — `context.tabs.<group>` auto-populated by BaseActorSheet
6
+ * (no manual `_prepareTabs` call).
7
+ * - `static DRAG_DROP` — wires `foundry.applications.ux.DragDrop` on first
8
+ * render with `isEditable`-gated permissions.
9
+ * - Typed `onDropItem(item, event)` — pre-resolves UUIDs, rejects
10
+ * non-`gear` items with a notification, lets `gear` flow through to
11
+ * Foundry's default `_onDropItem` by returning `undefined`.
12
+ */
13
+ import { BaseActorSheet } from '@vttforge/core';
14
+
15
+ const SYSTEM_ID = '{{ID}}';
16
+
17
+ interface AbilityViewModel {
18
+ key: string;
19
+ label: string;
20
+ value: number;
21
+ mod: number;
22
+ }
23
+
24
+ interface GearViewModel {
25
+ id: string;
26
+ name: string;
27
+ img: string;
28
+ kind: string;
29
+ quantity: number;
30
+ weight: number;
31
+ description: string;
32
+ }
33
+
34
+ // biome-ignore lint/suspicious/noExplicitAny: typed sheet bases ship in a later @vttforge/types release
35
+ const Base = BaseActorSheet() as any;
36
+
37
+ export class CharacterSheet extends Base {
38
+ static DEFAULT_OPTIONS = foundry.utils.mergeObject(
39
+ Base.DEFAULT_OPTIONS,
40
+ {
41
+ id: '{{ID}}-character',
42
+ classes: ['{{ID}}', 'sheet', 'actor', 'character'],
43
+ window: {
44
+ title: '{{LOCALE_PREFIX}}.Sheet.Character.title',
45
+ icon: 'fa-solid fa-user',
46
+ },
47
+ position: { width: 640, height: 700 },
48
+ actions: {
49
+ rollAbility: CharacterSheet._onRollAbility,
50
+ createGear: CharacterSheet._onCreateGear,
51
+ deleteItem: CharacterSheet._onDeleteItem,
52
+ },
53
+ },
54
+ { inplace: false },
55
+ );
56
+
57
+ static PARTS = {
58
+ sheet: {
59
+ template: `systems/${SYSTEM_ID}/templates/actor/character-sheet.hbs`,
60
+ },
61
+ };
62
+
63
+ static TABS = {
64
+ primary: {
65
+ tabs: [
66
+ {
67
+ id: 'abilities',
68
+ group: 'primary',
69
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.abilities',
70
+ icon: 'fa-solid fa-dumbbell',
71
+ },
72
+ {
73
+ id: 'inventory',
74
+ group: 'primary',
75
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.inventory',
76
+ icon: 'fa-solid fa-sack',
77
+ },
78
+ {
79
+ id: 'spells',
80
+ group: 'primary',
81
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.spells',
82
+ icon: 'fa-solid fa-wand-magic-sparkles',
83
+ },
84
+ {
85
+ id: 'biography',
86
+ group: 'primary',
87
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.biography',
88
+ icon: 'fa-solid fa-feather',
89
+ },
90
+ ],
91
+ initial: 'abilities',
92
+ },
93
+ };
94
+
95
+ static DRAG_DROP = [
96
+ {
97
+ dragSelector: '.sh-item[draggable=true]',
98
+ dropSelector: '.sh-body',
99
+ },
100
+ ];
101
+
102
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
103
+ async _prepareContext(options: any) {
104
+ const context = await super._prepareContext(options);
105
+ const actor = (this as unknown as { document: any }).document;
106
+ const system = actor.system;
107
+ const abilityLabels: Record<string, string> = {
108
+ str: '{{LOCALE_PREFIX}}.Ability.str',
109
+ dex: '{{LOCALE_PREFIX}}.Ability.dex',
110
+ con: '{{LOCALE_PREFIX}}.Ability.con',
111
+ int: '{{LOCALE_PREFIX}}.Ability.int',
112
+ wis: '{{LOCALE_PREFIX}}.Ability.wis',
113
+ cha: '{{LOCALE_PREFIX}}.Ability.cha',
114
+ };
115
+ context.actor = actor;
116
+ context.system = system;
117
+ context.isEditable = (this as unknown as { isEditable: boolean }).isEditable;
118
+ context.abilities = Object.entries(system.abilities ?? {}).map(
119
+ ([key, data]): AbilityViewModel => {
120
+ const ability = data as { value?: number; mod?: number } | number;
121
+ const value = typeof ability === 'number' ? ability : (ability?.value ?? 10);
122
+ const mod = typeof ability === 'number' ? 0 : (ability?.mod ?? 0);
123
+ return {
124
+ key,
125
+ label: game.i18n.localize(abilityLabels[key] ?? key),
126
+ value,
127
+ mod,
128
+ };
129
+ },
130
+ );
131
+ context.gear = actor.items
132
+ .filter((item: { type: string }) => item.type === 'gear')
133
+ .map(
134
+ (item: {
135
+ id: string;
136
+ name: string;
137
+ img: string;
138
+ system?: { kind?: string; quantity?: number; weight?: number; description?: string };
139
+ }): GearViewModel => ({
140
+ id: item.id,
141
+ name: item.name,
142
+ img: item.img,
143
+ kind: item.system?.kind ?? 'stowed',
144
+ quantity: item.system?.quantity ?? 1,
145
+ weight: item.system?.weight ?? 0,
146
+ description: item.system?.description ?? '',
147
+ }),
148
+ );
149
+ context.enrichedBiography = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
150
+ system.biography ?? '',
151
+ { relativeTo: actor, secrets: actor.isOwner },
152
+ );
153
+ return context;
154
+ }
155
+
156
+ /** Reject non-gear drops with a notification; let gear fall through. */
157
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
158
+ async onDropItem(item: any, _event: unknown): Promise<false | undefined> {
159
+ if (item?.type !== 'gear') {
160
+ ui.notifications?.warn(
161
+ game.i18n.format('{{LOCALE_PREFIX}}.Sheet.Drop.rejected', { type: item?.type ?? 'unknown' }),
162
+ );
163
+ return false;
164
+ }
165
+ return undefined;
166
+ }
167
+
168
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
169
+ static async _onRollAbility(this: any, _event: Event, target: HTMLElement) {
170
+ const key = target?.dataset?.ability;
171
+ if (!key) return;
172
+ const actor = this.document;
173
+ const mod = actor.system.abilities?.[key]?.mod ?? 0;
174
+ const roll = new Roll(`1d20 + ${mod}`, actor.getRollData());
175
+ await roll.evaluate();
176
+ await roll.toMessage({
177
+ speaker: ChatMessage.getSpeaker({ actor }),
178
+ flavor: game.i18n.format('{{LOCALE_PREFIX}}.Sheet.Roll.flavor', {
179
+ ability: game.i18n.localize(`{{LOCALE_PREFIX}}.Ability.${key}`),
180
+ }),
181
+ });
182
+ }
183
+
184
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
185
+ static async _onCreateGear(this: any, _event: Event, _target: HTMLElement) {
186
+ const cls = CONFIG.Item.documentClass;
187
+ const parent = this.document;
188
+ await cls.create(
189
+ { name: game.i18n.localize('{{LOCALE_PREFIX}}.Sheet.NewGearName'), type: 'gear' },
190
+ { parent, renderSheet: true },
191
+ );
192
+ }
193
+
194
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
195
+ static async _onDeleteItem(this: any, _event: Event, target: HTMLElement) {
196
+ const row = target?.closest('[data-item-id]') as HTMLElement | null;
197
+ const id = row?.dataset?.itemId;
198
+ if (!id) return;
199
+ const item = this.document.items.get(id);
200
+ await item?.delete();
201
+ }
202
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * GearSheet — minimal Item sheet built on `BaseItemSheet()` from
3
+ * `@vttforge/core`. Single PART, single TABS group, no DRAG_DROP — exercises
4
+ * the mirror surface of BaseActorSheet on ItemSheetV2.
5
+ */
6
+ import { BaseItemSheet } from '@vttforge/core';
7
+
8
+ const SYSTEM_ID = '{{ID}}';
9
+
10
+ // biome-ignore lint/suspicious/noExplicitAny: typed sheet bases ship in a later @vttforge/types release
11
+ const Base = BaseItemSheet() as any;
12
+
13
+ export class GearSheet extends Base {
14
+ static DEFAULT_OPTIONS = foundry.utils.mergeObject(
15
+ Base.DEFAULT_OPTIONS,
16
+ {
17
+ id: '{{ID}}-gear',
18
+ classes: ['{{ID}}', 'sheet', 'item', 'gear'],
19
+ window: {
20
+ title: '{{LOCALE_PREFIX}}.Sheet.Gear.title',
21
+ icon: 'fa-solid fa-sack',
22
+ },
23
+ position: { width: 480, height: 420 },
24
+ },
25
+ { inplace: false },
26
+ );
27
+
28
+ static PARTS = {
29
+ sheet: {
30
+ template: `systems/${SYSTEM_ID}/templates/item/gear-sheet.hbs`,
31
+ },
32
+ };
33
+
34
+ static TABS = {
35
+ primary: {
36
+ tabs: [
37
+ {
38
+ id: 'details',
39
+ group: 'primary',
40
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.details',
41
+ icon: 'fa-solid fa-list',
42
+ },
43
+ {
44
+ id: 'description',
45
+ group: 'primary',
46
+ label: '{{LOCALE_PREFIX}}.Sheet.Tabs.description',
47
+ icon: 'fa-solid fa-feather',
48
+ },
49
+ ],
50
+ initial: 'details',
51
+ },
52
+ };
53
+
54
+ // biome-ignore lint/suspicious/noExplicitAny: see @vttforge/types follow-up
55
+ async _prepareContext(options: any) {
56
+ const context = await super._prepareContext(options);
57
+ const item = (this as unknown as { document: any }).document;
58
+ context.item = item;
59
+ context.system = item.system;
60
+ context.isEditable = (this as unknown as { isEditable: boolean }).isEditable;
61
+ context.enrichedDescription =
62
+ await foundry.applications.ux.TextEditor.implementation.enrichHTML(
63
+ item.system?.description ?? '',
64
+ { relativeTo: item, secrets: item.isOwner },
65
+ );
66
+ return context;
67
+ }
68
+ }