svg-terminal 1.0.0 → 1.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/README.md +7 -6
- package/dist/{chunk-IVINEQLU.js → chunk-DVACBVLX.js} +167 -28
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Generate animated SVG terminals from a declarative YAML config. The output is a
|
|
|
18
18
|
```bash
|
|
19
19
|
npx svg-terminal init # writes terminal.yml
|
|
20
20
|
npx svg-terminal generate # writes terminal.svg
|
|
21
|
-
npx svg-terminal blocks # lists all
|
|
21
|
+
npx svg-terminal blocks # lists all 48 blocks
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Or as a GitHub Action — refresh your profile README on a schedule:
|
|
@@ -31,14 +31,14 @@ Or as a GitHub Action — refresh your profile README on a schedule:
|
|
|
31
31
|
commit: true
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
See the full [GitHub Action](#github-action) section below, the [block catalog](./examples/blocks/) (
|
|
34
|
+
See the full [GitHub Action](#github-action) section below, the [block catalog](./examples/blocks/) (48 blocks, one preview each), and the [12-theme gallery](#themes).
|
|
35
35
|
|
|
36
36
|
### What's in the box
|
|
37
37
|
|
|
38
38
|
- **Declarative YAML config** — write blocks, pick a theme, run the CLI
|
|
39
|
-
- **
|
|
39
|
+
- **48 built-in blocks** — across identity, retro / fake-system, status, ASCII art, single- and multi-line animation, and humor categories. Browse the [block catalog](./examples/blocks/) for previews of each
|
|
40
40
|
- **12 built-in themes** — dracula, nord, monokai, amber, green-phosphor, cyberpunk, solarized-dark, win95, catppuccin, tokyo-night, gruvbox, high-contrast (with chrome to match)
|
|
41
|
-
- **
|
|
41
|
+
- **Frame animation** — `BlockResult.animation = { frames, fps, loop }` powers the 10 animated blocks (spinners, clock, dice, progress bar, etc.). Frames may be single- **or multi-line** as of #69 (`jumping-jack` is the reference multi-line block)
|
|
42
42
|
- **Dynamic-block cache** — the 5 cacheable blocks (weather, github-stats, github-languages, quote, fun-fact) write to `.svg-terminal-cache.json`. Pair with `--frozen-cache` for offline CI builds
|
|
43
43
|
- **Reduced-motion respected** — `@media (prefers-reduced-motion)` clamps the CSS fade-ins AND (since v0.17) the frame cycle. SMIL-driven typing reveal, cursor walk, and scroll-on-overflow remain animated; pair with `--static` for full stillness
|
|
44
44
|
- **Schema-validated, XSS-safe** — strict zod schema on every config field; user-controllable values are escaped at SVG emit sites. See [SECURITY.md](./SECURITY.md)
|
|
@@ -125,7 +125,7 @@ Each is the same 2-block config (motd + neofetch) rendered against the named the
|
|
|
125
125
|
|
|
126
126
|
## Blocks
|
|
127
127
|
|
|
128
|
-
Run `svg-terminal blocks` to list all
|
|
128
|
+
Run `svg-terminal blocks` to list all 48 (cacheable ones marked `*`), or `svg-terminal blocks <name>` to print one block's config schema directly without grepping the source.
|
|
129
129
|
|
|
130
130
|
| Block | Description |
|
|
131
131
|
|-------|-------------|
|
|
@@ -172,6 +172,7 @@ Run `svg-terminal blocks` to list all 47 (cacheable ones marked `*`), or `svg-te
|
|
|
172
172
|
| `progress-bar` | Fake build progress bar that fills 0% → 100% |
|
|
173
173
|
| `bouncing-dot` | Single glyph bouncing left ↔ right |
|
|
174
174
|
| `dice-roll` | N d6 dice that tumble and land on a result |
|
|
175
|
+
| `jumping-jack` | Multi-line stick figure doing jumping jacks (reference multi-line animation) |
|
|
175
176
|
| `palette-swatch` | One-line render of all 16 theme palette colors |
|
|
176
177
|
| `semver-bump` | Current semver + bump preview (major/minor/patch) |
|
|
177
178
|
| `ascii-calendar` | Current-month calendar grid with today highlighted |
|
|
@@ -230,7 +231,7 @@ accessibility:
|
|
|
230
231
|
describe: false # default true — emit <desc> with full content
|
|
231
232
|
```
|
|
232
233
|
|
|
233
|
-
**Reduced-motion caveat.** The SVG emits an inline `@media (prefers-reduced-motion: reduce)` rule,
|
|
234
|
+
**Reduced-motion caveat.** The SVG emits an inline `@media (prefers-reduced-motion: reduce)` rule, which applies to CSS animations — the fade-ins and the frame cycle (single- and multi-line) honor it (migrated SMIL → CSS in v0.17). The remaining SMIL holdouts — typing reveal, cursor walk, and scroll-on-overflow — don't read the same CSS media query, so users who set the OS-level reduced-motion preference still see those animate. If that's a problem for your audience, generate with `--static` — same content, no motion at all.
|
|
234
235
|
|
|
235
236
|
### Caching API responses
|
|
236
237
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z51 } from "zod";
|
|
3
3
|
|
|
4
4
|
// src/core/config.ts
|
|
5
5
|
import { readFileSync } from "fs";
|
|
@@ -1284,22 +1284,39 @@ function buildRevealClip(clipId, startX, charWidth, charCount, fontSize, startTi
|
|
|
1284
1284
|
const finalWidth = roundCoord(charCount * charWidth);
|
|
1285
1285
|
return `<defs><clipPath id="${clipId}"><rect x="${startX}" y="${-fontSize}" width="${finalWidth}" height="${fontSize * 2}">${setHold("width", 0, startTime)}<animate attributeName="width" values="${values.join(";")}" keyTimes="${keyTimes.join(";")}" calcMode="discrete" begin="${startTime}ms" dur="${typingDuration}ms" fill="freeze"/></rect></clipPath></defs>`;
|
|
1286
1286
|
}
|
|
1287
|
-
function generateAnimatedOutputLine(y, frames, color, startTime, colorMap, chrome, fps, loop) {
|
|
1287
|
+
function generateAnimatedOutputLine(y, frames, color, startTime, colorMap, chrome, fps, loop, lineHeight) {
|
|
1288
1288
|
const n = frames.length;
|
|
1289
1289
|
const frameDurMs = 1e3 / fps;
|
|
1290
1290
|
const cycleMs = Math.round(n * frameDurMs);
|
|
1291
1291
|
const iter = loop ? "infinite" : "1";
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1292
|
+
const height = Math.max(1, ...frames.map((f) => f.length));
|
|
1293
|
+
const animFor = (i) => `animation: frame-cycle-${n} ${cycleMs}ms linear ${Math.round(i * frameDurMs)}ms ${iter}`;
|
|
1294
|
+
const renderRow = (row) => {
|
|
1295
|
+
const styled = hasMarkup(row);
|
|
1296
|
+
return {
|
|
1297
|
+
fill: styled ? "" : ` fill="${escapeXml(color)}"`,
|
|
1298
|
+
content: styled ? generateStyledText(parseMarkup(row, colorMap, color), color, chrome.dimOpacity) : escapeXml(row)
|
|
1299
|
+
};
|
|
1300
|
+
};
|
|
1301
|
+
let body;
|
|
1302
|
+
if (height === 1) {
|
|
1303
|
+
body = frames.map((frame, i) => {
|
|
1304
|
+
const { fill, content } = renderRow(frame[0] ?? "");
|
|
1305
|
+
return `<text class="tt frame-cycle-${n}"${fill} opacity="${i === 0 ? "1" : "0"}" style="${animFor(i)}">${content}</text>`;
|
|
1306
|
+
}).join("");
|
|
1307
|
+
} else {
|
|
1308
|
+
body = frames.map((frame, i) => {
|
|
1309
|
+
const rows = [];
|
|
1310
|
+
for (let r = 0; r < height; r++) {
|
|
1311
|
+
const { fill, content } = renderRow(frame[r] ?? "");
|
|
1312
|
+
rows.push(`<text y="${roundCoord(r * lineHeight)}"${fill}>${content}</text>`);
|
|
1313
|
+
}
|
|
1314
|
+
return `<g class="tt frame-cycle-${n}" opacity="${i === 0 ? "1" : "0"}" style="${animFor(i)}">${rows.join("")}</g>`;
|
|
1315
|
+
}).join("");
|
|
1316
|
+
}
|
|
1300
1317
|
return `
|
|
1301
1318
|
<g transform="translate(0, ${y})"${fadeInStyle(startTime)}>
|
|
1302
|
-
${
|
|
1319
|
+
${body}
|
|
1303
1320
|
</g>`;
|
|
1304
1321
|
}
|
|
1305
1322
|
function generateOutputLine(y, content, color, startTime, colorMap, chrome, pinWidth, fontSize) {
|
|
@@ -1351,7 +1368,8 @@ function generateAllLines(frames, terminal, lineHeight, colors, chrome, animatio
|
|
|
1351
1368
|
colorMap,
|
|
1352
1369
|
chromeConfig,
|
|
1353
1370
|
frame.framesFps ?? 4,
|
|
1354
|
-
frame.framesLoop ?? true
|
|
1371
|
+
frame.framesLoop ?? true,
|
|
1372
|
+
lineHeight
|
|
1355
1373
|
)
|
|
1356
1374
|
);
|
|
1357
1375
|
} else {
|
|
@@ -1375,11 +1393,16 @@ function generateAllLines(frames, terminal, lineHeight, colors, chrome, animatio
|
|
|
1375
1393
|
}
|
|
1376
1394
|
|
|
1377
1395
|
// src/core/svg-generator.ts
|
|
1396
|
+
function animationHeight(frames) {
|
|
1397
|
+
return Math.max(1, ...frames.map((f) => f.length));
|
|
1398
|
+
}
|
|
1378
1399
|
function countTotalLines(sequences) {
|
|
1379
1400
|
let total = 0;
|
|
1380
1401
|
for (const seq of sequences) {
|
|
1381
1402
|
if (seq.type === "command") {
|
|
1382
1403
|
total += 1;
|
|
1404
|
+
} else if (seq.frames && seq.frames.length > 0) {
|
|
1405
|
+
total += animationHeight(seq.frames);
|
|
1383
1406
|
} else {
|
|
1384
1407
|
total += seq.content.split("\n").length;
|
|
1385
1408
|
}
|
|
@@ -1625,12 +1648,14 @@ function createAnimationFrames(sequences, terminal, maxVisibleLines, scrollDurat
|
|
|
1625
1648
|
continue;
|
|
1626
1649
|
}
|
|
1627
1650
|
if (seq.frames && seq.frames.length > 0) {
|
|
1628
|
-
|
|
1651
|
+
const height = animationHeight(seq.frames);
|
|
1652
|
+
for (let r = 0; r < height; r++) buffer.push({ type: "output" });
|
|
1629
1653
|
frames.push({
|
|
1630
1654
|
time: currentTime,
|
|
1631
1655
|
type: "add-output",
|
|
1632
|
-
lineIndex: buffer.length -
|
|
1633
|
-
|
|
1656
|
+
lineIndex: buffer.length - height,
|
|
1657
|
+
// top row of the reserved band
|
|
1658
|
+
content: seq.frames[0].join("\n"),
|
|
1634
1659
|
// frame 0 acts as the static fallback
|
|
1635
1660
|
color: seq.color,
|
|
1636
1661
|
frames: seq.frames,
|
|
@@ -2170,7 +2195,63 @@ import { z as z7 } from "zod";
|
|
|
2170
2195
|
import { z as z6 } from "zod";
|
|
2171
2196
|
|
|
2172
2197
|
// src/core/http.ts
|
|
2198
|
+
import { isIP } from "net";
|
|
2173
2199
|
var DEFAULT_FETCH_TIMEOUT = 1e4;
|
|
2200
|
+
var MAX_REDIRECTS = 5;
|
|
2201
|
+
function ipv4Blocked(ip) {
|
|
2202
|
+
const parts = ip.split(".").map(Number);
|
|
2203
|
+
if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
|
|
2204
|
+
return false;
|
|
2205
|
+
}
|
|
2206
|
+
const [a, b] = parts;
|
|
2207
|
+
if (a === 0) return true;
|
|
2208
|
+
if (a === 10) return true;
|
|
2209
|
+
if (a === 127) return true;
|
|
2210
|
+
if (a === 169 && b === 254) return true;
|
|
2211
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
2212
|
+
if (a === 192 && b === 168) return true;
|
|
2213
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
2214
|
+
if (a === 255 && b === 255 && parts[2] === 255 && parts[3] === 255) return true;
|
|
2215
|
+
return false;
|
|
2216
|
+
}
|
|
2217
|
+
function ipv6Blocked(ip) {
|
|
2218
|
+
const v = ip.toLowerCase();
|
|
2219
|
+
if (v === "::1" || v === "::") return true;
|
|
2220
|
+
if (/^fe[89ab]/.test(v)) return true;
|
|
2221
|
+
if (/^f[cd]/.test(v)) return true;
|
|
2222
|
+
const dotted = v.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
2223
|
+
if (dotted) return ipv4Blocked(dotted[1]);
|
|
2224
|
+
const hex = v.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
2225
|
+
if (hex) {
|
|
2226
|
+
const hi = parseInt(hex[1], 16);
|
|
2227
|
+
const lo = parseInt(hex[2], 16);
|
|
2228
|
+
return ipv4Blocked(`${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`);
|
|
2229
|
+
}
|
|
2230
|
+
return false;
|
|
2231
|
+
}
|
|
2232
|
+
function isBlockedHost(hostname) {
|
|
2233
|
+
const host = hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
2234
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
2235
|
+
const kind = isIP(host);
|
|
2236
|
+
if (kind === 4) return ipv4Blocked(host);
|
|
2237
|
+
if (kind === 6) return ipv6Blocked(host);
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
function fetchBlockReason(url) {
|
|
2241
|
+
let parsed;
|
|
2242
|
+
try {
|
|
2243
|
+
parsed = new URL(url);
|
|
2244
|
+
} catch {
|
|
2245
|
+
return "unparseable URL";
|
|
2246
|
+
}
|
|
2247
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2248
|
+
return `unsupported scheme "${parsed.protocol}"`;
|
|
2249
|
+
}
|
|
2250
|
+
if (isBlockedHost(parsed.hostname)) {
|
|
2251
|
+
return "private / loopback / link-local address";
|
|
2252
|
+
}
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2174
2255
|
function safeUrlForLog(url) {
|
|
2175
2256
|
try {
|
|
2176
2257
|
const u = new URL(url);
|
|
@@ -2207,20 +2288,47 @@ async function readCappedText(response, url) {
|
|
|
2207
2288
|
}
|
|
2208
2289
|
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
2209
2290
|
}
|
|
2210
|
-
var USER_AGENT = `svg-terminal/${true ? "1.
|
|
2291
|
+
var USER_AGENT = `svg-terminal/${true ? "1.1.0" : "0.0.0-dev"}`;
|
|
2211
2292
|
async function fetchWithTimeout(url, timeoutMs = DEFAULT_FETCH_TIMEOUT) {
|
|
2293
|
+
const blocked = fetchBlockReason(url);
|
|
2294
|
+
if (blocked) {
|
|
2295
|
+
console.warn(`[svg-terminal] Refused to fetch ${safeUrlForLog(url)}: ${blocked}`);
|
|
2296
|
+
return null;
|
|
2297
|
+
}
|
|
2212
2298
|
const controller = new AbortController();
|
|
2213
2299
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2214
2300
|
try {
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2301
|
+
let currentUrl = url;
|
|
2302
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
2303
|
+
const response = await fetch(currentUrl, {
|
|
2304
|
+
signal: controller.signal,
|
|
2305
|
+
headers: { "User-Agent": USER_AGENT },
|
|
2306
|
+
redirect: "manual"
|
|
2307
|
+
});
|
|
2308
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("location")) {
|
|
2309
|
+
let next;
|
|
2310
|
+
try {
|
|
2311
|
+
next = new URL(response.headers.get("location"), currentUrl).toString();
|
|
2312
|
+
} catch {
|
|
2313
|
+
console.warn(`[svg-terminal] Bad redirect Location from ${safeUrlForLog(currentUrl)}`);
|
|
2314
|
+
return null;
|
|
2315
|
+
}
|
|
2316
|
+
const blockedHop = fetchBlockReason(next);
|
|
2317
|
+
if (blockedHop) {
|
|
2318
|
+
console.warn(`[svg-terminal] Refused redirect to ${safeUrlForLog(next)}: ${blockedHop}`);
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
currentUrl = next;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (!response.ok) {
|
|
2325
|
+
console.warn(`[svg-terminal] HTTP ${response.status} from ${safeUrlForLog(currentUrl)}`);
|
|
2326
|
+
return null;
|
|
2327
|
+
}
|
|
2328
|
+
return response;
|
|
2222
2329
|
}
|
|
2223
|
-
|
|
2330
|
+
console.warn(`[svg-terminal] Too many redirects (>${MAX_REDIRECTS}) fetching ${safeUrlForLog(url)}`);
|
|
2331
|
+
return null;
|
|
2224
2332
|
} catch (error) {
|
|
2225
2333
|
const message = error instanceof Error ? error.message : String(error);
|
|
2226
2334
|
if (message.includes("abort")) {
|
|
@@ -4339,6 +4447,34 @@ var tocBlock = {
|
|
|
4339
4447
|
}
|
|
4340
4448
|
};
|
|
4341
4449
|
|
|
4450
|
+
// src/blocks/jumping-jack.ts
|
|
4451
|
+
import { z as z50 } from "zod";
|
|
4452
|
+
var POSE_OPEN = ["\\o/", " | ", "/ \\"];
|
|
4453
|
+
var POSE_SHUT = [" o ", "/|\\", " | "];
|
|
4454
|
+
var jumpingJackSchema = z50.object({
|
|
4455
|
+
fps: z50.number().int().min(1).max(30).optional(),
|
|
4456
|
+
command: z50.string().optional(),
|
|
4457
|
+
color: z50.string().optional()
|
|
4458
|
+
}).strict();
|
|
4459
|
+
var jumpingJackBlock = {
|
|
4460
|
+
name: "jumping-jack",
|
|
4461
|
+
description: "A multi-line stick figure doing jumping jacks",
|
|
4462
|
+
configSchema: jumpingJackSchema,
|
|
4463
|
+
render(_context, config) {
|
|
4464
|
+
const fps = config["fps"] ?? 2;
|
|
4465
|
+
const command = config["command"] ?? "exercise";
|
|
4466
|
+
const color = config["color"] ?? "yellow";
|
|
4467
|
+
const paint = (rows) => rows.map((r) => `[[fg:${color}]]${r}[[/fg]]`);
|
|
4468
|
+
const frames = [paint(POSE_OPEN), paint(POSE_SHUT)];
|
|
4469
|
+
return {
|
|
4470
|
+
command,
|
|
4471
|
+
lines: frames[0],
|
|
4472
|
+
// static fallback (first pose, all 3 rows)
|
|
4473
|
+
animation: { frames, fps }
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
};
|
|
4477
|
+
|
|
4342
4478
|
// src/blocks/index.ts
|
|
4343
4479
|
function registerBuiltinBlocks() {
|
|
4344
4480
|
registerBlocks([
|
|
@@ -4388,7 +4524,8 @@ function registerBuiltinBlocks() {
|
|
|
4388
4524
|
paletteSwatchBlock,
|
|
4389
4525
|
semverBumpBlock,
|
|
4390
4526
|
asciiCalendarBlock,
|
|
4391
|
-
tocBlock
|
|
4527
|
+
tocBlock,
|
|
4528
|
+
jumpingJackBlock
|
|
4392
4529
|
]);
|
|
4393
4530
|
}
|
|
4394
4531
|
|
|
@@ -4404,7 +4541,7 @@ function validateBlockEntry(block, entry, index) {
|
|
|
4404
4541
|
try {
|
|
4405
4542
|
block.configSchema.parse(cfg);
|
|
4406
4543
|
} catch (err) {
|
|
4407
|
-
if (err instanceof
|
|
4544
|
+
if (err instanceof z51.ZodError) {
|
|
4408
4545
|
const issues = err.issues.map((i) => {
|
|
4409
4546
|
const path = i.path.length ? i.path.join(".") : "<root>";
|
|
4410
4547
|
return ` ${path}: ${i.message}`;
|
|
@@ -4470,7 +4607,9 @@ async function generate(userConfig, options = {}) {
|
|
|
4470
4607
|
pause: resolvePause(entry.pause ?? result.pause),
|
|
4471
4608
|
pinWidth: result.pinWidth,
|
|
4472
4609
|
...result.animation ? {
|
|
4473
|
-
frames
|
|
4610
|
+
// Carry frames as string[][] (rows preserved) straight through the
|
|
4611
|
+
// timeline — no lossy join/split round-trip (#69, nexus B-PIPELINE).
|
|
4612
|
+
frames: result.animation.frames,
|
|
4474
4613
|
framesFps: Math.min(30, Math.max(1, result.animation.fps ?? 4)),
|
|
4475
4614
|
framesLoop: result.animation.loop ?? true
|
|
4476
4615
|
} : {}
|
|
@@ -4600,4 +4739,4 @@ export {
|
|
|
4600
4739
|
inspectCache,
|
|
4601
4740
|
generateStatic
|
|
4602
4741
|
};
|
|
4603
|
-
//# sourceMappingURL=chunk-
|
|
4742
|
+
//# sourceMappingURL=chunk-DVACBVLX.js.map
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
mergeConfig,
|
|
12
12
|
setStrictBlockConfig,
|
|
13
13
|
themes
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-DVACBVLX.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
17
|
import { writeFileSync, watch as fsWatch } from "fs";
|
|
@@ -127,7 +127,7 @@ function isZodOptional(t) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// src/cli.ts
|
|
130
|
-
var VERSION = true ? "1.
|
|
130
|
+
var VERSION = true ? "1.1.0" : "0.0.0-dev";
|
|
131
131
|
var args = process.argv.slice(2);
|
|
132
132
|
var command = args[0];
|
|
133
133
|
function getFlag(name) {
|
package/dist/index.d.ts
CHANGED
|
@@ -211,8 +211,11 @@ interface Sequence {
|
|
|
211
211
|
pause?: number;
|
|
212
212
|
/** Delay before this sequence starts in ms */
|
|
213
213
|
delay?: number;
|
|
214
|
-
/** Optional multi-frame payload — output sequences only. Each entry is one
|
|
215
|
-
|
|
214
|
+
/** Optional multi-frame payload — output sequences only. Each entry is one
|
|
215
|
+
* frame; each frame is an array of rows. Multi-line frames are supported
|
|
216
|
+
* (#69) — single-row frames (the common case) are `[oneLine]`. Triggers
|
|
217
|
+
* frame-cycle rendering. */
|
|
218
|
+
frames?: string[][];
|
|
216
219
|
/** Frames per second when `frames` is set (default 4, capped at 30). */
|
|
217
220
|
framesFps?: number;
|
|
218
221
|
/** Loop frames forever when set (default true). */
|
|
@@ -233,8 +236,9 @@ interface AnimationFrame {
|
|
|
233
236
|
typingDuration?: number;
|
|
234
237
|
scrollLines?: number;
|
|
235
238
|
bufferStart?: number;
|
|
236
|
-
/** Multi-frame payload — present only on add-output frames spawned from
|
|
237
|
-
|
|
239
|
+
/** Multi-frame payload — present only on add-output frames spawned from
|
|
240
|
+
* animated blocks. Each frame is an array of rows (#69 multi-line). */
|
|
241
|
+
frames?: string[][];
|
|
238
242
|
framesFps?: number;
|
|
239
243
|
framesLoop?: boolean;
|
|
240
244
|
/** Width-pinning opt-in from BlockResult.pinWidth. */
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svg-terminal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Generate animated SVG terminals for GitHub READMEs from a declarative YAML config. 47 built-in blocks, 12 themes, zero runtime deps in the output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -59,13 +59,14 @@
|
|
|
59
59
|
"node": ">=22.0.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^10.0.1",
|
|
62
63
|
"@types/js-yaml": "^4.0.9",
|
|
63
64
|
"@types/node": "^22.19.19",
|
|
64
65
|
"@vitest/coverage-v8": "^4.1.7",
|
|
65
|
-
"eslint": "^
|
|
66
|
+
"eslint": "^10.4.1",
|
|
66
67
|
"tsup": "^8.0.0",
|
|
67
68
|
"tsx": "^4.22.3",
|
|
68
|
-
"typescript": "^
|
|
69
|
+
"typescript": "^6.0.3",
|
|
69
70
|
"typescript-eslint": "^8.60.0",
|
|
70
71
|
"vitest": "^4.1.7"
|
|
71
72
|
},
|