runcloud 0.1.5 → 0.1.8

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/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.5",
4
- "description": "Create and control run.cloud remote mobile simulator sessions",
3
+ "version": "0.1.8",
4
+ "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/newly-app/newly.git",
10
+ "directory": "newly-cli"
11
+ },
7
12
  "bin": {
8
13
  "runcloud": "dist/run-cloud.js"
9
14
  },
@@ -1,33 +0,0 @@
1
- # Parallel iOS simulators
2
-
3
- Start two or three run.cloud iOS simulator sessions at the same time, open a
4
- different site in each simulator, and view the live sessions side by side.
5
- The demo releases every session automatically after two minutes. A ten-minute
6
- server-side timeout is also set in case the local process is interrupted.
7
-
8
- ## Requirements
9
-
10
- - Node.js 20 or newer
11
- - `runcloud` 0.1.3 or newer: `npm install -g runcloud`
12
- - A simulator account authenticated either with `runcloud login` or with both
13
- `RUN_CLOUD_API_KEY` and `RUN_CLOUD_API_URL`
14
- - run.cloud simulator access and enough balance for two or three sessions
15
-
16
- The demo uses the CLI credential resolution path. It does not require both a
17
- saved login and an API key.
18
-
19
- ## Run
20
-
21
- ```bash
22
- npm run demo -- --open
23
- ```
24
-
25
- The default starts three sessions, opens the session URLs in browser tabs, and
26
- releases the sessions after 120 seconds. To run a smaller or shorter demo:
27
-
28
- ```bash
29
- npm run demo -- --count 2 --duration 60 --open
30
- ```
31
-
32
- Press Ctrl+C to release the sessions early. Omit `--open` when you only want
33
- the live session URLs printed to the terminal.
@@ -1,185 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
-
3
- const DEFAULT_TARGETS = [
4
- 'https://run.cloud',
5
- 'https://newly.app',
6
- 'https://reactnative.dev',
7
- ];
8
-
9
- export function parseOptions(args) {
10
- const options = { count: 3, duration: 120, open: false };
11
- for (let index = 0; index < args.length; index += 1) {
12
- const argument = args[index];
13
- if (argument === '--open') {
14
- options.open = true;
15
- continue;
16
- }
17
- if (argument === '--count' || argument === '--duration') {
18
- const raw = args[index + 1];
19
- if (!raw) throw new Error(`${argument} requires a value.`);
20
- options[argument.slice(2)] = Number(raw);
21
- index += 1;
22
- continue;
23
- }
24
- if (argument === '--help' || argument === '-h') {
25
- options.help = true;
26
- continue;
27
- }
28
- throw new Error(`Unknown option: ${argument}`);
29
- }
30
-
31
- if (!Number.isInteger(options.count) || options.count < 2 || options.count > 3) {
32
- throw new Error('--count must be 2 or 3.');
33
- }
34
- if (!Number.isInteger(options.duration) || options.duration < 1 || options.duration > 600) {
35
- throw new Error('--duration must be between 1 and 600 seconds.');
36
- }
37
- return options;
38
- }
39
-
40
- function runCloud(args) {
41
- return new Promise((resolve, reject) => {
42
- const child = spawn(process.env.RUNCLOUD_BIN || 'runcloud', args, {
43
- stdio: ['ignore', 'pipe', 'pipe'],
44
- });
45
- let stdout = '';
46
- let stderr = '';
47
- child.stdout.on('data', (chunk) => { stdout += chunk; });
48
- child.stderr.on('data', (chunk) => { stderr += chunk; });
49
- child.on('error', reject);
50
- child.on('close', (code) => {
51
- if (code !== 0) {
52
- reject(new Error(stderr.trim() || stdout.trim() || `runcloud exited with code ${code}`));
53
- return;
54
- }
55
- try {
56
- resolve(JSON.parse(stdout));
57
- } catch {
58
- reject(new Error(`runcloud returned invalid JSON: ${stdout.trim()}`));
59
- }
60
- });
61
- });
62
- }
63
-
64
- export function browserCommand(url, platform = process.platform) {
65
- return platform === 'darwin'
66
- ? ['open', [url]]
67
- : platform === 'win32'
68
- ? ['cmd', ['/c', 'start', '', url]]
69
- : ['xdg-open', [url]];
70
- }
71
-
72
- function openBrowser(url) {
73
- const command = browserCommand(url);
74
- const child = spawn(command[0], command[1], { detached: true, stdio: 'ignore' });
75
- child.on('error', () => undefined);
76
- child.unref();
77
- }
78
-
79
- function waitForDemo(seconds) {
80
- return new Promise((resolve) => {
81
- const finish = (reason) => {
82
- clearTimeout(timer);
83
- process.removeListener('SIGINT', interrupt);
84
- process.removeListener('SIGTERM', interrupt);
85
- resolve(reason);
86
- };
87
- const timer = setTimeout(() => finish('timer'), seconds * 1000);
88
- const interrupt = () => finish('interrupt');
89
- process.once('SIGINT', interrupt);
90
- process.once('SIGTERM', interrupt);
91
- });
92
- }
93
-
94
- async function releaseSessions(sessions) {
95
- if (sessions.length === 0) return;
96
- console.log('\nReleasing simulator sessions...');
97
- const results = await Promise.allSettled(
98
- sessions.map((session) => runCloud(['ios', 'delete', session.id, '--json'])),
99
- );
100
- results.forEach((result, index) => {
101
- const session = sessions[index];
102
- if (result.status === 'fulfilled') console.log(` released ${session.id}`);
103
- else console.error(` failed to release ${session.id}: ${result.reason.message}`);
104
- });
105
- }
106
-
107
- export async function runDemo(args = process.argv.slice(2)) {
108
- const options = parseOptions(args);
109
- if (options.help) {
110
- console.log('Usage: npm run demo -- [--count 2|3] [--duration seconds] [--open]');
111
- return;
112
- }
113
-
114
- console.log(`Starting ${options.count} iOS simulator sessions in parallel...`);
115
- const starts = await Promise.allSettled(
116
- Array.from({ length: options.count }, async (_, index) => {
117
- const session = await runCloud([
118
- 'ios',
119
- 'create',
120
- '--display-name',
121
- `parallel-demo-${index + 1}`,
122
- '--label',
123
- 'demo=parallel-simulators',
124
- '--hard-timeout',
125
- '10m',
126
- '--json',
127
- ]);
128
- try {
129
- await runCloud([
130
- 'ios',
131
- 'open-url',
132
- DEFAULT_TARGETS[index],
133
- '--id',
134
- session.id,
135
- '--json',
136
- ]);
137
- return { session, warning: null };
138
- } catch (error) {
139
- return {
140
- session,
141
- warning: error instanceof Error ? error.message : String(error),
142
- };
143
- }
144
- }),
145
- );
146
-
147
- const sessions = starts
148
- .filter((result) => result.status === 'fulfilled')
149
- .map((result) => result.value.session);
150
- const failures = starts
151
- .map((result, index) => ({ result, index }))
152
- .filter(({ result }) => result.status === 'rejected');
153
-
154
- starts.forEach((result, index) => {
155
- if (result.status === 'fulfilled' && result.value.warning) {
156
- console.error(` simulator ${index + 1} started, but its page did not open: ${result.value.warning}`);
157
- }
158
- });
159
- failures.forEach(({ result, index }) => {
160
- console.error(` simulator ${index + 1} failed: ${result.reason.message}`);
161
- });
162
- if (sessions.length === 0) throw new Error('No simulator sessions could be started.');
163
-
164
- console.log('\nLive sessions:');
165
- sessions.forEach((session, index) => {
166
- console.log(` ${index + 1}. ${session.url || session.baseUrl || session.id}`);
167
- if (options.open && session.url) openBrowser(session.url);
168
- });
169
-
170
- try {
171
- console.log(`\nKeeping the demo live for ${options.duration} seconds. Press Ctrl+C to finish early.`);
172
- await waitForDemo(options.duration);
173
- } finally {
174
- await releaseSessions(sessions);
175
- }
176
-
177
- if (failures.length > 0) process.exitCode = 1;
178
- }
179
-
180
- if (import.meta.url === new URL(process.argv[1], 'file:').href) {
181
- runDemo().catch((error) => {
182
- console.error(error instanceof Error ? error.message : String(error));
183
- process.exitCode = 1;
184
- });
185
- }
@@ -1,13 +0,0 @@
1
- {
2
- "name": "run-cloud-parallel-simulators-demo",
3
- "version": "1.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "demo": "node demo.mjs",
8
- "test": "node --test verify.mjs"
9
- },
10
- "engines": {
11
- "node": ">=20"
12
- }
13
- }
@@ -1,78 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
- import test from 'node:test';
4
- import { tmpdir } from 'node:os';
5
- import { dirname, join } from 'node:path';
6
- import { spawnSync } from 'node:child_process';
7
- import { fileURLToPath } from 'node:url';
8
- import { browserCommand, parseOptions } from './demo.mjs';
9
-
10
- test('defaults to three two-minute sessions', () => {
11
- assert.deepEqual(parseOptions([]), { count: 3, duration: 120, open: false });
12
- });
13
-
14
- test('accepts a two-session short demo', () => {
15
- assert.deepEqual(parseOptions(['--count', '2', '--duration', '30', '--open']), {
16
- count: 2,
17
- duration: 30,
18
- open: true,
19
- });
20
- });
21
-
22
- test('keeps the demo within the supported parallel range', () => {
23
- assert.throws(() => parseOptions(['--count', '20']), /must be 2 or 3/);
24
- });
25
-
26
- test('opens session URLs on Windows', () => {
27
- assert.deepEqual(browserCommand('https://sim.example/session', 'win32'), [
28
- 'cmd',
29
- ['/c', 'start', '', 'https://sim.example/session'],
30
- ]);
31
- });
32
-
33
- test('starts, opens, and releases every simulator', () => {
34
- const root = mkdtempSync(join(tmpdir(), 'run-cloud-parallel-demo-'));
35
- const fakeCli = join(root, 'runcloud');
36
- const operationLog = join(root, 'operations.jsonl');
37
- writeFileSync(
38
- fakeCli,
39
- `#!/usr/bin/env node
40
- import { appendFileSync } from 'node:fs';
41
- const args = process.argv.slice(2);
42
- appendFileSync(process.env.RUN_CLOUD_DEMO_LOG, JSON.stringify(args) + '\\n');
43
- if (args[0] === 'ios' && args[1] === 'create') {
44
- const name = args[args.indexOf('--display-name') + 1];
45
- process.stdout.write(JSON.stringify({ id: name, url: 'https://sim.example/' + name }));
46
- } else {
47
- process.stdout.write(JSON.stringify({ ok: true }));
48
- }
49
- `,
50
- );
51
- chmodSync(fakeCli, 0o755);
52
-
53
- try {
54
- const result = spawnSync(
55
- process.execPath,
56
- ['demo.mjs', '--count', '2', '--duration', '1'],
57
- {
58
- cwd: dirname(fileURLToPath(import.meta.url)),
59
- encoding: 'utf8',
60
- env: {
61
- ...process.env,
62
- RUNCLOUD_BIN: fakeCli,
63
- RUN_CLOUD_DEMO_LOG: operationLog,
64
- },
65
- },
66
- );
67
- assert.equal(result.status, 0, result.stderr);
68
- const operations = readFileSync(operationLog, 'utf8')
69
- .trim()
70
- .split('\n')
71
- .map((line) => JSON.parse(line));
72
- assert.equal(operations.filter((args) => args[1] === 'create').length, 2);
73
- assert.equal(operations.filter((args) => args[1] === 'open-url').length, 2);
74
- assert.equal(operations.filter((args) => args[1] === 'delete').length, 2);
75
- } finally {
76
- rmSync(root, { recursive: true, force: true });
77
- }
78
- });