@gamecrate/cli 0.1.0 → 1.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/README.md +204 -33
- package/dist/gamecrate.js +1486 -715
- package/dist/lib.js +320 -0
- package/dist/types/cli/args.d.ts +56 -0
- package/dist/types/cli/game.d.ts +2 -0
- package/dist/types/cli/help.d.ts +7 -0
- package/dist/types/cli/list.d.ts +2 -0
- package/dist/types/cli/output.d.ts +78 -0
- package/dist/types/cli/profile.d.ts +6 -0
- package/dist/types/config/builtin.d.ts +3 -0
- package/dist/types/config/jsonc.d.ts +5 -0
- package/dist/types/config/load.d.ts +47 -0
- package/dist/types/config/read.d.ts +11 -0
- package/dist/types/config/validate.d.ts +12 -0
- package/dist/types/docker/identity.d.ts +6 -0
- package/dist/types/docker/preflight.d.ts +3 -0
- package/dist/types/docker/run.d.ts +39 -0
- package/dist/types/docker/spec.d.ts +24 -0
- package/dist/types/docker/window.d.ts +50 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/launch/generate.d.ts +8 -0
- package/dist/types/launch/instance.d.ts +20 -0
- package/dist/types/launch/prepare.d.ts +87 -0
- package/dist/types/launch/resolve.d.ts +17 -0
- package/dist/types/launch/stage.d.ts +13 -0
- package/dist/types/launch/supervisor.d.ts +48 -0
- package/dist/types/lib.d.ts +3 -0
- package/dist/types/mods/modindex.d.ts +29 -0
- package/dist/types/mods/staleness.d.ts +28 -0
- package/dist/types/mods/worktree.d.ts +18 -0
- package/dist/types/plugin.d.ts +35 -0
- package/dist/types/run/registry.d.ts +22 -0
- package/dist/types/types.d.ts +428 -0
- package/package.json +15 -10
- package/src/cli/args.ts +0 -592
- package/src/cli/help.ts +0 -193
- package/src/cli/output.ts +0 -246
- package/src/config/builtin.ts +0 -19
- package/src/config/jsonc.ts +0 -21
- package/src/config/load.ts +0 -387
- package/src/config/validate.ts +0 -0
- package/src/docker/identity.ts +0 -25
- package/src/docker/preflight.ts +0 -243
- package/src/docker/run.ts +0 -212
- package/src/docker/spec.ts +0 -357
- package/src/docker/window.ts +0 -152
- package/src/index.ts +0 -875
- package/src/launch/generate.ts +0 -151
- package/src/launch/instance.ts +0 -106
- package/src/launch/prepare.ts +0 -332
- package/src/launch/resolve.ts +0 -383
- package/src/launch/stage.ts +0 -97
- package/src/lib.ts +0 -22
- package/src/mods/modindex.ts +0 -539
- package/src/mods/staleness.ts +0 -125
- package/src/mods/worktree.ts +0 -107
- package/src/plugin.ts +0 -152
- package/src/types.ts +0 -423
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface WindowWatch {
|
|
2
|
+
stop: () => void;
|
|
3
|
+
}
|
|
4
|
+
export interface Toplevel {
|
|
5
|
+
id: string;
|
|
6
|
+
wmClass: string;
|
|
7
|
+
}
|
|
8
|
+
/** Every window that appeared since the snapshot and belongs to this game, in wmctrl's order. */
|
|
9
|
+
export declare function newMatches(now: Toplevel[], seen: Set<string>, executable: string): Toplevel[];
|
|
10
|
+
export declare function parseWindowPid(stdout: string): number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Another supervisor's window. Our own claim, or a dead or non-gamecrate pid, is not.
|
|
13
|
+
* Deliberately not `isRunning`: the cmdline read already throws for a dead pid, and it stays
|
|
14
|
+
* readable for a supervisor owned by another user, where a signal check is denied and would
|
|
15
|
+
* have us steal that run's window.
|
|
16
|
+
*
|
|
17
|
+
* Matched on each argument's own basename rather than anywhere in the raw cmdline, so a
|
|
18
|
+
* gamecrate log path or data directory in an unrelated process's arguments is not a peer. An
|
|
19
|
+
* editor opened on a directory literally named gamecrate still is; nothing in /proc separates
|
|
20
|
+
* those two.
|
|
21
|
+
*
|
|
22
|
+
* It also stops matching `bun run src/index.ts`, where the old substring match caught the repo
|
|
23
|
+
* directory in the script path. Installed users are unaffected, the bin and gamecrate.js both
|
|
24
|
+
* match; it costs peer detection between two concurrent from-source dev runs.
|
|
25
|
+
*
|
|
26
|
+
* Residual: before adoption `_NET_WM_PID` is the container's pid namespace, so this looks a
|
|
27
|
+
* container-local number up in the host's `/proc`. A false hit makes the run skip its own
|
|
28
|
+
* window for the whole wait. Container game pids are small and low host pids are kernel
|
|
29
|
+
* threads with an empty cmdline, so in practice the read returns nothing and no match happens.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isPeerClaim(pid: number, self: number): boolean;
|
|
32
|
+
export interface AdoptOptions {
|
|
33
|
+
executable: string;
|
|
34
|
+
title: string;
|
|
35
|
+
/** Set for an engine that claims WM_DELETE_WINDOW and ignores it, RimWorld being the one. */
|
|
36
|
+
stripDelete: boolean;
|
|
37
|
+
/** Called once the window a stripDelete run adopted is gone. */
|
|
38
|
+
onClosed: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Retitles the window a headed X11 run opens and fixes what the WM knows about it. Snapshots
|
|
42
|
+
* the screen first and takes the first window that was not there: an X client inside a
|
|
43
|
+
* container reports a container-local _NET_WM_PID and hostname, so neither of those identifies
|
|
44
|
+
* the run from out here. The WM_CLASS comes from the executable, which narrows it to this game
|
|
45
|
+
* rather than any window that opened. Once adopted, _NET_WM_PID holds our pid, so a second run
|
|
46
|
+
* starting at the same time can see the window is already spoken for and keep waiting for its own.
|
|
47
|
+
*/
|
|
48
|
+
export declare function adoptNewWindow(opts: AdoptOptions): Promise<WindowWatch>;
|
|
49
|
+
/** `WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW, WM_TAKE_FOCUS`, or `: not found.` */
|
|
50
|
+
export declare function parseAtoms(stdout: string): string[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LaunchPlan } from '../types';
|
|
2
|
+
/** Rewritten in full every launch; the resolved list is the only source of truth. */
|
|
3
|
+
export declare function generateModsConfig(plan: LaunchPlan): Promise<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Merges key-by-key. The live Prefs holds ~40 tuned keys, so a rewrite destroys
|
|
6
|
+
* volumeMaster, uiScale, langFolderName and the nested screenShakeIntensity block.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergePrefs(plan: LaunchPlan): Promise<string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ParsedArgs, Problem, ProfileConfig, Settings, WorktreeRequest } from '../types';
|
|
2
|
+
export interface InstanceSelection {
|
|
3
|
+
/** Undefined for the base profile. */
|
|
4
|
+
name?: string;
|
|
5
|
+
/** profileDir, or <profileDir>/instances/<name>. */
|
|
6
|
+
dir: string;
|
|
7
|
+
requests: WorktreeRequest[];
|
|
8
|
+
problems: Problem[];
|
|
9
|
+
settings?: Partial<Settings>;
|
|
10
|
+
}
|
|
11
|
+
export interface InstanceOptions {
|
|
12
|
+
profileDir: string;
|
|
13
|
+
/** Absent when the caller only has a profile name, as `clean` on an unknown profile does. */
|
|
14
|
+
profile?: ProfileConfig;
|
|
15
|
+
args: Partial<ParsedArgs>;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
env?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Decides which sub-run of a profile this is. Any worktree in the set forks one. */
|
|
20
|
+
export declare function resolveInstance(options: InstanceOptions): InstanceSelection;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { BuildPolicy, GameConfig, LaunchPlan, PullPolicy } from '../types';
|
|
2
|
+
/** Resolved image id, so `launches.jsonl` records what actually ran, not a floating tag. */
|
|
3
|
+
export declare function imageDigest(ref: string): Promise<string | null>;
|
|
4
|
+
/**
|
|
5
|
+
* Pulls or builds per policy. Shared by the `build` subcommand and `run`, so a launch can no
|
|
6
|
+
* longer proceed against an image the user asked to refresh.
|
|
7
|
+
*/
|
|
8
|
+
export declare function acquireImage(game: string, config: GameConfig, pull: PullPolicy): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Tag for the derived image, keeping the registry path readable. A tag only exists after the
|
|
11
|
+
* last `/`: before it a colon is a registry port, and an `@` means a digest no suffix can ride.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runtimeLayerRef(ref: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Builds (once) a thin layer over the adapter's image carrying an X server and imagemagick.
|
|
16
|
+
* Detects the package manager so a debian-based game image works the same as an Arch one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensureRuntimeLayer(ref: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Builds local mods before launch. `always` builds every local mod; `auto` builds only the
|
|
21
|
+
* ones resolution flagged stale; `never` skips. A build failure stops the launch — shipping
|
|
22
|
+
* the previous DLL after a failed compile is how you debug code that is not running.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildLocalMods(plan: LaunchPlan, policy: BuildPolicy): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Per-instance launch lock. Two concurrent runs would both `rm -rf` the same stage tree, so
|
|
27
|
+
* the second is refused rather than allowed to race. Separate instances never meet here.
|
|
28
|
+
*/
|
|
29
|
+
export interface ProfileLock {
|
|
30
|
+
release: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Refuses when this profile and instance are already up, and clears a lock whose holder is
|
|
34
|
+
* gone. Detach runs this before it forks, so a stale lock cannot strand the child.
|
|
35
|
+
*/
|
|
36
|
+
export declare function clearLock(plan: LaunchPlan): Promise<void>;
|
|
37
|
+
/** The parent wrote this lock with the child's pid, so the child only has to drop it. */
|
|
38
|
+
export declare function heldLock(plan: LaunchPlan): ProfileLock;
|
|
39
|
+
export declare function takeLock(plan: LaunchPlan): Promise<ProfileLock>;
|
|
40
|
+
/** Everything ps, stop, attach and wait need about a run, without reopening the container. */
|
|
41
|
+
export interface LockRecord {
|
|
42
|
+
pid: number;
|
|
43
|
+
container: string;
|
|
44
|
+
game: string;
|
|
45
|
+
profile: string;
|
|
46
|
+
instance?: string;
|
|
47
|
+
detached: boolean;
|
|
48
|
+
mode?: string;
|
|
49
|
+
startedAt: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function lockPath(plan: LaunchPlan): string;
|
|
52
|
+
export declare function readLock(path: string): Promise<LockRecord | undefined>;
|
|
53
|
+
/** wx fails rather than truncating, which is what makes this a lock and not a note. */
|
|
54
|
+
export declare function writeLock(plan: LaunchPlan, record: Omit<LockRecord, 'startedAt'>): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* A signalled supervisor cannot finish before its own `docker stop --timeout` does, so the
|
|
57
|
+
* budget is derived from that rather than guessed alongside it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const STOP_RELEASE_WAIT_MS: number;
|
|
60
|
+
/**
|
|
61
|
+
* What stopRun did, because "the supervisor took the signal" and "the lock was stale anyway"
|
|
62
|
+
* are not the same answer and the caller has to say which one it is.
|
|
63
|
+
*/
|
|
64
|
+
export type StopOutcome = 'signalled' | 'orphaned' | 'held';
|
|
65
|
+
/**
|
|
66
|
+
* Signals the supervisor, not the container. runContainer turns SIGTERM into a docker stop and
|
|
67
|
+
* returns 130, so the run records itself as stopped rather than crashed. Only a run whose
|
|
68
|
+
* supervisor is already gone gets the container stopped out from under it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function stopRun(record: LockRecord, lockFile: string): Promise<StopOutcome>;
|
|
71
|
+
/**
|
|
72
|
+
* `--replace`: ends the run for this profile and instance only, so a parallel worktree run is
|
|
73
|
+
* untouched. Goes through stopRun so the replaced run reports `stopped`; a raw docker stop
|
|
74
|
+
* would land as 137 instead.
|
|
75
|
+
*/
|
|
76
|
+
export declare function replacePrevious(plan: LaunchPlan): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* A pid alone is not proof. Detach leaves a long-lived process per run, so a recycled number
|
|
79
|
+
* would refuse every future launch and give ps a ghost with an invented uptime.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isRunning(pid: number, startedAt?: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Grabs one frame from inside the running container. The run dir is already bind-mounted at
|
|
84
|
+
* CONTAINER_LOG_DIR, so the png lands next to that run's logs with no extra mount.
|
|
85
|
+
*/
|
|
86
|
+
export declare function captureScreenshot(container: string, plan: LaunchPlan): Promise<string | null>;
|
|
87
|
+
export declare function writeLaunchRecord(plan: LaunchPlan, image: string): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GamePlugin } from '../plugin';
|
|
2
|
+
import type { LaunchPlan, ModIndex, ParsedArgs, Problem, RootConfig } from '../types';
|
|
3
|
+
export interface ResolveOptions {
|
|
4
|
+
game: string;
|
|
5
|
+
profile: string;
|
|
6
|
+
root: RootConfig;
|
|
7
|
+
plugins: Map<string, GamePlugin>;
|
|
8
|
+
args?: Partial<ParsedArgs>;
|
|
9
|
+
/** Prebuilt index; buildIndex runs when absent. */
|
|
10
|
+
index?: ModIndex;
|
|
11
|
+
/** Overrides process.cwd() for ambient worktree detection; tests set it. */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolvePlan(options: ResolveOptions): Promise<{
|
|
15
|
+
plan: LaunchPlan;
|
|
16
|
+
problems: Problem[];
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LaunchPlan, Mount } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Wipes and rebuilds the staging tree, returning one read-only bind per mod.
|
|
4
|
+
* Never creates a symlink: a host symlink into a mod checkout dangles inside the container.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stageMods(plan: LaunchPlan): Promise<Mount[]>;
|
|
7
|
+
/** Pre-creates the profile skeleton as the caller, before docker can create it as root. */
|
|
8
|
+
export declare function ensureProfileTree(plan: LaunchPlan): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Walks with lstat semantics, so a dangling symlink inside a mounted tree is reported
|
|
11
|
+
* rather than thrown. Stops once `limit` foreign paths are found.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectForeignOwnership(dir: string, uid: number, limit?: number): Promise<string[]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { NoticeSchedule } from '../cli/output';
|
|
2
|
+
import type { LockRecord } from './prepare';
|
|
3
|
+
import type { ExitReason, LaunchPlan, LaunchResult } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Written once, after the spawn, with the child's pid: a take-then-rewrite would leave a dead
|
|
6
|
+
* pid over a live child, and the next launcher would unlink it and race the same stage tree.
|
|
7
|
+
* Writing first is not open either, since a placeholder pid reads as "no lock" everywhere.
|
|
8
|
+
*
|
|
9
|
+
* So the child is killed when the write loses the `wx` race, because a supervisor with no lock
|
|
10
|
+
* runs unguarded and its release would drop the winner's lock.
|
|
11
|
+
*
|
|
12
|
+
* Residual: a parent killed between the spawn and the write leaves that child unguarded, with
|
|
13
|
+
* nothing left to kill it. That window spans a fork and an exec, so milliseconds.
|
|
14
|
+
*/
|
|
15
|
+
export declare function forkSupervisor(plan: LaunchPlan, argv: string[]): Promise<number>;
|
|
16
|
+
export declare function recordExit(plan: LaunchPlan, result: LaunchResult): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* The supervisor has no terminal and its stdio is discarded, so the exit record is the only way
|
|
19
|
+
* a caller ever learns it died. The lock goes only once that record is on disk, so a failed
|
|
20
|
+
* write leaves it: clearLock takes a dead holder on the next launch, while a cleared lock with
|
|
21
|
+
* no record makes wait answer "no run recorded" for a run that really failed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function supervisorFailed(dir: string, code: number): Promise<number>;
|
|
24
|
+
export interface ExitRecord {
|
|
25
|
+
at: string;
|
|
26
|
+
code: number;
|
|
27
|
+
reason: ExitReason;
|
|
28
|
+
container?: string;
|
|
29
|
+
runDir?: string;
|
|
30
|
+
}
|
|
31
|
+
/** The other half of writeExit. Missing or corrupt reads as "no exit recorded", never a throw. */
|
|
32
|
+
export declare function lastExit(instanceDir: string): Promise<ExitRecord | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Blocks while a live holder has the lock. `orphaned` is a holder that died without recording
|
|
35
|
+
* anything, which is reachable with SIGKILL; `absent` is a run that never happened. Neither
|
|
36
|
+
* can ever produce an exit code, so neither may keep waiting for one.
|
|
37
|
+
*
|
|
38
|
+
* Once a lock has been seen, the pid outranks the file: the lock can vanish because someone
|
|
39
|
+
* else cleared it, but a live pid still owes us a record.
|
|
40
|
+
*/
|
|
41
|
+
export declare function awaitExit(instanceDir: string, poll?: number): Promise<ExitRecord | 'orphaned' | 'absent'>;
|
|
42
|
+
/**
|
|
43
|
+
* The lock is written right after the spawn, but the supervisor only opens its run log after
|
|
44
|
+
* preflight, staging and the image, so `current` can point at the previous run for as long as
|
|
45
|
+
* a pull takes. tail follows a descriptor, so starting there would watch a finished run and
|
|
46
|
+
* never catch up. Waiting is bounded by the holder: if it dies first, there is nothing coming.
|
|
47
|
+
*/
|
|
48
|
+
export declare function awaitRunLog(instanceDir: string, lock: LockRecord, poll?: number, notice?: NoticeSchedule): Promise<boolean>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { GamePlugin, ModsConfigInput } from './plugin';
|
|
2
|
+
export { PLUGIN_API_VERSION } from './plugin';
|
|
3
|
+
export type { DataDirSpec, DisplayBackend, DynamicModEntry, GameConfig, GameFilesSpec, ImageSpec, InstanceConfig, LibraryEntry, LogFileSpec, ModEntry, ModEntryObject, ModManifest, ModeName, ModsDirSpec, NetworkPolicy, ProfileConfig, ScanRoot, Settings, } from './types';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { GamePlugin } from '../plugin';
|
|
2
|
+
import type { GameConfig, ModIndex, ModRecord, Problem, WorktreeRequest } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* `**` spans separators, `*` and `?` do not; everything else is literal. Mod folders are
|
|
5
|
+
* routinely named `[KV] Mod Manager`, so brackets and braces must not be glob syntax.
|
|
6
|
+
*/
|
|
7
|
+
export declare function globMatch(pattern: string, path: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Stamps every record inside a requested worktree, then scans the worktree itself so a tree
|
|
10
|
+
* no scanRoot reaches still contributes. Only a caller who stood in, typed, or exported a
|
|
11
|
+
* directory can produce `selectedWorktree`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyWorktreeRequests(index: ModIndex, requests: WorktreeRequest[], config: GameConfig): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Forces one packageId to come from a named directory, whatever the profile pinned. This is
|
|
16
|
+
* the only override that reaches a mod already in preCore/core/dlc/base, because slot
|
|
17
|
+
* expansion is first-wins and a later `--mod` entry for a present id is dropped.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applySourceOverrides(index: ModIndex, overrides: string[], config: GameConfig): Promise<Problem[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Scans the game install, then every scan root in declaration order, then the workshop root.
|
|
22
|
+
* Local roots rescan every launch; only the workshop scan is cached, against the acf stamp.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildIndex(game: string, config: GameConfig, plugin: GamePlugin): Promise<ModIndex>;
|
|
25
|
+
/**
|
|
26
|
+
* `path:` and `workshop:` are explicit; a bare string resolves as exact packageId, then the
|
|
27
|
+
* game's alias map, then a CLI-only short name. Ambiguity that the ladder cannot break is fatal.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveModRef(index: ModIndex, ref: string, game: GameConfig): ModRecord | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StaleReport } from '../types';
|
|
2
|
+
export interface Timestamped {
|
|
3
|
+
/** Relative to the mod directory. */
|
|
4
|
+
path: string;
|
|
5
|
+
mtimeMs: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BuildTimes {
|
|
8
|
+
newestSource?: Timestamped;
|
|
9
|
+
newestAssembly?: Timestamped;
|
|
10
|
+
/** Every .cs mtime, so a report can count how many beat the assembly. */
|
|
11
|
+
sourceTimes: number[];
|
|
12
|
+
}
|
|
13
|
+
/** One walk answers both questions: is this stale, and which files say so. */
|
|
14
|
+
export declare function scanBuildTimes(dir: string): Promise<BuildTimes>;
|
|
15
|
+
/**
|
|
16
|
+
* Drives `--build auto`, so a mod that has never been compiled counts as stale. The warning
|
|
17
|
+
* is the stricter one: see staleReport.
|
|
18
|
+
*/
|
|
19
|
+
export declare function decideStale(times: BuildTimes): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Null when there is nothing to say: no C#, no assemblies to compare against, or the build is
|
|
22
|
+
* current. A mod may legitimately ship XML only, so this never reports on one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function staleReport(times: BuildTimes): StaleReport | null;
|
|
25
|
+
export declare function staleWarning(packageId: string, report: StaleReport, now?: number): string;
|
|
26
|
+
/** Coarse on purpose: "4m" is the whole signal, a duration to the second is noise. */
|
|
27
|
+
export declare function duration(ms: number): string;
|
|
28
|
+
export declare function ago(mtimeMs: number, now?: number): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Problem, WorktreeRequest, WorktreeSource } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves one worktree request. Returns null when `dir` is not inside a LINKED worktree,
|
|
4
|
+
* which is what keeps a primary checkout, a non-repo directory and a pruned gitdir from
|
|
5
|
+
* counting as a selection.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveWorktree(dir: string, source: WorktreeSource, order: number): WorktreeRequest | Problem;
|
|
8
|
+
/** True when `dir` is the worktree root or lives underneath it. */
|
|
9
|
+
export declare function contains(request: WorktreeRequest, dir: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Assembles every request in precedence order: explicit flags first (left to right), then the
|
|
12
|
+
* env var, then cwd. Only a linked worktree survives; everything else becomes an ignorable
|
|
13
|
+
* Problem so the caller can decide how loudly to say so.
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectRequests(flags: string[], env: string | undefined, cwd: string, disabled: boolean): {
|
|
16
|
+
requests: WorktreeRequest[];
|
|
17
|
+
problems: Problem[];
|
|
18
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GameConfig, ModManifest } from './types';
|
|
2
|
+
/** Bumped when a change would make an older plugin misbehave rather than merely lag. */
|
|
3
|
+
export declare const PLUGIN_API_VERSION = 1;
|
|
4
|
+
export interface ModsConfigInput {
|
|
5
|
+
version: string;
|
|
6
|
+
buildNumber: number;
|
|
7
|
+
/** Lowercased packageIds, load order. */
|
|
8
|
+
activeMods: string[];
|
|
9
|
+
knownExpansions: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A game's file formats plus the parts of its config that describe the game, not this machine.
|
|
13
|
+
* Everything here takes plain data and throws plain Errors, so no plugin links the core runtime.
|
|
14
|
+
*/
|
|
15
|
+
export interface GamePlugin {
|
|
16
|
+
apiVersion: number;
|
|
17
|
+
/** The key this game answers to on the command line. */
|
|
18
|
+
game: string;
|
|
19
|
+
/** Merged under the user's `games.<game>` block, so a user only writes what is theirs. */
|
|
20
|
+
defaults: Partial<GameConfig>;
|
|
21
|
+
/** null means "this file is not a mod manifest". A malformed file throws. */
|
|
22
|
+
parseManifest(text: string): ModManifest | null;
|
|
23
|
+
renderModsConfig(input: ModsConfigInput): string;
|
|
24
|
+
mergePrefs(existing: string | null, owned: Record<string, string>): string;
|
|
25
|
+
/** Prefs keys that put the game in a window instead of fullscreen. */
|
|
26
|
+
windowedPrefs: Record<string, string>;
|
|
27
|
+
/** Version.txt as the engine writes it. null when it does not parse. */
|
|
28
|
+
parseVersion(text: string): {
|
|
29
|
+
version: string;
|
|
30
|
+
buildNumber: number;
|
|
31
|
+
} | null;
|
|
32
|
+
}
|
|
33
|
+
/** Keyed by game name. A second plugin claiming a name already taken is a config error. */
|
|
34
|
+
export declare function loadPlugins(specs: string[], configFile: string): Promise<Map<string, GamePlugin>>;
|
|
35
|
+
export declare function requirePlugin(plugins: Map<string, GamePlugin>, game: string): GamePlugin;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LockRecord } from '../launch/prepare';
|
|
2
|
+
export interface RunRecord {
|
|
3
|
+
game: string;
|
|
4
|
+
profile: string;
|
|
5
|
+
instance?: string;
|
|
6
|
+
container: string;
|
|
7
|
+
pid?: number;
|
|
8
|
+
mode?: string;
|
|
9
|
+
startedAt?: string;
|
|
10
|
+
uptime?: string;
|
|
11
|
+
status: 'running' | 'starting' | 'orphaned';
|
|
12
|
+
}
|
|
13
|
+
export declare function parseDockerRuns(stdout: string): RunRecord[];
|
|
14
|
+
/** <dataRoot>/<game>/<profile>/.gamecrate/lock, plus one level of instances under each. */
|
|
15
|
+
export declare function walkLocks(dataRoot: string): Promise<LockRecord[]>;
|
|
16
|
+
type Docker = () => Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Docker answers for anything with a container. The lock walk covers the phase before one
|
|
19
|
+
* exists (pull, build, stage) and a lock whose container is already gone.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listRuns(dataRoot: string, docker?: Docker): Promise<RunRecord[]>;
|
|
22
|
+
export {};
|