@swipt/cli 0.1.0-nightly.43 → 0.1.0-nightly.45
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/client.mjs +103 -30
- package/connection.mjs +86 -26
- package/coredevice.mjs +68 -0
- package/device.mjs +274 -90
- package/ignore.mjs +17 -5
- package/metro.mjs +235 -65
- package/output.mjs +32 -7
- package/package.json +8 -3
- package/project.mjs +394 -106
- package/proto/system_pb.js +1 -1
- package/sideload/trust.js +235 -0
- package/sideload.mjs +649 -0
- package/swipt.mjs +374 -52
package/swipt.mjs
CHANGED
|
@@ -7,14 +7,64 @@ import * as connection from "./connection.mjs";
|
|
|
7
7
|
import * as device from "./device.mjs";
|
|
8
8
|
import { FalseResult } from "./output.mjs";
|
|
9
9
|
import { buildProject, createCommand, sourceCommand } from "./project.mjs";
|
|
10
|
+
import * as sideload from "./sideload.mjs";
|
|
10
11
|
|
|
11
12
|
const globalOptions = {
|
|
12
13
|
help: { type: "boolean", short: "h", help: "help" },
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
const deviceOption = {
|
|
16
|
+
const deviceOption = {
|
|
17
|
+
device: {
|
|
18
|
+
type: "string",
|
|
19
|
+
short: "d",
|
|
20
|
+
value: "NAME|URL",
|
|
21
|
+
help: "the device to use (SWIPT_DEVICE)",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const sideloadOptions = {
|
|
26
|
+
device: {
|
|
27
|
+
type: "string",
|
|
28
|
+
short: "d",
|
|
29
|
+
value: "NAME|UDID",
|
|
30
|
+
help: "the paired iOS device (SWIPT_DEVICE)",
|
|
31
|
+
},
|
|
32
|
+
team: {
|
|
33
|
+
type: "string",
|
|
34
|
+
value: "ID",
|
|
35
|
+
help: "the team ID (default: the team of the installed Swipt)",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
16
38
|
|
|
17
|
-
const
|
|
39
|
+
const passcodeOptions = {
|
|
40
|
+
passcode: {
|
|
41
|
+
type: "string",
|
|
42
|
+
value: "CODE",
|
|
43
|
+
help: "the device passcode, for unlocking it (SWIPT_PASSCODE)",
|
|
44
|
+
},
|
|
45
|
+
"passcode-type": {
|
|
46
|
+
type: "string",
|
|
47
|
+
value: "TYPE",
|
|
48
|
+
help: "pin for 4 or 6 digits, numeric or alphanumeric for a custom code (default pin)",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const nightlyOption = {
|
|
53
|
+
nightly: {
|
|
54
|
+
type: "boolean",
|
|
55
|
+
help: "the nightly build",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const durationUnits = {
|
|
60
|
+
ns: 1e-6,
|
|
61
|
+
us: 1e-3,
|
|
62
|
+
µs: 1e-3,
|
|
63
|
+
ms: 1,
|
|
64
|
+
s: 1000,
|
|
65
|
+
m: 60_000,
|
|
66
|
+
h: 3_600_000,
|
|
67
|
+
};
|
|
18
68
|
|
|
19
69
|
function parseDuration(text, option) {
|
|
20
70
|
if (text === undefined) {
|
|
@@ -28,7 +78,9 @@ function parseDuration(text, option) {
|
|
|
28
78
|
while (rest !== "") {
|
|
29
79
|
const match = /^(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|ms|s|m|h)/.exec(rest);
|
|
30
80
|
if (!match) {
|
|
31
|
-
throw new UsageError(
|
|
81
|
+
throw new UsageError(
|
|
82
|
+
`invalid argument ${JSON.stringify(text)} for ${option}: it is a duration such as 30s or 1m30s`,
|
|
83
|
+
);
|
|
32
84
|
}
|
|
33
85
|
total += Number(match[1]) * durationUnits[match[2]];
|
|
34
86
|
rest = rest.slice(match[0].length);
|
|
@@ -41,7 +93,9 @@ function parseCount(text, option) {
|
|
|
41
93
|
return undefined;
|
|
42
94
|
}
|
|
43
95
|
if (!/^\d+$/.test(text) || Number(text) > 0xffffffff) {
|
|
44
|
-
throw new UsageError(
|
|
96
|
+
throw new UsageError(
|
|
97
|
+
`invalid argument ${JSON.stringify(text)} for ${option}: it is a number`,
|
|
98
|
+
);
|
|
45
99
|
}
|
|
46
100
|
return Number(text);
|
|
47
101
|
}
|
|
@@ -52,11 +106,50 @@ const dataCommands = (operand, files) => {
|
|
|
52
106
|
name: "data",
|
|
53
107
|
short: `Manage the files in the data directory of ${operand === "" ? "the REPL" : "a swiplet"}`,
|
|
54
108
|
commands: [
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
{
|
|
110
|
+
name: "list",
|
|
111
|
+
use: `list ${operand}[PATH]`,
|
|
112
|
+
short: "List data files",
|
|
113
|
+
args: [before, before + 1],
|
|
114
|
+
options: {
|
|
115
|
+
recursive: { type: "boolean", help: "include subdirectories" },
|
|
116
|
+
},
|
|
117
|
+
run: files.list,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "read",
|
|
121
|
+
use: `read ${operand}PATH`,
|
|
122
|
+
short: "Read a data file or directory",
|
|
123
|
+
args: [before + 1, before + 1],
|
|
124
|
+
options: {
|
|
125
|
+
output: { type: "string", value: "PATH", help: "destination" },
|
|
126
|
+
},
|
|
127
|
+
run: files.read,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "write",
|
|
131
|
+
use: `write ${operand}PATH [FILE | -]`,
|
|
132
|
+
short: "Write a data file",
|
|
133
|
+
args: [before + 1, before + 2],
|
|
134
|
+
run: files.write,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "remove",
|
|
138
|
+
use: `remove ${operand}PATH`,
|
|
139
|
+
short: "Remove a data file",
|
|
140
|
+
args: [before + 1, before + 1],
|
|
141
|
+
options: {
|
|
142
|
+
recursive: { type: "boolean", help: "include directory contents" },
|
|
143
|
+
},
|
|
144
|
+
run: files.remove,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "clear",
|
|
148
|
+
use: `clear ${operand}`.trimEnd(),
|
|
149
|
+
short: "Remove every data file, keeping the data directory",
|
|
150
|
+
args: [before, before],
|
|
151
|
+
run: files.clear,
|
|
152
|
+
},
|
|
60
153
|
],
|
|
61
154
|
};
|
|
62
155
|
};
|
|
@@ -86,17 +179,28 @@ const commands = [
|
|
|
86
179
|
{
|
|
87
180
|
name: "source",
|
|
88
181
|
use: "source [SWIPLET...]",
|
|
89
|
-
short:
|
|
182
|
+
short:
|
|
183
|
+
'Gather the built swiplets of this directory\'s workspaces, or the given ones, into dist/ with a swiplets.json described by "source" in package.json',
|
|
90
184
|
args: [0, Infinity],
|
|
91
185
|
run: ({ args }) => sourceCommand(process.cwd(), args),
|
|
92
186
|
},
|
|
93
|
-
{
|
|
187
|
+
{
|
|
188
|
+
name: "deploy",
|
|
189
|
+
use: "deploy [PROJECT]",
|
|
190
|
+
short: "Build the project and install it on the device",
|
|
191
|
+
args: [0, 1],
|
|
192
|
+
options: deviceOption,
|
|
193
|
+
run: device.deployProject,
|
|
194
|
+
},
|
|
94
195
|
{
|
|
95
196
|
name: "develop",
|
|
96
197
|
use: "develop [PROJECT]",
|
|
97
198
|
short: "Build, install and follow a swiplet project as it changes",
|
|
98
199
|
args: [0, 1],
|
|
99
|
-
options: {
|
|
200
|
+
options: {
|
|
201
|
+
...deviceOption,
|
|
202
|
+
"no-logs": { type: "boolean", help: "without logs" },
|
|
203
|
+
},
|
|
100
204
|
run: device.develop,
|
|
101
205
|
},
|
|
102
206
|
],
|
|
@@ -105,9 +209,34 @@ const commands = [
|
|
|
105
209
|
name: "device",
|
|
106
210
|
short: "Manage the devices and read their logs",
|
|
107
211
|
commands: [
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
212
|
+
{
|
|
213
|
+
name: "list",
|
|
214
|
+
short: "List the devices, adding the ones found on the local network",
|
|
215
|
+
args: [0, 0],
|
|
216
|
+
run: connection.list,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "add",
|
|
220
|
+
use: "add URL",
|
|
221
|
+
short:
|
|
222
|
+
"Add a device by its URL; needed when it is remote or requires a token",
|
|
223
|
+
args: [1, 1],
|
|
224
|
+
run: connection.add,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: "remove",
|
|
228
|
+
use: "remove NAME",
|
|
229
|
+
short: "Forget a device",
|
|
230
|
+
args: [1, 1],
|
|
231
|
+
run: connection.remove,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "stop",
|
|
235
|
+
short: "Stop the Swipt server",
|
|
236
|
+
args: [0, 0],
|
|
237
|
+
options: deviceOption,
|
|
238
|
+
run: device.stop,
|
|
239
|
+
},
|
|
111
240
|
{
|
|
112
241
|
name: "logs",
|
|
113
242
|
short: "Print the logs",
|
|
@@ -115,12 +244,25 @@ const commands = [
|
|
|
115
244
|
options: {
|
|
116
245
|
...deviceOption,
|
|
117
246
|
follow: { type: "boolean", help: "follow new entries" },
|
|
118
|
-
tail: {
|
|
247
|
+
tail: {
|
|
248
|
+
type: "string",
|
|
249
|
+
value: "COUNT",
|
|
250
|
+
help: "number of past entries (default 100)",
|
|
251
|
+
},
|
|
119
252
|
since: { type: "string", value: "DURATION", help: "maximum age" },
|
|
120
|
-
level: {
|
|
253
|
+
level: {
|
|
254
|
+
type: "string",
|
|
255
|
+
value: "LEVEL",
|
|
256
|
+
help: "minimum level (default info)",
|
|
257
|
+
},
|
|
121
258
|
swiplet: { type: "string", value: "ID", help: "swiplet ID" },
|
|
122
259
|
},
|
|
123
|
-
parse: options => ({
|
|
260
|
+
parse: (options) => ({
|
|
261
|
+
...options,
|
|
262
|
+
tail: parseCount(options.tail, "--tail"),
|
|
263
|
+
since: parseDuration(options.since, "--since"),
|
|
264
|
+
level: options.level ?? "info",
|
|
265
|
+
}),
|
|
124
266
|
run: device.logs,
|
|
125
267
|
},
|
|
126
268
|
],
|
|
@@ -130,25 +272,74 @@ const commands = [
|
|
|
130
272
|
short: "Manage the swiplets on the device",
|
|
131
273
|
options: deviceOption,
|
|
132
274
|
commands: [
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
275
|
+
{
|
|
276
|
+
name: "list",
|
|
277
|
+
short: "List the swiplets",
|
|
278
|
+
args: [0, 0],
|
|
279
|
+
run: device.list,
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "show",
|
|
283
|
+
use: "show ID",
|
|
284
|
+
short: "Show a swiplet",
|
|
285
|
+
args: [1, 1],
|
|
286
|
+
run: device.show,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "install",
|
|
290
|
+
use: "install ARCHIVE",
|
|
291
|
+
short: "Install a swiplet archive",
|
|
292
|
+
args: [1, 1],
|
|
293
|
+
run: device.install,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "remove",
|
|
297
|
+
use: "remove ID",
|
|
298
|
+
short: "Remove a swiplet",
|
|
299
|
+
args: [1, 1],
|
|
300
|
+
run: device.remove,
|
|
301
|
+
},
|
|
137
302
|
{
|
|
138
303
|
name: "call",
|
|
139
304
|
use: "call ID ACTION",
|
|
140
305
|
short: "Call a swiplet action",
|
|
141
306
|
args: [2, 2],
|
|
142
|
-
options: {
|
|
307
|
+
options: {
|
|
308
|
+
input: {
|
|
309
|
+
type: "string",
|
|
310
|
+
value: "JSON",
|
|
311
|
+
help: "input JSON, @FILE or -",
|
|
312
|
+
},
|
|
313
|
+
},
|
|
143
314
|
run: device.call,
|
|
144
315
|
},
|
|
145
316
|
{
|
|
146
317
|
name: "service",
|
|
147
318
|
short: "Manage swiplet services",
|
|
148
319
|
commands: [
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
320
|
+
{
|
|
321
|
+
name: "enable",
|
|
322
|
+
use: "enable ID",
|
|
323
|
+
short: "Turn on a swiplet service",
|
|
324
|
+
args: [1, 1],
|
|
325
|
+
options: { watch: { type: "boolean", help: "watch the state" } },
|
|
326
|
+
run: device.setService(true),
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "disable",
|
|
330
|
+
use: "disable ID",
|
|
331
|
+
short: "Turn off a swiplet service",
|
|
332
|
+
args: [1, 1],
|
|
333
|
+
options: { watch: { type: "boolean", help: "watch the state" } },
|
|
334
|
+
run: device.setService(false),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "watch",
|
|
338
|
+
use: "watch [ID]",
|
|
339
|
+
short: "Watch swiplet services",
|
|
340
|
+
args: [0, 1],
|
|
341
|
+
run: device.serviceWatch,
|
|
342
|
+
},
|
|
152
343
|
],
|
|
153
344
|
},
|
|
154
345
|
dataCommands("ID ", device.swipletData),
|
|
@@ -174,20 +365,85 @@ const commands = [
|
|
|
174
365
|
],
|
|
175
366
|
args: [0, Infinity],
|
|
176
367
|
passthrough: device.isProgram,
|
|
177
|
-
options: {
|
|
368
|
+
options: {
|
|
369
|
+
output: {
|
|
370
|
+
type: "string",
|
|
371
|
+
value: "DIRECTORY",
|
|
372
|
+
help: "image directory (default: a new temporary directory)",
|
|
373
|
+
},
|
|
374
|
+
},
|
|
178
375
|
run: device.run,
|
|
179
376
|
},
|
|
180
|
-
{
|
|
181
|
-
|
|
377
|
+
{
|
|
378
|
+
name: "reset",
|
|
379
|
+
short: "Reset the session",
|
|
380
|
+
args: [0, 0],
|
|
381
|
+
run: device.reset,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "api",
|
|
385
|
+
short: "Print the TypeScript declarations of the globals and modules",
|
|
386
|
+
args: [0, 0],
|
|
387
|
+
run: device.api,
|
|
388
|
+
},
|
|
182
389
|
dataCommands("", device.replData),
|
|
183
390
|
],
|
|
184
391
|
},
|
|
185
|
-
{
|
|
392
|
+
{
|
|
393
|
+
name: "sideload",
|
|
394
|
+
short: "Install the Swipt app on a paired device",
|
|
395
|
+
options: sideloadOptions,
|
|
396
|
+
commands: [
|
|
397
|
+
{
|
|
398
|
+
name: "install",
|
|
399
|
+
short: "Install the latest release",
|
|
400
|
+
args: [0, 0],
|
|
401
|
+
options: { ...passcodeOptions, ...nightlyOption },
|
|
402
|
+
run: sideload.install,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "update",
|
|
406
|
+
short: "Install the latest release if it is newer",
|
|
407
|
+
args: [0, 0],
|
|
408
|
+
options: { ...passcodeOptions, ...nightlyOption },
|
|
409
|
+
run: sideload.update,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "start",
|
|
413
|
+
short: "Open the app, trusting its developer if needed",
|
|
414
|
+
args: [0, 0],
|
|
415
|
+
options: passcodeOptions,
|
|
416
|
+
run: sideload.start,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "uninstall",
|
|
420
|
+
short: "Remove the app",
|
|
421
|
+
args: [0, 0],
|
|
422
|
+
run: sideload.uninstall,
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "version",
|
|
428
|
+
short: "Print the version",
|
|
429
|
+
args: [0, 0],
|
|
430
|
+
run: device.printVersion,
|
|
431
|
+
},
|
|
186
432
|
];
|
|
187
433
|
|
|
188
|
-
const root = {
|
|
434
|
+
const root = {
|
|
435
|
+
name: "swipt",
|
|
436
|
+
short: "Operate a device through Swipt",
|
|
437
|
+
commands,
|
|
438
|
+
};
|
|
189
439
|
|
|
190
|
-
const parseOptions =
|
|
440
|
+
const parseOptions = (options) =>
|
|
441
|
+
Object.fromEntries(
|
|
442
|
+
Object.entries(options ?? {}).map(([name, option]) => [
|
|
443
|
+
name,
|
|
444
|
+
{ type: option.type, ...(option.short ? { short: option.short } : {}) },
|
|
445
|
+
]),
|
|
446
|
+
);
|
|
191
447
|
|
|
192
448
|
function allOptions(command, collected = {}) {
|
|
193
449
|
Object.assign(collected, parseOptions(command.options));
|
|
@@ -199,31 +455,61 @@ function allOptions(command, collected = {}) {
|
|
|
199
455
|
|
|
200
456
|
function usage(trail) {
|
|
201
457
|
const command = trail.at(-1);
|
|
202
|
-
const name = trail.map(it => it.name).join(" ");
|
|
458
|
+
const name = trail.map((it) => it.name).join(" ");
|
|
203
459
|
const lines = [command.short, ""];
|
|
204
460
|
if (command.commands) {
|
|
205
461
|
lines.push(`usage: ${name} COMMAND`, "", "commands:");
|
|
206
|
-
lines.push(
|
|
462
|
+
lines.push(
|
|
463
|
+
...columns(
|
|
464
|
+
command.commands.map((child) => [
|
|
465
|
+
` ${child.use ?? child.name}`,
|
|
466
|
+
child.short,
|
|
467
|
+
]),
|
|
468
|
+
).split("\n"),
|
|
469
|
+
);
|
|
207
470
|
} else {
|
|
208
|
-
lines.push(
|
|
471
|
+
lines.push(
|
|
472
|
+
`usage: ${trail
|
|
473
|
+
.slice(0, -1)
|
|
474
|
+
.map((it) => it.name)
|
|
475
|
+
.join(" ")} ${command.use ?? command.name}`.replace(/ +/g, " "),
|
|
476
|
+
);
|
|
209
477
|
}
|
|
210
478
|
if (command.description) {
|
|
211
479
|
lines.push("", ...command.description);
|
|
212
480
|
}
|
|
213
|
-
const options = {
|
|
481
|
+
const options = {
|
|
482
|
+
...trail.reduce((found, it) => ({ ...found, ...it.options }), {}),
|
|
483
|
+
...globalOptions,
|
|
484
|
+
};
|
|
214
485
|
lines.push("", "options:");
|
|
215
|
-
lines.push(
|
|
486
|
+
lines.push(
|
|
487
|
+
...columns(
|
|
488
|
+
Object.entries(options).map(([option, it]) => [
|
|
489
|
+
` ${it.short ? `-${it.short}, ` : ""}--${option}${it.value ? ` ${it.value}` : ""}`,
|
|
490
|
+
it.help,
|
|
491
|
+
]),
|
|
492
|
+
).split("\n"),
|
|
493
|
+
);
|
|
216
494
|
return lines.join("\n");
|
|
217
495
|
}
|
|
218
496
|
|
|
219
497
|
function columns(rows) {
|
|
220
|
-
const width = Math.max(...rows.map(row => row[0].length));
|
|
221
|
-
return rows.map(row => `${row[0].padEnd(width + 2)}${row[1]}`).join("\n");
|
|
498
|
+
const width = Math.max(...rows.map((row) => row[0].length));
|
|
499
|
+
return rows.map((row) => `${row[0].padEnd(width + 2)}${row[1]}`).join("\n");
|
|
222
500
|
}
|
|
223
501
|
|
|
224
502
|
function resolve(argv) {
|
|
225
|
-
const loose = parseArgs({
|
|
226
|
-
|
|
503
|
+
const loose = parseArgs({
|
|
504
|
+
args: argv,
|
|
505
|
+
options: { ...parseOptions(globalOptions), ...allOptions(root) },
|
|
506
|
+
allowPositionals: true,
|
|
507
|
+
strict: false,
|
|
508
|
+
tokens: true,
|
|
509
|
+
});
|
|
510
|
+
const positionals = loose.tokens.filter(
|
|
511
|
+
(token) => token.kind === "positional",
|
|
512
|
+
);
|
|
227
513
|
const trail = [root];
|
|
228
514
|
let consumed = 0;
|
|
229
515
|
let help = positionals[0]?.value === "help";
|
|
@@ -239,31 +525,56 @@ function resolve(argv) {
|
|
|
239
525
|
}
|
|
240
526
|
throw new UsageError("a command is missing");
|
|
241
527
|
}
|
|
242
|
-
const found = current.commands.find(it => it.name === word);
|
|
528
|
+
const found = current.commands.find((it) => it.name === word);
|
|
243
529
|
if (!found) {
|
|
244
|
-
throw new UsageError(
|
|
530
|
+
throw new UsageError(
|
|
531
|
+
`unknown command ${JSON.stringify(word)} for ${JSON.stringify(trail.map((it) => it.name).join(" "))}`,
|
|
532
|
+
);
|
|
245
533
|
}
|
|
246
534
|
trail.push(found);
|
|
247
535
|
consumed++;
|
|
248
536
|
}
|
|
249
537
|
const command = trail.at(-1);
|
|
250
|
-
const options = trail.reduce(
|
|
538
|
+
const options = trail.reduce(
|
|
539
|
+
(found, it) => ({ ...found, ...it.options }),
|
|
540
|
+
{},
|
|
541
|
+
);
|
|
251
542
|
const first = positionals[consumed];
|
|
252
|
-
const verbatim =
|
|
543
|
+
const verbatim =
|
|
544
|
+
first !== undefined && command.passthrough?.(first.value)
|
|
545
|
+
? first.index + 1
|
|
546
|
+
: argv.length;
|
|
253
547
|
let parsed;
|
|
254
548
|
try {
|
|
255
|
-
parsed = parseArgs({
|
|
549
|
+
parsed = parseArgs({
|
|
550
|
+
args: argv.slice(0, verbatim),
|
|
551
|
+
options: { ...parseOptions(globalOptions), ...parseOptions(options) },
|
|
552
|
+
allowPositionals: true,
|
|
553
|
+
strict: true,
|
|
554
|
+
});
|
|
256
555
|
} catch (error) {
|
|
257
556
|
const option = error.message.match(/^Unknown option '(-[^']*)'/)?.[1];
|
|
258
|
-
throw new UsageError(
|
|
557
|
+
throw new UsageError(
|
|
558
|
+
option === undefined
|
|
559
|
+
? error.message
|
|
560
|
+
: `${option} is not an option of ${JSON.stringify(trail.map((it) => it.name).join(" "))}`,
|
|
561
|
+
);
|
|
259
562
|
}
|
|
260
563
|
if (parsed.values.help) {
|
|
261
564
|
help = true;
|
|
262
565
|
}
|
|
263
566
|
const args = [...parsed.positionals.slice(consumed), ...argv.slice(verbatim)];
|
|
264
|
-
if (
|
|
567
|
+
if (
|
|
568
|
+
!help &&
|
|
569
|
+
(args.length < command.args[0] || args.length > command.args[1])
|
|
570
|
+
) {
|
|
265
571
|
const [least, most] = command.args;
|
|
266
|
-
const expected =
|
|
572
|
+
const expected =
|
|
573
|
+
least === most
|
|
574
|
+
? `${least} arg(s)`
|
|
575
|
+
: most === Infinity
|
|
576
|
+
? `at least ${least} arg(s)`
|
|
577
|
+
: `between ${least} and ${most} arg(s)`;
|
|
267
578
|
throw new UsageError(`accepts ${expected}, received ${args.length}`);
|
|
268
579
|
}
|
|
269
580
|
return { trail, help, args, values: parsed.values };
|
|
@@ -288,7 +599,10 @@ function printError(error, target, operation) {
|
|
|
288
599
|
break;
|
|
289
600
|
}
|
|
290
601
|
}
|
|
291
|
-
console.error(
|
|
602
|
+
console.error(
|
|
603
|
+
"error:",
|
|
604
|
+
operation === undefined ? message : `${operation}: ${message}`,
|
|
605
|
+
);
|
|
292
606
|
if (exit === 2) {
|
|
293
607
|
console.error('run "swipt help" for usage');
|
|
294
608
|
}
|
|
@@ -308,8 +622,16 @@ async function main() {
|
|
|
308
622
|
return;
|
|
309
623
|
}
|
|
310
624
|
const command = resolved.trail.at(-1);
|
|
311
|
-
const values = command.parse
|
|
312
|
-
|
|
625
|
+
const values = command.parse
|
|
626
|
+
? command.parse(resolved.values)
|
|
627
|
+
: resolved.values;
|
|
628
|
+
await command.run({
|
|
629
|
+
args: resolved.args,
|
|
630
|
+
options: values,
|
|
631
|
+
target,
|
|
632
|
+
signal: interrupt.signal,
|
|
633
|
+
printError: (error, operation) => printError(error, target, operation),
|
|
634
|
+
});
|
|
313
635
|
} catch (error) {
|
|
314
636
|
if (error instanceof FalseResult) {
|
|
315
637
|
process.exitCode = 1;
|