@swipt/cli 0.1.0-nightly.45 → 0.1.0-nightly.47

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/connection.mjs CHANGED
@@ -155,7 +155,7 @@ const requiresToken = (found) =>
155
155
  `${found.name} requires a token: swipt device add ${withToken(found.url)}`;
156
156
 
157
157
  export async function attach(context) {
158
- const given = context.options.device ?? process.env.SWIPT_DEVICE;
158
+ const given = context.options.device;
159
159
  const stored = load();
160
160
  let device;
161
161
  if (given && /[:/]/.test(given)) {
@@ -197,7 +197,7 @@ export async function attach(context) {
197
197
  }
198
198
  if (names.length > 1) {
199
199
  throw new Error(
200
- `there is more than one device: ${names.join(", ")}; choose one with -d or SWIPT_DEVICE`,
200
+ `there is more than one device: ${names.join(", ")}; choose one with -d`,
201
201
  );
202
202
  }
203
203
  device = parseURL(stored[names[0]]);
package/coredevice.mjs CHANGED
@@ -34,7 +34,7 @@ export function devicectl(args, { parse = false } = {}) {
34
34
  return parsed;
35
35
  }
36
36
 
37
- export function coreDevice(given) {
37
+ export function coreDevice(given, { required = false } = {}) {
38
38
  const devices = devicectl(["list", "devices"], { parse: true })
39
39
  .result.devices.filter(
40
40
  (device) =>
@@ -51,13 +51,19 @@ export function coreDevice(given) {
51
51
  sameName(device.udid, given) ||
52
52
  sameName(label(device.name), label(given)),
53
53
  )
54
- : devices;
54
+ : required
55
+ ? []
56
+ : devices;
55
57
  if (matches.length !== 1) {
56
- const found = matches.length === 0 ? "no" : "more than one";
58
+ const reason = !given
59
+ ? "choose a device with -d NAME|UDID"
60
+ : matches.length === 0
61
+ ? `no iOS device is named ${given}`
62
+ : `more than one iOS device is named ${given}; choose one by UDID`;
57
63
  throw new Error(
58
- given
59
- ? `${found} iOS device is named ${given}`
60
- : `${found} iOS device is paired; choose one with -d NAME|UDID`,
64
+ devices.length === 0
65
+ ? `${reason}; no iOS device is paired with this Mac`
66
+ : `${reason}; paired iOS devices:\n${devices.map((device) => ` ${device.name} ${device.udid}`).join("\n")}`,
61
67
  );
62
68
  }
63
69
  return matches[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swipt/cli",
3
- "version": "0.1.0-nightly.45",
3
+ "version": "0.1.0-nightly.47",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,11 +32,11 @@
32
32
  "react-native-worklets": "0.13.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@swipt/api": "0.1.0-nightly.45"
35
+ "@swipt/api": "0.1.0-nightly.47"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@swipt/server-darwin-arm64": "0.1.0-nightly.45",
39
- "@swipt/server-darwin-x64": "0.1.0-nightly.45"
38
+ "@swipt/server-darwin-arm64": "0.1.0-nightly.47",
39
+ "@swipt/server-darwin-x64": "0.1.0-nightly.47"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@bufbuild/protoc-gen-es": "^2.16.0"
package/sideload.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync, spawn, spawnSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
2
+ import { X509Certificate, createHash } from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import os from "node:os";
@@ -31,7 +31,7 @@ function passcodeOf(options) {
31
31
  `invalid argument ${JSON.stringify(type)} for --passcode-type: it is ${passcodeTypes.join(", ")}`,
32
32
  );
33
33
  }
34
- const passcode = options.passcode ?? process.env.SWIPT_PASSCODE;
34
+ const passcode = options.passcode;
35
35
  if (!passcode) {
36
36
  return undefined;
37
37
  }
@@ -51,7 +51,7 @@ function passcodeOf(options) {
51
51
  }
52
52
 
53
53
  function headers(accept) {
54
- const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
54
+ const token = process.env.GITHUB_TOKEN;
55
55
  return {
56
56
  Accept: accept,
57
57
  "X-GitHub-Api-Version": "2022-11-28",
@@ -72,7 +72,7 @@ async function github(url, accept, signal) {
72
72
  if (!response.ok) {
73
73
  let message = `GitHub answered ${response.status} for ${url}`;
74
74
  if ([401, 403, 404, 429].includes(response.status)) {
75
- message += `; ${process.env.GITHUB_TOKEN || process.env.GH_TOKEN ? "GITHUB_TOKEN" : "set GITHUB_TOKEN to a token that"} must read the repository and stay under the rate limit`;
75
+ message += `; ${process.env.GITHUB_TOKEN ? "GITHUB_TOKEN" : "set GITHUB_TOKEN to a token that"} must read the repository and stay under the rate limit`;
76
76
  }
77
77
  throw new Error(message);
78
78
  }
@@ -317,7 +317,45 @@ function resign(app, id, signing) {
317
317
  ]);
318
318
  }
319
319
 
320
- function locate(options, device) {
320
+ function xcodeTeams() {
321
+ const valid = new Set(
322
+ [
323
+ ...execFileSync(
324
+ "security",
325
+ ["find-identity", "-v", "-p", "codesigning"],
326
+ {
327
+ encoding: "utf8",
328
+ },
329
+ ).matchAll(/\) ([0-9A-F]{40}) "Apple Development: /g),
330
+ ].map((match) => match[1]),
331
+ );
332
+ const certificates = execFileSync(
333
+ "security",
334
+ ["find-certificate", "-a", "-Z", "-p", "-c", "Apple Development"],
335
+ { encoding: "utf8" },
336
+ ).matchAll(
337
+ /SHA-1 hash: ([0-9A-F]{40})[\s\S]*?(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)/g,
338
+ );
339
+ const teams = new Map();
340
+ for (const [, hash, pem] of certificates) {
341
+ if (!valid.has(hash)) {
342
+ continue;
343
+ }
344
+ const subject = new X509Certificate(pem).subject;
345
+ const id = /^OU=(.+)$/m.exec(subject)?.[1];
346
+ if (id !== undefined && teamPattern.test(id)) {
347
+ teams.set(id, /^O=(.+)$/m.exec(subject)?.[1].replaceAll("\\", ""));
348
+ }
349
+ }
350
+ return [...teams].map(([id, name]) => ({ id, name }));
351
+ }
352
+
353
+ const teamList = (teams) =>
354
+ teams
355
+ .map((team) => ` ${team.id}${team.name ? ` ${team.name}` : ""}`)
356
+ .join("\n");
357
+
358
+ function locate(options, device, fresh = false) {
321
359
  const found = apps(device.udid)
322
360
  .map((app) => ({
323
361
  ...app,
@@ -337,25 +375,35 @@ function locate(options, device) {
337
375
  }
338
376
  if (found.length > 1) {
339
377
  throw new Error(
340
- `Swipt is sideloaded on ${device.name} by more than one team (${found.map((app) => app.team).join(", ")}); choose one with --team`,
378
+ `Swipt is sideloaded on ${device.name} by more than one team; choose one with --team ID:\n${teamList(found.map((app) => ({ id: app.team })))}`,
341
379
  );
342
380
  }
381
+ if (found.length === 0 && fresh) {
382
+ const teams = xcodeTeams();
383
+ if (teams.length !== 1) {
384
+ throw new Error(
385
+ teams.length === 0
386
+ ? `Xcode has no development certificate; sign in at ${accountsHint}, or choose a team with --team ID`
387
+ : `choose a team with --team ID:\n${teamList(teams)}`,
388
+ );
389
+ }
390
+ return { team: teams[0].id, current: undefined };
391
+ }
343
392
  return { team: found[0]?.team, current: found[0] };
344
393
  }
345
394
 
346
395
  const buildText = (app) => `Swipt ${app.version} (${app.bundleVersion})`;
347
396
 
348
397
  async function deploy(context, onlyNewer) {
349
- const passcode = passcodeOf(context.options);
350
- const device = coreDevice(context.options.device ?? process.env.SWIPT_DEVICE);
351
- const { team, current } = locate(context.options, device);
352
- if (team === undefined) {
398
+ const device = coreDevice(context.options.device, { required: true });
399
+ const { team, current } = locate(context.options, device, !onlyNewer);
400
+ if (current === undefined && onlyNewer) {
353
401
  throw new Error(
354
- onlyNewer
355
- ? `Swipt is not sideloaded on ${device.name}; install it with "swipt sideload install"`
356
- : `Swipt is not sideloaded on ${device.name} yet; choose the team with --team ID, as shown in ${accountsHint}`,
402
+ `Swipt is not sideloaded on ${device.name}${team === undefined ? "" : ` by team ${team}`}; install it with "swipt sideload install"`,
357
403
  );
358
404
  }
405
+ const passcode =
406
+ current === undefined ? passcodeOf(context.options) : undefined;
359
407
  const release = await findRelease(context.options.nightly, context.signal);
360
408
  if (current !== undefined) {
361
409
  const installed = Number(current.bundleVersion);
@@ -432,12 +480,9 @@ export const install = (context) => deploy(context, false);
432
480
  export const update = (context) => deploy(context, true);
433
481
 
434
482
  const serverHint =
435
- "swipt-server is missing; install @swipt/cli with its optional dependencies, or name it with SWIPT_SERVER_PATH";
483
+ "swipt-server is missing; install @swipt/cli with its optional dependencies";
436
484
 
437
485
  function serverPath() {
438
- if (process.env.SWIPT_SERVER_PATH) {
439
- return process.env.SWIPT_SERVER_PATH;
440
- }
441
486
  let packaged;
442
487
  try {
443
488
  packaged = path.join(
@@ -540,12 +585,14 @@ function pair(device, id, work) {
540
585
  function serve(udid, passcode, signal) {
541
586
  const server = spawn(
542
587
  serverPath(),
543
- ["-d", udid, ...(passcode ? ["--passcode-type", passcode.type] : [])],
588
+ [
589
+ "-d",
590
+ udid,
591
+ ...(passcode
592
+ ? ["--passcode", passcode.passcode, "--passcode-type", passcode.type]
593
+ : []),
594
+ ],
544
595
  {
545
- env: {
546
- ...process.env,
547
- ...(passcode ? { SWIPT_PASSCODE: passcode.passcode } : {}),
548
- },
549
596
  stdio: ["ignore", "pipe", "pipe"],
550
597
  signal,
551
598
  },
@@ -586,7 +633,7 @@ function serve(udid, passcode, signal) {
586
633
 
587
634
  export async function start(context) {
588
635
  const passcode = passcodeOf(context.options);
589
- const device = coreDevice(context.options.device ?? process.env.SWIPT_DEVICE);
636
+ const device = coreDevice(context.options.device, { required: true });
590
637
  const { team, current } = locate(context.options, device);
591
638
  if (current === undefined) {
592
639
  throw new Error(
@@ -630,7 +677,7 @@ export async function start(context) {
630
677
  }
631
678
 
632
679
  export function uninstall(context) {
633
- const device = coreDevice(context.options.device ?? process.env.SWIPT_DEVICE);
680
+ const device = coreDevice(context.options.device, { required: true });
634
681
  const { team, current } = locate(context.options, device);
635
682
  if (current === undefined) {
636
683
  throw new Error(
package/swipt.mjs CHANGED
@@ -18,7 +18,7 @@ const deviceOption = {
18
18
  type: "string",
19
19
  short: "d",
20
20
  value: "NAME|URL",
21
- help: "the device to use (SWIPT_DEVICE)",
21
+ help: "the device to use",
22
22
  },
23
23
  };
24
24
 
@@ -27,33 +27,26 @@ const sideloadOptions = {
27
27
  type: "string",
28
28
  short: "d",
29
29
  value: "NAME|UDID",
30
- help: "the paired iOS device (SWIPT_DEVICE)",
30
+ help: "device paired with this Mac",
31
31
  },
32
32
  team: {
33
33
  type: "string",
34
34
  value: "ID",
35
- help: "the team ID (default: the team of the installed Swipt)",
35
+ help: "team ID, required if more than one",
36
36
  },
37
37
  };
38
38
 
39
- const passcodeOptions = {
40
- passcode: {
41
- type: "string",
42
- value: "CODE",
43
- help: "the device passcode, for unlocking it (SWIPT_PASSCODE)",
44
- },
39
+ const passcodeOptions = (help) => ({
40
+ passcode: { type: "string", value: "CODE", help },
45
41
  "passcode-type": {
46
42
  type: "string",
47
43
  value: "TYPE",
48
- help: "pin for 4 or 6 digits, numeric or alphanumeric for a custom code (default pin)",
44
+ help: "pin (4 or 6 digits, default), numeric or alphanumeric",
49
45
  },
50
- };
46
+ });
51
47
 
52
48
  const nightlyOption = {
53
- nightly: {
54
- type: "boolean",
55
- help: "the nightly build",
56
- },
49
+ nightly: { type: "boolean", help: "use nightly build" },
57
50
  };
58
51
 
59
52
  const durationUnits = {
@@ -391,33 +384,36 @@ const commands = [
391
384
  },
392
385
  {
393
386
  name: "sideload",
394
- short: "Install the Swipt app on a paired device",
387
+ short: "Sideload Swipt, signed by Xcode",
395
388
  options: sideloadOptions,
396
389
  commands: [
397
390
  {
398
391
  name: "install",
399
- short: "Install the latest release",
392
+ short: "Install latest release",
400
393
  args: [0, 0],
401
- options: { ...passcodeOptions, ...nightlyOption },
394
+ options: {
395
+ ...passcodeOptions("set up auto-unlock with this passcode"),
396
+ ...nightlyOption,
397
+ },
402
398
  run: sideload.install,
403
399
  },
404
400
  {
405
401
  name: "update",
406
- short: "Install the latest release if it is newer",
402
+ short: "Update to latest release",
407
403
  args: [0, 0],
408
- options: { ...passcodeOptions, ...nightlyOption },
404
+ options: nightlyOption,
409
405
  run: sideload.update,
410
406
  },
411
407
  {
412
408
  name: "start",
413
- short: "Open the app, trusting its developer if needed",
409
+ short: "Trust developer if needed and open Swipt",
414
410
  args: [0, 0],
415
- options: passcodeOptions,
411
+ options: passcodeOptions("unlock device with this passcode"),
416
412
  run: sideload.start,
417
413
  },
418
414
  {
419
415
  name: "uninstall",
420
- short: "Remove the app",
416
+ short: "Remove Swipt",
421
417
  args: [0, 0],
422
418
  run: sideload.uninstall,
423
419
  },