clap-ts 0.2.0 → 0.3.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/README.md +718 -102
- package/dist/argfile.d.ts +55 -0
- package/dist/argfile.js +155 -0
- package/dist/completions.d.ts +4 -1
- package/dist/completions.js +199 -16
- package/dist/config.d.ts +75 -0
- package/dist/config.js +134 -0
- package/dist/help.d.ts +4 -4
- package/dist/help.js +276 -81
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -3
- package/dist/install.d.ts +55 -0
- package/dist/install.js +185 -0
- package/dist/log.d.ts +78 -0
- package/dist/log.js +164 -0
- package/dist/man.d.ts +28 -0
- package/dist/man.js +234 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.js +165 -0
- package/dist/output.d.ts +111 -0
- package/dist/output.js +356 -0
- package/dist/parser.d.ts +40 -19
- package/dist/parser.js +789 -461
- package/dist/plugins.d.ts +58 -0
- package/dist/plugins.js +145 -0
- package/dist/progress.d.ts +89 -0
- package/dist/progress.js +205 -0
- package/dist/prompt.d.ts +99 -0
- package/dist/prompt.js +299 -0
- package/dist/runner.d.ts +5 -2
- package/dist/runner.js +341 -135
- package/dist/spec.d.ts +83 -0
- package/dist/spec.js +124 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +113 -0
- package/dist/types.d.ts +279 -11
- package/dist/validation.js +191 -69
- package/package.json +63 -6
package/dist/config.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration file loading, layered under the command line and environment.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { loadConfig } from 'clap-ts/config';
|
|
6
|
+
*
|
|
7
|
+
* const config = loadConfig('mytool');
|
|
8
|
+
* await runMain(main, { config: config?.values });
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Precedence ends up as command line, then environment, then config file, then
|
|
12
|
+
* the argument's own default. `ctx.valueSources` reports which one won.
|
|
13
|
+
*
|
|
14
|
+
* Only JSON is understood out of the box, which keeps this dependency-free.
|
|
15
|
+
* Point `parse` at a TOML or YAML reader to accept those.
|
|
16
|
+
*
|
|
17
|
+
* The search costs one `existsSync` per candidate per directory, so it is
|
|
18
|
+
* O(directories x candidates) and independent of how many files those
|
|
19
|
+
* directories hold. Listing each directory once instead would be one syscall
|
|
20
|
+
* per level, but `readdirSync` is O(entries): measured against a 2000-entry
|
|
21
|
+
* directory it took 117us where four `existsSync` calls took 2.4us. Walking up
|
|
22
|
+
* through a large directory is exactly the case that has to stay cheap.
|
|
23
|
+
*/
|
|
24
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
25
|
+
import { dirname, resolve } from 'node:path';
|
|
26
|
+
import { homedir } from 'node:os';
|
|
27
|
+
/** The home directory never changes within a process. */
|
|
28
|
+
const HOME = homedir();
|
|
29
|
+
const defaultFileCache = new Map();
|
|
30
|
+
function defaultFiles(name) {
|
|
31
|
+
let files = defaultFileCache.get(name);
|
|
32
|
+
if (files === undefined) {
|
|
33
|
+
files = [`.${name}rc`, `.${name}rc.json`, `${name}.config.json`, `.config/${name}.json`];
|
|
34
|
+
defaultFileCache.set(name, files);
|
|
35
|
+
}
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
function asRecord(value, path) {
|
|
39
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
40
|
+
throw new Error(`config at ${path} must be an object`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function readOne(path, parse) {
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
text = readFileSync(path, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new Error(`cannot read config at ${path}: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return { values: asRecord(parse(text, path), path), path };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
throw new Error(`cannot parse config at ${path}: ${message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Find and read the nearest configuration file, walking up from `cwd`.
|
|
63
|
+
*
|
|
64
|
+
* Returns `undefined` when nothing is found, and throws only when a file exists
|
|
65
|
+
* but cannot be read or parsed: a broken config should be loud, a missing one
|
|
66
|
+
* should not.
|
|
67
|
+
*/
|
|
68
|
+
export function loadConfig(name, opts) {
|
|
69
|
+
const parse = opts?.parse ?? ((text) => JSON.parse(text));
|
|
70
|
+
if (opts?.path !== undefined) {
|
|
71
|
+
return readOne(resolve(opts.path), parse);
|
|
72
|
+
}
|
|
73
|
+
const files = opts?.files ?? defaultFiles(name);
|
|
74
|
+
const packageKey = opts?.packageJsonKey === undefined ? name : opts.packageJsonKey;
|
|
75
|
+
const stopAt = resolve(opts?.stopAt ?? HOME);
|
|
76
|
+
const searchParents = opts?.searchParents !== false;
|
|
77
|
+
let dir = resolve(opts?.cwd ?? process.cwd());
|
|
78
|
+
for (;;) {
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
// Template concatenation rather than join(): join normalises, which this
|
|
81
|
+
// does not need, and the walk runs this on every candidate at every level.
|
|
82
|
+
const candidate = `${dir}/${file}`;
|
|
83
|
+
if (existsSync(candidate)) {
|
|
84
|
+
return readOne(candidate, parse);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let atProjectRoot = false;
|
|
88
|
+
if (packageKey !== null || opts?.stopAtProjectRoot === true) {
|
|
89
|
+
const pkgPath = `${dir}/package.json`;
|
|
90
|
+
if (existsSync(pkgPath)) {
|
|
91
|
+
atProjectRoot = true;
|
|
92
|
+
if (packageKey !== null) {
|
|
93
|
+
const section = readOne(pkgPath, parse).values[packageKey];
|
|
94
|
+
if (section !== undefined) {
|
|
95
|
+
return { values: asRecord(section, pkgPath), path: pkgPath };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!searchParents) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (opts?.stopAtProjectRoot === true && (atProjectRoot || existsSync(`${dir}/.git`))) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const parent = dirname(dir);
|
|
107
|
+
if (parent === dir || dir === stopAt) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
dir = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Load a config and hand back the run options to spread into `runMain`.
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* await runMain(main, { ...configOptions('mytool') });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function configOptions(name, opts) {
|
|
121
|
+
// A thunk, so runMain only searches the filesystem when some argument is
|
|
122
|
+
// still on its default.
|
|
123
|
+
let cached;
|
|
124
|
+
let loadedOnce = false;
|
|
125
|
+
return {
|
|
126
|
+
config: () => {
|
|
127
|
+
if (!loadedOnce) {
|
|
128
|
+
cached = loadConfig(name, opts)?.values;
|
|
129
|
+
loadedOnce = true;
|
|
130
|
+
}
|
|
131
|
+
return cached;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
package/dist/help.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* hideShortHelp/hideLongHelp, visibleAlias, hidePossibleValues,
|
|
7
7
|
* and custom styles.
|
|
8
8
|
*/
|
|
9
|
-
import type { CommandDef, CommandMeta, StylesDef } from './types.js';
|
|
9
|
+
import type { CommandDef, CommandMeta, OutputSink, StylesDef } from './types.js';
|
|
10
10
|
/**
|
|
11
11
|
* Render the full help text for a command.
|
|
12
12
|
* Matches clap's help format. Supports helpTemplate override,
|
|
@@ -20,12 +20,12 @@ export declare function renderUsage(command: CommandDef, parentNames?: string[],
|
|
|
20
20
|
/**
|
|
21
21
|
* Print help to stdout.
|
|
22
22
|
*/
|
|
23
|
-
export declare function showHelp(command: CommandDef, parentNames?: string[], isShortHelp?: boolean, styleOverrides?: Partial<StylesDef
|
|
23
|
+
export declare function showHelp(command: CommandDef, parentNames?: string[], isShortHelp?: boolean, styleOverrides?: Partial<StylesDef>, out?: OutputSink): void;
|
|
24
24
|
/**
|
|
25
25
|
* Print version to stdout.
|
|
26
26
|
*/
|
|
27
|
-
export declare function showVersion(meta: CommandMeta): void;
|
|
27
|
+
export declare function showVersion(meta: CommandMeta, isShort?: boolean, out?: OutputSink): void;
|
|
28
28
|
/**
|
|
29
29
|
* Print an error message with usage hint.
|
|
30
30
|
*/
|
|
31
|
-
export declare function showError(message: string, command: CommandDef, parentNames?: string[], styleOverrides?: Partial<StylesDef
|
|
31
|
+
export declare function showError(message: string, command: CommandDef, parentNames?: string[], styleOverrides?: Partial<StylesDef>, out?: OutputSink): void;
|