@effected/cli 0.22.1 → 0.23.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/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
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
+ import { Distribution } from "@effected/engine";
4
5
  import { ConfigValidationError } from "@effected/config-file";
5
6
  declare const Cancelled_base: Schema.Class<Cancelled, Schema.TaggedStruct<"Cancelled", {
6
7
  readonly reason: Schema.Literals<readonly ["escape", "interrupt"]>;
@@ -1847,6 +1848,24 @@ export declare class CliLog {
1847
1848
  * cannot lower it below the level the layer installed, because Effect has already dropped those records.
1848
1849
  */
1849
1850
  static readonly Level: Context.Reference<LogLevel.LogLevel>;
1851
+ /**
1852
+ * Read a log level the way `CliLog.layer` reads its environment variable: `Option.some` of the level, or
1853
+ * `Option.none()` when the text is not one.
1854
+ *
1855
+ * @remarks
1856
+ * Case-insensitive, and not trimmed. The spellings are `all`, `trace`, `debug`, `info`, `warn` (or `warning`),
1857
+ * `error`, `fatal` and `none`. For a host that reads a level from its own flag or config and wants the same answer
1858
+ * the layer would give.
1859
+ *
1860
+ * @example
1861
+ * ```ts
1862
+ * CliLog.parseLevel("WARNING") // Option.some("Warn")
1863
+ * CliLog.parseLevel("loud") // Option.none()
1864
+ * ```
1865
+ *
1866
+ * @param text - the level's spelling
1867
+ */
1868
+ static parseLevel(text: string): Option.Option<LogLevel.LogLevel>;
1850
1869
  /**
1851
1870
  * The whole logger set of a program: a `CliLogger` for ordinary lines plus the diagnostics sink.
1852
1871
  *
@@ -2030,10 +2049,21 @@ interface CliEnvOptions {
2030
2049
  */
2031
2050
  readonly formatter?: Partial<CliOutput.Formatter> | undefined;
2032
2051
  /**
2033
- * Turns an absolute path into its display form, for example relative to the workspace: the stack frames of the
2034
- * default failure report are shown through it. Only `CliRuntime.main` reads this. The identity by default.
2052
+ * How an absolute path is shown: a function, or `{ relativeTo: root }` to show a path under `root` relative to it.
2053
+ * The identity by default.
2054
+ *
2055
+ * @remarks
2056
+ * It is every path the kit shows for this run: `Render.context` takes it as its default, so `Doc.print`, a
2057
+ * `Doc.path` or file link, and a live view's `final` document follow it, and so do the stack frames of the default
2058
+ * failure report. An explicit `displayPath` passed to `Doc.print` or `Render.context` still wins.
2059
+ *
2060
+ * `{ relativeTo: root }` takes an absolute `root`. A path under it is shown relative to it; `root` itself, and a path
2061
+ * outside it, are shown absolute, since a `../..` chain says less than the absolute path. It uses the `Path` service
2062
+ * when the platform provides one (as `CliRuntime.main`'s does), and otherwise a plain prefix test on `/` and `\`.
2035
2063
  */
2036
- readonly displayPath?: ((absolute: string) => string) | undefined;
2064
+ readonly displayPath?: ((absolute: string) => string) | {
2065
+ readonly relativeTo: string;
2066
+ } | undefined;
2037
2067
  /**
2038
2068
  * Which frames of a defect's stack the default failure report shows: `app`, the default, leaves out every
2039
2069
  * `node_modules` frame (Effect's and any other dependency's) and the runtime's own (every `node:` frame and every
@@ -2310,7 +2340,8 @@ interface CliFailureOptions {
2310
2340
  * one line `interrupted`.
2311
2341
  *
2312
2342
  * All text goes through the document, so a control character in a message or a stack frame never reaches the terminal.
2313
- * Render it with `Render.context` and `Render.plain`, `ansi`, `markdown` or `githubLog`, or `Doc.print` it.
2343
+ * Building it never throws: an error whose `_tag`, `message`, `stack`, `cause` or `issue` getter throws is drawn without
2344
+ * that part (an unreadable `stack` as `no stack`, an unreadable `cause` ending the chain there). Render it with `Render.context` and `Render.plain`, `ansi`, `markdown` or `githubLog`, or `Doc.print` it.
2314
2345
  *
2315
2346
  * @public
2316
2347
  */
@@ -2324,6 +2355,93 @@ export declare class CliFailure {
2324
2355
  */
2325
2356
  static readonly toDoc: (cause: Cause.Cause<unknown>, options?: CliFailureOptions) => Document;
2326
2357
  }
2358
+ /**
2359
+ * An output format a `--format` flag can offer: `auto` (the renderer the audience gets, as `Doc.print` picks it),
2360
+ * one of the `Doc` renderers by name, or `json`, the command's data rather than its document.
2361
+ *
2362
+ * @public
2363
+ */
2364
+ type CliFormatName = "auto" | "plain" | "ansi" | "markdown" | "githubLog" | "json";
2365
+ /**
2366
+ * Options for {@link CliFormat.flag}.
2367
+ *
2368
+ * @public
2369
+ */
2370
+ interface CliFormatFlagOptions<F extends CliFormatName> {
2371
+ /** The format when the flag is not given: `auto` when it is offered, otherwise the first one. */
2372
+ readonly default?: F | undefined;
2373
+ /** The flag's name; `format` by default. */
2374
+ readonly name?: string | undefined;
2375
+ /** The flag's description in help; one naming the offered formats by default. */
2376
+ readonly description?: string | undefined;
2377
+ }
2378
+ /**
2379
+ * What {@link CliFormat.print} prints: the command's document and, for `json`, its data.
2380
+ *
2381
+ * @public
2382
+ */
2383
+ interface CliFormatOutput {
2384
+ /** The document, built only when a document format is chosen. */
2385
+ readonly doc: () => Document;
2386
+ /** The data `json` prints, built only when `json` is chosen. A `json` format with no `json` here is a defect. */
2387
+ readonly json?: (() => unknown) | undefined;
2388
+ }
2389
+ /**
2390
+ * Options for {@link CliFormat.print}.
2391
+ *
2392
+ * @public
2393
+ */
2394
+ interface CliFormatPrintOptions {
2395
+ /** The stream to write to; stdout by default. */
2396
+ readonly stream?: "stdout" | "stderr" | undefined;
2397
+ /** The JSON indent, in spaces; `2` by default, `0` for one line. */
2398
+ readonly indent?: number | undefined;
2399
+ }
2400
+ /**
2401
+ * A `--format` flag and the one print that honours it, so a command builds its report once as a `Doc` and its data
2402
+ * once as a value, and never writes per-format output by hand.
2403
+ *
2404
+ * @remarks
2405
+ * The flag is core's `Flag.Literals` with the kit's names, default and description; the print hands every document
2406
+ * format to `Doc.print` and writes `json` itself.
2407
+ *
2408
+ * ```ts
2409
+ * const format = CliFormat.flag(["auto", "markdown", "json"])
2410
+ * const doctor = Command.make("doctor", { format }, ({ format }) =>
2411
+ * CliFormat.print(format, { doc: () => report(results), json: () => results }),
2412
+ * )
2413
+ * ```
2414
+ *
2415
+ * @public
2416
+ */
2417
+ export declare class CliFormat {
2418
+ private constructor();
2419
+ /**
2420
+ * A `--format` flag offering `formats`.
2421
+ *
2422
+ * @remarks
2423
+ * Its default is `options.default`, else `auto` when it is offered, else the first format. Any other value is a
2424
+ * usage error from core's parser, naming the accepted ones.
2425
+ *
2426
+ * @param formats - the formats the command offers, at least one
2427
+ * @param options - the default, the flag's name and its description
2428
+ */
2429
+ static readonly flag: <const F extends CliFormatName>(formats: readonly [F, ...ReadonlyArray<F>], options?: CliFormatFlagOptions<F>) => Flag.Flag<F>;
2430
+ /**
2431
+ * Print a command's output in `format`.
2432
+ *
2433
+ * @remarks
2434
+ * `json` writes `output.json()` as JSON, one `Console.log` (or `Console.error` for stderr), indented by `indent`.
2435
+ * Under GitHub Actions a `##[` inside it is written as the JSON escape `#\u0023[`, which decodes to the same text,
2436
+ * so the runner cannot read it as a command. A value JSON cannot hold (a cycle, a bigint) is a defect. Every other
2437
+ * format prints `output.doc()` with `Doc.print`, which renders it as `auto` or the named renderer would.
2438
+ *
2439
+ * @param format - the chosen format, usually the value of {@link CliFormat.flag}
2440
+ * @param output - the document and the data
2441
+ * @param options - the stream and the JSON indent
2442
+ */
2443
+ static readonly print: (format: CliFormatName, output: CliFormatOutput, options?: CliFormatPrintOptions) => Effect.Effect<void, never, CliTheme | TerminalEnv | Audience | CliLinks>;
2444
+ }
2327
2445
  declare const CliInteractive_base: Context.Reference<boolean>;
2328
2446
  /**
2329
2447
  * Whether this run may prompt a person: a human audience, with a terminal on
@@ -2598,6 +2716,22 @@ interface FailureDetails {
2598
2716
  readonly status?: boolean | undefined;
2599
2717
  readonly spans?: "app" | "all" | "off" | undefined;
2600
2718
  }) => ReadonlyArray<string>;
2719
+ /**
2720
+ * The exit code this failure exits with: the error's own `Runtime.errorExitCode` when it carries one, else its
2721
+ * entry in `exitCodes` (a typed failure only), else `exitCode`, else `1`.
2722
+ *
2723
+ * @remarks
2724
+ * The code is decided before `render` is called, so a render can print it. A hook that parses a fixed stderr shape
2725
+ * builds it here, reading the tag off `error` itself:
2726
+ *
2727
+ * ```ts
2728
+ * const render = (error: unknown, details: FailureDetails) => {
2729
+ * const tag = Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag) ? error._tag : "Defect";
2730
+ * return `${details.exitCode} ${tag}: ${Fmt.sanitize(Fmt.describe(error))}`;
2731
+ * };
2732
+ * ```
2733
+ */
2734
+ readonly exitCode: number;
2601
2735
  }
2602
2736
  /**
2603
2737
  * How a failure is turned into output and an exit code.
@@ -2637,6 +2771,23 @@ interface ReportFailuresOptions {
2637
2771
  * POSIX exit status can carry — `256` wraps to `0` and passes a failed run.
2638
2772
  */
2639
2773
  readonly exitCode?: number | undefined;
2774
+ /**
2775
+ * Exit codes by tag: a typed failure whose `_tag` is a key here exits with that code.
2776
+ *
2777
+ * @remarks
2778
+ * Checked after the error's own `Runtime.errorExitCode` (an error that chose its code keeps it) and before
2779
+ * `exitCode`, which stays the fallback for every other failure, defects included. Only a typed failure is looked
2780
+ * up: a defect that happens to carry a `_tag` exits with `exitCode`. A usage error (`usageExitCode`), a cancel
2781
+ * (`130`) and a non-interactive prompt (`64`) carry their own codes and are not looked up either.
2782
+ *
2783
+ * Each code must be an integer in `1..255`: `0` would pass a failed run, and `256` wraps to `0`. Anything else
2784
+ * throws a `RangeError` when the options are passed, since it is a wiring mistake, not a runtime condition.
2785
+ *
2786
+ * ```ts
2787
+ * CliRuntime.main(program, { platform, exitCodes: { DataStoreError: 3, SidecarTimeoutError: 2 }, exitCode: 5 })
2788
+ * ```
2789
+ */
2790
+ readonly exitCodes?: Readonly<Record<string, number>> | undefined;
2640
2791
  /**
2641
2792
  * The exit code for a usage error: a `ShowHelp` carrying parse errors, or a
2642
2793
  * `CliError.UserError` `Command.runWith` already printed.
@@ -2713,6 +2864,26 @@ interface MainOptions<RP, EP> extends ReportFailuresOptions {
2713
2864
  * no errors are printed, so nothing marks the help as a usage error's.
2714
2865
  */
2715
2866
  readonly helpOnUsageError?: "stdout" | "stderr" | undefined;
2867
+ /**
2868
+ * The carrier package the bin was installed through, when a carrier launched it: `null` (or `undefined`) for a
2869
+ * direct install.
2870
+ *
2871
+ * @remarks
2872
+ * Given (even as `null`), `main` provides `@effected/engine`'s `CurrentDistribution` around the program, as
2873
+ * `Option.some` of the carrier or `Option.none()`, so a program and the engine it calls read the same value. With a
2874
+ * carrier, `--version` ends with its `via <name> <version>` suffix (`distributionSuffix`), appended to whatever
2875
+ * `formatVersion` is in effect, the coloured default or `env.formatter`'s: do not also append it in
2876
+ * `env.formatter`.
2877
+ *
2878
+ * Core's default `formatVersion` prints `<name> v<version>`. A program moving off a hand-rolled `--version` that
2879
+ * printed another shape (no `v`, say) keeps it by passing that shape as `env.formatter`'s `formatVersion`; the
2880
+ * suffix is appended to it either way.
2881
+ *
2882
+ * ```ts
2883
+ * CliRuntime.main(CliAudience.run(root, { version }), { platform, env, distribution: options.distribution ?? null })
2884
+ * ```
2885
+ */
2886
+ readonly distribution?: Distribution | null | undefined;
2716
2887
  }
2717
2888
  /**
2718
2889
  * Report a CLI program's failures through the program's own logger.
@@ -2988,6 +3159,20 @@ interface PercentOptions {
2988
3159
  */
2989
3160
  readonly scale?: 1 | 100 | undefined;
2990
3161
  }
3162
+ /**
3163
+ * Options for {@link Fmt.pad}.
3164
+ *
3165
+ * @public
3166
+ */
3167
+ interface PadOptions {
3168
+ /**
3169
+ * Where the text sits in the padded field: `left` (the default) fills on the right, `right` fills on the left, and
3170
+ * `center` splits the fill, the odd column going on the right.
3171
+ */
3172
+ readonly align?: "left" | "right" | "center" | undefined;
3173
+ /** The fill, one column wide; a space by default. Anything that is not exactly one column wide is a space. */
3174
+ readonly fill?: string | undefined;
3175
+ }
2991
3176
  /**
2992
3177
  * Small, pure formatting primitives for terminal output.
2993
3178
  *
@@ -3067,6 +3252,86 @@ export declare class Fmt {
3067
3252
  * @param plural - the noun otherwise; `singular` with an `s` appended by default
3068
3253
  */
3069
3254
  static readonly plural: (n: number, singular: string, plural?: string) => string;
3255
+ /**
3256
+ * Pad `text` to `width` terminal columns, measured as {@link Fmt.width} measures: a wide character counts two and
3257
+ * an escape sequence none, so coloured text pads to the same column as plain text.
3258
+ *
3259
+ * @remarks
3260
+ * Text already `width` columns or wider is returned unchanged: compose with {@link Fmt.truncate} to cut it. A
3261
+ * `width` that is not a finite number pads nothing. Never throws.
3262
+ *
3263
+ * @param text - the text to pad
3264
+ * @param width - the columns the result should take
3265
+ * @param options - the alignment and the fill
3266
+ */
3267
+ static readonly pad: (text: string, width: number, options?: PadOptions) => string;
3268
+ /**
3269
+ * `text` without its escape sequences: CSI (colour, cursor moves, erases), OSC (titles, OSC 8 hyperlinks) ending in
3270
+ * BEL or ST, the DCS, SOS, PM and APC strings, and every other escape, the nF sequences (`ESC ( B`) and the
3271
+ * two-byte ones (`ESC 7`, `ESC 8`, `ESC =`, `ESC c`).
3272
+ *
3273
+ * @remarks
3274
+ * The same strip {@link Fmt.width} and {@link Fmt.sanitize} start from. Only complete sequences go: a lone ESC, or
3275
+ * any other control character, is kept, and so is an ESC in front of a lowercase letter other than `c`, which a
3276
+ * stray ESC before a word would otherwise cost the word its first letter. An unterminated CSI or OSC keeps its `[`
3277
+ * or `]`. Use {@link Fmt.sanitize} for text that will be laid out and printed.
3278
+ *
3279
+ * @param text - the text
3280
+ */
3281
+ static readonly stripAnsi: (text: string) => string;
3282
+ /**
3283
+ * Whether `text` is nothing but escape sequences, as {@link Fmt.stripAnsi} reads them: a cursor hide or show, a
3284
+ * clear-screen, an erase-scrollback. Empty text is not escape-only, and neither is a bare control character
3285
+ * (`\r`, BEL), which is not an escape sequence.
3286
+ *
3287
+ * @param text - the text
3288
+ */
3289
+ static readonly isEscapeOnly: (text: string) => boolean;
3290
+ /**
3291
+ * Integers as compact ranges: `[1, 2, 3, 5, 10, 11]` is `"1-3,5,10-11"`, a run of consecutive numbers written
3292
+ * `start-end` and the rest one by one, joined with commas.
3293
+ *
3294
+ * @remarks
3295
+ * The input is sorted and its duplicates dropped first, so order does not matter. Anything that is not a finite
3296
+ * integer is left out, and no input is `""`. Never throws.
3297
+ *
3298
+ * @param numbers - the integers, such as line numbers
3299
+ */
3300
+ static readonly ranges: (numbers: Iterable<number>) => string;
3301
+ /**
3302
+ * Any value as text, without ever throwing.
3303
+ *
3304
+ * @remarks
3305
+ * A string is itself; a number, boolean, bigint or symbol is `String(value)`; `undefined` and `null` are
3306
+ * `"undefined"` and `"null"`. A value with a string `message` (an `Error`, one from another realm, a plain
3307
+ * `{ message }`) is that message. Anything else is its JSON, or `String(value)` when it has none (a cycle, a
3308
+ * function), or `"[unprintable value]"` when that throws too. A getter that throws is a value with no message.
3309
+ *
3310
+ * The text is not sanitised: pass it through {@link Fmt.sanitize} before printing data you do not control. For
3311
+ * the messages along an error's `cause` chain, use {@link Fmt.causeMessage}.
3312
+ *
3313
+ * @param value - the value
3314
+ */
3315
+ static readonly describe: (value: unknown) => string;
3316
+ /**
3317
+ * Every distinct non-empty message along a value's `cause` chain, nearest first and joined with `: `, or
3318
+ * `undefined` when nothing along it has one.
3319
+ *
3320
+ * @remarks
3321
+ * The kit's one guarded cause-message read, the one its own errors render a preserved cause with. A node's message
3322
+ * is a thrown string or a non-empty string `message` property, read without `instanceof Error`, so an error from
3323
+ * another realm or a plain `{ message }` object counts. A deeper message the joined text already contains is not
3324
+ * repeated, and one that contains the joined text replaces it, so a `SqlError` wrapping SQLite's own text reads
3325
+ * `Failed to execute statement: SQLITE_BUSY: database is locked`. It is the whole chain, not the innermost
3326
+ * message alone. The walk stops after eight nodes, so a cyclic chain terminates.
3327
+ *
3328
+ * Never throws: a `message` getter that throws leaves that node without text, and a `cause` getter that throws ends
3329
+ * the walk with the text found so far, `undefined` when there is none, so a caller's own fallback
3330
+ * (`?? Fmt.describe(value)`) applies.
3331
+ *
3332
+ * @param value - the error, or whatever was thrown
3333
+ */
3334
+ static readonly causeMessage: (value: unknown) => string | undefined;
3070
3335
  }
3071
3336
  /**
3072
3337
  * The severity of a GitHub annotation.
@@ -3317,6 +3582,8 @@ export declare class Render {
3317
3582
  * - `groupDepth` is the fiber's count of open Actions log groups (the fiber-local keyed by
3318
3583
  * `WorkflowCommand.groupDepthKey`, which `ActionLogger.group` provides), set when it is above `0`, so a
3319
3584
  * document printed inside a group opens no second one; see {@link RenderContext.groupDepth}.
3585
+ * - `displayPath` is the option, else the run's own path display (`CliEnvOptions.displayPath`, which
3586
+ * `CliEnv.layer` sets), else the identity.
3320
3587
  *
3321
3588
  * @param stream - the stream the output is for
3322
3589
  * @param options - an explicit width and a path display function
@@ -3527,5 +3794,5 @@ export declare class SchemaIssueRenderer {
3527
3794
  */
3528
3795
  static readonly render: (issue: unknown) => ReadonlyArray<string>;
3529
3796
  }
3530
- export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
3797
+ export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliFormatFlagOptions, CliFormatName, CliFormatOutput, CliFormatPrintOptions, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PadOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
3531
3798
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -15,6 +15,7 @@ import { CliColor } from "./CliColor.js";
15
15
  import { CliPrompt } from "./CliPrompt.js";
16
16
  import { CliEnv } from "./CliEnv.js";
17
17
  import { CliExit } from "./CliExit.js";
18
+ import { CliFormat } from "./CliFormat.js";
18
19
  import { CliLogger } from "./CliLogger.js";
19
20
  import { CliLog } from "./CliLog.js";
20
21
  import { CliMessage } from "./CliMessage.js";
@@ -23,4 +24,4 @@ import { ConfigIssueRenderer } from "./ConfigIssueRenderer.js";
23
24
  import { GithubAnnotation } from "./GithubAnnotation.js";
24
25
  import { SchemaIssueRenderer } from "./SchemaIssueRenderer.js";
25
26
 
26
- export { Cancelled, CliAudience, CliColor, CliDoc, CliEnv, CliExit, CliFailure, CliInteractive, CliLinks, CliLog, CliLogger, CliMessage, CliPrompt, CliRuntime, CliTheme, ConfigIssueRenderer, Doc, Fmt, GithubAnnotation, Glyphs, NotInteractive, Render, SchemaIssueRenderer, Status, Token };
27
+ export { Cancelled, CliAudience, CliColor, CliDoc, CliEnv, CliExit, CliFailure, CliFormat, CliInteractive, CliLinks, CliLog, CliLogger, CliMessage, CliPrompt, CliRuntime, CliTheme, ConfigIssueRenderer, Doc, Fmt, GithubAnnotation, Glyphs, NotInteractive, Render, SchemaIssueRenderer, Status, Token };
@@ -0,0 +1,57 @@
1
+ import { Predicate } from "effect";
2
+
3
+ // src/internal/causeMessage.ts
4
+ /** How many nodes of a cause chain the walk reads before stopping, so a cyclic chain cannot spin. */
5
+ const CAUSE_DEPTH = 8;
6
+ /**
7
+ * The message a single node carries: a thrown string, or a non-empty string `message` property. A getter or
8
+ * proxy trap that throws is a node with no message.
9
+ */
10
+ const messageAt = (node) => {
11
+ try {
12
+ const text = Predicate.isString(node) ? node : Predicate.hasProperty(node, "message") && Predicate.isString(node.message) ? node.message : void 0;
13
+ return text === void 0 || text.trim() === "" ? void 0 : text;
14
+ } catch {
15
+ return;
16
+ }
17
+ };
18
+ /** The next link of the chain: a node's `cause`, or `undefined` when it has none or reading it throws. */
19
+ const causeAt = (node) => {
20
+ try {
21
+ return Predicate.hasProperty(node, "cause") ? node.cause : void 0;
22
+ } catch {
23
+ return;
24
+ }
25
+ };
26
+ /**
27
+ * Every distinct non-empty message a preserved cause chain carries, nearest
28
+ * first and joined with `: `, or `undefined` when nothing along it has one.
29
+ *
30
+ * A `Schema.Defect()` field holds whatever was thrown, so the read is guarded
31
+ * rather than assumed: `Predicate.hasProperty` + `Predicate.isString` accepts
32
+ * an `Error`, a cross-realm `Error` that fails `instanceof`, a plain
33
+ * `{ message }` object and a thrown string alike. An empty or whitespace-only
34
+ * message counts as none. The walk follows `cause` links because a wrapper's
35
+ * text is often the generic summary and the driver's text underneath is the
36
+ * actionable detail, yet a wrapper that composes its own context must not
37
+ * lose it either; so nothing is dropped, and a deeper text the nearer one
38
+ * already contains is not repeated. The walk is bounded, so a cycle terminates.
39
+ *
40
+ * It never throws: every property read is guarded, so a `message` getter that throws leaves that link
41
+ * without text, and a `cause` getter that throws ends the walk with what was found so far.
42
+ */
43
+ const causeMessage = (cause) => {
44
+ let message;
45
+ let node = cause;
46
+ for (let depth = 0; depth < CAUSE_DEPTH && node !== void 0 && node !== null; depth++) {
47
+ const text = messageAt(node);
48
+ if (text !== void 0) {
49
+ if (message === void 0 || text.includes(message)) message = text;
50
+ else if (!message.includes(text)) message = `${message}: ${text}`;
51
+ }
52
+ node = causeAt(node);
53
+ }
54
+ return message;
55
+ };
56
+
57
+ export { causeMessage };
@@ -0,0 +1,46 @@
1
+ import { Context, Effect, Layer, Option, Path } from "effect";
2
+
3
+ // src/internal/displayPath.ts
4
+ /**
5
+ * The display that shows every path as it is.
6
+ *
7
+ * @internal
8
+ */
9
+ const identity = (absolute) => absolute;
10
+ /**
11
+ * The path display in effect: the identity unless `CliEnv.layer` was given a `displayPath`.
12
+ *
13
+ * @internal
14
+ */
15
+ const CurrentDisplayPath = Context.Reference("@effected/cli/CurrentDisplayPath", { defaultValue: () => identity });
16
+ /**
17
+ * A path relative to `root` when it lies under it, and the path unchanged when it is `root` itself or lies outside it
18
+ * (a `../..` chain says less than the absolute path). With a `Path` service its `relative` decides; without one, a
19
+ * plain prefix test on `/` and `\` does.
20
+ *
21
+ * @internal
22
+ */
23
+ const relativeTo = (root, path) => Option.match(path, {
24
+ onSome: (p) => (absolute) => {
25
+ const rel = p.relative(root, absolute);
26
+ return rel === "" || rel === ".." || rel.startsWith(`..${p.sep}`) || rel.startsWith("../") || p.isAbsolute(rel) ? absolute : rel;
27
+ },
28
+ onNone: () => {
29
+ let end = root.length;
30
+ while (end > 0 && (root[end - 1] === "/" || root[end - 1] === "\\")) end -= 1;
31
+ const base = root.slice(0, end);
32
+ return (absolute) => {
33
+ const next = absolute.charAt(base.length);
34
+ return absolute.startsWith(base) && (next === "/" || next === "\\") && absolute.length > base.length + 1 ? absolute.slice(base.length + 1) : absolute;
35
+ };
36
+ }
37
+ });
38
+ /**
39
+ * Sets {@link CurrentDisplayPath} from the option, reading `Path` from the context when it is there. No option sets
40
+ * nothing.
41
+ *
42
+ * @internal
43
+ */
44
+ const displayPathLayer = (option) => option === void 0 ? Layer.empty : Layer.effect(CurrentDisplayPath)(typeof option === "function" ? Effect.succeed(option) : Effect.map(Effect.serviceOption(Path.Path), (path) => relativeTo(option.relativeTo, path)));
45
+
46
+ export { CurrentDisplayPath, displayPathLayer, identity, relativeTo };
@@ -1,16 +1,24 @@
1
1
  // src/internal/displayWidth.ts
2
2
  const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
3
- const ANSI = /\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)|\u001B\[[0-?]*[ -/]*[@-~]/g;
3
+ const ANSI = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001B]*(?:\u0007|\u001B\\)|[PX^_][^\u001B]*\u001B\\|[()*+\-./][0-~]|\$[()*+\-./]?[@-~]|#[3-68]| [FGLMN]|[0-?@-Z\\^_c])/g;
4
4
  const EMOJI = /^\p{RGI_Emoji}$/v;
5
5
  const WIDE2 = /[\p{Emoji_Presentation}\u1100-\u115E\u2329\u232A\u2630-\u2637\u268A-\u268F\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312F\u3131-\u3163\u3165-\u318E\u3190-\u31E5\u31EF-\u321E\u3220-\u3247\u3250-\u4DBF\u4DC0-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6\u{16FE0}-\u{16FF6}\u{17000}-\u{191FF}\u{1AFF0}-\u{1AFFF}\u{1B000}-\u{1B2FF}\u{1D300}-\u{1D376}\u{1F200}-\u{1F265}\u{20000}-\u{3FFFD}]/u;
6
6
  const ZERO2 = /^[\p{Mn}\p{Me}\p{Cf}\p{Cc}\u115F\u1160\u3164\uFFA0]+$/u;
7
7
  /**
8
- * The text without its ANSI escape sequences: CSI (including SGR colour) and OSC (including OSC-8 hyperlinks).
8
+ * The text without its escape sequences: CSI (including SGR colour), OSC (including OSC-8 hyperlinks), the DCS, SOS,
9
+ * PM and APC strings, and every other escape (`ESC c`, `ESC 7`, `ESC ( B`).
9
10
  *
10
11
  * @internal
11
12
  */
12
13
  const stripAnsi = (input) => input.replace(ANSI, "");
13
14
  /**
15
+ * Whether `input` is nothing but escape sequences, as {@link stripAnsi} reads them; empty text is not. Every sequence
16
+ * starts with ESC, so text that does not is answered without running the strip.
17
+ *
18
+ * @internal
19
+ */
20
+ const isEscapeOnly = (input) => input.charCodeAt(0) === 27 && stripAnsi(input) === "";
21
+ /**
14
22
  * The display width of `input` in terminal columns: graphemes, wide East Asian characters and emoji count two,
15
23
  * combining marks, control characters and ANSI escapes count none.
16
24
  *
@@ -31,4 +39,4 @@ const displayWidth = (input) => {
31
39
  */
32
40
  const graphemes = (input) => Array.from(segmenter.segment(input), (s) => s.segment);
33
41
 
34
- export { displayWidth, graphemes, stripAnsi };
42
+ export { displayWidth, graphemes, isEscapeOnly, stripAnsi };
@@ -3,6 +3,7 @@ import { sanitize } from "../Fmt.js";
3
3
  import { CliLinks } from "../CliLinks.js";
4
4
  import { Glyphs } from "../Glyphs.js";
5
5
  import { CliTheme } from "../CliTheme.js";
6
+ import { identity } from "./displayPath.js";
6
7
  import { Render } from "../Render.js";
7
8
  import { CliFailure } from "../CliFailure.js";
8
9
  import { Config, Context, Effect, MutableRef, Option } from "effect";
@@ -31,7 +32,7 @@ const fallbackTarget = {
31
32
  paint: (_token, text) => text,
32
33
  glyphs: Glyphs.ascii,
33
34
  link: (_target, label) => label,
34
- displayPath: (absolute) => absolute,
35
+ displayPath: identity,
35
36
  neutralizeWorkflowCommands: true
36
37
  },
37
38
  format: "plain",
@@ -0,0 +1,40 @@
1
+ // src/internal/inspect.ts
2
+ /** The value's `_tag` when it is a string; `undefined` otherwise, and when reading it throws. */
3
+ const tagOf = (value) => {
4
+ if (typeof value !== "object" || value === null) return void 0;
5
+ try {
6
+ const tag = value._tag;
7
+ return typeof tag === "string" ? tag : void 0;
8
+ } catch {
9
+ return;
10
+ }
11
+ };
12
+ /**
13
+ * A failure value as report text: `String(value)`, except that a plain object whose `String` says only
14
+ * `[object Object]` is its string `message` when it has one. `String(value)` throws for an object with no prototype or
15
+ * a hostile `toString`: that value is `unprintable`.
16
+ */
17
+ const failureText = (value, unprintable = "[unprintable value]") => {
18
+ try {
19
+ const text = String(value);
20
+ if (text !== "[object Object]") return text;
21
+ const message = value?.message;
22
+ return typeof message === "string" ? message : text;
23
+ } catch {
24
+ return unprintable;
25
+ }
26
+ };
27
+ /**
28
+ * A property of a failure value, or `undefined` when it has none or reading it throws: a report must never fail on a
29
+ * hostile getter. An `Error`'s `stack` counts, since V8 formats it on first read and the formatting reads `message`.
30
+ */
31
+ const readProperty = (value, key) => {
32
+ if (typeof value !== "object" || value === null) return void 0;
33
+ try {
34
+ return value[key];
35
+ } catch {
36
+ return;
37
+ }
38
+ };
39
+
40
+ export { failureText, readProperty, tagOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/cli",
3
- "version": "0.22.1",
3
+ "version": "0.23.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,13 +57,15 @@
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "dependencies": {
60
+ "@effected/commands": "^0.17.0",
61
+ "@effected/engine": "^0.9.0",
60
62
  "@effected/github-commands": "^0.6.2"
61
63
  },
62
64
  "peerDependencies": {
63
- "@effected/config-file": "^0.20.0",
64
- "@effected/env": "^0.5.0",
65
- "@effected/glob": "^0.14.0",
66
- "@effected/walker": "^0.20.0",
65
+ "@effected/config-file": "^0.21.0",
66
+ "@effected/env": "^0.6.0",
67
+ "@effected/glob": "^0.15.0",
68
+ "@effected/walker": "^0.21.0",
67
69
  "@types/react": "^19.3.0",
68
70
  "effect": "^4.0.0",
69
71
  "ink": "^8.0.0",
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.1"
8
+ "packageVersion": "7.59.4"
9
9
  }
10
10
  ]
11
11
  }