@gamecrate/cli 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/gamecrate.js CHANGED
@@ -1056,7 +1056,6 @@ import { basename, join } from "node:path";
1056
1056
  var CONTAINER_RUNTIME_DIR = "/tmp/xdg";
1057
1057
  var CONTAINER_LOG_DIR = "/logs";
1058
1058
  var X11_SOCKET_DIR = "/tmp/.X11-unix";
1059
- var HOST_X11_DIR = "/run/host-x11";
1060
1059
  var CONTAINER_XAUTHORITY = "/tmp/xauth";
1061
1060
  var CONTAINER_XDG_DIR = "/xdg";
1062
1061
  var RUNTIME_DIR_SIZE = "64m";
@@ -1081,14 +1080,12 @@ function buildRunSpec(plan, modMounts, identity, image) {
1081
1080
  const proton = image?.launcher === "proton";
1082
1081
  const executable = image?.executable ?? game.executable;
1083
1082
  refuseProtonHeaded(plan.game, plan.mode, image);
1084
- const command = proton ? ["run-headless-windows", winPath(join(game.gameFiles.container, basename(executable)))] : headed ? ["run-headed", executable] : [
1083
+ const command = proton ? ["run-headless-windows", winPath(join(game.gameFiles.container, basename(executable)))] : headed ? [executable] : [
1085
1084
  "xvfb-run",
1086
1085
  "-a",
1087
1086
  `--server-args=-screen 0 ${settings.width}x${settings.height}x24`,
1088
1087
  executable
1089
1088
  ];
1090
- if (headed)
1091
- env.SCREEN = `${settings.width}x${settings.height}x24`;
1092
1089
  if (proton) {
1093
1090
  Object.assign(env, {
1094
1091
  SCREEN: `${settings.width}x${settings.height}x24`,
@@ -1109,7 +1106,7 @@ function buildRunSpec(plan, modMounts, identity, image) {
1109
1106
  }
1110
1107
  addScratch(mounts, env, plan, identity);
1111
1108
  if (headed)
1112
- addSession(mounts, env, plan, identity);
1109
+ addSession(mounts, env, plan);
1113
1110
  const deviceCgroupRules = [];
1114
1111
  if (settings.input) {
1115
1112
  mounts.push({ type: "bind", source: "/dev/input", target: "/dev/input", readonly: true });
@@ -1177,10 +1174,10 @@ function addScratch(mounts, env, plan, identity) {
1177
1174
  env.XDG_CACHE_HOME = `${CONTAINER_XDG_DIR}/cache`;
1178
1175
  env.XDG_DATA_HOME ??= `${CONTAINER_XDG_DIR}/data`;
1179
1176
  }
1180
- function addSession(mounts, env, plan, identity) {
1177
+ function addSession(mounts, env, plan) {
1181
1178
  const { settings } = plan;
1182
1179
  if (settings.display === "x11")
1183
- addX11(mounts, env, identity);
1180
+ addX11(mounts, env);
1184
1181
  else
1185
1182
  addWayland(mounts, env);
1186
1183
  if (!settings.audio)
@@ -1190,13 +1187,11 @@ function addSession(mounts, env, plan, identity) {
1190
1187
  }
1191
1188
  env.PULSE_SERVER = `unix:${CONTAINER_RUNTIME_DIR}/pulse/native`;
1192
1189
  }
1193
- function addX11(mounts, env, identity) {
1190
+ function addX11(mounts, env) {
1194
1191
  const x11 = x11Session();
1195
1192
  if (!x11)
1196
1193
  return;
1197
- mounts.push({ type: "tmpfs", target: X11_SOCKET_DIR, uid: identity.uid, gid: identity.gid, mode: "1777" });
1198
- mounts.push({ type: "bind", source: X11_SOCKET_DIR, target: HOST_X11_DIR, readonly: true });
1199
- env.HOST_X11_DIR = HOST_X11_DIR;
1194
+ mounts.push({ type: "bind", source: X11_SOCKET_DIR, target: X11_SOCKET_DIR });
1200
1195
  env.DISPLAY = x11.display;
1201
1196
  env.XDG_SESSION_TYPE = "x11";
1202
1197
  env.SDL_VIDEODRIVER = "x11";
@@ -2454,7 +2449,10 @@ function repeats(entries, key) {
2454
2449
  });
2455
2450
  return found;
2456
2451
  }
2457
- var TAG_COMPONENT = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
2452
+ function describe(value) {
2453
+ return typeof value === "string" ? value : JSON.stringify(value);
2454
+ }
2455
+ var TAG_COMPONENT = /^\w[\w.-]*$/;
2458
2456
  function steamBuildRules(ctx) {
2459
2457
  const push = (message, path, suggestion) => {
2460
2458
  ctx.issues.push({
@@ -2509,7 +2507,7 @@ function steamBuildRules(ctx) {
2509
2507
  const wants = depot === "windows" ? "proton" : "xvfb";
2510
2508
  if (base === wants)
2511
2509
  return;
2512
- push(`a ${String(depot)} depot cannot run on the "${base}" base`, ["variants", index, "base"], depot === "windows" ? 'set base to "proton"; it is the only base with wine' : 'set base to "xvfb", or set depot to "windows" if the image should run under wine');
2510
+ push(`a ${describe(depot)} depot cannot run on the "${base}" base`, ["variants", index, "base"], depot === "windows" ? 'set base to "proton"; it is the only base with wine' : 'set base to "xvfb", or set depot to "windows" if the image should run under wine');
2513
2511
  });
2514
2512
  }
2515
2513
  var steamBuildSchema = obj({
@@ -4528,7 +4526,10 @@ function refsLink(game) {
4528
4526
  }
4529
4527
  var MARK2 = "@@gamecrate@@ ";
4530
4528
  function extractScript(candidates, container) {
4531
- const quoted = candidates.map((c) => `'${join11(container, c).replaceAll("'", String.raw`'\''`)}'`);
4529
+ const quoted = candidates.map((c) => {
4530
+ const escaped = join11(container, c).replaceAll("'", String.raw`'\''`);
4531
+ return `'${escaped}'`;
4532
+ });
4532
4533
  return [
4533
4534
  `for d in ${quoted.join(" ")}; do`,
4534
4535
  ' set -- "$d"/*.dll',
@@ -4880,7 +4881,7 @@ async function modsAdd(args, ctx) {
4880
4881
  const target = await resolveTarget(args, ctx, game);
4881
4882
  const pins = await discover(source, gameConfig, requirePlugin(ctx.plugins, game), ctx);
4882
4883
  if (pins.length === 0) {
4883
- throw new GamecrateError(`no mod manifest at ${describe(source)}`, Exit.Resolution, `looked for ${gameConfig.manifest.file}`);
4884
+ throw new GamecrateError(`no mod manifest at ${describe2(source)}`, Exit.Resolution, `looked for ${gameConfig.manifest.file}`);
4884
4885
  }
4885
4886
  const where = new Map;
4886
4887
  for (const pin of pins) {
@@ -4893,7 +4894,7 @@ async function modsAdd(args, ctx) {
4893
4894
  const lines = [...where.entries()].filter(([, at]) => at.length > 1);
4894
4895
  const rows = lines.map(([id, at]) => ` ${id}: ${at.join(", ")}`).join(`
4895
4896
  `);
4896
- throw new GamecrateError(`${twice.length} mod id(s) are declared by more than one directory in ${describe(source)}`, Exit.Config, `${rows}
4897
+ throw new GamecrateError(`${twice.length} mod id(s) are declared by more than one directory in ${describe2(source)}`, Exit.Config, `${rows}
4897
4898
  pin one of them with --subdir`);
4898
4899
  }
4899
4900
  const clashes = pins.filter((pin) => existingKey(target.existing, pin.id) !== undefined);
@@ -5023,7 +5024,7 @@ async function syncWorkshop(ctx, name, pins) {
5023
5024
  function gitPin(url, entry) {
5024
5025
  return { url, ...entry.subdir === undefined ? {} : { subdir: entry.subdir } };
5025
5026
  }
5026
- function describe(source) {
5027
+ function describe2(source) {
5027
5028
  if (source.kind === "path")
5028
5029
  return source.value;
5029
5030
  if (source.kind === "workshop")
@@ -5302,7 +5303,8 @@ function dockerRun(mounts, script) {
5302
5303
  ];
5303
5304
  }
5304
5305
  function shq(value) {
5305
- return `'${value.replaceAll("'", `'\\''`)}'`;
5306
+ const escaped = value.replaceAll("'", String.raw`'\''`);
5307
+ return `'${escaped}'`;
5306
5308
  }
5307
5309
  function craneArgv(mounts, ref, lines) {
5308
5310
  const creds = ref === null ? null : registryCreds();
@@ -5353,7 +5355,7 @@ async function runLive(argv, what) {
5353
5355
  }
5354
5356
  function lastLine(text) {
5355
5357
  return text.split(`
5356
- `).map((l) => l.trim()).filter((l) => l !== "").at(-1) ?? "no output";
5358
+ `).map((l) => l.trim()).findLast((l) => l !== "") ?? "no output";
5357
5359
  }
5358
5360
  async function craneAppend(opts) {
5359
5361
  for (const p of opts.include) {
@@ -5537,7 +5539,7 @@ function sanitizeVersion(raw, fallback) {
5537
5539
  }
5538
5540
  function versionPrefixes(version) {
5539
5541
  const parts = version.split(".");
5540
- if (parts.length < 2 || parts.some((p) => p === ""))
5542
+ if (parts.length < 2 || parts.includes(""))
5541
5543
  return [];
5542
5544
  return parts.slice(0, -1).map((_, i) => parts.slice(0, i + 1).join("."));
5543
5545
  }
@@ -5683,6 +5685,10 @@ async function download(input, opts, ctx, say) {
5683
5685
  }
5684
5686
  return (await pending).dir;
5685
5687
  }
5688
+ function loadOutput(stdout, stderr) {
5689
+ return `${stdout}
5690
+ ${stderr}`.trim() || "(no output)";
5691
+ }
5686
5692
  async function resolvedBase(base, platform) {
5687
5693
  if (base.includes("@sha256:"))
5688
5694
  return base;
@@ -5708,7 +5714,7 @@ async function readGate(ref, opts) {
5708
5714
  reads.push(await craneLabels(ref));
5709
5715
  if (opts.load)
5710
5716
  reads.push(await inspectLabels(ref));
5711
- if (reads.some((labels) => labels === null))
5717
+ if (reads.includes(null))
5712
5718
  return { present: false, buildid: null };
5713
5719
  const ids = reads.map((labels) => labels?.["steam.buildid"] ?? null);
5714
5720
  return { present: reads.length > 0, buildid: ids.every((id) => id === ids[0]) ? ids[0] ?? null : null };
@@ -5726,8 +5732,7 @@ async function inspectLabels(ref) {
5726
5732
  async function dockerLoad(tar, ref) {
5727
5733
  const { code, stdout, stderr } = await capture(["docker", "load", "-i", tar]);
5728
5734
  if (code !== 0 || !stdout.includes(`Loaded image: ${ref}`)) {
5729
- throw new GamecrateError(`docker load failed for ${tar}`, Exit.Environment, `expected "Loaded image: ${ref}", got: ${`${stdout}
5730
- ${stderr}`.trim() || "(no output)"}`);
5735
+ throw new GamecrateError(`docker load failed for ${tar}`, Exit.Environment, `expected "Loaded image: ${ref}", got: ${loadOutput(stdout, stderr)}`);
5731
5736
  }
5732
5737
  }
5733
5738
  async function dockerLabel(ref, labels) {
@@ -6002,7 +6007,10 @@ ${cases}
6002
6007
  complete -F ${fn} ${NAME}
6003
6008
  `;
6004
6009
  }
6005
- const zshVerbs = SUBCOMMANDS.map((s) => ` '${s.name}:${s.summary.replaceAll("'", String.raw`'\''`)}'`).join(`
6010
+ const zshVerbs = SUBCOMMANDS.map((s) => {
6011
+ const summary = s.summary.replaceAll("'", String.raw`'\''`);
6012
+ return ` '${s.name}:${summary}'`;
6013
+ }).join(`
6006
6014
  `);
6007
6015
  const zshFlags = options.map((f) => {
6008
6016
  const desc = f.description.replaceAll("'", String.raw`'\''`).replaceAll(/[[\]:]/g, "");
@@ -7590,7 +7598,7 @@ function published2(value) {
7590
7598
  }
7591
7599
 
7592
7600
  // src/index.ts
7593
- var VERSION = "2.3.0";
7601
+ var VERSION = "2.3.1";
7594
7602
  async function main(argv) {
7595
7603
  const supervised = supervisedDir(argv);
7596
7604
  try {
@@ -7871,8 +7879,10 @@ async function rebuiltForUpdate(plan, config, args) {
7871
7879
  cwd: process.cwd(),
7872
7880
  configFile: await globalConfigPath(),
7873
7881
  ask: async (question) => {
7874
- if (args.yes)
7875
- return rebuilt = true;
7882
+ if (args.yes) {
7883
+ rebuilt = true;
7884
+ return true;
7885
+ }
7876
7886
  if (process.stdin.isTTY !== true || args.json) {
7877
7887
  warn("no terminal to ask, so the launch keeps the current image");
7878
7888
  return false;
@@ -8016,7 +8026,7 @@ async function copyOutLogs(plan) {
8016
8026
  try {
8017
8027
  await cp(source, target, { recursive: true, force: true });
8018
8028
  } catch (error) {
8019
- warn(`could not copy ${source}: ${describe2(error)}`);
8029
+ warn(`could not copy ${source}: ${describe3(error)}`);
8020
8030
  }
8021
8031
  }
8022
8032
  async function mods(args, config, plugins, defaults) {
@@ -8491,7 +8501,7 @@ async function spawnStatus(argv, interactive = false) {
8491
8501
  const proc = spawnArgv(argv, [interactive ? "inherit" : "ignore", "inherit", "inherit"]);
8492
8502
  return exited(proc);
8493
8503
  }
8494
- function describe2(error) {
8504
+ function describe3(error) {
8495
8505
  return error instanceof Error ? error.message : String(error);
8496
8506
  }
8497
8507
  function reportFatal(error) {
@@ -8503,7 +8513,7 @@ function reportFatal(error) {
8503
8513
  `);
8504
8514
  return error.code;
8505
8515
  }
8506
- process.stderr.write(`gamecrate: ${describe2(error)}
8516
+ process.stderr.write(`gamecrate: ${describe3(error)}
8507
8517
  `);
8508
8518
  return Exit.GameFailed;
8509
8519
  }
package/dist/lib.js CHANGED
@@ -242,7 +242,10 @@ function repeats(entries, key) {
242
242
  });
243
243
  return found;
244
244
  }
245
- var TAG_COMPONENT = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
245
+ function describe(value) {
246
+ return typeof value === "string" ? value : JSON.stringify(value);
247
+ }
248
+ var TAG_COMPONENT = /^\w[\w.-]*$/;
246
249
  function steamBuildRules(ctx) {
247
250
  const push = (message, path, suggestion) => {
248
251
  ctx.issues.push({
@@ -297,7 +300,7 @@ function steamBuildRules(ctx) {
297
300
  const wants = depot === "windows" ? "proton" : "xvfb";
298
301
  if (base === wants)
299
302
  return;
300
- push(`a ${String(depot)} depot cannot run on the "${base}" base`, ["variants", index, "base"], depot === "windows" ? 'set base to "proton"; it is the only base with wine' : 'set base to "xvfb", or set depot to "windows" if the image should run under wine');
303
+ push(`a ${describe(depot)} depot cannot run on the "${base}" base`, ["variants", index, "base"], depot === "windows" ? 'set base to "proton"; it is the only base with wine' : 'set base to "xvfb", or set depot to "windows" if the image should run under wine');
301
304
  });
302
305
  }
303
306
  var steamBuildSchema = obj({
@@ -1,6 +1,5 @@
1
1
  import type { ImageLaunch } from '../docker/spec';
2
- import type { ModeName, Problem } from '../types';
3
- import type { GameConfig } from '../types';
2
+ import type { GameConfig, ModeName, Problem } from '../types';
4
3
  /** What an image says about itself. Every field is null for an image gamecrate did not build. */
5
4
  export interface ImageFacts {
6
5
  present: boolean;
@@ -1,6 +1,5 @@
1
1
  import type { ImageFacts } from './image';
2
- import type { ParsedArgs, RootConfig } from '../types';
3
- import type { UpdateCheckSpec } from '../types';
2
+ import type { ParsedArgs, RootConfig, UpdateCheckSpec } from '../types';
4
3
  export interface CheckInput {
5
4
  facts: ImageFacts;
6
5
  spec: UpdateCheckSpec | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamecrate/cli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Run a modded game in a container, with mods resolved from your local checkouts.",
5
5
  "license": "MIT",
6
6
  "type": "module",