@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/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 = { device: { type: "string", short: "d", value: "NAME|URL", help: "the device to use (SWIPT_DEVICE)" } };
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 durationUnits = { ns: 1e-6, us: 1e-3, "µs": 1e-3, ms: 1, s: 1000, m: 60_000, h: 3_600_000 };
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(`invalid argument ${JSON.stringify(text)} for ${option}: it is a duration such as 30s or 1m30s`);
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(`invalid argument ${JSON.stringify(text)} for ${option}: it is a number`);
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
- { name: "list", use: `list ${operand}[PATH]`, short: "List data files", args: [before, before + 1], options: { recursive: { type: "boolean", help: "include subdirectories" } }, run: files.list },
56
- { name: "read", use: `read ${operand}PATH`, short: "Read a data file or directory", args: [before + 1, before + 1], options: { output: { type: "string", value: "PATH", help: "destination" } }, run: files.read },
57
- { name: "write", use: `write ${operand}PATH [FILE | -]`, short: "Write a data file", args: [before + 1, before + 2], run: files.write },
58
- { name: "remove", use: `remove ${operand}PATH`, short: "Remove a data file", args: [before + 1, before + 1], options: { recursive: { type: "boolean", help: "include directory contents" } }, run: files.remove },
59
- { name: "clear", use: `clear ${operand}`.trimEnd(), short: "Remove every data file, keeping the data directory", args: [before, before], run: files.clear },
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: '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',
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
- { name: "deploy", use: "deploy [PROJECT]", short: "Build the project and install it on the device", args: [0, 1], options: deviceOption, run: device.deployProject },
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: { ...deviceOption, "no-logs": { type: "boolean", help: "without logs" } },
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
- { name: "list", short: "List the devices, adding the ones found on the local network", args: [0, 0], run: connection.list },
109
- { name: "add", use: "add URL", short: "Add a device by its URL; needed when it is remote or requires a token", args: [1, 1], run: connection.add },
110
- { name: "remove", use: "remove NAME", short: "Forget a device", args: [1, 1], run: connection.remove },
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: { type: "string", value: "COUNT", help: "number of past entries (default 100)" },
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: { type: "string", value: "LEVEL", help: "minimum level (default info)" },
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 => ({ ...options, tail: parseCount(options.tail, "--tail"), since: parseDuration(options.since, "--since"), level: options.level ?? "info" }),
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
- { name: "list", short: "List the swiplets", args: [0, 0], run: device.list },
134
- { name: "show", use: "show ID", short: "Show a swiplet", args: [1, 1], run: device.show },
135
- { name: "install", use: "install ARCHIVE", short: "Install a swiplet archive", args: [1, 1], run: device.install },
136
- { name: "remove", use: "remove ID", short: "Remove a swiplet", args: [1, 1], run: device.remove },
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: { input: { type: "string", value: "JSON", help: "input JSON, @FILE or -" } },
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
- { name: "enable", use: "enable ID", short: "Turn on a swiplet service", args: [1, 1], options: { watch: { type: "boolean", help: "watch the state" } }, run: device.setService(true) },
150
- { name: "disable", use: "disable ID", short: "Turn off a swiplet service", args: [1, 1], options: { watch: { type: "boolean", help: "watch the state" } }, run: device.setService(false) },
151
- { name: "watch", use: "watch [ID]", short: "Watch swiplet services", args: [0, 1], run: device.serviceWatch },
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: { output: { type: "string", value: "DIRECTORY", help: "image directory (default: a new temporary directory)" } },
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
- { name: "reset", short: "Reset the session", args: [0, 0], run: device.reset },
181
- { name: "api", short: "Print the TypeScript declarations of the globals and modules", args: [0, 0], run: device.api },
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
- { name: "version", short: "Print the version", args: [0, 0], run: device.printVersion },
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 = { name: "swipt", short: "Operate a device through Swipt", commands };
434
+ const root = {
435
+ name: "swipt",
436
+ short: "Operate a device through Swipt",
437
+ commands,
438
+ };
189
439
 
190
- const parseOptions = options => Object.fromEntries(Object.entries(options ?? {}).map(([name, option]) => [name, { type: option.type, ...(option.short ? { short: option.short } : {}) }]));
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(...columns(command.commands.map(child => [` ${child.use ?? child.name}`, child.short])).split("\n"));
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(`usage: ${trail.slice(0, -1).map(it => it.name).join(" ")} ${command.use ?? command.name}`.replace(/ +/g, " "));
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 = { ...trail.reduce((found, it) => ({ ...found, ...it.options }), {}), ...globalOptions };
481
+ const options = {
482
+ ...trail.reduce((found, it) => ({ ...found, ...it.options }), {}),
483
+ ...globalOptions,
484
+ };
214
485
  lines.push("", "options:");
215
- lines.push(...columns(Object.entries(options).map(([option, it]) => [` ${it.short ? `-${it.short}, ` : ""}--${option}${it.value ? ` ${it.value}` : ""}`, it.help])).split("\n"));
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({ args: argv, options: { ...parseOptions(globalOptions), ...allOptions(root) }, allowPositionals: true, strict: false, tokens: true });
226
- const positionals = loose.tokens.filter(token => token.kind === "positional");
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(`unknown command ${JSON.stringify(word)} for ${JSON.stringify(trail.map(it => it.name).join(" "))}`);
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((found, it) => ({ ...found, ...it.options }), {});
538
+ const options = trail.reduce(
539
+ (found, it) => ({ ...found, ...it.options }),
540
+ {},
541
+ );
251
542
  const first = positionals[consumed];
252
- const verbatim = first !== undefined && command.passthrough?.(first.value) ? first.index + 1 : argv.length;
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({ args: argv.slice(0, verbatim), options: { ...parseOptions(globalOptions), ...parseOptions(options) }, allowPositionals: true, strict: true });
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(option === undefined ? error.message : `${option} is not an option of ${JSON.stringify(trail.map(it => it.name).join(" "))}`);
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 (!help && (args.length < command.args[0] || args.length > command.args[1])) {
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 = least === most ? `${least} arg(s)` : most === Infinity ? `at least ${least} arg(s)` : `between ${least} and ${most} arg(s)`;
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("error:", operation === undefined ? message : `${operation}: ${message}`);
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 ? command.parse(resolved.values) : resolved.values;
312
- await command.run({ args: resolved.args, options: values, target, signal: interrupt.signal, printError: (error, operation) => printError(error, target, operation) });
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;