@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 +40 -30
- package/dist/lib.js +5 -2
- package/dist/types/launch/image.d.ts +1 -2
- package/dist/types/launch/updates.d.ts +1 -2
- package/package.json +1 -1
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 ? [
|
|
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
|
|
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
|
|
1177
|
+
function addSession(mounts, env, plan) {
|
|
1181
1178
|
const { settings } = plan;
|
|
1182
1179
|
if (settings.display === "x11")
|
|
1183
|
-
addX11(mounts, env
|
|
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
|
|
1190
|
+
function addX11(mounts, env) {
|
|
1194
1191
|
const x11 = x11Session();
|
|
1195
1192
|
if (!x11)
|
|
1196
1193
|
return;
|
|
1197
|
-
mounts.push({ type: "
|
|
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
|
-
|
|
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 ${
|
|
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) =>
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
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()).
|
|
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.
|
|
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.
|
|
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: ${
|
|
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) =>
|
|
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.
|
|
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
|
-
|
|
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}: ${
|
|
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
|
|
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: ${
|
|
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
|
-
|
|
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 ${
|
|
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;
|