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 +17 -0
- package/dist/api.js +2 -0
- package/dist/commands/boxAccess.js +22 -11
- package/dist/commands/run-cloud.js +54 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.1.116.tgz +0 -0
- package/runcloud-0.1.114.tgz +0 -0
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
|
|
123
|
-
const
|
|
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',
|
|
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
|
|
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.
|
|
1
|
+
export const CLI_VERSION = '0.1.116';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.1.114.tgz
DELETED
|
Binary file
|