@guanghechen/commander 4.4.1 → 4.5.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/CHANGELOG.md +13 -0
- package/README.md +94 -4
- package/lib/cjs/index.cjs +155 -0
- package/lib/esm/index.mjs +151 -1
- package/lib/types/index.d.ts +76 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add built-in host and network validators (ip, domain, host), expose is helpers, and extend coerce
|
|
8
|
+
factories with port and choice support.
|
|
9
|
+
|
|
10
|
+
## 4.5.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- Add built-in coerce factories for numeric option parsing in commander.
|
|
15
|
+
|
|
3
16
|
## 4.4.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -202,6 +202,96 @@ new Command({ name: 'example', description: 'Option types demo' })
|
|
|
202
202
|
})
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
### Built-in Coerce Factories
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { Coerce, Command } from '@guanghechen/commander'
|
|
209
|
+
|
|
210
|
+
new Command({ name: 'example', desc: 'Coerce demo' })
|
|
211
|
+
.option({
|
|
212
|
+
long: 'offset',
|
|
213
|
+
type: 'number',
|
|
214
|
+
args: 'required',
|
|
215
|
+
coerce: Coerce.integer('--offset'),
|
|
216
|
+
desc: 'Signed offset',
|
|
217
|
+
})
|
|
218
|
+
.option({
|
|
219
|
+
long: 'parallel',
|
|
220
|
+
type: 'number',
|
|
221
|
+
args: 'required',
|
|
222
|
+
coerce: Coerce.positiveInteger('--parallel'),
|
|
223
|
+
desc: 'Parallel workers',
|
|
224
|
+
})
|
|
225
|
+
.option({
|
|
226
|
+
long: 'duration',
|
|
227
|
+
type: 'number',
|
|
228
|
+
args: 'required',
|
|
229
|
+
coerce: Coerce.positiveNumber('--duration'),
|
|
230
|
+
desc: 'Duration in seconds',
|
|
231
|
+
})
|
|
232
|
+
.option({
|
|
233
|
+
long: 'port',
|
|
234
|
+
type: 'number',
|
|
235
|
+
args: 'required',
|
|
236
|
+
coerce: Coerce.port('--port'),
|
|
237
|
+
desc: 'Server port',
|
|
238
|
+
})
|
|
239
|
+
.option({
|
|
240
|
+
long: 'domain',
|
|
241
|
+
type: 'string',
|
|
242
|
+
args: 'required',
|
|
243
|
+
coerce: Coerce.domain('--domain'),
|
|
244
|
+
desc: 'Domain name',
|
|
245
|
+
})
|
|
246
|
+
.option({
|
|
247
|
+
long: 'ip',
|
|
248
|
+
type: 'string',
|
|
249
|
+
args: 'required',
|
|
250
|
+
coerce: Coerce.ip('--ip'),
|
|
251
|
+
desc: 'IP address',
|
|
252
|
+
})
|
|
253
|
+
.option({
|
|
254
|
+
long: 'host',
|
|
255
|
+
type: 'string',
|
|
256
|
+
args: 'required',
|
|
257
|
+
coerce: Coerce.host('--host'),
|
|
258
|
+
desc: 'Host (IP or domain)',
|
|
259
|
+
})
|
|
260
|
+
.option({
|
|
261
|
+
long: 'mode',
|
|
262
|
+
type: 'string',
|
|
263
|
+
args: 'required',
|
|
264
|
+
coerce: Coerce.choice('--mode', ['dev', 'test', 'prod'] as const),
|
|
265
|
+
desc: 'Deploy mode',
|
|
266
|
+
})
|
|
267
|
+
.option({
|
|
268
|
+
long: 'scale',
|
|
269
|
+
type: 'number',
|
|
270
|
+
args: 'required',
|
|
271
|
+
coerce: Coerce.number('--scale'),
|
|
272
|
+
desc: 'Scale factor',
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Default error message format:
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
{name} is expected as {coerce type}, but got {raw}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
You can still override the message via `Coerce.xxx(name, 'custom error message')`.
|
|
283
|
+
|
|
284
|
+
### Built-in Is Helpers
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { isDomain, isIp, isIpv4, isIpv6 } from '@guanghechen/commander'
|
|
288
|
+
|
|
289
|
+
isIpv4('127.0.0.1') // true
|
|
290
|
+
isIpv6('::1') // true
|
|
291
|
+
isIp('2001:db8::1') // true
|
|
292
|
+
isDomain('example.com') // true
|
|
293
|
+
```
|
|
294
|
+
|
|
205
295
|
### Help Examples
|
|
206
296
|
|
|
207
297
|
```typescript
|
|
@@ -219,11 +309,11 @@ await cli.run({ argv: ['--help'], envs: process.env })
|
|
|
219
309
|
|
|
220
310
|
`usage` 是相对当前 command path 的片段,help 中会自动补齐前缀,例如 `mycli build --watch`。
|
|
221
311
|
|
|
222
|
-
`--color` / `--no-color` 仅控制 help 文本的终端着色;
|
|
223
|
-
|
|
312
|
+
`--color` / `--no-color` 仅控制 help 文本的终端着色; `--log-colorful` / `--no-log-colorful` 控制
|
|
313
|
+
`Reporter` 的日志着色。
|
|
224
314
|
|
|
225
|
-
当环境变量 `NO_COLOR` 存在时,help 渲染默认视为 `--no-color
|
|
226
|
-
|
|
315
|
+
当环境变量 `NO_COLOR` 存在时,help 渲染默认视为 `--no-color`;显式传入 `--color`
|
|
316
|
+
可以覆盖这个默认值。
|
|
227
317
|
|
|
228
318
|
## Reference
|
|
229
319
|
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -1186,6 +1186,156 @@ class Command {
|
|
|
1186
1186
|
}
|
|
1187
1187
|
}
|
|
1188
1188
|
|
|
1189
|
+
function isIpv4(rawValue) {
|
|
1190
|
+
const parts = rawValue.split('.');
|
|
1191
|
+
if (parts.length !== 4) {
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
for (const part of parts) {
|
|
1195
|
+
if (part.length < 1 || !/^\d+$/.test(part)) {
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
if (part.length > 1 && part.startsWith('0')) {
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
const value = Number(part);
|
|
1202
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
function countIpv6Segments(part, allowIpv4Tail) {
|
|
1209
|
+
if (!part) {
|
|
1210
|
+
return { count: 0, hasIpv4Tail: false };
|
|
1211
|
+
}
|
|
1212
|
+
const segments = part.split(':');
|
|
1213
|
+
let count = 0;
|
|
1214
|
+
let hasIpv4Tail = false;
|
|
1215
|
+
for (let i = 0; i < segments.length; ++i) {
|
|
1216
|
+
const segment = segments[i];
|
|
1217
|
+
const isLastSegment = i === segments.length - 1;
|
|
1218
|
+
if (!segment) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
if (segment.includes('.')) {
|
|
1222
|
+
if (!allowIpv4Tail || !isLastSegment || hasIpv4Tail || !isIpv4(segment)) {
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
hasIpv4Tail = true;
|
|
1226
|
+
count += 2;
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
count += 1;
|
|
1233
|
+
}
|
|
1234
|
+
return { count, hasIpv4Tail };
|
|
1235
|
+
}
|
|
1236
|
+
function isIpv6(rawValue) {
|
|
1237
|
+
if (!rawValue || !/^[0-9A-Fa-f:.]+$/.test(rawValue)) {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
const doubleColonCount = rawValue.split('::').length - 1;
|
|
1241
|
+
if (doubleColonCount > 1) {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
if (doubleColonCount === 0) {
|
|
1245
|
+
const full = countIpv6Segments(rawValue, true);
|
|
1246
|
+
return full !== null && full.count === 8;
|
|
1247
|
+
}
|
|
1248
|
+
const [left, right] = rawValue.split('::');
|
|
1249
|
+
const leftPart = countIpv6Segments(left, right.length === 0);
|
|
1250
|
+
const rightPart = countIpv6Segments(right, true);
|
|
1251
|
+
if (!leftPart || !rightPart) {
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
1254
|
+
const totalSegments = leftPart.count + rightPart.count;
|
|
1255
|
+
return totalSegments < 8;
|
|
1256
|
+
}
|
|
1257
|
+
function isIp(rawValue) {
|
|
1258
|
+
return isIpv4(rawValue) || isIpv6(rawValue);
|
|
1259
|
+
}
|
|
1260
|
+
function isDomain(rawValue) {
|
|
1261
|
+
if (rawValue.length < 1 || rawValue.length > 253 || rawValue.endsWith('.')) {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
const labels = rawValue.split('.');
|
|
1265
|
+
if (labels.length < 2) {
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
if (labels.some(label => label.length < 1 || label.length > 63)) {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
const labelPattern = /^[A-Za-z0-9-]+$/;
|
|
1272
|
+
if (labels.some(label => !labelPattern.test(label) || label.startsWith('-') || label.endsWith('-'))) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
const topLevelLabel = labels[labels.length - 1];
|
|
1276
|
+
return /[A-Za-z]/.test(topLevelLabel);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
class Coerce {
|
|
1280
|
+
constructor() { }
|
|
1281
|
+
static create(name, expectedType, validator, errorMessage) {
|
|
1282
|
+
return (rawValue) => {
|
|
1283
|
+
const value = Number(rawValue);
|
|
1284
|
+
if (!validator(value)) {
|
|
1285
|
+
throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
|
|
1286
|
+
}
|
|
1287
|
+
return value;
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
static choice(name, values, errorMessage) {
|
|
1291
|
+
return (rawValue) => {
|
|
1292
|
+
if (values.includes(rawValue)) {
|
|
1293
|
+
return rawValue;
|
|
1294
|
+
}
|
|
1295
|
+
throw new Error(errorMessage ?? `${name} is expected as one of [${values.join(', ')}], but got ${rawValue}`);
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
static domain(name, errorMessage) {
|
|
1299
|
+
return (rawValue) => {
|
|
1300
|
+
if (isDomain(rawValue)) {
|
|
1301
|
+
return rawValue;
|
|
1302
|
+
}
|
|
1303
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid domain, but got ${rawValue}`);
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
static host(name, errorMessage) {
|
|
1307
|
+
return (rawValue) => {
|
|
1308
|
+
if (isIp(rawValue) || isDomain(rawValue)) {
|
|
1309
|
+
return rawValue;
|
|
1310
|
+
}
|
|
1311
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid host (IP or domain), but got ${rawValue}`);
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
static integer(name, errorMessage) {
|
|
1315
|
+
return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
|
|
1316
|
+
}
|
|
1317
|
+
static ip(name, errorMessage) {
|
|
1318
|
+
return (rawValue) => {
|
|
1319
|
+
if (isIp(rawValue)) {
|
|
1320
|
+
return rawValue;
|
|
1321
|
+
}
|
|
1322
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid IP address, but got ${rawValue}`);
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
static number(name, errorMessage) {
|
|
1326
|
+
return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
|
|
1327
|
+
}
|
|
1328
|
+
static port(name, errorMessage) {
|
|
1329
|
+
return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
|
|
1330
|
+
}
|
|
1331
|
+
static positiveInteger(name, errorMessage) {
|
|
1332
|
+
return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
|
|
1333
|
+
}
|
|
1334
|
+
static positiveNumber(name, errorMessage) {
|
|
1335
|
+
return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1189
1339
|
function camelToKebabCase(str) {
|
|
1190
1340
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1191
1341
|
}
|
|
@@ -1545,11 +1695,16 @@ class PwshCompletion {
|
|
|
1545
1695
|
}
|
|
1546
1696
|
|
|
1547
1697
|
exports.BashCompletion = BashCompletion;
|
|
1698
|
+
exports.Coerce = Coerce;
|
|
1548
1699
|
exports.Command = Command;
|
|
1549
1700
|
exports.CommanderError = CommanderError;
|
|
1550
1701
|
exports.CompletionCommand = CompletionCommand;
|
|
1551
1702
|
exports.FishCompletion = FishCompletion;
|
|
1552
1703
|
exports.PwshCompletion = PwshCompletion;
|
|
1704
|
+
exports.isDomain = isDomain;
|
|
1705
|
+
exports.isIp = isIp;
|
|
1706
|
+
exports.isIpv4 = isIpv4;
|
|
1707
|
+
exports.isIpv6 = isIpv6;
|
|
1553
1708
|
exports.logColorfulOption = logColorfulOption;
|
|
1554
1709
|
exports.logDateOption = logDateOption;
|
|
1555
1710
|
exports.logLevelOption = logLevelOption;
|
package/lib/esm/index.mjs
CHANGED
|
@@ -1164,6 +1164,156 @@ class Command {
|
|
|
1164
1164
|
}
|
|
1165
1165
|
}
|
|
1166
1166
|
|
|
1167
|
+
function isIpv4(rawValue) {
|
|
1168
|
+
const parts = rawValue.split('.');
|
|
1169
|
+
if (parts.length !== 4) {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
for (const part of parts) {
|
|
1173
|
+
if (part.length < 1 || !/^\d+$/.test(part)) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
if (part.length > 1 && part.startsWith('0')) {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
const value = Number(part);
|
|
1180
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
function countIpv6Segments(part, allowIpv4Tail) {
|
|
1187
|
+
if (!part) {
|
|
1188
|
+
return { count: 0, hasIpv4Tail: false };
|
|
1189
|
+
}
|
|
1190
|
+
const segments = part.split(':');
|
|
1191
|
+
let count = 0;
|
|
1192
|
+
let hasIpv4Tail = false;
|
|
1193
|
+
for (let i = 0; i < segments.length; ++i) {
|
|
1194
|
+
const segment = segments[i];
|
|
1195
|
+
const isLastSegment = i === segments.length - 1;
|
|
1196
|
+
if (!segment) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
if (segment.includes('.')) {
|
|
1200
|
+
if (!allowIpv4Tail || !isLastSegment || hasIpv4Tail || !isIpv4(segment)) {
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
hasIpv4Tail = true;
|
|
1204
|
+
count += 2;
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
count += 1;
|
|
1211
|
+
}
|
|
1212
|
+
return { count, hasIpv4Tail };
|
|
1213
|
+
}
|
|
1214
|
+
function isIpv6(rawValue) {
|
|
1215
|
+
if (!rawValue || !/^[0-9A-Fa-f:.]+$/.test(rawValue)) {
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
const doubleColonCount = rawValue.split('::').length - 1;
|
|
1219
|
+
if (doubleColonCount > 1) {
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
if (doubleColonCount === 0) {
|
|
1223
|
+
const full = countIpv6Segments(rawValue, true);
|
|
1224
|
+
return full !== null && full.count === 8;
|
|
1225
|
+
}
|
|
1226
|
+
const [left, right] = rawValue.split('::');
|
|
1227
|
+
const leftPart = countIpv6Segments(left, right.length === 0);
|
|
1228
|
+
const rightPart = countIpv6Segments(right, true);
|
|
1229
|
+
if (!leftPart || !rightPart) {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
const totalSegments = leftPart.count + rightPart.count;
|
|
1233
|
+
return totalSegments < 8;
|
|
1234
|
+
}
|
|
1235
|
+
function isIp(rawValue) {
|
|
1236
|
+
return isIpv4(rawValue) || isIpv6(rawValue);
|
|
1237
|
+
}
|
|
1238
|
+
function isDomain(rawValue) {
|
|
1239
|
+
if (rawValue.length < 1 || rawValue.length > 253 || rawValue.endsWith('.')) {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
const labels = rawValue.split('.');
|
|
1243
|
+
if (labels.length < 2) {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
if (labels.some(label => label.length < 1 || label.length > 63)) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
const labelPattern = /^[A-Za-z0-9-]+$/;
|
|
1250
|
+
if (labels.some(label => !labelPattern.test(label) || label.startsWith('-') || label.endsWith('-'))) {
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
const topLevelLabel = labels[labels.length - 1];
|
|
1254
|
+
return /[A-Za-z]/.test(topLevelLabel);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
class Coerce {
|
|
1258
|
+
constructor() { }
|
|
1259
|
+
static create(name, expectedType, validator, errorMessage) {
|
|
1260
|
+
return (rawValue) => {
|
|
1261
|
+
const value = Number(rawValue);
|
|
1262
|
+
if (!validator(value)) {
|
|
1263
|
+
throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
|
|
1264
|
+
}
|
|
1265
|
+
return value;
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
static choice(name, values, errorMessage) {
|
|
1269
|
+
return (rawValue) => {
|
|
1270
|
+
if (values.includes(rawValue)) {
|
|
1271
|
+
return rawValue;
|
|
1272
|
+
}
|
|
1273
|
+
throw new Error(errorMessage ?? `${name} is expected as one of [${values.join(', ')}], but got ${rawValue}`);
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
static domain(name, errorMessage) {
|
|
1277
|
+
return (rawValue) => {
|
|
1278
|
+
if (isDomain(rawValue)) {
|
|
1279
|
+
return rawValue;
|
|
1280
|
+
}
|
|
1281
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid domain, but got ${rawValue}`);
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
static host(name, errorMessage) {
|
|
1285
|
+
return (rawValue) => {
|
|
1286
|
+
if (isIp(rawValue) || isDomain(rawValue)) {
|
|
1287
|
+
return rawValue;
|
|
1288
|
+
}
|
|
1289
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid host (IP or domain), but got ${rawValue}`);
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
static integer(name, errorMessage) {
|
|
1293
|
+
return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
|
|
1294
|
+
}
|
|
1295
|
+
static ip(name, errorMessage) {
|
|
1296
|
+
return (rawValue) => {
|
|
1297
|
+
if (isIp(rawValue)) {
|
|
1298
|
+
return rawValue;
|
|
1299
|
+
}
|
|
1300
|
+
throw new Error(errorMessage ?? `${name} is expected as a valid IP address, but got ${rawValue}`);
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
static number(name, errorMessage) {
|
|
1304
|
+
return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
|
|
1305
|
+
}
|
|
1306
|
+
static port(name, errorMessage) {
|
|
1307
|
+
return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
|
|
1308
|
+
}
|
|
1309
|
+
static positiveInteger(name, errorMessage) {
|
|
1310
|
+
return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
|
|
1311
|
+
}
|
|
1312
|
+
static positiveNumber(name, errorMessage) {
|
|
1313
|
+
return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1167
1317
|
function camelToKebabCase(str) {
|
|
1168
1318
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1169
1319
|
}
|
|
@@ -1522,4 +1672,4 @@ class PwshCompletion {
|
|
|
1522
1672
|
}
|
|
1523
1673
|
}
|
|
1524
1674
|
|
|
1525
|
-
export { BashCompletion, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, logColorfulOption, logDateOption, logLevelOption, silentOption };
|
|
1675
|
+
export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, logLevelOption, silentOption };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -223,6 +223,56 @@ interface ICommandParseResult {
|
|
|
223
223
|
/** Raw argument strings */
|
|
224
224
|
rawArgs: string[];
|
|
225
225
|
}
|
|
226
|
+
/** Built-in option resolution result (internal) */
|
|
227
|
+
interface ICommandBuiltinOptionResolved {
|
|
228
|
+
color: boolean;
|
|
229
|
+
logLevel: boolean;
|
|
230
|
+
silent: boolean;
|
|
231
|
+
logDate: boolean;
|
|
232
|
+
logColorful: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** Built-in config resolution result (internal) */
|
|
235
|
+
interface ICommandBuiltinResolved {
|
|
236
|
+
option: ICommandBuiltinOptionResolved;
|
|
237
|
+
command: {
|
|
238
|
+
help: boolean;
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** Subcommand registry entry (internal) */
|
|
242
|
+
interface ISubcommandEntry<TCommand = ICommand> {
|
|
243
|
+
name: string;
|
|
244
|
+
aliases: string[];
|
|
245
|
+
command: TCommand;
|
|
246
|
+
}
|
|
247
|
+
/** Internal route result */
|
|
248
|
+
interface IInternalRouteResult<TCommand = ICommand> {
|
|
249
|
+
chain: TCommand[];
|
|
250
|
+
remaining: string[];
|
|
251
|
+
}
|
|
252
|
+
/** Help option line (internal) */
|
|
253
|
+
interface IHelpOptionLine {
|
|
254
|
+
sig: string;
|
|
255
|
+
desc: string;
|
|
256
|
+
}
|
|
257
|
+
/** Help command line (internal) */
|
|
258
|
+
interface IHelpCommandLine {
|
|
259
|
+
name: string;
|
|
260
|
+
desc: string;
|
|
261
|
+
}
|
|
262
|
+
/** Help example line (internal) */
|
|
263
|
+
interface IHelpExampleLine {
|
|
264
|
+
title: string;
|
|
265
|
+
usage: string;
|
|
266
|
+
desc: string;
|
|
267
|
+
}
|
|
268
|
+
/** Structured help data for rendering (internal) */
|
|
269
|
+
interface IHelpData {
|
|
270
|
+
desc: string;
|
|
271
|
+
usage: string;
|
|
272
|
+
options: IHelpOptionLine[];
|
|
273
|
+
commands: IHelpCommandLine[];
|
|
274
|
+
examples: IHelpExampleLine[];
|
|
275
|
+
}
|
|
226
276
|
/** Error kinds for command parsing */
|
|
227
277
|
type ICommanderErrorKind = 'InvalidOptionFormat' | 'InvalidNegativeOption' | 'NegativeOptionWithValue' | 'NegativeOptionType' | 'UnknownOption' | 'UnknownSubcommand' | 'UnexpectedArgument' | 'MissingValue' | 'InvalidType' | 'UnsupportedShortSyntax' | 'OptionConflict' | 'MissingRequired' | 'InvalidChoice' | 'InvalidBooleanValue' | 'MissingRequiredArgument' | 'TooManyArguments' | 'ConfigurationError';
|
|
228
278
|
/** Commander error with structured information */
|
|
@@ -308,6 +358,30 @@ declare class Command implements ICommand {
|
|
|
308
358
|
getCompletionMeta(): ICompletionMeta;
|
|
309
359
|
}
|
|
310
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Pre-defined coerce factory methods for @guanghechen/commander.
|
|
363
|
+
*
|
|
364
|
+
* @module @guanghechen/commander/coerce
|
|
365
|
+
*/
|
|
366
|
+
declare class Coerce {
|
|
367
|
+
private constructor();
|
|
368
|
+
private static create;
|
|
369
|
+
static choice<TValue extends string>(name: string, values: ReadonlyArray<TValue>, errorMessage?: string): (rawValue: string) => TValue;
|
|
370
|
+
static domain(name: string, errorMessage?: string): (rawValue: string) => string;
|
|
371
|
+
static host(name: string, errorMessage?: string): (rawValue: string) => string;
|
|
372
|
+
static integer(name: string, errorMessage?: string): (rawValue: string) => number;
|
|
373
|
+
static ip(name: string, errorMessage?: string): (rawValue: string) => string;
|
|
374
|
+
static number(name: string, errorMessage?: string): (rawValue: string) => number;
|
|
375
|
+
static port(name: string, errorMessage?: string): (rawValue: string) => number;
|
|
376
|
+
static positiveInteger(name: string, errorMessage?: string): (rawValue: string) => number;
|
|
377
|
+
static positiveNumber(name: string, errorMessage?: string): (rawValue: string) => number;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
declare function isIpv4(rawValue: string): boolean;
|
|
381
|
+
declare function isIpv6(rawValue: string): boolean;
|
|
382
|
+
declare function isIp(rawValue: string): boolean;
|
|
383
|
+
declare function isDomain(rawValue: string): boolean;
|
|
384
|
+
|
|
311
385
|
/**
|
|
312
386
|
* Shell completion generators
|
|
313
387
|
*
|
|
@@ -436,5 +510,5 @@ declare const logColorfulOption: ICommandOptionConfig<boolean>;
|
|
|
436
510
|
*/
|
|
437
511
|
declare const silentOption: ICommandOptionConfig<boolean>;
|
|
438
512
|
|
|
439
|
-
export { BashCompletion, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, logColorfulOption, logDateOption, logLevelOption, silentOption };
|
|
440
|
-
export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandBuiltinCommandConfig, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandConfig, ICommandContext, ICommandExample, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandShiftResult, ICommandToken, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType };
|
|
513
|
+
export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, logLevelOption, silentOption };
|
|
514
|
+
export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandBuiltinCommandConfig, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandBuiltinOptionResolved, ICommandBuiltinResolved, ICommandConfig, ICommandContext, ICommandExample, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandShiftResult, ICommandToken, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType, IHelpCommandLine, IHelpData, IHelpExampleLine, IHelpOptionLine, IInternalRouteResult, ISubcommandEntry };
|