@swipt/cli 0.1.0-nightly.42 → 0.1.0-nightly.44
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/device.mjs +268 -90
- package/ignore.mjs +17 -5
- package/metro.mjs +235 -65
- package/output.mjs +32 -7
- package/package.json +3 -2
- package/project.mjs +442 -103
- package/swipt.mjs +300 -54
package/swipt.mjs
CHANGED
|
@@ -12,9 +12,24 @@ const globalOptions = {
|
|
|
12
12
|
help: { type: "boolean", short: "h", help: "help" },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const deviceOption = {
|
|
15
|
+
const deviceOption = {
|
|
16
|
+
device: {
|
|
17
|
+
type: "string",
|
|
18
|
+
short: "d",
|
|
19
|
+
value: "NAME|URL",
|
|
20
|
+
help: "the device to use (SWIPT_DEVICE)",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
16
23
|
|
|
17
|
-
const durationUnits = {
|
|
24
|
+
const durationUnits = {
|
|
25
|
+
ns: 1e-6,
|
|
26
|
+
us: 1e-3,
|
|
27
|
+
µs: 1e-3,
|
|
28
|
+
ms: 1,
|
|
29
|
+
s: 1000,
|
|
30
|
+
m: 60_000,
|
|
31
|
+
h: 3_600_000,
|
|
32
|
+
};
|
|
18
33
|
|
|
19
34
|
function parseDuration(text, option) {
|
|
20
35
|
if (text === undefined) {
|
|
@@ -28,7 +43,9 @@ function parseDuration(text, option) {
|
|
|
28
43
|
while (rest !== "") {
|
|
29
44
|
const match = /^(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|ms|s|m|h)/.exec(rest);
|
|
30
45
|
if (!match) {
|
|
31
|
-
throw new UsageError(
|
|
46
|
+
throw new UsageError(
|
|
47
|
+
`invalid argument ${JSON.stringify(text)} for ${option}: it is a duration such as 30s or 1m30s`,
|
|
48
|
+
);
|
|
32
49
|
}
|
|
33
50
|
total += Number(match[1]) * durationUnits[match[2]];
|
|
34
51
|
rest = rest.slice(match[0].length);
|
|
@@ -41,7 +58,9 @@ function parseCount(text, option) {
|
|
|
41
58
|
return undefined;
|
|
42
59
|
}
|
|
43
60
|
if (!/^\d+$/.test(text) || Number(text) > 0xffffffff) {
|
|
44
|
-
throw new UsageError(
|
|
61
|
+
throw new UsageError(
|
|
62
|
+
`invalid argument ${JSON.stringify(text)} for ${option}: it is a number`,
|
|
63
|
+
);
|
|
45
64
|
}
|
|
46
65
|
return Number(text);
|
|
47
66
|
}
|
|
@@ -52,11 +71,50 @@ const dataCommands = (operand, files) => {
|
|
|
52
71
|
name: "data",
|
|
53
72
|
short: `Manage the files in the data directory of ${operand === "" ? "the REPL" : "a swiplet"}`,
|
|
54
73
|
commands: [
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
{
|
|
75
|
+
name: "list",
|
|
76
|
+
use: `list ${operand}[PATH]`,
|
|
77
|
+
short: "List data files",
|
|
78
|
+
args: [before, before + 1],
|
|
79
|
+
options: {
|
|
80
|
+
recursive: { type: "boolean", help: "include subdirectories" },
|
|
81
|
+
},
|
|
82
|
+
run: files.list,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "read",
|
|
86
|
+
use: `read ${operand}PATH`,
|
|
87
|
+
short: "Read a data file or directory",
|
|
88
|
+
args: [before + 1, before + 1],
|
|
89
|
+
options: {
|
|
90
|
+
output: { type: "string", value: "PATH", help: "destination" },
|
|
91
|
+
},
|
|
92
|
+
run: files.read,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "write",
|
|
96
|
+
use: `write ${operand}PATH [FILE | -]`,
|
|
97
|
+
short: "Write a data file",
|
|
98
|
+
args: [before + 1, before + 2],
|
|
99
|
+
run: files.write,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "remove",
|
|
103
|
+
use: `remove ${operand}PATH`,
|
|
104
|
+
short: "Remove a data file",
|
|
105
|
+
args: [before + 1, before + 1],
|
|
106
|
+
options: {
|
|
107
|
+
recursive: { type: "boolean", help: "include directory contents" },
|
|
108
|
+
},
|
|
109
|
+
run: files.remove,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "clear",
|
|
113
|
+
use: `clear ${operand}`.trimEnd(),
|
|
114
|
+
short: "Remove every data file, keeping the data directory",
|
|
115
|
+
args: [before, before],
|
|
116
|
+
run: files.clear,
|
|
117
|
+
},
|
|
60
118
|
],
|
|
61
119
|
};
|
|
62
120
|
};
|
|
@@ -86,17 +144,28 @@ const commands = [
|
|
|
86
144
|
{
|
|
87
145
|
name: "source",
|
|
88
146
|
use: "source [SWIPLET...]",
|
|
89
|
-
short:
|
|
147
|
+
short:
|
|
148
|
+
'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
149
|
args: [0, Infinity],
|
|
91
150
|
run: ({ args }) => sourceCommand(process.cwd(), args),
|
|
92
151
|
},
|
|
93
|
-
{
|
|
152
|
+
{
|
|
153
|
+
name: "deploy",
|
|
154
|
+
use: "deploy [PROJECT]",
|
|
155
|
+
short: "Build the project and install it on the device",
|
|
156
|
+
args: [0, 1],
|
|
157
|
+
options: deviceOption,
|
|
158
|
+
run: device.deployProject,
|
|
159
|
+
},
|
|
94
160
|
{
|
|
95
161
|
name: "develop",
|
|
96
162
|
use: "develop [PROJECT]",
|
|
97
163
|
short: "Build, install and follow a swiplet project as it changes",
|
|
98
164
|
args: [0, 1],
|
|
99
|
-
options: {
|
|
165
|
+
options: {
|
|
166
|
+
...deviceOption,
|
|
167
|
+
"no-logs": { type: "boolean", help: "without logs" },
|
|
168
|
+
},
|
|
100
169
|
run: device.develop,
|
|
101
170
|
},
|
|
102
171
|
],
|
|
@@ -105,9 +174,27 @@ const commands = [
|
|
|
105
174
|
name: "device",
|
|
106
175
|
short: "Manage the devices and read their logs",
|
|
107
176
|
commands: [
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
177
|
+
{
|
|
178
|
+
name: "list",
|
|
179
|
+
short: "List the devices, adding the ones found on the local network",
|
|
180
|
+
args: [0, 0],
|
|
181
|
+
run: connection.list,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "add",
|
|
185
|
+
use: "add URL",
|
|
186
|
+
short:
|
|
187
|
+
"Add a device by its URL; needed when it is remote or requires a token",
|
|
188
|
+
args: [1, 1],
|
|
189
|
+
run: connection.add,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "remove",
|
|
193
|
+
use: "remove NAME",
|
|
194
|
+
short: "Forget a device",
|
|
195
|
+
args: [1, 1],
|
|
196
|
+
run: connection.remove,
|
|
197
|
+
},
|
|
111
198
|
{
|
|
112
199
|
name: "logs",
|
|
113
200
|
short: "Print the logs",
|
|
@@ -115,12 +202,25 @@ const commands = [
|
|
|
115
202
|
options: {
|
|
116
203
|
...deviceOption,
|
|
117
204
|
follow: { type: "boolean", help: "follow new entries" },
|
|
118
|
-
tail: {
|
|
205
|
+
tail: {
|
|
206
|
+
type: "string",
|
|
207
|
+
value: "COUNT",
|
|
208
|
+
help: "number of past entries (default 100)",
|
|
209
|
+
},
|
|
119
210
|
since: { type: "string", value: "DURATION", help: "maximum age" },
|
|
120
|
-
level: {
|
|
211
|
+
level: {
|
|
212
|
+
type: "string",
|
|
213
|
+
value: "LEVEL",
|
|
214
|
+
help: "minimum level (default info)",
|
|
215
|
+
},
|
|
121
216
|
swiplet: { type: "string", value: "ID", help: "swiplet ID" },
|
|
122
217
|
},
|
|
123
|
-
parse: options => ({
|
|
218
|
+
parse: (options) => ({
|
|
219
|
+
...options,
|
|
220
|
+
tail: parseCount(options.tail, "--tail"),
|
|
221
|
+
since: parseDuration(options.since, "--since"),
|
|
222
|
+
level: options.level ?? "info",
|
|
223
|
+
}),
|
|
124
224
|
run: device.logs,
|
|
125
225
|
},
|
|
126
226
|
],
|
|
@@ -130,25 +230,74 @@ const commands = [
|
|
|
130
230
|
short: "Manage the swiplets on the device",
|
|
131
231
|
options: deviceOption,
|
|
132
232
|
commands: [
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
233
|
+
{
|
|
234
|
+
name: "list",
|
|
235
|
+
short: "List the swiplets",
|
|
236
|
+
args: [0, 0],
|
|
237
|
+
run: device.list,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "show",
|
|
241
|
+
use: "show ID",
|
|
242
|
+
short: "Show a swiplet",
|
|
243
|
+
args: [1, 1],
|
|
244
|
+
run: device.show,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "install",
|
|
248
|
+
use: "install ARCHIVE",
|
|
249
|
+
short: "Install a swiplet archive",
|
|
250
|
+
args: [1, 1],
|
|
251
|
+
run: device.install,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "remove",
|
|
255
|
+
use: "remove ID",
|
|
256
|
+
short: "Remove a swiplet",
|
|
257
|
+
args: [1, 1],
|
|
258
|
+
run: device.remove,
|
|
259
|
+
},
|
|
137
260
|
{
|
|
138
261
|
name: "call",
|
|
139
262
|
use: "call ID ACTION",
|
|
140
263
|
short: "Call a swiplet action",
|
|
141
264
|
args: [2, 2],
|
|
142
|
-
options: {
|
|
265
|
+
options: {
|
|
266
|
+
input: {
|
|
267
|
+
type: "string",
|
|
268
|
+
value: "JSON",
|
|
269
|
+
help: "input JSON, @FILE or -",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
143
272
|
run: device.call,
|
|
144
273
|
},
|
|
145
274
|
{
|
|
146
275
|
name: "service",
|
|
147
276
|
short: "Manage swiplet services",
|
|
148
277
|
commands: [
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
278
|
+
{
|
|
279
|
+
name: "enable",
|
|
280
|
+
use: "enable ID",
|
|
281
|
+
short: "Turn on a swiplet service",
|
|
282
|
+
args: [1, 1],
|
|
283
|
+
options: { watch: { type: "boolean", help: "watch the state" } },
|
|
284
|
+
run: device.setService(true),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "disable",
|
|
288
|
+
use: "disable ID",
|
|
289
|
+
short: "Turn off a swiplet service",
|
|
290
|
+
args: [1, 1],
|
|
291
|
+
options: { watch: { type: "boolean", help: "watch the state" } },
|
|
292
|
+
run: device.setService(false),
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "watch",
|
|
296
|
+
use: "watch [ID]",
|
|
297
|
+
short: "Watch swiplet services",
|
|
298
|
+
args: [0, 1],
|
|
299
|
+
run: device.serviceWatch,
|
|
300
|
+
},
|
|
152
301
|
],
|
|
153
302
|
},
|
|
154
303
|
dataCommands("ID ", device.swipletData),
|
|
@@ -169,25 +318,56 @@ const commands = [
|
|
|
169
318
|
"FILE, or standard input given as - or no argument, is a program; only its console output and thrown error are printed, and the global args holds its ARGs.",
|
|
170
319
|
'Top-level await is available, and top-level bindings persist across runs until "swipt repl reset".',
|
|
171
320
|
"An image given to show(), or as the completion value of CODE, is saved as a PNG file and printed as its path.",
|
|
172
|
-
'
|
|
173
|
-
'dataDirectory is the directory that "swipt repl data" manages.',
|
|
321
|
+
'The globals are accessibility, input, device, screen, image, dialog, dataDirectory, show and sleep; import declarations and import() load the modules that swiplets get from Swipt; "swipt repl api" declares both.',
|
|
322
|
+
'dataDirectory, which also holds the expo-sqlite databases, is the directory that "swipt repl data" manages.',
|
|
174
323
|
],
|
|
175
324
|
args: [0, Infinity],
|
|
176
325
|
passthrough: device.isProgram,
|
|
177
|
-
options: {
|
|
326
|
+
options: {
|
|
327
|
+
output: {
|
|
328
|
+
type: "string",
|
|
329
|
+
value: "DIRECTORY",
|
|
330
|
+
help: "image directory (default: a new temporary directory)",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
178
333
|
run: device.run,
|
|
179
334
|
},
|
|
180
|
-
{
|
|
181
|
-
|
|
335
|
+
{
|
|
336
|
+
name: "reset",
|
|
337
|
+
short: "Reset the session",
|
|
338
|
+
args: [0, 0],
|
|
339
|
+
run: device.reset,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "api",
|
|
343
|
+
short: "Print the TypeScript declarations of the globals and modules",
|
|
344
|
+
args: [0, 0],
|
|
345
|
+
run: device.api,
|
|
346
|
+
},
|
|
182
347
|
dataCommands("", device.replData),
|
|
183
348
|
],
|
|
184
349
|
},
|
|
185
|
-
{
|
|
350
|
+
{
|
|
351
|
+
name: "version",
|
|
352
|
+
short: "Print the version",
|
|
353
|
+
args: [0, 0],
|
|
354
|
+
run: device.printVersion,
|
|
355
|
+
},
|
|
186
356
|
];
|
|
187
357
|
|
|
188
|
-
const root = {
|
|
358
|
+
const root = {
|
|
359
|
+
name: "swipt",
|
|
360
|
+
short: "Operate a device through Swipt",
|
|
361
|
+
commands,
|
|
362
|
+
};
|
|
189
363
|
|
|
190
|
-
const parseOptions =
|
|
364
|
+
const parseOptions = (options) =>
|
|
365
|
+
Object.fromEntries(
|
|
366
|
+
Object.entries(options ?? {}).map(([name, option]) => [
|
|
367
|
+
name,
|
|
368
|
+
{ type: option.type, ...(option.short ? { short: option.short } : {}) },
|
|
369
|
+
]),
|
|
370
|
+
);
|
|
191
371
|
|
|
192
372
|
function allOptions(command, collected = {}) {
|
|
193
373
|
Object.assign(collected, parseOptions(command.options));
|
|
@@ -199,31 +379,61 @@ function allOptions(command, collected = {}) {
|
|
|
199
379
|
|
|
200
380
|
function usage(trail) {
|
|
201
381
|
const command = trail.at(-1);
|
|
202
|
-
const name = trail.map(it => it.name).join(" ");
|
|
382
|
+
const name = trail.map((it) => it.name).join(" ");
|
|
203
383
|
const lines = [command.short, ""];
|
|
204
384
|
if (command.commands) {
|
|
205
385
|
lines.push(`usage: ${name} COMMAND`, "", "commands:");
|
|
206
|
-
lines.push(
|
|
386
|
+
lines.push(
|
|
387
|
+
...columns(
|
|
388
|
+
command.commands.map((child) => [
|
|
389
|
+
` ${child.use ?? child.name}`,
|
|
390
|
+
child.short,
|
|
391
|
+
]),
|
|
392
|
+
).split("\n"),
|
|
393
|
+
);
|
|
207
394
|
} else {
|
|
208
|
-
lines.push(
|
|
395
|
+
lines.push(
|
|
396
|
+
`usage: ${trail
|
|
397
|
+
.slice(0, -1)
|
|
398
|
+
.map((it) => it.name)
|
|
399
|
+
.join(" ")} ${command.use ?? command.name}`.replace(/ +/g, " "),
|
|
400
|
+
);
|
|
209
401
|
}
|
|
210
402
|
if (command.description) {
|
|
211
403
|
lines.push("", ...command.description);
|
|
212
404
|
}
|
|
213
|
-
const options = {
|
|
405
|
+
const options = {
|
|
406
|
+
...trail.reduce((found, it) => ({ ...found, ...it.options }), {}),
|
|
407
|
+
...globalOptions,
|
|
408
|
+
};
|
|
214
409
|
lines.push("", "options:");
|
|
215
|
-
lines.push(
|
|
410
|
+
lines.push(
|
|
411
|
+
...columns(
|
|
412
|
+
Object.entries(options).map(([option, it]) => [
|
|
413
|
+
` ${it.short ? `-${it.short}, ` : ""}--${option}${it.value ? ` ${it.value}` : ""}`,
|
|
414
|
+
it.help,
|
|
415
|
+
]),
|
|
416
|
+
).split("\n"),
|
|
417
|
+
);
|
|
216
418
|
return lines.join("\n");
|
|
217
419
|
}
|
|
218
420
|
|
|
219
421
|
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");
|
|
422
|
+
const width = Math.max(...rows.map((row) => row[0].length));
|
|
423
|
+
return rows.map((row) => `${row[0].padEnd(width + 2)}${row[1]}`).join("\n");
|
|
222
424
|
}
|
|
223
425
|
|
|
224
426
|
function resolve(argv) {
|
|
225
|
-
const loose = parseArgs({
|
|
226
|
-
|
|
427
|
+
const loose = parseArgs({
|
|
428
|
+
args: argv,
|
|
429
|
+
options: { ...parseOptions(globalOptions), ...allOptions(root) },
|
|
430
|
+
allowPositionals: true,
|
|
431
|
+
strict: false,
|
|
432
|
+
tokens: true,
|
|
433
|
+
});
|
|
434
|
+
const positionals = loose.tokens.filter(
|
|
435
|
+
(token) => token.kind === "positional",
|
|
436
|
+
);
|
|
227
437
|
const trail = [root];
|
|
228
438
|
let consumed = 0;
|
|
229
439
|
let help = positionals[0]?.value === "help";
|
|
@@ -239,31 +449,56 @@ function resolve(argv) {
|
|
|
239
449
|
}
|
|
240
450
|
throw new UsageError("a command is missing");
|
|
241
451
|
}
|
|
242
|
-
const found = current.commands.find(it => it.name === word);
|
|
452
|
+
const found = current.commands.find((it) => it.name === word);
|
|
243
453
|
if (!found) {
|
|
244
|
-
throw new UsageError(
|
|
454
|
+
throw new UsageError(
|
|
455
|
+
`unknown command ${JSON.stringify(word)} for ${JSON.stringify(trail.map((it) => it.name).join(" "))}`,
|
|
456
|
+
);
|
|
245
457
|
}
|
|
246
458
|
trail.push(found);
|
|
247
459
|
consumed++;
|
|
248
460
|
}
|
|
249
461
|
const command = trail.at(-1);
|
|
250
|
-
const options = trail.reduce(
|
|
462
|
+
const options = trail.reduce(
|
|
463
|
+
(found, it) => ({ ...found, ...it.options }),
|
|
464
|
+
{},
|
|
465
|
+
);
|
|
251
466
|
const first = positionals[consumed];
|
|
252
|
-
const verbatim =
|
|
467
|
+
const verbatim =
|
|
468
|
+
first !== undefined && command.passthrough?.(first.value)
|
|
469
|
+
? first.index + 1
|
|
470
|
+
: argv.length;
|
|
253
471
|
let parsed;
|
|
254
472
|
try {
|
|
255
|
-
parsed = parseArgs({
|
|
473
|
+
parsed = parseArgs({
|
|
474
|
+
args: argv.slice(0, verbatim),
|
|
475
|
+
options: { ...parseOptions(globalOptions), ...parseOptions(options) },
|
|
476
|
+
allowPositionals: true,
|
|
477
|
+
strict: true,
|
|
478
|
+
});
|
|
256
479
|
} catch (error) {
|
|
257
480
|
const option = error.message.match(/^Unknown option '(-[^']*)'/)?.[1];
|
|
258
|
-
throw new UsageError(
|
|
481
|
+
throw new UsageError(
|
|
482
|
+
option === undefined
|
|
483
|
+
? error.message
|
|
484
|
+
: `${option} is not an option of ${JSON.stringify(trail.map((it) => it.name).join(" "))}`,
|
|
485
|
+
);
|
|
259
486
|
}
|
|
260
487
|
if (parsed.values.help) {
|
|
261
488
|
help = true;
|
|
262
489
|
}
|
|
263
490
|
const args = [...parsed.positionals.slice(consumed), ...argv.slice(verbatim)];
|
|
264
|
-
if (
|
|
491
|
+
if (
|
|
492
|
+
!help &&
|
|
493
|
+
(args.length < command.args[0] || args.length > command.args[1])
|
|
494
|
+
) {
|
|
265
495
|
const [least, most] = command.args;
|
|
266
|
-
const expected =
|
|
496
|
+
const expected =
|
|
497
|
+
least === most
|
|
498
|
+
? `${least} arg(s)`
|
|
499
|
+
: most === Infinity
|
|
500
|
+
? `at least ${least} arg(s)`
|
|
501
|
+
: `between ${least} and ${most} arg(s)`;
|
|
267
502
|
throw new UsageError(`accepts ${expected}, received ${args.length}`);
|
|
268
503
|
}
|
|
269
504
|
return { trail, help, args, values: parsed.values };
|
|
@@ -288,7 +523,10 @@ function printError(error, target, operation) {
|
|
|
288
523
|
break;
|
|
289
524
|
}
|
|
290
525
|
}
|
|
291
|
-
console.error(
|
|
526
|
+
console.error(
|
|
527
|
+
"error:",
|
|
528
|
+
operation === undefined ? message : `${operation}: ${message}`,
|
|
529
|
+
);
|
|
292
530
|
if (exit === 2) {
|
|
293
531
|
console.error('run "swipt help" for usage');
|
|
294
532
|
}
|
|
@@ -308,8 +546,16 @@ async function main() {
|
|
|
308
546
|
return;
|
|
309
547
|
}
|
|
310
548
|
const command = resolved.trail.at(-1);
|
|
311
|
-
const values = command.parse
|
|
312
|
-
|
|
549
|
+
const values = command.parse
|
|
550
|
+
? command.parse(resolved.values)
|
|
551
|
+
: resolved.values;
|
|
552
|
+
await command.run({
|
|
553
|
+
args: resolved.args,
|
|
554
|
+
options: values,
|
|
555
|
+
target,
|
|
556
|
+
signal: interrupt.signal,
|
|
557
|
+
printError: (error, operation) => printError(error, target, operation),
|
|
558
|
+
});
|
|
313
559
|
} catch (error) {
|
|
314
560
|
if (error instanceof FalseResult) {
|
|
315
561
|
process.exitCode = 1;
|