@cvr/stacked 0.3.0 → 0.4.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/README.md +74 -23
- package/bin/stacked +0 -0
- package/package.json +11 -10
- package/scripts/build.ts +8 -1
- package/skills/stacked/SKILL.md +210 -43
- package/src/commands/adopt.ts +71 -8
- package/src/commands/amend.ts +114 -0
- package/src/commands/bottom.ts +26 -8
- package/src/commands/checkout.ts +25 -6
- package/src/commands/clean.ts +110 -27
- package/src/commands/create.ts +74 -12
- package/src/commands/delete.ts +92 -24
- package/src/commands/detect.ts +95 -54
- package/src/commands/doctor.ts +124 -0
- package/src/commands/down.ts +62 -0
- package/src/commands/helpers/detect.ts +22 -0
- package/src/commands/helpers/validate.ts +61 -0
- package/src/commands/index.ts +27 -1
- package/src/commands/init.ts +42 -0
- package/src/commands/list.ts +61 -22
- package/src/commands/log.ts +32 -6
- package/src/commands/rename.ts +49 -0
- package/src/commands/reorder.ts +103 -0
- package/src/commands/split.ts +108 -0
- package/src/commands/stacks.ts +32 -7
- package/src/commands/status.ts +57 -0
- package/src/commands/submit.ts +272 -16
- package/src/commands/sync.ts +131 -20
- package/src/commands/top.ts +24 -8
- package/src/commands/trunk.ts +37 -6
- package/src/commands/up.ts +55 -0
- package/src/errors/index.ts +37 -1
- package/src/global.d.ts +2 -0
- package/src/main.ts +78 -3
- package/src/services/Git.ts +96 -31
- package/src/services/GitHub.ts +56 -18
- package/src/services/Stack.ts +65 -58
- package/src/ui.ts +247 -0
package/src/services/Stack.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Layer, Ref, Schema, ServiceMap } from "effect";
|
|
2
|
+
import { rename } from "node:fs/promises";
|
|
2
3
|
import type { GitError } from "../errors/index.js";
|
|
3
4
|
import { StackError } from "../errors/index.js";
|
|
4
5
|
import { GitService } from "./Git.js";
|
|
@@ -34,10 +35,11 @@ export class StackService extends ServiceMap.Service<
|
|
|
34
35
|
) => Effect.Effect<void, StackError>;
|
|
35
36
|
readonly removeBranch: (stackName: string, branch: string) => Effect.Effect<void, StackError>;
|
|
36
37
|
readonly createStack: (name: string, branches: string[]) => Effect.Effect<void, StackError>;
|
|
38
|
+
readonly findBranchStack: (
|
|
39
|
+
branch: string,
|
|
40
|
+
) => Effect.Effect<{ name: string; stack: Stack } | null, StackError>;
|
|
37
41
|
readonly getTrunk: () => Effect.Effect<string, StackError>;
|
|
38
42
|
readonly setTrunk: (name: string) => Effect.Effect<void, StackError>;
|
|
39
|
-
readonly parentOf: (branch: string) => Effect.Effect<string, StackError>;
|
|
40
|
-
readonly childrenOf: (branch: string) => Effect.Effect<string[], StackError>;
|
|
41
43
|
}
|
|
42
44
|
>()("@cvr/stacked/services/Stack/StackService") {
|
|
43
45
|
static layer: Layer.Layer<StackService, never, GitService> = Layer.effect(
|
|
@@ -47,7 +49,7 @@ export class StackService extends ServiceMap.Service<
|
|
|
47
49
|
|
|
48
50
|
const stackFilePath = Effect.fn("stackFilePath")(function* () {
|
|
49
51
|
const gitDir = yield* git
|
|
50
|
-
.revParse("--git-dir")
|
|
52
|
+
.revParse("--absolute-git-dir")
|
|
51
53
|
.pipe(
|
|
52
54
|
Effect.mapError(
|
|
53
55
|
(e) => new StackError({ message: `Not a git repository: ${e.message}` }),
|
|
@@ -60,25 +62,64 @@ export class StackService extends ServiceMap.Service<
|
|
|
60
62
|
const decodeStackFile = Schema.decodeUnknownEffect(StackFileJson);
|
|
61
63
|
const encodeStackFile = Schema.encodeEffect(StackFileJson);
|
|
62
64
|
|
|
65
|
+
const detectTrunk = Effect.fn("StackService.detectTrunk")(function* () {
|
|
66
|
+
// Check common default branch names
|
|
67
|
+
for (const candidate of ["main", "master", "develop"]) {
|
|
68
|
+
const exists = yield* git
|
|
69
|
+
.branchExists(candidate)
|
|
70
|
+
.pipe(Effect.catchTag("GitError", () => Effect.succeed(false)));
|
|
71
|
+
if (exists) return candidate;
|
|
72
|
+
}
|
|
73
|
+
return "main";
|
|
74
|
+
});
|
|
75
|
+
|
|
63
76
|
const load = Effect.fn("StackService.load")(function* () {
|
|
64
77
|
const path = yield* stackFilePath();
|
|
65
78
|
const file = Bun.file(path);
|
|
66
|
-
const exists = yield* Effect.
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
const exists = yield* Effect.tryPromise({
|
|
80
|
+
try: () => file.exists(),
|
|
81
|
+
catch: () => new StackError({ message: `Failed to check if ${path} exists` }),
|
|
82
|
+
});
|
|
83
|
+
if (!exists) {
|
|
84
|
+
const trunk = yield* detectTrunk();
|
|
85
|
+
return { ...emptyStackFile, trunk } satisfies StackFile;
|
|
86
|
+
}
|
|
87
|
+
const text = yield* Effect.tryPromise({
|
|
88
|
+
try: () => file.text(),
|
|
89
|
+
catch: () => new StackError({ message: `Failed to read ${path}` }),
|
|
90
|
+
});
|
|
69
91
|
return yield* decodeStackFile(text).pipe(
|
|
70
|
-
Effect.
|
|
92
|
+
Effect.catchTag("SchemaError", (e) =>
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const backupPath = `${path}.backup`;
|
|
95
|
+
yield* Effect.tryPromise({
|
|
96
|
+
try: () => Bun.write(backupPath, text),
|
|
97
|
+
catch: () => new StackError({ message: `Failed to write backup to ${backupPath}` }),
|
|
98
|
+
});
|
|
99
|
+
yield* Effect.logWarning(
|
|
100
|
+
`Corrupted stack file, resetting: ${e.message}\nBackup saved to ${backupPath}`,
|
|
101
|
+
);
|
|
102
|
+
const trunk = yield* detectTrunk();
|
|
103
|
+
return { ...emptyStackFile, trunk } satisfies StackFile;
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
71
106
|
);
|
|
72
107
|
});
|
|
73
108
|
|
|
74
109
|
const save = Effect.fn("StackService.save")(function* (data: StackFile) {
|
|
75
110
|
const path = yield* stackFilePath();
|
|
111
|
+
const tmpPath = `${path}.tmp`;
|
|
76
112
|
const text = yield* encodeStackFile(data).pipe(
|
|
77
113
|
Effect.mapError(() => new StackError({ message: `Failed to encode stack data` })),
|
|
78
114
|
);
|
|
79
|
-
yield* Effect.
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
yield* Effect.tryPromise({
|
|
116
|
+
try: () => Bun.write(tmpPath, text + "\n"),
|
|
117
|
+
catch: () => new StackError({ message: `Failed to write ${tmpPath}` }),
|
|
118
|
+
});
|
|
119
|
+
yield* Effect.tryPromise({
|
|
120
|
+
try: () => rename(tmpPath, path),
|
|
121
|
+
catch: () => new StackError({ message: `Failed to rename ${tmpPath} to ${path}` }),
|
|
122
|
+
});
|
|
82
123
|
});
|
|
83
124
|
|
|
84
125
|
const findBranchStack = (data: StackFile, branch: string) => {
|
|
@@ -94,6 +135,9 @@ export class StackService extends ServiceMap.Service<
|
|
|
94
135
|
load: () => load(),
|
|
95
136
|
save: (data) => save(data),
|
|
96
137
|
|
|
138
|
+
findBranchStack: (branch: string) =>
|
|
139
|
+
load().pipe(Effect.map((data) => findBranchStack(data, branch))),
|
|
140
|
+
|
|
97
141
|
currentStack: Effect.fn("StackService.currentStack")(function* () {
|
|
98
142
|
const branch = yield* git.currentBranch();
|
|
99
143
|
const data = yield* load();
|
|
@@ -106,6 +150,12 @@ export class StackService extends ServiceMap.Service<
|
|
|
106
150
|
after?: string,
|
|
107
151
|
) {
|
|
108
152
|
const data = yield* load();
|
|
153
|
+
const existing = findBranchStack(data, branch);
|
|
154
|
+
if (existing !== null) {
|
|
155
|
+
return yield* new StackError({
|
|
156
|
+
message: `Branch "${branch}" is already in stack "${existing.name}"`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
109
159
|
const stack = data.stacks[stackName];
|
|
110
160
|
if (stack === undefined) {
|
|
111
161
|
return yield* new StackError({ message: `Stack "${stackName}" not found` });
|
|
@@ -172,34 +222,11 @@ export class StackService extends ServiceMap.Service<
|
|
|
172
222
|
const data = yield* load();
|
|
173
223
|
yield* save({ ...data, trunk: name });
|
|
174
224
|
}),
|
|
175
|
-
|
|
176
|
-
parentOf: Effect.fn("StackService.parentOf")(function* (branch: string) {
|
|
177
|
-
const data = yield* load();
|
|
178
|
-
for (const stack of Object.values(data.stacks)) {
|
|
179
|
-
const idx = stack.branches.indexOf(branch);
|
|
180
|
-
if (idx === 0) return data.trunk;
|
|
181
|
-
if (idx > 0) return stack.branches[idx - 1] ?? data.trunk;
|
|
182
|
-
}
|
|
183
|
-
return yield* new StackError({ message: `Branch "${branch}" not found in any stack` });
|
|
184
|
-
}),
|
|
185
|
-
|
|
186
|
-
childrenOf: Effect.fn("StackService.childrenOf")(function* (branch: string) {
|
|
187
|
-
const data = yield* load();
|
|
188
|
-
const children: string[] = [];
|
|
189
|
-
for (const stack of Object.values(data.stacks)) {
|
|
190
|
-
const idx = stack.branches.indexOf(branch);
|
|
191
|
-
const child = stack.branches[idx + 1];
|
|
192
|
-
if (idx !== -1 && child !== undefined) {
|
|
193
|
-
children.push(child);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return children;
|
|
197
|
-
}),
|
|
198
225
|
};
|
|
199
226
|
}),
|
|
200
227
|
);
|
|
201
228
|
|
|
202
|
-
static layerTest = (data?: StackFile) => {
|
|
229
|
+
static layerTest = (data?: StackFile, options?: { currentBranch?: string }) => {
|
|
203
230
|
const initial = data ?? emptyStackFile;
|
|
204
231
|
return Layer.effect(
|
|
205
232
|
StackService,
|
|
@@ -219,9 +246,12 @@ export class StackService extends ServiceMap.Service<
|
|
|
219
246
|
load: () => Ref.get(ref),
|
|
220
247
|
save: (d) => Ref.set(ref, d),
|
|
221
248
|
|
|
249
|
+
findBranchStack: (branch: string) =>
|
|
250
|
+
Ref.get(ref).pipe(Effect.map((d) => findBranchStack(d, branch))),
|
|
251
|
+
|
|
222
252
|
currentStack: Effect.fn("test.currentStack")(function* () {
|
|
223
253
|
const d = yield* Ref.get(ref);
|
|
224
|
-
return findBranchStack(d, "test-branch");
|
|
254
|
+
return findBranchStack(d, options?.currentBranch ?? "test-branch");
|
|
225
255
|
}),
|
|
226
256
|
|
|
227
257
|
addBranch: Effect.fn("test.addBranch")(function* (
|
|
@@ -269,29 +299,6 @@ export class StackService extends ServiceMap.Service<
|
|
|
269
299
|
|
|
270
300
|
getTrunk: () => Ref.get(ref).pipe(Effect.map((d) => d.trunk)),
|
|
271
301
|
setTrunk: (name: string) => Ref.update(ref, (d) => ({ ...d, trunk: name })),
|
|
272
|
-
|
|
273
|
-
parentOf: Effect.fn("test.parentOf")(function* (branch: string) {
|
|
274
|
-
const d = yield* Ref.get(ref);
|
|
275
|
-
for (const stack of Object.values(d.stacks)) {
|
|
276
|
-
const idx = stack.branches.indexOf(branch);
|
|
277
|
-
if (idx === 0) return d.trunk;
|
|
278
|
-
if (idx > 0) return stack.branches[idx - 1] ?? d.trunk;
|
|
279
|
-
}
|
|
280
|
-
return yield* new StackError({ message: `Branch "${branch}" not found in any stack` });
|
|
281
|
-
}),
|
|
282
|
-
|
|
283
|
-
childrenOf: Effect.fn("test.childrenOf")(function* (branch: string) {
|
|
284
|
-
const d = yield* Ref.get(ref);
|
|
285
|
-
const children: string[] = [];
|
|
286
|
-
for (const stack of Object.values(d.stacks)) {
|
|
287
|
-
const idx = stack.branches.indexOf(branch);
|
|
288
|
-
const child = stack.branches[idx + 1];
|
|
289
|
-
if (idx !== -1 && child !== undefined) {
|
|
290
|
-
children.push(child);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return children;
|
|
294
|
-
}),
|
|
295
302
|
};
|
|
296
303
|
}),
|
|
297
304
|
);
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { Config, Effect, ServiceMap } from "effect";
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// TTY & Color Detection
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
const stderrIsTTY = process.stderr.isTTY === true;
|
|
10
|
+
const stdoutIsTTY = process.stdout.isTTY === true;
|
|
11
|
+
|
|
12
|
+
// Lazy color instances — deferred so --no-color flag can set env before first use
|
|
13
|
+
let _stderrColors: ReturnType<typeof pc.createColors> | null = null;
|
|
14
|
+
let _stdoutColors: ReturnType<typeof pc.createColors> | null = null;
|
|
15
|
+
|
|
16
|
+
const colorRuntimeConfig = Config.all({
|
|
17
|
+
noColor: Config.string("NO_COLOR").pipe(
|
|
18
|
+
Config.map(() => true),
|
|
19
|
+
Config.orElse(() => Config.succeed(false)),
|
|
20
|
+
),
|
|
21
|
+
forceColor: Config.string("FORCE_COLOR").pipe(
|
|
22
|
+
Config.map(() => true),
|
|
23
|
+
Config.orElse(() => Config.succeed(false)),
|
|
24
|
+
),
|
|
25
|
+
term: Config.string("TERM").pipe(Config.withDefault("")),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const isColorEnabled = Effect.fn("ui.isColorEnabled")(function* (isTTY: boolean) {
|
|
29
|
+
const runtime = yield* colorRuntimeConfig;
|
|
30
|
+
if (runtime.noColor) return false;
|
|
31
|
+
if (runtime.forceColor) return true;
|
|
32
|
+
if (runtime.term === "dumb") return false;
|
|
33
|
+
return isTTY;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const getColors = Effect.fn("ui.getColors")(function* () {
|
|
37
|
+
if (_stderrColors !== null) return _stderrColors;
|
|
38
|
+
const enabled = yield* isColorEnabled(stderrIsTTY).pipe(
|
|
39
|
+
Effect.catchTag("ConfigError", () => Effect.succeed(false)),
|
|
40
|
+
);
|
|
41
|
+
_stderrColors = enabled ? pc : pc.createColors(false);
|
|
42
|
+
return _stderrColors;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const getStdoutColors = Effect.fn("ui.getStdoutColors")(function* () {
|
|
46
|
+
if (_stdoutColors !== null) return _stdoutColors;
|
|
47
|
+
const enabled = yield* isColorEnabled(stdoutIsTTY).pipe(
|
|
48
|
+
Effect.catchTag("ConfigError", () => Effect.succeed(false)),
|
|
49
|
+
);
|
|
50
|
+
_stdoutColors = enabled ? pc : pc.createColors(false);
|
|
51
|
+
return _stdoutColors;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Output Config (verbose/quiet, set by global flags)
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
export interface OutputConfig {
|
|
59
|
+
readonly verbose: boolean;
|
|
60
|
+
readonly quiet: boolean;
|
|
61
|
+
readonly yes: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const OutputConfig = ServiceMap.Reference("@cvr/stacked/OutputConfig", {
|
|
65
|
+
defaultValue: (): OutputConfig => ({ verbose: false, quiet: false, yes: false }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Interactive Prompts
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
const stdinIsTTY = process.stdin.isTTY === true;
|
|
73
|
+
|
|
74
|
+
export const confirm = Effect.fn("ui.confirm")(function* (message: string) {
|
|
75
|
+
const config = yield* OutputConfig;
|
|
76
|
+
if (config.yes || !stdinIsTTY) return true;
|
|
77
|
+
|
|
78
|
+
process.stderr.write(`${message} [y/N] `);
|
|
79
|
+
const answer = yield* Effect.tryPromise({
|
|
80
|
+
try: () => {
|
|
81
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
82
|
+
return new Promise<string>((resolve) => {
|
|
83
|
+
rl.question("", (ans) => {
|
|
84
|
+
rl.close();
|
|
85
|
+
resolve(ans);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
catch: () => "n" as const,
|
|
90
|
+
});
|
|
91
|
+
return answer.trim().toLowerCase() === "y";
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Styled Output (all write to stderr)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
const write = (msg: string) =>
|
|
99
|
+
Effect.sync(() => {
|
|
100
|
+
process.stderr.write(msg + "\n");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export const success = Effect.fn("ui.success")(function* (msg: string) {
|
|
104
|
+
const config = yield* OutputConfig;
|
|
105
|
+
if (config.quiet) return;
|
|
106
|
+
const colors = yield* getColors();
|
|
107
|
+
yield* write(colors.green(`✓ ${msg}`));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const warn = Effect.fn("ui.warn")(function* (msg: string) {
|
|
111
|
+
const config = yield* OutputConfig;
|
|
112
|
+
if (config.quiet) return;
|
|
113
|
+
const colors = yield* getColors();
|
|
114
|
+
yield* write(colors.yellow(`⚠ ${msg}`));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const info = Effect.fn("ui.info")(function* (msg: string) {
|
|
118
|
+
const config = yield* OutputConfig;
|
|
119
|
+
if (config.quiet) return;
|
|
120
|
+
const colors = yield* getColors();
|
|
121
|
+
yield* write(colors.cyan(msg));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const error = Effect.fn("ui.error")(function* (msg: string) {
|
|
125
|
+
const colors = yield* getColors();
|
|
126
|
+
yield* write(colors.red(msg));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const verbose = Effect.fn("ui.verbose")(function* (msg: string) {
|
|
130
|
+
const config = yield* OutputConfig;
|
|
131
|
+
if (!config.verbose) return;
|
|
132
|
+
const colors = yield* getColors();
|
|
133
|
+
yield* write(colors.dim(msg));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Spinner
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
141
|
+
|
|
142
|
+
export const withSpinner = <A, E, R>(
|
|
143
|
+
message: string,
|
|
144
|
+
effect: Effect.Effect<A, E, R>,
|
|
145
|
+
): Effect.Effect<A, E, R> => {
|
|
146
|
+
if (!stderrIsTTY) {
|
|
147
|
+
return write(message).pipe(Effect.andThen(effect));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Effect.gen(function* () {
|
|
151
|
+
const c = yield* getColors();
|
|
152
|
+
let frame = 0;
|
|
153
|
+
const interval = setInterval(() => {
|
|
154
|
+
const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
155
|
+
process.stderr.write(`\r${c.cyan(spinner ?? "⠋")} ${message}`);
|
|
156
|
+
frame++;
|
|
157
|
+
}, 80);
|
|
158
|
+
|
|
159
|
+
const cleanup = (icon: string) =>
|
|
160
|
+
Effect.sync(() => {
|
|
161
|
+
clearInterval(interval);
|
|
162
|
+
process.stderr.write(`\r${icon} ${message}\n`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const result = yield* effect.pipe(
|
|
166
|
+
Effect.tap(() => cleanup(c.green("✓"))),
|
|
167
|
+
Effect.tapError(() => cleanup(c.red("✗"))),
|
|
168
|
+
Effect.onInterrupt(() => cleanup(c.yellow("⚠"))),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Color Helpers — stderr (for tree views, status badges, etc.)
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
export const dim = Effect.fn("ui.dim")(function* (s: string) {
|
|
180
|
+
const colors = yield* getColors();
|
|
181
|
+
return colors.dim(s);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const bold = Effect.fn("ui.bold")(function* (s: string) {
|
|
185
|
+
const colors = yield* getColors();
|
|
186
|
+
return colors.bold(s);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export const green = Effect.fn("ui.green")(function* (s: string) {
|
|
190
|
+
const colors = yield* getColors();
|
|
191
|
+
return colors.green(s);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export const yellow = Effect.fn("ui.yellow")(function* (s: string) {
|
|
195
|
+
const colors = yield* getColors();
|
|
196
|
+
return colors.yellow(s);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export const cyan = Effect.fn("ui.cyan")(function* (s: string) {
|
|
200
|
+
const colors = yield* getColors();
|
|
201
|
+
return colors.cyan(s);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
export const red = Effect.fn("ui.red")(function* (s: string) {
|
|
205
|
+
const colors = yield* getColors();
|
|
206
|
+
return colors.red(s);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export const magenta = Effect.fn("ui.magenta")(function* (s: string) {
|
|
210
|
+
const colors = yield* getColors();
|
|
211
|
+
return colors.magenta(s);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Color Helpers — stdout (for Console.log output that may be piped)
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
export const stdout = {
|
|
219
|
+
dim: Effect.fn("ui.stdout.dim")(function* (s: string) {
|
|
220
|
+
const colors = yield* getStdoutColors();
|
|
221
|
+
return colors.dim(s);
|
|
222
|
+
}),
|
|
223
|
+
bold: Effect.fn("ui.stdout.bold")(function* (s: string) {
|
|
224
|
+
const colors = yield* getStdoutColors();
|
|
225
|
+
return colors.bold(s);
|
|
226
|
+
}),
|
|
227
|
+
green: Effect.fn("ui.stdout.green")(function* (s: string) {
|
|
228
|
+
const colors = yield* getStdoutColors();
|
|
229
|
+
return colors.green(s);
|
|
230
|
+
}),
|
|
231
|
+
yellow: Effect.fn("ui.stdout.yellow")(function* (s: string) {
|
|
232
|
+
const colors = yield* getStdoutColors();
|
|
233
|
+
return colors.yellow(s);
|
|
234
|
+
}),
|
|
235
|
+
cyan: Effect.fn("ui.stdout.cyan")(function* (s: string) {
|
|
236
|
+
const colors = yield* getStdoutColors();
|
|
237
|
+
return colors.cyan(s);
|
|
238
|
+
}),
|
|
239
|
+
red: Effect.fn("ui.stdout.red")(function* (s: string) {
|
|
240
|
+
const colors = yield* getStdoutColors();
|
|
241
|
+
return colors.red(s);
|
|
242
|
+
}),
|
|
243
|
+
magenta: Effect.fn("ui.stdout.magenta")(function* (s: string) {
|
|
244
|
+
const colors = yield* getStdoutColors();
|
|
245
|
+
return colors.magenta(s);
|
|
246
|
+
}),
|
|
247
|
+
};
|