feat: add one-command mobile simulator dev loop
Adds `mobile:sim:dev` / `sim:dev` to build, install, launch, and stream the iOS simulator in one command Updates mobile docs and serve-sim guidance with the new workflow and correct preview URL behavior Bumps `serve-sim` to a newer version
This commit is contained in:
@@ -19,6 +19,7 @@ Use `serve-sim` to stream and control a booted Apple Simulator from the terminal
|
|||||||
- `bun run mobile:sim:serve`
|
- `bun run mobile:sim:serve`
|
||||||
- `bun run mobile:sim:list`
|
- `bun run mobile:sim:list`
|
||||||
- `bun run mobile:sim:kill`
|
- `bun run mobile:sim:kill`
|
||||||
|
- `bun run mobile:sim:dev` — foreground build + run + stream in one command (`--no-build` to skip the build); intended for the user, agents should prefer the discrete scripts above
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ Use `serve-sim` to stream and control a booted Apple Simulator from the terminal
|
|||||||
```sh
|
```sh
|
||||||
bun run mobile:sim:serve
|
bun run mobile:sim:serve
|
||||||
```
|
```
|
||||||
Surface the returned `url` to the user. It normally starts at `http://localhost:3200`.
|
Surface the returned `url` to the user. It normally starts at `http://127.0.0.1:3100`; always use the `url` from the JSON output rather than assuming the port.
|
||||||
|
|
||||||
4. Stop helpers when finished unless the user asks to keep them running:
|
4. Stop helpers when finished unless the user asks to keep them running:
|
||||||
```sh
|
```sh
|
||||||
@@ -52,6 +53,8 @@ Use `serve-sim` to stream and control a booted Apple Simulator from the terminal
|
|||||||
- List streams: `bunx serve-sim --list -q`
|
- List streams: `bunx serve-sim --list -q`
|
||||||
- Accessibility tree: `curl http://localhost:3100/ax`
|
- Accessibility tree: `curl http://localhost:3100/ax`
|
||||||
|
|
||||||
|
Run direct CLI commands from `packages/mobile` (the binary lives in that package; plain `serve-sim` inside `with-mobile-env.mjs` from elsewhere fails with command not found).
|
||||||
|
|
||||||
Coordinates are normalized `0..1`, not pixels. Prefer `tap` for simple taps; do not emulate taps using separate `gesture` begin/end commands because that can register as long press.
|
Coordinates are normalized `0..1`, not pixels. Prefer `tap` for simple taps; do not emulate taps using separate `gesture` begin/end commands because that can register as long press.
|
||||||
|
|
||||||
## Preconditions
|
## Preconditions
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
},
|
},
|
||||||
"packages/electron": {
|
"packages/electron": {
|
||||||
"name": "@openchamber/electron",
|
"name": "@openchamber/electron",
|
||||||
"version": "1.16.3",
|
"version": "1.17.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openchamber/web": "workspace:*",
|
"@openchamber/web": "workspace:*",
|
||||||
"better-sqlite3": "^12.10.0",
|
"better-sqlite3": "^12.10.0",
|
||||||
@@ -128,13 +128,13 @@
|
|||||||
"@capacitor/cli": "^8.4.1",
|
"@capacitor/cli": "^8.4.1",
|
||||||
"@capacitor/ios": "^8.4.1",
|
"@capacitor/ios": "^8.4.1",
|
||||||
"@types/node": "^24.3.1",
|
"@types/node": "^24.3.1",
|
||||||
"serve-sim": "^0.1.34",
|
"serve-sim": "^0.1.45",
|
||||||
"typescript": "~5.9.0",
|
"typescript": "~5.9.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@openchamber/ui",
|
"name": "@openchamber/ui",
|
||||||
"version": "1.16.3",
|
"version": "1.17.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aparajita/capacitor-secure-storage": "^8.0.0",
|
"@aparajita/capacitor-secure-storage": "^8.0.0",
|
||||||
"@base-ui/react": "^1.4.0",
|
"@base-ui/react": "^1.4.0",
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
},
|
},
|
||||||
"packages/vscode": {
|
"packages/vscode": {
|
||||||
"name": "openchamber",
|
"name": "openchamber",
|
||||||
"version": "1.16.3",
|
"version": "1.17.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openchamber/ui": "workspace:*",
|
"@openchamber/ui": "workspace:*",
|
||||||
"@opencode-ai/sdk": "1.18.8",
|
"@opencode-ai/sdk": "1.18.8",
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@openchamber/web",
|
"name": "@openchamber/web",
|
||||||
"version": "1.16.3",
|
"version": "1.17.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"openchamber": "./bin/cli.js",
|
"openchamber": "./bin/cli.js",
|
||||||
},
|
},
|
||||||
@@ -2966,7 +2966,7 @@
|
|||||||
|
|
||||||
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
||||||
|
|
||||||
"serve-sim": ["serve-sim@0.1.43", "", { "dependencies": { "inspect-webkit": "^0.0.5", "ws": "^8.21.0" }, "bin": { "serve-sim": "dist/serve-sim.js" } }, "sha512-kLcWWucVZxPD2+73EAhku6iThATYXUTYlt8M4+sw1ZHZYkfFNhqCuhy8g+Z5JHCNUTf93t2qwCHPOPqvbYKRrw=="],
|
"serve-sim": ["serve-sim@0.1.45", "", { "dependencies": { "inspect-webkit": "^0.0.5", "sonner": "^2.0.7", "ws": "^8.21.0" }, "bin": { "serve-sim": "dist/serve-sim.js" } }, "sha512-I9YBJRz7DETanzh6hRD1yaVcUPO+xe4egjGNArk22mO7SIWZVCunHVTwpAbriP7H0aZMIKfQyVpuqvYonuKxBw=="],
|
||||||
|
|
||||||
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"mobile:sim:install": "bun run --cwd packages/mobile sim:install",
|
"mobile:sim:install": "bun run --cwd packages/mobile sim:install",
|
||||||
"mobile:sim:launch": "bun run --cwd packages/mobile sim:launch",
|
"mobile:sim:launch": "bun run --cwd packages/mobile sim:launch",
|
||||||
"mobile:sim:run": "bun run --cwd packages/mobile sim:run",
|
"mobile:sim:run": "bun run --cwd packages/mobile sim:run",
|
||||||
|
"mobile:sim:dev": "bun run --cwd packages/mobile sim:dev",
|
||||||
"mobile:sim:serve": "bun run --cwd packages/mobile sim:serve",
|
"mobile:sim:serve": "bun run --cwd packages/mobile sim:serve",
|
||||||
"mobile:sim:list": "bun run --cwd packages/mobile sim:list",
|
"mobile:sim:list": "bun run --cwd packages/mobile sim:list",
|
||||||
"mobile:sim:kill": "bun run --cwd packages/mobile sim:kill",
|
"mobile:sim:kill": "bun run --cwd packages/mobile sim:kill",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Run these from `packages/mobile`, or use the root `mobile:*` aliases.
|
|||||||
- `bun run build:android:debug`: builds a debug Android APK without launching an emulator.
|
- `bun run build:android:debug`: builds a debug Android APK without launching an emulator.
|
||||||
- `bun run build:ios:simulator`: builds an iOS Simulator app without launching Xcode or Simulator.
|
- `bun run build:ios:simulator`: builds an iOS Simulator app without launching Xcode or Simulator.
|
||||||
- `bun run sim:run`: boots a simulator if needed, installs the built iOS app, and launches it.
|
- `bun run sim:run`: boots a simulator if needed, installs the built iOS app, and launches it.
|
||||||
|
- `bun run sim:dev`: one-command dev loop — builds the simulator app, installs + launches it, starts the `serve-sim` stream, and prints the preview URL; Ctrl+C stops the stream. Pass `--no-build` to skip the build step.
|
||||||
- `bun run sim:serve`: starts `serve-sim` in detached JSON mode and prints the browser preview URL.
|
- `bun run sim:serve`: starts `serve-sim` in detached JSON mode and prints the browser preview URL.
|
||||||
- `bun run sim:list`: lists running `serve-sim` streams.
|
- `bun run sim:list`: lists running `serve-sim` streams.
|
||||||
- `bun run sim:kill`: stops running `serve-sim` streams.
|
- `bun run sim:kill`: stops running `serve-sim` streams.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"sim:install": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs install\"",
|
"sim:install": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs install\"",
|
||||||
"sim:launch": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs launch\"",
|
"sim:launch": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs launch\"",
|
||||||
"sim:run": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs run\"",
|
"sim:run": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim.mjs run\"",
|
||||||
|
"sim:dev": "node scripts/with-mobile-env.mjs \"node scripts/ios-sim-dev.mjs\"",
|
||||||
"sim:serve": "node scripts/with-mobile-env.mjs \"serve-sim --detach -q\"",
|
"sim:serve": "node scripts/with-mobile-env.mjs \"serve-sim --detach -q\"",
|
||||||
"sim:list": "node scripts/with-mobile-env.mjs \"serve-sim --list -q\"",
|
"sim:list": "node scripts/with-mobile-env.mjs \"serve-sim --list -q\"",
|
||||||
"sim:kill": "node scripts/with-mobile-env.mjs \"serve-sim --kill\"",
|
"sim:kill": "node scripts/with-mobile-env.mjs \"serve-sim --kill\"",
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
"@capacitor/cli": "^8.4.1",
|
"@capacitor/cli": "^8.4.1",
|
||||||
"@capacitor/ios": "^8.4.1",
|
"@capacitor/ios": "^8.4.1",
|
||||||
"@types/node": "^24.3.1",
|
"@types/node": "^24.3.1",
|
||||||
"serve-sim": "^0.1.34",
|
"serve-sim": "^0.1.45",
|
||||||
"typescript": "~5.9.0"
|
"typescript": "~5.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// One-command simulator dev loop: build the simulator app, install + launch it,
|
||||||
|
// start the serve-sim browser stream, and print the preview URL.
|
||||||
|
// The process then stays in the foreground so Ctrl+C stops the stream helpers.
|
||||||
|
//
|
||||||
|
// Pass --no-build to skip the (slow) web + xcodebuild step and just relaunch the
|
||||||
|
// previously built app with a fresh stream.
|
||||||
|
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const mobileRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
// serve-sim resolves from the package bin dir; plain `serve-sim` is not on PATH
|
||||||
|
// when this script is invoked outside `bun run`.
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
PATH: `${join(mobileRoot, 'node_modules', '.bin')}:${process.env.PATH || ''}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (command, args, options = {}) => {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: mobileRoot,
|
||||||
|
env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: options.capture ? ['ignore', 'pipe', 'inherit'] : 'inherit',
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
console.error(`[ios-sim-dev] ${command} ${args.join(' ')} exited with ${result.status ?? result.signal}`);
|
||||||
|
process.exit(result.status ?? 1);
|
||||||
|
}
|
||||||
|
return result.stdout?.trim() ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!process.argv.includes('--no-build')) {
|
||||||
|
run('node', ['scripts/ios-sim-build.mjs']);
|
||||||
|
}
|
||||||
|
|
||||||
|
run('node', ['scripts/ios-sim.mjs', 'run']);
|
||||||
|
|
||||||
|
const serveOutput = run('serve-sim', ['--detach', '-q'], { capture: true });
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = JSON.parse(serveOutput).url;
|
||||||
|
} catch {
|
||||||
|
// fall through to the guard below
|
||||||
|
}
|
||||||
|
if (!url) {
|
||||||
|
console.error(`[ios-sim-dev] Unexpected serve-sim output: ${serveOutput}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[ios-sim-dev] Stream ready at ${url}`);
|
||||||
|
console.log('[ios-sim-dev] Press Ctrl+C to stop the stream (the simulator stays running).');
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
spawnSync('serve-sim', ['--kill'], { cwd: mobileRoot, env, stdio: 'inherit' });
|
||||||
|
process.exit(0);
|
||||||
|
};
|
||||||
|
process.on('SIGINT', stop);
|
||||||
|
process.on('SIGTERM', stop);
|
||||||
|
// Keep the foreground process alive until a signal arrives.
|
||||||
|
setInterval(() => {}, 1 << 30);
|
||||||
Reference in New Issue
Block a user