@moku-labs/web 1.12.4 → 1.13.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/dist/index.cjs +53 -338
- package/dist/index.mjs +31 -316
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -15,6 +15,7 @@ let preact_render_to_string = require("preact-render-to-string");
|
|
|
15
15
|
let node_child_process = require("node:child_process");
|
|
16
16
|
let node_readline = require("node:readline");
|
|
17
17
|
let node_url = require("node:url");
|
|
18
|
+
let _moku_labs_common_cli = require("@moku-labs/common/cli");
|
|
18
19
|
let gray_matter = require("gray-matter");
|
|
19
20
|
gray_matter = require_convention.__toESM(gray_matter, 1);
|
|
20
21
|
let _shikijs_rehype = require("@shikijs/rehype");
|
|
@@ -6107,7 +6108,7 @@ async function readWranglerConfig(cwd) {
|
|
|
6107
6108
|
/** Relative path of the generated wrangler config. */
|
|
6108
6109
|
const WRANGLER_PATH = "wrangler.jsonc";
|
|
6109
6110
|
/** Relative path of the generated GitHub Actions workflow. */
|
|
6110
|
-
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6111
|
+
const WORKFLOW_PATH$1 = ".github/workflows/deploy.yml";
|
|
6111
6112
|
/** Wrangler `compatibility_date` used when the deploy config does not pin one. */
|
|
6112
6113
|
const DEFAULT_COMPATIBILITY_DATE = "2024-01-01";
|
|
6113
6114
|
/**
|
|
@@ -6165,12 +6166,12 @@ async function writeScaffolding(input) {
|
|
|
6165
6166
|
result
|
|
6166
6167
|
});
|
|
6167
6168
|
if (ci) await reconcile({
|
|
6168
|
-
relativePath: WORKFLOW_PATH,
|
|
6169
|
+
relativePath: WORKFLOW_PATH$1,
|
|
6169
6170
|
expected: generateGithubWorkflow({
|
|
6170
6171
|
slug,
|
|
6171
6172
|
...options.workflowTrigger ? { trigger: options.workflowTrigger } : {}
|
|
6172
6173
|
}),
|
|
6173
|
-
existing: await readMaybe(cwd, WORKFLOW_PATH),
|
|
6174
|
+
existing: await readMaybe(cwd, WORKFLOW_PATH$1),
|
|
6174
6175
|
cwd,
|
|
6175
6176
|
check,
|
|
6176
6177
|
result
|
|
@@ -6868,30 +6869,39 @@ async function resolveTrigger(ctx, choice) {
|
|
|
6868
6869
|
if (choice === 0) return "auto";
|
|
6869
6870
|
return await ctx.state.select("How should the versioned deploy be triggered?", ["On a version tag push (v*) + the manual Run-workflow button", "Manual Run-workflow button only (workflow_dispatch)"]) === 0 ? "versioned-tag" : "dispatch";
|
|
6870
6871
|
}
|
|
6872
|
+
/** Relative path of the GitHub Actions workflow the deploy plugin scaffolds. */
|
|
6873
|
+
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6871
6874
|
/**
|
|
6872
6875
|
* Offer to scaffold a GitHub Actions deploy workflow, letting the user choose how it is
|
|
6873
|
-
* triggered, then remind them which repo secrets to add.
|
|
6876
|
+
* triggered, then remind them which repo secrets to add. Short-circuits WITHOUT prompting
|
|
6877
|
+
* when {@link WORKFLOW_PATH} already exists — CI is already wired and the scaffold is
|
|
6878
|
+
* idempotent (a second setup would only no-op), so there is nothing to ask; it just
|
|
6879
|
+
* confirms the file and re-shows the secrets reminder. A no-op past a "skip" choice.
|
|
6874
6880
|
*
|
|
6875
6881
|
* @param ctx - The cli plugin context.
|
|
6882
|
+
* @param cwd - The project root (where `.github/workflows/deploy.yml` lives).
|
|
6876
6883
|
* @returns Resolves once any chosen workflow has been scaffolded.
|
|
6877
6884
|
* @example
|
|
6878
|
-
* await offerWorkflowSetup(ctx);
|
|
6885
|
+
* await offerWorkflowSetup(ctx, process.cwd());
|
|
6879
6886
|
*/
|
|
6880
|
-
async function offerWorkflowSetup(ctx) {
|
|
6887
|
+
async function offerWorkflowSetup(ctx, cwd) {
|
|
6881
6888
|
ctx.state.render.heading("Automate future deploys (GitHub Actions)");
|
|
6889
|
+
if ((0, node_fs.existsSync)(node_path$1.default.join(cwd, WORKFLOW_PATH))) {
|
|
6890
|
+
ctx.state.render.check(true, `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6891
|
+
ctx.state.render.info(SECRETS_HELP);
|
|
6892
|
+
return;
|
|
6893
|
+
}
|
|
6882
6894
|
const trigger = await resolveTrigger(ctx, await ctx.state.select("Set up a deploy workflow?", [
|
|
6883
6895
|
"Auto-deploy on every push to main",
|
|
6884
6896
|
"Manual / versioned deploy (choose trigger)",
|
|
6885
6897
|
"Skip for now"
|
|
6886
6898
|
]));
|
|
6887
6899
|
if (trigger === null) return;
|
|
6888
|
-
const
|
|
6900
|
+
const wrote = (await ctx.require(deployPlugin).init({
|
|
6889
6901
|
ci: true,
|
|
6890
6902
|
workflowTrigger: trigger
|
|
6891
|
-
});
|
|
6892
|
-
|
|
6893
|
-
const wrote = result.written.includes(workflowPath);
|
|
6894
|
-
ctx.state.render.check(true, wrote ? `wrote ${workflowPath}` : `${workflowPath} already exists (left unchanged)`);
|
|
6903
|
+
})).written.includes(WORKFLOW_PATH);
|
|
6904
|
+
ctx.state.render.check(true, wrote ? `wrote ${WORKFLOW_PATH}` : `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6895
6905
|
ctx.state.render.info(SECRETS_HELP);
|
|
6896
6906
|
}
|
|
6897
6907
|
/**
|
|
@@ -7087,7 +7097,7 @@ async function runDeployWizard(ctx, options) {
|
|
|
7087
7097
|
ctx.state.render.check(notFoundOk, `${ctx.config.notFoundFile} present`, notFoundOk ? void 0 : "Set build.notFound so the SSG emits it (CF Pages else flips to SPA mode).");
|
|
7088
7098
|
ctx.state.render.info("Tip: run `bun run preview` to eyeball the built site before deploying.");
|
|
7089
7099
|
const outcome = await runDeployStep(ctx, options);
|
|
7090
|
-
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx);
|
|
7100
|
+
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx, cwd);
|
|
7091
7101
|
return outcome;
|
|
7092
7102
|
}
|
|
7093
7103
|
//#endregion
|
|
@@ -8035,312 +8045,17 @@ function networkUrl(port, source = node_os.networkInterfaces) {
|
|
|
8035
8045
|
return ip === null ? null : `http://${ip}:${port}`;
|
|
8036
8046
|
}
|
|
8037
8047
|
//#endregion
|
|
8038
|
-
//#region src/plugins/cli/render/
|
|
8039
|
-
/**
|
|
8040
|
-
* @file cli plugin — TTY/NO_COLOR-aware ANSI color + box-drawing helpers shared by
|
|
8041
|
-
* the Panel renderer. Modeled on the legacy `scripts/_log.ts`: color and box glyphs
|
|
8042
|
-
* are emitted only on a real TTY with `NO_COLOR` unset; otherwise plain ASCII so
|
|
8043
|
-
* CI logs and pipes stay readable.
|
|
8044
|
-
*/
|
|
8045
|
-
/** The ANSI escape byte (ESC, `0x1b`), built so no literal control char is in source. */
|
|
8046
|
-
const ESC = String.fromCodePoint(27);
|
|
8047
|
-
/** ANSI SGR codes used by the Panel renderer (each prefixed with the ESC byte). */
|
|
8048
|
-
const ANSI = {
|
|
8049
|
-
reset: `${ESC}[0m`,
|
|
8050
|
-
bold: `${ESC}[1m`,
|
|
8051
|
-
dim: `${ESC}[2m`,
|
|
8052
|
-
red: `${ESC}[31m`,
|
|
8053
|
-
green: `${ESC}[32m`,
|
|
8054
|
-
yellow: `${ESC}[33m`,
|
|
8055
|
-
blue: `${ESC}[34m`,
|
|
8056
|
-
magenta: `${ESC}[35m`,
|
|
8057
|
-
cyan: `${ESC}[36m`,
|
|
8058
|
-
gray: `${ESC}[90m`
|
|
8059
|
-
};
|
|
8060
|
-
/**
|
|
8061
|
-
* The Moku brand pink (`#FF1E6F`) as an RGB triple, used for 24-bit truecolor output.
|
|
8062
|
-
* Degrades to {@link ANSI.magenta} on a 16-color TTY and to plain text off a TTY.
|
|
8063
|
-
*/
|
|
8064
|
-
const BRAND_PINK = {
|
|
8065
|
-
r: 255,
|
|
8066
|
-
g: 30,
|
|
8067
|
-
b: 111
|
|
8068
|
-
};
|
|
8069
|
-
/**
|
|
8070
|
-
* Build a 24-bit (truecolor) SGR foreground escape for the given RGB triple.
|
|
8071
|
-
*
|
|
8072
|
-
* @param r - Red channel (0–255).
|
|
8073
|
-
* @param g - Green channel (0–255).
|
|
8074
|
-
* @param b - Blue channel (0–255).
|
|
8075
|
-
* @returns The `ESC[38;2;r;g;bm` foreground sequence.
|
|
8076
|
-
* @example
|
|
8077
|
-
* fg24(255, 30, 111); // "\x1b[38;2;255;30;111m"
|
|
8078
|
-
*/
|
|
8079
|
-
function fg24(r, g, b) {
|
|
8080
|
-
return `${ESC}[38;2;${r};${g};${b}m`;
|
|
8081
|
-
}
|
|
8082
|
-
/** ANSI: erase the entire current line, leaving the cursor where it is. */
|
|
8083
|
-
const CLEAR_LINE = `${ESC}[2K`;
|
|
8084
|
-
/** ANSI: erase from the cursor to the end of the screen (drops stale trailing rows). */
|
|
8085
|
-
const CLEAR_BELOW = `${ESC}[0J`;
|
|
8086
|
-
/**
|
|
8087
|
-
* Braille spinner frames for live "working…" indicators on a TTY (advance one per tick).
|
|
8088
|
-
* Off a TTY the Panel never animates, so this is unused in plain/CI output.
|
|
8089
|
-
*/
|
|
8090
|
-
const SPINNER_FRAMES = [
|
|
8091
|
-
"⠋",
|
|
8092
|
-
"⠙",
|
|
8093
|
-
"⠹",
|
|
8094
|
-
"⠸",
|
|
8095
|
-
"⠼",
|
|
8096
|
-
"⠴",
|
|
8097
|
-
"⠦",
|
|
8098
|
-
"⠧",
|
|
8099
|
-
"⠇",
|
|
8100
|
-
"⠏"
|
|
8101
|
-
];
|
|
8102
|
-
/**
|
|
8103
|
-
* The ANSI sequence to move the cursor up `n` lines (empty string for `n <= 0`). The
|
|
8104
|
-
* Panel uses it to repaint a live block in place — move up over the previous draw, then
|
|
8105
|
-
* rewrite each row — so progress updates a fixed region instead of scrolling new lines.
|
|
8106
|
-
*
|
|
8107
|
-
* @param n - Number of lines to move the cursor up.
|
|
8108
|
-
* @returns The cursor-up escape sequence, or `""` when `n <= 0`.
|
|
8109
|
-
* @example
|
|
8110
|
-
* cursorUp(3); // "\x1b[3A"
|
|
8111
|
-
*/
|
|
8112
|
-
function cursorUp(n) {
|
|
8113
|
-
return n > 0 ? `${ESC}[${n}A` : "";
|
|
8114
|
-
}
|
|
8115
|
-
/** Unicode rounded box glyphs used when output is a color-capable TTY. */
|
|
8116
|
-
const UNICODE_BOX = {
|
|
8117
|
-
topLeft: "╭",
|
|
8118
|
-
topRight: "╮",
|
|
8119
|
-
bottomLeft: "╰",
|
|
8120
|
-
bottomRight: "╯",
|
|
8121
|
-
horizontal: "─",
|
|
8122
|
-
vertical: "│"
|
|
8123
|
-
};
|
|
8124
|
-
/** ASCII box glyphs used when output is piped/CI (plain mode). */
|
|
8125
|
-
const ASCII_BOX = {
|
|
8126
|
-
topLeft: "+",
|
|
8127
|
-
topRight: "+",
|
|
8128
|
-
bottomLeft: "+",
|
|
8129
|
-
bottomRight: "+",
|
|
8130
|
-
horizontal: "-",
|
|
8131
|
-
vertical: "|"
|
|
8132
|
-
};
|
|
8133
|
-
/**
|
|
8134
|
-
* Matches every ANSI SGR escape sequence (used to measure visible width). Built from
|
|
8135
|
-
* the {@link ESC} byte so no literal control character appears in the source regex.
|
|
8136
|
-
*/
|
|
8137
|
-
const ANSI_PATTERN = new RegExp(String.raw`${ESC}\[[0-9;]*m`, "g");
|
|
8138
|
-
/**
|
|
8139
|
-
* Whether ANSI color/box glyphs should be emitted: a TTY stream with `NO_COLOR`
|
|
8140
|
-
* unset. Reads `process.stdout.isTTY` and `process.env.NO_COLOR` by default so the
|
|
8141
|
-
* renderer auto-degrades in CI and pipes, exactly like the legacy logger.
|
|
8142
|
-
*
|
|
8143
|
-
* @param stream - Stream to probe for `isTTY` (defaults to `process.stdout`).
|
|
8144
|
-
* @param noColor - The `NO_COLOR` value (defaults to `process.env.NO_COLOR`).
|
|
8145
|
-
* @returns `true` when color should be used.
|
|
8146
|
-
* @example
|
|
8147
|
-
* supportsColor(); // true in an interactive terminal
|
|
8148
|
-
*/
|
|
8149
|
-
function supportsColor(stream = process.stdout, noColor = process.env.NO_COLOR) {
|
|
8150
|
-
return stream.isTTY === true && noColor === void 0;
|
|
8151
|
-
}
|
|
8152
|
-
/**
|
|
8153
|
-
* Whether the terminal advertises 24-bit (truecolor) support via `COLORTERM`, so the
|
|
8154
|
-
* renderer may emit the exact brand pink ({@link BRAND_PINK}) instead of the 16-color
|
|
8155
|
-
* `magenta` approximation. Always layered on top of {@link supportsColor} — truecolor
|
|
8156
|
-
* is never used when color itself is disabled.
|
|
8157
|
-
*
|
|
8158
|
-
* @param colorTerm - The `COLORTERM` value (defaults to `process.env.COLORTERM`).
|
|
8159
|
-
* @returns `true` when `COLORTERM` is `truecolor` or `24bit`.
|
|
8160
|
-
* @example
|
|
8161
|
-
* supportsTruecolor("truecolor"); // true
|
|
8162
|
-
*/
|
|
8163
|
-
function supportsTruecolor(colorTerm = process.env.COLORTERM) {
|
|
8164
|
-
return colorTerm === "truecolor" || colorTerm === "24bit";
|
|
8165
|
-
}
|
|
8166
|
-
/**
|
|
8167
|
-
* The braille spinner glyph for a given elapsed time, advancing one frame per
|
|
8168
|
-
* `frameMs`. Deriving the frame from wall-clock elapsed (rather than a tick counter)
|
|
8169
|
-
* keeps the spinner correct even when the animation ticker is briefly starved by a
|
|
8170
|
-
* synchronous build phase and several ticks coalesce — the glyph still reflects real
|
|
8171
|
-
* elapsed time instead of freezing on a stale frame.
|
|
8172
|
-
*
|
|
8173
|
-
* @param elapsedMs - Milliseconds since the live region opened.
|
|
8174
|
-
* @param frameMs - Milliseconds per frame (defaults to `80`).
|
|
8175
|
-
* @returns The active spinner glyph.
|
|
8176
|
-
* @example
|
|
8177
|
-
* spinnerFrameAt(240); // "⠹" (the 4th frame at 80ms/frame)
|
|
8178
|
-
*/
|
|
8179
|
-
function spinnerFrameAt(elapsedMs, frameMs = 80) {
|
|
8180
|
-
return SPINNER_FRAMES[Math.floor(Math.max(0, elapsedMs) / frameMs) % SPINNER_FRAMES.length] ?? "⠋";
|
|
8181
|
-
}
|
|
8182
|
-
/**
|
|
8183
|
-
* Select the box glyph set for the given color mode (Unicode on a TTY, ASCII off it).
|
|
8184
|
-
*
|
|
8185
|
-
* @param color - Whether color/Unicode output is enabled.
|
|
8186
|
-
* @returns The matching {@link BoxGlyphs} set.
|
|
8187
|
-
* @example
|
|
8188
|
-
* const glyphs = boxGlyphs(supportsColor());
|
|
8189
|
-
*/
|
|
8190
|
-
function boxGlyphs(color) {
|
|
8191
|
-
return color ? UNICODE_BOX : ASCII_BOX;
|
|
8192
|
-
}
|
|
8193
|
-
/**
|
|
8194
|
-
* The visible width of a string, ignoring any ANSI escape sequences it contains.
|
|
8195
|
-
*
|
|
8196
|
-
* @param text - The (possibly colorized) text to measure.
|
|
8197
|
-
* @returns The number of visible characters.
|
|
8198
|
-
* @example
|
|
8199
|
-
* visibleWidth(`${ANSI.red}hi${ANSI.reset}`); // 2
|
|
8200
|
-
*/
|
|
8201
|
-
function visibleWidth(text) {
|
|
8202
|
-
return text.replaceAll(ANSI_PATTERN, "").length;
|
|
8203
|
-
}
|
|
8204
|
-
/**
|
|
8205
|
-
* Build a {@link Palette} bound to a fixed color mode. When `color` is `false` every
|
|
8206
|
-
* helper returns its input unchanged, so the same render code path produces plain
|
|
8207
|
-
* output in CI/pipes.
|
|
8208
|
-
*
|
|
8209
|
-
* @param color - Whether color is enabled (typically `supportsColor()`).
|
|
8210
|
-
* @param truecolor - Whether 24-bit output is enabled (typically `supportsTruecolor()`);
|
|
8211
|
-
* only consulted by {@link Palette.pink}. Defaults to `false` (16-color magenta).
|
|
8212
|
-
* @returns The bound color palette.
|
|
8213
|
-
* @example
|
|
8214
|
-
* const palette = makePalette(supportsColor(), supportsTruecolor());
|
|
8215
|
-
* const line = palette.green("done");
|
|
8216
|
-
*/
|
|
8217
|
-
function makePalette(color, truecolor = false) {
|
|
8218
|
-
return {
|
|
8219
|
-
enabled: color,
|
|
8220
|
-
/**
|
|
8221
|
-
* Wrap text in the given ANSI code (returns it unchanged when color is off).
|
|
8222
|
-
*
|
|
8223
|
-
* @param code - The ANSI SGR code to apply.
|
|
8224
|
-
* @param text - The text to colorize.
|
|
8225
|
-
* @returns The colorized (or unchanged) text.
|
|
8226
|
-
* @example
|
|
8227
|
-
* palette.paint(ANSI.green, "ok");
|
|
8228
|
-
*/
|
|
8229
|
-
paint(code, text) {
|
|
8230
|
-
return color ? `${code}${text}${ANSI.reset}` : text;
|
|
8231
|
-
},
|
|
8232
|
-
/**
|
|
8233
|
-
* Bold the given text (no-op in plain mode).
|
|
8234
|
-
*
|
|
8235
|
-
* @param text - The text to embolden.
|
|
8236
|
-
* @returns The bold (or unchanged) text.
|
|
8237
|
-
* @example
|
|
8238
|
-
* palette.bold("title");
|
|
8239
|
-
*/
|
|
8240
|
-
bold(text) {
|
|
8241
|
-
return this.paint(ANSI.bold, text);
|
|
8242
|
-
},
|
|
8243
|
-
/**
|
|
8244
|
-
* Dim the given text (no-op in plain mode).
|
|
8245
|
-
*
|
|
8246
|
-
* @param text - The text to dim.
|
|
8247
|
-
* @returns The dim (or unchanged) text.
|
|
8248
|
-
* @example
|
|
8249
|
-
* palette.dim("· 84ms");
|
|
8250
|
-
*/
|
|
8251
|
-
dim(text) {
|
|
8252
|
-
return this.paint(ANSI.dim, text);
|
|
8253
|
-
},
|
|
8254
|
-
/**
|
|
8255
|
-
* Color the given text green (no-op in plain mode).
|
|
8256
|
-
*
|
|
8257
|
-
* @param text - The text to colorize.
|
|
8258
|
-
* @returns The green (or unchanged) text.
|
|
8259
|
-
* @example
|
|
8260
|
-
* palette.green("✓");
|
|
8261
|
-
*/
|
|
8262
|
-
green(text) {
|
|
8263
|
-
return this.paint(ANSI.green, text);
|
|
8264
|
-
},
|
|
8265
|
-
/**
|
|
8266
|
-
* Color the given text yellow (no-op in plain mode).
|
|
8267
|
-
*
|
|
8268
|
-
* @param text - The text to colorize.
|
|
8269
|
-
* @returns The yellow (or unchanged) text.
|
|
8270
|
-
* @example
|
|
8271
|
-
* palette.yellow("~");
|
|
8272
|
-
*/
|
|
8273
|
-
yellow(text) {
|
|
8274
|
-
return this.paint(ANSI.yellow, text);
|
|
8275
|
-
},
|
|
8276
|
-
/**
|
|
8277
|
-
* Color the given text red (no-op in plain mode).
|
|
8278
|
-
*
|
|
8279
|
-
* @param text - The text to colorize.
|
|
8280
|
-
* @returns The red (or unchanged) text.
|
|
8281
|
-
* @example
|
|
8282
|
-
* palette.red("✗");
|
|
8283
|
-
*/
|
|
8284
|
-
red(text) {
|
|
8285
|
-
return this.paint(ANSI.red, text);
|
|
8286
|
-
},
|
|
8287
|
-
/**
|
|
8288
|
-
* Color the given text cyan (no-op in plain mode).
|
|
8289
|
-
*
|
|
8290
|
-
* @param text - The text to colorize.
|
|
8291
|
-
* @returns The cyan (or unchanged) text.
|
|
8292
|
-
* @example
|
|
8293
|
-
* palette.cyan("http://localhost:4173");
|
|
8294
|
-
*/
|
|
8295
|
-
cyan(text) {
|
|
8296
|
-
return this.paint(ANSI.cyan, text);
|
|
8297
|
-
},
|
|
8298
|
-
/**
|
|
8299
|
-
* Color the given text the Moku brand pink: exact `#FF1E6F` (24-bit) when truecolor
|
|
8300
|
-
* is enabled, the 16-color `magenta` approximation otherwise, unchanged in plain mode.
|
|
8301
|
-
*
|
|
8302
|
-
* @param text - The text to colorize.
|
|
8303
|
-
* @returns The pink (or unchanged) text.
|
|
8304
|
-
* @example
|
|
8305
|
-
* palette.pink("▟▙ moku web");
|
|
8306
|
-
*/
|
|
8307
|
-
pink(text) {
|
|
8308
|
-
if (!color) return text;
|
|
8309
|
-
if (truecolor) return `${fg24(BRAND_PINK.r, BRAND_PINK.g, BRAND_PINK.b)}${text}${ANSI.reset}`;
|
|
8310
|
-
return this.paint(ANSI.magenta, text);
|
|
8311
|
-
}
|
|
8312
|
-
};
|
|
8313
|
-
}
|
|
8048
|
+
//#region src/plugins/cli/render/panel.ts
|
|
8314
8049
|
/**
|
|
8315
|
-
*
|
|
8316
|
-
*
|
|
8317
|
-
*
|
|
8318
|
-
*
|
|
8319
|
-
*
|
|
8320
|
-
* @
|
|
8321
|
-
*
|
|
8322
|
-
*
|
|
8323
|
-
* @returns The boxed lines (top border, content rows, bottom border).
|
|
8324
|
-
* @example
|
|
8325
|
-
* box(["Local: http://localhost:4173"], true, 62);
|
|
8050
|
+
* @file cli plugin — the Panel renderer (the "Velocity Lockup" CLI identity). Produces
|
|
8051
|
+
* the `▟▙ moku web` lockup + version/runtime banner, the live phase tree with an
|
|
8052
|
+
* animated indeterminate build bar, the BUILD summary + throughput sparkline, the
|
|
8053
|
+
* server-ready rail with a persistent breathing `◍ live` idle pulse, the compact
|
|
8054
|
+
* rebuild line, the deploy result, and diagnostic heading/check rows. TTY/`NO_COLOR`-
|
|
8055
|
+
* aware via {@link makePalette} (24-bit brand pink when truecolor is available, the
|
|
8056
|
+
* 16-color magenta approximation otherwise, plain text off a TTY); every line is
|
|
8057
|
+
* written through an injectable sink so tests can capture it.
|
|
8326
8058
|
*/
|
|
8327
|
-
function box(lines, color, minInnerWidth = 0) {
|
|
8328
|
-
const glyphs = boxGlyphs(color);
|
|
8329
|
-
const inner = Math.max(0, minInnerWidth, ...lines.map((line) => visibleWidth(line)));
|
|
8330
|
-
const horizontal = glyphs.horizontal.repeat(inner + 2);
|
|
8331
|
-
const top = `${glyphs.topLeft}${horizontal}${glyphs.topRight}`;
|
|
8332
|
-
const bottom = `${glyphs.bottomLeft}${horizontal}${glyphs.bottomRight}`;
|
|
8333
|
-
return [
|
|
8334
|
-
top,
|
|
8335
|
-
...lines.map((line) => {
|
|
8336
|
-
const pad = " ".repeat(inner - visibleWidth(line));
|
|
8337
|
-
return `${glyphs.vertical} ${line}${pad} ${glyphs.vertical}`;
|
|
8338
|
-
}),
|
|
8339
|
-
bottom
|
|
8340
|
-
];
|
|
8341
|
-
}
|
|
8342
|
-
//#endregion
|
|
8343
|
-
//#region src/plugins/cli/render/panel.ts
|
|
8344
8059
|
/** Per-command label shown beside the lockup wordmark. */
|
|
8345
8060
|
const COMMAND_LABEL = {
|
|
8346
8061
|
build: "build",
|
|
@@ -8430,7 +8145,7 @@ function durationSuffix(palette, durationMs) {
|
|
|
8430
8145
|
* railLine(" ├─ ✓ pages", "· 12ms");
|
|
8431
8146
|
*/
|
|
8432
8147
|
function railLine(left, right, width = RAIL_WIDTH) {
|
|
8433
|
-
const gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right));
|
|
8148
|
+
const gap = Math.max(1, width - (0, _moku_labs_common_cli.visibleWidth)(left) - (0, _moku_labs_common_cli.visibleWidth)(right));
|
|
8434
8149
|
return `${left}${" ".repeat(gap)}${right}`;
|
|
8435
8150
|
}
|
|
8436
8151
|
/**
|
|
@@ -8471,8 +8186,8 @@ function createPanelRenderer(options = {}) {
|
|
|
8471
8186
|
process.stdout.write(chunk);
|
|
8472
8187
|
});
|
|
8473
8188
|
const now = options.now ?? Date.now;
|
|
8474
|
-
const color = options.color ?? supportsColor();
|
|
8475
|
-
const palette = makePalette(color, options.truecolor ?? (color && supportsTruecolor()));
|
|
8189
|
+
const color = options.color ?? (0, _moku_labs_common_cli.supportsColor)();
|
|
8190
|
+
const palette = (0, _moku_labs_common_cli.makePalette)(color, options.truecolor ?? (color && (0, _moku_labs_common_cli.supportsTruecolor)()));
|
|
8476
8191
|
const version = options.version ?? "dev";
|
|
8477
8192
|
const coreVersion = options.coreVersion;
|
|
8478
8193
|
const g = glyphSet(color);
|
|
@@ -8499,7 +8214,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8499
8214
|
const renderPhaseRow = (row) => {
|
|
8500
8215
|
const branch = palette.dim(g.tree);
|
|
8501
8216
|
if (row.done) return railLine(` ${branch} ${palette.green("✓")} ${row.name}`, palette.dim(`· ${row.durationMs}ms`));
|
|
8502
|
-
return ` ${branch} ${palette.cyan(spinnerFrameAt(now() - blockStartedAt, SPIN_MS))} ${palette.dim(row.name)}`;
|
|
8217
|
+
return ` ${branch} ${palette.cyan((0, _moku_labs_common_cli.spinnerFrameAt)(now() - blockStartedAt, SPIN_MS))} ${palette.dim(row.name)}`;
|
|
8503
8218
|
};
|
|
8504
8219
|
/**
|
|
8505
8220
|
* Render the indeterminate "comet" build bar — a short pink fill window sweeping across
|
|
@@ -8530,10 +8245,10 @@ function createPanelRenderer(options = {}) {
|
|
|
8530
8245
|
* paintPhaseBlock();
|
|
8531
8246
|
*/
|
|
8532
8247
|
const paintPhaseBlock = () => {
|
|
8533
|
-
let frame = cursorUp(phaseDrawn);
|
|
8534
|
-
for (const row of phaseRows) frame += `${CLEAR_LINE}${renderPhaseRow(row)}\n`;
|
|
8535
|
-
frame += `${CLEAR_LINE}${renderBuildBar(now() - blockStartedAt)}\n`;
|
|
8536
|
-
writeRaw(frame + CLEAR_BELOW);
|
|
8248
|
+
let frame = (0, _moku_labs_common_cli.cursorUp)(phaseDrawn);
|
|
8249
|
+
for (const row of phaseRows) frame += `${_moku_labs_common_cli.CLEAR_LINE}${renderPhaseRow(row)}\n`;
|
|
8250
|
+
frame += `${_moku_labs_common_cli.CLEAR_LINE}${renderBuildBar(now() - blockStartedAt)}\n`;
|
|
8251
|
+
writeRaw(frame + _moku_labs_common_cli.CLEAR_BELOW);
|
|
8537
8252
|
phaseDrawn = phaseRows.length + 1;
|
|
8538
8253
|
};
|
|
8539
8254
|
/**
|
|
@@ -8543,9 +8258,9 @@ function createPanelRenderer(options = {}) {
|
|
|
8543
8258
|
* paintRebuildLine();
|
|
8544
8259
|
*/
|
|
8545
8260
|
const paintRebuildLine = () => {
|
|
8546
|
-
const spinner = palette.cyan(spinnerFrameAt(now() - rebuildStartedAt, SPIN_MS));
|
|
8261
|
+
const spinner = palette.cyan((0, _moku_labs_common_cli.spinnerFrameAt)(now() - rebuildStartedAt, SPIN_MS));
|
|
8547
8262
|
const elapsed = palette.dim(`· ${((now() - rebuildStartedAt) / 1e3).toFixed(1)}s`);
|
|
8548
|
-
writeRaw(`\r${CLEAR_LINE} ${spinner} rebuilding ${rebuildLabel} ${elapsed}`);
|
|
8263
|
+
writeRaw(`\r${_moku_labs_common_cli.CLEAR_LINE} ${spinner} rebuilding ${rebuildLabel} ${elapsed}`);
|
|
8549
8264
|
};
|
|
8550
8265
|
/**
|
|
8551
8266
|
* Repaint the persistent in-place `◍ live` idle pulse beneath the serve panel — the
|
|
@@ -8556,7 +8271,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8556
8271
|
* paintIdleLine();
|
|
8557
8272
|
*/
|
|
8558
8273
|
const paintIdleLine = () => {
|
|
8559
|
-
writeRaw(`\r${CLEAR_LINE} ${Math.floor((now() - idleStartedAt) / 450) % 2 === 0 ? palette.pink(g.liveOn) : palette.dim(g.liveOff)} ${palette.dim("live · waiting for changes…")}`);
|
|
8274
|
+
writeRaw(`\r${_moku_labs_common_cli.CLEAR_LINE} ${Math.floor((now() - idleStartedAt) / 450) % 2 === 0 ? palette.pink(g.liveOn) : palette.dim(g.liveOff)} ${palette.dim("live · waiting for changes…")}`);
|
|
8560
8275
|
};
|
|
8561
8276
|
/**
|
|
8562
8277
|
* Advance whichever live region is active by one frame (driven by the shared ticker).
|
|
@@ -8683,9 +8398,9 @@ function createPanelRenderer(options = {}) {
|
|
|
8683
8398
|
built(summary) {
|
|
8684
8399
|
if (rebuilding) return;
|
|
8685
8400
|
if (color && phaseOpen) {
|
|
8686
|
-
let frame = cursorUp(phaseDrawn);
|
|
8687
|
-
for (const row of phaseRows) frame += `${CLEAR_LINE}${renderPhaseRow(row)}\n`;
|
|
8688
|
-
writeRaw(frame + CLEAR_BELOW);
|
|
8401
|
+
let frame = (0, _moku_labs_common_cli.cursorUp)(phaseDrawn);
|
|
8402
|
+
for (const row of phaseRows) frame += `${_moku_labs_common_cli.CLEAR_LINE}${renderPhaseRow(row)}\n`;
|
|
8403
|
+
writeRaw(frame + _moku_labs_common_cli.CLEAR_BELOW);
|
|
8689
8404
|
}
|
|
8690
8405
|
const phaseDurations = phaseRows.map((row) => row.durationMs).filter((value) => value !== void 0);
|
|
8691
8406
|
phaseOpen = false;
|
|
@@ -8700,7 +8415,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8700
8415
|
const rateLabel = palette.dim(`${rate} pages/s`);
|
|
8701
8416
|
lines.push(railLine(spark, rateLabel, BOX_INNER));
|
|
8702
8417
|
}
|
|
8703
|
-
writeBlock(box(lines, color, BOX_INNER));
|
|
8418
|
+
writeBlock((0, _moku_labs_common_cli.box)(lines, color, BOX_INNER));
|
|
8704
8419
|
},
|
|
8705
8420
|
/**
|
|
8706
8421
|
* Render the server-ready rail (Local / Network URLs + watched dirs) and, on a TTY,
|
|
@@ -8714,7 +8429,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8714
8429
|
const network = info.network ? palette.cyan(info.network) : palette.dim("unavailable");
|
|
8715
8430
|
const lines = [`${palette.green("➜")} ${palette.bold("Local")} ${palette.cyan(info.local)}`, `${palette.green("➜")} ${palette.bold("Network")} ${network}`];
|
|
8716
8431
|
if (info.watching && info.watching.length > 0) lines.push(`${palette.dim("watching")} ${palette.dim(info.watching.join(", "))}`);
|
|
8717
|
-
writeBlock(box(lines, color, BOX_INNER));
|
|
8432
|
+
writeBlock((0, _moku_labs_common_cli.box)(lines, color, BOX_INNER));
|
|
8718
8433
|
if (color) {
|
|
8719
8434
|
serveMode = true;
|
|
8720
8435
|
idle = true;
|
|
@@ -8760,7 +8475,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8760
8475
|
rebuilding = false;
|
|
8761
8476
|
const line = ` ${palette.green("✓")} rebuilt ${palette.bold(String(info.pageCount))} pages ${palette.dim(`· ${info.durationMs}ms · reloaded`)}`;
|
|
8762
8477
|
if (settledRebuild && color) {
|
|
8763
|
-
writeRaw(`\r${CLEAR_LINE}${line}\n`);
|
|
8478
|
+
writeRaw(`\r${_moku_labs_common_cli.CLEAR_LINE}${line}\n`);
|
|
8764
8479
|
resumeIdle();
|
|
8765
8480
|
return;
|
|
8766
8481
|
}
|
|
@@ -8785,7 +8500,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8785
8500
|
const id = result.deploymentId ? ` ${dot} ${palette.dim(result.deploymentId)}` : "";
|
|
8786
8501
|
lines.push(`${palette.dim("→")} ${palette.cyan(result.url)}${id}`);
|
|
8787
8502
|
} else if (result.deploymentId) lines.push(palette.dim(`id ${result.deploymentId}`));
|
|
8788
|
-
writeBlock(box(lines, color, BOX_INNER));
|
|
8503
|
+
writeBlock((0, _moku_labs_common_cli.box)(lines, color, BOX_INNER));
|
|
8789
8504
|
},
|
|
8790
8505
|
/**
|
|
8791
8506
|
* Render a neutral informational line.
|
|
@@ -8822,7 +8537,7 @@ function createPanelRenderer(options = {}) {
|
|
|
8822
8537
|
const wasRebuilding = rebuilding;
|
|
8823
8538
|
if (rebuilding) {
|
|
8824
8539
|
rebuilding = false;
|
|
8825
|
-
if (color) writeRaw(`\r${CLEAR_LINE}`);
|
|
8540
|
+
if (color) writeRaw(`\r${_moku_labs_common_cli.CLEAR_LINE}`);
|
|
8826
8541
|
}
|
|
8827
8542
|
writeError(` ${palette.red("✗")} ${message}`);
|
|
8828
8543
|
if (cause !== void 0) writeError(String(cause));
|
|
@@ -8883,9 +8598,9 @@ const YES_PATTERN = /^y(es)?$/i;
|
|
|
8883
8598
|
/** Prompt rail width — matches the renderer's `RAIL_WIDTH` so the hint aligns with other rows. */
|
|
8884
8599
|
const PROMPT_WIDTH = 66;
|
|
8885
8600
|
/** Whether the interactive prompts render with the MOKU marker styling (color/TTY only). */
|
|
8886
|
-
const PROMPT_COLOR = supportsColor();
|
|
8601
|
+
const PROMPT_COLOR = (0, _moku_labs_common_cli.supportsColor)();
|
|
8887
8602
|
/** Shared palette for the interactive prompts (same brand colors as the Panel renderer). */
|
|
8888
|
-
const PROMPT_PALETTE = makePalette(PROMPT_COLOR, PROMPT_COLOR && supportsTruecolor());
|
|
8603
|
+
const PROMPT_PALETTE = (0, _moku_labs_common_cli.makePalette)(PROMPT_COLOR, PROMPT_COLOR && (0, _moku_labs_common_cli.supportsTruecolor)());
|
|
8889
8604
|
/**
|
|
8890
8605
|
* Build the styled y/N confirm prompt: a brand `◆` marker + the question on the left,
|
|
8891
8606
|
* a dim `y / N` hint + cyan `›` caret right-aligned to {@link PROMPT_WIDTH}. Falls back
|
|
@@ -8900,7 +8615,7 @@ function confirmPrompt(question) {
|
|
|
8900
8615
|
if (!PROMPT_COLOR) return `${question} [y/N] `;
|
|
8901
8616
|
const left = ` ${PROMPT_PALETTE.pink("◆")} ${question}`;
|
|
8902
8617
|
const right = `${PROMPT_PALETTE.dim("y / N")} ${PROMPT_PALETTE.cyan("›")} `;
|
|
8903
|
-
const gap = Math.max(1, PROMPT_WIDTH - visibleWidth(left) - visibleWidth(right));
|
|
8618
|
+
const gap = Math.max(1, PROMPT_WIDTH - (0, _moku_labs_common_cli.visibleWidth)(left) - (0, _moku_labs_common_cli.visibleWidth)(right));
|
|
8904
8619
|
return `${left}${" ".repeat(gap)}${right}`;
|
|
8905
8620
|
}
|
|
8906
8621
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { renderToString } from "preact-render-to-string";
|
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
14
|
import { createInterface } from "node:readline";
|
|
15
15
|
import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
|
|
16
|
+
import { CLEAR_BELOW, CLEAR_LINE, box, cursorUp, makePalette, spinnerFrameAt, supportsColor, supportsTruecolor, visibleWidth } from "@moku-labs/common/cli";
|
|
16
17
|
import minproc from "node:process";
|
|
17
18
|
import matter from "gray-matter";
|
|
18
19
|
import rehypeShiki from "@shikijs/rehype";
|
|
@@ -6094,7 +6095,7 @@ async function readWranglerConfig(cwd) {
|
|
|
6094
6095
|
/** Relative path of the generated wrangler config. */
|
|
6095
6096
|
const WRANGLER_PATH = "wrangler.jsonc";
|
|
6096
6097
|
/** Relative path of the generated GitHub Actions workflow. */
|
|
6097
|
-
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6098
|
+
const WORKFLOW_PATH$1 = ".github/workflows/deploy.yml";
|
|
6098
6099
|
/** Wrangler `compatibility_date` used when the deploy config does not pin one. */
|
|
6099
6100
|
const DEFAULT_COMPATIBILITY_DATE = "2024-01-01";
|
|
6100
6101
|
/**
|
|
@@ -6152,12 +6153,12 @@ async function writeScaffolding(input) {
|
|
|
6152
6153
|
result
|
|
6153
6154
|
});
|
|
6154
6155
|
if (ci) await reconcile({
|
|
6155
|
-
relativePath: WORKFLOW_PATH,
|
|
6156
|
+
relativePath: WORKFLOW_PATH$1,
|
|
6156
6157
|
expected: generateGithubWorkflow({
|
|
6157
6158
|
slug,
|
|
6158
6159
|
...options.workflowTrigger ? { trigger: options.workflowTrigger } : {}
|
|
6159
6160
|
}),
|
|
6160
|
-
existing: await readMaybe(cwd, WORKFLOW_PATH),
|
|
6161
|
+
existing: await readMaybe(cwd, WORKFLOW_PATH$1),
|
|
6161
6162
|
cwd,
|
|
6162
6163
|
check,
|
|
6163
6164
|
result
|
|
@@ -6855,30 +6856,39 @@ async function resolveTrigger(ctx, choice) {
|
|
|
6855
6856
|
if (choice === 0) return "auto";
|
|
6856
6857
|
return await ctx.state.select("How should the versioned deploy be triggered?", ["On a version tag push (v*) + the manual Run-workflow button", "Manual Run-workflow button only (workflow_dispatch)"]) === 0 ? "versioned-tag" : "dispatch";
|
|
6857
6858
|
}
|
|
6859
|
+
/** Relative path of the GitHub Actions workflow the deploy plugin scaffolds. */
|
|
6860
|
+
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6858
6861
|
/**
|
|
6859
6862
|
* Offer to scaffold a GitHub Actions deploy workflow, letting the user choose how it is
|
|
6860
|
-
* triggered, then remind them which repo secrets to add.
|
|
6863
|
+
* triggered, then remind them which repo secrets to add. Short-circuits WITHOUT prompting
|
|
6864
|
+
* when {@link WORKFLOW_PATH} already exists — CI is already wired and the scaffold is
|
|
6865
|
+
* idempotent (a second setup would only no-op), so there is nothing to ask; it just
|
|
6866
|
+
* confirms the file and re-shows the secrets reminder. A no-op past a "skip" choice.
|
|
6861
6867
|
*
|
|
6862
6868
|
* @param ctx - The cli plugin context.
|
|
6869
|
+
* @param cwd - The project root (where `.github/workflows/deploy.yml` lives).
|
|
6863
6870
|
* @returns Resolves once any chosen workflow has been scaffolded.
|
|
6864
6871
|
* @example
|
|
6865
|
-
* await offerWorkflowSetup(ctx);
|
|
6872
|
+
* await offerWorkflowSetup(ctx, process.cwd());
|
|
6866
6873
|
*/
|
|
6867
|
-
async function offerWorkflowSetup(ctx) {
|
|
6874
|
+
async function offerWorkflowSetup(ctx, cwd) {
|
|
6868
6875
|
ctx.state.render.heading("Automate future deploys (GitHub Actions)");
|
|
6876
|
+
if (existsSync(path.join(cwd, WORKFLOW_PATH))) {
|
|
6877
|
+
ctx.state.render.check(true, `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6878
|
+
ctx.state.render.info(SECRETS_HELP);
|
|
6879
|
+
return;
|
|
6880
|
+
}
|
|
6869
6881
|
const trigger = await resolveTrigger(ctx, await ctx.state.select("Set up a deploy workflow?", [
|
|
6870
6882
|
"Auto-deploy on every push to main",
|
|
6871
6883
|
"Manual / versioned deploy (choose trigger)",
|
|
6872
6884
|
"Skip for now"
|
|
6873
6885
|
]));
|
|
6874
6886
|
if (trigger === null) return;
|
|
6875
|
-
const
|
|
6887
|
+
const wrote = (await ctx.require(deployPlugin).init({
|
|
6876
6888
|
ci: true,
|
|
6877
6889
|
workflowTrigger: trigger
|
|
6878
|
-
});
|
|
6879
|
-
|
|
6880
|
-
const wrote = result.written.includes(workflowPath);
|
|
6881
|
-
ctx.state.render.check(true, wrote ? `wrote ${workflowPath}` : `${workflowPath} already exists (left unchanged)`);
|
|
6890
|
+
})).written.includes(WORKFLOW_PATH);
|
|
6891
|
+
ctx.state.render.check(true, wrote ? `wrote ${WORKFLOW_PATH}` : `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6882
6892
|
ctx.state.render.info(SECRETS_HELP);
|
|
6883
6893
|
}
|
|
6884
6894
|
/**
|
|
@@ -7074,7 +7084,7 @@ async function runDeployWizard(ctx, options) {
|
|
|
7074
7084
|
ctx.state.render.check(notFoundOk, `${ctx.config.notFoundFile} present`, notFoundOk ? void 0 : "Set build.notFound so the SSG emits it (CF Pages else flips to SPA mode).");
|
|
7075
7085
|
ctx.state.render.info("Tip: run `bun run preview` to eyeball the built site before deploying.");
|
|
7076
7086
|
const outcome = await runDeployStep(ctx, options);
|
|
7077
|
-
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx);
|
|
7087
|
+
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx, cwd);
|
|
7078
7088
|
return outcome;
|
|
7079
7089
|
}
|
|
7080
7090
|
//#endregion
|
|
@@ -8022,312 +8032,17 @@ function networkUrl(port, source = networkInterfaces) {
|
|
|
8022
8032
|
return ip === null ? null : `http://${ip}:${port}`;
|
|
8023
8033
|
}
|
|
8024
8034
|
//#endregion
|
|
8025
|
-
//#region src/plugins/cli/render/
|
|
8026
|
-
/**
|
|
8027
|
-
* @file cli plugin — TTY/NO_COLOR-aware ANSI color + box-drawing helpers shared by
|
|
8028
|
-
* the Panel renderer. Modeled on the legacy `scripts/_log.ts`: color and box glyphs
|
|
8029
|
-
* are emitted only on a real TTY with `NO_COLOR` unset; otherwise plain ASCII so
|
|
8030
|
-
* CI logs and pipes stay readable.
|
|
8031
|
-
*/
|
|
8032
|
-
/** The ANSI escape byte (ESC, `0x1b`), built so no literal control char is in source. */
|
|
8033
|
-
const ESC = String.fromCodePoint(27);
|
|
8034
|
-
/** ANSI SGR codes used by the Panel renderer (each prefixed with the ESC byte). */
|
|
8035
|
-
const ANSI = {
|
|
8036
|
-
reset: `${ESC}[0m`,
|
|
8037
|
-
bold: `${ESC}[1m`,
|
|
8038
|
-
dim: `${ESC}[2m`,
|
|
8039
|
-
red: `${ESC}[31m`,
|
|
8040
|
-
green: `${ESC}[32m`,
|
|
8041
|
-
yellow: `${ESC}[33m`,
|
|
8042
|
-
blue: `${ESC}[34m`,
|
|
8043
|
-
magenta: `${ESC}[35m`,
|
|
8044
|
-
cyan: `${ESC}[36m`,
|
|
8045
|
-
gray: `${ESC}[90m`
|
|
8046
|
-
};
|
|
8047
|
-
/**
|
|
8048
|
-
* The Moku brand pink (`#FF1E6F`) as an RGB triple, used for 24-bit truecolor output.
|
|
8049
|
-
* Degrades to {@link ANSI.magenta} on a 16-color TTY and to plain text off a TTY.
|
|
8050
|
-
*/
|
|
8051
|
-
const BRAND_PINK = {
|
|
8052
|
-
r: 255,
|
|
8053
|
-
g: 30,
|
|
8054
|
-
b: 111
|
|
8055
|
-
};
|
|
8056
|
-
/**
|
|
8057
|
-
* Build a 24-bit (truecolor) SGR foreground escape for the given RGB triple.
|
|
8058
|
-
*
|
|
8059
|
-
* @param r - Red channel (0–255).
|
|
8060
|
-
* @param g - Green channel (0–255).
|
|
8061
|
-
* @param b - Blue channel (0–255).
|
|
8062
|
-
* @returns The `ESC[38;2;r;g;bm` foreground sequence.
|
|
8063
|
-
* @example
|
|
8064
|
-
* fg24(255, 30, 111); // "\x1b[38;2;255;30;111m"
|
|
8065
|
-
*/
|
|
8066
|
-
function fg24(r, g, b) {
|
|
8067
|
-
return `${ESC}[38;2;${r};${g};${b}m`;
|
|
8068
|
-
}
|
|
8069
|
-
/** ANSI: erase the entire current line, leaving the cursor where it is. */
|
|
8070
|
-
const CLEAR_LINE = `${ESC}[2K`;
|
|
8071
|
-
/** ANSI: erase from the cursor to the end of the screen (drops stale trailing rows). */
|
|
8072
|
-
const CLEAR_BELOW = `${ESC}[0J`;
|
|
8073
|
-
/**
|
|
8074
|
-
* Braille spinner frames for live "working…" indicators on a TTY (advance one per tick).
|
|
8075
|
-
* Off a TTY the Panel never animates, so this is unused in plain/CI output.
|
|
8076
|
-
*/
|
|
8077
|
-
const SPINNER_FRAMES = [
|
|
8078
|
-
"⠋",
|
|
8079
|
-
"⠙",
|
|
8080
|
-
"⠹",
|
|
8081
|
-
"⠸",
|
|
8082
|
-
"⠼",
|
|
8083
|
-
"⠴",
|
|
8084
|
-
"⠦",
|
|
8085
|
-
"⠧",
|
|
8086
|
-
"⠇",
|
|
8087
|
-
"⠏"
|
|
8088
|
-
];
|
|
8089
|
-
/**
|
|
8090
|
-
* The ANSI sequence to move the cursor up `n` lines (empty string for `n <= 0`). The
|
|
8091
|
-
* Panel uses it to repaint a live block in place — move up over the previous draw, then
|
|
8092
|
-
* rewrite each row — so progress updates a fixed region instead of scrolling new lines.
|
|
8093
|
-
*
|
|
8094
|
-
* @param n - Number of lines to move the cursor up.
|
|
8095
|
-
* @returns The cursor-up escape sequence, or `""` when `n <= 0`.
|
|
8096
|
-
* @example
|
|
8097
|
-
* cursorUp(3); // "\x1b[3A"
|
|
8098
|
-
*/
|
|
8099
|
-
function cursorUp(n) {
|
|
8100
|
-
return n > 0 ? `${ESC}[${n}A` : "";
|
|
8101
|
-
}
|
|
8102
|
-
/** Unicode rounded box glyphs used when output is a color-capable TTY. */
|
|
8103
|
-
const UNICODE_BOX = {
|
|
8104
|
-
topLeft: "╭",
|
|
8105
|
-
topRight: "╮",
|
|
8106
|
-
bottomLeft: "╰",
|
|
8107
|
-
bottomRight: "╯",
|
|
8108
|
-
horizontal: "─",
|
|
8109
|
-
vertical: "│"
|
|
8110
|
-
};
|
|
8111
|
-
/** ASCII box glyphs used when output is piped/CI (plain mode). */
|
|
8112
|
-
const ASCII_BOX = {
|
|
8113
|
-
topLeft: "+",
|
|
8114
|
-
topRight: "+",
|
|
8115
|
-
bottomLeft: "+",
|
|
8116
|
-
bottomRight: "+",
|
|
8117
|
-
horizontal: "-",
|
|
8118
|
-
vertical: "|"
|
|
8119
|
-
};
|
|
8120
|
-
/**
|
|
8121
|
-
* Matches every ANSI SGR escape sequence (used to measure visible width). Built from
|
|
8122
|
-
* the {@link ESC} byte so no literal control character appears in the source regex.
|
|
8123
|
-
*/
|
|
8124
|
-
const ANSI_PATTERN = new RegExp(String.raw`${ESC}\[[0-9;]*m`, "g");
|
|
8125
|
-
/**
|
|
8126
|
-
* Whether ANSI color/box glyphs should be emitted: a TTY stream with `NO_COLOR`
|
|
8127
|
-
* unset. Reads `process.stdout.isTTY` and `process.env.NO_COLOR` by default so the
|
|
8128
|
-
* renderer auto-degrades in CI and pipes, exactly like the legacy logger.
|
|
8129
|
-
*
|
|
8130
|
-
* @param stream - Stream to probe for `isTTY` (defaults to `process.stdout`).
|
|
8131
|
-
* @param noColor - The `NO_COLOR` value (defaults to `process.env.NO_COLOR`).
|
|
8132
|
-
* @returns `true` when color should be used.
|
|
8133
|
-
* @example
|
|
8134
|
-
* supportsColor(); // true in an interactive terminal
|
|
8135
|
-
*/
|
|
8136
|
-
function supportsColor(stream = process.stdout, noColor = process.env.NO_COLOR) {
|
|
8137
|
-
return stream.isTTY === true && noColor === void 0;
|
|
8138
|
-
}
|
|
8139
|
-
/**
|
|
8140
|
-
* Whether the terminal advertises 24-bit (truecolor) support via `COLORTERM`, so the
|
|
8141
|
-
* renderer may emit the exact brand pink ({@link BRAND_PINK}) instead of the 16-color
|
|
8142
|
-
* `magenta` approximation. Always layered on top of {@link supportsColor} — truecolor
|
|
8143
|
-
* is never used when color itself is disabled.
|
|
8144
|
-
*
|
|
8145
|
-
* @param colorTerm - The `COLORTERM` value (defaults to `process.env.COLORTERM`).
|
|
8146
|
-
* @returns `true` when `COLORTERM` is `truecolor` or `24bit`.
|
|
8147
|
-
* @example
|
|
8148
|
-
* supportsTruecolor("truecolor"); // true
|
|
8149
|
-
*/
|
|
8150
|
-
function supportsTruecolor(colorTerm = process.env.COLORTERM) {
|
|
8151
|
-
return colorTerm === "truecolor" || colorTerm === "24bit";
|
|
8152
|
-
}
|
|
8153
|
-
/**
|
|
8154
|
-
* The braille spinner glyph for a given elapsed time, advancing one frame per
|
|
8155
|
-
* `frameMs`. Deriving the frame from wall-clock elapsed (rather than a tick counter)
|
|
8156
|
-
* keeps the spinner correct even when the animation ticker is briefly starved by a
|
|
8157
|
-
* synchronous build phase and several ticks coalesce — the glyph still reflects real
|
|
8158
|
-
* elapsed time instead of freezing on a stale frame.
|
|
8159
|
-
*
|
|
8160
|
-
* @param elapsedMs - Milliseconds since the live region opened.
|
|
8161
|
-
* @param frameMs - Milliseconds per frame (defaults to `80`).
|
|
8162
|
-
* @returns The active spinner glyph.
|
|
8163
|
-
* @example
|
|
8164
|
-
* spinnerFrameAt(240); // "⠹" (the 4th frame at 80ms/frame)
|
|
8165
|
-
*/
|
|
8166
|
-
function spinnerFrameAt(elapsedMs, frameMs = 80) {
|
|
8167
|
-
return SPINNER_FRAMES[Math.floor(Math.max(0, elapsedMs) / frameMs) % SPINNER_FRAMES.length] ?? "⠋";
|
|
8168
|
-
}
|
|
8169
|
-
/**
|
|
8170
|
-
* Select the box glyph set for the given color mode (Unicode on a TTY, ASCII off it).
|
|
8171
|
-
*
|
|
8172
|
-
* @param color - Whether color/Unicode output is enabled.
|
|
8173
|
-
* @returns The matching {@link BoxGlyphs} set.
|
|
8174
|
-
* @example
|
|
8175
|
-
* const glyphs = boxGlyphs(supportsColor());
|
|
8176
|
-
*/
|
|
8177
|
-
function boxGlyphs(color) {
|
|
8178
|
-
return color ? UNICODE_BOX : ASCII_BOX;
|
|
8179
|
-
}
|
|
8180
|
-
/**
|
|
8181
|
-
* The visible width of a string, ignoring any ANSI escape sequences it contains.
|
|
8182
|
-
*
|
|
8183
|
-
* @param text - The (possibly colorized) text to measure.
|
|
8184
|
-
* @returns The number of visible characters.
|
|
8185
|
-
* @example
|
|
8186
|
-
* visibleWidth(`${ANSI.red}hi${ANSI.reset}`); // 2
|
|
8187
|
-
*/
|
|
8188
|
-
function visibleWidth(text) {
|
|
8189
|
-
return text.replaceAll(ANSI_PATTERN, "").length;
|
|
8190
|
-
}
|
|
8191
|
-
/**
|
|
8192
|
-
* Build a {@link Palette} bound to a fixed color mode. When `color` is `false` every
|
|
8193
|
-
* helper returns its input unchanged, so the same render code path produces plain
|
|
8194
|
-
* output in CI/pipes.
|
|
8195
|
-
*
|
|
8196
|
-
* @param color - Whether color is enabled (typically `supportsColor()`).
|
|
8197
|
-
* @param truecolor - Whether 24-bit output is enabled (typically `supportsTruecolor()`);
|
|
8198
|
-
* only consulted by {@link Palette.pink}. Defaults to `false` (16-color magenta).
|
|
8199
|
-
* @returns The bound color palette.
|
|
8200
|
-
* @example
|
|
8201
|
-
* const palette = makePalette(supportsColor(), supportsTruecolor());
|
|
8202
|
-
* const line = palette.green("done");
|
|
8203
|
-
*/
|
|
8204
|
-
function makePalette(color, truecolor = false) {
|
|
8205
|
-
return {
|
|
8206
|
-
enabled: color,
|
|
8207
|
-
/**
|
|
8208
|
-
* Wrap text in the given ANSI code (returns it unchanged when color is off).
|
|
8209
|
-
*
|
|
8210
|
-
* @param code - The ANSI SGR code to apply.
|
|
8211
|
-
* @param text - The text to colorize.
|
|
8212
|
-
* @returns The colorized (or unchanged) text.
|
|
8213
|
-
* @example
|
|
8214
|
-
* palette.paint(ANSI.green, "ok");
|
|
8215
|
-
*/
|
|
8216
|
-
paint(code, text) {
|
|
8217
|
-
return color ? `${code}${text}${ANSI.reset}` : text;
|
|
8218
|
-
},
|
|
8219
|
-
/**
|
|
8220
|
-
* Bold the given text (no-op in plain mode).
|
|
8221
|
-
*
|
|
8222
|
-
* @param text - The text to embolden.
|
|
8223
|
-
* @returns The bold (or unchanged) text.
|
|
8224
|
-
* @example
|
|
8225
|
-
* palette.bold("title");
|
|
8226
|
-
*/
|
|
8227
|
-
bold(text) {
|
|
8228
|
-
return this.paint(ANSI.bold, text);
|
|
8229
|
-
},
|
|
8230
|
-
/**
|
|
8231
|
-
* Dim the given text (no-op in plain mode).
|
|
8232
|
-
*
|
|
8233
|
-
* @param text - The text to dim.
|
|
8234
|
-
* @returns The dim (or unchanged) text.
|
|
8235
|
-
* @example
|
|
8236
|
-
* palette.dim("· 84ms");
|
|
8237
|
-
*/
|
|
8238
|
-
dim(text) {
|
|
8239
|
-
return this.paint(ANSI.dim, text);
|
|
8240
|
-
},
|
|
8241
|
-
/**
|
|
8242
|
-
* Color the given text green (no-op in plain mode).
|
|
8243
|
-
*
|
|
8244
|
-
* @param text - The text to colorize.
|
|
8245
|
-
* @returns The green (or unchanged) text.
|
|
8246
|
-
* @example
|
|
8247
|
-
* palette.green("✓");
|
|
8248
|
-
*/
|
|
8249
|
-
green(text) {
|
|
8250
|
-
return this.paint(ANSI.green, text);
|
|
8251
|
-
},
|
|
8252
|
-
/**
|
|
8253
|
-
* Color the given text yellow (no-op in plain mode).
|
|
8254
|
-
*
|
|
8255
|
-
* @param text - The text to colorize.
|
|
8256
|
-
* @returns The yellow (or unchanged) text.
|
|
8257
|
-
* @example
|
|
8258
|
-
* palette.yellow("~");
|
|
8259
|
-
*/
|
|
8260
|
-
yellow(text) {
|
|
8261
|
-
return this.paint(ANSI.yellow, text);
|
|
8262
|
-
},
|
|
8263
|
-
/**
|
|
8264
|
-
* Color the given text red (no-op in plain mode).
|
|
8265
|
-
*
|
|
8266
|
-
* @param text - The text to colorize.
|
|
8267
|
-
* @returns The red (or unchanged) text.
|
|
8268
|
-
* @example
|
|
8269
|
-
* palette.red("✗");
|
|
8270
|
-
*/
|
|
8271
|
-
red(text) {
|
|
8272
|
-
return this.paint(ANSI.red, text);
|
|
8273
|
-
},
|
|
8274
|
-
/**
|
|
8275
|
-
* Color the given text cyan (no-op in plain mode).
|
|
8276
|
-
*
|
|
8277
|
-
* @param text - The text to colorize.
|
|
8278
|
-
* @returns The cyan (or unchanged) text.
|
|
8279
|
-
* @example
|
|
8280
|
-
* palette.cyan("http://localhost:4173");
|
|
8281
|
-
*/
|
|
8282
|
-
cyan(text) {
|
|
8283
|
-
return this.paint(ANSI.cyan, text);
|
|
8284
|
-
},
|
|
8285
|
-
/**
|
|
8286
|
-
* Color the given text the Moku brand pink: exact `#FF1E6F` (24-bit) when truecolor
|
|
8287
|
-
* is enabled, the 16-color `magenta` approximation otherwise, unchanged in plain mode.
|
|
8288
|
-
*
|
|
8289
|
-
* @param text - The text to colorize.
|
|
8290
|
-
* @returns The pink (or unchanged) text.
|
|
8291
|
-
* @example
|
|
8292
|
-
* palette.pink("▟▙ moku web");
|
|
8293
|
-
*/
|
|
8294
|
-
pink(text) {
|
|
8295
|
-
if (!color) return text;
|
|
8296
|
-
if (truecolor) return `${fg24(BRAND_PINK.r, BRAND_PINK.g, BRAND_PINK.b)}${text}${ANSI.reset}`;
|
|
8297
|
-
return this.paint(ANSI.magenta, text);
|
|
8298
|
-
}
|
|
8299
|
-
};
|
|
8300
|
-
}
|
|
8035
|
+
//#region src/plugins/cli/render/panel.ts
|
|
8301
8036
|
/**
|
|
8302
|
-
*
|
|
8303
|
-
*
|
|
8304
|
-
*
|
|
8305
|
-
*
|
|
8306
|
-
*
|
|
8307
|
-
* @
|
|
8308
|
-
*
|
|
8309
|
-
*
|
|
8310
|
-
* @returns The boxed lines (top border, content rows, bottom border).
|
|
8311
|
-
* @example
|
|
8312
|
-
* box(["Local: http://localhost:4173"], true, 62);
|
|
8037
|
+
* @file cli plugin — the Panel renderer (the "Velocity Lockup" CLI identity). Produces
|
|
8038
|
+
* the `▟▙ moku web` lockup + version/runtime banner, the live phase tree with an
|
|
8039
|
+
* animated indeterminate build bar, the BUILD summary + throughput sparkline, the
|
|
8040
|
+
* server-ready rail with a persistent breathing `◍ live` idle pulse, the compact
|
|
8041
|
+
* rebuild line, the deploy result, and diagnostic heading/check rows. TTY/`NO_COLOR`-
|
|
8042
|
+
* aware via {@link makePalette} (24-bit brand pink when truecolor is available, the
|
|
8043
|
+
* 16-color magenta approximation otherwise, plain text off a TTY); every line is
|
|
8044
|
+
* written through an injectable sink so tests can capture it.
|
|
8313
8045
|
*/
|
|
8314
|
-
function box(lines, color, minInnerWidth = 0) {
|
|
8315
|
-
const glyphs = boxGlyphs(color);
|
|
8316
|
-
const inner = Math.max(0, minInnerWidth, ...lines.map((line) => visibleWidth(line)));
|
|
8317
|
-
const horizontal = glyphs.horizontal.repeat(inner + 2);
|
|
8318
|
-
const top = `${glyphs.topLeft}${horizontal}${glyphs.topRight}`;
|
|
8319
|
-
const bottom = `${glyphs.bottomLeft}${horizontal}${glyphs.bottomRight}`;
|
|
8320
|
-
return [
|
|
8321
|
-
top,
|
|
8322
|
-
...lines.map((line) => {
|
|
8323
|
-
const pad = " ".repeat(inner - visibleWidth(line));
|
|
8324
|
-
return `${glyphs.vertical} ${line}${pad} ${glyphs.vertical}`;
|
|
8325
|
-
}),
|
|
8326
|
-
bottom
|
|
8327
|
-
];
|
|
8328
|
-
}
|
|
8329
|
-
//#endregion
|
|
8330
|
-
//#region src/plugins/cli/render/panel.ts
|
|
8331
8046
|
/** Per-command label shown beside the lockup wordmark. */
|
|
8332
8047
|
const COMMAND_LABEL = {
|
|
8333
8048
|
build: "build",
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"bun": ">=1.3.14"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@moku-labs/common": "0.
|
|
61
|
+
"@moku-labs/common": "0.2.0",
|
|
62
62
|
"@moku-labs/core": "0.1.4",
|
|
63
63
|
"@resvg/resvg-js": "2.6.2",
|
|
64
64
|
"@shikijs/rehype": "3.22.0",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"test:build-e2e": "bun test src/plugins/build/__tests__/e2e/",
|
|
127
127
|
"test:coverage": "vitest run --project unit --project integration --coverage"
|
|
128
128
|
},
|
|
129
|
-
"version": "1.
|
|
129
|
+
"version": "1.13.1"
|
|
130
130
|
}
|