@effected/cli 0.16.0 → 0.16.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/Doc.js CHANGED
@@ -463,8 +463,9 @@ var Doc = class {
463
463
  * and every other renderer writes nothing.
464
464
  *
465
465
  * @remarks
466
- * It is the kit's own command, so `githubLog` does not neutralize it; its message and properties are escaped, so
467
- * no text in them can end the command or start another. It is a command where a line starts: at the top level, as a
466
+ * It is the kit's own command, so `githubLog` does not neutralize the command itself; its message and properties are
467
+ * escaped, so no text in them can end the command or start another, and a `##[` in its message, title or file gets
468
+ * a braille pattern blank (U+2800) before the `[`, as in plain text. It is a command where a line starts: at the top level, as a
468
469
  * top-level section's child, or as a direct child of a group's body. Nested deeper, it is dropped.
469
470
  *
470
471
  * @param options - the level, and the optional file, position and title
package/Render.js CHANGED
@@ -232,7 +232,8 @@ var Render = class {
232
232
  * body, `::endgroup::`. An annotation is a command at the top level, as a top-level section's child, and as a direct
233
233
  * child of a group's body; anywhere deeper (inside a list, a callout, or a section within a group) it is nothing,
234
234
  * as in plain. Its message and properties are escaped, so no text can end
235
- * the command or start another, and the kit's own command is never neutralized. That is a top-level collapsible, or one that is a direct child of a
235
+ * the command or start another, and the kit's own command is never neutralized, though a `##[` in its message,
236
+ * title or file is. That is a top-level collapsible, or one that is a direct child of a
236
237
  * top-level section. GitHub does not nest groups, so a collapsible inside a group, or inside a list or callout
237
238
  * (where it would not start a line), keeps plain's rendering: its title on a line and its body indented.
238
239
  *
@@ -241,7 +242,7 @@ var Render = class {
241
242
  * do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
242
243
  * front, which the runner neither trims nor skips, and every `##[` gets one before the `[`. The text is
243
244
  * otherwise unchanged. A
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
+ * group's title is a command's data, so its `%`, CR and LF are escaped and a `##[` in it is neutralized. Lines are split at CR, LF and CRLF before
245
246
  * that check, as the runner splits them. There is no ANSI and `paint` and `link` are never called, and the audience
246
247
  * is treated as `agent`, as `plain` does.
247
248
  *
package/index.d.ts CHANGED
@@ -1382,8 +1382,9 @@ export declare class Doc {
1382
1382
  * and every other renderer writes nothing.
1383
1383
  *
1384
1384
  * @remarks
1385
- * It is the kit's own command, so `githubLog` does not neutralize it; its message and properties are escaped, so
1386
- * no text in them can end the command or start another. It is a command where a line starts: at the top level, as a
1385
+ * It is the kit's own command, so `githubLog` does not neutralize the command itself; its message and properties are
1386
+ * escaped, so no text in them can end the command or start another, and a `##[` in its message, title or file gets
1387
+ * a braille pattern blank (U+2800) before the `[`, as in plain text. It is a command where a line starts: at the top level, as a
1387
1388
  * top-level section's child, or as a direct child of a group's body. Nested deeper, it is dropped.
1388
1389
  *
1389
1390
  * @param options - the level, and the optional file, position and title
@@ -3474,7 +3475,8 @@ export declare class Render {
3474
3475
  * body, `::endgroup::`. An annotation is a command at the top level, as a top-level section's child, and as a direct
3475
3476
  * child of a group's body; anywhere deeper (inside a list, a callout, or a section within a group) it is nothing,
3476
3477
  * as in plain. Its message and properties are escaped, so no text can end
3477
- * the command or start another, and the kit's own command is never neutralized. That is a top-level collapsible, or one that is a direct child of a
3478
+ * the command or start another, and the kit's own command is never neutralized, though a `##[` in its message,
3479
+ * title or file is. That is a top-level collapsible, or one that is a direct child of a
3478
3480
  * top-level section. GitHub does not nest groups, so a collapsible inside a group, or inside a list or callout
3479
3481
  * (where it would not start a line), keeps plain's rendering: its title on a line and its body indented.
3480
3482
  *
@@ -3483,7 +3485,7 @@ export declare class Render {
3483
3485
  * do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
3484
3486
  * front, which the runner neither trims nor skips, and every `##[` gets one before the `[`. The text is
3485
3487
  * otherwise unchanged. A
3486
- * group's title is a command's data, so its `%`, CR and LF are escaped. Lines are split at CR, LF and CRLF before
3488
+ * group's title is a command's data, so its `%`, CR and LF are escaped and a `##[` in it is neutralized. Lines are split at CR, LF and CRLF before
3487
3489
  * that check, as the runner splits them. There is no ANSI and `paint` and `link` are never called, and the audience
3488
3490
  * is treated as `agent`, as `plain` does.
3489
3491
  *
@@ -5,10 +5,22 @@ import { CommandNeutralizer, WorkflowCommand } from "@effected/github-commands";
5
5
  //#region src/internal/renderGithubLog.ts
6
6
  /** Plain lines, neutralized; a block that draws nothing, such as an empty table, gives no line at all. */
7
7
  const plainLines = (blocks, ctx) => renderPlainLines(blocks, ctx).flatMap((line) => CommandNeutralizer.lines(line));
8
- /** An annotation as the kit's own command, on the trusted path: escaped by `WorkflowCommand`, never neutralized. */
9
- const annotationLine = (block) => WorkflowCommand[block.level](sanitize(block.message), {
10
- ...block.title === void 0 ? {} : { title: sanitize(block.title) },
11
- ...block.file === void 0 ? {} : { file: sanitize(block.file) },
8
+ /**
9
+ * A command's data, with every `##[` neutralized and its line breaks kept as they were. `WorkflowCommand` escapes the
10
+ * breaks, so the command stays one line, but the runner's legacy parser reads `##[` ANYWHERE in a line, including in
11
+ * a command's message and property values (it is tried when the V2 parser rejects the line), so each `##[` gets the
12
+ * neutralizer's marker before its `[`. The V2 rule does not apply: no part of the data starts a line of the log, so
13
+ * each part is neutralized behind a one-character lead that cannot start a command and is cut off again, which keeps
14
+ * a part that begins `::` as it was. The command itself is not neutralized: only the data that goes into it.
15
+ */
16
+ const commandData = (text) => text.split(/(\r\n|\r|\n)/).map((part, index) => index % 2 === 0 ? CommandNeutralizer.text(`.${part}`).slice(1) : part).join("");
17
+ /**
18
+ * An annotation as the kit's own command, on the trusted path: escaped by `WorkflowCommand`, and its message, title
19
+ * and file neutralized as data (the line and the columns are numbers, which carry nothing).
20
+ */
21
+ const annotationLine = (block) => WorkflowCommand[block.level](commandData(sanitize(block.message)), {
22
+ ...block.title === void 0 ? {} : { title: commandData(sanitize(block.title)) },
23
+ ...block.file === void 0 ? {} : { file: commandData(sanitize(block.file)) },
12
24
  ...block.line === void 0 ? {} : { startLine: block.line },
13
25
  ...block.endLine === void 0 ? {} : { endLine: block.endLine },
14
26
  ...block.col === void 0 ? {} : { startColumn: block.col },
@@ -20,7 +32,7 @@ const blockLines = (block, ctx) => {
20
32
  switch (block._tag) {
21
33
  case "Annotation": return [annotationLine(block)];
22
34
  case "Collapsible": {
23
- const title = plainInline(block.title, ctx).map((span) => span.text).join("");
35
+ const title = commandData(plainInline(block.title, ctx).map((span) => span.text).join(""));
24
36
  return [
25
37
  WorkflowCommand.group(title),
26
38
  ...bodyLines(block.body, ctx),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/cli",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
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,11 +57,11 @@
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "dependencies": {
60
- "@effected/github-commands": "^0.2.0"
60
+ "@effected/github-commands": "^0.2.1"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@effected/config-file": "^0.14.2",
64
- "@effected/env": "^0.1.0",
64
+ "@effected/env": "^0.1.1",
65
65
  "@effected/glob": "^0.10.0",
66
66
  "@effected/walker": "^0.15.0",
67
67
  "@types/react": "^19.3.0",
@@ -1,10 +1,36 @@
1
1
  import { PassThrough, Writable } from "node:stream";
2
2
 
3
3
  //#region src/ui/testing/fakeStreams.ts
4
+ /** The hide-cursor escape `cli-cursor` writes to a TTY stream as it arms the restore. */
5
+ const HIDE_CURSOR = `${String.fromCharCode(27)}[?25l`;
6
+ /** A cursor-show escape in a listener's source, as `restore-cursor` writes it (`'\u001B[?25h'`) or as the raw byte. */
7
+ const CURSOR_SHOW = /\[\?25h/;
8
+ /**
9
+ * Disarm the real terminal's cursor restore that a fake TTY arms, so no byte of a run on the fakes reaches the
10
+ * process's own streams (#983). The fake streams call it as the hide escape is written to them.
11
+ *
12
+ * @remarks
13
+ * Ink's frame writer hides the cursor through `cli-cursor` on its first render, and `cli-cursor` does it only for a
14
+ * stream that says it is a TTY, which the fakes do. Hiding it also arms `restore-cursor`, once per loaded copy of it
15
+ * (once per test file where the runner isolates modules): a `signal-exit` hook (`alwaysLast`, so on the `afterexit`
16
+ * event) that writes the cursor-show escape to the REAL `process.stderr` when the process exits or is signalled,
17
+ * whatever stream Ink was given and whether or not stderr is a terminal. Nothing on the fakes hid the real cursor, so
18
+ * that restore is only ever a stray byte in the test runner's output: this removes every `afterexit` listener that
19
+ * writes a cursor-show escape, right after the copy that armed it wrote its hide escape. The emitter is `signal-exit` 3's process-wide one, the version `restore-cursor` 4
20
+ * (the one Ink 8's `cli-cursor` takes) loads; without one there is nothing to disarm.
21
+ *
22
+ * The one read of `process` in the testing fakes, under the process-streams licence: it touches no stream.
23
+ */
24
+ const disarmCursorRestore = () => {
25
+ const emitter = process.__signal_exit_emitter__;
26
+ if (emitter === void 0) return;
27
+ for (const listener of emitter.listeners("afterexit")) if (CURSOR_SHOW.test(String(listener))) emitter.removeListener("afterexit", listener);
28
+ };
4
29
  const capture = (columns, rows, both, onWrite) => {
5
30
  const chunks = [];
6
31
  const stream = new Writable({ write(chunk, _encoding, callback) {
7
32
  const text = chunk.toString();
33
+ if (text.includes(HIDE_CURSOR)) disarmCursorRestore();
8
34
  chunks.push(text);
9
35
  both.push(text);
10
36
  onWrite?.(text);