runcloud 0.1.123 → 0.1.124
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/commands/run-cloud.js +114 -1
- package/dist/commands/xcode.js +91 -5
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.1.124.tgz +0 -0
- package/runcloud-0.1.123.tgz +0 -0
|
@@ -853,6 +853,7 @@ async function pushAsset(path, opts = {}) {
|
|
|
853
853
|
const api = client();
|
|
854
854
|
const prepared = await api.post('/run-cloud/assets/uploads', {
|
|
855
855
|
filename: name,
|
|
856
|
+
...(opts.orgId ? { orgId: opts.orgId } : {}),
|
|
856
857
|
...(opts.name ? { name: opts.name } : {}),
|
|
857
858
|
contentType: blob.type || 'application/octet-stream',
|
|
858
859
|
byteSize: blob.size,
|
|
@@ -886,6 +887,7 @@ async function createSimulatorSession(platform, opts) {
|
|
|
886
887
|
.filter((id) => typeof id === 'string' && id.length > 0),
|
|
887
888
|
];
|
|
888
889
|
return await client().post(`/run-cloud/${platform}`, {
|
|
890
|
+
...(opts.orgId ? { orgId: opts.orgId } : {}),
|
|
889
891
|
model: opts.model,
|
|
890
892
|
region: opts.region,
|
|
891
893
|
displayName: opts.displayName,
|
|
@@ -965,6 +967,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
965
967
|
.option('--model <model>', platform === 'ios' ? 'simulator model' : 'emulator model', platform === 'ios' ? 'iphone' : 'pixel')
|
|
966
968
|
.option('--region <region>', 'fleet region')
|
|
967
969
|
.option('--display-name <name>', 'human-readable name')
|
|
970
|
+
.option('--org-id <id>', 'organization to own the session')
|
|
968
971
|
.option('--label <key=value>', 'label to attach to the session', (v, p) => [...p, v], [])
|
|
969
972
|
.option('--install <file>', 'local app artifact to upload and install', (v, p) => [...p, v], [])
|
|
970
973
|
.option('--install-asset <name-or-id>', 'previously uploaded asset to install', (v, p) => [...p, v], [])
|
|
@@ -1416,6 +1419,7 @@ export function registerRunCloud(program) {
|
|
|
1416
1419
|
.description('Upload an app asset for simulator installation')
|
|
1417
1420
|
.argument('<file>')
|
|
1418
1421
|
.option('-n, --name <name>', 'asset name')
|
|
1422
|
+
.option('--org-id <id>', 'organization to own the Asset')
|
|
1419
1423
|
.option('--json', 'output JSON', false)
|
|
1420
1424
|
.action((file, opts) => action(async () => {
|
|
1421
1425
|
print(await pushAsset(file, opts), opts);
|
|
@@ -1423,9 +1427,11 @@ export function registerRunCloud(program) {
|
|
|
1423
1427
|
asset
|
|
1424
1428
|
.command('list')
|
|
1425
1429
|
.description('List uploaded assets')
|
|
1430
|
+
.option('--org-id <id>', 'organization ID')
|
|
1426
1431
|
.option('--json', 'output JSON', false)
|
|
1427
1432
|
.action((opts) => action(async () => {
|
|
1428
|
-
|
|
1433
|
+
const query = opts.orgId ? `?orgId=${encodeURIComponent(opts.orgId)}` : '';
|
|
1434
|
+
print(await client().get(`/run-cloud/assets${query}`), opts);
|
|
1429
1435
|
}));
|
|
1430
1436
|
asset
|
|
1431
1437
|
.command('pull')
|
|
@@ -1446,6 +1452,113 @@ export function registerRunCloud(program) {
|
|
|
1446
1452
|
.action((id, opts) => action(async () => {
|
|
1447
1453
|
print(await client().delete(`/run-cloud/assets/${encodeURIComponent(id)}`), opts);
|
|
1448
1454
|
}));
|
|
1455
|
+
const capability = program
|
|
1456
|
+
.command('capability')
|
|
1457
|
+
.alias('capabilities')
|
|
1458
|
+
.description('Mint, inspect, and revoke short-lived resource capabilities');
|
|
1459
|
+
capability
|
|
1460
|
+
.command('create')
|
|
1461
|
+
.description('Mint a bearer token bound to explicit org resources')
|
|
1462
|
+
.requiredOption('--scope <scope>', 'asset.read, mobile.launch, xcode.build.create, or xcode.build.read; repeatable', (value, previous) => [...previous, value], [])
|
|
1463
|
+
.option('--asset <id>', 'allowed Asset ID; repeatable', (value, previous) => [...previous, value], [])
|
|
1464
|
+
.option('--build <id>', 'allowed Xcode build ID; repeatable', (value, previous) => [...previous, value], [])
|
|
1465
|
+
.option('--idempotency-key <key>', 'allowed build or launch idempotency key; repeatable', (value, previous) => [...previous, value], [])
|
|
1466
|
+
.option('--org-id <id>', 'organization ID')
|
|
1467
|
+
.option('--name <name>', 'human-readable capability name')
|
|
1468
|
+
.option('--expires-in <seconds>', 'lifetime from 60 to 3600 seconds', '900')
|
|
1469
|
+
.option('--json', 'output JSON, including the one-time token', false)
|
|
1470
|
+
.action((opts) => action(async () => {
|
|
1471
|
+
const expiresInSeconds = Number(opts.expiresIn);
|
|
1472
|
+
if (!Number.isInteger(expiresInSeconds) || expiresInSeconds < 60 || expiresInSeconds > 3600) {
|
|
1473
|
+
throw new Error('--expires-in must be an integer from 60 to 3600');
|
|
1474
|
+
}
|
|
1475
|
+
const resources = {
|
|
1476
|
+
...(opts.asset?.length ? { assetIds: opts.asset } : {}),
|
|
1477
|
+
...(opts.build?.length ? { buildIds: opts.build } : {}),
|
|
1478
|
+
...(opts.idempotencyKey?.length ? { idempotencyKeys: opts.idempotencyKey } : {}),
|
|
1479
|
+
};
|
|
1480
|
+
if (Object.keys(resources).length === 0) {
|
|
1481
|
+
throw new Error('Provide at least one --asset, --build, or --idempotency-key resource');
|
|
1482
|
+
}
|
|
1483
|
+
print(await client().post('/run-cloud/capabilities', {
|
|
1484
|
+
scopes: opts.scope,
|
|
1485
|
+
resources,
|
|
1486
|
+
expiresInSeconds,
|
|
1487
|
+
...(opts.orgId ? { orgId: opts.orgId } : {}),
|
|
1488
|
+
...(opts.name ? { name: opts.name } : {}),
|
|
1489
|
+
}), opts);
|
|
1490
|
+
}));
|
|
1491
|
+
capability
|
|
1492
|
+
.command('list')
|
|
1493
|
+
.description('List capability metadata; secret token values are never returned')
|
|
1494
|
+
.option('--org-id <id>', 'organization ID')
|
|
1495
|
+
.option('--all', 'include expired and revoked capabilities', false)
|
|
1496
|
+
.option('--json', 'output JSON', false)
|
|
1497
|
+
.action((opts) => action(async () => {
|
|
1498
|
+
const query = new URLSearchParams();
|
|
1499
|
+
if (opts.orgId)
|
|
1500
|
+
query.set('orgId', opts.orgId);
|
|
1501
|
+
if (opts.all)
|
|
1502
|
+
query.set('active', '0');
|
|
1503
|
+
print(await client().get(`/run-cloud/capabilities${query.size ? `?${query}` : ''}`), opts);
|
|
1504
|
+
}));
|
|
1505
|
+
capability
|
|
1506
|
+
.command('revoke')
|
|
1507
|
+
.description('Revoke a capability immediately')
|
|
1508
|
+
.argument('<id>')
|
|
1509
|
+
.option('--json', 'output JSON', false)
|
|
1510
|
+
.action((id, opts) => action(async () => {
|
|
1511
|
+
print(await client().delete(`/run-cloud/capabilities/${encodeURIComponent(id)}`), opts);
|
|
1512
|
+
}));
|
|
1513
|
+
const mobile = program
|
|
1514
|
+
.command('mobile')
|
|
1515
|
+
.description('Idempotently launch an Asset or Xcode build in a mobile session');
|
|
1516
|
+
mobile
|
|
1517
|
+
.command('launch')
|
|
1518
|
+
.description('Create one session and install one reusable output')
|
|
1519
|
+
.option('--asset <id>', 'Asset ID to install')
|
|
1520
|
+
.option('--build <id>', 'successful Xcode simulator build to install')
|
|
1521
|
+
.requiredOption('--idempotency-key <key>', 'stable retry key owned by this launch intent')
|
|
1522
|
+
.option('--platform <ios|android>', 'mobile platform', 'ios')
|
|
1523
|
+
.option('--org-id <id>', 'organization ID')
|
|
1524
|
+
.option('--model <model>', 'simulator or emulator model')
|
|
1525
|
+
.option('--region <region>', 'fleet region')
|
|
1526
|
+
.option('--display-name <name>', 'human-readable session name')
|
|
1527
|
+
.option('--label <key=value>', 'session tag; repeatable', (value, previous) => [...previous, value], [])
|
|
1528
|
+
.option('--inactivity-timeout <duration>', 'idle timeout such as 60s, 3m, or none')
|
|
1529
|
+
.option('--hard-timeout <duration>', 'maximum lifetime such as 10m or 1h')
|
|
1530
|
+
.option('--codec <auto|mjpeg|webrtc>', 'viewer codec', 'auto')
|
|
1531
|
+
.option('--json', 'output JSON', false)
|
|
1532
|
+
.action((opts) => action(async () => {
|
|
1533
|
+
if (Boolean(opts.asset) === Boolean(opts.build)) {
|
|
1534
|
+
throw new Error('Provide exactly one of --asset or --build');
|
|
1535
|
+
}
|
|
1536
|
+
if (opts.platform !== 'ios' && opts.platform !== 'android') {
|
|
1537
|
+
throw new Error('--platform must be ios or android');
|
|
1538
|
+
}
|
|
1539
|
+
print(await client().postWithHeaders('/run-cloud/mobile/launch', {
|
|
1540
|
+
...(opts.asset ? { assetId: opts.asset } : { buildId: opts.build }),
|
|
1541
|
+
platform: opts.platform,
|
|
1542
|
+
...(opts.orgId ? { orgId: opts.orgId } : {}),
|
|
1543
|
+
session: {
|
|
1544
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
1545
|
+
...(opts.region ? { region: opts.region } : {}),
|
|
1546
|
+
...(opts.displayName ? { displayName: opts.displayName } : {}),
|
|
1547
|
+
tags: parseTags(opts.label),
|
|
1548
|
+
...(opts.inactivityTimeout ? { inactivityTimeout: opts.inactivityTimeout } : {}),
|
|
1549
|
+
...(opts.hardTimeout ? { hardTimeout: opts.hardTimeout } : {}),
|
|
1550
|
+
...(opts.codec ? { codec: opts.codec } : {}),
|
|
1551
|
+
},
|
|
1552
|
+
}, { 'Idempotency-Key': opts.idempotencyKey }), opts);
|
|
1553
|
+
}));
|
|
1554
|
+
mobile
|
|
1555
|
+
.command('get')
|
|
1556
|
+
.description('Read one mobile launch operation and its session or failure')
|
|
1557
|
+
.argument('<id>')
|
|
1558
|
+
.option('--json', 'output JSON', false)
|
|
1559
|
+
.action((id, opts) => action(async () => {
|
|
1560
|
+
print(await client().get(`/run-cloud/mobile/launches/${encodeURIComponent(id)}`), opts);
|
|
1561
|
+
}));
|
|
1449
1562
|
const skills = program.command('skills').description('Install run.cloud skills for AI coding agents');
|
|
1450
1563
|
skills
|
|
1451
1564
|
.command('install')
|
package/dist/commands/xcode.js
CHANGED
|
@@ -309,20 +309,22 @@ export async function requestFor(flags) {
|
|
|
309
309
|
timeoutSeconds,
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
|
-
async function logChunk(client, id, offset) {
|
|
313
|
-
return await retryXcodeRequest(async () => await client.get(`/run-cloud/xcode/builds/${encodeURIComponent(id)}/logs?offset=${offset}`));
|
|
312
|
+
async function logChunk(client, id, offset, source) {
|
|
313
|
+
return await retryXcodeRequest(async () => await client.get(`/run-cloud/xcode/builds/${encodeURIComponent(id)}/logs?offset=${offset}${source ? `&source=${source}` : ''}`));
|
|
314
314
|
}
|
|
315
315
|
async function waitForBuild(client, id, flags) {
|
|
316
316
|
const timeout = integerFlag(flags.waitTimeout, '--wait-timeout', 1, 23400, 22500);
|
|
317
317
|
const deadline = Date.now() + timeout * 1000;
|
|
318
318
|
let offset = 0;
|
|
319
|
+
let source;
|
|
319
320
|
for (;;) {
|
|
320
321
|
const build = await retryXcodeRequest(async () => await client.get(`/run-cloud/xcode/builds/${encodeURIComponent(id)}`));
|
|
321
322
|
if (build.logs.available || build.status === 'queued' || build.status === 'running') {
|
|
322
|
-
const chunk = await logChunk(client, id, offset);
|
|
323
|
+
const chunk = await logChunk(client, id, offset, source);
|
|
323
324
|
if (chunk.text)
|
|
324
325
|
(flags.json ? process.stderr : process.stdout).write(chunk.text);
|
|
325
326
|
offset = chunk.nextOffset;
|
|
327
|
+
source = chunk.source ?? source;
|
|
326
328
|
if (TERMINAL.has(build.status) && chunk.complete)
|
|
327
329
|
return build;
|
|
328
330
|
}
|
|
@@ -369,7 +371,7 @@ export function registerXcode(program) {
|
|
|
369
371
|
.option('--provisioning-profile <path>', 'provisioning profile; repeatable', repeated, [])
|
|
370
372
|
.option('--signing-method <method>', 'app-store-connect, release-testing, or debugging')
|
|
371
373
|
.option('--team-id <id>', 'Apple Developer team ID for cloud signing')
|
|
372
|
-
.option('--upload <filename>', 'filename for the retained run.cloud artifact')
|
|
374
|
+
.option('--upload <filename>', 'public filename for the retained run.cloud artifact')
|
|
373
375
|
.option('--signed-upload-url <url>', 'also PUT the artifact to this presigned HTTPS URL')
|
|
374
376
|
.option('--upload-to-appstore', 'upload the signed IPA to App Store Connect', false)
|
|
375
377
|
.option('--asc-key-id <id>', 'App Store Connect API key ID')
|
|
@@ -425,6 +427,64 @@ export function registerXcode(program) {
|
|
|
425
427
|
throw new Error(`${reason}\n${next}\nBuild ID: ${build.id}`);
|
|
426
428
|
}
|
|
427
429
|
}));
|
|
430
|
+
xcode
|
|
431
|
+
.command('rebundle')
|
|
432
|
+
.description('Reuse a compatible React Native native shell and replace only its JavaScript bundle')
|
|
433
|
+
.argument('[path]', 'local Expo or React Native source directory', '.')
|
|
434
|
+
.requiredOption('--basis-build <id>', 'successful simulator build providing the native shell')
|
|
435
|
+
.option('--expo-app-dir <path>', 'relative Expo app directory in a monorepo')
|
|
436
|
+
.option('--ignore <regex>', 'exclude source paths matching a regular expression; repeatable', repeated, [])
|
|
437
|
+
.option('--include <regex>', 'force-include matching source paths; repeatable', repeated, [])
|
|
438
|
+
.option('--additional-file <local=remote>', 'include an extra file; repeatable', repeated, [])
|
|
439
|
+
.option('--org-id <id>', 'organization to own the build')
|
|
440
|
+
.option('--idempotency-key <key>', 'stable key for retry-safe submission')
|
|
441
|
+
.option('--timeout-seconds <seconds>', 'remote bundle timeout, max 21600', '3600')
|
|
442
|
+
.option('--wait-timeout <seconds>', 'client wait timeout, max 23400', '22500')
|
|
443
|
+
.option('--no-wait', 'return after the control plane accepts the build')
|
|
444
|
+
.option('--detach', 'alias for --no-wait; terminal state remains queryable', false)
|
|
445
|
+
.option('-o, --output <path>', 'download the rebundled app here after success')
|
|
446
|
+
.option('--force', 'overwrite --output if it exists', false)
|
|
447
|
+
.option('--json', 'print the build record as JSON; streamed logs go to stderr', false)
|
|
448
|
+
.action((sourcePath, flags) => action(async () => {
|
|
449
|
+
if (!/^xcb_[A-Za-z0-9]+$/.test(flags.basisBuild)) {
|
|
450
|
+
throw new Error('--basis-build must be a run.cloud Xcode build ID');
|
|
451
|
+
}
|
|
452
|
+
const request = await requestFor({
|
|
453
|
+
...flags,
|
|
454
|
+
sdk: 'iphonesimulator',
|
|
455
|
+
configuration: 'Release',
|
|
456
|
+
});
|
|
457
|
+
request.reactNative = { ...(flags.expoAppDir ? { expoAppDir: flags.expoAppDir } : {}) };
|
|
458
|
+
request.reuse = { basisBuildId: flags.basisBuild };
|
|
459
|
+
const source = await packSource(sourcePath, flags);
|
|
460
|
+
const archiveBuffer = new ArrayBuffer(source.body.byteLength);
|
|
461
|
+
new Uint8Array(archiveBuffer).set(source.body);
|
|
462
|
+
const form = new FormData();
|
|
463
|
+
form.append('request', JSON.stringify(request));
|
|
464
|
+
form.append('source', new Blob([archiveBuffer], { type: 'application/gzip' }), source.filename);
|
|
465
|
+
const client = api();
|
|
466
|
+
const idempotencyKey = flags.idempotencyKey ?? `xcode_rebundle_${randomUUID()}`;
|
|
467
|
+
let build = await retryXcodeRequest(async () => await client.uploadForm('/run-cloud/xcode/builds', form, { 'Idempotency-Key': idempotencyKey }));
|
|
468
|
+
if (flags.detach || flags.wait === false) {
|
|
469
|
+
print(build, flags.json);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!flags.json)
|
|
473
|
+
console.error(`Rebundle ${build.id} accepted; streaming remote logs.`);
|
|
474
|
+
build = await waitForBuild(client, build.id, flags);
|
|
475
|
+
const artifactPath = flags.output && build.artifact.available
|
|
476
|
+
? await downloadArtifact(client, build, flags.output, flags.force)
|
|
477
|
+
: undefined;
|
|
478
|
+
if (flags.json)
|
|
479
|
+
print({ ...build, ...(artifactPath ? { artifactPath } : {}) }, true);
|
|
480
|
+
else if (build.status === 'succeeded') {
|
|
481
|
+
console.log(`Rebundle ${build.id} succeeded.`);
|
|
482
|
+
console.log(artifactPath ? `Artifact: ${artifactPath}` : `Asset: ${build.artifact.assetId}`);
|
|
483
|
+
}
|
|
484
|
+
if (build.status !== 'succeeded') {
|
|
485
|
+
throw new Error(`${build.failure?.reason ?? `Rebundle ended with ${build.status}`}\n${build.failure?.action ?? `Inspect logs with runcloud xcode logs ${build.id}`}\nBuild ID: ${build.id}`);
|
|
486
|
+
}
|
|
487
|
+
}));
|
|
428
488
|
xcode
|
|
429
489
|
.command('get')
|
|
430
490
|
.description('Show product-visible Xcode build state')
|
|
@@ -447,6 +507,30 @@ export function registerXcode(program) {
|
|
|
447
507
|
const result = await retryXcodeRequest(async () => await api().get(`/run-cloud/xcode/builds?${query}`));
|
|
448
508
|
print(flags.json ? result : result.items, flags.json);
|
|
449
509
|
}));
|
|
510
|
+
xcode
|
|
511
|
+
.command('events')
|
|
512
|
+
.description('Read ordered replayable Xcode build events after a durable cursor')
|
|
513
|
+
.option('--org-id <id>', 'organization ID')
|
|
514
|
+
.option('--build-id <id>', 'filter to one build')
|
|
515
|
+
.option('--after <cursor>', 'last processed event sequence', '0')
|
|
516
|
+
.option('--limit <count>', 'maximum events, 1 to 500', '100')
|
|
517
|
+
.option('--json', 'print the page, including nextCursor, as JSON', false)
|
|
518
|
+
.action((flags) => action(async () => {
|
|
519
|
+
const after = integerFlag(flags.after, '--after', 0, Number.MAX_SAFE_INTEGER, 0);
|
|
520
|
+
const limit = integerFlag(flags.limit, '--limit', 1, 500, 100);
|
|
521
|
+
const query = new URLSearchParams({ after: String(after), limit: String(limit) });
|
|
522
|
+
if (flags.orgId)
|
|
523
|
+
query.set('orgId', flags.orgId);
|
|
524
|
+
if (flags.buildId)
|
|
525
|
+
query.set('buildId', flags.buildId);
|
|
526
|
+
const page = await retryXcodeRequest(async () => await api().get(`/run-cloud/xcode/events?${query}`));
|
|
527
|
+
if (flags.json)
|
|
528
|
+
print(page, true);
|
|
529
|
+
else {
|
|
530
|
+
print(page.items);
|
|
531
|
+
console.error(`nextCursor: ${page.nextCursor}`);
|
|
532
|
+
}
|
|
533
|
+
}));
|
|
450
534
|
xcode
|
|
451
535
|
.command('logs')
|
|
452
536
|
.description('Read the sanitized live or retained Xcode build log')
|
|
@@ -456,13 +540,15 @@ export function registerXcode(program) {
|
|
|
456
540
|
.action((id, flags) => action(async () => {
|
|
457
541
|
const client = api();
|
|
458
542
|
let offset = 0;
|
|
543
|
+
let source;
|
|
459
544
|
for (;;) {
|
|
460
|
-
const chunk = await logChunk(client, id, offset);
|
|
545
|
+
const chunk = await logChunk(client, id, offset, source);
|
|
461
546
|
if (flags.json)
|
|
462
547
|
console.log(JSON.stringify(chunk));
|
|
463
548
|
else if (chunk.text)
|
|
464
549
|
process.stdout.write(chunk.text);
|
|
465
550
|
offset = chunk.nextOffset;
|
|
551
|
+
source = chunk.source ?? source;
|
|
466
552
|
if (!flags.follow || chunk.complete)
|
|
467
553
|
return;
|
|
468
554
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1000));
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.1.
|
|
1
|
+
export const CLI_VERSION = '0.1.124';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.1.123.tgz
DELETED
|
Binary file
|