@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,350 @@
1
+ # @vttforge/cli
2
+
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3eb8c96: `vttforge init` can now run without a terminal.
8
+
9
+ Five values — id, title, description, author, license — were always asked for, with no flag to supply them. Clack prompts read from stdin, so with none attached the scaffolder hung: no error, no output, nothing to debug. That ruled out CI and scripts, and meant nothing could test the scaffolder through its own entry point.
10
+
11
+ Each now has a flag, and `--yes` (`-y`) takes the default for anything not passed. A run with no terminal assumes `--yes`, so CI needs no extra flag.
12
+
13
+ ```bash
14
+ vttforge init pdf-character-sheet --type module --lang ts \
15
+ --title "PDF Character Sheet" --license Apache-2.0 --yes
16
+ ```
17
+
18
+ The directory name is the one thing with no sensible default. Without a terminal to ask, the run stops and says so.
19
+
20
+ ### Patch Changes
21
+
22
+ - 11187a0: Say what the scaffolded schemas mean.
23
+
24
+ The generated number fields declared `required: true` but left `nullable` alone, so they still accepted `null` — and now that the types say so, that shows up as an error the author has to think about. They set `nullable: false`.
25
+
26
+ The six ability scores share one spec instead of repeating it six times.
27
+ - 353633c: Build each ability score from a factory rather than one shared options object.
28
+
29
+ A field keeps a reference to the options it was handed, and some field classes write back into that object. Six fields sharing one object is safe for the field this template uses, but it is not a habit to teach in code people copy and adapt.
30
+ - c24b2e9: Point the templates at the core version this release publishes.
31
+
32
+ The `ColorField` and presence fixes take `@vttforge/core` to 0.6.0, and a
33
+ caret pins the minor on a 0.x version, so the templates' `^0.5.0` would not
34
+ have matched.
35
+
36
+ ## 0.4.0
37
+
38
+ ### Minor Changes
39
+
40
+ - 17a6b8c: Redraw only the sheets a template change actually affects.
41
+
42
+ A sheet declares its templates in `static PARTS`, so the mapping is on the
43
+ open window itself — no build-time graph needed. Each part names a primary
44
+ `template` and may list further `templates`, which is where a partial it
45
+ pulls in appears. Only the affected parts re-render, so scroll position and
46
+ focus survive everywhere else. When nothing claims the file the whole screen
47
+ still redraws, because a partial can be reached without any part naming it.
48
+
49
+ The watcher now also skips a file whose content did not change. Vite rewrites
50
+ its entire output on every build, so a filesystem event alone means "the
51
+ bundler ran", not "the developer edited this" — and without this the language
52
+ file gets resent untouched on every save, and the JSON path redraws every open
53
+ window, undoing the scoping on the file that did change.
54
+
55
+ Measured in a running world with 21 applications open: editing one sheet
56
+ template went from 35 renders to 1.
57
+
58
+ ## 0.3.0
59
+
60
+ ### Minor Changes
61
+
62
+ - 5b72b4b: `vttforge dev` now applies saves in place, without a page refresh.
63
+
64
+ It opens a small WebSocket, links `@vttforge/dev-module` into Foundry's
65
+ modules directory, and watches the build output — sending a payload per
66
+ changed CSS, template or language file.
67
+
68
+ The socket is hand-written rather than pulled from a package. The CLI ships
69
+ to every consumer, so each dependency is one they install too, and what is
70
+ needed here is a narrow slice of the protocol: accept the upgrade, send
71
+ unmasked text frames, notice when a client leaves.
72
+
73
+ Watching `dist/` rather than hooking into Vite keeps this correct across
74
+ bundler versions, and gives the served path directly — `dist/` is what
75
+ Foundry mounts.
76
+
77
+ Failing to start the bridge costs hot reload, not the dev loop. A busy port
78
+ or a missing companion package prints why and carries on watching.
79
+
80
+ New: `--hmr-port` to move the bridge off 31313.
81
+
82
+ ### Patch Changes
83
+
84
+ - 1fb967d: Close the three soundness gaps `vttforge audit` shipped with, and a fourth
85
+ found while fixing them.
86
+
87
+ - `filePathFields` is an object whose keys are the field paths, not a flat
88
+ array. Reading it as an array dropped every correctly declared path, so
89
+ rule 004 reported declared fields as missing.
90
+ - Modules register subtypes as `<moduleId>.<type>` while the manifest
91
+ declares the bare key. The two were compared verbatim, so no module
92
+ registration ever matched its own declaration.
93
+ - Rule 007 searched every schema in the project for the token attribute.
94
+ Token bars read `actor.system`, so an Item model declaring the same path
95
+ satisfied the check on a system whose bars were in fact broken. The
96
+ narrowing applies only when Actor registrations are known; with none
97
+ found the rule falls back to the previous behaviour.
98
+ - Registrations through bracket notation were invisible to the scanner
99
+ entirely — which is the only form modules can use, since a dot in the key
100
+ rules out property access.
101
+ - c90357e: Point the templates at the CLI version this release publishes.
102
+
103
+ The templates asked for `@vttforge/cli@^0.2.0` while the release takes it to
104
+ 0.3.0. A caret pins the minor on a 0.x version, so that range would not have
105
+ matched — every project scaffolded after the release would ask for a version
106
+ the release had just superseded.
107
+
108
+ ## 0.2.0
109
+
110
+ ### Minor Changes
111
+
112
+ - 9462144: Require Node 26.
113
+
114
+ The floor moves from `>=22.14.0` to `>=26.0.0` across every package and the
115
+ four scaffolding templates, and the bundler target for the Node-side
116
+ packages moves from `node22` to `node26`.
117
+
118
+ Node 22 entered maintenance in October 2025 and receives security fixes
119
+ only. Node 26 becomes the active LTS line on 2026-10-28.
120
+
121
+ This is breaking for anyone on Node 22 or 24. It is marked `minor` rather
122
+ than `major` on purpose: these packages are still on 0.x, where a minor
123
+ signals the break, and a major would push every package to 1.0.0 — a claim
124
+ of API stability that has not been audited, on packages two of which are
125
+ still stubs.
126
+
127
+ The templates move to the versions this release publishes. On 0.x a caret
128
+ pins the minor, so their old ranges would not have matched.
129
+
130
+ CI now pins Node through `actions/setup-node` instead of inheriting whatever
131
+ the runner image ships, so the version the packages declare is the version
132
+ they are tested on. It was not before: the workflow took the image's Node,
133
+ and nothing enforced the declared floor because `engine-strict` is not set.
134
+
135
+ ## 0.1.1
136
+
137
+ ### Patch Changes
138
+
139
+ - 5f03da9: Move to citty 0.2 and cover argument parsing with tests.
140
+
141
+ citty 0.2 is ESM-only, drops its runtime dependencies, and rewrites the
142
+ parser on top of `node:util.parseArgs`. Parsing behaviour is unchanged for
143
+ every flag this CLI defines — negation, aliases, positionals, `--flag=value`
144
+ and boolean defaults all produce identical results on 0.1.6 and 0.2.2.
145
+
146
+ The command tree moves from `bin.ts` to `cli.ts`, leaving the bin as the
147
+ launch only. The definitions were unreachable from tests before, because a
148
+ module that calls `runMain` on import runs the CLI. They are now covered,
149
+ including `--no-install` and `--no-git`, which would have made the
150
+ scaffolder install dependencies for someone who asked it not to had the
151
+ parser rewrite changed them.
152
+
153
+ ## 0.1.0
154
+
155
+ ### Minor Changes
156
+
157
+ - 139802c: feat(cli): ship `vttforge audit` — scan for seven v13 footguns
158
+
159
+ Closes the third and final slice of Track 1. `vttforge audit [path]` scans
160
+ a system or module project root and reports findings against the
161
+ `VTTF-AUDIT-NNN` catalog of v13 manifest + code footguns.
162
+
163
+ **Rules implemented:**
164
+
165
+ | ID | Severity | Scope | What it catches |
166
+ | ---------------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | `VTTF-AUDIT-001` | HIGH | manifest | `flags.hotReload` as array (v12) or missing required `extensions` key. Silently disables HMR. |
168
+ | `VTTF-AUDIT-002` | MEDIUM | manifest | Top-level `gridDistance` / `gridUnits` (v12) instead of `grid: {type, distance, units, diagonals}` (v13). Auto-migrates today, removed in v14. |
169
+ | `VTTF-AUDIT-003` | LOW | manifest | `styles: ["foo.css"]` (string array) instead of `[{src, layer?}]` (v13). Auto-migrates but loses cascade-layer control. |
170
+ | `VTTF-AUDIT-004` | MEDIUM | manifest + source | `HTMLField` / `FilePathField` declared in TypeDataModel schema but missing from `documentTypes.<Doc>.<subtype>.htmlFields` / `.filePathFields`. Server only sanitises declared paths → XSS risk on undeclared. Subtype-aware via `CONFIG.*.dataModels` + `registerSystem({ actorDataModels })` parsing; full-path matching through SchemaField nesting. |
171
+ | `VTTF-AUDIT-005` | MEDIUM | source | `class X extends TypeDataModel` without a `prepareBaseData()` method. Active Effects apply between base and derived; consumers see uninitialised fields. Checked per class. |
172
+ | `VTTF-AUDIT-006` | LOW | source | `_addDataFieldMigrations()` override on a TypeDataModel subclass. Real API is `static migrateData(source)` calling singular `super._addDataFieldMigration(...)`. |
173
+ | `VTTF-AUDIT-007` | MEDIUM | manifest + source | `primaryTokenAttribute` / `secondaryTokenAttribute` doesn't resolve to a `SchemaField({ value, max })` at the exact dot-path. Nested paths like `attributes.hp` are traversed. |
174
+
175
+ **CLI surface:**
176
+
177
+ ```bash
178
+ vttforge audit # scan cwd, print markdown
179
+ vttforge audit ./my-project # scan a different path
180
+ vttforge audit --json # machine-readable JSON for CI piping
181
+ vttforge audit --strict # exit 1 on any finding (default: HIGH only)
182
+ ```
183
+
184
+ **Exit codes:**
185
+ - `0` — clean, or only MEDIUM/LOW findings (advisory mode)
186
+ - `1` — at least one HIGH finding, or any finding in `--strict`
187
+
188
+ Uses `process.exitCode` (not `process.exit`) so piped JSON reports aren't
189
+ truncated on failing CI runs.
190
+
191
+ **Implementation notes:**
192
+ - Pure read-only — never modifies the source tree.
193
+ - Regex-based source scanning. The trade-off is occasional false negatives
194
+ on heavily-formatted code; an AST dependency (TypeScript compiler) would
195
+ add ~30MB to the CLI for seven pattern checks. Rule 007's
196
+ `SchemaField({value, max})` detection uses a balanced-brace scan plus a
197
+ depth-tracking top-level-key extractor to correctly distinguish
198
+ SchemaField siblings from nested constructor options.
199
+ - Rule 005 distinguishes `extends TypeDataModel` (direct, flagged) from
200
+ `extends BaseTypeDataModel()` (VTTForge factory, safe) via the `\b`
201
+ word boundary, and is checked per class so a sibling subclass without
202
+ the hook doesn't hide behind a sibling that has it.
203
+ - Rule 004 takes the FULL schema path through enclosing SchemaField
204
+ wrappers and matches exactly against declared paths (with `system.`
205
+ prefix stripped — Foundry convention).
206
+
207
+ **Tests:** 247 passing (+13 new test files, ~110 new test cases). Integration
208
+ suite scaffolds each of the four templates and runs the audit against the
209
+ output — they must report zero findings, otherwise either a template
210
+ regressed or the audit grew a false positive.
211
+
212
+ - 09de435: feat(cli): ship `vttforge dev` and `vttforge build`
213
+
214
+ `@vttforge/cli` graduates from "scaffold-only" to a full local dev loop.
215
+
216
+ **`vttforge dev`** — single-process dev experience for system/module work:
217
+ 1. Runs `vite build` once to populate `dist/` and emit the manifest.
218
+ 2. Reads the manifest, detects whether the project is a system or module.
219
+ 3. Resolves the Foundry user-data directory via a four-step chain:
220
+ `--data-dir` flag → `FOUNDRY_DATA_DIR` env → project's
221
+ `.vttforge/config.json` → OS default with interactive first-run
222
+ prompt that saves the choice. OS detection honors `XDG_DATA_HOME`
223
+ on Linux and `%LOCALAPPDATA%` on Windows.
224
+ 4. Drops a symlink at `<dataRoot>/Data/<systems|modules>/<id>` pointing
225
+ at the project's `dist/`. Cross-platform — `junction` on Windows so
226
+ the call succeeds without Developer Mode or admin elevation; safety
227
+ rails refuse to overwrite real files/directories.
228
+ 5. Spawns `vite build --watch` with inherited stdio, blocks until
229
+ SIGINT/SIGTERM, then cleans up the symlink and kills vite.
230
+
231
+ When Foundry runs with `--hotReload`, file saves trigger live reload
232
+ without a browser refresh — chokidar follows the symlink and Foundry's
233
+ built-in dispatcher swaps CSS/HBS/JSON in place. No additional client
234
+ wiring needed in this release.
235
+
236
+ **`vttforge build`** — produces a foundryvtt.com-ready release zip:
237
+ 1. Cleans `dist/`, runs `vite build` in production mode.
238
+ 2. Reads the manifest for `id` + `version`.
239
+ 3. Emits `<id>-<version>.zip` at the project root with contents at the
240
+ zip root (no wrapper folder), pulling in `LICENSE`, `README.md`, and
241
+ `CHANGELOG.md` from the project root when present and not already
242
+ inside `dist/`.
243
+
244
+ **Templates updated** — all four (system-ts, system-js, module-ts,
245
+ module-js) now ship `pnpm dev` / `pnpm build` aliased to the new
246
+ commands, drop the manual `ln -s` instructions, and gain
247
+ `@vttforge/cli` as a devDependency. The release workflow continues to
248
+ invoke vite directly so URL injection happens after build but before
249
+ zipping; it now validates tag-derived version strings and passes every
250
+ GitHub-supplied value via `env:` instead of inline interpolation to
251
+ close a command-injection vector that a malicious tag push could
252
+ otherwise exploit.
253
+
254
+ **Internals exposed** — `runDev`, `runBuild`, `emitReleaseZip`,
255
+ `setupDevSymlink`, `resolveFoundryDataDir`, `createLink`/`removeLink`,
256
+ `readManifest`, and `emitZip` are all importable from `@vttforge/cli`
257
+ for consumers building higher-level tooling.
258
+
259
+ **Dependencies** — adds `archiver` (zip emission) pinned to `^7` since
260
+ archiver v8 went ESM-only with renamed exports that DefinitelyTyped
261
+ hasn't caught up to yet.
262
+
263
+ - eb1c41d: feat(cli): ship the `vttforge init` scaffolder + four built-in templates
264
+
265
+ `@vttforge/cli` now provides a real, interactive scaffolder. `vttforge init`
266
+ (or `pnpm create vttforge` via the new `create-vttforge` package) walks the
267
+ user through naming the project, picking system/module + TypeScript/JavaScript,
268
+ and writing a complete VTTForge starter into a new directory.
269
+
270
+ **Bin commands:**
271
+ - `vttforge init [name]` — interactive scaffolder. Honors `--type`, `--lang`,
272
+ `--no-install`, `--no-git` flags; everything else is prompted for via
273
+ `@clack/prompts`. Detects the calling package manager via
274
+ `npm_config_user_agent` (pnpm / npm / bun / yarn) and offers to run
275
+ `<pm> install` after writing files. Optionally initializes a git repo
276
+ with an initial commit.
277
+ - `vttforge dev`, `vttforge build`, `vttforge audit` — reserved subcommands
278
+ that print a discoverable next-step message until their real
279
+ implementations land in a later release.
280
+
281
+ **Four built-in templates** under `packages/cli/templates/`:
282
+ - `system-ts` / `system-js` — realistic Foundry v13+ system that mirrors
283
+ the reference `examples/simple-system`. Ships `CharacterData` + `GearData`
284
+ TypeDataModels, `CharacterSheet` + `GearSheet` extending `BaseActorSheet`
285
+ / `BaseItemSheet`, a `createMigrationRunner` with one example migration,
286
+ Handlebars templates for both sheets, the four v13 manifest shapes
287
+ (`grid`, `styles: [{src}]`, `flags.hotReload` at the root of `flags`,
288
+ per-subtype `htmlFields`), and a tagged-release GitHub Actions workflow
289
+ that builds + zips + publishes to a GitHub Release.
290
+ - `module-ts` / `module-js` — minimal but realistic module. Registers a
291
+ client-scope setting via `SystemConfig` from `@vttforge/core`, listens on
292
+ `renderActorSheetV2` to tag the rendered sheet with a discreet badge,
293
+ exposes a tiny API on `game.modules.get(id).api`, and ships the same
294
+ tag-driven release workflow.
295
+
296
+ **New package — `create-vttforge`** — thin wrapper around `@vttforge/cli init`
297
+ that enables the `pnpm create vttforge my-system` / `npm create vttforge@latest my-system`
298
+ UX familiar from the `create-vite` / `create-next-app` ecosystem.
299
+
300
+ 44 unit tests cover the substitution logic, the package-manager detector, and
301
+ a per-template integration suite that scaffolds every variant into a temporary
302
+ directory and asserts shape (no leftover placeholders, valid manifest,
303
+ expected files). Templates are excluded from biome and vitest scanning since
304
+ they contain `{{PLACEHOLDER}}` strings that aren't valid TypeScript/JSON until
305
+ substitution.
306
+
307
+ - 4d34985: Move to Vite 8.
308
+
309
+ The plugin's `vite` peer range now reads `^8.2.2`, so a project still on
310
+ Vite 6 or 7 will not satisfy it. The four `vttforge init` templates move
311
+ with it, which keeps a freshly scaffolded project free of a peer conflict.
312
+
313
+ The plugin itself needed no code change — it uses only the `config` hook
314
+ and the `Plugin` and `UserConfig` types, all unchanged across the two
315
+ majors.
316
+
317
+ ### Patch Changes
318
+
319
+ - 6e36de2: Move the release-zip writer to archiver 8.
320
+
321
+ Archiver 8 is ESM-only and dropped its default export, so the old
322
+ `archiver('zip', options)` factory call is gone. The writer now builds the
323
+ named `ZipArchive` class directly. Same options, same output: package
324
+ contents sit at the zip root with no wrapper folder, and LICENSE, README
325
+ and CHANGELOG still ride along from the project root when present.
326
+
327
+ - edd88fe: Move the prompts to `@clack/prompts` 1.x, and fix a validator that let a
328
+ missing package id through.
329
+
330
+ Clack 1.x hands validators `string | undefined` instead of `string`, since
331
+ it calls them before anything is typed. Widening the signatures surfaced a
332
+ latent bug in the package-id check: it called `RegExp.test` on the raw
333
+ value, and an undefined value coerces to the string `"undefined"`, which
334
+ matches the allowed pattern. A missing id therefore passed validation. The
335
+ check now coalesces first.
336
+
337
+ The three validators are exported and covered by tests. They were only
338
+ reachable through the interactive prompt before, so nothing exercised them.
339
+
340
+ - 4ba725c: Point the scaffolding templates at the versions this release publishes.
341
+
342
+ The templates asked for `@vttforge/core@^0.3.0` and
343
+ `@vttforge/vite-plugin@^0.1.0`. On a 0.x version a caret pins the minor, so
344
+ `^0.3.0` means `>=0.3.0 <0.4.0` — and this release takes core to 0.4.0 and
345
+ the plugin to 0.2.0. Left alone, every project scaffolded after the release
346
+ would ask for versions the release had just superseded.
347
+
348
+ ## 0.0.1
349
+
350
+ Initial placeholder release — package name reserved on npm. Implementation lands in v0.3.0.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabricio Cavalcante de Souza and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,39 @@
1
1
  # @vttforge/cli
2
2
 
3
- Command-line tool to scaffold and build FoundryVTT systems and modules.
3
+ VTTForge CLI scaffolding, dev loop, and release builds for Foundry VTT v13+ systems and modules.
4
4
 
5
- > 🚧 **Placeholder release.** This is a reserved name on the npm registry. The first usable version is coming soon.
5
+ ## Commands
6
6
 
7
- Track progress and contribute at **https://github.com/vttforge/vttforge**.
7
+ ```bash
8
+ vttforge init <name> [--type system|module] [--lang ts|js] [--no-install] [--no-git]
9
+ vttforge dev [--foundry-data <path>]
10
+ vttforge build
11
+ ```
8
12
 
9
- ---
13
+ - `vttforge init` — interactive scaffolder. Asks for package id, title, author, license, Foundry compatibility version, and writes a runnable system/module starter into `<name>/`. Detects the calling package manager (`pnpm`, `npm`, `bun`, `yarn`) via `npm_config_user_agent` and offers to install deps + `git init`. Equivalent UX from `pnpm create vttforge <name>` via the `create-vttforge` package.
14
+ - `vttforge dev` — one-shot `vite build`, then symlink `dist/` into your Foundry user-data directory under `Data/<systems|modules>/<id>/`, then `vite build --watch` with inherited stdio. Cleans up the symlink on `Ctrl-C`. First run prompts for the Foundry data dir and saves the choice to `.vttforge/config.json`; subsequent runs skip the prompt. Override with `--foundry-data <path>` (alias: `--data-dir`) or set `FOUNDRY_DATA_DIR` in your environment.
15
+ - `vttforge build` — production `vite build` followed by `<id>-<version>.zip` emission at the project root. Pulls `LICENSE`, `README.md`, and `CHANGELOG.md` from the project root into the zip when present.
10
16
 
11
- VTTForge is an independent, community-developed project. It is not affiliated with, endorsed by, or sponsored by Foundry Gaming LLC. "Foundry Virtual Tabletop", "Foundry VTT", and "FVTT" are trademarks of Foundry Gaming LLC.
17
+ ## Library exports
18
+
19
+ `@vttforge/cli` also exposes its internals for consumers building higher-level tooling:
20
+
21
+ ```ts
22
+ import {
23
+ runInit, runDev, runBuild,
24
+ scaffold, substitute, templatesRoot,
25
+ resolveFoundryDataDir, foundryPackagesDir,
26
+ readManifest, emitZip,
27
+ createLink, removeLink, readLinkTarget,
28
+ resolveViteInvocation, runViteBuildOnce, spawnViteWatch,
29
+ detectPackageManager, detectProjectPackageManager, execInvocation, installCommand,
30
+ } from '@vttforge/cli';
31
+ ```
32
+
33
+ ## Foundry-aware HMR
34
+
35
+ Run Foundry with `--hotReload` (e.g. `foundryvtt --hotReload`) and `vttforge dev` becomes a live-reload loop without further setup: Foundry's built-in chokidar watcher follows the symlink, and its dispatcher swaps CSS / Handlebars / JSON files in place when vite re-emits them.
36
+
37
+ ## Stack
38
+
39
+ Citty (commands) + Clack (prompts) + Archiver (zip).
package/dist/bin.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/bin.mjs ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ import { M as runAuditCommand, c as runDev, n as ScaffoldError, r as runInit, t as VTTFORGE_CLI_VERSION, x as runBuild } from "./src-CWhUNM__.mjs";
3
+ import { defineCommand, runMain } from "citty";
4
+ //#region src/cli.ts
5
+ /**
6
+ * vttforge command tree.
7
+ *
8
+ * Kept apart from `bin.ts`, which only runs it. Argument definitions are the
9
+ * CLI's contract with its users — `--no-install` skipping the install, an
10
+ * alias resolving to its canonical name — and a module that calls `runMain`
11
+ * on import cannot be reached by a test. Exporting the commands from here
12
+ * lets the suite parse real argv against the real definitions.
13
+ */
14
+ const init = defineCommand({
15
+ meta: {
16
+ name: "init",
17
+ description: "Scaffold a new Foundry VTT system or module from a VTTForge template"
18
+ },
19
+ args: {
20
+ name: {
21
+ type: "positional",
22
+ description: "Directory name for the new system/module (also the default manifest id)",
23
+ required: false
24
+ },
25
+ type: {
26
+ type: "string",
27
+ description: "Package type: system | module"
28
+ },
29
+ lang: {
30
+ type: "string",
31
+ description: "Language: ts | js"
32
+ },
33
+ id: {
34
+ type: "string",
35
+ description: "Manifest id (defaults to a slug of the directory name)"
36
+ },
37
+ title: {
38
+ type: "string",
39
+ description: "Human-readable title shown in Foundry setup screens"
40
+ },
41
+ description: {
42
+ type: "string",
43
+ description: "One-line description for the manifest"
44
+ },
45
+ author: {
46
+ type: "string",
47
+ description: "Author name for the manifest (defaults to the git author)"
48
+ },
49
+ license: {
50
+ type: "string",
51
+ description: "SPDX license id (default MIT)"
52
+ },
53
+ yes: {
54
+ type: "boolean",
55
+ alias: "y",
56
+ default: false,
57
+ description: "Accept defaults for anything not passed, without prompting"
58
+ },
59
+ install: {
60
+ type: "boolean",
61
+ default: true,
62
+ description: "Install dependencies after scaffold (use --no-install to skip)"
63
+ },
64
+ git: {
65
+ type: "boolean",
66
+ default: true,
67
+ description: "Initialize a git repository after scaffold (use --no-git to skip)"
68
+ }
69
+ },
70
+ async run({ args }) {
71
+ try {
72
+ await runInit({
73
+ name: typeof args.name === "string" ? args.name : void 0,
74
+ type: typeof args.type === "string" ? args.type : void 0,
75
+ lang: typeof args.lang === "string" ? args.lang : void 0,
76
+ id: typeof args.id === "string" ? args.id : void 0,
77
+ title: typeof args.title === "string" ? args.title : void 0,
78
+ description: typeof args.description === "string" ? args.description : void 0,
79
+ author: typeof args.author === "string" ? args.author : void 0,
80
+ license: typeof args.license === "string" ? args.license : void 0,
81
+ yes: args.yes === true,
82
+ noInstall: args.install === false,
83
+ noGit: args.git === false
84
+ });
85
+ } catch (err) {
86
+ if (!(err instanceof ScaffoldError)) console.error(err instanceof Error ? err.message : String(err));
87
+ process.exit(1);
88
+ }
89
+ }
90
+ });
91
+ const dev = defineCommand({
92
+ meta: {
93
+ name: "dev",
94
+ description: "Symlink dist/ into Foundry Data and run vite build --watch"
95
+ },
96
+ args: {
97
+ "foundry-data": {
98
+ type: "string",
99
+ alias: "data-dir",
100
+ description: "Override the Foundry user-data directory (skips env / config / first-run prompt)"
101
+ },
102
+ "hmr-port": {
103
+ type: "string",
104
+ description: "Port for the hot reload bridge (default 31313)"
105
+ }
106
+ },
107
+ async run({ args }) {
108
+ const explicit = (typeof args["foundry-data"] === "string" ? args["foundry-data"] : void 0) ?? (typeof args["data-dir"] === "string" ? args["data-dir"] : void 0);
109
+ const rawPort = typeof args["hmr-port"] === "string" ? args["hmr-port"] : void 0;
110
+ const hmrPort = rawPort === void 0 ? void 0 : Number(rawPort);
111
+ if (hmrPort !== void 0 && !Number.isInteger(hmrPort)) {
112
+ console.error(`--hmr-port expects a whole number, got \`${rawPort}\`.`);
113
+ process.exit(1);
114
+ }
115
+ try {
116
+ await runDev({
117
+ dataDir: explicit,
118
+ hmrPort
119
+ });
120
+ } catch (err) {
121
+ console.error(err instanceof Error ? err.message : String(err));
122
+ process.exit(1);
123
+ }
124
+ }
125
+ });
126
+ const build = defineCommand({
127
+ meta: {
128
+ name: "build",
129
+ description: "Run vite build (production) and emit <id>-<version>.zip for foundryvtt.com"
130
+ },
131
+ async run() {
132
+ try {
133
+ await runBuild();
134
+ } catch (err) {
135
+ console.error(err instanceof Error ? err.message : String(err));
136
+ process.exit(1);
137
+ }
138
+ }
139
+ });
140
+ const audit = defineCommand({
141
+ meta: {
142
+ name: "audit",
143
+ description: "Scan a system/module project for the seven v13 manifest + code footguns"
144
+ },
145
+ args: {
146
+ path: {
147
+ type: "positional",
148
+ description: "Project root to scan (defaults to the current directory)",
149
+ required: false
150
+ },
151
+ json: {
152
+ type: "boolean",
153
+ default: false,
154
+ description: "Emit a machine-readable JSON report instead of markdown"
155
+ },
156
+ strict: {
157
+ type: "boolean",
158
+ default: false,
159
+ description: "Exit non-zero on any finding (default: only HIGH triggers a non-zero exit)"
160
+ }
161
+ },
162
+ async run({ args }) {
163
+ try {
164
+ const result = await runAuditCommand({
165
+ cwd: typeof args.path === "string" ? args.path : void 0,
166
+ format: args.json === true ? "json" : "markdown",
167
+ strict: args.strict === true
168
+ });
169
+ if (result.exitCode !== 0) process.exitCode = result.exitCode;
170
+ } catch (err) {
171
+ console.error(err instanceof Error ? err.message : String(err));
172
+ process.exitCode = 1;
173
+ }
174
+ }
175
+ });
176
+ const main = defineCommand({
177
+ meta: {
178
+ name: "vttforge",
179
+ version: VTTFORGE_CLI_VERSION,
180
+ description: "VTTForge CLI — scaffold, dev, build for Foundry v13+ systems and modules"
181
+ },
182
+ subCommands: {
183
+ init,
184
+ dev,
185
+ build,
186
+ audit
187
+ }
188
+ });
189
+ //#endregion
190
+ //#region src/bin.ts
191
+ /**
192
+ * vttforge bin — the executable entry.
193
+ *
194
+ * Nothing but the launch. The command tree lives in `cli.ts` so tests can
195
+ * import it without running the CLI.
196
+ */
197
+ runMain(main);
198
+ //#endregion
199
+ export {};
200
+
201
+ //# sourceMappingURL=bin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.mjs","names":[],"sources":["../src/cli.ts","../src/bin.ts"],"sourcesContent":["/**\n * vttforge command tree.\n *\n * Kept apart from `bin.ts`, which only runs it. Argument definitions are the\n * CLI's contract with its users — `--no-install` skipping the install, an\n * alias resolving to its canonical name — and a module that calls `runMain`\n * on import cannot be reached by a test. Exporting the commands from here\n * lets the suite parse real argv against the real definitions.\n */\nimport { defineCommand } from 'citty';\nimport { runAuditCommand } from './commands/audit.js';\nimport { runBuild } from './commands/build.js';\nimport { runDev } from './commands/dev.js';\nimport { runInit, ScaffoldError } from './commands/init.js';\nimport { VTTFORGE_CLI_VERSION } from './index.js';\n\nexport const init = defineCommand({\n meta: {\n name: 'init',\n description: 'Scaffold a new Foundry VTT system or module from a VTTForge template',\n },\n args: {\n name: {\n type: 'positional',\n description: 'Directory name for the new system/module (also the default manifest id)',\n required: false,\n },\n type: {\n type: 'string',\n description: 'Package type: system | module',\n },\n lang: {\n type: 'string',\n description: 'Language: ts | js',\n },\n id: {\n type: 'string',\n description: 'Manifest id (defaults to a slug of the directory name)',\n },\n title: {\n type: 'string',\n description: 'Human-readable title shown in Foundry setup screens',\n },\n description: {\n type: 'string',\n description: 'One-line description for the manifest',\n },\n author: {\n type: 'string',\n description: 'Author name for the manifest (defaults to the git author)',\n },\n license: {\n type: 'string',\n description: 'SPDX license id (default MIT)',\n },\n // Without a terminal there is nothing for a prompt to read, so it waits\n // forever. This flag takes the defaults instead — and the scaffolder\n // also assumes it when stdin is not a TTY, so CI need not pass it.\n yes: {\n type: 'boolean',\n alias: 'y',\n default: false,\n description: 'Accept defaults for anything not passed, without prompting',\n },\n // Citty parses `--no-X` as `args.X === false`, so define affirmative\n // flags with a true default. `--no-install` then yields `install: false`\n // which we forward as `noInstall: true`.\n install: {\n type: 'boolean',\n default: true,\n description: 'Install dependencies after scaffold (use --no-install to skip)',\n },\n git: {\n type: 'boolean',\n default: true,\n description: 'Initialize a git repository after scaffold (use --no-git to skip)',\n },\n },\n async run({ args }) {\n try {\n await runInit({\n name: typeof args.name === 'string' ? args.name : undefined,\n type: typeof args.type === 'string' ? (args.type as 'system' | 'module') : undefined,\n lang: typeof args.lang === 'string' ? (args.lang as 'ts' | 'js') : undefined,\n id: typeof args.id === 'string' ? args.id : undefined,\n title: typeof args.title === 'string' ? args.title : undefined,\n description: typeof args.description === 'string' ? args.description : undefined,\n author: typeof args.author === 'string' ? args.author : undefined,\n license: typeof args.license === 'string' ? args.license : undefined,\n yes: args.yes === true,\n noInstall: args.install === false,\n noGit: args.git === false,\n });\n } catch (err) {\n // ScaffoldError already surfaced its message via `p.cancel`; anything\n // else is a real crash worth showing. Either way the bin exits 1.\n if (!(err instanceof ScaffoldError)) {\n console.error(err instanceof Error ? err.message : String(err));\n }\n process.exit(1);\n }\n },\n});\n\nexport const dev = defineCommand({\n meta: {\n name: 'dev',\n description: 'Symlink dist/ into Foundry Data and run vite build --watch',\n },\n args: {\n 'foundry-data': {\n type: 'string',\n alias: 'data-dir',\n description:\n 'Override the Foundry user-data directory (skips env / config / first-run prompt)',\n },\n 'hmr-port': {\n type: 'string',\n description: 'Port for the hot reload bridge (default 31313)',\n },\n },\n async run({ args }) {\n // Citty surfaces aliases under the canonical name. Belt-and-suspenders:\n // accept either spelling so older docs and muscle memory keep working.\n const explicit =\n (typeof args['foundry-data'] === 'string' ? args['foundry-data'] : undefined) ??\n (typeof args['data-dir'] === 'string' ? args['data-dir'] : undefined);\n // A non-numeric port is the user's typo, not a reason to fall back to a\n // port they did not ask for — say so and stop.\n const rawPort = typeof args['hmr-port'] === 'string' ? args['hmr-port'] : undefined;\n const hmrPort = rawPort === undefined ? undefined : Number(rawPort);\n if (hmrPort !== undefined && !Number.isInteger(hmrPort)) {\n console.error(`--hmr-port expects a whole number, got \\`${rawPort}\\`.`);\n process.exit(1);\n }\n try {\n await runDev({ dataDir: explicit, hmrPort });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nexport const build = defineCommand({\n meta: {\n name: 'build',\n description: 'Run vite build (production) and emit <id>-<version>.zip for foundryvtt.com',\n },\n async run() {\n try {\n await runBuild();\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nexport const audit = defineCommand({\n meta: {\n name: 'audit',\n description: 'Scan a system/module project for the seven v13 manifest + code footguns',\n },\n args: {\n path: {\n type: 'positional',\n description: 'Project root to scan (defaults to the current directory)',\n required: false,\n },\n json: {\n type: 'boolean',\n default: false,\n description: 'Emit a machine-readable JSON report instead of markdown',\n },\n strict: {\n type: 'boolean',\n default: false,\n description: 'Exit non-zero on any finding (default: only HIGH triggers a non-zero exit)',\n },\n },\n async run({ args }) {\n try {\n const result = await runAuditCommand({\n cwd: typeof args.path === 'string' ? args.path : undefined,\n format: args.json === true ? 'json' : 'markdown',\n strict: args.strict === true,\n });\n // Set exitCode instead of calling process.exit so any pending stdout\n // writes (the JSON / markdown report) get flushed before the process\n // tears down. process.exit(1) would truncate large reports on the\n // exact runs CI needs them intact.\n if (result.exitCode !== 0) process.exitCode = result.exitCode;\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n },\n});\n\nexport const main = defineCommand({\n meta: {\n name: 'vttforge',\n version: VTTFORGE_CLI_VERSION,\n description: 'VTTForge CLI — scaffold, dev, build for Foundry v13+ systems and modules',\n },\n subCommands: {\n init,\n dev,\n build,\n audit,\n },\n});\n","#!/usr/bin/env node\n/**\n * vttforge bin — the executable entry.\n *\n * Nothing but the launch. The command tree lives in `cli.ts` so tests can\n * import it without running the CLI.\n */\nimport { runMain } from 'citty';\nimport { main } from './cli.js';\n\nrunMain(main);\n"],"mappings":"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"}