picopilot 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +224 -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 +259 -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 +482 -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 +264 -0
- package/src/commands/run.ts +285 -0
- package/src/commands/serve.test.ts +227 -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 +278 -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,137 @@
|
|
|
1
|
+
import {execFileSync} from 'node:child_process';
|
|
2
|
+
import {mkdtempSync, writeFileSync} from 'node:fs';
|
|
3
|
+
import {tmpdir} from 'node:os';
|
|
4
|
+
import {join} from 'node:path';
|
|
5
|
+
import {Cli} from 'incur';
|
|
6
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
7
|
+
import type {
|
|
8
|
+
LintReport,
|
|
9
|
+
ShrinkoAdapter,
|
|
10
|
+
ShrinkoResult,
|
|
11
|
+
} from '../engine/shrinko/index.js';
|
|
12
|
+
import {parseLint, shrinkoNotFound} from '../engine/shrinko/index.js';
|
|
13
|
+
import {type ShrinkoAdapterFactory, registerLint} from './lint.js';
|
|
14
|
+
|
|
15
|
+
const CART_TEXT =
|
|
16
|
+
'pico-8 cartridge // http://www.pico-8.com\nversion 42\n__lua__\nprint("hi")\n';
|
|
17
|
+
|
|
18
|
+
let dir: string;
|
|
19
|
+
let cartPath: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
dir = mkdtempSync(join(tmpdir(), 'picopilot-lint-'));
|
|
23
|
+
cartPath = join(dir, 'main.p8');
|
|
24
|
+
writeFileSync(cartPath, CART_TEXT);
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
try {
|
|
28
|
+
execFileSync('rm', ['-rf', dir]);
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** A stub adapter whose `lint` returns whatever the test wires; `count`/`minify` unused here. */
|
|
35
|
+
function stubAdapter(result: ShrinkoResult<LintReport>): ShrinkoAdapter {
|
|
36
|
+
return {
|
|
37
|
+
async count() {
|
|
38
|
+
throw new Error('not used');
|
|
39
|
+
},
|
|
40
|
+
async lint() {
|
|
41
|
+
return result;
|
|
42
|
+
},
|
|
43
|
+
async minify() {
|
|
44
|
+
throw new Error('not used');
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runLint(
|
|
50
|
+
factory: ShrinkoAdapterFactory,
|
|
51
|
+
argv: string[] = ['lint', cartPath, '--json'],
|
|
52
|
+
env: Record<string, string | undefined> = {PATH: '/x'},
|
|
53
|
+
) {
|
|
54
|
+
const cli = Cli.create('picopilot', {version: '0.0.0'});
|
|
55
|
+
registerLint(cli, factory);
|
|
56
|
+
let stdout = '';
|
|
57
|
+
let exitCode = 0;
|
|
58
|
+
await cli.serve(argv, {
|
|
59
|
+
stdout(s) {
|
|
60
|
+
stdout += s;
|
|
61
|
+
},
|
|
62
|
+
exit(code) {
|
|
63
|
+
exitCode = code;
|
|
64
|
+
},
|
|
65
|
+
env,
|
|
66
|
+
});
|
|
67
|
+
return {stdout, exitCode};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe('parseLint: shrinko --lint output to structured findings', () => {
|
|
71
|
+
it('parses file:line:col: message lines, dropping the filename', () => {
|
|
72
|
+
const out = [
|
|
73
|
+
'Lint warnings:',
|
|
74
|
+
"lintme.p8:5:8: Local 'unused' isn't used",
|
|
75
|
+
"lintme.p8:6:4: Identifier 'undefined_global' not found",
|
|
76
|
+
].join('\n');
|
|
77
|
+
const {findings} = parseLint(out);
|
|
78
|
+
expect(findings).toHaveLength(2);
|
|
79
|
+
expect(findings[0]).toEqual({
|
|
80
|
+
line: 5,
|
|
81
|
+
col: 8,
|
|
82
|
+
message: "Local 'unused' isn't used",
|
|
83
|
+
});
|
|
84
|
+
expect(findings[1]?.message).toContain('undefined_global');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('a clean cart (no warning lines) parses to zero findings, not an error', () => {
|
|
88
|
+
expect(parseLint('').findings).toEqual([]);
|
|
89
|
+
expect(parseLint('Lint warnings:\n').findings).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('picopilot lint: present', () => {
|
|
94
|
+
it('reports findings as data (exit 0) and CTAs when there are warnings', async () => {
|
|
95
|
+
const {stdout, exitCode} = await runLint(() =>
|
|
96
|
+
stubAdapter({
|
|
97
|
+
ok: true,
|
|
98
|
+
value: {findings: [{line: 5, col: 8, message: "Local 'x' isn't used"}]},
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
// Findings are DATA, not a failure exit.
|
|
102
|
+
expect(exitCode).toBe(0);
|
|
103
|
+
const out = JSON.parse(stdout);
|
|
104
|
+
expect(out.clean).toBe(false);
|
|
105
|
+
expect(out.count).toBe(1);
|
|
106
|
+
expect(out.findings[0].message).toContain('x');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('a clean cart reports clean=true and no CTA', async () => {
|
|
110
|
+
const {stdout, exitCode} = await runLint(() =>
|
|
111
|
+
stubAdapter({ok: true, value: {findings: []}}),
|
|
112
|
+
);
|
|
113
|
+
expect(exitCode).toBe(0);
|
|
114
|
+
const out = JSON.parse(stdout);
|
|
115
|
+
expect(out.clean).toBe(true);
|
|
116
|
+
expect(out.count).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('picopilot lint: shrinko absent + cart missing', () => {
|
|
121
|
+
it('returns structured shrinko-not-found + nonzero exit', async () => {
|
|
122
|
+
const {stdout, exitCode} = await runLint(() =>
|
|
123
|
+
stubAdapter(shrinkoNotFound()),
|
|
124
|
+
);
|
|
125
|
+
expect(exitCode).not.toBe(0);
|
|
126
|
+
expect(stdout).toContain('uv pip install shrinko');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('cart-not-found is a distinct picopilot-side error', async () => {
|
|
130
|
+
const {stdout, exitCode} = await runLint(
|
|
131
|
+
() => stubAdapter({ok: true, value: {findings: []}}),
|
|
132
|
+
['lint', join(dir, 'nope.p8'), '--json'],
|
|
133
|
+
);
|
|
134
|
+
expect(exitCode).not.toBe(0);
|
|
135
|
+
expect(stdout).toContain('no cart at');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {existsSync} from 'node:fs';
|
|
2
|
+
import {isAbsolute, resolve} from 'node:path';
|
|
3
|
+
import {type Cli, z} from 'incur';
|
|
4
|
+
import {
|
|
5
|
+
type ShrinkoAdapter,
|
|
6
|
+
ShellShrinkoAdapter,
|
|
7
|
+
} from '../engine/shrinko/index.js';
|
|
8
|
+
|
|
9
|
+
/** See {@link import('./tokens.js').ShrinkoAdapterFactory}: lint reuses the same seam. */
|
|
10
|
+
export type ShrinkoAdapterFactory = (env: NodeJS.ProcessEnv) => ShrinkoAdapter;
|
|
11
|
+
|
|
12
|
+
const defaultAdapterFactory: ShrinkoAdapterFactory = (env) =>
|
|
13
|
+
new ShellShrinkoAdapter({env});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers `picopilot lint` (US #4): surface PICO-8-specific issues (undefined
|
|
17
|
+
* globals, unused/duplicate locals, and, via the parsed findings, the classic
|
|
18
|
+
* gotchas) as STRUCTURED findings via shrinko8 `--lint`, so an agent catches
|
|
19
|
+
* them before running. Findings are DATA: a cart WITH warnings is a successful
|
|
20
|
+
* lint that reports them (exit 0), not a failure exit; only a broken cart or a
|
|
21
|
+
* missing dependency is an error.
|
|
22
|
+
*
|
|
23
|
+
* shrinko-backed: absent shrinko returns the structured `shrinko-not-found`
|
|
24
|
+
* error + nonzero exit (ADR-0002), mirroring `tokens`.
|
|
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 function registerLint(
|
|
30
|
+
cli: Cli.Cli,
|
|
31
|
+
adapterFactory: ShrinkoAdapterFactory = defaultAdapterFactory,
|
|
32
|
+
): void {
|
|
33
|
+
cli.command('lint', {
|
|
34
|
+
description:
|
|
35
|
+
'Surface PICO-8 lint findings (undefined globals, unused/duplicate locals) via shrinko8. Requires shrinko.',
|
|
36
|
+
args: z.object({
|
|
37
|
+
cart: z
|
|
38
|
+
.string()
|
|
39
|
+
.default('main.p8')
|
|
40
|
+
.describe(
|
|
41
|
+
'The .p8 cart to lint. Defaults to main.p8 in the current folder.',
|
|
42
|
+
),
|
|
43
|
+
}),
|
|
44
|
+
env: z.object({
|
|
45
|
+
PATH: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Used to locate shrinko8 in the child process.'),
|
|
49
|
+
}),
|
|
50
|
+
output: z.object({
|
|
51
|
+
findings: z
|
|
52
|
+
.array(
|
|
53
|
+
z.object({
|
|
54
|
+
line: z.number().describe('1-based line in the cart.'),
|
|
55
|
+
col: z.number().describe('1-based column.'),
|
|
56
|
+
message: z.string().describe('The lint warning text.'),
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
.describe(
|
|
60
|
+
'Every lint warning shrinko reported (empty when the cart is clean).',
|
|
61
|
+
),
|
|
62
|
+
clean: z.boolean().describe('True when there are no findings.'),
|
|
63
|
+
count: z.number().describe('Number of findings.'),
|
|
64
|
+
}),
|
|
65
|
+
examples: [
|
|
66
|
+
{description: 'Lint main.p8'},
|
|
67
|
+
{description: 'Lint a specific cart', args: {cart: 'game.p8'}},
|
|
68
|
+
],
|
|
69
|
+
async run({args, env, error, ok}) {
|
|
70
|
+
const cartPath = isAbsolute(args.cart)
|
|
71
|
+
? args.cart
|
|
72
|
+
: resolve(process.cwd(), args.cart);
|
|
73
|
+
|
|
74
|
+
if (!existsSync(cartPath)) {
|
|
75
|
+
return error({
|
|
76
|
+
code: 'cart-not-found',
|
|
77
|
+
message: `no cart at ${cartPath}`,
|
|
78
|
+
exitCode: 1,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const adapter = adapterFactory(env as NodeJS.ProcessEnv);
|
|
83
|
+
const result = await adapter.lint(cartPath);
|
|
84
|
+
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
return error({
|
|
87
|
+
code: result.reason,
|
|
88
|
+
message: `shrinko is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
89
|
+
exitCode: 1,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const {findings} = result.value;
|
|
94
|
+
const clean = findings.length === 0;
|
|
95
|
+
|
|
96
|
+
// Findings are DATA, not a failure exit (an agent reads them and fixes the
|
|
97
|
+
// code). When there ARE findings, CTA toward the run/verify loop.
|
|
98
|
+
const cta = clean
|
|
99
|
+
? undefined
|
|
100
|
+
: {
|
|
101
|
+
description: `${findings.length} lint finding(s). Fix them, then re-check:`,
|
|
102
|
+
commands: [
|
|
103
|
+
{command: 'lint', description: 'Re-lint after fixing.'},
|
|
104
|
+
{
|
|
105
|
+
command: 'verify',
|
|
106
|
+
description: 'Run the static gate before running.',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return ok(
|
|
112
|
+
{findings: [...findings], clean, count: findings.length},
|
|
113
|
+
cta === undefined ? undefined : {cta},
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {execFileSync} from 'node:child_process';
|
|
2
|
+
import {existsSync, mkdtempSync, readFileSync, writeFileSync} from 'node:fs';
|
|
3
|
+
import {tmpdir} from 'node:os';
|
|
4
|
+
import {join} from 'node:path';
|
|
5
|
+
import {Cli} from 'incur';
|
|
6
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
7
|
+
import type {
|
|
8
|
+
MinifyReport,
|
|
9
|
+
ShrinkoAdapter,
|
|
10
|
+
ShrinkoResult,
|
|
11
|
+
} from '../engine/shrinko/index.js';
|
|
12
|
+
import {shrinkoNotFound} from '../engine/shrinko/index.js';
|
|
13
|
+
import {type ShrinkoAdapterFactory, registerMinify} from './minify.js';
|
|
14
|
+
|
|
15
|
+
const CART_TEXT =
|
|
16
|
+
'pico-8 cartridge // http://www.pico-8.com\nversion 42\n__lua__\nprint("hi")\n';
|
|
17
|
+
|
|
18
|
+
let dir: string;
|
|
19
|
+
let cartPath: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
dir = mkdtempSync(join(tmpdir(), 'picopilot-minify-'));
|
|
23
|
+
cartPath = join(dir, 'main.p8');
|
|
24
|
+
writeFileSync(cartPath, CART_TEXT);
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
try {
|
|
28
|
+
execFileSync('rm', ['-rf', dir]);
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A stub adapter whose `minify` records the outPath it was handed, WRITES a fake
|
|
36
|
+
* minified cart there (so the command's on-disk assertions are real), and
|
|
37
|
+
* returns a wired token delta. `count`/`lint` unused.
|
|
38
|
+
*/
|
|
39
|
+
function stubAdapter(
|
|
40
|
+
result: ShrinkoResult<MinifyReport> | 'write',
|
|
41
|
+
seen?: {outPath?: string},
|
|
42
|
+
): ShrinkoAdapter {
|
|
43
|
+
return {
|
|
44
|
+
async count() {
|
|
45
|
+
throw new Error('not used');
|
|
46
|
+
},
|
|
47
|
+
async lint() {
|
|
48
|
+
throw new Error('not used');
|
|
49
|
+
},
|
|
50
|
+
async minify(_cart, outPath) {
|
|
51
|
+
if (seen !== undefined) seen.outPath = outPath;
|
|
52
|
+
if (result === 'write') {
|
|
53
|
+
writeFileSync(outPath, '-- minified\n');
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
value: {beforeTokens: 100, afterTokens: 60, outPath},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runMinify(
|
|
65
|
+
factory: ShrinkoAdapterFactory,
|
|
66
|
+
argv: string[] = ['minify', cartPath, '--json'],
|
|
67
|
+
env: Record<string, string | undefined> = {PATH: '/x'},
|
|
68
|
+
) {
|
|
69
|
+
const cli = Cli.create('picopilot', {version: '0.0.0'});
|
|
70
|
+
registerMinify(cli, factory);
|
|
71
|
+
let stdout = '';
|
|
72
|
+
let exitCode = 0;
|
|
73
|
+
await cli.serve(argv, {
|
|
74
|
+
stdout(s) {
|
|
75
|
+
stdout += s;
|
|
76
|
+
},
|
|
77
|
+
exit(code) {
|
|
78
|
+
exitCode = code;
|
|
79
|
+
},
|
|
80
|
+
env,
|
|
81
|
+
});
|
|
82
|
+
return {stdout, exitCode};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe('picopilot minify: compiler-style (ADR-0007)', () => {
|
|
86
|
+
it('writes <name>.min.p8 by default and reports the token delta', async () => {
|
|
87
|
+
const {stdout, exitCode} = await runMinify(() => stubAdapter('write'));
|
|
88
|
+
expect(exitCode).toBe(0);
|
|
89
|
+
const out = JSON.parse(stdout);
|
|
90
|
+
expect(out.outPath).toBe(join(dir, 'main.min.p8'));
|
|
91
|
+
expect(out.beforeTokens).toBe(100);
|
|
92
|
+
expect(out.afterTokens).toBe(60);
|
|
93
|
+
expect(out.saved).toBe(40);
|
|
94
|
+
expect(existsSync(join(dir, 'main.min.p8'))).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('NEVER mutates the source cart (the compiler invariant)', async () => {
|
|
98
|
+
const before = readFileSync(cartPath, 'utf8');
|
|
99
|
+
await runMinify(() => stubAdapter('write'));
|
|
100
|
+
expect(readFileSync(cartPath, 'utf8')).toBe(before); // source byte-identical
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('honours an explicit --out path', async () => {
|
|
104
|
+
const seen: {outPath?: string} = {};
|
|
105
|
+
const chosen = join(dir, 'dist', 'game.p8');
|
|
106
|
+
await runMinify(
|
|
107
|
+
() => stubAdapter('write', seen),
|
|
108
|
+
['minify', cartPath, '--out', chosen, '--json'],
|
|
109
|
+
);
|
|
110
|
+
expect(seen.outPath).toBe(chosen);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('picopilot minify: no-clobber + source guard', () => {
|
|
115
|
+
it('refuses to overwrite an existing output without --force', async () => {
|
|
116
|
+
writeFileSync(join(dir, 'main.min.p8'), 'existing artifact');
|
|
117
|
+
const {stdout, exitCode} = await runMinify(() => stubAdapter('write'));
|
|
118
|
+
expect(exitCode).not.toBe(0);
|
|
119
|
+
expect(stdout.toLowerCase()).toContain('refusing to overwrite');
|
|
120
|
+
// The existing artifact is untouched (the adapter never ran).
|
|
121
|
+
expect(readFileSync(join(dir, 'main.min.p8'), 'utf8')).toBe(
|
|
122
|
+
'existing artifact',
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('--force overwrites an existing output', async () => {
|
|
127
|
+
writeFileSync(join(dir, 'main.min.p8'), 'old');
|
|
128
|
+
const {exitCode} = await runMinify(
|
|
129
|
+
() => stubAdapter('write'),
|
|
130
|
+
['minify', cartPath, '--force', '--json'],
|
|
131
|
+
);
|
|
132
|
+
expect(exitCode).toBe(0);
|
|
133
|
+
expect(readFileSync(join(dir, 'main.min.p8'), 'utf8')).toContain(
|
|
134
|
+
'minified',
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('refuses --out pointing AT the source (no in-place mutation)', async () => {
|
|
139
|
+
const {stdout, exitCode} = await runMinify(
|
|
140
|
+
() => stubAdapter('write'),
|
|
141
|
+
['minify', cartPath, '--out', cartPath, '--json'],
|
|
142
|
+
);
|
|
143
|
+
expect(exitCode).not.toBe(0);
|
|
144
|
+
expect(stdout.toLowerCase()).toContain('must differ from the source');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('picopilot minify: shrinko absent + cart missing', () => {
|
|
149
|
+
it('returns structured shrinko-not-found (and writes nothing)', async () => {
|
|
150
|
+
const {stdout, exitCode} = await runMinify(() =>
|
|
151
|
+
stubAdapter(shrinkoNotFound()),
|
|
152
|
+
);
|
|
153
|
+
expect(exitCode).not.toBe(0);
|
|
154
|
+
expect(stdout).toContain('uv pip install shrinko');
|
|
155
|
+
expect(existsSync(join(dir, 'main.min.p8'))).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('cart-not-found is a distinct picopilot-side error', async () => {
|
|
159
|
+
const {stdout, exitCode} = await runMinify(
|
|
160
|
+
() => stubAdapter('write'),
|
|
161
|
+
['minify', join(dir, 'nope.p8'), '--json'],
|
|
162
|
+
);
|
|
163
|
+
expect(exitCode).not.toBe(0);
|
|
164
|
+
expect(stdout).toContain('no cart at');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {existsSync} from 'node:fs';
|
|
2
|
+
import {dirname, isAbsolute, join, resolve} from 'node:path';
|
|
3
|
+
import {type Cli, z} from 'incur';
|
|
4
|
+
import {
|
|
5
|
+
type ShrinkoAdapter,
|
|
6
|
+
ShellShrinkoAdapter,
|
|
7
|
+
} from '../engine/shrinko/index.js';
|
|
8
|
+
|
|
9
|
+
/** See {@link import('./tokens.js').ShrinkoAdapterFactory}: minify reuses the same seam. */
|
|
10
|
+
export type ShrinkoAdapterFactory = (env: NodeJS.ProcessEnv) => ShrinkoAdapter;
|
|
11
|
+
|
|
12
|
+
const defaultAdapterFactory: ShrinkoAdapterFactory = (env) =>
|
|
13
|
+
new ShellShrinkoAdapter({env});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Derives the default output path `<name>.min.p8` next to the source cart:
|
|
17
|
+
* `main.p8` -> `main.min.p8`, `game.p8.png` -> `game.min.p8.png` is NOT the
|
|
18
|
+
* case (we only special-case the final `.p8`); a plain `<name>.p8` becomes
|
|
19
|
+
* `<name>.min.p8`. Anything without a `.p8` suffix just gets `.min.p8` appended.
|
|
20
|
+
*/
|
|
21
|
+
function defaultOutPath(cartPath: string): string {
|
|
22
|
+
const dir = dirname(cartPath);
|
|
23
|
+
const base = cartPath.slice(dir.length + 1);
|
|
24
|
+
const out = base.endsWith('.p8')
|
|
25
|
+
? `${base.slice(0, -'.p8'.length)}.min.p8`
|
|
26
|
+
: `${base}.min.p8`;
|
|
27
|
+
return join(dir, out);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registers `picopilot minify` (US #5, ADR-0007): run shrinko8's SAFE
|
|
32
|
+
* minification and write the result to a SEPARATE artifact (`<name>.min.p8` by
|
|
33
|
+
* default), reporting the before/after token delta. It is COMPILER-STYLE: the
|
|
34
|
+
* user's authored source (`main.p8` / the `#include`d `main.lua`) is NEVER
|
|
35
|
+
* touched, and it refuses to clobber an existing output unless `--force`.
|
|
36
|
+
*
|
|
37
|
+
* Safe-only by default (never aggressive-minify silently). shrinko-backed:
|
|
38
|
+
* absent shrinko returns the structured `shrinko-not-found` error + nonzero
|
|
39
|
+
* exit, mirroring `tokens`/`lint`.
|
|
40
|
+
*
|
|
41
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
42
|
+
* tests pass a stub to drive present/absent without the real binary.
|
|
43
|
+
*/
|
|
44
|
+
export function registerMinify(
|
|
45
|
+
cli: Cli.Cli,
|
|
46
|
+
adapterFactory: ShrinkoAdapterFactory = defaultAdapterFactory,
|
|
47
|
+
): void {
|
|
48
|
+
cli.command('minify', {
|
|
49
|
+
description:
|
|
50
|
+
'Safe-minify a cart to a SEPARATE artifact (<name>.min.p8), reporting the token delta. Source untouched. Requires shrinko.',
|
|
51
|
+
args: z.object({
|
|
52
|
+
cart: z
|
|
53
|
+
.string()
|
|
54
|
+
.default('main.p8')
|
|
55
|
+
.describe(
|
|
56
|
+
'The .p8 cart to minify. Defaults to main.p8 in the current folder.',
|
|
57
|
+
),
|
|
58
|
+
}),
|
|
59
|
+
options: z.object({
|
|
60
|
+
out: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe(
|
|
64
|
+
'Output cart path. Defaults to <name>.min.p8 next to the source.',
|
|
65
|
+
),
|
|
66
|
+
force: z
|
|
67
|
+
.boolean()
|
|
68
|
+
.default(false)
|
|
69
|
+
.describe(
|
|
70
|
+
'Overwrite an existing output cart. Default off (no-clobber).',
|
|
71
|
+
),
|
|
72
|
+
}),
|
|
73
|
+
env: z.object({
|
|
74
|
+
PATH: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Used to locate shrinko8 in the child process.'),
|
|
78
|
+
}),
|
|
79
|
+
output: z.object({
|
|
80
|
+
outPath: z
|
|
81
|
+
.string()
|
|
82
|
+
.describe('Absolute path of the minified cart written.'),
|
|
83
|
+
beforeTokens: z.number().describe('Tokens in the source cart.'),
|
|
84
|
+
afterTokens: z.number().describe('Tokens in the minified cart.'),
|
|
85
|
+
saved: z.number().describe('Tokens reclaimed (before - after).'),
|
|
86
|
+
}),
|
|
87
|
+
examples: [
|
|
88
|
+
{description: 'Minify main.p8 to main.min.p8'},
|
|
89
|
+
{
|
|
90
|
+
description: 'Minify to a chosen path',
|
|
91
|
+
args: {cart: 'game.p8'},
|
|
92
|
+
options: {out: 'dist/game.p8'},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
async run({args, options, env, error, ok}) {
|
|
96
|
+
const cartPath = isAbsolute(args.cart)
|
|
97
|
+
? args.cart
|
|
98
|
+
: resolve(process.cwd(), args.cart);
|
|
99
|
+
|
|
100
|
+
if (!existsSync(cartPath)) {
|
|
101
|
+
return error({
|
|
102
|
+
code: 'cart-not-found',
|
|
103
|
+
message: `no cart at ${cartPath}`,
|
|
104
|
+
exitCode: 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const outPath =
|
|
109
|
+
options.out !== undefined
|
|
110
|
+
? isAbsolute(options.out)
|
|
111
|
+
? options.out
|
|
112
|
+
: resolve(process.cwd(), options.out)
|
|
113
|
+
: defaultOutPath(cartPath);
|
|
114
|
+
|
|
115
|
+
// Guard the source FIRST: never let --out point AT the source cart (that
|
|
116
|
+
// would be in-place mutation, which ADR-0007 forbids). Checked before the
|
|
117
|
+
// no-clobber guard so it wins even though the source obviously exists.
|
|
118
|
+
if (outPath === cartPath) {
|
|
119
|
+
return error({
|
|
120
|
+
code: 'output-is-source',
|
|
121
|
+
message: `--out must differ from the source cart (${cartPath}); minify never mutates the source`,
|
|
122
|
+
exitCode: 1,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// No-clobber (ADR-0007, mirrors init): refuse to overwrite an existing
|
|
127
|
+
// artifact unless --force, so a minify never silently destroys a prior one.
|
|
128
|
+
if (existsSync(outPath) && !options.force) {
|
|
129
|
+
return error({
|
|
130
|
+
code: 'output-exists',
|
|
131
|
+
message: `refusing to overwrite existing ${outPath}; pass --force to overwrite`,
|
|
132
|
+
exitCode: 1,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const adapter = adapterFactory(env as NodeJS.ProcessEnv);
|
|
137
|
+
const result = await adapter.minify(cartPath, outPath);
|
|
138
|
+
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
return error({
|
|
141
|
+
code: result.reason,
|
|
142
|
+
message: `shrinko is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
143
|
+
exitCode: 1,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const {beforeTokens, afterTokens} = result.value;
|
|
148
|
+
return ok({
|
|
149
|
+
outPath,
|
|
150
|
+
beforeTokens,
|
|
151
|
+
afterTokens,
|
|
152
|
+
saved: beforeTokens - afterTokens,
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|