@fzgame/cli 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Fanzeal
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,314 @@
1
- # Temporary Holding Version
1
+ # Develop an FZ game
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This guide covers creating a game project and running it on your machine. You do not need GameHub, MinIO, or a published build for day-to-day work. `fzgame dev` serves the game, the editor, and a mock runtime that `@fzgame/kit` talks to.
4
+
5
+ ## Install
6
+
7
+ Node.js 20 or later.
8
+
9
+ ```bash
10
+ npm i -g @fzgame/cli
11
+ fzgame --version
12
+ ```
13
+
14
+ The published package carries the click-counter example, so `fzgame init` works without a checkout.
15
+
16
+ ### From this monorepo
17
+
18
+ ```bash
19
+ pnpm install
20
+ pnpm --filter @fzgame/kit build
21
+ pnpm --filter @fzgame/cli build
22
+ node packages/fzgame-cli/dist/cli.cjs --help
23
+ ```
24
+
25
+ Below, `fzgame` means the global `fzgame` command or `node packages/fzgame-cli/dist/cli.cjs` from the repo root. `fzgame --help` lists the five commands: `init`, `build`, `publish`, `dev`, and `runner`.
26
+
27
+ ## Init a project
28
+
29
+ A game root is a folder with `fzgame.json` plus three directories:
30
+
31
+ ```
32
+ my-game/
33
+ fzgame.json
34
+ game/ # player iframe (required: game/index.html)
35
+ editor/ # instance config UI (required: editor/index.html)
36
+ handler/ # backend artifact uploaded to S3 (required: handler/index.js)
37
+ ```
38
+
39
+ ### Scaffold the example
40
+
41
+ Fastest path. Click Counter is a complete local game.
42
+
43
+ ```bash
44
+ fzgame init my-game
45
+ cd my-game
46
+ fzgame dev
47
+ ```
48
+
49
+ `init` refuses a directory that already has files; pass `--force` to write into one. An existing `.gitignore` is kept.
50
+
51
+ Then edit `my-game/fzgame.json` (`info.name`, `info.slug`, `info.version`) and the files under `game/`, `editor/`, and `handler/`.
52
+
53
+ ### Create the layout yourself
54
+
55
+ ```bash
56
+ mkdir -p my-game/game my-game/editor my-game/handler
57
+ ```
58
+
59
+ `my-game/fzgame.json`:
60
+
61
+ ```json
62
+ {
63
+ "info": {
64
+ "name": "My Game",
65
+ "slug": "my-game",
66
+ "version": 1,
67
+ "image": "",
68
+ "description": "",
69
+ "author": "",
70
+ "git_url": ""
71
+ },
72
+ "entry": {
73
+ "game": "game/index.html",
74
+ "editor": "editor/index.html",
75
+ "handler": "handler/index.js"
76
+ },
77
+ "formSchema": {
78
+ "type": "object",
79
+ "properties": {}
80
+ },
81
+ "resultSchema": {
82
+ "type": "object",
83
+ "properties": {}
84
+ },
85
+ "snapshotSchema": {
86
+ "type": "object",
87
+ "properties": {}
88
+ },
89
+ "gameDataSchema": {
90
+ "type": "object",
91
+ "properties": {}
92
+ }
93
+ }
94
+ ```
95
+
96
+ Five parts of `fzgame.json`:
97
+
98
+ 1. `info` — catalog identity: `name` (min 2 chars), `slug` (kebab-case), `version` (positive integer, default `1`), optional `image`, `description`, `author`, `git_url` (http(s))
99
+ 2. `formSchema` — JSON Schema for the create/edit form (instance config). Optional `formUiSchema` for JSON Forms / RJSF widgets
100
+ 3. `resultSchema` — JSON Schema for the payload `finish` / the handler returns; stored on the play session `result`
101
+ 4. `snapshotSchema` — JSON Schema for mid-play state (`kit.setState` → `metadata.snapshot`)
102
+ 5. `gameDataSchema` — JSON Schema for persistent instance/content data
103
+
104
+ `entry` is optional; the paths above are the defaults. Legacy flat `name` / `slug` / `version` / `configSchema` still parse (`configSchema` is an alias for `formSchema`). `kit.config()` reads `formSchema` property defaults, then instance overrides.
105
+
106
+ `my-game/game/index.html` (loads the kit, opens a play session, starts the lifecycle):
107
+
108
+ ```html
109
+ <!doctype html>
110
+ <html>
111
+ <head>
112
+ <meta charset="utf-8" />
113
+ <title>My Game</title>
114
+ </head>
115
+ <body>
116
+ <p id="status">Loading…</p>
117
+ <script type="module">
118
+ const params = new URLSearchParams(location.search)
119
+ const kitUrl = params.get('kit') || './vendor/kit.js'
120
+ const { createKit } = await import(kitUrl)
121
+
122
+ const kit = createKit()
123
+ await kit.load()
124
+ await kit.connect()
125
+ await kit.emit('ready')
126
+ await kit.emit('start')
127
+
128
+ document.getElementById('status').textContent = kit.snapshot.status
129
+ </script>
130
+ </body>
131
+ </html>
132
+ ```
133
+
134
+ `my-game/editor/index.html` (saves config to the host via `postMessage`):
135
+
136
+ ```html
137
+ <!doctype html>
138
+ <html>
139
+ <head>
140
+ <meta charset="utf-8" />
141
+ <title>My Game editor</title>
142
+ </head>
143
+ <body>
144
+ <form id="form">
145
+ <button type="submit">Save</button>
146
+ </form>
147
+ <script type="module">
148
+ const form = document.getElementById('form')
149
+ window.addEventListener('message', (event) => {
150
+ if (event.data?.source !== 'fzgame-host' || event.data?.type !== 'init') return
151
+ // apply event.data.config onto the form
152
+ })
153
+ parent.postMessage({ source: 'fzgame-editor', type: 'ready' }, '*')
154
+ form.addEventListener('submit', (event) => {
155
+ event.preventDefault()
156
+ parent.postMessage({ source: 'fzgame-editor', type: 'save', config: {} }, '*')
157
+ })
158
+ </script>
159
+ </body>
160
+ </html>
161
+ ```
162
+
163
+ `my-game/handler/index.js`:
164
+
165
+ ```js
166
+ export default {
167
+ async fetch() {
168
+ return new Response(JSON.stringify({ ok: true }), {
169
+ headers: { 'content-type': 'application/json' },
170
+ })
171
+ },
172
+ }
173
+ ```
174
+
175
+ ## Start local development
176
+
177
+ From the game root:
178
+
179
+ ```bash
180
+ fzgame dev
181
+ ```
182
+
183
+ Or from anywhere:
184
+
185
+ ```bash
186
+ fzgame dev --cwd my-game
187
+ ```
188
+
189
+ `dev` serves the game, the editor, and a mock runtime, and needs no GameHub instance. From a monorepo checkout without a global install, `node packages/fzgame-cli/dist/cli.cjs dev --cwd my-game`.
190
+
191
+ Options:
192
+
193
+ | Flag | Default | Meaning |
194
+ | --- | --- | --- |
195
+ | `--cwd <dir>` | current directory | Game root (must contain `fzgame.json`) |
196
+ | `-p, --port <port>` | `3400` | Dev server port |
197
+ | `--host <host>` | `127.0.0.1` | Bind address. `0.0.0.0` exposes the mock runtime on the LAN; it has no authentication, so the default keeps it on this machine |
198
+
199
+ Open [http://127.0.0.1:3400](http://127.0.0.1:3400) for the launcher, or attach the portal studio while `fzgame dev` is running:
200
+
201
+ [http://localhost:3000/game-editor/sandbox](http://localhost:3000/game-editor/sandbox)
202
+
203
+ The sandbox shows play on the left and the editor on the right. The game URL includes a `config` query JSON so the kit can load instance config. Device size, a resizable event console, and kit events (`load`, `connect`, `emit`, `snapshot`) plus editor saves live on the same page. A different origin is `?origin=http://127.0.0.1:3400`.
204
+
205
+ The launcher links to:
206
+
207
+ - **Open game** — `/game/index.html` with `gameInstanceId`, `playerToken`, `apiUrl`, and `kit` already in the query string
208
+ - **Open editor** — `/editor/index.html` with the same `apiUrl` / `kit`
209
+
210
+ Reload the browser after you edit HTML, JS, or CSS. The server reads files from disk on each request.
211
+
212
+ ### What the dev server provides
213
+
214
+ - Static files from the game root (`/game`, `/editor`, `/handler`)
215
+ - `@fzgame/kit` at `/@fzgame/kit.js`
216
+ - Mock GameHub runtime at `/games/runtime` (in-memory sessions, no database)
217
+ - `/health` for the portal sandbox (CORS is open so the studio can watch the origin)
218
+
219
+ The game page is launched as:
220
+
221
+ ```
222
+ /game/index.html?gameInstanceId=dev-instance&playerToken=dev-player&apiUrl=http://127.0.0.1:3400&kit=/@fzgame/kit.js
223
+ ```
224
+
225
+ `createKit()` reads `apiUrl`, `gameInstanceId`, and `playerToken` from that query string.
226
+
227
+ Sandbox and editor preview use `?preview=true&gameSetting={...}` instead. That skips play-session calls and applies the JSON as `kit.config()`.
228
+
229
+ If `/@fzgame/kit.js` returns 404, the kit is missing its build output: reinstall the CLI, or in a monorepo run `pnpm --filter @fzgame/kit build`.
230
+
231
+ ## Talk to GameHub from the game
232
+
233
+ ```js
234
+ import { createKit } from '/@fzgame/kit.js' // in `fzgame dev`; use `./vendor/kit.js` after publish
235
+
236
+ const kit = createKit()
237
+ await kit.load() // GET /games/runtime/instances/:gameInstanceId
238
+ await kit.connect() // POST /games/runtime/sessions { gameInstanceId, playerToken }
239
+ await kit.emit('ready')
240
+ await kit.emit('start')
241
+ await kit.setState({ score: 1 }) // local snapshot + PUT .../snapshot
242
+ await kit.emit('score', { score: 1 })
243
+ await kit.emit('finish', { score: 1, result: { score: 1 } })
244
+
245
+ kit.config() // formSchema defaults merged with instance overrides
246
+ kit.snapshot // { status, score, result, state }
247
+ ```
248
+
249
+ Lifecycle:
250
+
251
+ ```
252
+ CREATED → READY → PLAYING ⇄ PAUSED → FINISHED
253
+ ↘ abort / expire
254
+ ```
255
+
256
+ Emit `ready`, `start`, `pause`, `finish`, `abort`, `score`, or `snapshot`. `finish` and `score` accept `{ score, result }`.
257
+
258
+ On a published GameHub instance the host still passes `apiUrl`, `gameInstanceId`, and `playerToken`. `playerToken` is the player's `identity` or id (the player must already exist).
259
+
260
+ ## Editor host protocol
261
+
262
+ The portal and BO load `editor/index.html` in an iframe.
263
+
264
+ - Host → editor: `{ source: 'fzgame-host', type: 'init', config, configSchema, gameInstanceId }` (`configSchema` is the unwrapped `formSchema`)
265
+ - Editor → host: `{ source: 'fzgame-editor', type: 'ready' }`
266
+ - Editor → host: `{ source: 'fzgame-editor', type: 'save', config }`
267
+
268
+ In local `fzgame dev` there is no parent to persist config. Use the editor page to check the UI; save is wired when the same HTML is opened from the portal instance editor.
269
+
270
+ ## Next: pack and publish
271
+
272
+ When the game runs locally:
273
+
274
+ ```bash
275
+ fzgame build --cwd my-game
276
+ fzgame publish --cwd my-game --api http://localhost:3020 --token <session>
277
+ ```
278
+
279
+ Every flag falls back to an environment variable, and `fzgame` reads a `.env` file from the current directory before it parses arguments. Values already exported in the shell win over the file.
280
+
281
+ | Variable | Used by | Meaning |
282
+ | --- | --- | --- |
283
+ | `FZGAME_API` | `publish`, `runner start` | GameHub API (default `http://localhost:3020`) |
284
+ | `FZGAME_SERVER` | `runner start` | Alias for `FZGAME_API` |
285
+ | `FZGAME_TOKEN` | `publish` | Session token, same as `--token` |
286
+ | `FZGAME_SPACE_ID` | `publish` | Space id, publishes a space-private game |
287
+ | `FZGAME_RUNNER_KEY` | `runner start` | Runner key from the BO receipt |
288
+ | `FZGAME_RUNNER_PASS` | `runner start` | Runner pass from the BO receipt |
289
+
290
+ ## CI runner
291
+
292
+ BO **Runners → Generate runner** opens the runner page with a one-time pass and CLI / Docker start commands. Start the worker with `--server` pointing at the GameHub API:
293
+
294
+ ```bash
295
+ fzgame runner start --server http://localhost:3020 --key fzr_... --pass ...
296
+ ```
297
+
298
+ From this repo:
299
+
300
+ ```bash
301
+ node packages/fzgame-cli/dist/cli.cjs runner start --server http://localhost:3020 --key fzr_... --pass ...
302
+ ```
303
+
304
+ Docker (after generating the receipt):
305
+
306
+ ```bash
307
+ docker compose run --rm runner --server http://host.docker.internal:3020 --key fzr_... --pass ...
308
+ ```
309
+
310
+ Or set `FZGAME_RUNNER_KEY` and `FZGAME_RUNNER_PASS` and `docker compose up -d runner`. The runner claims queued git deploys, then fetch → build → optional test → publish preview.
311
+
312
+ `build` writes `my-game/dist/<slug>-v<version>.zip`. `publish` uploads that zip to `/games/publishing` (add `--space <id>` for a space-private game). The zip is validated, then `game/`, `editor/`, and `handler/` are stored on MinIO/S3.
313
+
314
+ Publishing needs the GameHub API and a logged-in session token. It is not required for local development.