@gamecrate/cli 1.0.0 → 1.2.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 CHANGED
@@ -20,238 +20,52 @@ npm install -g ./packages/cli
20
20
 
21
21
  The build needs [bun](https://bun.sh) and Node 22 or newer.
22
22
 
23
- ## Configuration
23
+ ## First launch
24
24
 
25
- Your config lives at `~/.config/gamecrate/profiles.json`. If you set `XDG_CONFIG_HOME`, the
26
- path moves with it. The file accepts JSONC, so comments and trailing commas are fine.
25
+ Write `~/.config/gamecrate/profiles.yml`. This is the smallest RimWorld config that runs:
27
26
 
28
- A minimal config for RimWorld:
29
-
30
- ```jsonc
31
- {
32
- "plugins": ["@gamecrate/rimworld"],
33
- "games": {
34
- "rimworld": {
35
- // The plugin already says source: "mount" and container: "/game".
36
- "gameFiles": { "host": "~/games/RimWorld" },
37
- "image": { "ref": "ghcr.io/your-org/rimworld:1.6", "acquire": "pull" },
38
- "workshopRoot": "~/.steam/steam/steamapps/workshop/content/294100",
39
- "scanRoots": [{ "path": "~/projects/mods", "maxDepth": 2 }],
40
- "profiles": {
41
- "dev": { "mods": ["brrainz.harmony", "yourname.yourmod"] }
42
- }
43
- }
44
- }
45
- }
27
+ ```yaml
28
+ plugins: ['@gamecrate/rimworld']
29
+ games:
30
+ rimworld:
31
+ # The plugin already says source: mount and container: /game.
32
+ gameFiles:
33
+ host: ~/games/RimWorld
34
+ image:
35
+ ref: ghcr.io/your-org/rimworld:1.6
36
+ acquire: pull
37
+ workshopRoot: ~/.steam/steam/steamapps/workshop/content/294100
38
+ scanRoots:
39
+ - path: ~/projects/mods
40
+ maxDepth: 2
41
+ profiles:
42
+ dev:
43
+ mods: [brrainz.harmony, yourname.yourmod]
46
44
  ```
47
45
 
48
- Run `gamecrate config edit` to open the file in `$EDITOR` and validate it on save.
49
-
50
- ### How plugins resolve
51
-
52
- `plugins` is an array of strings. gamecrate loads each one before it reads the rest of the
53
- file, because a plugin decides what a valid game block looks like.
54
-
55
- A string that starts with `.` or `/` is a path. gamecrate resolves it against the directory
56
- holding your config, so `./plugins/mygame` means `~/.config/gamecrate/plugins/mygame`. A `~`
57
- at the front expands to your home directory.
58
-
59
- Anything else is a package name. gamecrate walks `node_modules` upward from the config
60
- directory, the same way Node does. To use a bare name such as `@gamecrate/rimworld`, install it
61
- where that walk can find it:
46
+ The bare name `@gamecrate/rimworld` only resolves once the package is installed next to the
47
+ config. [Configuration](docs/configuration.md#how-plugins-resolve) shows both ways to point at
48
+ a plugin. Then:
62
49
 
63
50
  ```sh
64
- cd ~/.config/gamecrate
65
- npm init -y
66
- npm install @gamecrate/rimworld
51
+ gamecrate doctor
52
+ gamecrate rimworld dev
67
53
  ```
68
54
 
69
- Each plugin claims one game name. Two plugins claiming the same name is a config error, and so
70
- is a `games` block whose name no plugin claims.
71
-
72
- ### An array you write replaces the plugin's array
73
-
74
- A plugin ships defaults for its game. Your `games.<name>` block merges on top of those
75
- defaults, key by key. Objects merge. Scalars overwrite.
76
-
77
- **Arrays do not concatenate.** The array you write replaces the plugin's array outright. Write
78
- three entries under `dlc` and the game has three, not the plugin's five plus your three. The
79
- same holds for `modes`, `scanRoots`, and `saveExtensions`. To add one DLC, copy the plugin's
80
- full list and append to it.
81
-
82
- The settings ladder is the one exception. `settings` merges through five layers: the top-level
83
- `defaults`, then `games.<game>`, then the profile, then the instance, then the command line.
84
- Arrays inside `settings`, which means `gameArgs` and `dockerArgs`, concatenate at every layer.
85
-
86
- When a config error points at a key you never wrote, the message says which plugin's defaults
87
- supplied it.
88
-
89
- ### Profiles
90
-
91
- A profile names a mod set. `extends` inherits a parent profile's mods and appends its own.
92
- `exclude` drops entries by glob. `alias` and `aliases` give one profile extra names, and they
93
- share the parent's data directory. `instances` splits one profile into named sub-runs, each
94
- with its own saves, logs, lock, and container.
95
-
96
- gamecrate ships one profile of its own, `modless`. It resolves to the core game plus its
97
- official DLC, and you cannot redefine it.
55
+ `doctor` checks Docker, the image, the game directory, and the workshop root before anything
56
+ launches. The second line resolves the `dev` profile, stages its two mods, and starts the game.
57
+ `gamecrate rimworld dev --print-plan` shows what it would do without launching.
98
58
 
99
- ### Per-directory defaults
59
+ ## Documentation
100
60
 
101
- gamecrate looks for a `.gamecrate.yml` file in the current directory and every parent. Keys in
102
- it stand in for flags you would otherwise type, so a mod repo can pin its own game, profile,
103
- and extra Docker arguments:
104
-
105
- ```yaml
106
- game: rimworld
107
- profile: dev
108
- mode: headed
109
- ```
110
-
111
- A flag on the command line beats the file.
112
-
113
- ## Subcommands
114
-
115
- The subcommand slot defaults to `run`, so `gamecrate rimworld dev` and
116
- `gamecrate run rimworld dev` do the same thing.
117
-
118
- | Subcommand | What it does |
61
+ | Read this | When you want to |
119
62
  | --- | --- |
120
- | `run <game> [profile]` | Resolve, stage, and launch |
121
- | `list [game]` | Games, profiles, and where each profile came from |
122
- | `mods <game> [profile]` | The resolved mod set: source kind plus absolute path |
123
- | `doctor` | Preflight: Docker, CDI, registry auth, game dirs, scan roots, permissions |
124
- | `clean <game> <profile>` | Tiered wipe of a profile |
125
- | `clone <game> <src> <dst>` | Reflink-copy a profile's precious tier |
126
- | `logs <game> <profile>` | Print the last run's captured logs |
127
- | `build <game>` | Build or pull the runtime image, no launch |
128
- | `shell <game> [profile]` | Same mounts, bash instead of the game |
129
- | `verify <game> [profile]` | What the running container bound, and whether it looks current |
130
- | `config edit` | Open `profiles.json` in `$EDITOR`, validate on save |
131
- | `fix-perms <game> [profile]` | Chown foreign-owned files back to the caller |
132
- | `help [topic]` | Help for a subcommand or a game |
133
- | `version` | Print the version |
134
-
135
- `gamecrate help <game>` lists that game's profiles and modes. `gamecrate help completion bash`
136
- and `gamecrate help completion zsh` print a shell completion script.
137
-
138
- Game arguments go after a bare `--` and nowhere else.
139
-
140
- ## Flags
141
-
142
- Mod set:
143
-
144
- - `--mod <id>` adds a mod to the profile set. Repeatable.
145
- - `--without <id>` drops a mod from the resolved set. Repeatable.
146
- - `--only <id>` restricts the resolved set to these mods. Repeatable.
147
- - `--use <packageId>=<path>` forces one mod to load from a directory. Repeatable.
148
- - `--sort <topo|none>` picks the profile order or a topological sort.
149
-
150
- Worktrees and instances:
151
-
152
- - `--worktree <path>` promotes mods from a linked git worktree, in its own instance. Repeatable,
153
- and earlier flags outrank later ones.
154
- - `--no-worktree` ignores the current directory and `$GAMECRATE_WORKTREE`.
155
- - `--instance <name>` runs under a named sub-profile with its own saves, logs, and container.
156
-
157
- Display and lifetime:
158
-
159
- - `--mode <headed|headless|screenshot>` chooses how the game displays.
160
- - `--resolution <width>x<height>` overrides the game resolution.
161
- - `--marker <str>` exits 0 as soon as that string appears in the log.
162
- - `--timeout <seconds>` kills the container after that long.
163
- - `--render-wait <seconds>` sets the settle time before a screenshot.
164
-
165
- Container and build:
166
-
167
- - `--network <none|bridge|host>` sets the container network mode.
168
- - `--pull <always|missing|never>` decides when to pull the runtime image.
169
- - `--build` compiles local C# mods first. `--no-build` never compiles, even when an assembly
170
- looks stale.
171
- - `--no-stale-check` drops the warning about sources newer than assemblies. The check still runs.
172
- - `--docker-arg <arg>` adds one argv element to `docker run`. Repeatable.
173
- - `--root` runs as root instead of mapping your uid.
174
- - `--replace` stops whatever holds this profile and instance, then launches. `--no-replace`
175
- refuses instead.
176
-
177
- Output and dry runs:
178
-
179
- - `--dry-run` resolves and validates fully, then writes nothing.
180
- - `--print-plan` prints the resolved launch plan instead of launching.
181
- - `--log <path>` routes launch output to one file.
182
- - `--json` switches to machine-readable output.
183
-
184
- `clean` adds three tier flags: `--staging` (the default), `--logs`, and `--all`. `--all` deletes
185
- saves, so it needs `--yes`.
186
-
187
- ## Environment variables
188
-
189
- Each variable stands in for one flag, and only when you leave that flag off:
190
-
191
- `GAMECRATE_INSTANCE`, `GAMECRATE_MODE`, `GAMECRATE_MARKER`, `GAMECRATE_TIMEOUT`,
192
- `GAMECRATE_RENDER_WAIT`, `GAMECRATE_NETWORK`, `GAMECRATE_PULL`, `GAMECRATE_BUILD`,
193
- `GAMECRATE_SORT`, `GAMECRATE_ROOT`.
194
-
195
- `GAMECRATE_WORKTREE` names a worktree to promote, and `--no-worktree` cancels it.
196
-
197
- ## Exit codes
198
-
199
- - `0` success
200
- - `1` the game itself failed
201
- - `2` usage error
202
- - `3` config error
203
- - `4` resolution error
204
- - `5` environment problem, such as a missing Docker
205
- - `6` the marker never appeared before the timeout
206
- - `7` refused, because this profile and instance already run
207
- - `8` `verify` found a stale mod
208
- - `130` interrupted
209
-
210
- ## Write a plugin
211
-
212
- A plugin is an ES module with a default export that satisfies `GamePlugin`. It takes plain data
213
- and throws plain `Error` objects, so it never imports the gamecrate runtime at run time.
214
-
215
- ```ts
216
- import type { GamePlugin } from '@gamecrate/cli'
217
-
218
- const plugin: GamePlugin = {
219
- apiVersion: 1,
220
- game: 'mygame',
221
- defaults: { /* Partial<GameConfig> */ },
222
- parseManifest: (text) => null,
223
- renderModsConfig: (input) => '',
224
- mergePrefs: (existing, owned) => '',
225
- windowedPrefs: { fullscreen: 'False' },
226
- parseVersion: (text) => null,
227
- }
228
-
229
- export default plugin
230
- ```
231
-
232
- The members:
233
-
234
- - `apiVersion` must equal `PLUGIN_API_VERSION`, which is `1`. A mismatch fails the load with a
235
- message naming both numbers.
236
- - `game` is the word the command line answers to, such as `rimworld`.
237
- - `defaults` is a `Partial<GameConfig>`. It holds facts about the game itself, never about one
238
- machine. Leave install paths, workshop roots, scan roots, and images to the user.
239
- - `parseManifest(text)` reads one mod manifest and returns a `ModManifest`, or `null` when the
240
- file is not a manifest at all. A malformed manifest throws.
241
- - `renderModsConfig(input)` takes a version, a build number, the active package ids in load
242
- order, and the known expansions, then returns the file the engine reads.
243
- - `mergePrefs(existing, owned)` folds the keys gamecrate owns into the player's own prefs file.
244
- `existing` is `null` on the first run.
245
- - `windowedPrefs` lists the prefs keys that put the game in a window rather than fullscreen.
246
- - `parseVersion(text)` reads the engine's own version file and returns a version string and a
247
- build number, or `null` when the text does not parse.
248
-
249
- Load your plugin by path while you develop it:
250
-
251
- ```jsonc
252
- {
253
- "plugins": ["~/projects/gamecrate-mygame/dist/index.js"]
254
- }
255
- ```
256
-
257
- [`@gamecrate/rimworld`](../rimworld) is a complete example in about fifty lines.
63
+ | [Configuration](docs/configuration.md) | Write the config file, load a plugin, define profiles, or set per-repository defaults |
64
+ | [Mod sources](docs/mod-sources.md) | Pin a mod to a directory, a workshop item, or a git repository, and learn which copy of a mod wins |
65
+ | [The `mods` commands](docs/mods-commands.md) | Add, remove, and sync library pins from the command line |
66
+ | [Running](docs/running.md) | Launch, run in the background, read the result, close a window, and clean up |
67
+ | [Reference](docs/reference.md) | Every subcommand, flag, environment variable, and exit code |
68
+ | [Writing a plugin](docs/plugins.md) | Teach gamecrate a new game |
69
+
70
+ [`@gamecrate/rimworld`](../rimworld) is the RimWorld plugin, and the one complete example of
71
+ the plugin contract.