@gamecrate/cli 1.1.0 → 1.3.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/README.md +37 -382
- package/dist/gamecrate.js +3935 -2474
- package/dist/lib.js +40 -11
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/mods.d.ts +18 -0
- package/dist/types/config/write.d.ts +14 -0
- package/dist/types/launch/prepare.d.ts +1 -1
- package/dist/types/launch/resolve.d.ts +5 -0
- package/dist/types/mods/acf.d.ts +19 -0
- package/dist/types/mods/modindex.d.ts +9 -3
- package/dist/types/mods/source.d.ts +61 -0
- package/dist/types/mods/steamcmd.d.ts +56 -0
- package/dist/types/mods/workshop.d.ts +15 -0
- package/dist/types/mods/workshopapi.d.ts +13 -0
- package/dist/types/types.d.ts +39 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# @gamecrate/cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
gamecrate launches a modded game inside a Docker container. You describe a profile once in one
|
|
4
|
+
config file, then run it by name. Each profile gets its own save directory, its own logs, and
|
|
5
|
+
only the mods it lists.
|
|
5
6
|
|
|
6
|
-
The
|
|
7
|
-
facts
|
|
7
|
+
The tool knows nothing about any one game. A plugin supplies the file formats and the engine
|
|
8
|
+
facts, so the core stays the same for every title.
|
|
9
|
+
|
|
10
|
+
This package is the `gamecrate` command itself. It resolves a profile to a mod set, stages that
|
|
11
|
+
set, and launches the game. Without at least one plugin, it has no games to run.
|
|
8
12
|
|
|
9
13
|
## Install
|
|
10
14
|
|
|
@@ -20,25 +24,15 @@ npm install -g ./packages/cli
|
|
|
20
24
|
|
|
21
25
|
The build needs [bun](https://bun.sh) and Node 22 or newer.
|
|
22
26
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
Your config lives in `~/.config/gamecrate/`. If you set `XDG_CONFIG_HOME`, the directory moves
|
|
26
|
-
with it. The file is named `profiles`, and gamecrate reads four suffixes:
|
|
27
|
-
|
|
28
|
-
| Suffix | Format |
|
|
29
|
-
| --- | --- |
|
|
30
|
-
| `.yml` | YAML |
|
|
31
|
-
| `.yaml` | YAML |
|
|
32
|
-
| `.json` | JSON with comments and trailing commas |
|
|
33
|
-
| `.jsonc` | JSON with comments and trailing commas |
|
|
27
|
+
## First launch
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
you edit the wrong file for twenty minutes.
|
|
37
|
-
|
|
38
|
-
A minimal config for RimWorld:
|
|
29
|
+
Write `~/.config/gamecrate/profiles.yml`. This is the smallest RimWorld config that runs:
|
|
39
30
|
|
|
40
31
|
```yaml
|
|
41
32
|
plugins: ['@gamecrate/rimworld']
|
|
33
|
+
# Optional. Without it, gamecrate uses steamcmd from PATH, then a Docker image.
|
|
34
|
+
steamcmd:
|
|
35
|
+
path: ~/.local/bin/steamcmd
|
|
42
36
|
games:
|
|
43
37
|
rimworld:
|
|
44
38
|
# The plugin already says source: mount and container: /game.
|
|
@@ -47,6 +41,7 @@ games:
|
|
|
47
41
|
image:
|
|
48
42
|
ref: ghcr.io/your-org/rimworld:1.6
|
|
49
43
|
acquire: pull
|
|
44
|
+
# Optional. Only for workshop items a Steam install already downloaded.
|
|
50
45
|
workshopRoot: ~/.steam/steam/steamapps/workshop/content/294100
|
|
51
46
|
scanRoots:
|
|
52
47
|
- path: ~/projects/mods
|
|
@@ -56,373 +51,33 @@ games:
|
|
|
56
51
|
mods: [brrainz.harmony, yourname.yourmod]
|
|
57
52
|
```
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`plugins` is an array of strings. gamecrate loads each one before it reads the rest of the
|
|
65
|
-
file, because a plugin decides what a valid game block looks like.
|
|
66
|
-
|
|
67
|
-
A string that starts with `.` or `/` is a path. gamecrate resolves it against the directory
|
|
68
|
-
holding your config, so `./plugins/mygame` means `~/.config/gamecrate/plugins/mygame`. A `~`
|
|
69
|
-
at the front expands to your home directory.
|
|
70
|
-
|
|
71
|
-
Anything else is a package name. gamecrate walks `node_modules` upward from the config
|
|
72
|
-
directory, the same way Node does. To use a bare name such as `@gamecrate/rimworld`, install it
|
|
73
|
-
where that walk can find it:
|
|
54
|
+
The bare name `@gamecrate/rimworld` only resolves once the package is installed next to the
|
|
55
|
+
config. [Configuration](docs/configuration.md#how-plugins-resolve) shows both ways to point at
|
|
56
|
+
a plugin. A workshop item reaches a profile two ways: gamecrate downloads it with steamcmd, or
|
|
57
|
+
it reads an existing Steam install under `workshopRoot`. Both keys are optional, and
|
|
58
|
+
[Mod sources](docs/mod-sources.md) covers the choice. Then:
|
|
74
59
|
|
|
75
60
|
```sh
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
npm install @gamecrate/rimworld
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Each plugin claims one game name. Two plugins claiming the same name is a config error, and so
|
|
82
|
-
is a `games` block whose name no plugin claims.
|
|
83
|
-
|
|
84
|
-
### An array you write replaces the plugin's array
|
|
85
|
-
|
|
86
|
-
A plugin ships defaults for its game. Your `games.<name>` block merges on top of those
|
|
87
|
-
defaults, key by key. Objects merge. Scalars overwrite.
|
|
88
|
-
|
|
89
|
-
**Arrays do not concatenate.** The array you write replaces the plugin's array outright. Write
|
|
90
|
-
three entries under `dlc` and the game has three, not the plugin's five plus your three. The
|
|
91
|
-
same holds for `modes`, `scanRoots`, and `saveExtensions`. To add one DLC, copy the plugin's
|
|
92
|
-
full list and append to it.
|
|
93
|
-
|
|
94
|
-
The settings ladder is the one exception. `settings` merges through five layers: the top-level
|
|
95
|
-
`defaults`, then `games.<game>`, then the profile, then the instance, then the command line.
|
|
96
|
-
Arrays inside `settings`, which means `gameArgs` and `dockerArgs`, concatenate at every layer,
|
|
97
|
-
unlike the `dlc`, `modes` and `scanRoots` lists in the preceding section, which replace.
|
|
98
|
-
|
|
99
|
-
When a config error points at a key you never wrote, the message says which plugin's defaults
|
|
100
|
-
supplied it.
|
|
101
|
-
|
|
102
|
-
### Profiles
|
|
103
|
-
|
|
104
|
-
A profile names a mod set. `extends` inherits a parent profile's mods and appends its own.
|
|
105
|
-
`exclude` drops entries by glob. `alias` and `aliases` give one profile extra names, and they
|
|
106
|
-
share the parent's data directory. `instances` splits one profile into named sub-runs, each
|
|
107
|
-
with its own saves, logs, lock, and container.
|
|
108
|
-
|
|
109
|
-
`description` is one line saying what the profile is for. `gamecrate list` prints it on its own
|
|
110
|
-
line under the profile, and `gamecrate list --json` carries it as a `description` field on that
|
|
111
|
-
profile. It changes nothing about the launch.
|
|
112
|
-
|
|
113
|
-
```yaml
|
|
114
|
-
profiles:
|
|
115
|
-
dev:
|
|
116
|
-
description: harmony plus the mod I am working on
|
|
117
|
-
mods: [brrainz.harmony, yourname.yourmod]
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
rimworld (headed, headless, screenshot)
|
|
122
|
-
modless built-in: core + official DLC
|
|
123
|
-
dev 2 entries
|
|
124
|
-
harmony plus the mod I am working on
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
A profile can also carry a default for three flags, so you stop typing them:
|
|
128
|
-
|
|
129
|
-
| Key | Stands in for |
|
|
130
|
-
| --- | --- |
|
|
131
|
-
| `detach` | `--detach`. `--no-detach` overrides it. |
|
|
132
|
-
| `replace` | `--replace`. `--no-replace` overrides it. |
|
|
133
|
-
| `build` | `--build` and `--no-build`. Takes `auto`, `always`, or `never`. |
|
|
134
|
-
|
|
135
|
-
gamecrate ships one profile of its own, `modless`. It resolves to the core game plus its
|
|
136
|
-
official DLC, and you cannot redefine it.
|
|
137
|
-
|
|
138
|
-
### Per-directory defaults
|
|
139
|
-
|
|
140
|
-
gamecrate looks for a `.gamecrate` file in the current directory and every parent. It takes the
|
|
141
|
-
same four suffixes as the global config. `.gamecrate.yml`, `.gamecrate.yaml`,
|
|
142
|
-
`.gamecrate.json`, and `.gamecrate.jsonc` all work, and two of them in one directory is the same
|
|
143
|
-
error. Most keys stand in for a flag of the same name, camel-cased. A mod repo can pin its own
|
|
144
|
-
game, profile, and extra Docker arguments:
|
|
145
|
-
|
|
146
|
-
```yaml
|
|
147
|
-
game: rimworld
|
|
148
|
-
defaultProfile: dev
|
|
149
|
-
mode: headed
|
|
150
|
-
detach: true
|
|
151
|
-
dockerArgs: ['--cpus', '4']
|
|
61
|
+
gamecrate doctor
|
|
62
|
+
gamecrate rimworld dev
|
|
152
63
|
```
|
|
153
64
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
| --- | --- |
|
|
160
|
-
| `game` | The game the rest of the file talks about. `profiles` and `settings` both need it. |
|
|
161
|
-
| `defaultProfile` | The profile to use when you name none. Without it, the first key in `profiles` wins, and with neither, `modless` does. |
|
|
162
|
-
| `profiles` | Profiles for `game`, written exactly like the ones in the global config. |
|
|
163
|
-
| `settings` | A settings block assigned over `games.<game>.settings` at load time. |
|
|
164
|
-
|
|
165
|
-
**A repo `settings:` block is assigned over `games.<game>.settings` when the config loads**,
|
|
166
|
-
before anything resolves. It is not a sixth layer: by the time the ladder runs, there is one game
|
|
167
|
-
block holding whatever the repo supplied.
|
|
168
|
-
|
|
169
|
-
**A repo profile replaces a global profile of the same name outright.** It does not merge, so
|
|
170
|
-
the global profile's `instances` and `aliases` are gone for that run. This is deliberate: a
|
|
171
|
-
merge would leave the global profile's `mods` showing through the repo's shorter list. Give the
|
|
172
|
-
repo profile a name of its own when you want both.
|
|
173
|
-
|
|
174
|
-
The file is untrusted input. It ships inside any repo you clone, and `mods`, `use`, `worktree`,
|
|
175
|
-
and now `profiles` can all name directories anywhere on your disk to bind-mount into the
|
|
176
|
-
container.
|
|
177
|
-
Read a stranger's `.gamecrate.yml` before you run gamecrate in their repo.
|
|
65
|
+
`doctor` checks Docker, the image, the game directory, and the workshop root before anything
|
|
66
|
+
launches. When a config uses workshop items, it also reports which steamcmd it found and the
|
|
67
|
+
directories downloaded items land in. The second line resolves the `dev` profile, stages its
|
|
68
|
+
two mods, and starts the game.
|
|
69
|
+
`gamecrate rimworld dev --print-plan` shows what it would do without launching.
|
|
178
70
|
|
|
179
|
-
##
|
|
71
|
+
## Documentation
|
|
180
72
|
|
|
181
|
-
|
|
182
|
-
`gamecrate run rimworld dev` do the same thing.
|
|
183
|
-
|
|
184
|
-
| Subcommand | What it does |
|
|
73
|
+
| Read this | When you want to |
|
|
185
74
|
| --- | --- |
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
| `mods
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
| `ps` | Every live run: game, profile/instance, mode, pid, container, then uptime or status |
|
|
196
|
-
| `stop <game> [profile]` | Stop a detached run and release its lock |
|
|
197
|
-
| `build <game>` | Build or pull the runtime image, no launch |
|
|
198
|
-
| `shell <game> [profile]` | Same mounts, bash instead of the game |
|
|
199
|
-
| `verify <game> [profile]` | What the running container bound, and whether it looks current |
|
|
200
|
-
| `config edit` | Open the global config in `$VISUAL` or `$EDITOR`, validate on save |
|
|
201
|
-
| `fix-perms <game> [profile]` | Chown foreign-owned files back to the caller |
|
|
202
|
-
| `help [topic]` | Help for a subcommand or a game |
|
|
203
|
-
| `version` | Print the version |
|
|
204
|
-
|
|
205
|
-
`gamecrate help <game>` lists that game's profiles and modes. `gamecrate help completion bash`
|
|
206
|
-
and `gamecrate help completion zsh` print a shell completion script.
|
|
207
|
-
|
|
208
|
-
Game arguments go after a bare `--` and nowhere else.
|
|
209
|
-
|
|
210
|
-
## Flags
|
|
211
|
-
|
|
212
|
-
Mod set:
|
|
213
|
-
|
|
214
|
-
- `--mod <id>` adds a mod to the profile set. Repeatable.
|
|
215
|
-
- `--without <id>` drops a mod from the resolved set. Repeatable.
|
|
216
|
-
- `--only <id>` restricts the resolved set to these mods. Repeatable.
|
|
217
|
-
- `--use <packageId>=<path>` forces one mod to load from a directory. Repeatable.
|
|
218
|
-
- `--sort <topo|none>` picks the profile order or a topological sort.
|
|
219
|
-
|
|
220
|
-
Worktrees and instances:
|
|
221
|
-
|
|
222
|
-
- `--worktree <path>` promotes mods from a linked git worktree, in its own instance. Repeatable,
|
|
223
|
-
and earlier flags outrank later ones.
|
|
224
|
-
- `--no-worktree` turns worktree promotion off completely. It ignores the current directory,
|
|
225
|
-
`$GAMECRATE_WORKTREE`, any `--worktree` flag you also typed, and an instance's configured
|
|
226
|
-
`worktree`.
|
|
227
|
-
- `--instance <name>` runs under a named sub-profile with its own saves, logs, and container.
|
|
228
|
-
|
|
229
|
-
Display and lifetime:
|
|
230
|
-
|
|
231
|
-
- `--mode <headed|headless|screenshot>` chooses how the game displays.
|
|
232
|
-
- `--resolution <width>x<height>` overrides the game resolution.
|
|
233
|
-
- `--marker <str>` exits 0 as soon as that string appears in the log.
|
|
234
|
-
- `--timeout <seconds>` bounds a run with a `--marker`, in any mode, and a `--mode headless`
|
|
235
|
-
run without one. A `--mode screenshot` run with no marker is bounded by `--render-wait`
|
|
236
|
-
instead, and a headed run by its window, so neither of those reads this.
|
|
237
|
-
- `--render-wait <seconds>` sets the settle time before a screenshot.
|
|
238
|
-
|
|
239
|
-
Container and build:
|
|
240
|
-
|
|
241
|
-
- `--network <none|bridge|host>` sets the container network mode.
|
|
242
|
-
- `--pull <always|missing|never>` decides when to pull the runtime image.
|
|
243
|
-
- `--build` compiles local C# mods first. `--no-build` never compiles, even when an assembly
|
|
244
|
-
looks stale.
|
|
245
|
-
- `--no-stale-check` drops the warning about sources newer than assemblies. The check still runs.
|
|
246
|
-
- `--docker-arg <arg>` adds one argv element to `docker run`. Repeatable.
|
|
247
|
-
- `--root` runs as root instead of mapping your uid.
|
|
248
|
-
- `--replace` stops whatever holds this profile and instance, then launches. `--no-replace`
|
|
249
|
-
refuses instead.
|
|
250
|
-
- `--detach` launches in the background. `--no-detach` stays in the foreground, whatever the
|
|
251
|
-
profile or the repo config asks for.
|
|
252
|
-
|
|
253
|
-
Output and dry runs:
|
|
254
|
-
|
|
255
|
-
- `--dry-run` resolves and validates fully, then writes nothing.
|
|
256
|
-
- `--print-plan` prints the resolved launch plan instead of launching.
|
|
257
|
-
- `--log <path>` routes launch output to one file.
|
|
258
|
-
- `--json` switches to machine-readable output.
|
|
259
|
-
- `-f`, `--follow` keeps printing as the run writes. `logs` takes it.
|
|
260
|
-
|
|
261
|
-
`clean` adds three tier flags: `--staging` (the default), `--logs`, and `--all`. `--all` deletes
|
|
262
|
-
saves, so it needs `--yes`.
|
|
263
|
-
|
|
264
|
-
## Detached runs
|
|
265
|
-
|
|
266
|
-
`--detach` re-executes gamecrate as a background supervisor. It writes the lock with that
|
|
267
|
-
supervisor's pid, prints the container name, and gives you the prompt back. The
|
|
268
|
-
supervisor owns the run from there. It pulls or builds the image, stages the mods, starts the
|
|
269
|
-
container, and records the exit.
|
|
270
|
-
|
|
271
|
-
Three things refuse a `--detach` you typed, and each says so:
|
|
272
|
-
|
|
273
|
-
- `shell` needs the terminal that `--detach` gives up.
|
|
274
|
-
- `--dry-run` has no run to supervise.
|
|
275
|
-
- `--print-plan` has no run to supervise.
|
|
276
|
-
|
|
277
|
-
The refusal is for the flag, not for detaching. A `detach: true` in a profile or a repo config
|
|
278
|
-
is a default, so `shell`, `--dry-run` and `--print-plan` ignore it and run in the foreground
|
|
279
|
-
without a word.
|
|
280
|
-
|
|
281
|
-
A profile or a repo config can set `detach: true`. `--no-detach` is the way past that.
|
|
282
|
-
|
|
283
|
-
### Read the result with `wait`
|
|
284
|
-
|
|
285
|
-
A detached launch returns `0` to your shell as soon as gamecrate writes the lock. That says
|
|
286
|
-
the supervisor started, and nothing about how the game ended. **`wait` is how a script gets the
|
|
287
|
-
real exit code.** It blocks until the run ends, then exits with that code:
|
|
288
|
-
|
|
289
|
-
```sh
|
|
290
|
-
gamecrate rimworld dev --detach
|
|
291
|
-
gamecrate wait rimworld dev
|
|
292
|
-
echo $?
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
gamecrate sends no desktop notification when a detached run fails. Nothing pops up and nothing
|
|
296
|
-
writes to your terminal, so `wait`, `ps`, and the `last-exit.json` file are the only ways to
|
|
297
|
-
learn about it. That file sits at `.gamecrate/last-exit.json` inside the instance directory,
|
|
298
|
-
whose name depends on how gamecrate hashes the worktree path, so a script cannot compute it.
|
|
299
|
-
Use `wait`.
|
|
300
|
-
|
|
301
|
-
`wait` exits `2` when no run was ever recorded, and `7` when the lock holder died without
|
|
302
|
-
recording an exit. `7` means the lock is stale: `gamecrate stop <game> <profile>` clears it.
|
|
303
|
-
|
|
304
|
-
### Watch a run that is already going
|
|
305
|
-
|
|
306
|
-
```sh
|
|
307
|
-
gamecrate ps
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
```
|
|
311
|
-
rimworld mpf/simulator-test headless 1474870 gamecrate-rimworld-mpf-simulator-test Up 7 minutes
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
The columns are game, profile with its instance, mode, supervisor pid, container, and
|
|
315
|
-
uptime. A run that holds a lock but has no container yet reads `starting`, because staging and
|
|
316
|
-
the image come before `docker run`. A lock with no live supervisor reads `orphaned`, and
|
|
317
|
-
`ps` tells you to run `stop`.
|
|
318
|
-
|
|
319
|
-
`gamecrate attach <game> <profile>` streams that run's captured output from the beginning of
|
|
320
|
-
the log. Ctrl-C stops the stream and leaves the game running. `gamecrate logs <game> <profile>
|
|
321
|
-
-f` follows the same log from the end instead.
|
|
322
|
-
|
|
323
|
-
`gamecrate stop <game> <profile>` signals the supervisor and waits for the lock to be released.
|
|
324
|
-
It clears the lock itself only when the holder died first; a live supervisor releases its own.
|
|
325
|
-
It exits `7` when the lock still names a live process after the wait, and it does not look at
|
|
326
|
-
the container to decide that.
|
|
327
|
-
|
|
328
|
-
A run whose supervisor is already dead takes a different path: `stop` stops the container
|
|
329
|
-
itself, clears the stale lock, and exits `0`.
|
|
330
|
-
|
|
331
|
-
## Headed runs on X11
|
|
332
|
-
|
|
333
|
-
A headed run retitles its own window and fixes what the window manager knows about it. That
|
|
334
|
-
needs two programs on the host.
|
|
335
|
-
|
|
336
|
-
**`xprop` is load-bearing.** gamecrate writes its own pid onto the window it adopts, then reads
|
|
337
|
-
that pid back. That is how it tells its window apart from another run's. Without `xprop` it can
|
|
338
|
-
do neither, so two concurrent headed runs adopt the same window. Destroying that one window
|
|
339
|
-
tears down both containers, and the second run loses an unsaved game.
|
|
340
|
-
|
|
341
|
-
The titlebar X is the one close route that stays harmless without `xprop`. RimWorld claims
|
|
342
|
-
`WM_DELETE_WINDOW` and then ignores it, so that button does nothing until gamecrate strips the
|
|
343
|
-
claim. Stripping it takes `xprop`. Every other route to destroying the window, such as a window
|
|
344
|
-
manager shortcut or `xkill`, fires both teardowns.
|
|
345
|
-
|
|
346
|
-
With `xprop` installed, the strip is what makes the titlebar X end the run. The window manager
|
|
347
|
-
destroys the window, gamecrate sees it go, and stops that run's container.
|
|
348
|
-
|
|
349
|
-
A missing `wmctrl` costs more than the title. gamecrate warns and then skips the whole window
|
|
350
|
-
step, because the snapshot it needs comes from `wmctrl`. So the `WM_DELETE_WINDOW` strip never
|
|
351
|
-
runs even with `xprop` installed, the titlebar X goes back to doing nothing, and closing the
|
|
352
|
-
window no longer stops the container.
|
|
353
|
-
|
|
354
|
-
## Environment variables
|
|
355
|
-
|
|
356
|
-
Each variable stands in for one flag, and only when you leave that flag off:
|
|
357
|
-
|
|
358
|
-
`GAMECRATE_INSTANCE`, `GAMECRATE_MODE`, `GAMECRATE_MARKER`, `GAMECRATE_TIMEOUT`,
|
|
359
|
-
`GAMECRATE_RENDER_WAIT`, `GAMECRATE_NETWORK`, `GAMECRATE_PULL`, `GAMECRATE_BUILD`,
|
|
360
|
-
`GAMECRATE_SORT`, `GAMECRATE_ROOT`.
|
|
361
|
-
|
|
362
|
-
`GAMECRATE_WORKTREE` names a worktree to promote, and `--no-worktree` cancels it.
|
|
363
|
-
|
|
364
|
-
## Exit codes
|
|
365
|
-
|
|
366
|
-
- `0` success
|
|
367
|
-
- `1` the game itself failed
|
|
368
|
-
- `2` usage error
|
|
369
|
-
- `3` config error
|
|
370
|
-
- `4` resolution error
|
|
371
|
-
- `5` environment problem, such as a missing Docker
|
|
372
|
-
- `6` the marker never appeared before the timeout
|
|
373
|
-
- `7` refused, because this profile and instance already run
|
|
374
|
-
- `8` `verify` found a stale mod
|
|
375
|
-
- `130` interrupted
|
|
376
|
-
|
|
377
|
-
`wait` exits with whatever code the detached run recorded, which is any code in this list
|
|
378
|
-
except `8`, since only `verify` returns that and `verify` is never supervised.
|
|
379
|
-
A supervisor that fails before Docker records `3`, `4`, `5` or `7`.
|
|
380
|
-
`wait` hands that code back unchanged.
|
|
381
|
-
So a script must handle the whole list, not only the four codes a finished game uses.
|
|
382
|
-
|
|
383
|
-
## Write a plugin
|
|
384
|
-
|
|
385
|
-
A plugin is an ES module with a default export that satisfies `GamePlugin`. It takes plain data
|
|
386
|
-
and throws plain `Error` objects, so it never imports the gamecrate runtime at run time.
|
|
387
|
-
|
|
388
|
-
```ts
|
|
389
|
-
import type { GamePlugin } from '@gamecrate/cli'
|
|
390
|
-
|
|
391
|
-
const plugin: GamePlugin = {
|
|
392
|
-
apiVersion: 1,
|
|
393
|
-
game: 'mygame',
|
|
394
|
-
defaults: { /* Partial<GameConfig> */ },
|
|
395
|
-
parseManifest: (text) => null,
|
|
396
|
-
renderModsConfig: (input) => '',
|
|
397
|
-
mergePrefs: (existing, owned) => '',
|
|
398
|
-
windowedPrefs: { fullscreen: 'False' },
|
|
399
|
-
parseVersion: (text) => null,
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export default plugin
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
The members:
|
|
406
|
-
|
|
407
|
-
- `apiVersion` must equal `PLUGIN_API_VERSION`, which is `1`. A mismatch fails the load with a
|
|
408
|
-
message naming both numbers.
|
|
409
|
-
- `game` is the word the command line answers to, such as `rimworld`.
|
|
410
|
-
- `defaults` is a `Partial<GameConfig>`. It holds facts about the game itself, never about one
|
|
411
|
-
machine. Leave install paths, workshop roots, scan roots, and images to the user.
|
|
412
|
-
- `parseManifest(text)` reads one mod manifest and returns a `ModManifest`, or `null` when the
|
|
413
|
-
file is not a manifest at all. A malformed manifest throws.
|
|
414
|
-
- `renderModsConfig(input)` takes a version, a build number, the active package ids in load
|
|
415
|
-
order, and the known expansions, then returns the file the engine reads.
|
|
416
|
-
- `mergePrefs(existing, owned)` folds the keys gamecrate owns into the player's own prefs file.
|
|
417
|
-
`existing` is `null` on the first run.
|
|
418
|
-
- `windowedPrefs` lists the prefs keys that put the game in a window rather than fullscreen.
|
|
419
|
-
- `parseVersion(text)` reads the engine's own version file and returns a version string and a
|
|
420
|
-
build number, or `null` when the text does not parse.
|
|
421
|
-
|
|
422
|
-
Load your plugin by path while you develop it:
|
|
423
|
-
|
|
424
|
-
```yaml
|
|
425
|
-
plugins: [~/projects/gamecrate-mygame/dist/index.js]
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
[`@gamecrate/rimworld`](../rimworld) is a complete example in about fifty lines.
|
|
75
|
+
| [Configuration](docs/configuration.md) | Write the config file, load a plugin, define profiles, or set per-repository defaults |
|
|
76
|
+
| [Mod sources](docs/mod-sources.md) | Pin a mod to a directory, a git repository, or a workshop item gamecrate downloads, and learn which copy wins |
|
|
77
|
+
| [The `mods` commands](docs/mods-commands.md) | Add, remove, and sync library pins from the command line |
|
|
78
|
+
| [Running](docs/running.md) | Launch, run in the background, read the result, close a window, and clean up |
|
|
79
|
+
| [Reference](docs/reference.md) | Every subcommand, flag, environment variable, and exit code |
|
|
80
|
+
| [Writing a plugin](docs/plugins.md) | Teach gamecrate a new game |
|
|
81
|
+
|
|
82
|
+
[`@gamecrate/rimworld`](../rimworld) is the RimWorld plugin, and the one complete example of
|
|
83
|
+
the plugin contract.
|