create-thally-docs 0.10.78 → 0.10.80
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 +24 -0
- package/dist/{chunk-VGGE6JIL.js → chunk-DIX4EUI4.js} +33 -354
- package/dist/chunk-J7255UQA.js +34 -0
- package/dist/chunk-KDLP27DS.js +87 -0
- package/dist/chunk-KGKSB3PU.js +222 -0
- package/dist/chunk-L2YCVQBR.js +249 -0
- package/dist/chunk-VGE5L66Z.js +349 -0
- package/dist/{chunk-W5WQ2V7P.js → chunk-WWIATVAD.js} +14 -4
- package/dist/index.js +134 -50
- package/dist/migrate/index.d.ts +3 -1
- package/dist/migrate/index.js +6 -3
- package/dist/migration-worker.d.ts +2 -0
- package/dist/migration-worker.js +37 -0
- package/dist/scaffold.d.ts +6 -0
- package/dist/scaffold.js +4 -2
- package/dist/starter-update.js +2 -1
- package/dist/terminal.d.ts +75 -0
- package/dist/terminal.js +17 -0
- package/package.json +8 -3
- package/dist/chunk-QLMRRNLL.js +0 -162
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
personalizeStarter,
|
|
4
|
+
updateEnvExample
|
|
5
|
+
} from "./chunk-4VKRBJV2.js";
|
|
6
|
+
import {
|
|
7
|
+
resetTrackingConfig,
|
|
8
|
+
writeTrackingConfig
|
|
9
|
+
} from "./chunk-J7255UQA.js";
|
|
10
|
+
import {
|
|
11
|
+
downloadStarter
|
|
12
|
+
} from "./chunk-WWIATVAD.js";
|
|
13
|
+
import {
|
|
14
|
+
terminal
|
|
15
|
+
} from "./chunk-L2YCVQBR.js";
|
|
16
|
+
|
|
17
|
+
// src/scaffold.ts
|
|
18
|
+
import { existsSync, mkdirSync, readdirSync } from "fs";
|
|
19
|
+
import { resolve as resolve2 } from "path";
|
|
20
|
+
|
|
21
|
+
// src/utils.ts
|
|
22
|
+
import { execFileSync, execSync, spawn } from "child_process";
|
|
23
|
+
import { resolve } from "path";
|
|
24
|
+
function slugify(name) {
|
|
25
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
26
|
+
}
|
|
27
|
+
async function runProjectCommand(command, args, cwd, timeout, onProgress) {
|
|
28
|
+
const isQuiet = terminal.isRich && !terminal.isVerbose;
|
|
29
|
+
await new Promise((complete, reject) => {
|
|
30
|
+
const child = spawn(command, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
detached: process.platform !== "win32",
|
|
33
|
+
stdio: isQuiet ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
34
|
+
// Windows resolves npm via a .cmd shim; command and arguments here are
|
|
35
|
+
// fixed by the toolchain, never interpolated from imported content.
|
|
36
|
+
shell: process.platform === "win32" && command === "npm"
|
|
37
|
+
});
|
|
38
|
+
let tail = Buffer.alloc(0);
|
|
39
|
+
let omitted = false;
|
|
40
|
+
const collect = (chunk) => {
|
|
41
|
+
const combined = Buffer.concat([tail, chunk]);
|
|
42
|
+
omitted ||= combined.length > 64 * 1024;
|
|
43
|
+
tail = combined.subarray(Math.max(0, combined.length - 64 * 1024));
|
|
44
|
+
const stages = chunk.toString("utf8").split(/[\r\n]+/).map((line) => line.trim()).filter((line) => /^(?:>|npm (?:warn|error)|added \d+|.*(?:Creating an optimized|Compiled successfully|Collecting page data|Generating static pages|Finalizing page optimization))/.test(line));
|
|
45
|
+
if (stages.length) onProgress?.(stages.at(-1));
|
|
46
|
+
};
|
|
47
|
+
child.stdout?.on("data", collect);
|
|
48
|
+
child.stderr?.on("data", collect);
|
|
49
|
+
let interrupted;
|
|
50
|
+
let didTimeOut = false;
|
|
51
|
+
let escalation;
|
|
52
|
+
const killTree = (signal) => {
|
|
53
|
+
if (!child.pid) return;
|
|
54
|
+
try {
|
|
55
|
+
if (process.platform === "win32") {
|
|
56
|
+
execFileSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
57
|
+
} else {
|
|
58
|
+
process.kill(-child.pid, signal);
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
child.kill(signal);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const interrupt = (signal) => {
|
|
65
|
+
interrupted = signal;
|
|
66
|
+
killTree(signal);
|
|
67
|
+
escalation = setTimeout(() => killTree("SIGKILL"), 1e3);
|
|
68
|
+
};
|
|
69
|
+
const onInterrupt = () => interrupt("SIGINT");
|
|
70
|
+
const onTerminate = () => interrupt("SIGTERM");
|
|
71
|
+
const onExit = () => killTree("SIGKILL");
|
|
72
|
+
const deadline = timeout ? setTimeout(() => {
|
|
73
|
+
didTimeOut = true;
|
|
74
|
+
interrupt("SIGTERM");
|
|
75
|
+
}, timeout) : void 0;
|
|
76
|
+
process.once("SIGINT", onInterrupt);
|
|
77
|
+
process.once("SIGTERM", onTerminate);
|
|
78
|
+
process.once("exit", onExit);
|
|
79
|
+
const cleanup = () => {
|
|
80
|
+
if (interrupted) killTree("SIGKILL");
|
|
81
|
+
if (deadline) clearTimeout(deadline);
|
|
82
|
+
if (escalation) clearTimeout(escalation);
|
|
83
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
84
|
+
process.removeListener("SIGTERM", onTerminate);
|
|
85
|
+
process.removeListener("exit", onExit);
|
|
86
|
+
};
|
|
87
|
+
child.once("error", (error) => {
|
|
88
|
+
cleanup();
|
|
89
|
+
reject(error);
|
|
90
|
+
});
|
|
91
|
+
child.once("close", (code, signal) => {
|
|
92
|
+
cleanup();
|
|
93
|
+
if (code === 0 && !interrupted) {
|
|
94
|
+
complete();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const failure = new Error(`${command} ${args.join(" ")} failed (${interrupted ?? signal ?? `exit ${code}`}).`);
|
|
98
|
+
if (didTimeOut) failure.message += ` Timed out after ${timeout}ms.`;
|
|
99
|
+
if (interrupted && !didTimeOut) failure.exitCode = interrupted === "SIGINT" ? 130 : 143;
|
|
100
|
+
failure.output = `${omitted ? "Earlier output omitted; use --verbose for the full log.\n" : ""}${tail.toString("utf8")}`;
|
|
101
|
+
reject(failure);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async function initGit(targetDir) {
|
|
106
|
+
try {
|
|
107
|
+
await terminal.step("Initialize Git repository", async (update) => {
|
|
108
|
+
update("Initialize Git repository");
|
|
109
|
+
await runProjectCommand("git", ["init"], targetDir);
|
|
110
|
+
update("Stage generated project files");
|
|
111
|
+
await runProjectCommand("git", ["add", "-A"], targetDir);
|
|
112
|
+
update("Create initial project commit");
|
|
113
|
+
await runProjectCommand("git", ["commit", "-m", "Initial commit from create-thally-docs"], targetDir);
|
|
114
|
+
}, "Git repository initialized");
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error.exitCode === 130 || error.exitCode === 143) throw error;
|
|
117
|
+
const failure = error;
|
|
118
|
+
const diagnostic = [failure.output, failure.stdout, failure.stderr].filter(Boolean).map((value) => String(value)).join("\n").slice(-64 * 1024);
|
|
119
|
+
if (diagnostic.trim()) terminal.error(diagnostic.trimEnd());
|
|
120
|
+
terminal.warn("Could not finish Git setup. Run git status in the project directory, fix the reported issue, then retry the commit.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function installDeps(targetDir) {
|
|
124
|
+
try {
|
|
125
|
+
await terminal.step("Install dependencies", (update) => runProjectCommand("npm", [
|
|
126
|
+
"install",
|
|
127
|
+
"--prefer-offline",
|
|
128
|
+
"--no-audit",
|
|
129
|
+
"--no-fund",
|
|
130
|
+
"--progress=false"
|
|
131
|
+
], targetDir, void 0, (message) => update(`Install dependencies: ${message}`)), "Dependencies installed");
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const failure = error;
|
|
134
|
+
if (failure.output?.trim()) terminal.error(failure.output.trimEnd());
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function quoteDirectory(directory) {
|
|
139
|
+
const absolute = resolve(directory);
|
|
140
|
+
return process.platform === "win32" ? `"${absolute.replace(/"/g, '""')}"` : `'${absolute.replace(/'/g, "'\\''")}'`;
|
|
141
|
+
}
|
|
142
|
+
function previewCommand(directory, needsInstall = false) {
|
|
143
|
+
return `cd ${quoteDirectory(directory)} && ${needsInstall ? "npm install && " : ""}npm run dev`;
|
|
144
|
+
}
|
|
145
|
+
function logo(version) {
|
|
146
|
+
terminal.intro("init", version, "Build and publish docs for people and AI tools.");
|
|
147
|
+
}
|
|
148
|
+
function success(projectDir, projectName, dependenciesInstalled) {
|
|
149
|
+
terminal.detail("Project", projectName);
|
|
150
|
+
terminal.detail("Output", projectDir);
|
|
151
|
+
terminal.section("Make it yours", [
|
|
152
|
+
"src/content/ Write your documentation",
|
|
153
|
+
"docs.json Set up navigation",
|
|
154
|
+
"src/data/site.ts Name, links, and branding"
|
|
155
|
+
]);
|
|
156
|
+
terminal.outro("Your docs project is ready. The dev server will print your preview URL.");
|
|
157
|
+
terminal.nextAction("Preview locally", previewCommand(projectDir, !dependenciesInstalled));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/scaffold.ts
|
|
161
|
+
async function scaffold(options) {
|
|
162
|
+
const {
|
|
163
|
+
projectDir,
|
|
164
|
+
projectName,
|
|
165
|
+
description,
|
|
166
|
+
brandPreset,
|
|
167
|
+
repoUrl,
|
|
168
|
+
doInstall,
|
|
169
|
+
enableAiChat = true,
|
|
170
|
+
i18nLocales,
|
|
171
|
+
trackRepos,
|
|
172
|
+
colors
|
|
173
|
+
} = options;
|
|
174
|
+
const targetDir = resolve2(projectDir);
|
|
175
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
176
|
+
throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
|
|
177
|
+
}
|
|
178
|
+
mkdirSync(targetDir, { recursive: true });
|
|
179
|
+
const slug = slugify(projectName);
|
|
180
|
+
await terminal.step(
|
|
181
|
+
"Downloading starter",
|
|
182
|
+
() => downloadStarter(targetDir, projectName, void 0, { announce: false }),
|
|
183
|
+
"Starter downloaded"
|
|
184
|
+
);
|
|
185
|
+
await terminal.step("Configuring project", () => {
|
|
186
|
+
personalizeStarter(targetDir, {
|
|
187
|
+
projectName,
|
|
188
|
+
packageName: slug,
|
|
189
|
+
description,
|
|
190
|
+
brandPreset,
|
|
191
|
+
repoUrl,
|
|
192
|
+
enableAiChat,
|
|
193
|
+
i18nLocales,
|
|
194
|
+
colors
|
|
195
|
+
});
|
|
196
|
+
resetTrackingConfig(targetDir);
|
|
197
|
+
if (trackRepos?.length) writeTrackingConfig(targetDir, trackRepos);
|
|
198
|
+
updateEnvExample(targetDir);
|
|
199
|
+
}, "Project configured");
|
|
200
|
+
if (trackRepos?.length) {
|
|
201
|
+
const list = trackRepos.map((r) => `${r.owner}/${r.repo}`).join(", ");
|
|
202
|
+
terminal.info(`Thally Track enabled \u2014 watching ${list} (branch main, all files; refine in docs.json).`);
|
|
203
|
+
terminal.info("To finish wiring it: `thally track setup` (pick a trigger) + `thally agent init`,");
|
|
204
|
+
terminal.info("then add your ANTHROPIC_API_KEY. See /guides/thally-track.");
|
|
205
|
+
}
|
|
206
|
+
if (doInstall) {
|
|
207
|
+
await installDeps(targetDir);
|
|
208
|
+
}
|
|
209
|
+
await initGit(targetDir);
|
|
210
|
+
return { projectDir: targetDir };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
slugify,
|
|
215
|
+
runProjectCommand,
|
|
216
|
+
initGit,
|
|
217
|
+
installDeps,
|
|
218
|
+
previewCommand,
|
|
219
|
+
logo,
|
|
220
|
+
success,
|
|
221
|
+
scaffold
|
|
222
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/terminal.ts
|
|
4
|
+
import { intro, outro, spinner } from "@clack/prompts";
|
|
5
|
+
import { stripVTControlCharacters } from "util";
|
|
6
|
+
var PRODUCT_TAGLINE = "Product knowledge, kept in step with your code.";
|
|
7
|
+
var OLIVE = { rgb: "38;2;174;187;106", palette: "38;5;143" };
|
|
8
|
+
var OLIVE_EDGE = { rgb: "38;2;115;121;56", palette: "38;5;101" };
|
|
9
|
+
function oliveCode(env, edge = false) {
|
|
10
|
+
const color = edge ? OLIVE_EDGE : OLIVE;
|
|
11
|
+
return env.COLORTERM === "truecolor" || env.COLORTERM === "24bit" || ["iTerm.app", "vscode", "WezTerm"].includes(env.TERM_PROGRAM ?? "") ? color.rgb : color.palette;
|
|
12
|
+
}
|
|
13
|
+
var accentUsers = 0;
|
|
14
|
+
var restoreAccent;
|
|
15
|
+
function acquireTerminalAccent() {
|
|
16
|
+
if (!terminal.isRich) return () => {
|
|
17
|
+
};
|
|
18
|
+
if (accentUsers === 0) {
|
|
19
|
+
const output = process.stdout;
|
|
20
|
+
const previous = output.write;
|
|
21
|
+
const writeOriginal = previous.bind(output);
|
|
22
|
+
const opening = `\x1B[${oliveCode(process.env)}m`;
|
|
23
|
+
const themed = function(chunk, encodingOrCallback, callback) {
|
|
24
|
+
const text = typeof chunk === "string" ? chunk.replace(/\x1b\[(?:32|35|36)m/g, opening) : chunk;
|
|
25
|
+
return typeof encodingOrCallback === "string" ? writeOriginal(text, encodingOrCallback, callback) : writeOriginal(text, encodingOrCallback);
|
|
26
|
+
};
|
|
27
|
+
output.write = themed;
|
|
28
|
+
restoreAccent = () => {
|
|
29
|
+
if (output.write === themed) output.write = previous;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
accentUsers += 1;
|
|
33
|
+
let released = false;
|
|
34
|
+
return () => {
|
|
35
|
+
if (released) return;
|
|
36
|
+
released = true;
|
|
37
|
+
accentUsers -= 1;
|
|
38
|
+
if (accentUsers === 0) {
|
|
39
|
+
restoreAccent?.();
|
|
40
|
+
restoreAccent = void 0;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var WORDMARK = [
|
|
45
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
46
|
+
"\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
|
|
47
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ",
|
|
48
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ",
|
|
49
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
50
|
+
" \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
|
|
51
|
+
];
|
|
52
|
+
var SMALL_WORDMARK = [
|
|
53
|
+
"\u2580\u2588\u2580 \u2588 \u2588 \u2584\u2580\u2588 \u2588 \u2588 \u2588 \u2588",
|
|
54
|
+
" \u2588 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588 \u2588 \u2588 ",
|
|
55
|
+
" \u2580 \u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580\u2580\u2580 \u2580 "
|
|
56
|
+
];
|
|
57
|
+
function terminalText(value) {
|
|
58
|
+
return stripVTControlCharacters(value).replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, "");
|
|
59
|
+
}
|
|
60
|
+
function createTerminal(options = {}) {
|
|
61
|
+
const output = options.stdout ?? process.stdout;
|
|
62
|
+
const errors = options.stderr ?? process.stderr;
|
|
63
|
+
const environment = () => options.env ?? process.env;
|
|
64
|
+
const arguments_ = () => options.argv ?? process.argv.slice(2);
|
|
65
|
+
const isRich = () => Boolean(output.isTTY) && !environment().CI && environment().TERM !== "dumb" && environment().NO_COLOR === void 0;
|
|
66
|
+
const canAnimate = () => isRich() && Boolean(process.stdin.isTTY) && output === process.stdout && errors === process.stderr && !arguments_().includes("--verbose");
|
|
67
|
+
const paint = (text, code, rich = isRich()) => rich ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
68
|
+
const accent = (text, edge = false) => paint(text, oliveCode(environment(), edge));
|
|
69
|
+
const write = (text) => output.write(`${text}
|
|
70
|
+
`);
|
|
71
|
+
const wordmark = () => {
|
|
72
|
+
const columns = output.columns ?? 80;
|
|
73
|
+
const lines = columns >= Math.max(...WORDMARK.map((line) => line.length)) + 2 ? WORDMARK : SMALL_WORDMARK;
|
|
74
|
+
if (!isRich() || columns < Math.max(...lines.map((line) => line.length)) + 2) {
|
|
75
|
+
write(` ${accent("THALLY")}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const lettering = lines.map((line) => line.split(/(█+)/).map((part) => accent(part, !part.includes("\u2588"))).join(""));
|
|
79
|
+
for (const line of lettering) write(` ${line}`);
|
|
80
|
+
};
|
|
81
|
+
const diagnostic = (message, isError) => {
|
|
82
|
+
const rich = isRich() && Boolean(errors.isTTY);
|
|
83
|
+
const symbol = isError ? rich ? "\u25A0" : "[error]" : rich ? "\u25B2" : "[warn]";
|
|
84
|
+
const line = ` ${paint(symbol, isError ? 31 : 33, rich)} ${terminalText(message)}`;
|
|
85
|
+
if (errors === process.stderr) console.warn(line);
|
|
86
|
+
else errors.write(`${line}
|
|
87
|
+
`);
|
|
88
|
+
};
|
|
89
|
+
const render = {
|
|
90
|
+
/** Whether interactive decoration is supported by the current output. */
|
|
91
|
+
get isRich() {
|
|
92
|
+
return isRich();
|
|
93
|
+
},
|
|
94
|
+
/** Whether the caller requested detailed subprocess output. */
|
|
95
|
+
get isVerbose() {
|
|
96
|
+
return arguments_().includes("--verbose");
|
|
97
|
+
},
|
|
98
|
+
/** Introduce a command with the responsive wordmark and its immediate task. */
|
|
99
|
+
intro(command, version, subtitle = PRODUCT_TAGLINE) {
|
|
100
|
+
const heading = `${terminalText(command)}${version ? ` ${isRich() ? "\xB7" : "-"} v${terminalText(version)}` : ""}`;
|
|
101
|
+
write("");
|
|
102
|
+
wordmark();
|
|
103
|
+
write("");
|
|
104
|
+
write(` ${PRODUCT_TAGLINE}`);
|
|
105
|
+
if (subtitle !== PRODUCT_TAGLINE) write(` ${paint(terminalText(subtitle), 2)}`);
|
|
106
|
+
write("");
|
|
107
|
+
if (isRich() && output === process.stdout) intro(accent(heading));
|
|
108
|
+
else write(` ${accent(heading)}`);
|
|
109
|
+
write("");
|
|
110
|
+
},
|
|
111
|
+
/** Display a source, destination, or result with a consistent label. */
|
|
112
|
+
detail(label, value) {
|
|
113
|
+
write(` ${paint(terminalText(label).padEnd(10), 2)} ${terminalText(value)}`);
|
|
114
|
+
},
|
|
115
|
+
/** Display a neutral fact without implying success. */
|
|
116
|
+
info(message) {
|
|
117
|
+
write(` ${paint(isRich() ? "\u2502" : "-", 2)} ${terminalText(message)}`);
|
|
118
|
+
},
|
|
119
|
+
/** Record a completed operation in the retained transcript. */
|
|
120
|
+
success(message) {
|
|
121
|
+
write(` ${accent(isRich() ? "\u2713" : "[ok]")} ${terminalText(message)}`);
|
|
122
|
+
},
|
|
123
|
+
/** Keep warnings visible on stderr even when successful task logs are quiet. */
|
|
124
|
+
warn(message) {
|
|
125
|
+
diagnostic(message, false);
|
|
126
|
+
},
|
|
127
|
+
/** Keep errors visible on stderr and separate from machine-readable stdout. */
|
|
128
|
+
error(message) {
|
|
129
|
+
diagnostic(message, true);
|
|
130
|
+
},
|
|
131
|
+
/** Group next steps or results without a fixed-width box. */
|
|
132
|
+
section(title, lines) {
|
|
133
|
+
write("");
|
|
134
|
+
write(` ${paint(terminalText(title), 1)}`);
|
|
135
|
+
for (const line of lines) write(` ${terminalText(line)}`);
|
|
136
|
+
write("");
|
|
137
|
+
},
|
|
138
|
+
/** Make the next action prominent while retaining one intact shell command. */
|
|
139
|
+
nextAction(title, command) {
|
|
140
|
+
const cleanTitle = terminalText(title);
|
|
141
|
+
const cleanCommand = terminalText(command);
|
|
142
|
+
write("");
|
|
143
|
+
if (!isRich()) {
|
|
144
|
+
write(` Next action: ${cleanTitle}`);
|
|
145
|
+
write(" Copy and run this command:");
|
|
146
|
+
write(` ${cleanCommand}`);
|
|
147
|
+
write("");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const label = `NEXT \u2192 ${cleanTitle}`;
|
|
151
|
+
write(` ${paint(paint(paint(` ${label} `, 1), 30), oliveCode(environment()).replace(/^38;/, "48;"))}`);
|
|
152
|
+
write("");
|
|
153
|
+
write(" Copy and run to open your local preview:");
|
|
154
|
+
const canFrame = !cleanCommand.includes("\n") && cleanCommand.length + 8 <= (output.columns ?? 80);
|
|
155
|
+
if (canFrame) {
|
|
156
|
+
const width = cleanCommand.length + 4;
|
|
157
|
+
write(` ${accent(`\u256D${"\u2500".repeat(width)}\u256E`)}`);
|
|
158
|
+
write(` ${accent("\u2502")}${" ".repeat(width)}${accent("\u2502")}`);
|
|
159
|
+
write(` ${accent("\u2502")} ${accent(paint(cleanCommand, 1))} ${accent("\u2502")}`);
|
|
160
|
+
write(` ${accent("\u2502")}${" ".repeat(width)}${accent("\u2502")}`);
|
|
161
|
+
write(` ${accent(`\u2570${"\u2500".repeat(width)}\u256F`)}`);
|
|
162
|
+
} else write(` ${accent(paint(cleanCommand, 1))}`);
|
|
163
|
+
write("");
|
|
164
|
+
},
|
|
165
|
+
/** Finish a command without masking warnings or incomplete validation. */
|
|
166
|
+
outro(message) {
|
|
167
|
+
if (isRich() && output === process.stdout) outro(terminalText(message));
|
|
168
|
+
else {
|
|
169
|
+
write("");
|
|
170
|
+
write(` ${terminalText(message)}`);
|
|
171
|
+
write("");
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
/** Render progress, retain the outcome, and always release Clack's cursor hooks. */
|
|
175
|
+
async step(label, action, completedLabel = label) {
|
|
176
|
+
const progress = canAnimate() ? spinner({ indicator: "timer" }) : void 0;
|
|
177
|
+
const releaseAccent = progress ? acquireTerminalAccent() : void 0;
|
|
178
|
+
const cleanLabel = terminalText(label).replace(/\n/g, " ");
|
|
179
|
+
const limit = Math.max(8, (output.columns ?? 80) - 16);
|
|
180
|
+
const fit = (message) => Array.from(terminalText(message).replace(/\n/g, " ")).slice(0, limit).join("");
|
|
181
|
+
let activity = cleanLabel;
|
|
182
|
+
const began = Date.now();
|
|
183
|
+
const update = (message) => {
|
|
184
|
+
const next = terminalText(message).replace(/\n/g, " ");
|
|
185
|
+
if (next === activity) return;
|
|
186
|
+
activity = next;
|
|
187
|
+
if (progress) progress.message(fit(next));
|
|
188
|
+
else render.info(next);
|
|
189
|
+
};
|
|
190
|
+
const heartbeat = !progress ? setInterval(() => {
|
|
191
|
+
render.info(`${activity} (${Math.floor((Date.now() - began) / 1e3)}s elapsed)`);
|
|
192
|
+
}, 15e3) : void 0;
|
|
193
|
+
const cancel = () => {
|
|
194
|
+
progress?.stop("Cancelled", 1);
|
|
195
|
+
releaseAccent?.();
|
|
196
|
+
process.exit(130);
|
|
197
|
+
};
|
|
198
|
+
const terminate = () => {
|
|
199
|
+
progress?.stop("Cancelled", 1);
|
|
200
|
+
releaseAccent?.();
|
|
201
|
+
process.exit(143);
|
|
202
|
+
};
|
|
203
|
+
if (progress) {
|
|
204
|
+
process.once("SIGINT", cancel);
|
|
205
|
+
process.once("SIGTERM", terminate);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
if (progress) progress.start(fit(cleanLabel));
|
|
209
|
+
else render.info(cleanLabel);
|
|
210
|
+
const result = await action(update);
|
|
211
|
+
if (progress) progress.stop(terminalText(completedLabel));
|
|
212
|
+
else render.success(completedLabel);
|
|
213
|
+
return result;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (progress) progress.stop(`${cleanLabel}: failed`, 2);
|
|
216
|
+
else render.error(`${cleanLabel}: failed`);
|
|
217
|
+
throw error;
|
|
218
|
+
} finally {
|
|
219
|
+
releaseAccent?.();
|
|
220
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
221
|
+
if (progress) {
|
|
222
|
+
process.removeListener("SIGINT", cancel);
|
|
223
|
+
process.removeListener("SIGTERM", terminate);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
return render;
|
|
229
|
+
}
|
|
230
|
+
var terminal = createTerminal();
|
|
231
|
+
function formatHelp(text) {
|
|
232
|
+
if (!terminal.isRich) return text;
|
|
233
|
+
const accent = `\x1B[${oliveCode(process.env)}m`;
|
|
234
|
+
return text.split("\n").map((line) => {
|
|
235
|
+
const heading = line.trimStart();
|
|
236
|
+
if (heading.length < 2 || !heading.endsWith(":")) return line;
|
|
237
|
+
const indentation = line.slice(0, line.length - heading.length);
|
|
238
|
+
return `${indentation}${accent}${heading}\x1B[0m`;
|
|
239
|
+
}).join("\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export {
|
|
243
|
+
PRODUCT_TAGLINE,
|
|
244
|
+
acquireTerminalAccent,
|
|
245
|
+
terminalText,
|
|
246
|
+
createTerminal,
|
|
247
|
+
terminal,
|
|
248
|
+
formatHelp
|
|
249
|
+
};
|