runcloud 0.1.114 → 0.1.116

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/README.md CHANGED
@@ -82,6 +82,23 @@ runcloud android press-key <id> enter
82
82
  runcloud android screenshot <id> --output android.png --json
83
83
  ```
84
84
 
85
+ `open-url` accepts HTTPS URLs and application deep links on both platforms. Keep
86
+ the complete target quoted so its URI delimiters stay in one argument:
87
+
88
+ ```bash
89
+ runcloud ios open-url \
90
+ 'runcloudproof://open/items%2F42?message=hello%20world&return=https%3A%2F%2Fexample.com%2Fdone%3Fx%3D1%26y%3Dtwo#proof' \
91
+ --id <id> \
92
+ --json
93
+ ```
94
+
95
+ Success returns `ok`, `platform`, `sessionId`, `device`, `leaseId`, and the
96
+ exact input `url`. Replace `ios` with `android` for an emulator. See
97
+ [Open URLs and Deep Links](https://docs.run.cloud/platform/open-urls) for SDK
98
+ and REST examples, platform behavior, validation, and troubleshooting.
99
+ On iOS, a first custom-scheme handoff can still require confirming the system's
100
+ **Open in “App”?** prompt after the success acknowledgement.
101
+
85
102
  Useful `create` flags: `--region`, `--display-name`, `--inactivity-timeout 3m`,
86
103
  `--hard-timeout 1h`, `--install-asset <name-or-id>`, `--rm` (release when the
87
104
  command exits), `--json`.
package/dist/api.js CHANGED
@@ -10,6 +10,7 @@ export class ApiError extends Error {
10
10
  sessionId;
11
11
  platform;
12
12
  action;
13
+ suggestedAction;
13
14
  interactionStatus;
14
15
  acceptedAt;
15
16
  completedAt;
@@ -65,6 +66,7 @@ function parsedError(body) {
65
66
  ...(typeof parsed.sessionId === 'string' ? { sessionId: parsed.sessionId } : {}),
66
67
  ...(platform ? { platform } : {}),
67
68
  ...(typeof parsed.action === 'string' ? { action: parsed.action } : {}),
69
+ ...(typeof nested?.action === 'string' ? { suggestedAction: nested.action } : {}),
68
70
  ...(interactionStatus ? { interactionStatus } : {}),
69
71
  ...(typeof parsed.acceptedAt === 'string' ? { acceptedAt: parsed.acceptedAt } : {}),
70
72
  ...(typeof parsed.completedAt === 'string' ? { completedAt: parsed.completedAt } : {}),
@@ -1,7 +1,7 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
2
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
3
3
  import { homedir, hostname as localHostname } from 'node:os';
4
- import { join } from 'node:path';
4
+ import { dirname, join } from 'node:path';
5
5
  import WebSocket, { createWebSocketStream } from 'ws';
6
6
  import { ApiClient, friendlyApiError } from '../api.js';
7
7
  import { requireCredentials } from '../config.js';
@@ -85,6 +85,22 @@ function sshHome() {
85
85
  function keyPath() {
86
86
  return join(sshHome(), 'id_ed25519');
87
87
  }
88
+ function sshConfigPath() {
89
+ return join(homedir(), '.ssh', 'config');
90
+ }
91
+ export function sshCommandArgs(configPath, alias, command = []) {
92
+ return ['-F', configPath, alias, ...command];
93
+ }
94
+ export function scpCommandArgs(configPath, recursive, source, destination) {
95
+ return [
96
+ '-F',
97
+ configPath,
98
+ ...(recursive ? ['-r'] : []),
99
+ '--',
100
+ source,
101
+ destination,
102
+ ];
103
+ }
88
104
  function exec(command, args) {
89
105
  return new Promise((resolve, reject) => {
90
106
  const child = spawn(command, args, { stdio: 'inherit' });
@@ -119,8 +135,8 @@ function sshAlias(box) {
119
135
  return `runcloud-${box.name}`;
120
136
  }
121
137
  function writeSshConfig(box, privateKey) {
122
- const sshDir = join(homedir(), '.ssh');
123
- const configPath = join(sshDir, 'config');
138
+ const configPath = sshConfigPath();
139
+ const sshDir = dirname(configPath);
124
140
  mkdirSync(sshDir, { recursive: true, mode: 0o700 });
125
141
  const current = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
126
142
  const start = `# BEGIN RUN CLOUD ${box.id}`;
@@ -147,7 +163,7 @@ async function setupSsh(reference) {
147
163
  const client = api();
148
164
  const box = await resolveBox(client, reference);
149
165
  const privateKey = await ensureSshKey(client, box);
150
- return { box, alias: writeSshConfig(box, privateKey) };
166
+ return { box, alias: writeSshConfig(box, privateKey), configPath: sshConfigPath() };
151
167
  }
152
168
  function output(value, opts) {
153
169
  if (opts.json)
@@ -221,7 +237,7 @@ export function registerAccessCommands(parent, opts = {}) {
221
237
  note('ssh');
222
238
  return action(async () => {
223
239
  const configured = await setupSsh(reference);
224
- const code = await exec('ssh', [configured.alias, ...command]);
240
+ const code = await exec('ssh', sshCommandArgs(configured.configPath, configured.alias, command));
225
241
  if (code !== 0)
226
242
  process.exitCode = code;
227
243
  });
@@ -252,12 +268,7 @@ export function registerAccessCommands(parent, opts = {}) {
252
268
  }
253
269
  const configured = await setupSsh(reference);
254
270
  const remote = (path) => path.startsWith(':') ? `${configured.alias}:${path.slice(1)}` : path;
255
- const args = [
256
- ...(cmdOpts.recursive ? ['-r'] : []),
257
- '--',
258
- remote(source),
259
- remote(destination),
260
- ];
271
+ const args = scpCommandArgs(configured.configPath, cmdOpts.recursive === true, remote(source), remote(destination));
261
272
  const code = await exec('scp', args);
262
273
  if (code !== 0)
263
274
  process.exitCode = code;
@@ -733,6 +733,58 @@ async function createSimulatorSession(platform, opts) {
733
733
  ...(opts.devHost ? { devHost: opts.devHost } : {}),
734
734
  });
735
735
  }
736
+ async function openSimulatorSessionUrl(platform, sessionId, url) {
737
+ const path = `/run-cloud/${platform}/${encodeURIComponent(sessionId)}/open-url`;
738
+ try {
739
+ const response = await client().post(path, { url });
740
+ const result = typeof response === 'object' && response !== null && !Array.isArray(response)
741
+ ? response
742
+ : {};
743
+ if (result.ok !== true
744
+ || result.platform !== platform
745
+ || result.sessionId !== sessionId
746
+ || typeof result.device !== 'string'
747
+ || result.device.length === 0
748
+ || typeof result.leaseId !== 'string'
749
+ || result.leaseId.length === 0
750
+ || result.url !== url) {
751
+ throw new ApiError(0, 'Simulator open URL returned an invalid acknowledgement', path, {
752
+ code: 'open_url_invalid_response',
753
+ retryable: true,
754
+ sessionId,
755
+ platform,
756
+ action: 'open-url',
757
+ });
758
+ }
759
+ return {
760
+ ok: true,
761
+ platform,
762
+ sessionId,
763
+ device: result.device,
764
+ leaseId: result.leaseId,
765
+ url,
766
+ };
767
+ }
768
+ catch (error) {
769
+ if (error instanceof ApiTransportError) {
770
+ throw new ApiError(0, 'Simulator open URL could not reach the run.cloud API', path, {
771
+ code: 'open_url_transport_error',
772
+ retryable: true,
773
+ sessionId,
774
+ platform,
775
+ action: 'open-url',
776
+ });
777
+ }
778
+ if (error instanceof ApiError) {
779
+ Object.assign(error, {
780
+ sessionId: error.sessionId ?? sessionId,
781
+ platform: error.platform ?? platform,
782
+ action: error.action ?? 'open-url',
783
+ });
784
+ }
785
+ throw error;
786
+ }
787
+ }
736
788
  function registerSimulatorCommands(program, platform) {
737
789
  const label = platform === 'android' ? 'Android emulator' : 'iOS simulator';
738
790
  const article = platform === 'android' ? 'an' : 'an';
@@ -792,7 +844,7 @@ function registerSimulatorCommands(program, platform) {
792
844
  .requiredOption('--id <id>', 'session id')
793
845
  .option('--json', 'output JSON', false)
794
846
  .action((url, opts) => action(async () => {
795
- print(await client().post(`/run-cloud/${platform}/${encodeURIComponent(opts.id)}/open-url`, { url }), opts);
847
+ print(await openSimulatorSessionUrl(platform, opts.id, url), opts);
796
848
  }, opts));
797
849
  addInteractionOptions(simulator
798
850
  .command('tap')
@@ -1036,6 +1088,7 @@ function jsonErrorEnvelope(error) {
1036
1088
  retryable: error.retryable ?? (error.status === 408 || error.status === 429 || error.status >= 500),
1037
1089
  httpStatus: error.status,
1038
1090
  ...(error.details === undefined ? {} : { details: error.details }),
1091
+ ...(error.suggestedAction ? { action: error.suggestedAction } : {}),
1039
1092
  },
1040
1093
  };
1041
1094
  }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.114';
1
+ export const CLI_VERSION = '0.1.116';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.114",
3
+ "version": "0.1.116",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file