@debuffjp/cli 0.0.0-stage → 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.
Files changed (90) hide show
  1. package/README.md +86 -2
  2. package/aseprite/new-font.lua +31 -0
  3. package/bin/debuff.js +3 -0
  4. package/client.d.ts +3 -0
  5. package/dist/agent-link.d.ts +28 -0
  6. package/dist/agent-link.d.ts.map +1 -0
  7. package/dist/agent-link.js +122 -0
  8. package/dist/agent-link.js.map +1 -0
  9. package/dist/assets/aseprite.d.ts +29 -0
  10. package/dist/assets/aseprite.d.ts.map +1 -0
  11. package/dist/assets/aseprite.js +107 -0
  12. package/dist/assets/aseprite.js.map +1 -0
  13. package/dist/assets/atlas.d.ts +58 -0
  14. package/dist/assets/atlas.d.ts.map +1 -0
  15. package/dist/assets/atlas.js +164 -0
  16. package/dist/assets/atlas.js.map +1 -0
  17. package/dist/assets/builtin.d.ts +5 -0
  18. package/dist/assets/builtin.d.ts.map +1 -0
  19. package/dist/assets/builtin.js +47 -0
  20. package/dist/assets/builtin.js.map +1 -0
  21. package/dist/assets/command.d.ts +4 -0
  22. package/dist/assets/command.d.ts.map +1 -0
  23. package/dist/assets/command.js +35 -0
  24. package/dist/assets/command.js.map +1 -0
  25. package/dist/assets/compile.d.ts +29 -0
  26. package/dist/assets/compile.d.ts.map +1 -0
  27. package/dist/assets/compile.js +209 -0
  28. package/dist/assets/compile.js.map +1 -0
  29. package/dist/assets/index.d.ts +6 -0
  30. package/dist/assets/index.d.ts.map +1 -0
  31. package/dist/assets/index.js +6 -0
  32. package/dist/assets/index.js.map +1 -0
  33. package/dist/cli.d.ts +3 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +257 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/config.d.ts +24 -0
  38. package/dist/config.d.ts.map +1 -0
  39. package/dist/config.js +53 -0
  40. package/dist/config.js.map +1 -0
  41. package/dist/fuzz.d.ts +43 -0
  42. package/dist/fuzz.d.ts.map +1 -0
  43. package/dist/fuzz.js +229 -0
  44. package/dist/fuzz.js.map +1 -0
  45. package/dist/index.d.ts +12 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +11 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/inspect.d.ts +35 -0
  50. package/dist/inspect.d.ts.map +1 -0
  51. package/dist/inspect.js +37 -0
  52. package/dist/inspect.js.map +1 -0
  53. package/dist/plugin.d.ts +146 -0
  54. package/dist/plugin.d.ts.map +1 -0
  55. package/dist/plugin.js +2 -0
  56. package/dist/plugin.js.map +1 -0
  57. package/dist/report.d.ts +26 -0
  58. package/dist/report.d.ts.map +1 -0
  59. package/dist/report.js +80 -0
  60. package/dist/report.js.map +1 -0
  61. package/dist/scenario/browser.d.ts +12 -0
  62. package/dist/scenario/browser.d.ts.map +1 -0
  63. package/dist/scenario/browser.js +59 -0
  64. package/dist/scenario/browser.js.map +1 -0
  65. package/dist/scenario/index.d.ts +5 -0
  66. package/dist/scenario/index.d.ts.map +1 -0
  67. package/dist/scenario/index.js +4 -0
  68. package/dist/scenario/index.js.map +1 -0
  69. package/dist/scenario/run.d.ts +14 -0
  70. package/dist/scenario/run.d.ts.map +1 -0
  71. package/dist/scenario/run.js +255 -0
  72. package/dist/scenario/run.js.map +1 -0
  73. package/dist/scenario/schema.d.ts +8 -0
  74. package/dist/scenario/schema.d.ts.map +1 -0
  75. package/dist/scenario/schema.js +23 -0
  76. package/dist/scenario/schema.js.map +1 -0
  77. package/dist/scenario/types.d.ts +103 -0
  78. package/dist/scenario/types.d.ts.map +1 -0
  79. package/dist/scenario/types.js +2 -0
  80. package/dist/scenario/types.js.map +1 -0
  81. package/dist/vite-plugin.d.ts +18 -0
  82. package/dist/vite-plugin.d.ts.map +1 -0
  83. package/dist/vite-plugin.js +122 -0
  84. package/dist/vite-plugin.js.map +1 -0
  85. package/dist/vite.d.ts +13 -0
  86. package/dist/vite.d.ts.map +1 -0
  87. package/dist/vite.js +25 -0
  88. package/dist/vite.js.map +1 -0
  89. package/package.json +71 -4
  90. package/schema/scenario.schema.json +120 -0
package/README.md CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # @debuffjp/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `debuff` command: dev server, production builds with size reports, asset manifests, browser scenarios, fuzz playtesting and project inspection. Built on Vite.
4
+
5
+ ```bash
6
+ npm install -D @debuffjp/cli
7
+ npx debuff --help
8
+ ```
9
+
10
+ | Command | What it does |
11
+ | --- | --- |
12
+ | `debuff create [dir]` | Scaffold a game (same as `npm create debuff`) |
13
+ | `debuff dev [--port] [--host]` | Dev server with hot reload; regenerates the asset manifest on change |
14
+ | `debuff build [--sourcemap]` | Production build, then a raw/gzip/brotli report. Fails if dev-only code leaks |
15
+ | `debuff report` | Size report for an existing `dist/` |
16
+ | `debuff assets` | Run the asset pipeline: list every asset, its importer and size, plus unused and generated files |
17
+ | `debuff scenario [files] [--update] [--strict]` | Run `scenarios/*.json` in headless Chrome/Edge |
18
+ | `debuff fuzz [--seconds] [--runs] [--seed] [--keys]` | (next release) Play headless with random but plausible input. Reports explained errors and stuck states, each saved as a scenario in `.debuff/fuzz/` that reproduces it. Exits 1 on errors |
19
+ | `debuff inspect` | JSON summary of the project |
20
+
21
+ Add `--json` for machine-readable output and `--root <dir>` to target another project.
22
+
23
+ ## Scenarios
24
+
25
+ ```json
26
+ {
27
+ "$schema": "../node_modules/@debuffjp/cli/schema/scenario.schema.json",
28
+ "steps": [
29
+ { "press": "ArrowRight" },
30
+ { "wait": 30 },
31
+ { "expect": { "state.player.x": { "gt": 200 } } },
32
+ { "set": { "player.speed": 400 } },
33
+ { "capture": "after-move" }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ Time advances only through `wait` steps, so runs are deterministic. Replays saved from the dev panel (or the MCP `game_replay` tool) are scenarios too. Captures are compared against per-platform baselines in `scenarios/__baselines__/`. A missing baseline is recorded unless you pass `--strict`. Set `DEBUFF_BROWSER` to use a specific Chromium build.
39
+
40
+ ## Programmatic use
41
+
42
+ ```ts
43
+ import { buildReport, compileAssets, runScenarios } from "@debuffjp/cli";
44
+ ```
45
+
46
+ ## Assets and plugins
47
+
48
+ Put source assets in `assets/`. The pipeline builds an in-memory manifest (`/assets/manifest.json`). Only files the manifest references are served in dev and emitted in builds, and sources are never modified.
49
+
50
+ Add tooling plugins in `debuff.config.ts`:
51
+
52
+ ```ts
53
+ import { defineConfig } from "@debuffjp/cli";
54
+
55
+ export default defineConfig({
56
+ plugins: [
57
+ {
58
+ name: "maps",
59
+ apiVersion: 1,
60
+ // Any language: SourceFile JSON on stdin, ImportResult JSON on stdout.
61
+ assets: [{ name: "maps", extensions: [".map"], command: ["python", "tools/import_map.py"] }],
62
+ },
63
+ ],
64
+ });
65
+ ```
66
+
67
+ ### Aseprite
68
+
69
+ The first-party `aseprite()` plugin runs Aseprite for you, so `.aseprite` and `.ase` files in `assets/` load like PNGs with no export step:
70
+
71
+ ```ts
72
+ import { aseprite, defineConfig } from "@debuffjp/cli";
73
+
74
+ export default defineConfig({ plugins: [aseprite()] });
75
+ ```
76
+
77
+ - `hero.aseprite` with several frames becomes a sprite sheet: `assets.frames("hero")` returns the frames in order.
78
+ - `font@6x9.aseprite` flattens to one image with a 6×9 grid, ready for `BitmapFont`.
79
+ - Hidden layers are left out, so guide layers stay in the source.
80
+
81
+ It looks for Aseprite in `$ASEPRITE`, then on `PATH`, then in the usual install folders (including Steam). You can also pass `aseprite({ binary })`. If it can't find Aseprite, the build warns and skips those files. Results are cached by file contents, so Aseprite only runs when a file changes.
82
+
83
+ `aseprite/new-font.lua` creates a blank font sheet with the grid set up:
84
+
85
+ ```bash
86
+ aseprite -b --script-param w=6 --script-param h=9 --script-param out=assets/font@6x9.aseprite --script node_modules/@debuffjp/cli/aseprite/new-font.lua
87
+ ```
@@ -0,0 +1,31 @@
1
+ -- Creates a blank bitmap-font sheet for debuff: one cell per printable ASCII character
2
+ -- (space to "~", 16 per row), with Aseprite's grid set to the cell size.
3
+ --
4
+ -- aseprite -b --script-param w=6 --script-param h=9 --script-param out=assets/font@6x9.aseprite --script new-font.lua
5
+ --
6
+ -- Draw each glyph from the cell's left edge; debuff measures widths from the rightmost
7
+ -- opaque column. With the aseprite() plugin in debuff.config.ts the file loads as-is; without
8
+ -- it, export with: aseprite -b assets/font@6x9.aseprite --save-as assets/font@6x9.png
9
+ -- Then: new BitmapFont({ frames: assets.frames("font"), chars: BitmapFont.ASCII })
10
+
11
+ local w = tonumber(app.params["w"] or "6")
12
+ local h = tonumber(app.params["h"] or "9")
13
+ local out = app.params["out"] or "font.aseprite"
14
+ local cols, count = 16, 95
15
+ local rows = math.ceil(count / cols)
16
+
17
+ local sprite = Sprite(w * cols, h * rows, ColorMode.RGB)
18
+ sprite.gridBounds = Rectangle(0, 0, w, h)
19
+ sprite.layers[1].name = "glyphs"
20
+
21
+ -- A hidden guide layer: a dot in each cell's top-left marks where a character goes.
22
+ local guide = sprite:newLayer()
23
+ guide.name = "guide (hidden)"
24
+ guide.isVisible = false
25
+ local img = Image(sprite.width, sprite.height, ColorMode.RGB)
26
+ for i = 0, count - 1 do
27
+ img:drawPixel((i % cols) * w, (i // cols) * h, app.pixelColor.rgba(255, 0, 255, 255))
28
+ end
29
+ sprite:newCel(guide, 1, img, Point(0, 0))
30
+
31
+ sprite:saveAs(out)
package/bin/debuff.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Committed shim so npm can link the bin before dist/ is built.
3
+ import "../dist/cli.js";
package/client.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ // Ambient types for game code built by the debuff CLI (import.meta.env, asset imports).
2
+ // Add to tsconfig: "types": ["@debuffjp/cli/client"]
3
+ /// <reference types="vite/client" />
@@ -0,0 +1,28 @@
1
+ import type { ViteDevServer } from "vite";
2
+ /** Endpoints shared with `@debuffjp/dev` (page side) and `@debuffjp/mcp` (agent side). */
3
+ export declare const AGENT_PATHS: {
4
+ readonly events: "/__debuff/agent/events";
5
+ readonly attach: "/__debuff/agent/attach";
6
+ readonly result: "/__debuff/agent/result";
7
+ readonly rpc: "/__debuff/agent/rpc";
8
+ readonly status: "/__debuff/agent/status";
9
+ };
10
+ /** Written while `debuff dev` runs, so tools can find the live game. */
11
+ export declare const DEV_SERVER_FILE = ".debuff/dev-server.json";
12
+ export interface DevServerInfo {
13
+ url: string;
14
+ pid: number;
15
+ }
16
+ export interface AgentStatus {
17
+ /** Open game pages listening for agent commands. */
18
+ pages: number;
19
+ /** Agents currently attached (holding an attach connection). */
20
+ agents: number;
21
+ }
22
+ /**
23
+ * Agent link (server side): relays dev-hook calls from tools to the game open in the developer's
24
+ * browser. Pages subscribe over Server-Sent Events; a tool POSTs `{ method, args }` to the RPC endpoint,
25
+ * the newest page runs it and POSTs the result back. Dev server only, localhost only.
26
+ */
27
+ export declare function attachAgentLink(server: ViteDevServer, root: string): void;
28
+ //# sourceMappingURL=agent-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-link.d.ts","sourceRoot":"","sources":["../src/agent-link.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAE1C,0FAA0F;AAC1F,eAAO,MAAM,WAAW;aACtB,MAAM,EAAE,wBAAwB;aAChC,MAAM,EAAE,wBAAwB;aAChC,MAAM,EAAE,wBAAwB;aAChC,GAAG,EAAE,qBAAqB;aAC1B,MAAM,EAAE,wBAAwB;CACxB,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,eAAe,4BAA4B,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAqFzE"}
@@ -0,0 +1,122 @@
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Endpoints shared with `@debuffjp/dev` (page side) and `@debuffjp/mcp` (agent side). */
4
+ export const AGENT_PATHS = {
5
+ events: "/__debuff/agent/events",
6
+ attach: "/__debuff/agent/attach",
7
+ result: "/__debuff/agent/result",
8
+ rpc: "/__debuff/agent/rpc",
9
+ status: "/__debuff/agent/status",
10
+ };
11
+ /** Written while `debuff dev` runs, so tools can find the live game. */
12
+ export const DEV_SERVER_FILE = ".debuff/dev-server.json";
13
+ /**
14
+ * Agent link (server side): relays dev-hook calls from tools to the game open in the developer's
15
+ * browser. Pages subscribe over Server-Sent Events; a tool POSTs `{ method, args }` to the RPC endpoint,
16
+ * the newest page runs it and POSTs the result back. Dev server only, localhost only.
17
+ */
18
+ export function attachAgentLink(server, root) {
19
+ const pages = [];
20
+ const pending = new Map();
21
+ let agents = 0;
22
+ const announce = () => {
23
+ for (const page of pages)
24
+ send(page, "presence", { agents });
25
+ };
26
+ let nextId = 1;
27
+ const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
28
+ server.middlewares.use(async (req, res, next) => {
29
+ const url = req.url?.split("?")[0];
30
+ if (!url?.startsWith("/__debuff/agent/"))
31
+ return next();
32
+ try {
33
+ if (url === AGENT_PATHS.events && req.method === "GET") {
34
+ res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
35
+ res.write(": debuff agent link\n\n");
36
+ pages.push(res);
37
+ send(res, "presence", { agents });
38
+ const keepAlive = setInterval(() => res.write(": ping\n\n"), 15_000);
39
+ req.on("close", () => {
40
+ clearInterval(keepAlive);
41
+ pages.splice(pages.indexOf(res), 1);
42
+ });
43
+ return;
44
+ }
45
+ if (url === AGENT_PATHS.status && req.method === "GET")
46
+ return reply(res, 200, { pages: pages.length, agents });
47
+ if (url === AGENT_PATHS.attach && req.method === "GET") {
48
+ // Held open by an agent for as long as it is attached; closing it (or the agent dying) detaches.
49
+ res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
50
+ res.write(": attached\n\n");
51
+ agents++;
52
+ announce();
53
+ const keepAlive = setInterval(() => res.write(": ping\n\n"), 15_000);
54
+ req.on("close", () => {
55
+ clearInterval(keepAlive);
56
+ agents--;
57
+ announce();
58
+ });
59
+ return;
60
+ }
61
+ if (url === AGENT_PATHS.result && req.method === "POST") {
62
+ const body = (await readJson(req));
63
+ const p = pending.get(body.id);
64
+ if (p) {
65
+ clearTimeout(p.timer);
66
+ pending.delete(body.id);
67
+ p.resolve(body);
68
+ }
69
+ return reply(res, 204);
70
+ }
71
+ if (url === AGENT_PATHS.rpc && req.method === "POST") {
72
+ const { method, args = [] } = (await readJson(req));
73
+ const page = pages.at(-1);
74
+ if (!page)
75
+ return reply(res, 503, { ok: false, error: "No game page is open. Open the dev server URL in a browser." });
76
+ const id = String(nextId++);
77
+ const result = await new Promise((resolve) => {
78
+ const timer = setTimeout(() => {
79
+ pending.delete(id);
80
+ resolve({ id, ok: false, error: `The game page did not answer "${method}" within 10 s (is the tab in the background?)` });
81
+ }, 10_000);
82
+ pending.set(id, { resolve, timer });
83
+ send(page, "command", { id, method, args });
84
+ });
85
+ return reply(res, 200, result);
86
+ }
87
+ reply(res, 404, { ok: false, error: `Unknown agent endpoint ${url}` });
88
+ }
89
+ catch (err) {
90
+ reply(res, 400, { ok: false, error: err instanceof Error ? err.message : String(err) });
91
+ }
92
+ });
93
+ const file = join(root, DEV_SERVER_FILE);
94
+ server.httpServer?.once("listening", () => {
95
+ const url = server.resolvedUrls?.local[0];
96
+ if (!url)
97
+ return;
98
+ mkdirSync(join(root, ".debuff"), { recursive: true });
99
+ writeFileSync(file, `${JSON.stringify({ url, pid: process.pid }, null, 2)}\n`);
100
+ });
101
+ server.httpServer?.once("close", () => {
102
+ for (const p of pending.values())
103
+ clearTimeout(p.timer);
104
+ for (const page of pages)
105
+ page.end();
106
+ rmSync(file, { force: true });
107
+ });
108
+ }
109
+ function reply(res, status, body) {
110
+ res.statusCode = status;
111
+ if (body === undefined)
112
+ return void res.end();
113
+ res.setHeader("Content-Type", "application/json");
114
+ res.end(JSON.stringify(body));
115
+ }
116
+ async function readJson(req) {
117
+ let raw = "";
118
+ for await (const chunk of req)
119
+ raw += chunk;
120
+ return JSON.parse(raw || "{}");
121
+ }
122
+ //# sourceMappingURL=agent-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-link.js","sourceRoot":"","sources":["../src/agent-link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { FunctionImporter, ToolingPlugin } from "../plugin.js";
2
+ export interface AsepriteOptions {
3
+ /** Path to the Aseprite executable. Default: `$ASEPRITE`, then `aseprite` on PATH, then the usual install folders. */
4
+ binary?: string;
5
+ /** @internal Runs Aseprite; replaced in tests. */
6
+ exec?: (binary: string, args: string[]) => void;
7
+ }
8
+ /** Find the Aseprite executable, or undefined. */
9
+ export declare function findAseprite(explicit?: string): string | undefined;
10
+ /**
11
+ * Imports `.aseprite` / `.ase` files by running Aseprite, so you never export by hand.
12
+ *
13
+ * - `name@WxH.aseprite`: flattened to one PNG with a W×H grid, e.g. a bitmap-font sheet.
14
+ * - `name.aseprite` with several frames: a sprite sheet, one cell per frame, so `assets.frames("name")` returns them in order.
15
+ *
16
+ * Hidden layers are left out, so guide layers stay in the source only. Results are cached by content.
17
+ */
18
+ export declare function asepriteImporter(options?: AsepriteOptions): FunctionImporter;
19
+ /**
20
+ * Tooling plugin: `.aseprite` files in `assets/` become PNGs and sprite sheets automatically.
21
+ *
22
+ * ```ts
23
+ * // debuff.config.ts
24
+ * import { aseprite, defineConfig } from "@debuffjp/cli";
25
+ * export default defineConfig({ plugins: [aseprite()] });
26
+ * ```
27
+ */
28
+ export declare function aseprite(options?: AsepriteOptions): ToolingPlugin;
29
+ //# sourceMappingURL=aseprite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aseprite.d.ts","sourceRoot":"","sources":["../../src/assets/aseprite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAA4B,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC9B,sHAAsH;IACtH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACjD;AAmBD,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQlE;AAOD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,eAAoB,GAAG,gBAAgB,CAiDhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,aAAa,CAErE"}
@@ -0,0 +1,107 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { delimiter, join } from "node:path";
5
+ const GRID = /@(\d+)x(\d+)$/;
6
+ /** Where Aseprite usually lives, per platform (Steam and standalone installs). */
7
+ function candidates() {
8
+ const env = process.env;
9
+ if (process.platform === "win32") {
10
+ const roots = [env["ProgramFiles"], env["ProgramFiles(x86)"]].filter((r) => !!r);
11
+ return roots.flatMap((r) => [join(r, "Aseprite", "Aseprite.exe"), join(r, "Steam", "steamapps", "common", "Aseprite", "Aseprite.exe")]);
12
+ }
13
+ if (process.platform === "darwin")
14
+ return [
15
+ "/Applications/Aseprite.app/Contents/MacOS/aseprite",
16
+ join(env["HOME"] ?? "", "Library/Application Support/Steam/steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite"),
17
+ ];
18
+ return ["/usr/bin/aseprite", "/usr/local/bin/aseprite", join(env["HOME"] ?? "", ".steam/steam/steamapps/common/Aseprite/aseprite")];
19
+ }
20
+ /** Find the Aseprite executable, or undefined. */
21
+ export function findAseprite(explicit) {
22
+ if (explicit)
23
+ return existsSync(explicit) ? explicit : undefined;
24
+ const fromEnv = process.env["ASEPRITE"];
25
+ if (fromEnv && existsSync(fromEnv))
26
+ return fromEnv;
27
+ const names = process.platform === "win32" ? ["aseprite.exe", "Aseprite.exe"] : ["aseprite"];
28
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter).filter(Boolean))
29
+ for (const name of names)
30
+ if (existsSync(join(dir, name)))
31
+ return join(dir, name);
32
+ return candidates().find((p) => existsSync(p));
33
+ }
34
+ /**
35
+ * Imports `.aseprite` / `.ase` files by running Aseprite, so you never export by hand.
36
+ *
37
+ * - `name@WxH.aseprite`: flattened to one PNG with a W×H grid, e.g. a bitmap-font sheet.
38
+ * - `name.aseprite` with several frames: a sprite sheet, one cell per frame, so `assets.frames("name")` returns them in order.
39
+ *
40
+ * Hidden layers are left out, so guide layers stay in the source only. Results are cached by content.
41
+ */
42
+ export function asepriteImporter(options = {}) {
43
+ const exec = options.exec ?? ((bin, args) => execFileSync(bin, args, { stdio: "pipe" }));
44
+ return {
45
+ name: "aseprite",
46
+ extensions: [".aseprite", ".ase"],
47
+ import(file) {
48
+ const binary = options.exec ? (options.binary ?? "aseprite") : findAseprite(options.binary);
49
+ if (!binary)
50
+ return {
51
+ assets: {},
52
+ warnings: [
53
+ `Skipped ${file.rel}: Aseprite not found. Install it, put it on PATH, or set ASEPRITE to its executable (or pass aseprite({ binary })).`,
54
+ ],
55
+ };
56
+ let id = file.id;
57
+ const grid = GRID.exec(id);
58
+ if (grid)
59
+ id = id.slice(0, grid.index);
60
+ const out = file.rel.slice(0, -file.ext.length).replace(GRID, "");
61
+ const dir = mkdtempSync(join(tmpdir(), "debuff-aseprite-"));
62
+ try {
63
+ const png = join(dir, "sheet.png");
64
+ const data = join(dir, "sheet.json");
65
+ exec(binary, ["-b", file.path, "--sheet", png, "--sheet-type", "rows", "--data", data, "--format", "json-array"]);
66
+ const sheet = JSON.parse(readFileSync(data, "utf8"));
67
+ const first = sheet.frames[0]?.frame;
68
+ const cell = grid
69
+ ? { frameWidth: Number(grid[1]), frameHeight: Number(grid[2]) }
70
+ : sheet.frames.length > 1 && first
71
+ ? { frameWidth: first.w, frameHeight: first.h }
72
+ : undefined;
73
+ return {
74
+ assets: {
75
+ [id]: {
76
+ type: "image",
77
+ src: `assets/${out}.png`,
78
+ ...(file.group ? { group: file.group } : {}),
79
+ ...(cell ? { grid: cell } : {}),
80
+ },
81
+ },
82
+ outputs: [{ path: `${out}.png`, contents: readFileSync(png).toString("base64"), encoding: "base64" }],
83
+ };
84
+ }
85
+ catch (err) {
86
+ const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
87
+ return { assets: {}, warnings: [`Skipped ${file.rel}: Aseprite failed (${detail})`] };
88
+ }
89
+ finally {
90
+ rmSync(dir, { recursive: true, force: true });
91
+ }
92
+ },
93
+ };
94
+ }
95
+ /**
96
+ * Tooling plugin: `.aseprite` files in `assets/` become PNGs and sprite sheets automatically.
97
+ *
98
+ * ```ts
99
+ * // debuff.config.ts
100
+ * import { aseprite, defineConfig } from "@debuffjp/cli";
101
+ * export default defineConfig({ plugins: [aseprite()] });
102
+ * ```
103
+ */
104
+ export function aseprite(options = {}) {
105
+ return { name: "aseprite", apiVersion: 1, assets: [asepriteImporter(options)] };
106
+ }
107
+ //# sourceMappingURL=aseprite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aseprite.js","sourceRoot":"","sources":["../../src/assets/aseprite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ export interface AtlasOptions {
2
+ /** Largest atlas side in pixels. Default 2048 (safe on every WebGL2 device). */
3
+ maxSize?: number;
4
+ /** Transparent gap between sprites, in pixels. Default 2. */
5
+ padding?: number;
6
+ /** Edge pixels copied outward into the padding so filtering never samples a neighbour. Default 1. */
7
+ extrude?: number;
8
+ /** Asset ids (or id prefixes ending in "/") to leave out of atlases. */
9
+ exclude?: string[];
10
+ }
11
+ export interface Placement {
12
+ x: number;
13
+ y: number;
14
+ }
15
+ export interface PackedBin {
16
+ width: number;
17
+ height: number;
18
+ /** Placement per input index, in this bin. */
19
+ items: Map<number, Placement>;
20
+ }
21
+ /**
22
+ * MaxRects bin packing (best-short-side-fit). Inputs are sorted largest-first; anything that doesn't
23
+ * fit opens a new bin. Bins are cropped to their used area (rounded up to a multiple of 4).
24
+ * Throws if a single item can't fit in an empty bin.
25
+ */
26
+ export declare function packRects(sizes: readonly {
27
+ w: number;
28
+ h: number;
29
+ }[], maxSize: number, gap: number): PackedBin[];
30
+ export interface Rgba {
31
+ width: number;
32
+ height: number;
33
+ /** RGBA, 8 bits per channel. */
34
+ data: Uint8Array;
35
+ }
36
+ /** Decode any PNG (palette, grey, RGB, 16-bit…) to 8-bit RGBA. */
37
+ export declare function decodePng(bytes: Uint8Array): Rgba;
38
+ /** Copy `img` into `atlas` at (x, y), extruding its edge pixels `extrude` px outward. */
39
+ export declare function blit(atlas: Rgba, img: Rgba, x: number, y: number, extrude: number): void;
40
+ export interface AtlasInput {
41
+ id: string;
42
+ png: Uint8Array;
43
+ }
44
+ export interface BuiltAtlas {
45
+ png: Uint8Array;
46
+ width: number;
47
+ height: number;
48
+ /** Source rectangle per input id. */
49
+ rects: Map<string, {
50
+ x: number;
51
+ y: number;
52
+ w: number;
53
+ h: number;
54
+ }>;
55
+ }
56
+ /** Pack PNGs into as few atlases as fit `maxSize`. */
57
+ export declare function buildAtlases(inputs: readonly AtlasInput[], options?: AtlasOptions): BuiltAtlas[];
58
+ //# sourceMappingURL=atlas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atlas.d.ts","sourceRoot":"","sources":["../../src/assets/atlas.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAC/B;AASD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CA6B/G;AAgDD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CA4BjD;AAED,yFAAyF;AACzF,wBAAgB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAkBxF;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,UAAU,EAAE,CA8BpG"}