effective-progress 0.4.2 → 0.5.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/src/types.ts CHANGED
@@ -1,75 +1,4 @@
1
1
  import { Brand, Context, Effect, Option, Schema } from "effect";
2
- import type { PartialDeep } from "type-fest";
3
- import type { ProgressColumn } from "./renderer";
4
-
5
- export const RendererConfigSchema = Schema.Struct({
6
- disableUserInput: Schema.Boolean,
7
- renderIntervalMillis: Schema.Number,
8
- maxLogLines: Schema.optional(Schema.Number),
9
- nonTtyUpdateStep: Schema.Number,
10
- width: Schema.Union(Schema.Number, Schema.Literal("fullwidth")),
11
- columnGap: Schema.Number,
12
- columns: Schema.Array(Schema.Unknown),
13
- });
14
- export type RendererConfigShape = {
15
- readonly disableUserInput: boolean;
16
- readonly renderIntervalMillis: number;
17
- readonly maxLogLines?: number;
18
- readonly nonTtyUpdateStep: number;
19
- readonly width: number | "fullwidth";
20
- readonly columnGap: number;
21
- readonly columns: ReadonlyArray<ProgressColumn | string>;
22
- };
23
- const decodeRendererConfigSchemaSync = Schema.decodeUnknownSync(RendererConfigSchema);
24
- export const decodeRendererConfigSync = (input: unknown): RendererConfigShape => {
25
- if (typeof input === "object" && input !== null && "determinateTaskLayout" in input) {
26
- throw new Error("determinateTaskLayout has been removed. Use columns instead.");
27
- }
28
- if (typeof input === "object" && input !== null && "maxTaskWidth" in input) {
29
- throw new Error("maxTaskWidth has been removed. Use width on RendererConfig instead.");
30
- }
31
-
32
- return decodeRendererConfigSchemaSync(input) as RendererConfigShape;
33
- };
34
-
35
- export const ProgressBarConfigSchema = Schema.Struct({
36
- spinnerFrames: Schema.NonEmptyArray(Schema.String),
37
- barWidth: Schema.Number,
38
- fillChar: Schema.String,
39
- emptyChar: Schema.String,
40
- leftBracket: Schema.String,
41
- rightBracket: Schema.String,
42
- });
43
- export type ProgressBarConfigShape = typeof ProgressBarConfigSchema.Type;
44
- export const decodeProgressBarConfigSync = Schema.decodeUnknownSync(ProgressBarConfigSchema);
45
-
46
- export const defaultRendererConfig: RendererConfigShape = {
47
- disableUserInput: true,
48
- renderIntervalMillis: 100, // 10 FPS
49
- maxLogLines: 0,
50
- nonTtyUpdateStep: 5,
51
- width: 120,
52
- columnGap: 1,
53
- columns: [],
54
- };
55
-
56
- export const defaultProgressBarConfig: ProgressBarConfigShape = {
57
- spinnerFrames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
58
- barWidth: 40,
59
- fillChar: "━",
60
- emptyChar: "─",
61
- leftBracket: "",
62
- rightBracket: "",
63
- };
64
-
65
- export class RendererConfig extends Context.Tag("stromseng.dev/effective-progress/RendererConfig")<
66
- RendererConfig,
67
- PartialDeep<RendererConfigShape>
68
- >() {}
69
-
70
- export class ProgressBarConfig extends Context.Tag(
71
- "stromseng.dev/effective-progress/ProgressBarConfig",
72
- )<ProgressBarConfig, PartialDeep<ProgressBarConfigShape>>() {}
73
2
 
74
3
  const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
75
4
 
@@ -85,7 +14,6 @@ export interface AddTaskOptions {
85
14
  readonly total?: number;
86
15
  readonly transient?: boolean;
87
16
  readonly parentId?: TaskId;
88
- readonly progressbar?: PartialDeep<ProgressBarConfigShape>;
89
17
  }
90
18
 
91
19
  export interface UpdateTaskOptions {
@@ -123,7 +51,6 @@ export class TaskSnapshot extends Schema.TaggedClass<TaskSnapshot>()("TaskSnapsh
123
51
  status: TaskStatusSchema,
124
52
  transient: Schema.Boolean,
125
53
  units: TaskUnitsSchema,
126
- config: ProgressBarConfigSchema,
127
54
  startedAt: Schema.Number,
128
55
  completedAt: Schema.NullOr(Schema.Number),
129
56
  }) {}
package/src/console.ts DELETED
@@ -1,85 +0,0 @@
1
- import { Console, Effect } from "effect";
2
- import { formatWithOptions } from "node:util";
3
-
4
- export const makeProgressConsole = (
5
- progressLog: (...args: ReadonlyArray<unknown>) => Effect.Effect<void, never, never>,
6
- outerConsole: Console.Console,
7
- ): Console.Console => {
8
- const log = (...args: ReadonlyArray<unknown>) => progressLog(...args);
9
- const unsafeLog = (...args: ReadonlyArray<unknown>) => {
10
- Effect.runFork(progressLog(...args));
11
- };
12
-
13
- const delegate = (effect: Effect.Effect<void, never, never>) => effect;
14
-
15
- return Console.Console.of({
16
- [Console.TypeId]: Console.TypeId,
17
- assert(condition, ...args) {
18
- return condition ? Effect.void : log("Assertion failed:", ...args);
19
- },
20
- clear: Effect.void,
21
- count: (_label) => Effect.void,
22
- countReset: (_label) => Effect.void,
23
- debug: (...args) => log(...args),
24
- dir: (item, options) => log(formatWithOptions(options ?? {}, "%O", item)),
25
- dirxml: (...args) => log(...args),
26
- error: (...args) => log(...args),
27
- group: (...args) => log(...args),
28
- groupEnd: Effect.void,
29
- info: (...args) => log(...args),
30
- log: (...args) => log(...args),
31
- table: (tabularData, properties) => log(tabularData, properties),
32
- time: (_label) => Effect.void,
33
- timeEnd: (_label) => Effect.void,
34
- timeLog: (_label, ...args) => log(...args),
35
- trace: (...args) => delegate(outerConsole.trace(...args)),
36
- warn: (...args) => log(...args),
37
- unsafe: {
38
- assert(condition, ...args) {
39
- if (!condition) unsafeLog("Assertion failed:", ...args);
40
- },
41
- clear() {},
42
- count(_label) {},
43
- countReset(_label) {},
44
- debug(...args) {
45
- unsafeLog(...args);
46
- },
47
- dir(item, options) {
48
- unsafeLog(formatWithOptions(options ?? {}, "%O", item));
49
- },
50
- dirxml(...args) {
51
- unsafeLog(...args);
52
- },
53
- error(...args) {
54
- unsafeLog(...args);
55
- },
56
- group(...args) {
57
- unsafeLog(...args);
58
- },
59
- groupCollapsed(...args) {
60
- unsafeLog(...args);
61
- },
62
- groupEnd() {},
63
- info(...args) {
64
- unsafeLog(...args);
65
- },
66
- log(...args) {
67
- unsafeLog(...args);
68
- },
69
- table(tabularData, properties) {
70
- unsafeLog(tabularData, properties);
71
- },
72
- time(_label) {},
73
- timeEnd(_label) {},
74
- timeLog(_label, ...args) {
75
- unsafeLog(...args);
76
- },
77
- trace(...args) {
78
- outerConsole.unsafe.trace(...args);
79
- },
80
- warn(...args) {
81
- unsafeLog(...args);
82
- },
83
- },
84
- });
85
- };
@@ -1,138 +0,0 @@
1
- import type { CellWrapMode } from "./types";
2
-
3
- const ESC = String.fromCharCode(27);
4
- const ANSI_PATTERN = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
5
- const RESET_ANSI = "\x1b[0m";
6
-
7
- interface AnsiToken {
8
- readonly kind: "ansi" | "char";
9
- readonly value: string;
10
- }
11
-
12
- const textWidth = (text: string): number => Array.from(text).length;
13
-
14
- export const stripAnsi = (text: string): string => text.replace(ANSI_PATTERN, "");
15
-
16
- export const visibleWidth = (text: string): number => textWidth(stripAnsi(text));
17
-
18
- const tokenizeAnsi = (text: string): ReadonlyArray<AnsiToken> => {
19
- const tokens: Array<AnsiToken> = [];
20
- let i = 0;
21
-
22
- while (i < text.length) {
23
- if (text[i] === "\x1b" && text[i + 1] === "[") {
24
- let j = i + 2;
25
- while (j < text.length) {
26
- const code = text.charCodeAt(j);
27
- j += 1;
28
- if (code >= 0x40 && code <= 0x7e) {
29
- break;
30
- }
31
- }
32
- tokens.push({ kind: "ansi", value: text.slice(i, j) });
33
- i = j;
34
- continue;
35
- }
36
-
37
- const codePoint = text.codePointAt(i);
38
- if (codePoint === undefined) {
39
- break;
40
- }
41
-
42
- const char = String.fromCodePoint(codePoint);
43
- tokens.push({ kind: "char", value: char });
44
- i += char.length;
45
- }
46
-
47
- return tokens;
48
- };
49
-
50
- const fitPlainText = (text: string, width: number, wrapMode: CellWrapMode): string => {
51
- const target = Math.max(0, Math.floor(width));
52
- if (target <= 0) {
53
- return "";
54
- }
55
-
56
- const chars = Array.from(text);
57
- let result = text;
58
-
59
- if (chars.length > target) {
60
- if (wrapMode === "ellipsis") {
61
- result = target === 1 ? "…" : `${chars.slice(0, target - 1).join("")}…`;
62
- } else {
63
- result = chars.slice(0, target).join("");
64
- }
65
- }
66
-
67
- const currentWidth = textWidth(result);
68
- if (currentWidth < target) {
69
- result += " ".repeat(target - currentWidth);
70
- }
71
-
72
- return result;
73
- };
74
-
75
- const fitAnsiText = (text: string, width: number, wrapMode: CellWrapMode): string => {
76
- const target = Math.max(0, Math.floor(width));
77
- if (target <= 0) {
78
- return "";
79
- }
80
-
81
- const tokens = tokenizeAnsi(text);
82
- const totalVisible = tokens.reduce((sum, token) => sum + (token.kind === "char" ? 1 : 0), 0);
83
-
84
- if (totalVisible <= target) {
85
- const padBy = target - totalVisible;
86
- return padBy > 0 ? `${text}${" ".repeat(padBy)}` : text;
87
- }
88
-
89
- const keepVisible = wrapMode === "ellipsis" ? Math.max(0, target - 1) : target;
90
- let visible = 0;
91
- let sawAnsi = false;
92
- let output = "";
93
-
94
- for (const token of tokens) {
95
- if (token.kind === "ansi") {
96
- sawAnsi = true;
97
- if (keepVisible > 0 && visible <= keepVisible) {
98
- output += token.value;
99
- }
100
- continue;
101
- }
102
-
103
- if (visible >= keepVisible) {
104
- break;
105
- }
106
-
107
- output += token.value;
108
- visible += 1;
109
- }
110
-
111
- if (wrapMode === "ellipsis" && target > 0) {
112
- output += "…";
113
- }
114
-
115
- if (sawAnsi && !output.endsWith(RESET_ANSI)) {
116
- output += RESET_ANSI;
117
- }
118
-
119
- const padBy = target - visibleWidth(output);
120
- if (padBy > 0) {
121
- output += " ".repeat(padBy);
122
- }
123
-
124
- return output;
125
- };
126
-
127
- export const fitRenderedText = (
128
- text: string,
129
- width: number,
130
- wrapMode: CellWrapMode,
131
- isTTY: boolean,
132
- ): string => {
133
- const raw = isTTY ? text : stripAnsi(text);
134
- if (!isTTY || raw.indexOf("\x1b[") === -1) {
135
- return fitPlainText(raw, width, wrapMode);
136
- }
137
- return fitAnsiText(raw, width, wrapMode);
138
- };
@@ -1,67 +0,0 @@
1
- import type { TaskSnapshot } from "../types";
2
-
3
- /**
4
- * Defines how a column claims width during the shrink/fit stage.
5
- */
6
- export type ColumnTrack =
7
- | {
8
- readonly _tag: "Auto";
9
- }
10
- | {
11
- readonly _tag: "Fixed";
12
- readonly width: number;
13
- }
14
- | {
15
- readonly _tag: "Fraction";
16
- readonly weight: number;
17
- };
18
-
19
- export const Track = {
20
- auto: (): ColumnTrack => ({ _tag: "Auto" }),
21
- fixed: (width: number): ColumnTrack => ({
22
- _tag: "Fixed",
23
- width: Math.max(0, Math.floor(width)),
24
- }),
25
- fr: (weight = 1): ColumnTrack => ({
26
- _tag: "Fraction",
27
- weight: Math.max(0.001, weight),
28
- }),
29
- } as const;
30
-
31
- /**
32
- * Tree relationship metadata for rendering connectors.
33
- */
34
- export interface TaskTreeInfo {
35
- readonly depth: number;
36
- readonly hasNextSibling: boolean;
37
- readonly hasChildren: boolean;
38
- readonly ancestorHasNextSibling: ReadonlyArray<boolean>;
39
- }
40
-
41
- export type CellWrapMode = "truncate" | "ellipsis";
42
-
43
- export interface ProgressColumnContext {
44
- readonly task: TaskSnapshot;
45
- readonly depth: number;
46
- readonly tree: TaskTreeInfo;
47
- readonly now: number;
48
- readonly tick: number;
49
- readonly isTTY: boolean;
50
- }
51
-
52
- export interface ProgressColumnVariant {
53
- readonly measure?: (context: ProgressColumnContext) => number;
54
- readonly render: (context: ProgressColumnContext, width: number) => string;
55
- }
56
-
57
- export interface ProgressColumn {
58
- readonly id: string;
59
- readonly track?: ColumnTrack;
60
- readonly minWidth?: number;
61
- readonly maxWidth?: number;
62
- readonly collapsePriority?: number;
63
- readonly wrapMode?: CellWrapMode;
64
- readonly measure?: (context: ProgressColumnContext) => number;
65
- readonly render: (context: ProgressColumnContext, width: number) => string;
66
- readonly variants?: (context: ProgressColumnContext) => ReadonlyArray<ProgressColumnVariant>;
67
- }