runcloud 0.2.177 → 0.2.178

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.
@@ -0,0 +1,26 @@
1
+ const PATH = '/run-cloud/admin/fleet-releases';
2
+ export const RELEASE_CHANNELS = ['ios/dev', 'ios/prod', 'android/dev', 'android/prod'];
3
+ export async function listReleaseChannels(client) {
4
+ const body = await client.get(PATH, { signal: AbortSignal.timeout(30000) });
5
+ if (!body?.configured)
6
+ throw new Error('Fleet release storage is not configured on this API');
7
+ return { unavailable: body.unavailable === true, channels: body.channels ?? [] };
8
+ }
9
+ export async function promoteReleaseChannel(client, input) {
10
+ if (!RELEASE_CHANNELS.includes(input.channel))
11
+ throw new Error(`Channel must be one of ${RELEASE_CHANNELS.join(', ')}`);
12
+ const { channels, unavailable } = await listReleaseChannels(client);
13
+ if (unavailable)
14
+ throw new Error('Fleet release storage is unavailable; nothing was promoted');
15
+ const current = channels.find(channel => channel.channel === input.channel);
16
+ return await client.post(PATH, { channel: input.channel, version: input.version, reason: input.reason,
17
+ digest: input.digest ?? null, artifact: input.artifact ?? null, expectedRevision: current?.revision ?? 0 }, { signal: AbortSignal.timeout(30000) });
18
+ }
19
+ export function releaseText(channels) {
20
+ return RELEASE_CHANNELS.map(name => {
21
+ const channel = channels.find(item => item.channel === name);
22
+ return channel
23
+ ? `${name.padEnd(13)} ${channel.version} r${channel.revision} by ${channel.promotedBy} at ${channel.promotedAt}${channel.digest ? ` ${channel.digest}` : ''}`
24
+ : `${name.padEnd(13)} unset (hosts use their inventory pin)`;
25
+ }).join('\n');
26
+ }
@@ -4,6 +4,7 @@ import { readFileSync, statSync } from 'node:fs';
4
4
  import { ApiClient, ApiError } from '../api.js';
5
5
  import { requireCredentials } from '../config.js';
6
6
  import { surveyFleet, surveyText } from './run-cloud-fleet-survey.js';
7
+ import { listReleaseChannels, promoteReleaseChannel, releaseText } from './run-cloud-fleet-releases.js';
7
8
  const ROOT = '/run-cloud/admin/simulators';
8
9
  const RANGES = ['1h', '6h', '24h', '7d', '30d', '90d', '1y', '2y'];
9
10
  export const ACCESSIBILITY_CODES = ['accessibility_device_unavailable', 'accessibility_capture_failed'];
@@ -300,7 +301,7 @@ function printReport(report, json) {
300
301
  console.log(`${failure.timestamp ?? 'time missing'} ${failure.code ?? failure.type ?? 'failure'} host=${failure.hostId ?? 'unknown'} device=${failure.deviceId ?? 'unknown'} providerSession=${failure.providerSessionId ?? 'unknown'}; ${failure.correlation.causation}`);
301
302
  }
302
303
  export function registerRunCloudFleet(program) {
303
- const fleet = program.command('fleet').description('Read-only admin fleet telemetry, diagnosis, events and original failure evidence');
304
+ const fleet = program.command('fleet').description('Admin fleet telemetry, diagnosis, events, failure evidence and serve-sim release channels');
304
305
  fleet.command('survey [hostId]').description('Survey all fleet signals in seconds; authenticated admin reads, no local services')
305
306
  .option('--json', 'Structured metrics, diagnosis, observations and coverage')
306
307
  .option('--range <range>', 'Alert/input-lag window: 1h, 6h, 24h, 7d', '24h')
@@ -330,6 +331,19 @@ export function registerRunCloudFleet(program) {
330
331
  requireFleetReadAccess(result.coverage.reason);
331
332
  console.log(JSON.stringify({ coverage: result.coverage, items: result.items.map(eventEvidence) }, null, 2));
332
333
  }));
334
+ fleet.command('releases').description('Show the serve-sim release channel each pool runs (admin read)')
335
+ .option('--json', 'Structured channel records')
336
+ .action(opts => action(async () => {
337
+ const { channels, unavailable } = await listReleaseChannels(credentialsClient());
338
+ console.log(opts.json ? JSON.stringify({ unavailable, channels }, null, 2) : `${unavailable ? 'Warning: release storage was partly unavailable\n' : ''}${releaseText(channels)}`);
339
+ }));
340
+ fleet.command('promote <channel> <version>').description('Point one pool (ios|android/dev|prod) at a serve-sim build; audited admin write, no host is touched')
341
+ .requiredOption('--reason <text>', 'Why this build is promoted; kept in the audit history')
342
+ .option('--digest <sha256>', 'Artifact content digest, sha256:<64 hex>').option('--artifact <ref>', 'Where the artifact comes from, e.g. gh-run:<id>')
343
+ .action((channel, version, opts) => action(async () => {
344
+ const release = await promoteReleaseChannel(credentialsClient(), { channel, version, reason: opts.reason, digest: opts.digest, artifact: opts.artifact });
345
+ console.log(`${release.channel} -> ${release.version} (revision ${release.revision}; was ${release.previous?.version ?? 'unset'})`);
346
+ }));
333
347
  fleet.command('logs <hostId>').description('Read bounded retained host logs; no SSH or host changes')
334
348
  .option('--source <source>', 'service, warm_pool, or event_log', 'service').option('--limit <rows>', 'Maximum lines (1..500)', '100')
335
349
  .action((hostId, opts) => action(async () => {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.2.177';
1
+ export const CLI_VERSION = '0.2.178';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.177",
3
+ "version": "0.2.178",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file