@swipt/cli 0.1.0-nightly.33 → 0.1.0-nightly.35

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 CHANGED
@@ -1,3 +1,5 @@
1
+ import dns from "node:dns";
2
+
1
3
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
4
 
3
5
  import { LogService } from "./proto/logs_pb.js";
@@ -9,6 +11,8 @@ const largestMessage = 128 << 20;
9
11
  const contentType = "application/grpc-web+proto";
10
12
  const webSocketSubprotocol = "grpc-websockets";
11
13
 
14
+ dns.setDefaultResultOrder("ipv4first");
15
+
12
16
  export const Code = {
13
17
  Canceled: 1,
14
18
  Unknown: 2,
@@ -111,23 +115,20 @@ function checkStatus(headers) {
111
115
  return true;
112
116
  }
113
117
 
114
- function requestHeaders(connection, timeoutMs) {
118
+ function requestHeaders(connection) {
115
119
  const headers = { "content-type": contentType, "x-grpc-web": "1" };
116
120
  if (connection.token !== "") {
117
121
  headers.authorization = `Bearer ${connection.token}`;
118
122
  }
119
- if (timeoutMs > 0) {
120
- headers["grpc-timeout"] = `${Math.ceil(timeoutMs)}m`;
121
- }
122
123
  return headers;
123
124
  }
124
125
 
125
- async function* invokeHTTP(connection, method, input, { signal, timeoutMs }) {
126
+ async function* invokeHTTP(connection, method, input, { signal }) {
126
127
  let response;
127
128
  try {
128
129
  response = await fetch(`${connection.base}/${method.parent.typeName}/${method.name}`, {
129
130
  method: "POST",
130
- headers: requestHeaders(connection, timeoutMs),
131
+ headers: requestHeaders(connection),
131
132
  body: frame(method.input, input),
132
133
  signal,
133
134
  });
@@ -164,7 +165,7 @@ async function* invokeHTTP(connection, method, input, { signal, timeoutMs }) {
164
165
  }
165
166
  }
166
167
 
167
- async function* invokeWebSocket(connection, method, input, { signal, timeoutMs }) {
168
+ async function* invokeWebSocket(connection, method, input, { signal }) {
168
169
  const socket = new WebSocket(`${connection.webSocketBase}/${method.parent.typeName}/${method.name}`, [webSocketSubprotocol]);
169
170
  socket.binaryType = "arraybuffer";
170
171
  const reader = new FrameReader(method.output);
@@ -219,7 +220,7 @@ async function* invokeWebSocket(connection, method, input, { signal, timeoutMs }
219
220
  signal?.addEventListener("abort", abort, { once: true });
220
221
  try {
221
222
  await opened;
222
- const headers = requestHeaders(connection, timeoutMs);
223
+ const headers = requestHeaders(connection);
223
224
  socket.send(Buffer.from(`${Object.entries(headers).map(([key, value]) => `${key}: ${value}\r\n`).join("")}`));
224
225
  const sender = (async () => {
225
226
  try {
@@ -277,30 +278,15 @@ function bind(service, connection) {
277
278
  for (const method of service.methods) {
278
279
  const streamsInput = method.methodKind === "client_streaming" || method.methodKind === "bidi_streaming";
279
280
  client[method.localName] = (input, options = {}) => {
280
- const call = (streamsInput ? invokeWebSocket : invokeHTTP)(connection, method, input, { signal: options.signal, timeoutMs: options.timeoutMs ?? 0 });
281
+ const call = (streamsInput ? invokeWebSocket : invokeHTTP)(connection, method, input, { signal: options.signal });
281
282
  return method.methodKind === "unary" || method.methodKind === "client_streaming" ? single(call) : call;
282
283
  };
283
284
  }
284
285
  return client;
285
286
  }
286
287
 
287
- export function open({ host, token }) {
288
- host ||= process.env.SWIPT_HOST ?? "";
289
- token ||= process.env.SWIPT_TOKEN ?? "";
290
- if (host === "") {
291
- throw new UsageError('give --host or set SWIPT_HOST; "swipt devices" lists the hosts on the local network');
292
- }
293
- let parsed;
294
- try {
295
- parsed = new URL(host.includes("://") ? host : `http://${host}`);
296
- } catch {
297
- throw new UsageError(`--host is not a URL: ${host}`);
298
- }
299
- if (parsed.hostname === "" || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
300
- throw new UsageError(`--host is an http or https URL: ${host}`);
301
- }
302
- const base = `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
303
- const connection = { token, base, webSocketBase: base.replace(/^http/, "ws") };
288
+ export function open({ url, token }) {
289
+ const connection = { token, base: url, webSocketBase: url.replace(/^http/, "ws") };
304
290
  return {
305
291
  system: bind(SystemService, connection),
306
292
  logs: bind(LogService, connection),
package/connection.mjs ADDED
@@ -0,0 +1,227 @@
1
+ import dgram from "node:dgram";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { setTimeout as sleep } from "node:timers/promises";
6
+
7
+ import Bonjour from "bonjour-service";
8
+
9
+ import { Code, RpcError, UsageError, open } from "./client.mjs";
10
+ import { columns } from "./output.mjs";
11
+
12
+ const browseLimit = 1500;
13
+ const browseRequery = 300;
14
+
15
+ const storeFile = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "swipt", "devices.json");
16
+
17
+ function load() {
18
+ let text;
19
+ try {
20
+ text = fs.readFileSync(storeFile, "utf8");
21
+ } catch (error) {
22
+ if (error.code === "ENOENT") {
23
+ return {};
24
+ }
25
+ throw error;
26
+ }
27
+ return JSON.parse(text);
28
+ }
29
+
30
+ function save(devices) {
31
+ fs.mkdirSync(path.dirname(storeFile), { recursive: true, mode: 0o700 });
32
+ fs.writeFileSync(storeFile, `${JSON.stringify(devices, null, 2)}\n`, { mode: 0o600 });
33
+ }
34
+
35
+ function label(name) {
36
+ const slug = [...name.replaceAll(/['’]/g, "")]
37
+ .map(character => (/[A-Za-z0-9]/.test(character) ? character : "-"))
38
+ .join("")
39
+ .split("-")
40
+ .filter(part => part !== "")
41
+ .join("-");
42
+ return slug === "" ? name : slug;
43
+ }
44
+
45
+ const sameName = (a, b) => a.toLowerCase() === b.toLowerCase();
46
+
47
+ const storedName = (stored, name) => Object.keys(stored).find(it => sameName(it, name));
48
+
49
+ function parseURL(text) {
50
+ let parsed;
51
+ try {
52
+ parsed = new URL(text.includes("://") ? text : `http://${text}`);
53
+ } catch {
54
+ throw new UsageError(`${text} is not a URL`);
55
+ }
56
+ if (parsed.hostname === "" || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
57
+ throw new UsageError(`${text} is not an http or https URL`);
58
+ }
59
+ let token;
60
+ try {
61
+ token = decodeURIComponent(parsed.username);
62
+ } catch {
63
+ token = parsed.username;
64
+ }
65
+ return { url: `${parsed.protocol}//${parsed.host}${parsed.pathname.replace(/\/+$/, "")}`, token };
66
+ }
67
+
68
+ export const withToken = url => url.replace("://", "://TOKEN@");
69
+
70
+ async function browse(signal, until) {
71
+ const bonjour = new Bonjour({ socket: dgram.createSocket("udp4"), bind: false });
72
+ const found = new Map();
73
+ const enough = new AbortController();
74
+ try {
75
+ const browser = bonjour.find({ type: "swipt" }, service => {
76
+ if (!service.host || found.has(service.name)) {
77
+ return;
78
+ }
79
+ const host = service.host.replace(/\.$/, "").toLowerCase();
80
+ found.set(service.name, {
81
+ name: label(service.name),
82
+ url: `http://${host.includes(":") ? `[${host}]` : host}:${service.port}`,
83
+ authenticates: service.txt?.auth === "1",
84
+ });
85
+ if (until?.([...found.values()])) {
86
+ enough.abort();
87
+ }
88
+ });
89
+ const requery = setInterval(() => browser.update(), browseRequery);
90
+ try {
91
+ await sleep(browseLimit, undefined, { signal: AbortSignal.any([signal, enough.signal]) });
92
+ } catch (error) {
93
+ if (signal.aborted) {
94
+ throw error;
95
+ }
96
+ } finally {
97
+ clearInterval(requery);
98
+ }
99
+ browser.stop();
100
+ } finally {
101
+ bonjour.destroy();
102
+ }
103
+ return [...found.values()];
104
+ }
105
+
106
+ async function discover(signal, stored, until) {
107
+ const found = await browse(signal, until);
108
+ let changed = false;
109
+ for (const it of found) {
110
+ if (it.authenticates) {
111
+ continue;
112
+ }
113
+ const name = storedName(stored, it.name);
114
+ if (name === undefined) {
115
+ stored[it.name] = it.url;
116
+ changed = true;
117
+ } else {
118
+ const current = parseURL(stored[name]);
119
+ if (current.token === "" && current.url !== it.url) {
120
+ stored[name] = it.url;
121
+ changed = true;
122
+ }
123
+ }
124
+ }
125
+ if (changed) {
126
+ save(stored);
127
+ }
128
+ return found;
129
+ }
130
+
131
+ const requiresToken = found => `${found.name} requires a token: swipt connect ${withToken(found.url)}`;
132
+
133
+ export async function attach(context) {
134
+ const given = context.options.device ?? process.env.SWIPT_DEVICE;
135
+ const stored = load();
136
+ let device;
137
+ if (given && /[:/]/.test(given)) {
138
+ device = parseURL(given);
139
+ } else if (given) {
140
+ const name = label(given);
141
+ let known = storedName(stored, name);
142
+ if (known === undefined) {
143
+ const found = (await discover(context.signal, stored, services => services.some(it => sameName(it.name, name)))).find(it => sameName(it.name, name));
144
+ if (!found) {
145
+ throw new Error(`there is no device ${name}; "swipt devices" lists them`);
146
+ }
147
+ if (found.authenticates) {
148
+ throw new Error(requiresToken(found));
149
+ }
150
+ known = found.name;
151
+ }
152
+ device = parseURL(stored[known]);
153
+ } else {
154
+ let found = [];
155
+ if (Object.keys(stored).length === 0) {
156
+ found = await discover(context.signal, stored);
157
+ }
158
+ const names = Object.keys(stored);
159
+ if (names.length === 0) {
160
+ const locked = found.filter(it => it.authenticates);
161
+ if (locked.length > 0) {
162
+ throw new Error(locked.map(requiresToken).join("\n"));
163
+ }
164
+ throw new Error('there is no device; turn on Swipt › Developer › Local Network, or connect one with "swipt connect URL"');
165
+ }
166
+ if (names.length > 1) {
167
+ throw new Error(`there is more than one device: ${names.join(", ")}; choose one with -d or SWIPT_DEVICE`);
168
+ }
169
+ device = parseURL(stored[names[0]]);
170
+ }
171
+ context.target.url = device.url;
172
+ return open(device);
173
+ }
174
+
175
+ async function reachable(device, signal) {
176
+ try {
177
+ await open(device).system.getInfo({}, { signal });
178
+ return true;
179
+ } catch (error) {
180
+ return error instanceof RpcError && error.code === Code.Unauthenticated;
181
+ }
182
+ }
183
+
184
+ export async function devices({ signal }) {
185
+ const stored = load();
186
+ const deadline = AbortSignal.any([signal, AbortSignal.timeout(browseLimit)]);
187
+ const probes = new Map(Object.entries(stored).map(([name, url]) => [name, reachable(parseURL(url), deadline)]));
188
+ const found = await discover(signal, stored);
189
+ const names = Object.keys(stored).sort((a, b) => a.localeCompare(b));
190
+ if (names.length === 0) {
191
+ console.error('no device publishes Swipt on the local network; turn on Swipt › Developer › Local Network, or connect one with "swipt connect URL"');
192
+ return;
193
+ }
194
+ const rows = [];
195
+ for (const name of names) {
196
+ const { url } = parseURL(stored[name]);
197
+ const online = found.some(it => sameName(it.name, name) && it.url === url) || (probes.has(name) && (await probes.get(name)));
198
+ rows.push([name, url, online ? "" : "offline"]);
199
+ }
200
+ console.log(columns(rows));
201
+ }
202
+
203
+ export async function connect(context) {
204
+ const device = parseURL(context.args[0]);
205
+ context.target.url = device.url;
206
+ const info = await open(device).system.getInfo({}, { signal: context.signal });
207
+ const name = label(info.deviceName) || new URL(device.url).hostname;
208
+ const stored = load();
209
+ const previous = storedName(stored, name);
210
+ if (previous !== undefined) {
211
+ delete stored[previous];
212
+ }
213
+ stored[name] = context.args[0];
214
+ save(stored);
215
+ console.log("connected", name);
216
+ }
217
+
218
+ export function disconnect({ args }) {
219
+ const stored = load();
220
+ const name = storedName(stored, label(args[0]));
221
+ if (name === undefined) {
222
+ throw new Error(`there is no device ${args[0]}`);
223
+ }
224
+ delete stored[name];
225
+ save(stored);
226
+ console.log("disconnected", name);
227
+ }
package/device.mjs CHANGED
@@ -7,9 +7,9 @@ import { setTimeout as sleep } from "node:timers/promises";
7
7
 
8
8
  import { fromJson, toJson } from "@bufbuild/protobuf";
9
9
  import { ValueSchema, timestampFromMs } from "@bufbuild/protobuf/wkt";
10
- import Bonjour from "bonjour-service";
11
10
 
12
- import { UsageError, open, unauthenticated } from "./client.mjs";
11
+ import { UsageError, unauthenticated } from "./client.mjs";
12
+ import { attach } from "./connection.mjs";
13
13
  import { Ignorer, relativePath } from "./ignore.mjs";
14
14
  import { columns, dateTime, enumText, printLog, printScriptError, serviceText } from "./output.mjs";
15
15
  import { buildProject, loadProject } from "./project.mjs";
@@ -18,10 +18,8 @@ import { ServiceStatus, ServiceStatusSchema } from "./proto/swiplets_pb.js";
18
18
  import { sources } from "./sourcemap.mjs";
19
19
 
20
20
  const uploadChunk = 256 << 10;
21
- const browseLimit = 1500;
22
21
  const rebuildDelay = 300;
23
22
  const logReconnect = 2000;
24
- const scriptGrace = 10_000;
25
23
 
26
24
  const { version } = createRequire(import.meta.url)("./package.json");
27
25
 
@@ -41,33 +39,7 @@ function elapsed(since) {
41
39
  return `${Math.floor(seconds / 60)}m${Number((seconds % 60).toFixed(3))}s`;
42
40
  }
43
41
 
44
- const session = (context, run) => run(open(context.globals));
45
-
46
- export async function devices({ signal }) {
47
- const bonjour = new Bonjour();
48
- const found = new Map();
49
- try {
50
- const browser = bonjour.find({ type: "swipt" }, service => {
51
- if (service.host && !found.has(service.name)) {
52
- found.set(service.name, { name: service.name, host: service.host.replace(/\.$/, ""), port: service.port });
53
- }
54
- });
55
- try {
56
- await sleep(browseLimit, undefined, { signal });
57
- } catch {
58
- return;
59
- }
60
- browser.stop();
61
- } finally {
62
- bonjour.destroy();
63
- }
64
- if (found.size === 0) {
65
- console.error("no device publishes Swipt on the local network; turn on Swipt › Developer › Local Network");
66
- return;
67
- }
68
- const hostPort = service => (service.host.includes(":") ? `[${service.host}]:${service.port}` : `${service.host}:${service.port}`);
69
- console.log(columns([["NAME", "HOST"], ...[...found.values()].map(service => [service.name, `http://${hostPort(service)}`])]));
70
- }
42
+ const session = (context, run) => attach(context).then(run);
71
43
 
72
44
  export function printVersion() {
73
45
  console.log("swipt", version, `node ${process.versions.node}`, `${process.platform}/${process.arch}`);
@@ -436,7 +408,7 @@ export async function develop(context) {
436
408
  const project = loadProject(path.resolve(context.args[0] ?? "."));
437
409
  const root = project.root;
438
410
  sources.root = root;
439
- const client = open(context.globals);
411
+ const client = await attach(context);
440
412
  const controller = new AbortController();
441
413
  const inner = AbortSignal.any([signal, controller.signal]);
442
414
  let fatalReject;
@@ -573,7 +545,7 @@ class ImageSink {
573
545
  }
574
546
 
575
547
  export async function js(context) {
576
- const { args, options, globals } = context;
548
+ const { args, options, signal } = context;
577
549
  if (options.api) {
578
550
  if (args.length > 0 || options.file !== undefined || options.reset) {
579
551
  throw new UsageError("--api takes no script and no --reset");
@@ -584,7 +556,6 @@ export async function js(context) {
584
556
  return;
585
557
  }
586
558
  const { code, run } = readScript(args, options);
587
- const signal = globals.timeoutMs > 0 ? AbortSignal.any([context.interrupt, AbortSignal.timeout(globals.timeoutMs + scriptGrace)]) : context.interrupt;
588
559
  await session(context, async client => {
589
560
  if (options.reset) {
590
561
  await client.repl.reset({}, { signal });
@@ -595,7 +566,7 @@ export async function js(context) {
595
566
  const script = options.file ?? "input.js";
596
567
  const images = new ImageSink(options.output);
597
568
  let failure;
598
- for await (const response of client.repl.execute({ code, timeoutMs: globals.timeoutMs }, { signal })) {
569
+ for await (const response of client.repl.execute({ code }, { signal })) {
599
570
  switch (response.part.case) {
600
571
  case "console": {
601
572
  const output = response.part.value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swipt/cli",
3
- "version": "0.1.0-nightly.33",
3
+ "version": "0.1.0-nightly.35",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "esbuild": "^0.28.2"
26
26
  },
27
27
  "peerDependencies": {
28
- "@swipt/api": "0.1.0-nightly.33"
28
+ "@swipt/api": "0.1.0-nightly.35"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@bufbuild/protoc-gen-es": "^2.16.0"
package/swipt.mjs CHANGED
@@ -3,15 +3,14 @@ import path from "node:path";
3
3
  import { parseArgs } from "node:util";
4
4
 
5
5
  import { Code, RpcError, UsageError } from "./client.mjs";
6
+ import * as connection from "./connection.mjs";
6
7
  import * as device from "./device.mjs";
7
8
  import { FalseResult } from "./output.mjs";
8
9
  import { buildProject, createCommand, sourceCommand } from "./project.mjs";
9
10
  import { sources } from "./sourcemap.mjs";
10
11
 
11
12
  const globalOptions = {
12
- host: { type: "string", value: "URL", help: "Swipt URL (SWIPT_HOST)" },
13
- token: { type: "string", value: "TOKEN", help: "API token (SWIPT_TOKEN)" },
14
- timeout: { type: "string", value: "DURATION", help: "time limit" },
13
+ device: { type: "string", short: "d", value: "NAME|URL", help: "the device to use (SWIPT_DEVICE)" },
15
14
  help: { type: "boolean", short: "h", help: "help" },
16
15
  };
17
16
 
@@ -73,7 +72,9 @@ const commands = [
73
72
  args: [0, Infinity],
74
73
  run: ({ args }) => sourceCommand(process.cwd(), args),
75
74
  },
76
- { name: "devices", short: "List the devices that publish Swipt on the local network", args: [0, 0], run: device.devices },
75
+ { name: "devices", short: "List the devices, adding the ones found on the local network", args: [0, 0], run: connection.devices },
76
+ { name: "connect", use: "connect URL", short: "Add a device by its URL; needed when it is remote or requires a token", args: [1, 1], run: connection.connect },
77
+ { name: "disconnect", use: "disconnect NAME", short: "Forget a device", args: [1, 1], run: connection.disconnect },
77
78
  { name: "info", short: "Show the device and its connection", args: [0, 0], run: device.info },
78
79
  { name: "version", short: "Print the version", args: [0, 0], run: device.printVersion },
79
80
  { name: "swiplets", use: "swiplets [ID]", short: "List the swiplets or show one", args: [0, 1], options: projectOption, run: device.swiplets },
@@ -168,7 +169,7 @@ function usage(trail) {
168
169
  }
169
170
  const options = { ...trail.reduce((found, it) => ({ ...found, ...it.options }), {}), ...globalOptions };
170
171
  lines.push("", "options:");
171
- lines.push(...columns(Object.entries(options).map(([option, it]) => [` --${option}${it.value ? ` ${it.value}` : ""}`, it.help])).split("\n"));
172
+ lines.push(...columns(Object.entries(options).map(([option, it]) => [` ${it.short ? `-${it.short}, ` : ""}--${option}${it.value ? ` ${it.value}` : ""}`, it.help])).split("\n"));
172
173
  return lines.join("\n");
173
174
  }
174
175
 
@@ -221,29 +222,22 @@ function resolve(argv) {
221
222
  return { trail, help, args, values: parsed.values };
222
223
  }
223
224
 
224
- function printError(error, { host, timeout, timedOut }, operation) {
225
+ function printError(error, target, operation) {
225
226
  let exit = 1;
226
227
  let message = error.message;
227
228
  if (error instanceof UsageError) {
228
229
  exit = 2;
229
- } else if (timedOut) {
230
- message = `the command ran past --timeout ${timeout}`;
231
230
  } else if (error instanceof RpcError) {
232
231
  switch (error.code) {
233
232
  case Code.Unavailable:
234
233
  exit = 5;
235
- message = `Swipt at ${host} is unreachable: ${message}`;
234
+ message = `Swipt at ${target.url} is unreachable: ${message}`;
236
235
  break;
237
236
  case Code.Unauthenticated:
238
- message += "; copy the token from Swipt › Developer";
237
+ message += `; connect with the URL from Swipt › Developer: swipt connect ${connection.withToken(target.url)}`;
239
238
  break;
240
239
  case Code.Unimplemented:
241
- message = `Swipt at ${host} does not have this command; update Swipt`;
242
- break;
243
- case Code.DeadlineExceeded:
244
- if (timeout) {
245
- message = `the command ran past --timeout ${timeout}`;
246
- }
240
+ message = `Swipt at ${target.url} does not have this command; update Swipt`;
247
241
  break;
248
242
  }
249
243
  }
@@ -259,8 +253,7 @@ async function main() {
259
253
  const interrupted = () => interrupt.abort();
260
254
  process.once("SIGINT", interrupted);
261
255
  process.once("SIGTERM", interrupted);
262
- let timeout;
263
- const state = { host: "", timeout: undefined, timedOut: false };
256
+ const target = { url: "" };
264
257
  try {
265
258
  const resolved = resolve(process.argv.slice(2));
266
259
  if (resolved.help) {
@@ -269,28 +262,17 @@ async function main() {
269
262
  }
270
263
  const command = resolved.trail.at(-1);
271
264
  const values = command.parse ? command.parse(resolved.values) : resolved.values;
272
- state.host = values.host ?? process.env.SWIPT_HOST ?? "";
273
- state.timeout = values.timeout;
274
- const timeoutMs = parseDuration(values.timeout, "--timeout");
275
- if (timeoutMs > 0) {
276
- timeout = AbortSignal.timeout(timeoutMs);
277
- timeout.addEventListener("abort", () => {
278
- state.timedOut = true;
279
- });
280
- }
281
265
  if (values.project !== undefined) {
282
266
  sources.root = path.resolve(values.project);
283
267
  }
284
- const globals = { host: values.host ?? "", token: values.token ?? "", timeoutMs };
285
- const signal = timeout ? AbortSignal.any([interrupt.signal, timeout]) : interrupt.signal;
286
- await command.run({ args: resolved.args, options: values, globals, signal, interrupt: interrupt.signal, printError: (error, operation) => printError(error, state, operation) });
268
+ await command.run({ args: resolved.args, options: values, target, signal: interrupt.signal, printError: (error, operation) => printError(error, target, operation) });
287
269
  } catch (error) {
288
270
  if (error instanceof FalseResult) {
289
271
  process.exitCode = 1;
290
272
  } else if (interrupt.signal.aborted) {
291
273
  process.exitCode = 130;
292
274
  } else {
293
- process.exitCode = printError(error, state);
275
+ process.exitCode = printError(error, target);
294
276
  }
295
277
  } finally {
296
278
  process.off("SIGINT", interrupted);