@effected/cli 0.20.0 → 0.22.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/Doc.js CHANGED
@@ -523,6 +523,11 @@ var Doc = class {
523
523
  * GitHub's log format only when it says GitHub Actions, and plain text otherwise, including when it is
524
524
  * absent. An explicit `format` is honoured whatever the audience.
525
525
  *
526
+ * The context is built here, at print time, so it carries the fiber's count of open Actions log groups
527
+ * (`RenderContext.groupDepth`): printed inside an `ActionLogger.group` from `@effected/github-actions`, a
528
+ * document's top-level collapsible renders as plain text rather than as a second `::group::`, which would end
529
+ * the open one at the runner.
530
+ *
526
531
  * @param doc - the document
527
532
  * @param options - the stream and the format
528
533
  */
package/Fmt.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { displayWidth, graphemes, stripAnsi } from "./internal/displayWidth.js";
2
+ import { Duration, Option } from "effect";
2
3
 
3
4
  // src/Fmt.ts
4
5
  const CONTROL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;
@@ -76,18 +77,27 @@ var Fmt = class {
76
77
  return useEllipsis ? `${out}${ellipsis}` : out;
77
78
  };
78
79
  /**
79
- * A duration in milliseconds as short human text: `250ms`, `1.2s`, `2s`, `1m 3s`, `2m`, `1h 2m`, `2h`.
80
+ * A duration as short human text: `250ms`, `1.2s`, `2s`, `1m 3s`, `2m`, `1h 2m`, `2h`.
80
81
  *
81
82
  * @remarks
82
83
  * Under a second it is whole milliseconds; under a minute, seconds to one decimal with a trailing `.0`
83
84
  * dropped; under an hour, minutes and whole seconds, with the seconds dropped when zero; from an hour, hours and
84
85
  * whole minutes, the seconds dropped. A value that rounds up to the next unit (`999.6` to a second, `59999` to a
85
86
  * minute, `3599999` to an hour) is written in that unit, so `1000ms`, `60s` and `60m` never appear. There is no
86
- * days unit. A negative or non-finite input is `0ms`.
87
+ * days unit. The format is bespoke: core's `Duration.format` renders differently.
87
88
  *
88
- * @param ms - the duration in milliseconds
89
+ * The input is anything `Duration.Input` admits. A plain number is milliseconds and is used as given, never
90
+ * converted through a `Duration`, which rounds a fractional millisecond to whole nanoseconds and would move a value
91
+ * such as `999.4999999` across the rounding boundary. A `bigint` is nanoseconds, as in core. A negative, infinite
92
+ * or invalid input (a string `Duration.Input` cannot parse) is `0ms`: a formatter in a render path never throws.
93
+ *
94
+ * @param input - the duration; a number is milliseconds
89
95
  */
90
- static duration = (ms) => {
96
+ static duration = (input) => {
97
+ const ms = typeof input === "number" ? input : Option.match(Duration.fromInput(input), {
98
+ onNone: () => 0,
99
+ onSome: Duration.toMillis
100
+ });
91
101
  const value = Number.isFinite(ms) ? Math.max(0, ms) : 0;
92
102
  const whole = Math.round(value);
93
103
  if (whole < 1e3) return `${whole}ms`;
package/Render.js CHANGED
@@ -8,11 +8,16 @@ import { renderAnsi } from "./internal/renderAnsi.js";
8
8
  import { renderPlain } from "./internal/renderPlain.js";
9
9
  import { renderGithubLog } from "./internal/renderGithubLog.js";
10
10
  import { renderMarkdown } from "./internal/renderMarkdown.js";
11
- import { Effect } from "effect";
11
+ import { Context, Effect } from "effect";
12
12
  import { Audience, TerminalEnv } from "@effected/env";
13
- import { CommandNeutralizer } from "@effected/github-commands";
13
+ import { CommandNeutralizer, WorkflowCommand } from "@effected/github-commands";
14
14
 
15
15
  // src/Render.ts
16
+ /**
17
+ * The fiber-local count of open Actions log groups, by the kit-wide key, so a group `@effected/github-actions`'
18
+ * `ActionLogger.group` opened is visible here without an edge between the two packages.
19
+ */
20
+ const GroupDepth = Context.Reference(WorkflowCommand.groupDepthKey, { defaultValue: () => 0 });
16
21
  /** A renderer's text, with workflow commands neutralized when the context says the runner is reading it. */
17
22
  const guarded = (text, ctx) => ctx.neutralizeWorkflowCommands === true ? CommandNeutralizer.text(text) : text;
18
23
  /**
@@ -53,7 +58,10 @@ var Render = class {
53
58
  * required), for every audience, since the runner reads whatever is written there;
54
59
  * - `width` is the option, else `TerminalEnv.width()` for a human whose stream is a terminal, and **unbounded**
55
60
  * (`Infinity`) for a human whose stream is not one (`tool | grep`, `tool > out.txt`), an agent or a CI, so nothing
56
- * a reader needs is truncated or wrapped for a terminal that is not there: a pipe has no width to honour.
61
+ * a reader needs is truncated or wrapped for a terminal that is not there: a pipe has no width to honour;
62
+ * - `groupDepth` is the fiber's count of open Actions log groups (the fiber-local keyed by
63
+ * `WorkflowCommand.groupDepthKey`, which `ActionLogger.group` provides), set when it is above `0`, so a
64
+ * document printed inside a group opens no second one; see {@link RenderContext.groupDepth}.
57
65
  *
58
66
  * @param stream - the stream the output is for
59
67
  * @param options - an explicit width and a path display function
@@ -64,8 +72,11 @@ var Render = class {
64
72
  const theme = (yield* CliTheme).forStream(stream);
65
73
  const seen = CliTheme.forAudience(theme, kind);
66
74
  const links = yield* CliLinks;
75
+ const underActions = yield* underGithubActions;
76
+ const groupDepth = yield* GroupDepth;
67
77
  return {
68
- ...(yield* underGithubActions) ? { neutralizeWorkflowCommands: true } : {},
78
+ ...underActions ? { neutralizeWorkflowCommands: true } : {},
79
+ ...groupDepth > 0 ? { groupDepth } : {},
69
80
  width: options?.width ?? (kind === "human" && terminal[stream].isTerminal ? terminal.width() : Number.POSITIVE_INFINITY),
70
81
  audience: kind,
71
82
  color: seen.color,
@@ -88,7 +99,8 @@ var Render = class {
88
99
  * with no escape of any kind. A colour level paints with the default token styles. An `agent` is colourless and
89
100
  * unlinked whatever `color` and `links` say, as in {@link Render.context}.
90
101
  *
91
- * @param options - the audience, and the colour, glyphs, width, path display, links, neutralizing and link base
102
+ * @param options - the audience, and the colour, glyphs, width, path display, links, neutralizing, link base and
103
+ * open group depth
92
104
  */
93
105
  static contextOf = (options) => {
94
106
  const agent = options.audience === "agent";
@@ -107,7 +119,8 @@ var Render = class {
107
119
  }),
108
120
  displayPath: options.displayPath ?? ((absolute) => absolute),
109
121
  ...options.neutralizeWorkflowCommands ?? options.audience === "ci" ? { neutralizeWorkflowCommands: true } : options.neutralizeWorkflowCommands === false ? { neutralizeWorkflowCommands: false } : {},
110
- ...options.linkBase === void 0 ? {} : { linkBase: options.linkBase }
122
+ ...options.linkBase === void 0 ? {} : { linkBase: options.linkBase },
123
+ ...options.groupDepth === void 0 ? {} : { groupDepth: options.groupDepth }
111
124
  };
112
125
  };
113
126
  /**
@@ -237,6 +250,15 @@ var Render = class {
237
250
  * top-level section. GitHub does not nest groups, so a collapsible inside a group, or inside a list or callout
238
251
  * (where it would not start a line), keeps plain's rendering: its title on a line and its body indented.
239
252
  *
253
+ * A group already open around the output counts the same way. When the context's `groupDepth` is above `0`
254
+ * ({@link RenderContext.groupDepth}, which `Render.context` reads from the fiber-local `ActionLogger.group` in
255
+ * `@effected/github-actions` provides, by the key `WorkflowCommand.groupDepthKey`), a top-level collapsible keeps
256
+ * plain's rendering too, so a document printed inside an `ActionLogger.group` does not open a second group, which
257
+ * would end the first at the runner. The depth is the context's, read when the context was built. What no depth
258
+ * can fix is two fibers writing groups at once: the runner has one log stream, so concurrent sibling groups, this
259
+ * renderer's or `ActionLogger`'s, interleave in it. Render and print a document from the fiber that owns the
260
+ * stream, not from a fiber racing another group.
261
+ *
240
262
  * The runner has two command parsers, and a line is a command if either accepts it: after its leading whitespace it
241
263
  * starts with `::`, or `##[` occurs ANYWHERE in it (a bare `##` is not one). A document's text must not be able to
242
264
  * do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Array as Array$1, Cause, Context, Effect, FileSystem, Layer, LogLevel, Logger, MutableRef, Option, Path, Runtime, Schema, Stdio, Terminal } from "effect";
1
+ import { Array as Array$1, Cause, Context, Duration, Effect, FileSystem, Layer, LogLevel, Logger, MutableRef, Option, Path, Runtime, Schema, Stdio, Terminal } from "effect";
2
2
  import { Audience, AudienceKind, ColorLevel, CurrentRuntimeEnv, RuntimeEnv, TerminalEnv } from "@effected/env";
3
3
  import { CliError, CliOutput, Command, Flag, Param, Prompt } from "effect/cli";
4
4
  import { ConfigValidationError } from "@effected/config-file";
@@ -1412,6 +1412,11 @@ export declare class Doc {
1412
1412
  * GitHub's log format only when it says GitHub Actions, and plain text otherwise, including when it is
1413
1413
  * absent. An explicit `format` is honoured whatever the audience.
1414
1414
  *
1415
+ * The context is built here, at print time, so it carries the fiber's count of open Actions log groups
1416
+ * (`RenderContext.groupDepth`): printed inside an `ActionLogger.group` from `@effected/github-actions`, a
1417
+ * document's top-level collapsible renders as plain text rather than as a second `::group::`, which would end
1418
+ * the open one at the runner.
1419
+ *
1415
1420
  * @param doc - the document
1416
1421
  * @param options - the stream and the format
1417
1422
  */
@@ -3025,18 +3030,23 @@ export declare class Fmt {
3025
3030
  */
3026
3031
  static readonly truncate: (text: string, width: number, options?: TruncateOptions) => string;
3027
3032
  /**
3028
- * A duration in milliseconds as short human text: `250ms`, `1.2s`, `2s`, `1m 3s`, `2m`, `1h 2m`, `2h`.
3033
+ * A duration as short human text: `250ms`, `1.2s`, `2s`, `1m 3s`, `2m`, `1h 2m`, `2h`.
3029
3034
  *
3030
3035
  * @remarks
3031
3036
  * Under a second it is whole milliseconds; under a minute, seconds to one decimal with a trailing `.0`
3032
3037
  * dropped; under an hour, minutes and whole seconds, with the seconds dropped when zero; from an hour, hours and
3033
3038
  * whole minutes, the seconds dropped. A value that rounds up to the next unit (`999.6` to a second, `59999` to a
3034
3039
  * minute, `3599999` to an hour) is written in that unit, so `1000ms`, `60s` and `60m` never appear. There is no
3035
- * days unit. A negative or non-finite input is `0ms`.
3040
+ * days unit. The format is bespoke: core's `Duration.format` renders differently.
3041
+ *
3042
+ * The input is anything `Duration.Input` admits. A plain number is milliseconds and is used as given, never
3043
+ * converted through a `Duration`, which rounds a fractional millisecond to whole nanoseconds and would move a value
3044
+ * such as `999.4999999` across the rounding boundary. A `bigint` is nanoseconds, as in core. A negative, infinite
3045
+ * or invalid input (a string `Duration.Input` cannot parse) is `0ms`: a formatter in a render path never throws.
3036
3046
  *
3037
- * @param ms - the duration in milliseconds
3047
+ * @param input - the duration; a number is milliseconds
3038
3048
  */
3039
- static readonly duration: (ms: number) => string;
3049
+ static readonly duration: (input: Duration.Input) => string;
3040
3050
  /**
3041
3051
  * A ratio from 0 to 1 as a percentage, or a number already from 0 to 100 with `scale: 100`: `83.3%`, `50%`,
3042
3052
  * `100%`.
@@ -3204,6 +3214,20 @@ interface RenderContext {
3204
3214
  * cannot open. The other renderers do not read it.
3205
3215
  */
3206
3216
  readonly linkBase?: string | undefined;
3217
+ /**
3218
+ * How many GitHub Actions log groups are open around the output. Unset or `0`, none is: `Render.githubLog` then
3219
+ * writes a top-level collapsible as a `::group::`. Above `0` it writes the collapsible as plain does, its title on
3220
+ * a line and its body indented, because the runner does not nest groups and a second `::group::` would end the open
3221
+ * one. The other renderers do not read it.
3222
+ *
3223
+ * @remarks
3224
+ * `Render.context` reads it from the fiber-local keyed by `WorkflowCommand.groupDepthKey`
3225
+ * (`@effected/github-commands`), the count `ActionLogger.group` in `@effected/github-actions` provides around a
3226
+ * group's body, so a context built inside a group knows it. It is read when the context is built, not when a
3227
+ * document is rendered: a context built outside a group and used inside one still says none is open, so build it
3228
+ * where the document is printed, as `Doc.print` does.
3229
+ */
3230
+ readonly groupDepth?: number | undefined;
3207
3231
  }
3208
3232
  /**
3209
3233
  * Options for {@link Render.contextOf}.
@@ -3233,6 +3257,8 @@ interface RenderContextOfOptions {
3233
3257
  readonly neutralizeWorkflowCommands?: boolean | undefined;
3234
3258
  /** See {@link RenderContext.linkBase}; unset by default. */
3235
3259
  readonly linkBase?: string | undefined;
3260
+ /** See {@link RenderContext.groupDepth}; unset (no group open) by default. */
3261
+ readonly groupDepth?: number | undefined;
3236
3262
  }
3237
3263
  /**
3238
3264
  * Options for {@link Render.context}.
@@ -3287,7 +3313,10 @@ export declare class Render {
3287
3313
  * required), for every audience, since the runner reads whatever is written there;
3288
3314
  * - `width` is the option, else `TerminalEnv.width()` for a human whose stream is a terminal, and **unbounded**
3289
3315
  * (`Infinity`) for a human whose stream is not one (`tool | grep`, `tool > out.txt`), an agent or a CI, so nothing
3290
- * a reader needs is truncated or wrapped for a terminal that is not there: a pipe has no width to honour.
3316
+ * a reader needs is truncated or wrapped for a terminal that is not there: a pipe has no width to honour;
3317
+ * - `groupDepth` is the fiber's count of open Actions log groups (the fiber-local keyed by
3318
+ * `WorkflowCommand.groupDepthKey`, which `ActionLogger.group` provides), set when it is above `0`, so a
3319
+ * document printed inside a group opens no second one; see {@link RenderContext.groupDepth}.
3291
3320
  *
3292
3321
  * @param stream - the stream the output is for
3293
3322
  * @param options - an explicit width and a path display function
@@ -3302,7 +3331,8 @@ export declare class Render {
3302
3331
  * with no escape of any kind. A colour level paints with the default token styles. An `agent` is colourless and
3303
3332
  * unlinked whatever `color` and `links` say, as in {@link Render.context}.
3304
3333
  *
3305
- * @param options - the audience, and the colour, glyphs, width, path display, links, neutralizing and link base
3334
+ * @param options - the audience, and the colour, glyphs, width, path display, links, neutralizing, link base and
3335
+ * open group depth
3306
3336
  */
3307
3337
  static readonly contextOf: (options: RenderContextOfOptions) => RenderContext;
3308
3338
  /**
@@ -3432,6 +3462,15 @@ export declare class Render {
3432
3462
  * top-level section. GitHub does not nest groups, so a collapsible inside a group, or inside a list or callout
3433
3463
  * (where it would not start a line), keeps plain's rendering: its title on a line and its body indented.
3434
3464
  *
3465
+ * A group already open around the output counts the same way. When the context's `groupDepth` is above `0`
3466
+ * ({@link RenderContext.groupDepth}, which `Render.context` reads from the fiber-local `ActionLogger.group` in
3467
+ * `@effected/github-actions` provides, by the key `WorkflowCommand.groupDepthKey`), a top-level collapsible keeps
3468
+ * plain's rendering too, so a document printed inside an `ActionLogger.group` does not open a second group, which
3469
+ * would end the first at the runner. The depth is the context's, read when the context was built. What no depth
3470
+ * can fix is two fibers writing groups at once: the runner has one log stream, so concurrent sibling groups, this
3471
+ * renderer's or `ActionLogger`'s, interleave in it. Render and print a document from the fiber that owns the
3472
+ * stream, not from a fiber racing another group.
3473
+ *
3435
3474
  * The runner has two command parsers, and a line is a command if either accepts it: after its leading whitespace it
3436
3475
  * starts with `::`, or `##[` occurs ANYWHERE in it (a bare `##` is not one). A document's text must not be able to
3437
3476
  * do that (`::add-mask::`, `::error::`, `##[error]`), so such a `::` line gets a braille pattern blank (U+2800) in
@@ -32,6 +32,7 @@ const blockLines = (block, ctx) => {
32
32
  switch (block._tag) {
33
33
  case "Annotation": return [annotationLine(block)];
34
34
  case "Collapsible": {
35
+ if ((ctx.groupDepth ?? 0) > 0) return plainLines([block], ctx);
35
36
  const title = commandData(plainInline(block.title, ctx).map((span) => span.text).join(""));
36
37
  return [
37
38
  WorkflowCommand.group(title),
@@ -48,7 +49,8 @@ const blockLines = (block, ctx) => {
48
49
  }
49
50
  };
50
51
  /**
51
- * Render a document for a GitHub Actions log: plain text, with a top-level collapsible as a group.
52
+ * Render a document for a GitHub Actions log: plain text, with a top-level collapsible as a group when no group is
53
+ * open around the output (`ctx.groupDepth`).
52
54
  *
53
55
  * @internal
54
56
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/cli",
3
- "version": "0.20.0",
3
+ "version": "0.22.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,10 +57,10 @@
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "dependencies": {
60
- "@effected/github-commands": "^0.6.0"
60
+ "@effected/github-commands": "^0.6.2"
61
61
  },
62
62
  "peerDependencies": {
63
- "@effected/config-file": "^0.18.0",
63
+ "@effected/config-file": "^0.19.1",
64
64
  "@effected/env": "^0.5.0",
65
65
  "@effected/glob": "^0.14.0",
66
66
  "@effected/walker": "^0.19.0",
package/ui/CliUi.js CHANGED
@@ -306,7 +306,7 @@ var CliUi = class CliUi {
306
306
  * so core renders its own message and `CliRuntime.main` exits `64`. Name the parameter with `flag` (the name
307
307
  * without dashes) or `argument` so that error can be built.
308
308
  *
309
- * The options are {@link @effected/cli!CliPromptFallbackOptions}, the same as `CliPrompt.fallback`'s, and `clear`
309
+ * The options are `CliPromptFallbackOptions` (from `@effected/cli`), the same as `CliPrompt.fallback`'s, and `clear`
310
310
  * as for {@link CliUi.run}.
311
311
  *
312
312
  * It runs during parsing, whose environment is core's alone, so it reads `CliTheme` if one is there: with
package/ui/UiKey.js CHANGED
@@ -5,7 +5,12 @@ const named = (name) => ({
5
5
  });
6
6
  const CONTROL = /[\u0000-\u001f\u007f]/;
7
7
  /**
8
- * Normalising Ink's input into {@link UiKey}s.
8
+ * Normalising Ink's input into {@link (UiKey:type)}s.
9
+ *
10
+ * @remarks
11
+ * A typed `const` rather than the house static-class form by force, not by
12
+ * choice: a class cannot share its name with the {@link (UiKey:type)} union
13
+ * (TS2300, duplicate identifier), and that union is the key model itself.
9
14
  *
10
15
  * @public
11
16
  */
package/ui.d.ts CHANGED
@@ -484,7 +484,7 @@ export declare class CliUi {
484
484
  * so core renders its own message and `CliRuntime.main` exits `64`. Name the parameter with `flag` (the name
485
485
  * without dashes) or `argument` so that error can be built.
486
486
  *
487
- * The options are {@link @effected/cli!CliPromptFallbackOptions}, the same as `CliPrompt.fallback`'s, and `clear`
487
+ * The options are `CliPromptFallbackOptions` (from `@effected/cli`), the same as `CliPrompt.fallback`'s, and `clear`
488
488
  * as for {@link CliUi.run}.
489
489
  *
490
490
  * It runs during parsing, whose environment is core's alone, so it reads `CliTheme` if one is there: with
@@ -619,7 +619,12 @@ export type UiKey = {
619
619
  readonly char: string;
620
620
  };
621
621
  /**
622
- * Normalising Ink's input into {@link UiKey}s.
622
+ * Normalising Ink's input into {@link (UiKey:type)}s.
623
+ *
624
+ * @remarks
625
+ * A typed `const` rather than the house static-class form by force, not by
626
+ * choice: a class cannot share its name with the {@link (UiKey:type)} union
627
+ * (TS2300, duplicate identifier), and that union is the key model itself.
623
628
  *
624
629
  * @public
625
630
  */
@@ -1517,8 +1522,8 @@ interface TextInputScreenOptions {
1517
1522
  * does the scrollback; erase the frame as well with `clear: true` on the run when even the mask's length should not
1518
1523
  * stay behind.
1519
1524
  *
1520
- * The message `validate` returns is drawn as it is, unmasked: a message that echoes the value (`"ghp_abc is a
1521
- * token"`) draws the secret in the frame. Say what is wrong without quoting the value.
1525
+ * The message `validate` returns is drawn as it is, unmasked: a message that echoes the value
1526
+ * (`"ghp_abc is a token"`) draws the secret in the frame. Say what is wrong without quoting the value.
1522
1527
  */
1523
1528
  readonly mask?: string | true | ((value: string) => boolean);
1524
1529
  }
@@ -1665,7 +1670,7 @@ declare const UiStreams_base: Context.Reference<UiStreamsShape>;
1665
1670
  */
1666
1671
  export declare class UiStreams extends UiStreams_base {}
1667
1672
  /**
1668
- * The styling props of an Ink `Text` that a {@link @effected/cli!Style} maps to.
1673
+ * The styling props of an Ink `Text` that a `Style` (from `@effected/cli`) maps to.
1669
1674
  *
1670
1675
  * @public
1671
1676
  */