cli-kiss 0.2.5 → 0.2.6
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/dist/index.d.ts +15 -9
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +2 -2
- package/docs/.vitepress/theme/style.css +6 -2
- package/docs/guide/{05_types.md → 05_input_types.md} +19 -17
- package/docs/guide/{06_run.md → 06_run_as_cli.md} +10 -15
- package/docs/index.md +3 -2
- package/package.json +1 -1
- package/src/lib/Run.ts +16 -17
- package/src/lib/Type.ts +39 -41
- package/src/lib/Typo.ts +30 -10
- package/src/lib/Usage.ts +3 -3
- package/tests/unit.runner.colors.ts +123 -121
|
@@ -28,8 +28,8 @@ export default defineConfig({
|
|
|
28
28
|
{ text: "Commands", link: "/guide/02_commands" },
|
|
29
29
|
{ text: "Options", link: "/guide/03_options" },
|
|
30
30
|
{ text: "Positionals", link: "/guide/04_positionals" },
|
|
31
|
-
{ text: "Types", link: "/guide/
|
|
32
|
-
{ text: "Running your CLI", link: "/guide/
|
|
31
|
+
{ text: "Input Types", link: "/guide/05_input_types" },
|
|
32
|
+
{ text: "Running your CLI", link: "/guide/06_run_as_cli" },
|
|
33
33
|
],
|
|
34
34
|
},
|
|
35
35
|
],
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
3
|
-
--vp-home-hero-
|
|
2
|
+
/*
|
|
3
|
+
--vp-home-hero-name-color: transparent;
|
|
4
|
+
--vp-home-hero-name-background: linear-gradient(-50deg, #ff003caa 30%, #459900aa 70%);
|
|
5
|
+
*/
|
|
6
|
+
--vp-home-hero-image-background-image: linear-gradient( -50deg, #ff003c88 25%, #008732aa 60% );
|
|
7
|
+
--vp-home-hero-image-filter: blur(60px);
|
|
4
8
|
}
|
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
# Types
|
|
1
|
+
# Input Types
|
|
2
2
|
|
|
3
3
|
A `Type<Value>` converts a raw CLI string into a typed value:
|
|
4
4
|
|
|
5
5
|
- Contains a `content` label about the type of data being decoded
|
|
6
6
|
- Paired with a `decoder` function that throws if the value is invalid.
|
|
7
7
|
|
|
8
|
+
A `Type<Value>` can then be used as a value for an `Option` or `Positional`
|
|
9
|
+
|
|
8
10
|
## Built-in types
|
|
9
11
|
|
|
10
|
-
All type factories accept an optional `name` parameter that overrides the label
|
|
12
|
+
All type factories accept an optional `name` parameter that overrides the label
|
|
13
|
+
shown in help/errors.
|
|
11
14
|
|
|
12
|
-
| Type factory | Content type | Accepts
|
|
13
|
-
| -------------- | ------------ |
|
|
14
|
-
| `type` | `string` | Any string
|
|
15
|
-
| `typeBoolean` | `boolean` | `true/yes/on/
|
|
16
|
-
| `typeNumber` | `number` | Integers, floats, scientific notation
|
|
17
|
-
| `typeInteger` | `bigint` | Integer strings only
|
|
18
|
-
| `typeDatetime` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended)
|
|
19
|
-
| `typeUrl` | `URL` | Absolute URLs
|
|
20
|
-
| `typePath` | `string` | Non-empty path strings; optional sync existence check
|
|
15
|
+
| Type factory | Content type | Accepts |
|
|
16
|
+
| -------------- | ------------ | ------------------------------------------------------------------- |
|
|
17
|
+
| `type` | `string` | Any string |
|
|
18
|
+
| `typeBoolean` | `boolean` | `true/yes/on/y` → true, `false/no/off/n` → false (case-insensitive) |
|
|
19
|
+
| `typeNumber` | `number` | Integers, floats, scientific notation |
|
|
20
|
+
| `typeInteger` | `bigint` | Integer strings only |
|
|
21
|
+
| `typeDatetime` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended) |
|
|
22
|
+
| `typeUrl` | `URL` | Absolute URLs |
|
|
23
|
+
| `typePath` | `string` | Non-empty path strings; optional sync existence check |
|
|
21
24
|
|
|
22
25
|
```ts
|
|
23
26
|
type("greeting").decoder("hello"); // → "hello"
|
|
@@ -32,8 +35,8 @@ typePath().decoder("/usr/bin"); // → "/usr/bin"
|
|
|
32
35
|
`typePath` also accepts a second argument for existence checks:
|
|
33
36
|
|
|
34
37
|
```ts
|
|
35
|
-
typePath("config", { checkSyncExistAs: "file" });
|
|
36
|
-
typePath("dir",
|
|
38
|
+
typePath("config", { checkSyncExistAs: "file" }); // throws if not a file
|
|
39
|
+
typePath("dir", { checkSyncExistAs: "directory" }); // throws if not a directory
|
|
37
40
|
```
|
|
38
41
|
|
|
39
42
|
## `typeChoice` — string enum
|
|
@@ -101,7 +104,7 @@ const typePort = typeConverted("port", typeNumber(), (n) => {
|
|
|
101
104
|
return n;
|
|
102
105
|
});
|
|
103
106
|
// "--port 8080" → 8080
|
|
104
|
-
// "--port 99999" →
|
|
107
|
+
// "--port 99999" → throws
|
|
105
108
|
```
|
|
106
109
|
|
|
107
110
|
## `typeRenamed` — rename a type
|
|
@@ -109,8 +112,7 @@ const typePort = typeConverted("port", typeNumber(), (n) => {
|
|
|
109
112
|
Wraps a type with a different label for clearer errors:
|
|
110
113
|
|
|
111
114
|
```ts
|
|
112
|
-
const
|
|
113
|
-
// errors show "user-id" instead of "integer"
|
|
115
|
+
const typeUserId = typeRenamed(typeInteger("u64"), "user-id");
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
## Custom types
|
|
@@ -124,7 +126,7 @@ const typeHexColor: Type<string> = {
|
|
|
124
126
|
if (/^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
125
127
|
return value;
|
|
126
128
|
}
|
|
127
|
-
throw new Error(`Not a valid color: "${value}"`);
|
|
129
|
+
throw new Error(`Not a valid hex color: "${value}"`);
|
|
128
130
|
},
|
|
129
131
|
};
|
|
130
132
|
// "#ff0000" → "#ff0000"
|
|
@@ -18,14 +18,14 @@ await runAndExit(cliName, cliArgs, context, command, options?);
|
|
|
18
18
|
|
|
19
19
|
### Options
|
|
20
20
|
|
|
21
|
-
| Option | Type
|
|
22
|
-
| -------------- |
|
|
23
|
-
| `buildVersion` | `string?`
|
|
24
|
-
| `usageOnHelp` | `boolean?`
|
|
25
|
-
| `usageOnError` | `boolean?`
|
|
26
|
-
| `colorSetup` | `
|
|
27
|
-
| `onError` | `(error: unknown) => void`
|
|
28
|
-
| `onExit` | `(code: number) => never`
|
|
21
|
+
| Option | Type | Default | Description |
|
|
22
|
+
| -------------- | ----------------------------------- | -------------- | ------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `buildVersion` | `string?` | — | Enables `--version` flag; prints `<cliName> <buildVersion>` |
|
|
24
|
+
| `usageOnHelp` | `boolean?` | `true` | Enables `--help` flag |
|
|
25
|
+
| `usageOnError` | `boolean?` | `true` | Prints usage to stderr when parsing fails |
|
|
26
|
+
| `colorSetup` | `flag` / `env` / `always` / `never` | `"flag"` | Color mode: `"flag"` adds a `--color` option; `"env"` reads env vars; others force the mode |
|
|
27
|
+
| `onError` | `(error: unknown) => void` | — | Custom handler for parse and execution errors |
|
|
28
|
+
| `onExit` | `(code: number) => never` | `process.exit` | Override for testing |
|
|
29
29
|
|
|
30
30
|
### Exit codes
|
|
31
31
|
|
|
@@ -118,17 +118,12 @@ Colors are auto-detected by default (`colorSetup: "flag"` adds a `--color`
|
|
|
118
118
|
option). Override:
|
|
119
119
|
|
|
120
120
|
```ts
|
|
121
|
+
// Read from env vars (FORCE_COLOR, NO_COLOR), same as `--color=auto`
|
|
122
|
+
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "env" });
|
|
121
123
|
// Force colors on
|
|
122
124
|
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "always" });
|
|
123
|
-
|
|
124
125
|
// Force colors off (useful in CI)
|
|
125
126
|
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "never" });
|
|
126
|
-
|
|
127
|
-
// Read from env vars (FORCE_COLOR, NO_COLOR, MOCK_COLOR)
|
|
128
|
-
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "env" });
|
|
129
|
-
|
|
130
|
-
// Deterministic mock output (useful in snapshot tests)
|
|
131
|
-
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "mock" });
|
|
132
127
|
```
|
|
133
128
|
|
|
134
129
|
## Testing your CLI
|
package/docs/index.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
layout: home
|
|
3
3
|
|
|
4
4
|
hero:
|
|
5
|
-
name: CLI-
|
|
5
|
+
name: CLI-Kiss
|
|
6
6
|
text: CLI for TypeScript.
|
|
7
7
|
|
|
8
8
|
tagline:
|
|
9
|
-
No bloat, no dependencies.<br/>Standard behavior users expect.<br/>Keep It
|
|
9
|
+
No bloat, no dependencies.<br/>Standard behavior users expect.<br/>Keep It
|
|
10
|
+
Simple and Stupid, it just does the job.
|
|
10
11
|
|
|
11
12
|
image:
|
|
12
13
|
src: /hero.png
|
package/package.json
CHANGED
package/src/lib/Run.ts
CHANGED
|
@@ -5,6 +5,11 @@ import { typeChoice } from "./Type";
|
|
|
5
5
|
import { TypoSupport } from "./Typo";
|
|
6
6
|
import { usageToStyledLines } from "./Usage";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Color selection modes availables
|
|
10
|
+
*/
|
|
11
|
+
export type RunColorMode = "env" | "always" | "never" | "mock";
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* Main entry point: parses CLI arguments, executes the matched command, and exits.
|
|
10
15
|
* Handles `--help`, `--version`, usage-on-error, and exit codes.
|
|
@@ -53,7 +58,7 @@ export async function runAndExit<Context>(
|
|
|
53
58
|
context: Context,
|
|
54
59
|
command: Command<Context, void>,
|
|
55
60
|
options?: {
|
|
56
|
-
colorSetup?: "flag" |
|
|
61
|
+
colorSetup?: "flag" | RunColorMode | undefined;
|
|
57
62
|
usageOnHelp?: boolean | undefined;
|
|
58
63
|
usageOnError?: boolean | undefined;
|
|
59
64
|
buildVersion?: string | undefined;
|
|
@@ -68,14 +73,12 @@ export async function runAndExit<Context>(
|
|
|
68
73
|
let typoSupport = TypoSupport.none();
|
|
69
74
|
const colorSetup = options?.colorSetup ?? "flag";
|
|
70
75
|
if (colorSetup === "flag") {
|
|
71
|
-
const colorOption = optionSingleValue<"auto" |
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
).registerAndMakeDecoder(readerArgs);
|
|
76
|
+
const colorOption = optionSingleValue<"auto" | RunColorMode>({
|
|
77
|
+
long: "color",
|
|
78
|
+
type: typeChoice("color-mode", ["auto", "always", "never", "mock"]),
|
|
79
|
+
defaultWhenNotDefined: () => "auto",
|
|
80
|
+
defaultWhenNotInlined: () => "always",
|
|
81
|
+
}).registerAndMakeDecoder(readerArgs);
|
|
79
82
|
preprocessors.push(() => {
|
|
80
83
|
try {
|
|
81
84
|
typoSupport = computeTypoSupport(colorOption.getAndDecodeValue());
|
|
@@ -86,11 +89,7 @@ export async function runAndExit<Context>(
|
|
|
86
89
|
return undefined;
|
|
87
90
|
});
|
|
88
91
|
} else {
|
|
89
|
-
|
|
90
|
-
typoSupport = TypoSupport.inferFromEnv();
|
|
91
|
-
} else {
|
|
92
|
-
typoSupport = computeTypoSupport(colorSetup);
|
|
93
|
-
}
|
|
92
|
+
typoSupport = computeTypoSupport(colorSetup);
|
|
94
93
|
}
|
|
95
94
|
if (options?.usageOnHelp ?? true) {
|
|
96
95
|
const helpOption = optionFlag({ long: "help" }).registerAndMakeDecoder(
|
|
@@ -182,12 +181,12 @@ function computeUsageString<Context, Result>(
|
|
|
182
181
|
}).join("\n");
|
|
183
182
|
}
|
|
184
183
|
|
|
185
|
-
function computeTypoSupport(
|
|
186
|
-
colorMode: "auto" | "always" | "never" | "mock",
|
|
187
|
-
): TypoSupport {
|
|
184
|
+
function computeTypoSupport(colorMode: "auto" | RunColorMode): TypoSupport {
|
|
188
185
|
switch (colorMode) {
|
|
189
186
|
case "auto":
|
|
190
187
|
return TypoSupport.inferFromEnv();
|
|
188
|
+
case "env":
|
|
189
|
+
return TypoSupport.inferFromEnv();
|
|
191
190
|
case "always":
|
|
192
191
|
return TypoSupport.tty();
|
|
193
192
|
case "never":
|
package/src/lib/Type.ts
CHANGED
|
@@ -51,23 +51,19 @@ export function typeBoolean(name?: string): Type<boolean> {
|
|
|
51
51
|
content: name ?? "boolean",
|
|
52
52
|
decoder(input: string) {
|
|
53
53
|
const lower = input.toLowerCase();
|
|
54
|
-
if (
|
|
54
|
+
if (typeBooleanValuesTrue.has(lower)) {
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
|
-
if (
|
|
57
|
+
if (typeBooleanValuesFalse.has(lower)) {
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
new TypoText(
|
|
62
|
-
new TypoString(`Not a boolean: `),
|
|
63
|
-
new TypoString(`"${input}"`, typoStyleQuote),
|
|
64
|
-
),
|
|
65
|
-
);
|
|
60
|
+
throwInvalidValue("a boolean", input);
|
|
66
61
|
},
|
|
67
62
|
};
|
|
68
63
|
}
|
|
69
|
-
|
|
70
|
-
const
|
|
64
|
+
|
|
65
|
+
export const typeBooleanValuesTrue = new Set(["true", "yes", "on", "y"]);
|
|
66
|
+
export const typeBooleanValuesFalse = new Set(["false", "no", "off", "n"]);
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
69
|
* Parses a date/time string via `Date.parse`.
|
|
@@ -91,12 +87,7 @@ export function typeDatetime(name?: string): Type<Date> {
|
|
|
91
87
|
}
|
|
92
88
|
return new Date(timestampMs);
|
|
93
89
|
} catch {
|
|
94
|
-
|
|
95
|
-
new TypoText(
|
|
96
|
-
new TypoString(`Not a valid ISO_8601 datetime: `),
|
|
97
|
-
new TypoString(`"${input}"`, typoStyleQuote),
|
|
98
|
-
),
|
|
99
|
-
);
|
|
90
|
+
throwInvalidValue("a valid ISO_8601 datetime", input);
|
|
100
91
|
}
|
|
101
92
|
},
|
|
102
93
|
};
|
|
@@ -109,7 +100,7 @@ export function typeDatetime(name?: string): Type<Date> {
|
|
|
109
100
|
* ```ts
|
|
110
101
|
* typeNumber("my-number").decoder("3.14") // → 3.14
|
|
111
102
|
* typeNumber("my-number").decoder("-1") // → -1
|
|
112
|
-
* typeNumber("my-number").decoder("hello") // throws
|
|
103
|
+
* typeNumber("my-number").decoder("hello") // throws
|
|
113
104
|
* ```
|
|
114
105
|
*/
|
|
115
106
|
export function typeNumber(name?: string): Type<number> {
|
|
@@ -123,12 +114,7 @@ export function typeNumber(name?: string): Type<number> {
|
|
|
123
114
|
}
|
|
124
115
|
return parsed;
|
|
125
116
|
} catch {
|
|
126
|
-
|
|
127
|
-
new TypoText(
|
|
128
|
-
new TypoString(`Not a number: `),
|
|
129
|
-
new TypoString(`"${input}"`, typoStyleQuote),
|
|
130
|
-
),
|
|
131
|
-
);
|
|
117
|
+
throwInvalidValue("a number", input);
|
|
132
118
|
}
|
|
133
119
|
},
|
|
134
120
|
};
|
|
@@ -141,8 +127,8 @@ export function typeNumber(name?: string): Type<number> {
|
|
|
141
127
|
* @example
|
|
142
128
|
* ```ts
|
|
143
129
|
* typeInteger("my-integer").decoder("42") // → 42n
|
|
144
|
-
* typeInteger("my-integer").decoder("3.14") // throws
|
|
145
|
-
* typeInteger("my-integer").decoder("abc") // throws
|
|
130
|
+
* typeInteger("my-integer").decoder("3.14") // throws
|
|
131
|
+
* typeInteger("my-integer").decoder("abc") // throws
|
|
146
132
|
* ```
|
|
147
133
|
*/
|
|
148
134
|
export function typeInteger(name?: string): Type<bigint> {
|
|
@@ -152,12 +138,7 @@ export function typeInteger(name?: string): Type<bigint> {
|
|
|
152
138
|
try {
|
|
153
139
|
return BigInt(input);
|
|
154
140
|
} catch {
|
|
155
|
-
|
|
156
|
-
new TypoText(
|
|
157
|
-
new TypoString(`Not an integer: `),
|
|
158
|
-
new TypoString(`"${input}"`, typoStyleQuote),
|
|
159
|
-
),
|
|
160
|
-
);
|
|
141
|
+
throwInvalidValue("an integer", input);
|
|
161
142
|
}
|
|
162
143
|
},
|
|
163
144
|
};
|
|
@@ -170,7 +151,7 @@ export function typeInteger(name?: string): Type<bigint> {
|
|
|
170
151
|
* @example
|
|
171
152
|
* ```ts
|
|
172
153
|
* typeUrl("my-url").decoder("https://example.com") // → URL { href: "https://example.com/", ... }
|
|
173
|
-
* typeUrl("my-url").decoder("not-a-url") // throws
|
|
154
|
+
* typeUrl("my-url").decoder("not-a-url") // throws
|
|
174
155
|
* ```
|
|
175
156
|
*/
|
|
176
157
|
export function typeUrl(name?: string): Type<URL> {
|
|
@@ -180,12 +161,7 @@ export function typeUrl(name?: string): Type<URL> {
|
|
|
180
161
|
try {
|
|
181
162
|
return new URL(input);
|
|
182
163
|
} catch {
|
|
183
|
-
|
|
184
|
-
new TypoText(
|
|
185
|
-
new TypoString(`Not an URL: `),
|
|
186
|
-
new TypoString(`"${input}"`, typoStyleQuote),
|
|
187
|
-
),
|
|
188
|
-
);
|
|
164
|
+
throwInvalidValue("an URL", input);
|
|
189
165
|
}
|
|
190
166
|
},
|
|
191
167
|
};
|
|
@@ -296,7 +272,20 @@ export function typePath(
|
|
|
296
272
|
throw new Error(`Path cannot contain null characters`);
|
|
297
273
|
}
|
|
298
274
|
if (checks?.checkSyncExistAs !== undefined) {
|
|
299
|
-
|
|
275
|
+
function safeStatSync(path: string) {
|
|
276
|
+
try {
|
|
277
|
+
return statSync(path);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
throw new TypoError(
|
|
280
|
+
new TypoText(
|
|
281
|
+
new TypoString(`Path does not exist: `),
|
|
282
|
+
new TypoString(`"${path}"`, typoStyleQuote),
|
|
283
|
+
),
|
|
284
|
+
error,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const stats = safeStatSync(input);
|
|
300
289
|
const preview = stats.isDirectory()
|
|
301
290
|
? "directory"
|
|
302
291
|
: stats.isFile()
|
|
@@ -305,7 +294,7 @@ export function typePath(
|
|
|
305
294
|
if (checks.checkSyncExistAs === "file" && !stats.isFile()) {
|
|
306
295
|
throw new TypoError(
|
|
307
296
|
new TypoText(
|
|
308
|
-
new TypoString(`Expected a
|
|
297
|
+
new TypoString(`Expected a file but found: ${preview}: `),
|
|
309
298
|
new TypoString(`"${input}"`, typoStyleQuote),
|
|
310
299
|
),
|
|
311
300
|
);
|
|
@@ -313,7 +302,7 @@ export function typePath(
|
|
|
313
302
|
if (checks.checkSyncExistAs === "directory" && !stats.isDirectory()) {
|
|
314
303
|
throw new TypoError(
|
|
315
304
|
new TypoText(
|
|
316
|
-
new TypoString(`Expected a
|
|
305
|
+
new TypoString(`Expected a directory but found: ${preview}: `),
|
|
317
306
|
new TypoString(`"${input}"`, typoStyleQuote),
|
|
318
307
|
),
|
|
319
308
|
);
|
|
@@ -474,3 +463,12 @@ export function typeList<Value>(
|
|
|
474
463
|
},
|
|
475
464
|
};
|
|
476
465
|
}
|
|
466
|
+
|
|
467
|
+
function throwInvalidValue(kind: string, input: string): never {
|
|
468
|
+
throw new TypoError(
|
|
469
|
+
new TypoText(
|
|
470
|
+
new TypoString(`Not ${kind}: `),
|
|
471
|
+
new TypoString(`"${input}"`, typoStyleQuote),
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
}
|
package/src/lib/Typo.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { typeBooleanValuesFalse } from "./Type";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Color names for terminal styling, used by {@link TypoStyle}.
|
|
3
5
|
* `dark*` = standard ANSI (30–37); `bright*` = high-intensity (90–97).
|
|
@@ -358,27 +360,38 @@ export class TypoSupport {
|
|
|
358
360
|
* Auto-detects styling mode from the process environment on best-effort basis.
|
|
359
361
|
*/
|
|
360
362
|
static inferFromEnv(): TypoSupport {
|
|
361
|
-
|
|
363
|
+
/*
|
|
364
|
+
console.warn({
|
|
365
|
+
no: readEnvVar("NO_COLOR"),
|
|
366
|
+
force: readEnvVar("FORCE_COLOR"),
|
|
367
|
+
mock: readEnvVar("MOCK_COLOR"),
|
|
368
|
+
term: readEnvVar("TERM"),
|
|
369
|
+
tty: process.stdout.isTTY,
|
|
370
|
+
});
|
|
371
|
+
*/
|
|
372
|
+
if (!process || !process.env || !process.stdout) {
|
|
362
373
|
return TypoSupport.none();
|
|
363
374
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return undefined;
|
|
367
|
-
}
|
|
368
|
-
return process.env[name];
|
|
375
|
+
if (readEnvVar("NO_COLOR")) {
|
|
376
|
+
return TypoSupport.none();
|
|
369
377
|
}
|
|
370
378
|
const envForceColor = readEnvVar("FORCE_COLOR");
|
|
371
379
|
if (envForceColor === "0") {
|
|
372
380
|
return TypoSupport.none();
|
|
373
381
|
}
|
|
374
382
|
if (envForceColor !== undefined) {
|
|
375
|
-
|
|
383
|
+
if (!typeBooleanValuesFalse.has(envForceColor.toLowerCase())) {
|
|
384
|
+
return TypoSupport.tty();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (readEnvVar("MOCK_COLOR")) {
|
|
388
|
+
return TypoSupport.mock();
|
|
376
389
|
}
|
|
377
|
-
if (
|
|
390
|
+
if (!process.stdout.isTTY) {
|
|
378
391
|
return TypoSupport.none();
|
|
379
392
|
}
|
|
380
|
-
if (readEnvVar("
|
|
381
|
-
return TypoSupport.
|
|
393
|
+
if (readEnvVar("TERM")?.toLowerCase() === "dumb") {
|
|
394
|
+
return TypoSupport.none();
|
|
382
395
|
}
|
|
383
396
|
return TypoSupport.tty();
|
|
384
397
|
}
|
|
@@ -496,3 +509,10 @@ const ttyCodeBgColors: Record<TypoColor, string> = {
|
|
|
496
509
|
brightCyan: "\x1b[106m",
|
|
497
510
|
brightWhite: "\x1b[107m",
|
|
498
511
|
};
|
|
512
|
+
|
|
513
|
+
function readEnvVar(name: string) {
|
|
514
|
+
if (!(name in process.env)) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
return process.env[name];
|
|
518
|
+
}
|
package/src/lib/Usage.ts
CHANGED
|
@@ -116,16 +116,16 @@ export type UsageOption = {
|
|
|
116
116
|
* <detail lines...>
|
|
117
117
|
*
|
|
118
118
|
* Positionals:
|
|
119
|
-
* <
|
|
119
|
+
* <label> <description> (<hint>)
|
|
120
120
|
*
|
|
121
121
|
* Subcommands:
|
|
122
122
|
* <name> <description> (<hint>)
|
|
123
123
|
*
|
|
124
124
|
* Options:
|
|
125
|
-
* -s, --long <
|
|
125
|
+
* -s, --long <label><annotation> <description> (<hint>)
|
|
126
126
|
*
|
|
127
127
|
* Examples:
|
|
128
|
-
* <
|
|
128
|
+
* <explanation>
|
|
129
129
|
* <command line>
|
|
130
130
|
*
|
|
131
131
|
* ```
|