runcloud 0.1.24 → 0.1.26
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 +61 -5
- package/dist/commands/build.js +17 -0
- package/dist/commands/desktop.js +9 -5
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ runcloud account # show your entitlement and metered balance
|
|
|
34
34
|
|
|
35
35
|
Email signs you in without leaving the terminal. Google and GitHub open a browser and
|
|
36
36
|
show a short code to confirm, so login works over SSH and on headless machines too —
|
|
37
|
-
pick with `--email`, `--google`, or `--
|
|
37
|
+
pick with `--email`, `--google`, `--github`, or `--browser` to skip the prompt.
|
|
38
38
|
|
|
39
39
|
The token expires — re-run `runcloud login` when a command reports you're logged out.
|
|
40
40
|
|
|
@@ -84,6 +84,8 @@ runcloud sandbox create --disk 80 # request an 80 GiB root filesy
|
|
|
84
84
|
runcloud sandbox exec <id> npm run build # run a command inside it
|
|
85
85
|
runcloud sandbox shell <id> # interactive shell, streamed over the API
|
|
86
86
|
runcloud sandbox logs <id> --lines 200 # tail its logs
|
|
87
|
+
runcloud sandbox metrics <id> --range 24h # CPU, memory, network, and disk usage
|
|
88
|
+
runcloud sandbox pause <id> # park it; `resume` brings it back warm
|
|
87
89
|
runcloud sandbox list
|
|
88
90
|
runcloud sandbox rm <id> # destroy it
|
|
89
91
|
```
|
|
@@ -112,13 +114,21 @@ runcloud sandbox key "$SANDBOX_ID" "ctrl+l"
|
|
|
112
114
|
Treat a signed desktop URL as a secret. It grants temporary access to that
|
|
113
115
|
desktop without asking the browser to send your CLI credential.
|
|
114
116
|
|
|
115
|
-
###
|
|
117
|
+
### Lifetime, pausing, and a public hostname
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
Two clocks stop a sandbox, and the first one bites after five minutes:
|
|
120
|
+
|
|
121
|
+
- `--timeout <seconds>` — maximum lifetime, wall-clock from boot or the last
|
|
122
|
+
resume, busy or not. **Defaults to 300**; `0` removes the limit.
|
|
123
|
+
- `--idle-pause <seconds>` — pause after that much inactivity.
|
|
124
|
+
|
|
125
|
+
Both *pause* rather than destroy. `runcloud sandbox resume <id>` brings a
|
|
126
|
+
sandbox back warm with its filesystem intact, and a paused sandbox is destroyed
|
|
127
|
+
after 48 hours. `--persistent` stops both clocks; `--expose` implies it and
|
|
128
|
+
publishes a guest port at a stable `<name>-box.run.cloud` hostname.
|
|
120
129
|
|
|
121
130
|
```bash
|
|
131
|
+
runcloud sandbox create --timeout 3600 # an hour of wall clock, then pause
|
|
122
132
|
runcloud sandbox create --idle-pause 600 # pause after 10 idle minutes
|
|
123
133
|
runcloud sandbox create --name my-project --persistent # never pauses
|
|
124
134
|
runcloud sandbox create --name my-project --expose 3000 # persistent + https://my-project-box.run.cloud
|
|
@@ -126,6 +136,10 @@ runcloud sandbox expose <id> --port 8080 # expose later, or m
|
|
|
126
136
|
runcloud sandbox get <id> # inspect, including the hostname
|
|
127
137
|
```
|
|
128
138
|
|
|
139
|
+
The CLI only sets the timer at create time. To extend it on a running sandbox —
|
|
140
|
+
what a long job or a live agent session needs — call
|
|
141
|
+
`sandbox.setTimeout(id, seconds)` from [`@run-cloud/sdk`](https://www.npmjs.com/package/@run-cloud/sdk).
|
|
142
|
+
|
|
129
143
|
`--expose` needs `--name` — that name is the hostname prefix, and it is fixed
|
|
130
144
|
once minted.
|
|
131
145
|
|
|
@@ -152,6 +166,48 @@ runcloud sandbox restore <snapshot-id> --name fork # fork a NEW sandbox from t
|
|
|
152
166
|
runcloud sandbox snapshot rm <snapshot-id>
|
|
153
167
|
```
|
|
154
168
|
|
|
169
|
+
### Secrets
|
|
170
|
+
|
|
171
|
+
Store secrets in a named group, then attach the group when you create a sandbox:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
runcloud secret-group create aws --key AWS_ACCESS_KEY_ID --key AWS_SECRET_ACCESS_KEY
|
|
175
|
+
runcloud secret-group create prod --from-dotenv ./.env.production
|
|
176
|
+
runcloud secret-group list
|
|
177
|
+
runcloud secret-group show aws # names only, never values
|
|
178
|
+
runcloud secrets set DATABASE_URL --group prod # hidden prompt, or --stdin
|
|
179
|
+
runcloud secrets set-file sa.json --group prod --path .config/gcloud/sa.json --from-file ./sa.json
|
|
180
|
+
|
|
181
|
+
runcloud sandbox create --secret-group prod --env LOG_LEVEL=debug
|
|
182
|
+
runcloud sandbox create --secret prod/DATABASE_URL # one secret out of a group
|
|
183
|
+
runcloud sandbox create --no-secrets # state explicitly that it holds none
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`--secret-group`, `--secret`, and `--env` are repeatable and **order matters** —
|
|
187
|
+
a later one wins a name collision, and `--env` is applied last.
|
|
188
|
+
|
|
189
|
+
`runcloud sandbox secrets <id>` re-attaches on a running sandbox. It *replaces*
|
|
190
|
+
the whole set rather than merging, which is how a credential gets revoked:
|
|
191
|
+
passing no flags at all takes them all back.
|
|
192
|
+
|
|
193
|
+
A value can never be passed as a command argument — it would land in your shell
|
|
194
|
+
history, in `ps` output, and in CI logs. Use a hidden prompt, a file, or stdin.
|
|
195
|
+
|
|
196
|
+
## Custom images
|
|
197
|
+
|
|
198
|
+
A sandbox boots an OCI image. Register your own once, then create sandboxes from
|
|
199
|
+
it:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
runcloud image create --ref python:3.12-slim # registers, and waits for the build
|
|
203
|
+
runcloud image list
|
|
204
|
+
runcloud image refresh python:3.12-slim # re-pin the tag to its current upstream digest
|
|
205
|
+
runcloud sandbox create --image python:3.12-slim
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Built in: `runcloud/agent-base` (the default), `runcloud/desktop`, and
|
|
209
|
+
`runcloud/box` (the default with `--expose`).
|
|
210
|
+
|
|
155
211
|
## Demos
|
|
156
212
|
|
|
157
213
|
```bash
|
package/dist/commands/build.js
CHANGED
|
@@ -194,6 +194,23 @@ async function waitForBuild(client, buildId, log) {
|
|
|
194
194
|
status = event.status;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
if (status !== 'completed' && status !== 'failed' && status !== 'cancelled') {
|
|
198
|
+
const deadline = Date.now() + 60 * 60 * 1000;
|
|
199
|
+
let lastLoggedStatus = status;
|
|
200
|
+
while (Date.now() < deadline) {
|
|
201
|
+
const build = (await client.get(`/builds/${buildId}`));
|
|
202
|
+
const current = typeof build.status === 'string' ? build.status : status;
|
|
203
|
+
if (current !== lastLoggedStatus) {
|
|
204
|
+
log(`Build ${buildId} ${current}`);
|
|
205
|
+
lastLoggedStatus = current;
|
|
206
|
+
}
|
|
207
|
+
status = current;
|
|
208
|
+
if (status === 'completed' || status === 'failed' || status === 'cancelled')
|
|
209
|
+
return status;
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
|
211
|
+
}
|
|
212
|
+
throw new Error(`iOS simulator build ${buildId} timed out`);
|
|
213
|
+
}
|
|
197
214
|
return status;
|
|
198
215
|
}
|
|
199
216
|
async function waitForAndroidApkBuild(client, buildId, log) {
|
package/dist/commands/desktop.js
CHANGED
|
@@ -11,11 +11,11 @@ export function desktopRunUrl(project, mode, start = true, platform = 'ios') {
|
|
|
11
11
|
url.searchParams.set('request', randomUUID());
|
|
12
12
|
return url.toString();
|
|
13
13
|
}
|
|
14
|
-
export async function startDesktopRun(project, mode, platform = 'ios', fetchImpl = fetch) {
|
|
14
|
+
export async function startDesktopRun(project, mode, platform = 'ios', key = 'default', sessionId, fetchImpl = fetch) {
|
|
15
15
|
const response = await fetchImpl(DESKTOP_RUN_START_URL, {
|
|
16
16
|
method: 'POST',
|
|
17
17
|
headers: { 'content-type': 'application/json' },
|
|
18
|
-
body: JSON.stringify({ cwd: resolve(project), key
|
|
18
|
+
body: JSON.stringify({ cwd: resolve(project), key, mode, platform, ...(sessionId ? { sessionId } : {}) }),
|
|
19
19
|
});
|
|
20
20
|
if (response.ok)
|
|
21
21
|
return;
|
|
@@ -38,14 +38,18 @@ export function registerDesktop(program) {
|
|
|
38
38
|
.requiredOption('--project <path>', 'local Expo project directory')
|
|
39
39
|
.option('--mode <mode>', 'simulator mode: local or remote', 'local')
|
|
40
40
|
.option('--platform <platform>', 'simulator platform: ios or android', 'ios')
|
|
41
|
+
.option('--key <key>', 'run lane key in the desktop sidecar', 'default')
|
|
42
|
+
.option('--session-id <id>', 'control-plane session id for session-scoped remote simulator builds')
|
|
43
|
+
.option('--no-open', 'skip the newly:// opener and call the running sidecar directly')
|
|
41
44
|
.action(async (opts) => {
|
|
42
45
|
if (opts.mode !== 'local' && opts.mode !== 'remote')
|
|
43
46
|
throw new Error('--mode must be local or remote');
|
|
44
47
|
if (opts.platform !== 'ios' && opts.platform !== 'android')
|
|
45
48
|
throw new Error('--platform must be ios or android');
|
|
46
49
|
const url = desktopRunUrl(opts.project, opts.mode, false, opts.platform);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if (opts.open)
|
|
51
|
+
openDesktopUrl(url);
|
|
52
|
+
await startDesktopRun(opts.project, opts.mode, opts.platform, opts.key, opts.sessionId);
|
|
53
|
+
console.log(`${opts.open ? 'Opened Newly and started' : 'Started'} the ${opts.mode} ${opts.platform} simulator run for ${resolve(opts.project)}`);
|
|
50
54
|
});
|
|
51
55
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.1.
|
|
1
|
+
export const CLI_VERSION = '0.1.26';
|