runcloud 0.2.191 → 0.2.193

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.
@@ -1033,6 +1033,52 @@ async function openSimulatorSessionUrl(platform, sessionId, url) {
1033
1033
  throw error;
1034
1034
  }
1035
1035
  }
1036
+ function parseCoordinate(value, name) {
1037
+ const limit = name === 'latitude' ? 90 : 180;
1038
+ const parsed = Number(value);
1039
+ if (value.trim() === '' || !Number.isFinite(parsed) || Math.abs(parsed) > limit) {
1040
+ throw new Error(`--${name} must be a number between -${limit} and ${limit}`);
1041
+ }
1042
+ return parsed;
1043
+ }
1044
+ async function setSimulatorSessionLocation(platform, sessionId, point) {
1045
+ const path = `/run-cloud/${platform}/${encodeURIComponent(sessionId)}/location`;
1046
+ try {
1047
+ const response = (point ? await client().post(path, point) : await client().delete(path));
1048
+ const result = typeof response === 'object' && response !== null && !Array.isArray(response)
1049
+ ? response
1050
+ : {};
1051
+ if (result.ok !== true || result.platform !== platform || result.sessionId !== sessionId || result.cleared !== (point === null)) {
1052
+ throw new ApiError(0, 'Simulator location returned an invalid acknowledgement', path, {
1053
+ code: 'location_invalid_response',
1054
+ retryable: true,
1055
+ sessionId,
1056
+ platform,
1057
+ action: 'location',
1058
+ });
1059
+ }
1060
+ return result;
1061
+ }
1062
+ catch (error) {
1063
+ if (error instanceof ApiTransportError) {
1064
+ throw new ApiError(0, 'Simulator location could not reach the run.cloud API', path, {
1065
+ code: 'location_transport_error',
1066
+ retryable: true,
1067
+ sessionId,
1068
+ platform,
1069
+ action: 'location',
1070
+ });
1071
+ }
1072
+ if (error instanceof ApiError) {
1073
+ Object.assign(error, {
1074
+ sessionId: error.sessionId ?? sessionId,
1075
+ platform: error.platform ?? platform,
1076
+ action: error.action ?? 'location',
1077
+ });
1078
+ }
1079
+ throw error;
1080
+ }
1081
+ }
1036
1082
  function registerSimulatorCommands(program, platform) {
1037
1083
  const label = platform === 'android' ? 'Android emulator' : 'iOS simulator';
1038
1084
  const article = platform === 'android' ? 'an' : 'an';
@@ -1133,6 +1179,33 @@ function registerSimulatorCommands(program, platform) {
1133
1179
  .action((url, opts) => action(async () => {
1134
1180
  print(await openSimulatorSessionUrl(platform, opts.id, url), opts);
1135
1181
  }, opts));
1182
+ const location = simulator
1183
+ .command('location')
1184
+ .description(`Set or clear the simulated GPS location of ${article} ${label} session`);
1185
+ location
1186
+ .command('set')
1187
+ .description('Set the simulated location (WGS84 degrees)')
1188
+ .argument('<id>', 'session id')
1189
+ .requiredOption('--latitude <degrees>', 'latitude from -90 to 90')
1190
+ .requiredOption('--longitude <degrees>', 'longitude from -180 to 180')
1191
+ .option('--json', 'output JSON', false)
1192
+ .action((id, opts) => action(async () => {
1193
+ const point = {
1194
+ latitude: parseCoordinate(opts.latitude, 'latitude'),
1195
+ longitude: parseCoordinate(opts.longitude, 'longitude'),
1196
+ };
1197
+ print(await setSimulatorSessionLocation(platform, id, point), opts);
1198
+ }, opts));
1199
+ location
1200
+ .command('clear')
1201
+ .description(platform === 'android'
1202
+ ? 'Return to the emulator default location'
1203
+ : 'Remove the simulated location')
1204
+ .argument('<id>', 'session id')
1205
+ .option('--json', 'output JSON', false)
1206
+ .action((id, opts) => action(async () => {
1207
+ print(await setSimulatorSessionLocation(platform, id, null), opts);
1208
+ }, opts));
1136
1209
  addInteractionOptions(simulator
1137
1210
  .command('tap')
1138
1211
  .description(`Tap normalized display coordinates in ${article} ${label} session`)
@@ -1448,8 +1521,38 @@ function registerSimulatorCommands(program, platform) {
1448
1521
  writeSimulatorLogEntry(entry, opts.json === true);
1449
1522
  }
1450
1523
  }, opts));
1524
+ if (platform === 'ios') {
1525
+ simulator
1526
+ .command('crashes')
1527
+ .description('Show native crash reports from an active iOS session, symbolicated from Xcode build dSYMs')
1528
+ .argument('<id>')
1529
+ .option('--json', 'output JSON', false)
1530
+ .action((id, opts) => action(async () => {
1531
+ const result = await client().get(`/run-cloud/ios/${encodeURIComponent(id)}/crashes`);
1532
+ if (opts.json) {
1533
+ print(result, opts);
1534
+ return;
1535
+ }
1536
+ const crashes = Array.isArray(result.crashes) ? result.crashes : [];
1537
+ if (crashes.length === 0)
1538
+ console.error('No crash reports captured for this lease.');
1539
+ for (const crash of crashes)
1540
+ console.log(formatSimulatorCrash(crash));
1541
+ }, opts));
1542
+ }
1451
1543
  return simulator;
1452
1544
  }
1545
+ export function formatSimulatorCrash(crash) {
1546
+ const exception = [crash.exception?.type, crash.exception?.signal].filter(Boolean).join(' / ');
1547
+ const lines = [`${crash.capturedAt} ${crash.process ?? 'unknown process'}${exception ? ` — ${exception}` : ''} (${crash.symbolication ?? 'unknown'})`];
1548
+ const thread = crash.threads?.find((candidate) => candidate.crashed) ?? crash.threads?.[0];
1549
+ for (const frame of thread?.frames.slice(0, 32) ?? []) {
1550
+ const location = frame.file ? ` (${frame.file}${frame.line ? `:${frame.line}` : ''})` : '';
1551
+ const name = frame.symbol ?? `${frame.address}${frame.marker ? ` [${frame.marker}]` : ''}`;
1552
+ lines.push(` ${String(frame.index).padStart(2)} ${(frame.image ?? '???').padEnd(24)} ${name}${location}`);
1553
+ }
1554
+ return lines.join('\n');
1555
+ }
1453
1556
  function jsonErrorEnvelope(error) {
1454
1557
  if (error instanceof ApiError) {
1455
1558
  return {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.2.191';
1
+ export const CLI_VERSION = '0.2.193';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.191",
3
+ "version": "0.2.193",
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
@@ -40,7 +40,7 @@ runcloud ios get "$SESSION_ID" --json
40
40
  ```
41
41
 
42
42
  Replace `ios` with `android` for an Android artifact. The shared lifecycle is
43
- `create`, `list`, `get`, `open-url`, `logs`, and `delete`. Use `--json` whenever
43
+ `create`, `list`, `get`, `open-url`, `location set|clear`, `logs`, and `delete`. Use `--json` whenever
44
44
  another program consumes output, and inspect `runcloud ios|android --help` for
45
45
  create and log options.
46
46
 
Binary file