picopilot 0.1.4 → 0.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +111 -16
- package/dist/cli.js.map +1 -1
- package/dist/commands/audio.d.ts +3 -3
- package/dist/commands/audio.d.ts.map +1 -1
- package/dist/commands/audio.js +61 -44
- package/dist/commands/audio.js.map +1 -1
- package/dist/commands/cart-includes.d.ts +11 -0
- package/dist/commands/cart-includes.d.ts.map +1 -0
- package/dist/commands/cart-includes.js +26 -0
- package/dist/commands/cart-includes.js.map +1 -0
- package/dist/commands/doctor.d.ts +22 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +222 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +3 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +66 -86
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/gfx.d.ts +2 -2
- package/dist/commands/gfx.d.ts.map +1 -1
- package/dist/commands/gfx.js +3 -2
- package/dist/commands/gfx.js.map +1 -1
- package/dist/commands/label-cart.d.ts +72 -0
- package/dist/commands/label-cart.d.ts.map +1 -0
- package/dist/commands/label-cart.js +228 -0
- package/dist/commands/label-cart.js.map +1 -0
- package/dist/commands/lint.d.ts +2 -2
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +3 -2
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/minify.d.ts +2 -2
- package/dist/commands/minify.d.ts.map +1 -1
- package/dist/commands/minify.js +3 -2
- package/dist/commands/minify.js.map +1 -1
- package/dist/commands/pico8-env.d.ts +76 -0
- package/dist/commands/pico8-env.d.ts.map +1 -0
- package/dist/commands/pico8-env.js +71 -0
- package/dist/commands/pico8-env.js.map +1 -0
- package/dist/commands/playtest.d.ts +2 -2
- package/dist/commands/playtest.d.ts.map +1 -1
- package/dist/commands/playtest.js +6 -32
- package/dist/commands/playtest.js.map +1 -1
- package/dist/commands/run.d.ts +3 -4
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +46 -19
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/serve.d.ts +2 -2
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +65 -19
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/shrinko-env.d.ts +22 -0
- package/dist/commands/shrinko-env.d.ts.map +1 -0
- package/dist/commands/shrinko-env.js +15 -0
- package/dist/commands/shrinko-env.js.map +1 -0
- package/dist/commands/tokens.d.ts +4 -4
- package/dist/commands/tokens.d.ts.map +1 -1
- package/dist/commands/tokens.js +3 -2
- package/dist/commands/tokens.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -2
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +3 -2
- package/dist/commands/verify.js.map +1 -1
- package/dist/engine/child-env.d.ts +14 -0
- package/dist/engine/child-env.d.ts.map +1 -0
- package/dist/engine/child-env.js +21 -0
- package/dist/engine/child-env.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +15 -9
- package/dist/engine/gfx/label.d.ts.map +1 -1
- package/dist/engine/gfx/label.js +130 -38
- package/dist/engine/gfx/label.js.map +1 -1
- package/dist/engine/pico8/adapter.d.ts +23 -3
- package/dist/engine/pico8/adapter.d.ts.map +1 -1
- package/dist/engine/pico8/adapter.js.map +1 -1
- package/dist/engine/pico8/cleanup.d.ts +16 -0
- package/dist/engine/pico8/cleanup.d.ts.map +1 -0
- package/dist/engine/pico8/cleanup.js +74 -0
- package/dist/engine/pico8/cleanup.js.map +1 -0
- package/dist/engine/pico8/display.d.ts +149 -0
- package/dist/engine/pico8/display.d.ts.map +1 -0
- package/dist/engine/pico8/display.js +311 -0
- package/dist/engine/pico8/display.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -1
- package/dist/engine/pico8/drive.js +28 -2
- package/dist/engine/pico8/drive.js.map +1 -1
- package/dist/engine/pico8/harness.d.ts +8 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -1
- package/dist/engine/pico8/harness.js +9 -1
- package/dist/engine/pico8/harness.js.map +1 -1
- package/dist/engine/pico8/index.d.ts +3 -0
- package/dist/engine/pico8/index.d.ts.map +1 -1
- package/dist/engine/pico8/index.js +3 -0
- package/dist/engine/pico8/index.js.map +1 -1
- package/dist/engine/pico8/session-daemon-entry.d.ts +1 -1
- package/dist/engine/pico8/session-daemon-entry.js +1 -1
- package/dist/engine/pico8/session-daemon-main.d.ts +1 -1
- package/dist/engine/pico8/session-daemon-main.js +1 -1
- package/dist/engine/pico8/session-daemon.d.ts +1 -1
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -1
- package/dist/engine/pico8/session-daemon.js +19 -19
- package/dist/engine/pico8/session-daemon.js.map +1 -1
- package/dist/engine/pico8/session.d.ts +1 -1
- package/dist/engine/pico8/session.js +1 -1
- package/dist/engine/pico8/shell.d.ts +56 -3
- package/dist/engine/pico8/shell.d.ts.map +1 -1
- package/dist/engine/pico8/shell.js +188 -24
- package/dist/engine/pico8/shell.js.map +1 -1
- package/dist/engine/pico8/supervisor.d.ts +1 -1
- package/dist/engine/pico8/supervisor.js +1 -1
- package/dist/engine/shrinko/adapter.d.ts +1 -1
- package/dist/engine/shrinko/shell.d.ts +3 -3
- package/dist/engine/shrinko/shell.js +3 -3
- package/package.json +2 -1
- package/src/cli.test.ts +188 -0
- package/src/cli.ts +122 -16
- package/src/commands/audio.test.ts +76 -1
- package/src/commands/audio.ts +79 -57
- package/src/commands/cart-includes.ts +27 -0
- package/src/commands/doctor.test.ts +149 -0
- package/src/commands/doctor.ts +280 -0
- package/src/commands/export.test.ts +327 -3
- package/src/commands/export.ts +81 -99
- package/src/commands/gfx.ts +7 -6
- package/src/commands/label-cart.ts +304 -0
- package/src/commands/lint.test.ts +6 -0
- package/src/commands/lint.ts +7 -6
- package/src/commands/minify.test.ts +6 -0
- package/src/commands/minify.ts +7 -6
- package/src/commands/pico8-env.ts +124 -0
- package/src/commands/playtest.test.ts +3 -0
- package/src/commands/playtest.ts +10 -34
- package/src/commands/run.test.ts +116 -2
- package/src/commands/run.ts +62 -24
- package/src/commands/serve.test.ts +138 -2
- package/src/commands/serve.ts +80 -21
- package/src/commands/shrinko-env.test.ts +68 -0
- package/src/commands/shrinko-env.ts +40 -0
- package/src/commands/tokens.test.ts +19 -0
- package/src/commands/tokens.ts +9 -6
- package/src/commands/verify.test.ts +12 -0
- package/src/commands/verify.ts +7 -4
- package/src/engine/child-env.ts +22 -0
- package/src/engine/gfx/fixtures/pico8-screenshot-384.png +0 -0
- package/src/engine/gfx/label.test.ts +467 -1
- package/src/engine/gfx/label.ts +147 -41
- package/src/engine/pico8/adapter.ts +25 -3
- package/src/engine/pico8/cleanup.test.ts +28 -0
- package/src/engine/pico8/cleanup.ts +76 -0
- package/src/engine/pico8/display.test.ts +210 -0
- package/src/engine/pico8/display.ts +429 -0
- package/src/engine/pico8/drive.test.ts +20 -0
- package/src/engine/pico8/drive.ts +25 -2
- package/src/engine/pico8/fake-xvfb.test-support.ts +55 -0
- package/src/engine/pico8/harness.test.ts +26 -0
- package/src/engine/pico8/harness.ts +18 -2
- package/src/engine/pico8/index.ts +3 -0
- package/src/engine/pico8/session-daemon-entry.ts +1 -1
- package/src/engine/pico8/session-daemon-main.ts +1 -1
- package/src/engine/pico8/session-daemon.test.ts +32 -1
- package/src/engine/pico8/session-daemon.ts +25 -17
- package/src/engine/pico8/session.test.ts +1 -1
- package/src/engine/pico8/session.ts +1 -1
- package/src/engine/pico8/shell.test.ts +449 -10
- package/src/engine/pico8/shell.ts +255 -26
- package/src/engine/pico8/supervisor.ts +1 -1
- package/src/engine/pico8/virtual-display.process.test.ts +206 -0
- package/src/engine/shrinko/adapter.ts +1 -1
- package/src/engine/shrinko/shell.ts +3 -3
- package/src/engine/skills/skills.test.ts +10 -0
- package/src/skills/picopilot-audio/SKILL.md +4 -8
- package/src/skills/picopilot-debug/SKILL.md +18 -1
- package/src/skills/picopilot-overview/SKILL.md +9 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kill-on-abort for child processes picopilot owns while a run is in flight
|
|
3
|
+
* (the PICO-8 process group, a private Xvfb). Normal endings (sentinel,
|
|
4
|
+
* backstop, crash) are handled by the run itself; this covers picopilot being
|
|
5
|
+
* stopped mid-run: `process.exit`, an uncaught throw, or SIGINT / SIGTERM /
|
|
6
|
+
* SIGHUP sent to picopilot alone (an agent harness or `timeout` killing it).
|
|
7
|
+
* Without it, a detached PICO-8 or an Xvfb would outlive picopilot.
|
|
8
|
+
*
|
|
9
|
+
* `kill` must be synchronous (it runs inside an `exit` handler). On a signal,
|
|
10
|
+
* the hook kills, then re-raises the signal with picopilot's handler removed so
|
|
11
|
+
* the default behaviour (terminate) still happens, unless some other listener
|
|
12
|
+
* owns that signal (an MCP server, a test runner), which then decides.
|
|
13
|
+
*/
|
|
14
|
+
const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
15
|
+
/**
|
|
16
|
+
* The kill functions of every run in flight. ONE listener per event serves them
|
|
17
|
+
* all (installed while the set is non-empty), so many concurrent runs (an MCP
|
|
18
|
+
* server) never trip Node's MaxListeners warning.
|
|
19
|
+
*/
|
|
20
|
+
const pending = new Set();
|
|
21
|
+
const signalHandlers = new Map();
|
|
22
|
+
function killAll() {
|
|
23
|
+
for (const kill of [...pending]) {
|
|
24
|
+
pending.delete(kill);
|
|
25
|
+
try {
|
|
26
|
+
kill();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// best effort: the process may already be gone
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function onExit() {
|
|
34
|
+
killAll();
|
|
35
|
+
}
|
|
36
|
+
function install() {
|
|
37
|
+
process.on('exit', onExit);
|
|
38
|
+
for (const signal of SIGNALS) {
|
|
39
|
+
const handler = () => {
|
|
40
|
+
killAll();
|
|
41
|
+
uninstall();
|
|
42
|
+
// Re-raise for the default behaviour (terminate) unless another listener
|
|
43
|
+
// (an MCP server, a test runner) owns this signal.
|
|
44
|
+
if (process.listenerCount(signal) === 0) {
|
|
45
|
+
process.kill(process.pid, signal);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
signalHandlers.set(signal, handler);
|
|
49
|
+
process.on(signal, handler);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function uninstall() {
|
|
53
|
+
process.removeListener('exit', onExit);
|
|
54
|
+
for (const [signal, handler] of signalHandlers) {
|
|
55
|
+
process.removeListener(signal, handler);
|
|
56
|
+
}
|
|
57
|
+
signalHandlers.clear();
|
|
58
|
+
}
|
|
59
|
+
/** Registers `kill` to run if picopilot exits or is signalled; returns the disposer. */
|
|
60
|
+
export function killOnAbort(kill) {
|
|
61
|
+
let entry = () => kill();
|
|
62
|
+
if (pending.size === 0)
|
|
63
|
+
install();
|
|
64
|
+
pending.add(entry);
|
|
65
|
+
return () => {
|
|
66
|
+
if (entry === undefined)
|
|
67
|
+
return;
|
|
68
|
+
pending.delete(entry);
|
|
69
|
+
entry = undefined;
|
|
70
|
+
if (pending.size === 0)
|
|
71
|
+
uninstall();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../../src/engine/pico8/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,OAAO,GAA8B,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE3E;;;;GAIG;AACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc,CAAC;AACtC,MAAM,cAAc,GAAG,IAAI,GAAG,EAA8B,CAAC;AAE7D,SAAS,OAAO;IACf,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC;YACJ,IAAI,EAAE,CAAC;QACR,CAAC;QAAC,MAAM,CAAC;YACR,+CAA+C;QAChD,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,MAAM;IACd,OAAO,EAAE,CAAC;AACX,CAAC;AAED,SAAS,OAAO;IACf,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,GAAG,EAAE;YACpB,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,CAAC;YACZ,yEAAyE;YACzE,mDAAmD;YACnD,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACnC,CAAC;QACF,CAAC,CAAC;QACF,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;AACF,CAAC;AAED,SAAS,SAAS;IACjB,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,cAAc,EAAE,CAAC;QAChD,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC3C,IAAI,KAAK,GAA6B,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,GAAG,EAAE;QACX,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,KAAK,GAAG,SAAS,CAAC;QAClB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;YAAE,SAAS,EAAE,CAAC;IACrC,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DISPLAY MODE seam (ADR-0017): how a PICO-8 `-run` session (a real
|
|
3
|
+
* audio+video session, today only the audio-record path) gets a display on a
|
|
4
|
+
* machine that may have none (a server, CI, an agent sandbox).
|
|
5
|
+
*
|
|
6
|
+
* - `native`: spawn PICO-8 as-is (it uses whatever DISPLAY/WAYLAND_DISPLAY the
|
|
7
|
+
* caller has, or fails to open a window if there is none).
|
|
8
|
+
* - `xvfb`: always run a `-run` session on a private virtual X display.
|
|
9
|
+
* - `auto`: the default. Use a virtual display only when the session needs one
|
|
10
|
+
* and none is available (no DISPLAY and no WAYLAND_DISPLAY); otherwise native.
|
|
11
|
+
*
|
|
12
|
+
* Headless `-x` passes (run screenshots, playtest, export, the label capture)
|
|
13
|
+
* never need a display, so they never consult this module and never pay the
|
|
14
|
+
* Xvfb startup cost.
|
|
15
|
+
*
|
|
16
|
+
* The virtual display prefers spawning `Xvfb -displayfd <fd>` directly: the X
|
|
17
|
+
* server picks a free display number itself and writes it back on the fd, so
|
|
18
|
+
* parallel runs never race for a fixed `:99`. When only the Debian `xvfb-run`
|
|
19
|
+
* script exists, it is the fallback (it wraps the PICO-8 command instead).
|
|
20
|
+
* Either way the virtual display lives exactly as long as the run.
|
|
21
|
+
*/
|
|
22
|
+
/** The three display modes. `auto` is the default. */
|
|
23
|
+
export declare const DISPLAY_MODES: readonly ["auto", "xvfb", "native"];
|
|
24
|
+
/** A display mode: how a `-run` session gets a display (see the module doc). */
|
|
25
|
+
export type DisplayMode = (typeof DISPLAY_MODES)[number];
|
|
26
|
+
/** The default display mode when no flag, env var or config sets one. */
|
|
27
|
+
export declare const DEFAULT_DISPLAY_MODE: DisplayMode;
|
|
28
|
+
/** The env var that sets the display mode (below the `--display` flag, above picopilot.json). */
|
|
29
|
+
export declare const DISPLAY_ENV_VAR: "PICOPILOT_DISPLAY";
|
|
30
|
+
/** The top-level picopilot.json key that sets the display mode (lowest precedence). */
|
|
31
|
+
export declare const DISPLAY_CONFIG_KEY: "display";
|
|
32
|
+
/** Whether `value` names a valid {@link DisplayMode}. */
|
|
33
|
+
export declare function isDisplayMode(value: unknown): value is DisplayMode;
|
|
34
|
+
/** The outcome of {@link resolveDisplayMode}: a mode and where it came from, or an invalid value. */
|
|
35
|
+
export type DisplayModeResolution = {
|
|
36
|
+
readonly ok: true;
|
|
37
|
+
readonly mode: DisplayMode;
|
|
38
|
+
readonly source: 'flag' | 'env' | 'config' | 'default';
|
|
39
|
+
} | {
|
|
40
|
+
readonly ok: false;
|
|
41
|
+
readonly source: 'flag' | 'env' | 'config';
|
|
42
|
+
readonly value: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the display mode with the precedence `flag > env > config > default`
|
|
46
|
+
* (the same order every picopilot setting uses). An empty/whitespace value is
|
|
47
|
+
* treated as unset; an unknown value is reported (never silently ignored), so a
|
|
48
|
+
* typo like `PICOPILOT_DISPLAY=xfvb` is a visible error rather than `auto`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveDisplayMode(sources: {
|
|
51
|
+
readonly flag?: unknown;
|
|
52
|
+
readonly env?: unknown;
|
|
53
|
+
readonly config?: unknown;
|
|
54
|
+
}): DisplayModeResolution;
|
|
55
|
+
/** Whether the environment already offers a display (X11 `DISPLAY` or a Wayland socket). */
|
|
56
|
+
export declare function hasNativeDisplay(env: NodeJS.ProcessEnv): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a `-run` session in this `mode` + `env` should run on a virtual
|
|
59
|
+
* display: always for `xvfb`, never for `native`, and for `auto` only when
|
|
60
|
+
* there is no native display.
|
|
61
|
+
*/
|
|
62
|
+
export declare function needsVirtualDisplay(mode: DisplayMode, env: NodeJS.ProcessEnv): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Finds an executable the way a shell would: a name containing `/` is checked
|
|
65
|
+
* as a path, anything else is searched on `env.PATH`. Returns the resolved path,
|
|
66
|
+
* or `undefined`. Pure lookup (no spawn), so it is cheap enough for `doctor` and
|
|
67
|
+
* for the failure path.
|
|
68
|
+
*/
|
|
69
|
+
export declare function findExecutable(name: string, env: NodeJS.ProcessEnv): string | undefined;
|
|
70
|
+
/** Which virtual-display backend this machine offers, if any (direct Xvfb is preferred). */
|
|
71
|
+
export type VirtualDisplayBackend = 'Xvfb' | 'xvfb-run';
|
|
72
|
+
/** Detects the best available virtual-display backend on `env.PATH`. */
|
|
73
|
+
export declare function detectVirtualDisplayBackend(env: NodeJS.ProcessEnv, find?: (name: string, env: NodeJS.ProcessEnv) => string | undefined): VirtualDisplayBackend | undefined;
|
|
74
|
+
/** The remedy a `display-unavailable` error names. */
|
|
75
|
+
export declare const DISPLAY_REMEDY: "install Xvfb (Debian/Ubuntu: apt install xvfb; Fedora: dnf install xorg-x11-server-Xvfb; NixOS: add xvfb-run or xorg.xvfb), or run under a desktop session (DISPLAY or WAYLAND_DISPLAY set)";
|
|
76
|
+
/**
|
|
77
|
+
* The structured "this session needs a display and there is none" failure
|
|
78
|
+
* (mirrors {@link import('./adapter.js').Pico8NotFound}). `detail` says what was
|
|
79
|
+
* tried (Xvfb missing, or Xvfb failed to start and why).
|
|
80
|
+
*/
|
|
81
|
+
export interface DisplayUnavailable {
|
|
82
|
+
readonly ok: false;
|
|
83
|
+
readonly reason: 'display-unavailable';
|
|
84
|
+
readonly remedy: typeof DISPLAY_REMEDY;
|
|
85
|
+
readonly detail: string;
|
|
86
|
+
}
|
|
87
|
+
/** Builds a {@link DisplayUnavailable} value. */
|
|
88
|
+
export declare function displayUnavailable(detail: string): DisplayUnavailable;
|
|
89
|
+
/**
|
|
90
|
+
* A running virtual display, scoped to ONE PICO-8 session. `env` holds the
|
|
91
|
+
* variables the child must see (DISPLAY for direct Xvfb); `wrap` rewrites the
|
|
92
|
+
* PICO-8 command (identity for direct Xvfb, `xvfb-run -a ...` for the fallback);
|
|
93
|
+
* `stop` tears the X server down and is idempotent.
|
|
94
|
+
*/
|
|
95
|
+
export interface VirtualDisplay {
|
|
96
|
+
readonly backend: VirtualDisplayBackend;
|
|
97
|
+
readonly env: Readonly<Record<string, string>>;
|
|
98
|
+
wrap(file: string, args: string[]): {
|
|
99
|
+
file: string;
|
|
100
|
+
args: string[];
|
|
101
|
+
};
|
|
102
|
+
stop(): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A spawned Xvfb process, abstracted so tests inject a fake. `onDisplayFd`
|
|
106
|
+
* delivers what the server writes on its `-displayfd` pipe (the display number
|
|
107
|
+
* followed by a newline); `onExit` fires when it exits; `kill` sends a signal.
|
|
108
|
+
*/
|
|
109
|
+
export interface XvfbProcess {
|
|
110
|
+
readonly pid: number | undefined;
|
|
111
|
+
onDisplayFd(cb: (chunk: string) => void): void;
|
|
112
|
+
onStderr(cb: (chunk: string) => void): void;
|
|
113
|
+
onExit(cb: (code: number | null) => void): void;
|
|
114
|
+
onError(cb: (err: NodeJS.ErrnoException) => void): void;
|
|
115
|
+
kill(signal: NodeJS.Signals): void;
|
|
116
|
+
}
|
|
117
|
+
/** The Xvfb spawn seam: tests pass a fake that writes a display number (or never does). */
|
|
118
|
+
export type XvfbSpawner = (file: string, args: string[], env: NodeJS.ProcessEnv) => XvfbProcess;
|
|
119
|
+
/**
|
|
120
|
+
* The real {@link XvfbSpawner}: stdio `[ignore, ignore, pipe, pipe]` so fd 3 is
|
|
121
|
+
* the display-number pipe. NOT detached, so a terminal Ctrl-C reaches it with
|
|
122
|
+
* the rest of picopilot's process group.
|
|
123
|
+
*/
|
|
124
|
+
export declare const spawnXvfb: XvfbSpawner;
|
|
125
|
+
/** Dependencies for {@link startVirtualDisplay}, injectable for tests. */
|
|
126
|
+
export interface VirtualDisplayDeps {
|
|
127
|
+
/** Locates `Xvfb` / `xvfb-run` (defaults to a PATH search). */
|
|
128
|
+
readonly find?: (name: string, env: NodeJS.ProcessEnv) => string | undefined;
|
|
129
|
+
/** Spawns Xvfb (defaults to {@link spawnXvfb}). */
|
|
130
|
+
readonly spawnXvfb?: XvfbSpawner;
|
|
131
|
+
/** How long to wait for Xvfb to report its display number (ms). */
|
|
132
|
+
readonly startTimeoutMs?: number;
|
|
133
|
+
/** How long to wait after SIGTERM before SIGKILL on stop (ms). */
|
|
134
|
+
readonly stopGraceMs?: number;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Starts a private virtual display for one PICO-8 session. Prefers `Xvfb
|
|
138
|
+
* -displayfd` (no fixed display number, so parallel runs never collide), falls
|
|
139
|
+
* back to wrapping the command in `xvfb-run -a`, and resolves the structured
|
|
140
|
+
* {@link DisplayUnavailable} when neither exists or Xvfb fails to start. Never
|
|
141
|
+
* throws. The returned display MUST be stopped by the caller (the shell adapter
|
|
142
|
+
* does it in a `finally`); a kill-on-abort hook also kills a direct Xvfb so an
|
|
143
|
+
* abrupt picopilot exit or signal never leaves one behind.
|
|
144
|
+
*/
|
|
145
|
+
export declare function startVirtualDisplay(env: NodeJS.ProcessEnv, deps?: VirtualDisplayDeps): Promise<{
|
|
146
|
+
ok: true;
|
|
147
|
+
display: VirtualDisplay;
|
|
148
|
+
} | DisplayUnavailable>;
|
|
149
|
+
//# sourceMappingURL=display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/display.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DISPLAY MODE seam (ADR-0017): how a PICO-8 `-run` session (a real
|
|
3
|
+
* audio+video session, today only the audio-record path) gets a display on a
|
|
4
|
+
* machine that may have none (a server, CI, an agent sandbox).
|
|
5
|
+
*
|
|
6
|
+
* - `native`: spawn PICO-8 as-is (it uses whatever DISPLAY/WAYLAND_DISPLAY the
|
|
7
|
+
* caller has, or fails to open a window if there is none).
|
|
8
|
+
* - `xvfb`: always run a `-run` session on a private virtual X display.
|
|
9
|
+
* - `auto`: the default. Use a virtual display only when the session needs one
|
|
10
|
+
* and none is available (no DISPLAY and no WAYLAND_DISPLAY); otherwise native.
|
|
11
|
+
*
|
|
12
|
+
* Headless `-x` passes (run screenshots, playtest, export, the label capture)
|
|
13
|
+
* never need a display, so they never consult this module and never pay the
|
|
14
|
+
* Xvfb startup cost.
|
|
15
|
+
*
|
|
16
|
+
* The virtual display prefers spawning `Xvfb -displayfd <fd>` directly: the X
|
|
17
|
+
* server picks a free display number itself and writes it back on the fd, so
|
|
18
|
+
* parallel runs never race for a fixed `:99`. When only the Debian `xvfb-run`
|
|
19
|
+
* script exists, it is the fallback (it wraps the PICO-8 command instead).
|
|
20
|
+
* Either way the virtual display lives exactly as long as the run.
|
|
21
|
+
*/
|
|
22
|
+
import { spawn } from 'node:child_process';
|
|
23
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
24
|
+
import { delimiter, join } from 'node:path';
|
|
25
|
+
import { killOnAbort } from './cleanup.js';
|
|
26
|
+
/** The three display modes. `auto` is the default. */
|
|
27
|
+
export const DISPLAY_MODES = ['auto', 'xvfb', 'native'];
|
|
28
|
+
/** The default display mode when no flag, env var or config sets one. */
|
|
29
|
+
export const DEFAULT_DISPLAY_MODE = 'auto';
|
|
30
|
+
/** The env var that sets the display mode (below the `--display` flag, above picopilot.json). */
|
|
31
|
+
export const DISPLAY_ENV_VAR = 'PICOPILOT_DISPLAY';
|
|
32
|
+
/** The top-level picopilot.json key that sets the display mode (lowest precedence). */
|
|
33
|
+
export const DISPLAY_CONFIG_KEY = 'display';
|
|
34
|
+
/** Whether `value` names a valid {@link DisplayMode}. */
|
|
35
|
+
export function isDisplayMode(value) {
|
|
36
|
+
return (typeof value === 'string' &&
|
|
37
|
+
DISPLAY_MODES.includes(value));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the display mode with the precedence `flag > env > config > default`
|
|
41
|
+
* (the same order every picopilot setting uses). An empty/whitespace value is
|
|
42
|
+
* treated as unset; an unknown value is reported (never silently ignored), so a
|
|
43
|
+
* typo like `PICOPILOT_DISPLAY=xfvb` is a visible error rather than `auto`.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveDisplayMode(sources) {
|
|
46
|
+
const ordered = [
|
|
47
|
+
['flag', sources.flag],
|
|
48
|
+
['env', sources.env],
|
|
49
|
+
['config', sources.config],
|
|
50
|
+
];
|
|
51
|
+
for (const [source, raw] of ordered) {
|
|
52
|
+
if (raw === undefined || raw === null)
|
|
53
|
+
continue;
|
|
54
|
+
const value = String(raw).trim();
|
|
55
|
+
if (value.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
if (isDisplayMode(value))
|
|
58
|
+
return { ok: true, mode: value, source };
|
|
59
|
+
return { ok: false, source, value };
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, mode: DEFAULT_DISPLAY_MODE, source: 'default' };
|
|
62
|
+
}
|
|
63
|
+
/** Whether the environment already offers a display (X11 `DISPLAY` or a Wayland socket). */
|
|
64
|
+
export function hasNativeDisplay(env) {
|
|
65
|
+
const set = (v) => v !== undefined && v.trim() !== '';
|
|
66
|
+
return set(env.DISPLAY) || set(env.WAYLAND_DISPLAY);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a `-run` session in this `mode` + `env` should run on a virtual
|
|
70
|
+
* display: always for `xvfb`, never for `native`, and for `auto` only when
|
|
71
|
+
* there is no native display.
|
|
72
|
+
*/
|
|
73
|
+
export function needsVirtualDisplay(mode, env) {
|
|
74
|
+
if (mode === 'xvfb')
|
|
75
|
+
return true;
|
|
76
|
+
if (mode === 'native')
|
|
77
|
+
return false;
|
|
78
|
+
return !hasNativeDisplay(env);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Finds an executable the way a shell would: a name containing `/` is checked
|
|
82
|
+
* as a path, anything else is searched on `env.PATH`. Returns the resolved path,
|
|
83
|
+
* or `undefined`. Pure lookup (no spawn), so it is cheap enough for `doctor` and
|
|
84
|
+
* for the failure path.
|
|
85
|
+
*/
|
|
86
|
+
export function findExecutable(name, env) {
|
|
87
|
+
const isExec = (p) => {
|
|
88
|
+
try {
|
|
89
|
+
if (!statSync(p).isFile())
|
|
90
|
+
return false;
|
|
91
|
+
accessSync(p, constants.X_OK);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
if (name.includes('/'))
|
|
99
|
+
return isExec(name) ? name : undefined;
|
|
100
|
+
for (const dir of (env.PATH ?? '').split(delimiter)) {
|
|
101
|
+
if (dir === '')
|
|
102
|
+
continue;
|
|
103
|
+
const candidate = join(dir, name);
|
|
104
|
+
if (isExec(candidate))
|
|
105
|
+
return candidate;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/** Detects the best available virtual-display backend on `env.PATH`. */
|
|
110
|
+
export function detectVirtualDisplayBackend(env, find = findExecutable) {
|
|
111
|
+
if (find('Xvfb', env) !== undefined)
|
|
112
|
+
return 'Xvfb';
|
|
113
|
+
if (find('xvfb-run', env) !== undefined)
|
|
114
|
+
return 'xvfb-run';
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
/** The remedy a `display-unavailable` error names. */
|
|
118
|
+
export const DISPLAY_REMEDY = 'install Xvfb (Debian/Ubuntu: apt install xvfb; Fedora: dnf install xorg-x11-server-Xvfb; NixOS: add xvfb-run or xorg.xvfb), or run under a desktop session (DISPLAY or WAYLAND_DISPLAY set)';
|
|
119
|
+
/** Builds a {@link DisplayUnavailable} value. */
|
|
120
|
+
export function displayUnavailable(detail) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
reason: 'display-unavailable',
|
|
124
|
+
remedy: DISPLAY_REMEDY,
|
|
125
|
+
detail,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** The fd number Xvfb writes its display number to (`-displayfd 3`). */
|
|
129
|
+
const DISPLAY_FD = 3;
|
|
130
|
+
/** The screen geometry for the virtual display (PICO-8's default window fits easily). */
|
|
131
|
+
const XVFB_SCREEN = '640x540x24';
|
|
132
|
+
/**
|
|
133
|
+
* The real {@link XvfbSpawner}: stdio `[ignore, ignore, pipe, pipe]` so fd 3 is
|
|
134
|
+
* the display-number pipe. NOT detached, so a terminal Ctrl-C reaches it with
|
|
135
|
+
* the rest of picopilot's process group.
|
|
136
|
+
*/
|
|
137
|
+
export const spawnXvfb = (file, args, env) => {
|
|
138
|
+
const child = spawn(file, args, {
|
|
139
|
+
env,
|
|
140
|
+
stdio: ['ignore', 'ignore', 'pipe', 'pipe'],
|
|
141
|
+
});
|
|
142
|
+
const fd = child.stdio[DISPLAY_FD];
|
|
143
|
+
fd?.setEncoding('utf8');
|
|
144
|
+
child.stderr?.setEncoding('utf8');
|
|
145
|
+
return {
|
|
146
|
+
pid: child.pid,
|
|
147
|
+
onDisplayFd(cb) {
|
|
148
|
+
fd?.on('data', cb);
|
|
149
|
+
},
|
|
150
|
+
onStderr(cb) {
|
|
151
|
+
child.stderr?.on('data', cb);
|
|
152
|
+
},
|
|
153
|
+
onExit(cb) {
|
|
154
|
+
child.on('exit', cb);
|
|
155
|
+
},
|
|
156
|
+
onError(cb) {
|
|
157
|
+
child.on('error', cb);
|
|
158
|
+
},
|
|
159
|
+
kill(signal) {
|
|
160
|
+
try {
|
|
161
|
+
child.kill(signal);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// already gone
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
/** The default time Xvfb gets to come up before the start is declared failed. */
|
|
170
|
+
const DEFAULT_XVFB_START_TIMEOUT_MS = 10_000;
|
|
171
|
+
/** The default time Xvfb gets to exit on SIGTERM before SIGKILL. */
|
|
172
|
+
const DEFAULT_XVFB_STOP_GRACE_MS = 2_000;
|
|
173
|
+
/**
|
|
174
|
+
* Starts a private virtual display for one PICO-8 session. Prefers `Xvfb
|
|
175
|
+
* -displayfd` (no fixed display number, so parallel runs never collide), falls
|
|
176
|
+
* back to wrapping the command in `xvfb-run -a`, and resolves the structured
|
|
177
|
+
* {@link DisplayUnavailable} when neither exists or Xvfb fails to start. Never
|
|
178
|
+
* throws. The returned display MUST be stopped by the caller (the shell adapter
|
|
179
|
+
* does it in a `finally`); a kill-on-abort hook also kills a direct Xvfb so an
|
|
180
|
+
* abrupt picopilot exit or signal never leaves one behind.
|
|
181
|
+
*/
|
|
182
|
+
export async function startVirtualDisplay(env, deps = {}) {
|
|
183
|
+
const find = deps.find ?? findExecutable;
|
|
184
|
+
const xvfb = find('Xvfb', env);
|
|
185
|
+
if (xvfb !== undefined) {
|
|
186
|
+
return startDirectXvfb(xvfb, env, deps);
|
|
187
|
+
}
|
|
188
|
+
const xvfbRun = find('xvfb-run', env);
|
|
189
|
+
if (xvfbRun !== undefined) {
|
|
190
|
+
return { ok: true, display: xvfbRunDisplay(xvfbRun) };
|
|
191
|
+
}
|
|
192
|
+
return displayUnavailable('there is no display (DISPLAY and WAYLAND_DISPLAY are unset) and neither Xvfb nor xvfb-run is on PATH to provide a virtual one');
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The `xvfb-run` fallback: no server to manage here, the script starts Xvfb,
|
|
196
|
+
* runs the command with DISPLAY set, and kills Xvfb when the command exits. The
|
|
197
|
+
* whole thing runs in PICO-8's process group, so the adapter's group kill
|
|
198
|
+
* (sentinel / backstop) takes Xvfb down with it.
|
|
199
|
+
*/
|
|
200
|
+
function xvfbRunDisplay(xvfbRun) {
|
|
201
|
+
return {
|
|
202
|
+
backend: 'xvfb-run',
|
|
203
|
+
env: {},
|
|
204
|
+
wrap(file, args) {
|
|
205
|
+
return {
|
|
206
|
+
file: xvfbRun,
|
|
207
|
+
args: [
|
|
208
|
+
'-a',
|
|
209
|
+
'-s',
|
|
210
|
+
`-screen 0 ${XVFB_SCREEN} -nolisten tcp`,
|
|
211
|
+
file,
|
|
212
|
+
...args,
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
async stop() { },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** Starts `Xvfb -displayfd 3` and waits for it to report the display number it picked. */
|
|
220
|
+
function startDirectXvfb(file, env, deps) {
|
|
221
|
+
const spawner = deps.spawnXvfb ?? spawnXvfb;
|
|
222
|
+
const startTimeoutMs = deps.startTimeoutMs ?? DEFAULT_XVFB_START_TIMEOUT_MS;
|
|
223
|
+
const stopGraceMs = deps.stopGraceMs ?? DEFAULT_XVFB_STOP_GRACE_MS;
|
|
224
|
+
const args = [
|
|
225
|
+
'-displayfd',
|
|
226
|
+
String(DISPLAY_FD),
|
|
227
|
+
'-screen',
|
|
228
|
+
'0',
|
|
229
|
+
XVFB_SCREEN,
|
|
230
|
+
'-nolisten',
|
|
231
|
+
'tcp',
|
|
232
|
+
];
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
let exited = false;
|
|
235
|
+
let settled = false;
|
|
236
|
+
let fdText = '';
|
|
237
|
+
let stderr = '';
|
|
238
|
+
const exitWaiters = [];
|
|
239
|
+
let timer;
|
|
240
|
+
const proc = spawner(file, args, env);
|
|
241
|
+
// Safety net: if picopilot exits or is signalled mid-run, kill the server
|
|
242
|
+
// synchronously so no stray Xvfb is left behind.
|
|
243
|
+
const disposeAbort = killOnAbort(() => proc.kill('SIGKILL'));
|
|
244
|
+
const stop = () => {
|
|
245
|
+
disposeAbort();
|
|
246
|
+
if (exited)
|
|
247
|
+
return Promise.resolve();
|
|
248
|
+
return new Promise((done) => {
|
|
249
|
+
exitWaiters.push(done);
|
|
250
|
+
proc.kill('SIGTERM');
|
|
251
|
+
const hard = setTimeout(() => {
|
|
252
|
+
if (!exited)
|
|
253
|
+
proc.kill('SIGKILL');
|
|
254
|
+
}, stopGraceMs);
|
|
255
|
+
exitWaiters.push(() => clearTimeout(hard));
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
const fail = (detail) => {
|
|
259
|
+
if (settled)
|
|
260
|
+
return;
|
|
261
|
+
settled = true;
|
|
262
|
+
clearTimeout(timer);
|
|
263
|
+
void stop().then(() => resolve(displayUnavailable(detail)));
|
|
264
|
+
};
|
|
265
|
+
proc.onExit(() => {
|
|
266
|
+
exited = true;
|
|
267
|
+
for (const w of exitWaiters.splice(0))
|
|
268
|
+
w();
|
|
269
|
+
if (!settled) {
|
|
270
|
+
disposeAbort();
|
|
271
|
+
fail(`Xvfb exited before reporting a display${stderrTail(stderr)}`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
proc.onError((err) => {
|
|
275
|
+
exited = true;
|
|
276
|
+
for (const w of exitWaiters.splice(0))
|
|
277
|
+
w();
|
|
278
|
+
fail(`Xvfb failed to start (${err.code ?? err.message})`);
|
|
279
|
+
});
|
|
280
|
+
proc.onStderr((chunk) => {
|
|
281
|
+
stderr += chunk;
|
|
282
|
+
});
|
|
283
|
+
proc.onDisplayFd((chunk) => {
|
|
284
|
+
if (settled)
|
|
285
|
+
return;
|
|
286
|
+
fdText += chunk;
|
|
287
|
+
const m = /^\s*(\d+)\s*\n/.exec(fdText);
|
|
288
|
+
if (m === null)
|
|
289
|
+
return;
|
|
290
|
+
settled = true;
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
const display = `:${m[1]}`;
|
|
293
|
+
resolve({
|
|
294
|
+
ok: true,
|
|
295
|
+
display: {
|
|
296
|
+
backend: 'Xvfb',
|
|
297
|
+
env: { DISPLAY: display },
|
|
298
|
+
wrap: (f, a) => ({ file: f, args: a }),
|
|
299
|
+
stop,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
timer = setTimeout(() => fail(`Xvfb did not report a display within ${startTimeoutMs}ms${stderrTail(stderr)}`), startTimeoutMs);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/** The last few lines of Xvfb's stderr, as a `: ...` suffix (or empty). */
|
|
307
|
+
function stderrTail(stderr) {
|
|
308
|
+
const lines = stderr.trim().split('\n').filter(Boolean).slice(-3);
|
|
309
|
+
return lines.length === 0 ? '' : `: ${lines.join(' | ')}`;
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=display.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.js","sourceRoot":"","sources":["../../../src/engine/pico8/display.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/drive.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/drive.ts"],"names":[],"mappings":"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"}
|
|
@@ -353,13 +353,39 @@ export function resolveIncludes(lua, readInclude, seen = new Set()) {
|
|
|
353
353
|
const path = m[1];
|
|
354
354
|
if (seen.has(path))
|
|
355
355
|
return line; // cycle guard: leave it
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
// `#include other.p8` takes that cart's code, `other.p8:n` only tab n.
|
|
357
|
+
const p8 = /^(.+\.p8)(?::(\d+))?$/i.exec(path);
|
|
358
|
+
const raw = readInclude(p8 !== null ? p8[1] : path);
|
|
359
|
+
if (raw === undefined)
|
|
358
360
|
return line; // unreadable: leave verbatim
|
|
361
|
+
const content = p8 !== null ? p8Code(raw, p8[2]) : raw;
|
|
362
|
+
if (content === undefined)
|
|
363
|
+
return line;
|
|
359
364
|
return resolveIncludes(content, readInclude, new Set([...seen, path]));
|
|
360
365
|
})
|
|
361
366
|
.join('\n');
|
|
362
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* The code an `#include` of a `.p8` cart pulls in: its `__lua__` section, or
|
|
370
|
+
* only tab `tab` (tabs are separated by `-->8` lines). `undefined` when the
|
|
371
|
+
* text does not parse as a cart or the tab does not exist.
|
|
372
|
+
*/
|
|
373
|
+
function p8Code(cartText, tab) {
|
|
374
|
+
let lua;
|
|
375
|
+
try {
|
|
376
|
+
lua = Cart.parse(cartText).getSection('lua');
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
if (lua === undefined)
|
|
382
|
+
return undefined;
|
|
383
|
+
const body = lua.replace(/\n$/, '');
|
|
384
|
+
if (tab === undefined)
|
|
385
|
+
return body;
|
|
386
|
+
const tabs = body.split(/\r?\n-->8\r?\n/);
|
|
387
|
+
return tabs[Number(tab)];
|
|
388
|
+
}
|
|
363
389
|
/** The default base filename SHOT names each screenshot (`play0.png`, ...). */
|
|
364
390
|
export const SHOT_BASENAME = 'play';
|
|
365
391
|
/**
|