runcloud 0.1.13 → 0.1.15

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/dist/api.js CHANGED
@@ -176,8 +176,14 @@ function headline(err) {
176
176
  if (sandboxMatch) {
177
177
  return `Sandbox ID not found: ${decodeURIComponent(sandboxMatch[1])}.`;
178
178
  }
179
- if (!err.detail || /^not found$/i.test(err.detail.trim()))
180
- return 'Resource not found.';
179
+ if (err.path?.startsWith('/run-cloud/images')) {
180
+ return 'Image not found — check the reference, and that it has been registered.';
181
+ }
182
+ if (!err.detail || /^not found$/i.test(err.detail.trim())) {
183
+ return err.path
184
+ ? "This version of the CLI asked for something the server doesn't have."
185
+ : 'Resource not found.';
186
+ }
181
187
  return err.detail;
182
188
  }
183
189
  if (err.status === 409)
@@ -0,0 +1,100 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ import { ApiClient, friendlyApiError } from '../api.js';
3
+ import { requireCredentials } from '../config.js';
4
+ const POLL_INTERVAL_MS = 3_000;
5
+ const DEFAULT_WAIT_TIMEOUT_SECONDS = 1_200;
6
+ function runCloudApi() {
7
+ const credentials = requireCredentials();
8
+ return new ApiClient(credentials.apiUrl, credentials.token);
9
+ }
10
+ function print(value, opts) {
11
+ if (opts.json) {
12
+ console.log(JSON.stringify(value, null, 2));
13
+ return;
14
+ }
15
+ if (Array.isArray(value)) {
16
+ for (const item of value)
17
+ console.log(formatRecord(item));
18
+ return;
19
+ }
20
+ console.log(formatRecord(value));
21
+ }
22
+ function formatRecord(value) {
23
+ if (!value || typeof value !== 'object')
24
+ return String(value);
25
+ return Object.entries(value)
26
+ .filter(([, v]) => v !== undefined && v !== null)
27
+ .map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : String(v)}`)
28
+ .join('\n');
29
+ }
30
+ async function run(fn) {
31
+ try {
32
+ await fn();
33
+ }
34
+ catch (err) {
35
+ console.error(friendlyApiError(err));
36
+ process.exitCode = 1;
37
+ }
38
+ }
39
+ function withOutput(cmd) {
40
+ return cmd.option('--json', 'output JSON', false);
41
+ }
42
+ function parseTimeoutSeconds(value) {
43
+ const n = Number(value);
44
+ if (!Number.isFinite(n) || n <= 0) {
45
+ throw new InvalidArgumentError('--timeout must be a positive number of seconds');
46
+ }
47
+ return n;
48
+ }
49
+ const TERMINAL_STATES = new Set(['ready', 'failed']);
50
+ export async function pollImageRegistration(api, ref, opts = {}) {
51
+ const intervalMs = opts.intervalMs ?? POLL_INTERVAL_MS;
52
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_SECONDS * 1000;
53
+ const deadline = Date.now() + timeoutMs;
54
+ for (;;) {
55
+ const data = await api.get('/run-cloud/images');
56
+ const items = (data?.items ?? []);
57
+ const row = items.find((item) => item.ref === ref || item.name === ref);
58
+ if (row && TERMINAL_STATES.has(row.state))
59
+ return row;
60
+ if (Date.now() > deadline) {
61
+ throw new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for image ${ref} to become ready`);
62
+ }
63
+ await new Promise((done) => setTimeout(done, intervalMs));
64
+ }
65
+ }
66
+ export function registerImage(program) {
67
+ const image = program
68
+ .command('image')
69
+ .description('Register and manage the OCI images run.cloud sandboxes boot from');
70
+ withOutput(image
71
+ .command('create')
72
+ .description('Register (build-once) an OCI image ref for run.cloud sandboxes')
73
+ .requiredOption('--ref <ref>', 'OCI image ref to register, e.g. python:3.12-slim')
74
+ .option('--wait', 'poll until the image is ready or failed', false)
75
+ .option('--timeout <seconds>', `max time to wait with --wait, in seconds (default ${DEFAULT_WAIT_TIMEOUT_SECONDS})`, parseTimeoutSeconds, DEFAULT_WAIT_TIMEOUT_SECONDS)).action((opts) => run(async () => {
76
+ const api = runCloudApi();
77
+ const created = await api.post('/run-cloud/images', { ref: opts.ref });
78
+ if (!opts.wait) {
79
+ print(created, opts);
80
+ return;
81
+ }
82
+ const finalRow = await pollImageRegistration(api, opts.ref, { timeoutMs: opts.timeout * 1000 });
83
+ if (finalRow.state === 'failed') {
84
+ throw new Error(finalRow.error
85
+ ? `image ${opts.ref} failed to build: ${finalRow.error}`
86
+ : `image ${opts.ref} failed to build`);
87
+ }
88
+ print(finalRow, opts);
89
+ }));
90
+ withOutput(image.command('list').description('List images your organization has registered')).action((opts) => run(async () => {
91
+ const data = await runCloudApi().get('/run-cloud/images');
92
+ print(data?.items ?? [], opts);
93
+ }));
94
+ withOutput(image
95
+ .command('refresh')
96
+ .description("Move a tag's pin to its current upstream digest")
97
+ .argument('<ref>', 'OCI image ref to refresh')).action((ref, opts) => run(async () => {
98
+ print(await runCloudApi().post('/run-cloud/images/refresh', { ref }), opts);
99
+ }));
100
+ }
@@ -164,7 +164,11 @@ export function registerSandbox(program) {
164
164
  const api = runCloudApi();
165
165
  const [data, boxes] = await Promise.all([
166
166
  api.get(`/run-cloud/sandboxes${qs}`),
167
- api.get('/run-cloud/boxes'),
167
+ api.get('/run-cloud/boxes').catch((err) => {
168
+ if (err instanceof ApiError && err.status === 404)
169
+ return { items: [] };
170
+ throw err;
171
+ }),
168
172
  ]);
169
173
  const bySandbox = new Map((boxes?.items ?? []).map((box) => [box.sandboxId, box]));
170
174
  const items = (data?.items ?? []).map((item) => {
package/dist/run-cloud.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { registerImage } from './commands/image.js';
3
4
  import { registerLogin } from './commands/login.js';
4
5
  import { registerRunCloud } from './commands/run-cloud.js';
5
6
  import { registerSandbox } from './commands/sandbox.js';
@@ -10,4 +11,5 @@ const program = new Command('runcloud')
10
11
  registerLogin(program);
11
12
  registerRunCloud(program);
12
13
  registerSandbox(program);
14
+ registerImage(program);
13
15
  await program.parseAsync(process.argv);
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.13';
1
+ export const CLI_VERSION = '0.1.15';
package/package.json CHANGED
@@ -1,8 +1,36 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
+ "keywords": [
7
+ "runcloud",
8
+ "run-cloud",
9
+ "run cloud",
10
+ "run.cloud",
11
+ "ios-simulator",
12
+ "android-emulator",
13
+ "simulator",
14
+ "emulator",
15
+ "device-farm",
16
+ "device-cloud",
17
+ "mobile-testing",
18
+ "mobile-app-testing",
19
+ "ios-testing",
20
+ "android-testing",
21
+ "mobile-automation",
22
+ "cloud-testing",
23
+ "e2e-testing",
24
+ "sandbox",
25
+ "cloud-sandbox",
26
+ "code-sandbox",
27
+ "agent-sandbox",
28
+ "ai-sandbox",
29
+ "code-execution",
30
+ "code-interpreter",
31
+ "remote-execution",
32
+ "cli"
33
+ ],
6
34
  "type": "module",
7
35
  "repository": {
8
36
  "type": "git",
@@ -17,9 +45,6 @@
17
45
  },
18
46
  "dependencies": {
19
47
  "commander": "^12.1.0",
20
- "esbuild": "0.25.12",
21
- "google-auth-library": "^9.15.0",
22
- "tar": "^7.4.3",
23
48
  "ws": "^8.21.0"
24
49
  }
25
50
  }