@avandar/acclimate 0.1.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/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/index.cjs +381 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +223 -0
- package/dist/index.d.ts +223 -0
- package/dist/index.js +379 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Avandar Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Acclimate
|
|
2
|
+
|
|
3
|
+
A lightweight TypeScript framework for building type-safe command line
|
|
4
|
+
interfaces.
|
|
5
|
+
|
|
6
|
+
This library is not complete and is still missing a lot of functionality.
|
|
7
|
+
We do not recommend this library be used in production.
|
|
8
|
+
|
|
9
|
+
See the To Do section at the end for what is still missing.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install acclimate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Acclimate } from "acclimate";
|
|
21
|
+
|
|
22
|
+
const cli = Acclimate.createCLI("demo-cli")
|
|
23
|
+
.description("A tiny demo CLI")
|
|
24
|
+
.action(() => {
|
|
25
|
+
console.log("Hello, world!");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
Acclimate.run(cli);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
Public exports from `acclimate`:
|
|
34
|
+
|
|
35
|
+
- `Acclimate`
|
|
36
|
+
|
|
37
|
+
### `Acclimate`
|
|
38
|
+
|
|
39
|
+
#### `Acclimate.createCLI(name)`
|
|
40
|
+
|
|
41
|
+
Create a new CLI instance.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
Acclimate.createCLI(name: string): IAcclimateCLI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### `Acclimate.run(cli)`
|
|
48
|
+
|
|
49
|
+
Run a CLI instance using `process.argv.slice(2)`.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
Acclimate.run(cli: IAcclimateCLI): void
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `IAcclimateCLI` (CLI builder)
|
|
56
|
+
|
|
57
|
+
`createCLI()` returns an immutable builder. Each method returns a new CLI
|
|
58
|
+
instance with updated configuration.
|
|
59
|
+
|
|
60
|
+
#### `cli.description(description)`
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
cli.description(description: string): IAcclimateCLI
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### `cli.action(action)`
|
|
67
|
+
|
|
68
|
+
Set the function executed when the CLI matches (after parsing).
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
cli.action(
|
|
72
|
+
action: (args: FullCLIArgValues<...>) => void,
|
|
73
|
+
): IAcclimateCLI
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### `cli.addPositionalArg(param)`
|
|
77
|
+
|
|
78
|
+
Add a positional argument. Positional args are parsed in order.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
cli.addPositionalArg(param: CLIPositionalParam): IAcclimateCLI
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `cli.addOption(param)`
|
|
85
|
+
|
|
86
|
+
Add an option local to this CLI level.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
cli.addOption(param: CLIOptionParam): IAcclimateCLI
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### `cli.addGlobalOption(param)`
|
|
93
|
+
|
|
94
|
+
Add an option that is available to this CLI and all sub-commands.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
cli.addGlobalOption(param: CLIOptionParam): IAcclimateCLI
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `cli.addCommand(commandName, commandCLI)`
|
|
101
|
+
|
|
102
|
+
Add a sub-command (a nested CLI). If the first positional token matches a
|
|
103
|
+
command name, parsing continues using that command's CLI.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
cli.addCommand(
|
|
107
|
+
commandName: string,
|
|
108
|
+
commandCLI: IAcclimateCLI,
|
|
109
|
+
): IAcclimateCLI
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `cli.getCommandCLI(commandName)`
|
|
113
|
+
|
|
114
|
+
Get a command CLI by name (throws if missing).
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
cli.getCommandCLI(commandName: string): IAcclimateCLI
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Param config types
|
|
121
|
+
|
|
122
|
+
Acclimate uses config objects to describe positional args and options.
|
|
123
|
+
|
|
124
|
+
- **Positional args**: `CLIPositionalParam`
|
|
125
|
+
- **name**: `string` (prefer `camelCase` to match runtime parsing)
|
|
126
|
+
- **type**: `"string" | "number" | "boolean"`
|
|
127
|
+
- **required**: `boolean`
|
|
128
|
+
- **description?**: `string`
|
|
129
|
+
- **defaultValue?**: depends on `type`
|
|
130
|
+
- **choices?**: allowed values list
|
|
131
|
+
- **parser?**: `(value: string) => value`
|
|
132
|
+
- **validator?**: `(value) => true | string`
|
|
133
|
+
|
|
134
|
+
- **Options**: `CLIOptionParam`
|
|
135
|
+
- **name**: `--${string}` (example: `--dry-run`)
|
|
136
|
+
- **aliases?**: `readonly ("--x" | "-x")[]`
|
|
137
|
+
- **required**: `boolean`
|
|
138
|
+
- Same `type`, `defaultValue`, `choices`, `parser`, `validator` fields as a
|
|
139
|
+
positional arg
|
|
140
|
+
|
|
141
|
+
### Parsing behavior (current)
|
|
142
|
+
|
|
143
|
+
- **Positional args**: validated and parsed in order; extra positional args
|
|
144
|
+
throw an error.
|
|
145
|
+
- **Options**: parsing starts at the first token that begins with `-`. Each
|
|
146
|
+
option consumes tokens until the next option; its raw value is the consumed
|
|
147
|
+
tokens joined with spaces.
|
|
148
|
+
- **Option keys in `action(args)`**: option names are camel-cased, so
|
|
149
|
+
`--dry-run` becomes `dryRun`.
|
|
150
|
+
- **Boolean flags**: `--flag` with no value parses as `true` (only the
|
|
151
|
+
literal string `false` parses as `false`).
|
|
152
|
+
- **Errors**: missing required args/options (and invalid values) throw
|
|
153
|
+
`CLIError`.
|
|
154
|
+
|
|
155
|
+
## Prerequisites
|
|
156
|
+
|
|
157
|
+
- Node.js **18+**
|
|
158
|
+
- npm (bundled with Node)
|
|
159
|
+
|
|
160
|
+
## Project Layout
|
|
161
|
+
|
|
162
|
+
- `src/` — framework source code.
|
|
163
|
+
- `examples/` — small usage samples; `basic.ts` is runnable via `npm run demo`.
|
|
164
|
+
- `tests/` — Vitest suite.
|
|
165
|
+
- `dist/` — build output generated by `tsup`.
|
|
166
|
+
|
|
167
|
+
## Setup
|
|
168
|
+
|
|
169
|
+
Install dependencies:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npm install
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Common Scripts
|
|
176
|
+
|
|
177
|
+
- `npm run dev` — build in watch mode with tsup.
|
|
178
|
+
- `npm run build` — produce CJS/ESM bundles and type declarations in `dist/`.
|
|
179
|
+
- `npm run demo` — execute the `examples/basic.ts` sample with tsx.
|
|
180
|
+
- `npm test` — run the Vitest suite once.
|
|
181
|
+
- `npm run test:watch` — run tests in watch mode.
|
|
182
|
+
- `npm run lint` / `npm run lint:fix` — check or auto-fix with ESLint.
|
|
183
|
+
- `npm run format` / `npm run format:fix` — check or write Prettier
|
|
184
|
+
formatting.
|
|
185
|
+
- `npm run type` - check typescript types
|
|
186
|
+
|
|
187
|
+
## Running & Testing
|
|
188
|
+
|
|
189
|
+
1. Start a build (optional during development):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm run dev
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
1. Run the sample CLI:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm run demo
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
1. Execute tests:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npm test
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The `prepare` script runs `npm run build`, so the package will compile
|
|
208
|
+
automatically when installed from a git dependency.
|
|
209
|
+
|
|
210
|
+
## Examples
|
|
211
|
+
|
|
212
|
+
### Hello world
|
|
213
|
+
|
|
214
|
+
Matches `examples/basic.ts`.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { Acclimate } from "acclimate";
|
|
218
|
+
|
|
219
|
+
const cli = Acclimate.createCLI("demo-cli").action(() => {
|
|
220
|
+
console.log("Hello, world!");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
Acclimate.run(cli);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Positional args + options
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { Acclimate } from "acclimate";
|
|
230
|
+
|
|
231
|
+
const cli = Acclimate.createCLI("greet")
|
|
232
|
+
.addPositionalArg({
|
|
233
|
+
name: "name",
|
|
234
|
+
type: "string",
|
|
235
|
+
required: true,
|
|
236
|
+
description: "Who to greet",
|
|
237
|
+
})
|
|
238
|
+
.addOption({
|
|
239
|
+
name: "--shout",
|
|
240
|
+
type: "boolean",
|
|
241
|
+
required: false,
|
|
242
|
+
aliases: ["-s"] as const,
|
|
243
|
+
description: "Uppercase the output",
|
|
244
|
+
defaultValue: false,
|
|
245
|
+
})
|
|
246
|
+
.action(({ name, shout }) => {
|
|
247
|
+
const message = `Hello, ${name}!`;
|
|
248
|
+
console.log(shout ? message.toUpperCase() : message);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
Acclimate.run(cli);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Sub-commands + global options
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { Acclimate } from "acclimate";
|
|
258
|
+
|
|
259
|
+
const initCmd = Acclimate.createCLI("init")
|
|
260
|
+
.description("Initialize a project")
|
|
261
|
+
.action(({ verbose }) => {
|
|
262
|
+
console.log(`init (verbose=${verbose})`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const root = Acclimate.createCLI("acme")
|
|
266
|
+
.addGlobalOption({
|
|
267
|
+
name: "--verbose",
|
|
268
|
+
type: "boolean",
|
|
269
|
+
required: false,
|
|
270
|
+
aliases: ["-v"] as const,
|
|
271
|
+
defaultValue: false,
|
|
272
|
+
})
|
|
273
|
+
.addCommand("init", initCmd);
|
|
274
|
+
|
|
275
|
+
Acclimate.run(root);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## To do
|
|
279
|
+
|
|
280
|
+
- [ ] Add semantic arguments: e.g. email (which has a default validator)
|
|
281
|
+
- [ ] Add default `help` command and `--help` option. This should show the CLI
|
|
282
|
+
description and all param documentation.
|
|
283
|
+
- [ ] Make the `description` actually get printed.
|
|
284
|
+
- [ ] Show all CLI param descriptions if command is run with no arguments or if
|
|
285
|
+
there is a param-related error.
|
|
286
|
+
- [ ] Add helper functions to log to stdout in different colors
|
|
287
|
+
- [ ] Add logic for `askIfEmpty` to enter an interactive mode to receive inputs
|
|
288
|
+
for different params.
|
|
289
|
+
- [ ] Add an option to only `askIfEmptyAndRequired`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var changeCase = require('change-case');
|
|
4
|
+
var tsPattern = require('ts-pattern');
|
|
5
|
+
|
|
6
|
+
// src/CLIError.ts
|
|
7
|
+
var CLIError = class _CLIError extends Error {
|
|
8
|
+
static invalidCLIParamValue(options) {
|
|
9
|
+
return new _CLIError({
|
|
10
|
+
message: options.message ?? `Invalid value for CLI param "${options.paramName}"`,
|
|
11
|
+
code: "invalid_cli_param_value",
|
|
12
|
+
details: { paramName: options.paramName, paramValue: options.paramValue }
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
static missingRequiredOption(options) {
|
|
16
|
+
return new _CLIError({
|
|
17
|
+
message: options.message ?? `Required option "${options.optionName}" is missing`,
|
|
18
|
+
code: "missing_required_option",
|
|
19
|
+
details: { optionName: options.optionName }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static missingRequiredPositionalArg(options) {
|
|
23
|
+
return new _CLIError({
|
|
24
|
+
message: options.message ?? `Required positional argument "${options.positionalArgName}" is missing`,
|
|
25
|
+
code: "missing_required_positional_arg",
|
|
26
|
+
details: { positionalArgName: options.positionalArgName }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
static unknownOption(options) {
|
|
30
|
+
return new _CLIError({
|
|
31
|
+
message: options.message ?? `Option "${options.optionName}" not found`,
|
|
32
|
+
code: "unknown_option",
|
|
33
|
+
details: { optionName: options.optionName }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
static tooManyPositionalArgs(options) {
|
|
37
|
+
return new _CLIError({
|
|
38
|
+
message: options.message ?? "Too many positional arguments provided.",
|
|
39
|
+
code: "too_many_positional_args",
|
|
40
|
+
details: { count: options.count }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
static invalidPositionalArgConfig(options) {
|
|
44
|
+
return new _CLIError({
|
|
45
|
+
message: options.message ?? `Positional argument configuration for "${options.positionalArgName}" is invalid`,
|
|
46
|
+
code: "invalid_positional_arg_config",
|
|
47
|
+
details: { positionalArgName: options.positionalArgName }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
static unknownCommand(options) {
|
|
51
|
+
return new _CLIError({
|
|
52
|
+
message: options.message ?? `Command "${options.commandName}" not found`,
|
|
53
|
+
code: "unknown_command",
|
|
54
|
+
details: { commandName: options.commandName }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
static alreadyRun(options = {}) {
|
|
58
|
+
return new _CLIError({
|
|
59
|
+
message: options.message ?? "CLI has already been run",
|
|
60
|
+
code: "already_run",
|
|
61
|
+
details: void 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
constructor(options) {
|
|
65
|
+
super(`\u274C ${options.message}`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/AcclimateCLI/AcclimateCLI.ts
|
|
70
|
+
function AcclimateCLI(state) {
|
|
71
|
+
return {
|
|
72
|
+
state,
|
|
73
|
+
action: (action) => {
|
|
74
|
+
return AcclimateCLI({
|
|
75
|
+
...state,
|
|
76
|
+
action
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
description: (description) => {
|
|
80
|
+
return AcclimateCLI({
|
|
81
|
+
...state,
|
|
82
|
+
description
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
addPositionalArg: (param) => {
|
|
86
|
+
if (param.required && state.positionalArgs.some((p) => {
|
|
87
|
+
return !p.required;
|
|
88
|
+
})) {
|
|
89
|
+
throw CLIError.invalidPositionalArgConfig({
|
|
90
|
+
positionalArgName: param.name,
|
|
91
|
+
message: "Required positional arguments must be before optional positional arguments"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return AcclimateCLI({
|
|
95
|
+
...state,
|
|
96
|
+
positionalArgs: [
|
|
97
|
+
...state.positionalArgs,
|
|
98
|
+
{ ...param, required: param.required ?? true }
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
addGlobalOption: (param) => {
|
|
103
|
+
const newAliases = (param.aliases ?? []).reduce(
|
|
104
|
+
(acc, alias) => {
|
|
105
|
+
acc[alias] = param.name;
|
|
106
|
+
return acc;
|
|
107
|
+
},
|
|
108
|
+
{}
|
|
109
|
+
);
|
|
110
|
+
return AcclimateCLI({
|
|
111
|
+
...state,
|
|
112
|
+
aliases: { ...state.aliases, ...newAliases },
|
|
113
|
+
globalOptionArgs: {
|
|
114
|
+
...state.globalOptionArgs,
|
|
115
|
+
[param.name]: param
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
addOption: (param) => {
|
|
120
|
+
const newAliases = (param.aliases ?? []).reduce(
|
|
121
|
+
(acc, alias) => {
|
|
122
|
+
acc[alias] = param.name;
|
|
123
|
+
return acc;
|
|
124
|
+
},
|
|
125
|
+
{}
|
|
126
|
+
);
|
|
127
|
+
return AcclimateCLI({
|
|
128
|
+
...state,
|
|
129
|
+
aliases: { ...state.aliases, ...newAliases },
|
|
130
|
+
optionArgs: { ...state.optionArgs, [param.name]: param }
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
addCommand: (commandName, cli) => {
|
|
134
|
+
return AcclimateCLI({
|
|
135
|
+
...state,
|
|
136
|
+
commands: {
|
|
137
|
+
...state.commands,
|
|
138
|
+
[commandName]: cli
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
getCommandCLI: (commandName) => {
|
|
143
|
+
const cmd = state.commands[commandName];
|
|
144
|
+
if (!cmd) {
|
|
145
|
+
throw CLIError.unknownCommand({
|
|
146
|
+
commandName,
|
|
147
|
+
message: `Command "${commandName}" not found`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return cmd;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/AcclimateCLI/createCLI/defaultCLIState.ts
|
|
156
|
+
var defaultCLIState = {
|
|
157
|
+
aliases: {},
|
|
158
|
+
description: void 0,
|
|
159
|
+
commands: {},
|
|
160
|
+
positionalArgs: [],
|
|
161
|
+
optionArgs: {},
|
|
162
|
+
globalOptionArgs: {},
|
|
163
|
+
action: () => {
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/AcclimateCLI/createCLI/createCLI.ts
|
|
168
|
+
function createCLI(name) {
|
|
169
|
+
return AcclimateCLI({ ...defaultCLIState, name });
|
|
170
|
+
}
|
|
171
|
+
function _isValidFullOptionName(name) {
|
|
172
|
+
return name.startsWith("--");
|
|
173
|
+
}
|
|
174
|
+
function _isValidOptionAlias(name) {
|
|
175
|
+
return name.startsWith("-");
|
|
176
|
+
}
|
|
177
|
+
function isEmptyObject(obj) {
|
|
178
|
+
return Object.keys(obj).length === 0;
|
|
179
|
+
}
|
|
180
|
+
function _validateParsedValue(options) {
|
|
181
|
+
const { parsedValue, paramConfig } = options;
|
|
182
|
+
if (paramConfig.validator) {
|
|
183
|
+
const validator = paramConfig.validator;
|
|
184
|
+
const validationResult = validator(parsedValue);
|
|
185
|
+
if (validationResult === true) {
|
|
186
|
+
return parsedValue;
|
|
187
|
+
}
|
|
188
|
+
throw CLIError.invalidCLIParamValue({
|
|
189
|
+
paramName: paramConfig.name,
|
|
190
|
+
paramValue: parsedValue,
|
|
191
|
+
message: typeof validationResult === "string" ? validationResult : void 0
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return parsedValue;
|
|
195
|
+
}
|
|
196
|
+
function _parseAndValidateValue(options) {
|
|
197
|
+
const { inputValue, defaultValue, paramConfig } = options;
|
|
198
|
+
if (inputValue === void 0) {
|
|
199
|
+
if (defaultValue === void 0) {
|
|
200
|
+
if (_isValidFullOptionName(paramConfig.name)) {
|
|
201
|
+
throw CLIError.missingRequiredOption({
|
|
202
|
+
optionName: paramConfig.name
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
throw CLIError.missingRequiredPositionalArg({
|
|
206
|
+
positionalArgName: paramConfig.name
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return _validateParsedValue({
|
|
211
|
+
parsedValue: defaultValue,
|
|
212
|
+
paramConfig
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const parsedValue = paramConfig.parser ? paramConfig.parser(inputValue) : tsPattern.match(paramConfig.type).with("boolean", () => {
|
|
216
|
+
return inputValue === "false" ? false : true;
|
|
217
|
+
}).with("number", () => {
|
|
218
|
+
return Number.parseInt(inputValue, 10);
|
|
219
|
+
}).with("string", () => {
|
|
220
|
+
return inputValue;
|
|
221
|
+
}).exhaustive(() => {
|
|
222
|
+
return inputValue;
|
|
223
|
+
});
|
|
224
|
+
return _validateParsedValue({
|
|
225
|
+
parsedValue,
|
|
226
|
+
paramConfig
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function _replaceAliases({
|
|
230
|
+
aliasedOptionArgs,
|
|
231
|
+
cli
|
|
232
|
+
}) {
|
|
233
|
+
const aliasMap = cli.state.aliases;
|
|
234
|
+
return Object.entries(aliasedOptionArgs).reduce(
|
|
235
|
+
(acc, [aliasKey, value]) => {
|
|
236
|
+
const alias = aliasKey;
|
|
237
|
+
const optionName = aliasMap[alias] ?? alias;
|
|
238
|
+
acc[optionName] = value;
|
|
239
|
+
return acc;
|
|
240
|
+
},
|
|
241
|
+
{}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
function _runCLIHelper(options) {
|
|
245
|
+
const { rawGlobalOptionArgs, rawOptionArgs, rawPositionalArgs, cli } = {
|
|
246
|
+
...options,
|
|
247
|
+
rawGlobalOptionArgs: _replaceAliases({
|
|
248
|
+
aliasedOptionArgs: options.rawGlobalOptionArgs,
|
|
249
|
+
cli: options.cli
|
|
250
|
+
}),
|
|
251
|
+
rawOptionArgs: _replaceAliases({
|
|
252
|
+
aliasedOptionArgs: options.rawOptionArgs,
|
|
253
|
+
// TODO(jpsyx): fix that this does not have the CLI may not have all
|
|
254
|
+
cli: options.cli
|
|
255
|
+
})
|
|
256
|
+
};
|
|
257
|
+
const firstArg = rawPositionalArgs[0];
|
|
258
|
+
if (firstArg && !isEmptyObject(cli.state.commands) && cli.state.commands[firstArg]) {
|
|
259
|
+
const commandCLI = Object.values(
|
|
260
|
+
cli.state.globalOptionArgs
|
|
261
|
+
).reduce(
|
|
262
|
+
(newCmd, argConfig) => {
|
|
263
|
+
return newCmd.addGlobalOption(argConfig);
|
|
264
|
+
},
|
|
265
|
+
cli.getCommandCLI(firstArg)
|
|
266
|
+
);
|
|
267
|
+
return _runCLIHelper({
|
|
268
|
+
rawPositionalArgs: rawPositionalArgs.slice(1),
|
|
269
|
+
rawOptionArgs,
|
|
270
|
+
rawGlobalOptionArgs,
|
|
271
|
+
cli: commandCLI
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (rawPositionalArgs.length > cli.state.positionalArgs.length) {
|
|
275
|
+
throw CLIError.tooManyPositionalArgs({
|
|
276
|
+
count: rawPositionalArgs.length
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const parsedPositionalArgs = cli.state.positionalArgs.reduce(
|
|
280
|
+
(acc, argConfig, idx) => {
|
|
281
|
+
const rawVal = rawPositionalArgs[idx];
|
|
282
|
+
if (argConfig.required && rawVal === void 0) {
|
|
283
|
+
throw CLIError.missingRequiredPositionalArg({
|
|
284
|
+
positionalArgName: argConfig.name
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
acc[argConfig.name] = _parseAndValidateValue({
|
|
288
|
+
inputValue: rawVal,
|
|
289
|
+
defaultValue: argConfig.defaultValue,
|
|
290
|
+
paramConfig: argConfig
|
|
291
|
+
});
|
|
292
|
+
return acc;
|
|
293
|
+
},
|
|
294
|
+
{}
|
|
295
|
+
);
|
|
296
|
+
const parsedOptionArgs = Object.values(
|
|
297
|
+
cli.state.optionArgs
|
|
298
|
+
).reduce(
|
|
299
|
+
(acc, argConfig) => {
|
|
300
|
+
const rawVal = rawOptionArgs[argConfig.name];
|
|
301
|
+
if (argConfig.required && rawVal === void 0) {
|
|
302
|
+
throw CLIError.missingRequiredOption({
|
|
303
|
+
optionName: argConfig.name
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
acc[changeCase.camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
307
|
+
inputValue: rawVal,
|
|
308
|
+
defaultValue: argConfig.defaultValue,
|
|
309
|
+
paramConfig: argConfig
|
|
310
|
+
});
|
|
311
|
+
return acc;
|
|
312
|
+
},
|
|
313
|
+
{}
|
|
314
|
+
);
|
|
315
|
+
const parsedGlobalOptionArgs = Object.values(
|
|
316
|
+
cli.state.globalOptionArgs
|
|
317
|
+
).reduce(
|
|
318
|
+
(acc, argConfig) => {
|
|
319
|
+
const rawVal = rawGlobalOptionArgs[argConfig.name];
|
|
320
|
+
if (argConfig.required && rawVal === void 0) {
|
|
321
|
+
throw CLIError.missingRequiredOption({
|
|
322
|
+
optionName: argConfig.name
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
acc[changeCase.camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
326
|
+
inputValue: rawVal,
|
|
327
|
+
defaultValue: argConfig.defaultValue,
|
|
328
|
+
paramConfig: argConfig
|
|
329
|
+
});
|
|
330
|
+
return acc;
|
|
331
|
+
},
|
|
332
|
+
{}
|
|
333
|
+
);
|
|
334
|
+
cli.state.action({
|
|
335
|
+
...parsedPositionalArgs,
|
|
336
|
+
...parsedOptionArgs,
|
|
337
|
+
...parsedGlobalOptionArgs
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function runCLI(options) {
|
|
341
|
+
const { input, cli } = options;
|
|
342
|
+
const firstOptionIdx = input.findIndex((token) => {
|
|
343
|
+
return token.startsWith("-");
|
|
344
|
+
});
|
|
345
|
+
const [rawPositionalArgs, rest] = firstOptionIdx === -1 ? [input, []] : [input.slice(0, firstOptionIdx), input.slice(firstOptionIdx)];
|
|
346
|
+
const rawOptionArgs = {};
|
|
347
|
+
const rawGlobalOptionArgs = {};
|
|
348
|
+
let currentAlias;
|
|
349
|
+
let currentVals = [];
|
|
350
|
+
const { aliases, globalOptionArgs } = cli.state;
|
|
351
|
+
for (const argVal of rest.concat("-")) {
|
|
352
|
+
if (_isValidOptionAlias(argVal)) {
|
|
353
|
+
if (currentAlias) {
|
|
354
|
+
const rawValue = currentVals.join(" ");
|
|
355
|
+
const optionName = aliases[currentAlias] ?? currentAlias;
|
|
356
|
+
if (_isValidFullOptionName(optionName) && !isEmptyObject(globalOptionArgs) && globalOptionArgs[optionName]) {
|
|
357
|
+
rawGlobalOptionArgs[optionName] = rawValue;
|
|
358
|
+
} else {
|
|
359
|
+
rawOptionArgs[optionName] = rawValue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
currentAlias = argVal;
|
|
363
|
+
currentVals = [];
|
|
364
|
+
} else {
|
|
365
|
+
currentVals.push(argVal);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
_runCLIHelper({ rawPositionalArgs, rawOptionArgs, rawGlobalOptionArgs, cli });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/Acclimate.ts
|
|
372
|
+
var Acclimate = {
|
|
373
|
+
createCLI,
|
|
374
|
+
run: (cli) => {
|
|
375
|
+
runCLI({ cli, input: process.argv.slice(2) });
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
exports.Acclimate = Acclimate;
|
|
380
|
+
//# sourceMappingURL=index.cjs.map
|
|
381
|
+
//# sourceMappingURL=index.cjs.map
|