picopilot 0.0.0 → 0.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/LICENSE +661 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audio.d.ts +17 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +315 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/export.d.ts +36 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +257 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gfx.d.ts +28 -0
- package/dist/commands/gfx.d.ts.map +1 -0
- package/dist/commands/gfx.js +521 -0
- package/dist/commands/gfx.js.map +1 -0
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +151 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +20 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +90 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/minify.d.ts +20 -0
- package/dist/commands/minify.d.ts.map +1 -0
- package/dist/commands/minify.js +129 -0
- package/dist/commands/minify.js.map +1 -0
- package/dist/commands/music.d.ts +13 -0
- package/dist/commands/music.d.ts.map +1 -0
- package/dist/commands/music.js +241 -0
- package/dist/commands/music.js.map +1 -0
- package/dist/commands/playtest.d.ts +29 -0
- package/dist/commands/playtest.d.ts.map +1 -0
- package/dist/commands/playtest.js +631 -0
- package/dist/commands/playtest.js.map +1 -0
- package/dist/commands/run.d.ts +30 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +50 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +213 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/sfx.d.ts +13 -0
- package/dist/commands/sfx.d.ts.map +1 -0
- package/dist/commands/sfx.js +254 -0
- package/dist/commands/sfx.js.map +1 -0
- package/dist/commands/tokens.d.ts +36 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +129 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/verify.d.ts +57 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +213 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +24 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/engine/audio/index.d.ts +14 -0
- package/dist/engine/audio/index.d.ts.map +1 -0
- package/dist/engine/audio/index.js +14 -0
- package/dist/engine/audio/index.js.map +1 -0
- package/dist/engine/audio/music.d.ts +116 -0
- package/dist/engine/audio/music.d.ts.map +1 -0
- package/dist/engine/audio/music.js +138 -0
- package/dist/engine/audio/music.js.map +1 -0
- package/dist/engine/audio/section.d.ts +34 -0
- package/dist/engine/audio/section.d.ts.map +1 -0
- package/dist/engine/audio/section.js +67 -0
- package/dist/engine/audio/section.js.map +1 -0
- package/dist/engine/audio/sfx.d.ts +141 -0
- package/dist/engine/audio/sfx.d.ts.map +1 -0
- package/dist/engine/audio/sfx.js +506 -0
- package/dist/engine/audio/sfx.js.map +1 -0
- package/dist/engine/cart/cart.d.ts +75 -0
- package/dist/engine/cart/cart.d.ts.map +1 -0
- package/dist/engine/cart/cart.js +153 -0
- package/dist/engine/cart/cart.js.map +1 -0
- package/dist/engine/cart/errors.d.ts +29 -0
- package/dist/engine/cart/errors.d.ts.map +1 -0
- package/dist/engine/cart/errors.js +26 -0
- package/dist/engine/cart/errors.js.map +1 -0
- package/dist/engine/cart/gfx.d.ts +79 -0
- package/dist/engine/cart/gfx.d.ts.map +1 -0
- package/dist/engine/cart/gfx.js +171 -0
- package/dist/engine/cart/gfx.js.map +1 -0
- package/dist/engine/cart/index.d.ts +15 -0
- package/dist/engine/cart/index.d.ts.map +1 -0
- package/dist/engine/cart/index.js +15 -0
- package/dist/engine/cart/index.js.map +1 -0
- package/dist/engine/cart/map.d.ts +46 -0
- package/dist/engine/cart/map.d.ts.map +1 -0
- package/dist/engine/cart/map.js +118 -0
- package/dist/engine/cart/map.js.map +1 -0
- package/dist/engine/config/config.d.ts +74 -0
- package/dist/engine/config/config.d.ts.map +1 -0
- package/dist/engine/config/config.js +103 -0
- package/dist/engine/config/config.js.map +1 -0
- package/dist/engine/config/index.d.ts +7 -0
- package/dist/engine/config/index.d.ts.map +1 -0
- package/dist/engine/config/index.js +7 -0
- package/dist/engine/config/index.js.map +1 -0
- package/dist/engine/gfx/grid.d.ts +51 -0
- package/dist/engine/gfx/grid.d.ts.map +1 -0
- package/dist/engine/gfx/grid.js +116 -0
- package/dist/engine/gfx/grid.js.map +1 -0
- package/dist/engine/gfx/index.d.ts +17 -0
- package/dist/engine/gfx/index.d.ts.map +1 -0
- package/dist/engine/gfx/index.js +17 -0
- package/dist/engine/gfx/index.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +34 -0
- package/dist/engine/gfx/label.d.ts.map +1 -0
- package/dist/engine/gfx/label.js +190 -0
- package/dist/engine/gfx/label.js.map +1 -0
- package/dist/engine/gfx/overlap.d.ts +42 -0
- package/dist/engine/gfx/overlap.d.ts.map +1 -0
- package/dist/engine/gfx/overlap.js +57 -0
- package/dist/engine/gfx/overlap.js.map +1 -0
- package/dist/engine/gfx/palette.d.ts +29 -0
- package/dist/engine/gfx/palette.d.ts.map +1 -0
- package/dist/engine/gfx/palette.js +46 -0
- package/dist/engine/gfx/palette.js.map +1 -0
- package/dist/engine/gfx/png.d.ts +31 -0
- package/dist/engine/gfx/png.d.ts.map +1 -0
- package/dist/engine/gfx/png.js +165 -0
- package/dist/engine/gfx/png.js.map +1 -0
- package/dist/engine/gfx/render.d.ts +42 -0
- package/dist/engine/gfx/render.d.ts.map +1 -0
- package/dist/engine/gfx/render.js +72 -0
- package/dist/engine/gfx/render.js.map +1 -0
- package/dist/engine/index.d.ts +22 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +22 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/pico8/adapter.d.ts +267 -0
- package/dist/engine/pico8/adapter.d.ts.map +1 -0
- package/dist/engine/pico8/adapter.js +37 -0
- package/dist/engine/pico8/adapter.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts +308 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -0
- package/dist/engine/pico8/drive.js +581 -0
- package/dist/engine/pico8/drive.js.map +1 -0
- package/dist/engine/pico8/harness.d.ts +146 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -0
- package/dist/engine/pico8/harness.js +251 -0
- package/dist/engine/pico8/harness.js.map +1 -0
- package/dist/engine/pico8/index.d.ts +21 -0
- package/dist/engine/pico8/index.d.ts.map +1 -0
- package/dist/engine/pico8/index.js +21 -0
- package/dist/engine/pico8/index.js.map +1 -0
- package/dist/engine/pico8/sentinel.d.ts +63 -0
- package/dist/engine/pico8/sentinel.d.ts.map +1 -0
- package/dist/engine/pico8/sentinel.js +86 -0
- package/dist/engine/pico8/sentinel.js.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.js +11 -0
- package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
- package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
- package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-main.js +71 -0
- package/dist/engine/pico8/session-daemon-main.js.map +1 -0
- package/dist/engine/pico8/session-daemon.d.ts +68 -0
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon.js +264 -0
- package/dist/engine/pico8/session-daemon.js.map +1 -0
- package/dist/engine/pico8/session.d.ts +140 -0
- package/dist/engine/pico8/session.d.ts.map +1 -0
- package/dist/engine/pico8/session.js +250 -0
- package/dist/engine/pico8/session.js.map +1 -0
- package/dist/engine/pico8/shell.d.ts +103 -0
- package/dist/engine/pico8/shell.d.ts.map +1 -0
- package/dist/engine/pico8/shell.js +324 -0
- package/dist/engine/pico8/shell.js.map +1 -0
- package/dist/engine/pico8/supervisor.d.ts +137 -0
- package/dist/engine/pico8/supervisor.d.ts.map +1 -0
- package/dist/engine/pico8/supervisor.js +140 -0
- package/dist/engine/pico8/supervisor.js.map +1 -0
- package/dist/engine/scaffold/index.d.ts +8 -0
- package/dist/engine/scaffold/index.d.ts.map +1 -0
- package/dist/engine/scaffold/index.js +8 -0
- package/dist/engine/scaffold/index.js.map +1 -0
- package/dist/engine/scaffold/scaffold.d.ts +49 -0
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
- package/dist/engine/scaffold/scaffold.js +217 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -0
- package/dist/engine/shrinko/adapter.d.ts +167 -0
- package/dist/engine/shrinko/adapter.d.ts.map +1 -0
- package/dist/engine/shrinko/adapter.js +107 -0
- package/dist/engine/shrinko/adapter.js.map +1 -0
- package/dist/engine/shrinko/index.d.ts +13 -0
- package/dist/engine/shrinko/index.d.ts.map +1 -0
- package/dist/engine/shrinko/index.js +13 -0
- package/dist/engine/shrinko/index.js.map +1 -0
- package/dist/engine/shrinko/shell.d.ts +69 -0
- package/dist/engine/shrinko/shell.d.ts.map +1 -0
- package/dist/engine/shrinko/shell.js +156 -0
- package/dist/engine/shrinko/shell.js.map +1 -0
- package/dist/engine/skills/index.d.ts +8 -0
- package/dist/engine/skills/index.d.ts.map +1 -0
- package/dist/engine/skills/index.js +8 -0
- package/dist/engine/skills/index.js.map +1 -0
- package/dist/engine/skills/skills.d.ts +104 -0
- package/dist/engine/skills/skills.d.ts.map +1 -0
- package/dist/engine/skills/skills.js +149 -0
- package/dist/engine/skills/skills.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -2
- package/src/bin.ts +4 -0
- package/src/cli.ts +61 -0
- package/src/commands/audio.test.ts +280 -0
- package/src/commands/audio.ts +410 -0
- package/src/commands/export.test.ts +310 -0
- package/src/commands/export.ts +316 -0
- package/src/commands/gfx-io.test.ts +178 -0
- package/src/commands/gfx.test.ts +470 -0
- package/src/commands/gfx.ts +645 -0
- package/src/commands/init.test.ts +460 -0
- package/src/commands/init.ts +193 -0
- package/src/commands/lint.test.ts +137 -0
- package/src/commands/lint.ts +117 -0
- package/src/commands/minify.test.ts +166 -0
- package/src/commands/minify.ts +156 -0
- package/src/commands/music.test.ts +272 -0
- package/src/commands/music.ts +288 -0
- package/src/commands/playtest.test.ts +317 -0
- package/src/commands/playtest.ts +773 -0
- package/src/commands/run.test.ts +238 -0
- package/src/commands/run.ts +259 -0
- package/src/commands/serve.test.ts +218 -0
- package/src/commands/serve.ts +272 -0
- package/src/commands/sfx.test.ts +349 -0
- package/src/commands/sfx.ts +300 -0
- package/src/commands/tokens.test.ts +212 -0
- package/src/commands/tokens.ts +168 -0
- package/src/commands/verify.test.ts +238 -0
- package/src/commands/verify.ts +256 -0
- package/src/commands/version.test.ts +70 -0
- package/src/commands/version.ts +24 -0
- package/src/engine/audio/index.ts +47 -0
- package/src/engine/audio/music.test.ts +188 -0
- package/src/engine/audio/music.ts +218 -0
- package/src/engine/audio/section.test.ts +124 -0
- package/src/engine/audio/section.ts +79 -0
- package/src/engine/audio/sfx.test.ts +410 -0
- package/src/engine/audio/sfx.ts +694 -0
- package/src/engine/cart/cart.test.ts +304 -0
- package/src/engine/cart/cart.ts +211 -0
- package/src/engine/cart/errors.ts +39 -0
- package/src/engine/cart/fixtures/crlf.p8 +6 -0
- package/src/engine/cart/fixtures/full.p8 +174 -0
- package/src/engine/cart/fixtures/header-only.p8 +2 -0
- package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
- package/src/engine/cart/fixtures/minimal.p8 +10 -0
- package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
- package/src/engine/cart/gfx.ts +195 -0
- package/src/engine/cart/index.ts +27 -0
- package/src/engine/cart/map.ts +134 -0
- package/src/engine/config/config.ts +115 -0
- package/src/engine/config/index.ts +12 -0
- package/src/engine/gfx/grid.test.ts +128 -0
- package/src/engine/gfx/grid.ts +136 -0
- package/src/engine/gfx/index.ts +34 -0
- package/src/engine/gfx/label.test.ts +105 -0
- package/src/engine/gfx/label.ts +229 -0
- package/src/engine/gfx/overlap.test.ts +71 -0
- package/src/engine/gfx/overlap.ts +66 -0
- package/src/engine/gfx/palette.test.ts +35 -0
- package/src/engine/gfx/palette.ts +54 -0
- package/src/engine/gfx/png.test.ts +111 -0
- package/src/engine/gfx/png.ts +196 -0
- package/src/engine/gfx/render.test.ts +180 -0
- package/src/engine/gfx/render.ts +106 -0
- package/src/engine/index.ts +21 -0
- package/src/engine/pico8/adapter.ts +303 -0
- package/src/engine/pico8/drive.test.ts +415 -0
- package/src/engine/pico8/drive.ts +729 -0
- package/src/engine/pico8/harness.test.ts +181 -0
- package/src/engine/pico8/harness.ts +354 -0
- package/src/engine/pico8/index.ts +20 -0
- package/src/engine/pico8/sentinel.test.ts +80 -0
- package/src/engine/pico8/sentinel.ts +93 -0
- package/src/engine/pico8/session-daemon-entry.ts +12 -0
- package/src/engine/pico8/session-daemon-main.ts +98 -0
- package/src/engine/pico8/session-daemon.test.ts +212 -0
- package/src/engine/pico8/session-daemon.ts +306 -0
- package/src/engine/pico8/session.test.ts +239 -0
- package/src/engine/pico8/session.ts +321 -0
- package/src/engine/pico8/shell.test.ts +583 -0
- package/src/engine/pico8/shell.ts +412 -0
- package/src/engine/pico8/supervisor.test.ts +105 -0
- package/src/engine/pico8/supervisor.ts +197 -0
- package/src/engine/scaffold/index.ts +15 -0
- package/src/engine/scaffold/scaffold.ts +236 -0
- package/src/engine/shrinko/adapter.ts +241 -0
- package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
- package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
- package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
- package/src/engine/shrinko/index.ts +33 -0
- package/src/engine/shrinko/shell.ts +224 -0
- package/src/engine/shrinko/shrinko.test.ts +182 -0
- package/src/engine/skills/index.ts +14 -0
- package/src/engine/skills/skills.test.ts +376 -0
- package/src/engine/skills/skills.ts +205 -0
- package/src/index.ts +13 -0
- package/src/skills/game-design-reference/SKILL.md +70 -0
- package/src/skills/game-jam/SKILL.md +45 -0
- package/src/skills/picopilot-art/SKILL.md +62 -0
- package/src/skills/picopilot-audio/SKILL.md +194 -0
- package/src/skills/picopilot-code/SKILL.md +97 -0
- package/src/skills/picopilot-code/reference/README.md +42 -0
- package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
- package/src/skills/picopilot-code/reference/platformer.md +119 -0
- package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
- package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
- package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
- package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
- package/src/skills/picopilot-debug/SKILL.md +195 -0
- package/src/skills/picopilot-overview/SKILL.md +52 -0
- package/src/version.ts +22 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Cli } from 'incur';
|
|
2
|
+
import { type Pico8Adapter } from '../engine/pico8/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Injects the PICO-8 adapter `run` uses (defaults to the shell adapter over
|
|
5
|
+
* native `pico8 -x`). Mirrors the shrinko `adapterFactory` seam: a test passes a
|
|
6
|
+
* stub to drive the sentinel/backstop/absent paths without the paid binary, and
|
|
7
|
+
* to isolate the child `env` (its `PICO8_PATH`/`PATH`).
|
|
8
|
+
*/
|
|
9
|
+
export type Pico8AdapterFactory = (env: NodeJS.ProcessEnv) => Pico8Adapter;
|
|
10
|
+
/**
|
|
11
|
+
* Registers `picopilot run`, the THIN sentinel-watch + screenshot-collect
|
|
12
|
+
* orchestration command (US #14, ADR-0006). It is NOT a `pico8` wrapper: its
|
|
13
|
+
* value is the glue an agent should not hand-roll every run, launch PICO-8,
|
|
14
|
+
* stream stdout, kill on the cart's done-sentinel (backstop as the safety net),
|
|
15
|
+
* and collect screenshots + printh + exit reason into ONE structured result.
|
|
16
|
+
*
|
|
17
|
+
* The cart cooperates: it self-screenshots via `extcmd("screen")` on a timer and
|
|
18
|
+
* prints the done-sentinel via `printh` when finished (the `picopilot-debug`
|
|
19
|
+
* skill teaches the recipe). A cart cannot self-quit the app, so the external
|
|
20
|
+
* sentinel-kill IS the quit mechanism.
|
|
21
|
+
*
|
|
22
|
+
* PICO-8 absent → structured `pico8-not-found` + nonzero exit (mirrors the
|
|
23
|
+
* shrinko boundary), never a crash or a hang. That absent path is the one
|
|
24
|
+
* exercised in CI; live runs are a manual/opt-in tier.
|
|
25
|
+
*
|
|
26
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
27
|
+
* tests pass a stub to drive present/absent without the real binary.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerRun(cli: Cli.Cli, adapterFactory?: Pico8AdapterFactory): void;
|
|
30
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAClC,OAAO,EAIN,KAAK,YAAY,EAEjB,MAAM,0BAA0B,CAAC;AAElC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AAQ3E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CA4MN"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { z } from 'incur';
|
|
5
|
+
import { buildRecordHarness, DONE_SENTINEL, HarnessError, ShellPico8Adapter, } from '../engine/pico8/index.js';
|
|
6
|
+
const defaultAdapterFactory = (env) => new ShellPico8Adapter({ env });
|
|
7
|
+
/** The default backstop: kill a cart that never signals/exits after this many ms. */
|
|
8
|
+
const DEFAULT_BACKSTOP_MS = 15_000;
|
|
9
|
+
/**
|
|
10
|
+
* Registers `picopilot run`, the THIN sentinel-watch + screenshot-collect
|
|
11
|
+
* orchestration command (US #14, ADR-0006). It is NOT a `pico8` wrapper: its
|
|
12
|
+
* value is the glue an agent should not hand-roll every run, launch PICO-8,
|
|
13
|
+
* stream stdout, kill on the cart's done-sentinel (backstop as the safety net),
|
|
14
|
+
* and collect screenshots + printh + exit reason into ONE structured result.
|
|
15
|
+
*
|
|
16
|
+
* The cart cooperates: it self-screenshots via `extcmd("screen")` on a timer and
|
|
17
|
+
* prints the done-sentinel via `printh` when finished (the `picopilot-debug`
|
|
18
|
+
* skill teaches the recipe). A cart cannot self-quit the app, so the external
|
|
19
|
+
* sentinel-kill IS the quit mechanism.
|
|
20
|
+
*
|
|
21
|
+
* PICO-8 absent → structured `pico8-not-found` + nonzero exit (mirrors the
|
|
22
|
+
* shrinko boundary), never a crash or a hang. That absent path is the one
|
|
23
|
+
* exercised in CI; live runs are a manual/opt-in tier.
|
|
24
|
+
*
|
|
25
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
26
|
+
* tests pass a stub to drive present/absent without the real binary.
|
|
27
|
+
*/
|
|
28
|
+
export function registerRun(cli, adapterFactory = defaultAdapterFactory) {
|
|
29
|
+
cli.command('run', {
|
|
30
|
+
description: "Run a cart on the user's PICO-8, capture printh + screenshots, end on a done-sentinel. Requires PICO-8.",
|
|
31
|
+
args: z.object({
|
|
32
|
+
cart: z
|
|
33
|
+
.string()
|
|
34
|
+
.default('main.p8')
|
|
35
|
+
.describe('The .p8 cart to run. Defaults to main.p8 in the current folder.'),
|
|
36
|
+
}),
|
|
37
|
+
options: z.object({
|
|
38
|
+
shotDir: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Where PICO-8 writes screenshots (-desktop). Defaults to an isolated temp dir; NEVER ~/Desktop.'),
|
|
42
|
+
sentinel: z
|
|
43
|
+
.string()
|
|
44
|
+
.default(DONE_SENTINEL)
|
|
45
|
+
.describe('The stdout line that ends the run. The cart printh-es this when done.'),
|
|
46
|
+
backstopMs: z
|
|
47
|
+
.number()
|
|
48
|
+
.int()
|
|
49
|
+
.positive()
|
|
50
|
+
.default(DEFAULT_BACKSTOP_MS)
|
|
51
|
+
.describe('Safety backstop: kill PICO-8 after this many ms if it never signals.'),
|
|
52
|
+
input: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('One-shot scripted input passed as `-p` (the cart reads it via stat(6)) for automated playtests.'),
|
|
56
|
+
recordAudio: z
|
|
57
|
+
.boolean()
|
|
58
|
+
.default(false)
|
|
59
|
+
.describe("Also capture a WAV of the run's audio alongside the screenshots (ADR-0009). A REAL-TIME recording that needs a real audio+video session (a separate -run pass, NOT the headless screenshot pass), never an offline export."),
|
|
60
|
+
}),
|
|
61
|
+
// incur resolves these from the (test-overridable) env source and hands
|
|
62
|
+
// them to the child, so PICO8_PATH/PATH locate the binary in isolation.
|
|
63
|
+
env: z.object({
|
|
64
|
+
PICO8_PATH: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Explicit path to the PICO-8 binary (else `pico8` on PATH).'),
|
|
68
|
+
PATH: z.string().optional().describe('Used to locate `pico8`.'),
|
|
69
|
+
}),
|
|
70
|
+
output: z.object({
|
|
71
|
+
screenshots: z
|
|
72
|
+
.array(z.string())
|
|
73
|
+
.describe('Absolute paths of the PNG screenshots the cart produced.'),
|
|
74
|
+
printh: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe('Captured stdout (the cart printh output + PICO-8 lines).'),
|
|
77
|
+
exitReason: z
|
|
78
|
+
.enum(['sentinel', 'timeout', 'exit'])
|
|
79
|
+
.describe('sentinel = cart signalled done; timeout = backstop fired; exit = PICO-8 quit.'),
|
|
80
|
+
shotDir: z.string().describe('The dir screenshots were written to.'),
|
|
81
|
+
wav: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Absolute path of the captured WAV (only with --record-audio; omitted if no audio was captured).'),
|
|
85
|
+
audioCaptured: z
|
|
86
|
+
.boolean()
|
|
87
|
+
.describe('Whether a non-empty WAV was captured (false unless --record-audio ran and produced audio).'),
|
|
88
|
+
}),
|
|
89
|
+
examples: [
|
|
90
|
+
{ description: 'Run main.p8' },
|
|
91
|
+
{ description: 'Run a specific cart', args: { cart: 'game.p8' } },
|
|
92
|
+
{
|
|
93
|
+
description: 'Collect screenshots into a chosen dir',
|
|
94
|
+
args: { cart: 'game.p8' },
|
|
95
|
+
options: { shotDir: './shots' },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
async run({ args, options, env, error, ok }) {
|
|
99
|
+
const cartPath = isAbsolute(args.cart)
|
|
100
|
+
? args.cart
|
|
101
|
+
: resolve(process.cwd(), args.cart);
|
|
102
|
+
// A missing cart is a picopilot-side error, distinct from PICO-8 absence.
|
|
103
|
+
if (!existsSync(cartPath)) {
|
|
104
|
+
return error({
|
|
105
|
+
code: 'cart-not-found',
|
|
106
|
+
message: `no cart at ${cartPath}`,
|
|
107
|
+
exitCode: 1,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Screenshot dir: a user-given path, else an isolated temp dir. NEVER the
|
|
111
|
+
// default ~/Desktop (the `-desktop` flag is the isolation lever). Create
|
|
112
|
+
// it so PICO-8 has somewhere to write.
|
|
113
|
+
const shotDir = options.shotDir !== undefined
|
|
114
|
+
? isAbsolute(options.shotDir)
|
|
115
|
+
? options.shotDir
|
|
116
|
+
: resolve(process.cwd(), options.shotDir)
|
|
117
|
+
: mkdtempSync(join(tmpdir(), 'picopilot-run-'));
|
|
118
|
+
if (!existsSync(shotDir))
|
|
119
|
+
mkdirSync(shotDir, { recursive: true });
|
|
120
|
+
const adapter = adapterFactory(env);
|
|
121
|
+
const result = await adapter.run({
|
|
122
|
+
cartPath,
|
|
123
|
+
shotDir,
|
|
124
|
+
sentinel: options.sentinel,
|
|
125
|
+
backstopMs: options.backstopMs,
|
|
126
|
+
input: options.input,
|
|
127
|
+
});
|
|
128
|
+
if (!result.ok) {
|
|
129
|
+
// The two-tier structured failure (US #19, ADR-0006): PICO-8 absent.
|
|
130
|
+
// The message carries the exact remedy + needs; the code is the handle.
|
|
131
|
+
return error({
|
|
132
|
+
code: result.reason,
|
|
133
|
+
message: `PICO-8 is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
134
|
+
exitCode: 1,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const { screenshots, printh, exitReason } = result.value;
|
|
138
|
+
// --record-audio: an ADDITIVE second pass that records the run's audio to a
|
|
139
|
+
// WAV. It cannot reuse the headless `-x` screenshot pass (which mixes no
|
|
140
|
+
// audio -> an empty WAV, ADR-0009), so it does a separate `-run` capture via
|
|
141
|
+
// the record seam, injecting the same cooperative recorder `audio record`
|
|
142
|
+
// uses. The WAV lands in the isolated shotDir alongside the screenshots.
|
|
143
|
+
let wav;
|
|
144
|
+
let audioCaptured = false;
|
|
145
|
+
if (options.recordAudio) {
|
|
146
|
+
try {
|
|
147
|
+
const harness = buildRecordHarness(readFileSync(cartPath, 'utf8'), {
|
|
148
|
+
sentinel: options.sentinel,
|
|
149
|
+
});
|
|
150
|
+
const harnessCart = join(shotDir, 'record-audio-harness.p8');
|
|
151
|
+
writeFileSync(harnessCart, harness.cartText);
|
|
152
|
+
const rec = await adapter.record({
|
|
153
|
+
cartPath: harnessCart,
|
|
154
|
+
wavDir: shotDir,
|
|
155
|
+
wavBasename: harness.wavBasename,
|
|
156
|
+
sentinel: options.sentinel,
|
|
157
|
+
backstopMs: options.backstopMs,
|
|
158
|
+
});
|
|
159
|
+
if (rec.ok) {
|
|
160
|
+
wav = rec.value.wavPath;
|
|
161
|
+
audioCaptured = rec.value.wavPath !== undefined;
|
|
162
|
+
}
|
|
163
|
+
// A record-pass pico8-not-found is impossible here: the screenshot
|
|
164
|
+
// pass above already returned pico8-not-found if PICO-8 were absent.
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
if (!(e instanceof HarnessError))
|
|
168
|
+
throw e;
|
|
169
|
+
// A harness-build failure (unparseable cart) leaves audioCaptured false;
|
|
170
|
+
// the screenshot run's report still stands.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// A backstop timeout is a soft warning: the cart never signalled done, so
|
|
174
|
+
// it may have hung or errored. CTA the agent toward the static checks
|
|
175
|
+
// (lint/tokens) to catch a code fault before re-running.
|
|
176
|
+
const cta = exitReason === 'timeout'
|
|
177
|
+
? {
|
|
178
|
+
description: 'The cart never printed the done-sentinel (backstop fired). Check for a fault:',
|
|
179
|
+
commands: [
|
|
180
|
+
{
|
|
181
|
+
command: 'verify',
|
|
182
|
+
description: 'Run the static gate (tokens + lint + integrity).',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
command: 'tokens',
|
|
186
|
+
description: 'Confirm the cart is under the token budget.',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
: undefined;
|
|
191
|
+
return ok({
|
|
192
|
+
screenshots: [...screenshots],
|
|
193
|
+
printh,
|
|
194
|
+
exitReason,
|
|
195
|
+
shotDir,
|
|
196
|
+
wav,
|
|
197
|
+
audioCaptured,
|
|
198
|
+
}, cta === undefined ? undefined : { cta });
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import { type Cli } from 'incur';
|
|
3
|
+
import { type Pico8Adapter } from '../engine/pico8/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Injects the PICO-8 adapter `serve` uses (defaults to the shell adapter). Same
|
|
6
|
+
* seam as `export`/`run`: a test passes a stub so the export step needs no paid
|
|
7
|
+
* binary, and the child `env` (`PICO8_PATH`/`PATH`) stays isolated.
|
|
8
|
+
*/
|
|
9
|
+
export type Pico8AdapterFactory = (env: NodeJS.ProcessEnv) => Pico8Adapter;
|
|
10
|
+
/**
|
|
11
|
+
* Injects the HTTP-server factory `serve` binds. The default is a zero-dep
|
|
12
|
+
* `node:http` static server over the export dir; a test injects a fake to assert
|
|
13
|
+
* the serve loop WITHOUT opening a real socket. The factory returns the started
|
|
14
|
+
* server plus the bound port (so `:0` -> an OS-assigned port is observable).
|
|
15
|
+
*/
|
|
16
|
+
export type ServerFactory = (rootDir: string, port: number) => Promise<{
|
|
17
|
+
server: Server;
|
|
18
|
+
port: number;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Registers a one-shot cleanup for the serving case: `serve` runs until the user
|
|
22
|
+
* interrupts it (the socket keeps the process alive), so the temp export dir is
|
|
23
|
+
* removed on SIGINT/SIGTERM and on normal `exit`. The handler fires at most once.
|
|
24
|
+
* The default uses `process`; a test injects a fake to assert the wiring without
|
|
25
|
+
* touching real signal handlers.
|
|
26
|
+
*/
|
|
27
|
+
export type CleanupRegistrar = (cleanup: () => void) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Registers `picopilot serve`, the "play this cart in a browser" loop. It ALWAYS
|
|
30
|
+
* exports first (the quick round-trip: cart -> standalone HTML bundle -> local
|
|
31
|
+
* server -> URL you open), so it takes a cart path, not a pre-built dir. The
|
|
32
|
+
* export step hard-requires PICO-8 (absence is the structured `pico8-not-found`
|
|
33
|
+
* value + a nonzero exit, mirroring `run`/`export`); the static server itself is
|
|
34
|
+
* a zero-dep `node:http` file server over the temp export dir, so no extra
|
|
35
|
+
* dependency and no ~/Desktop pollution.
|
|
36
|
+
*
|
|
37
|
+
* It is distinct from `export`: `export` PRODUCES a bundle at a chosen dest (for
|
|
38
|
+
* the showcase); `serve` throws the bundle in a temp dir and serves it for a
|
|
39
|
+
* quick manual play. It runs until interrupted (the server keeps the process
|
|
40
|
+
* alive), which is why the injected {@link ServerFactory} seam lets a test drive
|
|
41
|
+
* the export + bind path without leaving a real socket open.
|
|
42
|
+
*
|
|
43
|
+
* @param adapterFactory injects the PICO-8 adapter (defaults to the shell one).
|
|
44
|
+
* @param serverFactory injects the HTTP server (defaults to the zero-dep static
|
|
45
|
+
* server); a test passes a fake to assert the loop without a real socket.
|
|
46
|
+
* @param registerCleanup injects the exit/signal cleanup wiring (defaults to the
|
|
47
|
+
* real `process` handlers); a test passes a fake to capture the cleanup fn.
|
|
48
|
+
*/
|
|
49
|
+
export declare function registerServe(cli: Cli.Cli, adapterFactory?: Pico8AdapterFactory, serverFactory?: ServerFactory, registerCleanup?: CleanupRegistrar): void;
|
|
50
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,MAAM,EAAC,MAAM,WAAW,CAAC;AAGpD,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAClC,OAAO,EAEN,KAAK,YAAY,EAEjB,MAAM,0BAA0B,CAAC;AAElC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AAK3E;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,KACR,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC;AAiB7C;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AA6E7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,EAC3D,aAAa,GAAE,aAAoC,EACnD,eAAe,GAAE,gBAA0C,GACzD,IAAI,CA4GN"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { createReadStream, existsSync, mkdtempSync, rmSync, statSync, } from 'node:fs';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { extname, isAbsolute, join, normalize, resolve } from 'node:path';
|
|
5
|
+
import { z } from 'incur';
|
|
6
|
+
import { EXPORT_HTML_NAME, ShellPico8Adapter, } from '../engine/pico8/index.js';
|
|
7
|
+
const defaultAdapterFactory = (env) => new ShellPico8Adapter({ env });
|
|
8
|
+
const DEFAULT_PORT = 5858;
|
|
9
|
+
const DEFAULT_BACKSTOP_MS = 30_000;
|
|
10
|
+
/**
|
|
11
|
+
* Best-effort recursive removal of the temp export dir, swallowing any error (a
|
|
12
|
+
* cleanup failure must never mask the command's own result or crash on exit).
|
|
13
|
+
*/
|
|
14
|
+
function removeDir(dir) {
|
|
15
|
+
try {
|
|
16
|
+
rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// A stubborn temp dir is not fatal; the OS reaps it eventually.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const defaultCleanupRegistrar = (cleanup) => {
|
|
23
|
+
let done = false;
|
|
24
|
+
const once = () => {
|
|
25
|
+
if (done)
|
|
26
|
+
return;
|
|
27
|
+
done = true;
|
|
28
|
+
cleanup();
|
|
29
|
+
};
|
|
30
|
+
process.once('exit', once);
|
|
31
|
+
// On a signal, run cleanup then re-exit so the temp dir is gone even on Ctrl-C.
|
|
32
|
+
process.once('SIGINT', () => {
|
|
33
|
+
once();
|
|
34
|
+
process.exit(130);
|
|
35
|
+
});
|
|
36
|
+
process.once('SIGTERM', () => {
|
|
37
|
+
once();
|
|
38
|
+
process.exit(143);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
/** MIME types the static server needs for a PICO-8 export (html + the runtime js). */
|
|
42
|
+
const MIME = {
|
|
43
|
+
'.html': 'text/html; charset=utf-8',
|
|
44
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
45
|
+
'.wasm': 'application/wasm',
|
|
46
|
+
'.png': 'image/png',
|
|
47
|
+
'.json': 'application/json; charset=utf-8',
|
|
48
|
+
'.css': 'text/css; charset=utf-8',
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The default {@link ServerFactory}: a minimal, zero-dependency `node:http`
|
|
52
|
+
* static file server rooted at `rootDir`. It resolves `/` to `index.html`,
|
|
53
|
+
* path-traversal-guards every request against `rootDir`, and streams files with
|
|
54
|
+
* a best-effort content type. It exists to PLAY an export locally, not to be a
|
|
55
|
+
* production server. Binding `:0` yields an OS-assigned port, reported back so
|
|
56
|
+
* the command can print the real URL.
|
|
57
|
+
*/
|
|
58
|
+
const defaultServerFactory = (rootDir, port) => new Promise((resolvePromise, reject) => {
|
|
59
|
+
const server = createServer((req, res) => {
|
|
60
|
+
const urlPath = decodeURIComponent((req.url ?? '/').split('?')[0]);
|
|
61
|
+
// Resolve the request against the root and guard against traversal: the
|
|
62
|
+
// resolved path must stay inside rootDir.
|
|
63
|
+
const rel = normalize(urlPath).replace(/^(\.\.[/\\])+/, '');
|
|
64
|
+
let filePath = join(rootDir, rel);
|
|
65
|
+
if (!filePath.startsWith(rootDir)) {
|
|
66
|
+
res.writeHead(403).end('forbidden');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
if (existsSync(filePath) && statSync(filePath).isDirectory()) {
|
|
71
|
+
filePath = join(filePath, 'index.html');
|
|
72
|
+
}
|
|
73
|
+
if (!existsSync(filePath)) {
|
|
74
|
+
res.writeHead(404).end('not found');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
res.writeHead(200, {
|
|
78
|
+
'content-type': MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream',
|
|
79
|
+
});
|
|
80
|
+
createReadStream(filePath).pipe(res);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
res.writeHead(500).end('server error');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
server.on('error', reject);
|
|
87
|
+
server.listen(port, '127.0.0.1', () => {
|
|
88
|
+
const addr = server.address();
|
|
89
|
+
const bound = typeof addr === 'object' && addr !== null ? addr.port : port;
|
|
90
|
+
resolvePromise({ server, port: bound });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Registers `picopilot serve`, the "play this cart in a browser" loop. It ALWAYS
|
|
95
|
+
* exports first (the quick round-trip: cart -> standalone HTML bundle -> local
|
|
96
|
+
* server -> URL you open), so it takes a cart path, not a pre-built dir. The
|
|
97
|
+
* export step hard-requires PICO-8 (absence is the structured `pico8-not-found`
|
|
98
|
+
* value + a nonzero exit, mirroring `run`/`export`); the static server itself is
|
|
99
|
+
* a zero-dep `node:http` file server over the temp export dir, so no extra
|
|
100
|
+
* dependency and no ~/Desktop pollution.
|
|
101
|
+
*
|
|
102
|
+
* It is distinct from `export`: `export` PRODUCES a bundle at a chosen dest (for
|
|
103
|
+
* the showcase); `serve` throws the bundle in a temp dir and serves it for a
|
|
104
|
+
* quick manual play. It runs until interrupted (the server keeps the process
|
|
105
|
+
* alive), which is why the injected {@link ServerFactory} seam lets a test drive
|
|
106
|
+
* the export + bind path without leaving a real socket open.
|
|
107
|
+
*
|
|
108
|
+
* @param adapterFactory injects the PICO-8 adapter (defaults to the shell one).
|
|
109
|
+
* @param serverFactory injects the HTTP server (defaults to the zero-dep static
|
|
110
|
+
* server); a test passes a fake to assert the loop without a real socket.
|
|
111
|
+
* @param registerCleanup injects the exit/signal cleanup wiring (defaults to the
|
|
112
|
+
* real `process` handlers); a test passes a fake to capture the cleanup fn.
|
|
113
|
+
*/
|
|
114
|
+
export function registerServe(cli, adapterFactory = defaultAdapterFactory, serverFactory = defaultServerFactory, registerCleanup = defaultCleanupRegistrar) {
|
|
115
|
+
cli.command('serve', {
|
|
116
|
+
description: 'Export a cart and serve it locally so you can play it in a browser. Requires PICO-8.',
|
|
117
|
+
args: z.object({
|
|
118
|
+
cart: z
|
|
119
|
+
.string()
|
|
120
|
+
.default('main.p8')
|
|
121
|
+
.describe('The .p8 cart to export and serve. Defaults to main.p8 in the current folder.'),
|
|
122
|
+
}),
|
|
123
|
+
options: z.object({
|
|
124
|
+
port: z
|
|
125
|
+
.number()
|
|
126
|
+
.int()
|
|
127
|
+
.min(0)
|
|
128
|
+
.max(65535)
|
|
129
|
+
.default(DEFAULT_PORT)
|
|
130
|
+
.describe('Port to serve on (0 = an OS-assigned free port).'),
|
|
131
|
+
backstopMs: z
|
|
132
|
+
.number()
|
|
133
|
+
.int()
|
|
134
|
+
.positive()
|
|
135
|
+
.default(DEFAULT_BACKSTOP_MS)
|
|
136
|
+
.describe('Safety backstop for the export step.'),
|
|
137
|
+
}),
|
|
138
|
+
env: z.object({
|
|
139
|
+
PICO8_PATH: z
|
|
140
|
+
.string()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe('Explicit path to the PICO-8 binary (else `pico8` on PATH).'),
|
|
143
|
+
PATH: z.string().optional().describe('Used to locate `pico8`.'),
|
|
144
|
+
}),
|
|
145
|
+
output: z.object({
|
|
146
|
+
url: z.string().describe('The local URL to open in a browser.'),
|
|
147
|
+
port: z.number().describe('The port the server bound to.'),
|
|
148
|
+
serveDir: z
|
|
149
|
+
.string()
|
|
150
|
+
.describe('The temp dir the export bundle was served from.'),
|
|
151
|
+
}),
|
|
152
|
+
examples: [
|
|
153
|
+
{ description: 'Export and serve main.p8' },
|
|
154
|
+
{ description: 'Serve a specific cart', args: { cart: 'game.p8' } },
|
|
155
|
+
{
|
|
156
|
+
description: 'Serve on a chosen port',
|
|
157
|
+
args: { cart: 'game.p8' },
|
|
158
|
+
options: { port: 8080 },
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
async run({ args, options, env, error, ok }) {
|
|
162
|
+
const cartPath = isAbsolute(args.cart)
|
|
163
|
+
? args.cart
|
|
164
|
+
: resolve(process.cwd(), args.cart);
|
|
165
|
+
if (!existsSync(cartPath)) {
|
|
166
|
+
return error({
|
|
167
|
+
code: 'cart-not-found',
|
|
168
|
+
message: `no cart at ${cartPath}`,
|
|
169
|
+
exitCode: 1,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Always export first, into an isolated temp dir (never a user path or
|
|
173
|
+
// ~/Desktop). The bundle is the standalone pair the static server plays.
|
|
174
|
+
const serveDir = mkdtempSync(join(tmpdir(), 'picopilot-serve-'));
|
|
175
|
+
const adapter = adapterFactory(env);
|
|
176
|
+
const result = await adapter.export({
|
|
177
|
+
cartPath,
|
|
178
|
+
outDir: serveDir,
|
|
179
|
+
htmlName: EXPORT_HTML_NAME,
|
|
180
|
+
backstopMs: options.backstopMs,
|
|
181
|
+
});
|
|
182
|
+
if (!result.ok) {
|
|
183
|
+
// Nothing is being served: remove the temp dir now, not on exit.
|
|
184
|
+
removeDir(serveDir);
|
|
185
|
+
return error({
|
|
186
|
+
code: result.reason,
|
|
187
|
+
message: `PICO-8 is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
188
|
+
exitCode: 1,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (result.value.jsPath === undefined) {
|
|
192
|
+
removeDir(serveDir);
|
|
193
|
+
return error({
|
|
194
|
+
code: 'export-failed',
|
|
195
|
+
message: result.value.labelWarning
|
|
196
|
+
? 'PICO-8 produced no bundle (the cart has no __label__). Capture a label first, then re-serve.'
|
|
197
|
+
: 'PICO-8 produced no export bundle. Check the cart runs without a boot error.',
|
|
198
|
+
exitCode: 1,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const { server, port } = await serverFactory(serveDir, options.port);
|
|
202
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
203
|
+
// The server keeps the process alive until interrupted; the temp export dir
|
|
204
|
+
// is reaped on SIGINT/SIGTERM/exit so a long-lived serve leaves no leak.
|
|
205
|
+
// `server` is referenced so its lifetime is the command's; a test's fake
|
|
206
|
+
// resolves without a real socket.
|
|
207
|
+
registerCleanup(() => removeDir(serveDir));
|
|
208
|
+
void server;
|
|
209
|
+
return ok({ url, port, serveDir });
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Cli } from 'incur';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the `sfx` command group (`sfx from-mml`) on the root CLI.
|
|
4
|
+
*
|
|
5
|
+
* `sfx` is a shrinko-FREE and PICO-8-FREE command group: it transpiles
|
|
6
|
+
* picopilot-MML to `__sfx__` and merges it through the cart model entirely in
|
|
7
|
+
* TS, so it works with both absent and never spawns a child process. The
|
|
8
|
+
* picopilot-MML dialect (ADR-0005 + the audio spike finding) is TRACKER-ROW
|
|
9
|
+
* based: durations are row counts at one SFX speed, NOT tempo (ADR-0008), and
|
|
10
|
+
* out-of-range pitch / >32 rows are structured refusals, never silent clamps.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerSfx(cli: Cli.Cli): void;
|
|
13
|
+
//# sourceMappingURL=sfx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfx.d.ts","sourceRoot":"","sources":["../../src/commands/sfx.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,GAAG,EAAI,MAAM,OAAO,CAAC;AA6R7B;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,IAAI,CAE9C"}
|