Skip to content

Commit af8ffc9

Browse files
committed
showreel v1.1.6 — shot.mjs entry guard + CLI parse test coverage
shot.mjs called main() unconditionally — alone among the scripts it had no import.meta.url entry guard, so importing it (to reuse parse, or from a test) ran the CLI against the importer's argv (printed usage, called process.exit). Wrapped main() in the standard guard; the CLI entry is unchanged. Exported parse from shot/compose and added argument-parsing tests for shot, compose and tape — the CLI parsers that had no dedicated coverage. The shot bug surfaced precisely because the new test imported it. 547 tests.
1 parent 45a4059 commit af8ffc9

6 files changed

Lines changed: 75 additions & 6 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
},
66
"metadata": {
77
"description": "Personal marketplace hosting the Showreel plugin",
8-
"version": "1.1.5"
8+
"version": "1.1.6"
99
},
1010
"plugins": [
1111
{
1212
"name": "showreel",
1313
"source": "./showreel",
1414
"description": "Your UI, on its showreel \u2014 annotated screenshots, feature demos, cinematic flow recordings (gif/mp4), terminal captures and before/after composites in one command each. Self-contained Chromium motor, deterministic placement, self-validated output; no MCP, cheap on tokens.",
15-
"version": "1.1.5"
15+
"version": "1.1.6"
1616
}
1717
]
1818
}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ Showreel turns "show me" into a finished visual — annotated screenshots, isola
99
demos, flow GIFs, terminal recordings and before/after composites — driven entirely
1010
by CSS selectors and JSON steps.
1111

12+
## [1.1.6] — 2026-06-24
13+
14+
### Fixed
15+
- **`shot.mjs` ran `main()` on import.** Unlike every other script it had no
16+
entry-point guard, so importing it (to reuse `parse`, or from a test) executed
17+
the CLI against the importer's argv — printing usage and calling `process.exit`.
18+
Wrapped `main()` in the standard `import.meta.url` guard.
19+
20+
### Tests
21+
- Exported `parse` from `shot`/`compose` and added argument-parsing tests for
22+
`shot`, `compose` and `tape` — the CLI parsers that had no dedicated coverage.
23+
547 tests.
24+
1225
## [1.1.5] — 2026-06-24
1326

1427
### Fixed
@@ -161,6 +174,7 @@ First stable release — the full capture motor.
161174
offline two implementations of the same time contract. 530 tests, no network or
162175
browser needed.
163176

177+
[1.1.6]: https://github.com/HeyRenan/showreel/compare/v1.1.5...v1.1.6
164178
[1.1.5]: https://github.com/HeyRenan/showreel/compare/v1.1.4...v1.1.5
165179
[1.1.4]: https://github.com/HeyRenan/showreel/compare/v1.1.3...v1.1.4
166180
[1.1.3]: https://github.com/HeyRenan/showreel/compare/v1.1.2...v1.1.3

showreel/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "showreel",
3-
"version": "1.1.5",
3+
"version": "1.1.6",
44
"description": "Your UI, on its showreel \u2014 annotated screenshots, feature demos, cinematic flow recordings (gif/mp4), terminal captures and before/after composites in one command each. Self-contained Chromium motor, deterministic placement, self-validated output; no MCP, cheap on tokens.",
55
"author": {
66
"name": "Renan"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// cli-parse.test.mjs — argument-parsing guards for the CLI tools that lacked a
2+
// dedicated parse test (shot, compose, tape). Locks in the behaviour intensive
3+
// testing exercised by hand: positional mapping, numeric/string flag validation,
4+
// unknown-flag rejection, and the surplus-positional guard.
5+
6+
import { test } from 'node:test';
7+
import assert from 'node:assert/strict';
8+
import { parse as shotParse } from '../shot.mjs';
9+
import { parse as composeParse } from '../compose.mjs';
10+
import { parseArgs as tapeParse } from '../tape.mjs';
11+
12+
test('shot: positionals map to url/selector/out; flags parse', () => {
13+
const a = shotParse(['http://x/', '.card', 'out.png', '--width', '1440', '--pad', '8']);
14+
assert.equal(a.url, 'http://x/');
15+
assert.equal(a.selector, '.card');
16+
assert.equal(a.out, 'out.png');
17+
assert.equal(a.width, 1440);
18+
assert.equal(a.pad, 8);
19+
});
20+
21+
test('shot: bad numeric flag, unknown flag, and surplus positionals all throw', () => {
22+
assert.throws(() => shotParse(['u', 's', 'o', '--width', 'abc']), /--width must be a number/);
23+
assert.throws(() => shotParse(['u', 's', 'o', '--nope']), /shot: unknown arg --nope/);
24+
assert.throws(() => shotParse(['u', 's', 'o', 'extra']), /too many positional/);
25+
});
26+
27+
test('compose: positionals map to aPng/bPng/out; flags parse', () => {
28+
const a = composeParse(['a.png', 'b.png', 'out.png', '--labels', 'Before,After', '--gap', '12']);
29+
assert.equal(a.aPng, 'a.png');
30+
assert.equal(a.bPng, 'b.png');
31+
assert.equal(a.out, 'out.png');
32+
assert.equal(a.labels, 'Before,After');
33+
assert.equal(a.gap, 12);
34+
});
35+
36+
test('compose: unknown flag, surplus positionals, and a flag-as-value all throw', () => {
37+
assert.throws(() => composeParse(['a', 'b', 'o', '--frob']), /compose: unknown arg --frob/);
38+
assert.throws(() => composeParse(['a', 'b', 'o', 'x']), /too many positional/);
39+
assert.throws(() => composeParse(['a', 'b', 'o', '--labels', '--gap']), /--labels needs a value/);
40+
});
41+
42+
test('tape: positionals collected; numeric + string flags parse', () => {
43+
const a = tapeParse(['out.gif', '--width', '900', '--steps-json', '[]', '--theme', 'dark']);
44+
assert.deepEqual(a.positional, ['out.gif']);
45+
assert.equal(a.width, 900);
46+
assert.equal(a.stepsJson, '[]');
47+
assert.equal(a.theme, 'dark');
48+
});
49+
50+
test('tape: unknown flag and a non-integer width throw', () => {
51+
assert.throws(() => tapeParse(['o.gif', '--bogus']), /tape: unknown arg --bogus/);
52+
assert.throws(() => tapeParse(['o.gif', '--width', '12.5']), /--width must be a whole number/);
53+
});

showreel/scripts/compose.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ async function composeGifs(a, labels) {
109109
console.log('OK ' + a.out);
110110
}
111111

112-
function parse(argv) {
112+
export function parse(argv) {
113113
const a = { gap: 24, pad: 28, labelH: 44 };
114114
const pos = [];
115115
for (let i = 0; i < argv.length; i++) {

showreel/scripts/shot.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { writeFileSync } from 'node:fs';
77
import { Browser } from '../lib/browser.mjs';
88
import { num } from './cli-args.mjs';
99

10-
function parse(argv) {
10+
export function parse(argv) {
1111
const a = { width: 900, height: 1400, dpr: 1, pad: 16 };
1212
const pos = [];
1313
for (let i = 0; i < argv.length; i++) {
@@ -56,4 +56,6 @@ async function main() {
5656
}
5757
}
5858

59-
main().catch((e) => { console.error(String(e.message || e)); process.exit(1); });
59+
if (import.meta.url === `file://${process.argv[1]}`) {
60+
main().catch((e) => { console.error(String(e.message || e)); process.exit(1); });
61+
}

0 commit comments

Comments
 (0)