@effected/cli 0.13.0 → 0.14.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/Render.js +3 -3
- package/index.d.ts +5 -5
- package/package.json +5 -5
- package/ui/UiTheme.js +3 -2
- package/ui/testing/CliUiTest.js +37 -12
- package/ui/testing/terminalModel.js +13 -4
package/Render.js
CHANGED
|
@@ -186,7 +186,7 @@ var Render = class {
|
|
|
186
186
|
*
|
|
187
187
|
* Under GitHub Actions (`neutralizeWorkflowCommands`) the same neutralizing applies, since markdown can be printed
|
|
188
188
|
* to the log. Markdown escapes `[` in text, so `##[` cannot appear outside code and the headings are untouched
|
|
189
|
-
* (a bare `##` is not a command); code spans and blocks, which are not escaped, get the
|
|
189
|
+
* (a bare `##` is not a command); code spans and blocks, which are not escaped, get the blank marker, which can
|
|
190
190
|
* also land inside code or table text where it would otherwise have formed a command.
|
|
191
191
|
*
|
|
192
192
|
* GitHub also turns `@user`, `@org/team`, `#123` and commit SHAs in rendered markdown into mentions and references.
|
|
@@ -238,8 +238,8 @@ var Render = class {
|
|
|
238
238
|
*
|
|
239
239
|
* The runner has two command parsers, and a line is a command if either accepts it: after its leading whitespace it
|
|
240
240
|
* starts with `::`, or `##[` occurs ANYWHERE in it (a bare `##` is not one). A document's text must not be able to
|
|
241
|
-
* do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a
|
|
242
|
-
* the runner
|
|
241
|
+
* do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
|
|
242
|
+
* front, which the runner neither trims nor skips, and every `##[` gets one before the `[`. The text is
|
|
243
243
|
* otherwise unchanged. A
|
|
244
244
|
* group's title is a command's data, so its `%`, CR and LF are escaped. Lines are split at CR, LF and CRLF before
|
|
245
245
|
* that check, as the runner splits them. There is no ANSI and `paint` and `link` are never called, and the audience
|
package/index.d.ts
CHANGED
|
@@ -3224,8 +3224,8 @@ interface RenderContext {
|
|
|
3224
3224
|
/**
|
|
3225
3225
|
* Whether the output will be read by the GitHub Actions runner, which has two command parsers: a line is a command
|
|
3226
3226
|
* if, after .NET whitespace, it starts with `::`, or if `##[` occurs ANYWHERE in it (a bare `##` is not one). When
|
|
3227
|
-
* `true`, `plain`, `ansi` and `markdown` put a
|
|
3228
|
-
* `[`
|
|
3227
|
+
* `true`, `plain`, `ansi` and `markdown` put a braille pattern blank (U+2800, one blank cell) in front of such a
|
|
3228
|
+
* `::` line and before the `[` of each `##[`, so a document's text, an error message, say, can never inject a command. Unset or `false`
|
|
3229
3229
|
* leaves their text alone. `Render.githubLog` ignores it and always neutralizes: its output is for the runner by
|
|
3230
3230
|
* definition.
|
|
3231
3231
|
*
|
|
@@ -3418,7 +3418,7 @@ export declare class Render {
|
|
|
3418
3418
|
*
|
|
3419
3419
|
* Under GitHub Actions (`neutralizeWorkflowCommands`) the same neutralizing applies, since markdown can be printed
|
|
3420
3420
|
* to the log. Markdown escapes `[` in text, so `##[` cannot appear outside code and the headings are untouched
|
|
3421
|
-
* (a bare `##` is not a command); code spans and blocks, which are not escaped, get the
|
|
3421
|
+
* (a bare `##` is not a command); code spans and blocks, which are not escaped, get the blank marker, which can
|
|
3422
3422
|
* also land inside code or table text where it would otherwise have formed a command.
|
|
3423
3423
|
*
|
|
3424
3424
|
* GitHub also turns `@user`, `@org/team`, `#123` and commit SHAs in rendered markdown into mentions and references.
|
|
@@ -3470,8 +3470,8 @@ export declare class Render {
|
|
|
3470
3470
|
*
|
|
3471
3471
|
* The runner has two command parsers, and a line is a command if either accepts it: after its leading whitespace it
|
|
3472
3472
|
* starts with `::`, or `##[` occurs ANYWHERE in it (a bare `##` is not one). A document's text must not be able to
|
|
3473
|
-
* do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a
|
|
3474
|
-
* the runner
|
|
3473
|
+
* do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
|
|
3474
|
+
* front, which the runner neither trims nor skips, and every `##[` gets one before the `[`. The text is
|
|
3475
3475
|
* otherwise unchanged. A
|
|
3476
3476
|
* group's title is a command's data, so its `%`, CR and LF are escaped. Lines are split at CR, LF and CRLF before
|
|
3477
3477
|
* that check, as the runner splits them. There is no ANSI and `paint` and `link` are never called, and the audience
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The presentation boundary of an effect/cli program: audience-aware output, a document IR and renderers, editor links, failure reports and logging, plus opt-in Ink screens, widgets and a live view",
|
|
6
6
|
"keywords": [
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"./package.json": "./package.json"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@effected/github-commands": "^0.
|
|
60
|
+
"@effected/github-commands": "^0.2.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@effected/config-file": "^0.14.2",
|
|
64
64
|
"@effected/env": "^0.1.0",
|
|
65
65
|
"@effected/glob": "^0.10.0",
|
|
66
66
|
"@effected/walker": "^0.15.0",
|
|
67
|
-
"@types/react": "^19.
|
|
67
|
+
"@types/react": "^19.3.0",
|
|
68
68
|
"effect": "^4.0.0",
|
|
69
|
-
"ink": "^
|
|
70
|
-
"react": "^19.
|
|
69
|
+
"ink": "^8.0.0",
|
|
70
|
+
"react": "^19.3.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {
|
|
73
73
|
"@effected/config-file": {
|
package/ui/UiTheme.js
CHANGED
|
@@ -107,8 +107,9 @@ const useTerminalSize = () => {
|
|
|
107
107
|
stdout.off("resize", redraw);
|
|
108
108
|
};
|
|
109
109
|
}, [stdout, followsStdout]);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
110
|
+
const reported = stdout;
|
|
111
|
+
const columns = override?.columns ?? reported.columns;
|
|
112
|
+
const rows = override?.rows ?? reported.rows;
|
|
112
113
|
return {
|
|
113
114
|
columns: Math.max(1, known(columns, 80) - 1),
|
|
114
115
|
rows: Math.max(1, known(rows, 24) - 1)
|
package/ui/testing/CliUiTest.js
CHANGED
|
@@ -218,6 +218,10 @@ const LEADING_MOVES = /^(?:\u001b\[[0-9;?]*[A-Za-ln-z])+/;
|
|
|
218
218
|
* moves; a write of moves alone is Ink clearing the frame for a log line, not a frame. stdout and stderr are two
|
|
219
219
|
* streams, each read alone, and `fake.written` is both in the order written: one terminal, for `written` and the
|
|
220
220
|
* transcript.
|
|
221
|
+
*
|
|
222
|
+
* Ink calls `onRender` after it writes a render's frame, in the same synchronous turn, so the frame is the last
|
|
223
|
+
* frame-like write of that turn. A production render whose write Ink's throttle defers to its trailing timer has
|
|
224
|
+
* none yet: the next frame-like write is taken instead.
|
|
221
225
|
*/
|
|
222
226
|
const makeTerminal = (options, settings = { screens: "debug" }) => {
|
|
223
227
|
const columns = options.columns ?? 80;
|
|
@@ -226,25 +230,48 @@ const makeTerminal = (options, settings = { screens: "debug" }) => {
|
|
|
226
230
|
const captures = [];
|
|
227
231
|
let lastWrite = 0;
|
|
228
232
|
let frameDue = false;
|
|
233
|
+
let turn = [];
|
|
234
|
+
/** A chunk read as a frame in `mode`, or `undefined` for one that is not a frame (brackets or moves alone). */
|
|
235
|
+
const asFrame = (chunk, mode) => {
|
|
236
|
+
if (mode === "debug") return chunk;
|
|
237
|
+
const text = chunk.replace(FRAME_BRACKETS, "");
|
|
238
|
+
if (text === "" || CONTROLS_ONLY.test(text)) return void 0;
|
|
239
|
+
return text.replace(LEADING_MOVES, "").replace(/\n+$/, "");
|
|
240
|
+
};
|
|
229
241
|
const fake = makeFakeStreams({
|
|
230
242
|
columns,
|
|
231
243
|
rows,
|
|
232
244
|
onStdoutWrite: (chunk) => {
|
|
233
245
|
lastWrite = Date.now();
|
|
246
|
+
if (turn.length === 0) {
|
|
247
|
+
const opened = turn;
|
|
248
|
+
queueMicrotask(() => {
|
|
249
|
+
if (turn === opened) turn = [];
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
turn.push(chunk);
|
|
234
253
|
const current = captures.at(-1);
|
|
235
254
|
if (!frameDue || current === void 0 || current.ended) return;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
current.raws.push(chunk);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const text = chunk.replace(FRAME_BRACKETS, "");
|
|
242
|
-
if (text === "") return;
|
|
255
|
+
const frame = asFrame(chunk, current.mode);
|
|
256
|
+
if (frame === void 0) return;
|
|
243
257
|
frameDue = false;
|
|
244
|
-
|
|
245
|
-
current.raws.push(text.replace(LEADING_MOVES, "").replace(/\n+$/, ""));
|
|
258
|
+
current.raws.push(frame);
|
|
246
259
|
}
|
|
247
260
|
});
|
|
261
|
+
/** Called as Ink reports a render: take the frame it wrote this turn, or wait for the one its throttle defers. */
|
|
262
|
+
const rendered = () => {
|
|
263
|
+
const current = captures.at(-1);
|
|
264
|
+
if (current === void 0 || current.ended) return;
|
|
265
|
+
for (let index = turn.length - 1; index >= 0; index--) {
|
|
266
|
+
const frame = asFrame(turn[index], current.mode);
|
|
267
|
+
if (frame === void 0) continue;
|
|
268
|
+
turn = [];
|
|
269
|
+
frameDue = false;
|
|
270
|
+
current.raws.push(frame);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
frameDue = true;
|
|
274
|
+
};
|
|
248
275
|
const streams = fake.streams;
|
|
249
276
|
const stream = {
|
|
250
277
|
isTerminal: true,
|
|
@@ -263,9 +290,7 @@ const makeTerminal = (options, settings = { screens: "debug" }) => {
|
|
|
263
290
|
}).pipe(Layer.provide(terminal)), CliInteractive.layerTest(options.interactive ?? true), Layer.succeed(UiStreams, streams), Layer.succeed(UiRenderOptions, {
|
|
264
291
|
...settings.screens === "debug" ? { debug: true } : {},
|
|
265
292
|
maxFps: 1e3,
|
|
266
|
-
onRender:
|
|
267
|
-
frameDue = true;
|
|
268
|
-
},
|
|
293
|
+
onRender: rendered,
|
|
269
294
|
onMount: (kind) => {
|
|
270
295
|
const mode = kind === "live" ? "production" : settings.screens;
|
|
271
296
|
captures.push({
|
|
@@ -6,8 +6,9 @@ const ESC = String.fromCharCode(27);
|
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
8
|
* It applies printable text, line feeds, the erase and cursor moves Ink's log-update writes (erase line, cursor up,
|
|
9
|
-
* cursor to column
|
|
10
|
-
*
|
|
9
|
+
* down and forward, cursor to a column, cursor to the next line), and the clears of Ink's full-clear frame: `ESC[J`
|
|
10
|
+
* erases from the cursor to the end of the screen, `ESC[2J` blanks the visible screen, `ESC[3J` drops the scrollback
|
|
11
|
+
* above it, `ESC[H` homes the cursor to the screen's top left. The visible screen is the last
|
|
11
12
|
* `rows` lines; with `rows` unknown, the whole buffer counts as the screen, so a clear takes everything. Every other
|
|
12
13
|
* escape is ignored. It does not wrap a line wider than the terminal. Shared by `CliUiTest.live`'s transcript and the
|
|
13
14
|
* kit's own production-path tests.
|
|
@@ -38,8 +39,16 @@ const screenAfter = (written, rows) => {
|
|
|
38
39
|
}
|
|
39
40
|
} else if (command === "K") lines[row] = "";
|
|
40
41
|
else if (command === "A") row = Math.max(0, row - Number(params === "" ? 1 : params));
|
|
41
|
-
else if (command === "
|
|
42
|
-
|
|
42
|
+
else if (command === "B" || command === "E") {
|
|
43
|
+
row += Number(params === "" ? 1 : params);
|
|
44
|
+
if (command === "E") column = 0;
|
|
45
|
+
while (lines.length <= row) lines.push("");
|
|
46
|
+
} else if (command === "C") column += Number(params === "" ? 1 : params);
|
|
47
|
+
else if (command === "G") column = Math.max(0, Number(params === "" ? 1 : params) - 1);
|
|
48
|
+
else if (command === "J" && (params === "" || params === "0")) {
|
|
49
|
+
lines[row] = (lines[row] ?? "").slice(0, column);
|
|
50
|
+
for (let index = row + 1; index < lines.length; index++) lines[index] = "";
|
|
51
|
+
} else if (command === "J" && params === "2") for (let index = screenTop(); index < lines.length; index++) lines[index] = "";
|
|
43
52
|
else if (command === "J" && params === "3") {
|
|
44
53
|
const top = screenTop();
|
|
45
54
|
lines.splice(0, top);
|