svg-terminal 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/chunk-IVINEQLU.js +4603 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +524 -0
- package/dist/index.d.ts +733 -0
- package/dist/index.js +101 -0
- package/package.json +76 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BlockConfigError,
|
|
4
|
+
ConfigError,
|
|
5
|
+
generate,
|
|
6
|
+
generateStatic,
|
|
7
|
+
getBlock,
|
|
8
|
+
inspectCache,
|
|
9
|
+
listBlocks,
|
|
10
|
+
loadConfig,
|
|
11
|
+
mergeConfig,
|
|
12
|
+
setStrictBlockConfig,
|
|
13
|
+
themes
|
|
14
|
+
} from "./chunk-IVINEQLU.js";
|
|
15
|
+
|
|
16
|
+
// src/cli.ts
|
|
17
|
+
import { writeFileSync, watch as fsWatch } from "fs";
|
|
18
|
+
import { basename, dirname, resolve } from "path";
|
|
19
|
+
|
|
20
|
+
// src/core/cli-helpers.ts
|
|
21
|
+
function formatModeTag(opts) {
|
|
22
|
+
const parts = [
|
|
23
|
+
opts.isStatic && "static",
|
|
24
|
+
opts.minify && "minified",
|
|
25
|
+
opts.cacheMode !== "normal" && `cache:${opts.cacheMode}`
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
return parts.length ? ` (${parts.join(", ")})` : "";
|
|
28
|
+
}
|
|
29
|
+
function humanAge(seconds) {
|
|
30
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "0s";
|
|
31
|
+
if (seconds < 60) return `${Math.floor(seconds)}s`;
|
|
32
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
33
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
|
|
34
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
35
|
+
}
|
|
36
|
+
function minifySvg(svg) {
|
|
37
|
+
return svg.replace(/>\s+</g, "><").replace(/\n\s+/g, "\n").replace(/^\s+|\s+$/g, "");
|
|
38
|
+
}
|
|
39
|
+
function resolveCacheMode(args2) {
|
|
40
|
+
const seen = [];
|
|
41
|
+
if (args2.includes("--no-cache")) seen.push({ flag: "--no-cache", mode: "off" });
|
|
42
|
+
if (args2.includes("--refresh-cache")) seen.push({ flag: "--refresh-cache", mode: "refresh" });
|
|
43
|
+
if (args2.includes("--frozen-cache")) seen.push({ flag: "--frozen-cache", mode: "frozen" });
|
|
44
|
+
const modeIdx = args2.indexOf("--cache-mode");
|
|
45
|
+
if (modeIdx !== -1) {
|
|
46
|
+
const value = args2[modeIdx + 1];
|
|
47
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
48
|
+
throw new Error("--cache-mode requires a value (normal | refresh | frozen | off)");
|
|
49
|
+
}
|
|
50
|
+
if (!isCacheMode(value)) {
|
|
51
|
+
throw new Error(`Invalid --cache-mode value: ${value} (expected normal | refresh | frozen | off)`);
|
|
52
|
+
}
|
|
53
|
+
seen.push({ flag: `--cache-mode ${value}`, mode: value });
|
|
54
|
+
}
|
|
55
|
+
if (seen.length > 1) {
|
|
56
|
+
const flags = seen.map((s) => s.flag).join(" and ");
|
|
57
|
+
throw new Error(`Conflicting cache flags: can't combine ${flags}`);
|
|
58
|
+
}
|
|
59
|
+
return seen[0]?.mode ?? "normal";
|
|
60
|
+
}
|
|
61
|
+
function isCacheMode(s) {
|
|
62
|
+
return s === "normal" || s === "refresh" || s === "frozen" || s === "off";
|
|
63
|
+
}
|
|
64
|
+
var SECRET_KEY_RE = /token|secret|password|api[-_]?key|auth|credential|bearer|webhook[-_]?url/i;
|
|
65
|
+
function scrubSecrets(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
if (ancestors.has(value)) return "[CIRCULAR]";
|
|
68
|
+
ancestors.add(value);
|
|
69
|
+
try {
|
|
70
|
+
return value.map((v) => scrubSecrets(v, ancestors));
|
|
71
|
+
} finally {
|
|
72
|
+
ancestors.delete(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (value && typeof value === "object") {
|
|
76
|
+
if (ancestors.has(value)) return "[CIRCULAR]";
|
|
77
|
+
ancestors.add(value);
|
|
78
|
+
try {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [k, v] of Object.entries(value)) {
|
|
81
|
+
out[k] = SECRET_KEY_RE.test(k) ? "[REDACTED]" : scrubSecrets(v, ancestors);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
} finally {
|
|
85
|
+
ancestors.delete(value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function formatZodType(t) {
|
|
91
|
+
if (!t || typeof t !== "object") return "unknown";
|
|
92
|
+
const ctor = t.constructor?.name ?? "unknown";
|
|
93
|
+
const def = t._def ?? {};
|
|
94
|
+
if (ctor === "ZodOptional" || ctor === "ZodNullable") {
|
|
95
|
+
return def.innerType ? formatZodType(def.innerType) : "unknown";
|
|
96
|
+
}
|
|
97
|
+
switch (ctor) {
|
|
98
|
+
case "ZodString":
|
|
99
|
+
return "string";
|
|
100
|
+
case "ZodNumber":
|
|
101
|
+
return "number";
|
|
102
|
+
case "ZodBoolean":
|
|
103
|
+
return "boolean";
|
|
104
|
+
case "ZodEnum": {
|
|
105
|
+
const opts = t.options ?? def.values ?? [];
|
|
106
|
+
return opts.map((v) => `"${v}"`).join(" | ") || "enum";
|
|
107
|
+
}
|
|
108
|
+
case "ZodArray": {
|
|
109
|
+
const elt = t.element ?? def.type;
|
|
110
|
+
return `array of ${formatZodType(elt)}`;
|
|
111
|
+
}
|
|
112
|
+
case "ZodRecord":
|
|
113
|
+
return `record<string, ${def.valueType ? formatZodType(def.valueType) : "unknown"}>`;
|
|
114
|
+
case "ZodObject":
|
|
115
|
+
return "object";
|
|
116
|
+
case "ZodLiteral":
|
|
117
|
+
return JSON.stringify(t.value);
|
|
118
|
+
case "ZodUnion":
|
|
119
|
+
return "union";
|
|
120
|
+
default:
|
|
121
|
+
return ctor.replace(/^Zod/, "").toLowerCase();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function isZodOptional(t) {
|
|
125
|
+
const ctor = t?.constructor?.name;
|
|
126
|
+
return ctor === "ZodOptional" || ctor === "ZodNullable";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/cli.ts
|
|
130
|
+
var VERSION = true ? "1.0.0" : "0.0.0-dev";
|
|
131
|
+
var args = process.argv.slice(2);
|
|
132
|
+
var command = args[0];
|
|
133
|
+
function getFlag(name) {
|
|
134
|
+
const idx = args.indexOf(`--${name}`);
|
|
135
|
+
if (idx === -1) return void 0;
|
|
136
|
+
return args[idx + 1];
|
|
137
|
+
}
|
|
138
|
+
function hasFlag(name) {
|
|
139
|
+
return args.includes(`--${name}`);
|
|
140
|
+
}
|
|
141
|
+
var GENERATE_KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
142
|
+
"config",
|
|
143
|
+
"output",
|
|
144
|
+
"static",
|
|
145
|
+
"minify",
|
|
146
|
+
"strict",
|
|
147
|
+
"watch",
|
|
148
|
+
"no-cache",
|
|
149
|
+
"refresh-cache",
|
|
150
|
+
"frozen-cache",
|
|
151
|
+
"cache-mode",
|
|
152
|
+
"timings",
|
|
153
|
+
"explain"
|
|
154
|
+
]);
|
|
155
|
+
function warnUnknownFlags(tokens, known) {
|
|
156
|
+
const valueFlags = /* @__PURE__ */ new Set(["config", "output", "cache-mode"]);
|
|
157
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
158
|
+
const tok = tokens[i];
|
|
159
|
+
if (!tok.startsWith("--")) continue;
|
|
160
|
+
const name = tok.slice(2);
|
|
161
|
+
if (!known.has(name)) {
|
|
162
|
+
console.error(`\x1B[33m[svg-terminal] warning: unknown flag "${tok}" \u2014 ignoring\x1B[0m`);
|
|
163
|
+
}
|
|
164
|
+
if (valueFlags.has(name)) i++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function formatWatchError(err) {
|
|
168
|
+
if (err instanceof ConfigError || err instanceof BlockConfigError) {
|
|
169
|
+
console.error(`\x1B[31m${err.formatted}\x1B[0m`);
|
|
170
|
+
} else {
|
|
171
|
+
console.error("\x1B[31mError:\x1B[0m", err instanceof Error ? err.message : err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function main() {
|
|
175
|
+
if (hasFlag("version") || command === "--version") {
|
|
176
|
+
console.log(`svg-terminal ${VERSION}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
switch (command) {
|
|
180
|
+
case "generate": {
|
|
181
|
+
warnUnknownFlags(args.slice(1), GENERATE_KNOWN_FLAGS);
|
|
182
|
+
const configPath = getFlag("config") ?? "terminal.yml";
|
|
183
|
+
const outputPath = getFlag("output") ?? "terminal.svg";
|
|
184
|
+
const isStatic = hasFlag("static");
|
|
185
|
+
const minify = hasFlag("minify");
|
|
186
|
+
const strict = hasFlag("strict");
|
|
187
|
+
const watch = hasFlag("watch");
|
|
188
|
+
const timings = hasFlag("timings");
|
|
189
|
+
const explain = hasFlag("explain");
|
|
190
|
+
const cacheMode = resolveCacheMode(args);
|
|
191
|
+
setStrictBlockConfig(strict);
|
|
192
|
+
const resolvedConfigPath = resolve(configPath);
|
|
193
|
+
const resolvedOutputPath = resolve(outputPath);
|
|
194
|
+
const modeTag = formatModeTag({ isStatic, minify, cacheMode });
|
|
195
|
+
const cacheStats = { hit: 0, miss: 0, refreshed: 0, fallback: 0, fallbacks: [] };
|
|
196
|
+
const onCacheEvent = (evt, key) => {
|
|
197
|
+
cacheStats[evt]++;
|
|
198
|
+
if (evt === "fallback") cacheStats.fallbacks.push(key);
|
|
199
|
+
};
|
|
200
|
+
const runOnce = async () => {
|
|
201
|
+
cacheStats.hit = 0;
|
|
202
|
+
cacheStats.miss = 0;
|
|
203
|
+
cacheStats.refreshed = 0;
|
|
204
|
+
cacheStats.fallback = 0;
|
|
205
|
+
cacheStats.fallbacks = [];
|
|
206
|
+
const start = performance.now();
|
|
207
|
+
const tLoadStart = performance.now();
|
|
208
|
+
const userConfig = await loadConfig(resolvedConfigPath);
|
|
209
|
+
const tLoadMs = performance.now() - tLoadStart;
|
|
210
|
+
if (explain) {
|
|
211
|
+
const merged = mergeConfig(userConfig);
|
|
212
|
+
const explainDump = scrubSecrets({
|
|
213
|
+
configPath: resolvedConfigPath,
|
|
214
|
+
outputPath: resolvedOutputPath,
|
|
215
|
+
theme: merged.theme.name,
|
|
216
|
+
window: merged.window,
|
|
217
|
+
text: merged.text,
|
|
218
|
+
effects: merged.effects,
|
|
219
|
+
variables: userConfig.variables,
|
|
220
|
+
blockCount: userConfig.blocks.length,
|
|
221
|
+
blocks: userConfig.blocks.map((entry) => {
|
|
222
|
+
const block = getBlock(entry.block);
|
|
223
|
+
return {
|
|
224
|
+
name: entry.block,
|
|
225
|
+
cacheable: block?.cacheable ?? false,
|
|
226
|
+
registered: !!block,
|
|
227
|
+
config: entry.config
|
|
228
|
+
};
|
|
229
|
+
}),
|
|
230
|
+
maxDuration: merged.maxDuration
|
|
231
|
+
});
|
|
232
|
+
console.error(`[svg-terminal --explain]
|
|
233
|
+
${JSON.stringify(explainDump, null, 2)}`);
|
|
234
|
+
}
|
|
235
|
+
const genOpts = { configPath: resolvedConfigPath, cacheMode, onCacheEvent };
|
|
236
|
+
const tGenStart = performance.now();
|
|
237
|
+
let svg = isStatic ? await generateStatic(userConfig, genOpts) : await generate(userConfig, genOpts);
|
|
238
|
+
const tGenMs = performance.now() - tGenStart;
|
|
239
|
+
const tWriteStart = performance.now();
|
|
240
|
+
if (minify) svg = minifySvg(svg);
|
|
241
|
+
writeFileSync(resolvedOutputPath, svg, "utf-8");
|
|
242
|
+
const tWriteMs = performance.now() - tWriteStart;
|
|
243
|
+
const elapsed = Math.round(performance.now() - start);
|
|
244
|
+
const prefix = watch ? `[${(/* @__PURE__ */ new Date()).toTimeString().slice(0, 8)}] ` : "";
|
|
245
|
+
const duration = watch ? `, ${elapsed}ms` : "";
|
|
246
|
+
console.log(`${prefix}Generated ${outputPath}${modeTag} (${(svg.length / 1024).toFixed(1)} KB${duration})`);
|
|
247
|
+
if (timings) {
|
|
248
|
+
console.error(
|
|
249
|
+
`[svg-terminal --timings] load: ${tLoadMs.toFixed(1)}ms generate: ${tGenMs.toFixed(1)}ms write: ${tWriteMs.toFixed(1)}ms total: ${elapsed}ms`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const totalEvents = cacheStats.hit + cacheStats.miss + cacheStats.refreshed + cacheStats.fallback;
|
|
253
|
+
if (totalEvents > 0) {
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (cacheStats.hit) parts.push(`\x1B[32mhit ${cacheStats.hit}\x1B[0m`);
|
|
256
|
+
if (cacheStats.miss) parts.push(`miss ${cacheStats.miss}`);
|
|
257
|
+
if (cacheStats.refreshed) parts.push(`refreshed ${cacheStats.refreshed}`);
|
|
258
|
+
if (cacheStats.fallback) parts.push(`\x1B[33mfallback ${cacheStats.fallback}\x1B[0m`);
|
|
259
|
+
const fallbackList = cacheStats.fallbacks.length > 0 ? ` [${cacheStats.fallbacks.join(", ")}]` : "";
|
|
260
|
+
console.error(`[svg-terminal cache] ${parts.join(" ")}${fallbackList}`);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
if (!watch) {
|
|
264
|
+
await runOnce();
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await runOnce();
|
|
269
|
+
} catch (e) {
|
|
270
|
+
formatWatchError(e);
|
|
271
|
+
}
|
|
272
|
+
console.log(`\x1B[2m[svg-terminal] watching ${configPath}... (Ctrl-C to exit)\x1B[0m`);
|
|
273
|
+
const DEBOUNCE_MS = 100;
|
|
274
|
+
let timer = null;
|
|
275
|
+
let running = false;
|
|
276
|
+
let queued = false;
|
|
277
|
+
const trigger = () => {
|
|
278
|
+
if (timer) clearTimeout(timer);
|
|
279
|
+
timer = setTimeout(async () => {
|
|
280
|
+
timer = null;
|
|
281
|
+
if (running) {
|
|
282
|
+
queued = true;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
running = true;
|
|
286
|
+
try {
|
|
287
|
+
await runOnce();
|
|
288
|
+
} catch (e) {
|
|
289
|
+
formatWatchError(e);
|
|
290
|
+
} finally {
|
|
291
|
+
running = false;
|
|
292
|
+
if (queued) {
|
|
293
|
+
queued = false;
|
|
294
|
+
trigger();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}, DEBOUNCE_MS);
|
|
298
|
+
};
|
|
299
|
+
const configDir = dirname(resolvedConfigPath);
|
|
300
|
+
const configName = basename(resolvedConfigPath);
|
|
301
|
+
const watcher = fsWatch(configDir, { persistent: true }, (_event, filename) => {
|
|
302
|
+
if (filename === configName) trigger();
|
|
303
|
+
});
|
|
304
|
+
watcher.on("error", (err) => {
|
|
305
|
+
console.error(`\x1B[31m[svg-terminal] watch error: ${err.message}\x1B[0m`);
|
|
306
|
+
});
|
|
307
|
+
const cleanup = () => {
|
|
308
|
+
watcher.close();
|
|
309
|
+
console.log("\n\x1B[2m[svg-terminal] stopped\x1B[0m");
|
|
310
|
+
process.exit(0);
|
|
311
|
+
};
|
|
312
|
+
process.on("SIGINT", cleanup);
|
|
313
|
+
process.on("SIGTERM", cleanup);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "init": {
|
|
317
|
+
const starter = `# svg-terminal configuration
|
|
318
|
+
# See: https://github.com/williamzujkowski/svg-terminal
|
|
319
|
+
|
|
320
|
+
theme: dracula
|
|
321
|
+
|
|
322
|
+
window:
|
|
323
|
+
width: 1000
|
|
324
|
+
height: 560
|
|
325
|
+
title: "user@terminal:~"
|
|
326
|
+
# style: macos # macos | win95 | floating | minimal | none
|
|
327
|
+
# autoHeight: false # Auto-calculate height from content
|
|
328
|
+
# minHeight: 300 # Minimum height when autoHeight is true
|
|
329
|
+
# maxHeight: 1200 # Maximum height when autoHeight is true
|
|
330
|
+
|
|
331
|
+
terminal:
|
|
332
|
+
prompt: "user@host:~$ "
|
|
333
|
+
fontSize: 14
|
|
334
|
+
|
|
335
|
+
effects:
|
|
336
|
+
textGlow: false # phosphor halo \u2014 try true with amber / green-phosphor / cyberpunk
|
|
337
|
+
shadow: true
|
|
338
|
+
scanlines: true
|
|
339
|
+
|
|
340
|
+
# Animation timing (all values in ms)
|
|
341
|
+
# animation:
|
|
342
|
+
# cursorBlinkCycle: 1000
|
|
343
|
+
# outputLineStagger: 50
|
|
344
|
+
# commandOutputPause: 300
|
|
345
|
+
# loop: true # true (infinite) | false (play once) | number (N times)
|
|
346
|
+
|
|
347
|
+
# Window chrome appearance
|
|
348
|
+
# chrome:
|
|
349
|
+
# titleFontSize: 13
|
|
350
|
+
# dimOpacity: 0.6
|
|
351
|
+
|
|
352
|
+
blocks:
|
|
353
|
+
- block: neofetch
|
|
354
|
+
config:
|
|
355
|
+
username: user
|
|
356
|
+
hostname: terminal
|
|
357
|
+
os: TerminalOS v1.0
|
|
358
|
+
shell: bash 5.2
|
|
359
|
+
role: Developer
|
|
360
|
+
languages: TypeScript, Python
|
|
361
|
+
|
|
362
|
+
- block: fortune
|
|
363
|
+
config:
|
|
364
|
+
fortunes:
|
|
365
|
+
- "The best code is no code at all."
|
|
366
|
+
- "Talk is cheap. Show me the code."
|
|
367
|
+
- "First, solve the problem. Then, write the code."
|
|
368
|
+
|
|
369
|
+
# Uncomment to see an animated block (the library's signature feature) \u2014
|
|
370
|
+
# spinners, clocks, dice rolls. Multi-frame animation, single-line restriction.
|
|
371
|
+
# - block: loading-spinner
|
|
372
|
+
# config:
|
|
373
|
+
# label: "deploying to production"
|
|
374
|
+
|
|
375
|
+
- block: custom
|
|
376
|
+
config:
|
|
377
|
+
command: echo "Thanks for visiting!"
|
|
378
|
+
lines:
|
|
379
|
+
- "[[fg:green]]Thanks for visiting my profile![[/fg]]"
|
|
380
|
+
- ""
|
|
381
|
+
- "Have a great day!"
|
|
382
|
+
`;
|
|
383
|
+
const targetPath = resolve("terminal.yml");
|
|
384
|
+
try {
|
|
385
|
+
writeFileSync(targetPath, starter, { encoding: "utf-8", flag: hasFlag("force") ? "w" : "wx" });
|
|
386
|
+
} catch (err) {
|
|
387
|
+
if (err.code === "EEXIST") {
|
|
388
|
+
console.error("terminal.yml already exists. Use --force to overwrite.");
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
throw err;
|
|
392
|
+
}
|
|
393
|
+
console.log("Created terminal.yml \u2014 edit it and run: svg-terminal generate");
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case "themes": {
|
|
397
|
+
console.log("Available themes:");
|
|
398
|
+
for (const name of Object.keys(themes)) {
|
|
399
|
+
console.log(` - ${name}`);
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case "blocks": {
|
|
404
|
+
const target = args[1];
|
|
405
|
+
if (target) {
|
|
406
|
+
const block = getBlock(target);
|
|
407
|
+
if (!block) {
|
|
408
|
+
console.error(`Unknown block "${target}". Run: svg-terminal blocks`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
console.log(`${block.name} \u2014 ${block.description}`);
|
|
412
|
+
if (block.cacheable) console.log(" cacheable: yes (participates in .svg-terminal-cache.json)");
|
|
413
|
+
const schema = block.configSchema;
|
|
414
|
+
const shape = schema?.shape;
|
|
415
|
+
if (!shape || Object.keys(shape).length === 0) {
|
|
416
|
+
console.log(" Config: (no fields)");
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
const names = Object.keys(shape);
|
|
420
|
+
const w = Math.max(...names.map((n) => n.length));
|
|
421
|
+
console.log(" Config:");
|
|
422
|
+
for (const name of names) {
|
|
423
|
+
const t = shape[name];
|
|
424
|
+
const tag = isZodOptional(t) ? "" : " (required)";
|
|
425
|
+
console.log(` ${name.padEnd(w)} ${formatZodType(t)}${tag}`);
|
|
426
|
+
}
|
|
427
|
+
console.log("");
|
|
428
|
+
console.log(` Plus the universal entry-level keys: command, color, typing, pause`);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
console.log("Available blocks (cacheable blocks marked *):");
|
|
432
|
+
for (const name of listBlocks()) {
|
|
433
|
+
const block = getBlock(name);
|
|
434
|
+
const mark = block?.cacheable ? " *" : "";
|
|
435
|
+
console.log(` - ${name}${mark}`);
|
|
436
|
+
}
|
|
437
|
+
console.log("");
|
|
438
|
+
console.log("Run `svg-terminal blocks <name>` to see a block's config schema.");
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case "cache": {
|
|
442
|
+
const sub = args[1];
|
|
443
|
+
if (sub !== "check") {
|
|
444
|
+
console.error("Usage: svg-terminal cache check [--config <path>]");
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
const configPath = getFlag("config") ?? "terminal.yml";
|
|
448
|
+
const resolved = resolve(configPath);
|
|
449
|
+
const userConfig = await loadConfig(resolved);
|
|
450
|
+
const { filePath, results } = inspectCache(userConfig, resolved);
|
|
451
|
+
const w = Math.max(0, ...results.map((r) => r.key.length));
|
|
452
|
+
console.log(`Checking cache at ${filePath}...`);
|
|
453
|
+
if (results.length === 0) {
|
|
454
|
+
console.log(" (no cacheable blocks in this config)");
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
let bad = 0;
|
|
458
|
+
for (const r of results) {
|
|
459
|
+
const ageStr = r.ageSeconds !== void 0 ? `age ${humanAge(r.ageSeconds)}` : "";
|
|
460
|
+
const tag = r.status === "OK" ? `\x1B[32mOK\x1B[0m ` : r.status === "STALE" ? `\x1B[33mSTALE\x1B[0m` : `\x1B[31mMISS\x1B[0m `;
|
|
461
|
+
console.log(` ${r.key.padEnd(w)} ${tag} ${ageStr}`);
|
|
462
|
+
if (r.status !== "OK") bad++;
|
|
463
|
+
}
|
|
464
|
+
if (bad > 0) {
|
|
465
|
+
console.error(`
|
|
466
|
+
${bad} cacheable block(s) need a refresh. Run: svg-terminal generate --refresh-cache`);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
default: {
|
|
472
|
+
console.log(`svg-terminal \u2014 Generate animated SVG terminals
|
|
473
|
+
|
|
474
|
+
Commands:
|
|
475
|
+
generate Generate SVG from config file
|
|
476
|
+
init Create a starter terminal.yml (refuses to overwrite without --force)
|
|
477
|
+
themes List available themes
|
|
478
|
+
blocks [<name>] List available block types, or print one block's config schema
|
|
479
|
+
cache check Verify dynamic-block cache freshness (exit 1 on stale/missing)
|
|
480
|
+
|
|
481
|
+
Generate options:
|
|
482
|
+
--config <path> Config file path (default: terminal.yml)
|
|
483
|
+
--output <path> Output file path (default: terminal.svg)
|
|
484
|
+
--static Render the non-animated final-frame snapshot
|
|
485
|
+
--minify Strip inter-element whitespace for smaller output
|
|
486
|
+
--strict Promote unknown-block-config-key warnings to hard errors
|
|
487
|
+
--watch Re-generate on config file change (Ctrl-C to exit)
|
|
488
|
+
--timings Print per-phase wall-clock timings (load, generate, write) to stderr
|
|
489
|
+
--explain Print the resolved config + block list as JSON to stderr
|
|
490
|
+
|
|
491
|
+
Cache events appear in stderr automatically when any dynamic block
|
|
492
|
+
participates ([svg-terminal cache] hit N miss N fallback N [block-names]).
|
|
493
|
+
|
|
494
|
+
Cache modes (mutually exclusive \u2014 defaults to normal):
|
|
495
|
+
--no-cache Don't read or write the dynamic-block fetch cache
|
|
496
|
+
--refresh-cache Force refresh: ignore existing cache entries, re-fetch all
|
|
497
|
+
--frozen-cache Use cached values only; never fetch (CI offline mode)
|
|
498
|
+
--cache-mode <m> Explicit: normal | refresh | frozen | off
|
|
499
|
+
|
|
500
|
+
Init options:
|
|
501
|
+
--force Overwrite an existing terminal.yml
|
|
502
|
+
|
|
503
|
+
Global:
|
|
504
|
+
--version Print version number
|
|
505
|
+
|
|
506
|
+
Examples:
|
|
507
|
+
svg-terminal init
|
|
508
|
+
svg-terminal generate --config terminal.yml --output terminal.svg
|
|
509
|
+
svg-terminal generate --static
|
|
510
|
+
svg-terminal generate --watch
|
|
511
|
+
svg-terminal cache check --config terminal.yml
|
|
512
|
+
`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
main().catch((err) => {
|
|
517
|
+
if (err instanceof ConfigError || err instanceof BlockConfigError) {
|
|
518
|
+
console.error(err.formatted);
|
|
519
|
+
} else {
|
|
520
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
521
|
+
}
|
|
522
|
+
process.exit(1);
|
|
523
|
+
});
|
|
524
|
+
//# sourceMappingURL=cli.js.map
|