serve-sim 0.1.38 → 0.1.39

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.
@@ -72,6 +72,8 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p);
72
72
  AVCaptureDeviceInput *SimCamFakeInputForPosition(AVCaptureDevicePosition p);
73
73
 
74
74
  AVCaptureConnection *SimCamFakeConnectionForOutput(AVCaptureOutput *out);
75
+ void SimCamSetOutputInput(AVCaptureOutput *out, AVCaptureInput *input);
76
+ AVCaptureInput *SimCamOutputInput(AVCaptureOutput *out);
75
77
 
76
78
  CMAttitude *SimCamSharedAttitude(void);
77
79
  CMAccelerometerData *SimCamSharedAccelerometerData(void);
@@ -149,6 +149,8 @@ void SimCamLogSwallowedRuntimeError(NSString *via, id object, NSDictionary *user
149
149
  - (NSArray *)supportedColorSpaces { return @[]; }
150
150
  - (NSArray *)supportedDepthDataFormats { return @[]; }
151
151
  - (BOOL)isPortraitEffectSupported { return NO; }
152
+ - (NSArray<Class> *)unsupportedCaptureOutputClasses { return @[]; }
153
+ - (BOOL)isStreamingDisparitySupported { return NO; }
152
154
  - (float)minISO { return 25.0f; }
153
155
  - (float)maxISO { return 6400.0f; }
154
156
  - (CMTime)minExposureDuration { return CMTimeMake(1, 8000); }
@@ -286,6 +288,8 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p) {
286
288
  - (AVCaptureVideoOrientation)_videoOrientation;
287
289
  @end
288
290
 
291
+ static AVCaptureInputPort *SimCamFakeInputPortForInput(AVCaptureInput *input, AVCaptureDevicePosition position);
292
+
289
293
  @implementation SimCamFakeConnection {
290
294
  __weak AVCaptureOutput *_outputRef;
291
295
  AVCaptureDevicePosition _position;
@@ -311,8 +315,12 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p) {
311
315
  return c;
312
316
  }
313
317
  - (AVCaptureOutput *)output { return _outputRef; }
314
- - (NSArray *)inputPorts { return @[]; }
315
- - (AVCaptureInput *)input { return nil; }
318
+ - (NSArray *)inputPorts {
319
+ AVCaptureInput *input = SimCamOutputInput(_outputRef) ?: SimCamFakeInputForPosition(_position);
320
+ AVCaptureInputPort *port = SimCamFakeInputPortForInput(input, _position);
321
+ return port ? @[port] : @[];
322
+ }
323
+ - (AVCaptureInput *)input { return SimCamOutputInput(_outputRef) ?: SimCamFakeInputForPosition(_position); }
316
324
  - (AVCaptureVideoPreviewLayer *)videoPreviewLayer { return nil; }
317
325
  - (BOOL)isEnabled { return _enabled; }
318
326
  - (void)setEnabled:(BOOL)e { _enabled = e; }
@@ -411,6 +419,67 @@ AVCaptureConnection *SimCamFakeConnectionForOutput(AVCaptureOutput *out) {
411
419
  return conn;
412
420
  }
413
421
 
422
+ #pragma mark - SimCamFakeInputPort
423
+
424
+ @interface SimCamFakeInputPort : AVCaptureInputPort
425
+ @end
426
+
427
+ @implementation SimCamFakeInputPort {
428
+ __weak AVCaptureInput *_inputRef;
429
+ AVCaptureDevicePosition _position;
430
+ }
431
+ + (instancetype)allocWithZone:(NSZone *)zone {
432
+ return class_createInstance([SimCamFakeInputPort class], 0);
433
+ }
434
+ + (instancetype)portForInput:(AVCaptureInput *)input position:(AVCaptureDevicePosition)position {
435
+ SimCamFakeInputPort *p = [self alloc];
436
+ if (p) {
437
+ p->_inputRef = input;
438
+ p->_position = position;
439
+ }
440
+ return p;
441
+ }
442
+ - (AVCaptureInput *)input { return _inputRef; }
443
+ - (AVMediaType)mediaType { return AVMediaTypeVideo; }
444
+ - (AVCaptureDeviceType)sourceDeviceType { return AVCaptureDeviceTypeBuiltInWideAngleCamera; }
445
+ - (AVCaptureDevicePosition)sourceDevicePosition { return _position; }
446
+ - (CMFormatDescriptionRef)formatDescription { return SimCamSharedFakeFormat().formatDescription; }
447
+ - (BOOL)isEnabled { return YES; }
448
+ - (void)setEnabled:(BOOL)enabled { (void)enabled; }
449
+ @end
450
+
451
+ static char kSimCamOutputInputRefKey;
452
+ static char kSimCamFakeInputPortKey;
453
+
454
+ void SimCamSetOutputInput(AVCaptureOutput *out, AVCaptureInput *input) {
455
+ if (!out) return;
456
+ if (!input) {
457
+ objc_setAssociatedObject(out, &kSimCamOutputInputRefKey, nil, OBJC_ASSOCIATION_RETAIN);
458
+ return;
459
+ }
460
+ SimCamWeakRef *ref = [SimCamWeakRef new];
461
+ ref.target = input;
462
+ objc_setAssociatedObject(out, &kSimCamOutputInputRefKey, ref, OBJC_ASSOCIATION_RETAIN);
463
+ }
464
+
465
+ AVCaptureInput *SimCamOutputInput(AVCaptureOutput *out) {
466
+ if (!out) return nil;
467
+ SimCamWeakRef *ref = objc_getAssociatedObject(out, &kSimCamOutputInputRefKey);
468
+ return ref.target;
469
+ }
470
+
471
+ static AVCaptureInputPort *SimCamFakeInputPortForInput(AVCaptureInput *input, AVCaptureDevicePosition position) {
472
+ if (!input) return nil;
473
+ AVCaptureInputPort *port = objc_getAssociatedObject(input, &kSimCamFakeInputPortKey);
474
+ if (!port) {
475
+ port = (AVCaptureInputPort *)[SimCamFakeInputPort portForInput:input position:position];
476
+ if (port) {
477
+ objc_setAssociatedObject(input, &kSimCamFakeInputPortKey, port, OBJC_ASSOCIATION_RETAIN);
478
+ }
479
+ }
480
+ return port;
481
+ }
482
+
414
483
  #pragma mark - SimCamFakeInput marking
415
484
 
416
485
  static char kSimCamFakeInputKey;
@@ -200,6 +200,7 @@ static BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
200
200
  static char kSimCamSessionRunningKey;
201
201
  static char kSimCamSessionInputsKey;
202
202
  static char kSimCamSessionOutputsKey;
203
+ static char kSimCamOutputAttachedToFakeSessionKey;
203
204
 
204
205
  static NSMutableArray *SimCamSessionTrackedInputs(AVCaptureSession *s) {
205
206
  NSMutableArray *arr = objc_getAssociatedObject(s, &kSimCamSessionInputsKey);
@@ -218,6 +219,39 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
218
219
  return arr;
219
220
  }
220
221
 
222
+ static AVCaptureInput *SimCamFirstFakeInputForSession(AVCaptureSession *s) {
223
+ for (AVCaptureInput *candidate in SimCamSessionTrackedInputs(s)) {
224
+ if (SimCamIsFakeInput(candidate)) return candidate;
225
+ }
226
+ return nil;
227
+ }
228
+
229
+ // Real AVFoundation only exposes output connections after an output has been
230
+ // attached to a session. Keep this per-output so newly created outputs still
231
+ // look disconnected during client-side session configuration checks.
232
+ static void SimCamMarkOutputAttachedToFakeSession(AVCaptureSession *s, AVCaptureOutput *output) {
233
+ if (!output) return;
234
+ objc_setAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey, @YES, OBJC_ASSOCIATION_RETAIN);
235
+ SimCamSetOutputInput(output, SimCamFirstFakeInputForSession(s));
236
+ }
237
+ static void SimCamUnmarkOutputAttachedToFakeSession(AVCaptureOutput *output) {
238
+ if (!output) return;
239
+ objc_setAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey, nil, OBJC_ASSOCIATION_RETAIN);
240
+ SimCamSetOutputInput(output, nil);
241
+ }
242
+ static BOOL SimCamOutputAttachedToFakeSession(AVCaptureOutput *output) {
243
+ if (!output) return NO;
244
+ return [objc_getAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey) boolValue];
245
+ }
246
+ static void SimCamRefreshAttachedOutputInputsForSession(AVCaptureSession *s) {
247
+ AVCaptureInput *input = SimCamFirstFakeInputForSession(s);
248
+ for (AVCaptureOutput *output in SimCamSessionTrackedOutputs(s)) {
249
+ if (SimCamOutputAttachedToFakeSession(output)) {
250
+ SimCamSetOutputInput(output, input);
251
+ }
252
+ }
253
+ }
254
+
221
255
  @interface AVCaptureSession (SimCam)
222
256
  @end
223
257
  @implementation AVCaptureSession (SimCam)
@@ -229,6 +263,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
229
263
  SimCamMarkSessionUsingFakeCamera(self, YES);
230
264
  NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
231
265
  if (![tracked containsObject:input]) [tracked addObject:input];
266
+ SimCamRefreshAttachedOutputInputsForSession(self);
232
267
  simcam_log(@"addInput: fake input (%@) — tracked (count=%lu), skipping native add",
233
268
  p == AVCaptureDevicePositionBack ? @"back" : @"front",
234
269
  (unsigned long)tracked.count);
@@ -248,6 +283,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
248
283
  SimCamMarkSessionUsingFakeCamera(self, YES);
249
284
  NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
250
285
  if (![tracked containsObject:input]) [tracked addObject:input];
286
+ SimCamRefreshAttachedOutputInputsForSession(self);
251
287
  simcam_log(@"addInputWithNoConnections: fake input (%@) — tracked (count=%lu), skipping native add",
252
288
  p == AVCaptureDevicePositionBack ? @"back" : @"front",
253
289
  (unsigned long)tracked.count);
@@ -259,6 +295,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
259
295
  if (SimCamIsFakeInput(input)) {
260
296
  NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
261
297
  [tracked removeObject:input];
298
+ SimCamRefreshAttachedOutputInputsForSession(self);
262
299
  if (tracked.count == 0) SimCamMarkSessionUsingFakeCamera(self, NO);
263
300
  simcam_log(@"removeInput: fake input — untracked (count=%lu)",
264
301
  (unsigned long)tracked.count);
@@ -268,6 +305,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
268
305
  }
269
306
  - (void)simcam_addOutput:(AVCaptureOutput *)output {
270
307
  SimCamSetPosition(output, SimCamPositionOf(self));
308
+ SimCamMarkOutputAttachedToFakeSession(self, output);
271
309
  SimCamMarkCameraInUse();
272
310
  NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
273
311
  if (![tracked containsObject:output]) [tracked addObject:output];
@@ -279,6 +317,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
279
317
  - (BOOL)simcam_canAddOutput:(AVCaptureOutput *)output { return YES; }
280
318
  - (void)simcam_addOutputWithNoConnections:(AVCaptureOutput *)output {
281
319
  SimCamSetPosition(output, SimCamPositionOf(self));
320
+ SimCamMarkOutputAttachedToFakeSession(self, output);
282
321
  SimCamMarkCameraInUse();
283
322
  NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
284
323
  if (![tracked containsObject:output]) [tracked addObject:output];
@@ -288,6 +327,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
288
327
  (int)SimCamPositionOf(self));
289
328
  }
290
329
  - (void)simcam_removeOutput:(AVCaptureOutput *)output {
330
+ SimCamUnmarkOutputAttachedToFakeSession(output);
291
331
  NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
292
332
  [tracked removeObject:output];
293
333
  simcam_log(@"removeOutput: %@ — untracked (count=%lu)",
@@ -436,7 +476,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
436
476
  - (AVCaptureConnection *)simcam_connectionWithMediaType:(AVMediaType)mediaType {
437
477
  AVCaptureConnection *real = [self simcam_connectionWithMediaType:mediaType];
438
478
  if (real) return real;
439
- if (!SimCamCameraIsInUse()) return nil;
479
+ if (!SimCamOutputAttachedToFakeSession(self)) return nil;
440
480
  if (![mediaType isEqualToString:AVMediaTypeVideo]) return nil;
441
481
  AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
442
482
  simcam_log(@"connectionWithMediaType:%@ → fake %p for %@ %p",
@@ -446,7 +486,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
446
486
  - (NSArray<AVCaptureConnection *> *)simcam_connections {
447
487
  NSArray *real = [self simcam_connections];
448
488
  if (real.count > 0) return real;
449
- if (!SimCamCameraIsInUse()) return real ?: @[];
489
+ if (!SimCamOutputAttachedToFakeSession(self)) return real ?: @[];
450
490
  AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
451
491
  return fake ? @[fake] : @[];
452
492
  }
package/bin/serve-sim-bin CHANGED
Binary file
package/dist/serve-sim.js CHANGED
@@ -92,7 +92,7 @@ Usage:
92
92
  serve-sim permissions reset <permission|all> <bundle-id> [-d <udid|name>]
93
93
  serve-sim permissions list [bundle-id] [-d <udid|name>]
94
94
 
95
- Permissions: ${xe().join(", ")}`),process.exit(1);let s=t.device?Y(t.device):C_();if(!s)console.error("No booted simulator. Boot one or pass -d <udid|name>."),process.exit(1);if(t.verb==="list"){let i={udid:s,bundleId:t.bundleId??null,tcc:Ti(s,t.bundleId),location:ci(s,t.bundleId),notifications:bi(s,t.bundleId)};console.log(JSON.stringify(i,null,e?0:2)),process.exit(0)}let C=t.bundleId;try{if(t.permission==="all")for(let i of xe())ft(s,"reset",i,void 0,C);else ft(s,t.verb,t.permission,t.value,C)}catch(i){console.error(i?.message??String(i)),process.exit(1)}if(e)console.log(JSON.stringify({udid:s,verb:t.verb,permission:t.permission,value:t.value??null,bundleId:C}));else{let i=t.value?` (${t.value})`:"";console.log(`\uD83D\uDD10 ${t.verb} ${t.permission}${i} for ${C} on ${s}`)}process.exit(0)}eS();var P_="./serve-sim-bin-gr4fhr28.";var D_=at(import.meta.url);function LS(){if(!X(r_))Ne(r_,{recursive:!0})}function t_(_){if(_)return NS(Ae(_));for(let e of ye()){let S=NS(e);if(S)return S}return null}var we={at:0,booted:null};function gy(){let _=Date.now();if(we.booted&&_-we.at<1000)return we.booted;try{let e=k("xcrun simctl list devices booted -j",{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3000}),S=JSON.parse(e),t=new Set;for(let s of Object.values(S.devices))for(let C of s)if(C.state==="Booted")t.add(C.udid);return we={at:_,booted:t},t}catch{return null}}function NS(_){try{if(!X(_))return o_("state file missing %s",_),null;let e=JSON.parse(S_(_,"utf-8"));try{process.kill(e.pid,0)}catch{return o_("helper pid %d dead, removing stale state %s",e.pid,_),U_(_),null}let S=gy();if(S&&!S.has(e.device)){o_("helper pid %d bound to non-booted device %s — killing stale helper",e.pid,e.device),console.error(`[serve-sim] Helper pid ${e.pid} is bound to device ${e.device} which is no longer booted — killing stale helper.`);try{process.kill(e.pid,"SIGTERM")}catch{}try{U_(_)}catch{}return null}return o_("state ok pid=%d device=%s port=%d",e.pid,e.device,e.port),e}catch(e){return o_("readStateFile threw for %s: %o",_,e),null}}function ge(){let _=[];for(let e of ye()){let S=NS(e);if(S)_.push(S)}return _}function ps(_){LS(),ue(Ae(_.device),JSON.stringify(_,null,2)),o_("wrote state pid=%d device=%s port=%d",_.pid,_.device,_.port)}function Se(_){if(_){o_("clearState device=%s",_);try{U_(Ae(_))}catch{}}else{o_("clearState (all)");for(let e of ye())try{U_(e)}catch{}}}function py(){let _=P_.startsWith("/$bunfs/");if(!_&&X(P_))return P_;if(!_){let C=i_(D_,"../bin/serve-sim-bin");if(X(C))return C;throw Error(`serve-sim-bin not found. Run 'bun run build:swift' first.
95
+ Permissions: ${xe().join(", ")}`),process.exit(1);let s=t.device?Y(t.device):C_();if(!s)console.error("No booted simulator. Boot one or pass -d <udid|name>."),process.exit(1);if(t.verb==="list"){let i={udid:s,bundleId:t.bundleId??null,tcc:Ti(s,t.bundleId),location:ci(s,t.bundleId),notifications:bi(s,t.bundleId)};console.log(JSON.stringify(i,null,e?0:2)),process.exit(0)}let C=t.bundleId;try{if(t.permission==="all")for(let i of xe())ft(s,"reset",i,void 0,C);else ft(s,t.verb,t.permission,t.value,C)}catch(i){console.error(i?.message??String(i)),process.exit(1)}if(e)console.log(JSON.stringify({udid:s,verb:t.verb,permission:t.permission,value:t.value??null,bundleId:C}));else{let i=t.value?` (${t.value})`:"";console.log(`\uD83D\uDD10 ${t.verb} ${t.permission}${i} for ${C} on ${s}`)}process.exit(0)}eS();var P_="./serve-sim-bin-tahdc0bd.";var D_=at(import.meta.url);function LS(){if(!X(r_))Ne(r_,{recursive:!0})}function t_(_){if(_)return NS(Ae(_));for(let e of ye()){let S=NS(e);if(S)return S}return null}var we={at:0,booted:null};function gy(){let _=Date.now();if(we.booted&&_-we.at<1000)return we.booted;try{let e=k("xcrun simctl list devices booted -j",{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3000}),S=JSON.parse(e),t=new Set;for(let s of Object.values(S.devices))for(let C of s)if(C.state==="Booted")t.add(C.udid);return we={at:_,booted:t},t}catch{return null}}function NS(_){try{if(!X(_))return o_("state file missing %s",_),null;let e=JSON.parse(S_(_,"utf-8"));try{process.kill(e.pid,0)}catch{return o_("helper pid %d dead, removing stale state %s",e.pid,_),U_(_),null}let S=gy();if(S&&!S.has(e.device)){o_("helper pid %d bound to non-booted device %s — killing stale helper",e.pid,e.device),console.error(`[serve-sim] Helper pid ${e.pid} is bound to device ${e.device} which is no longer booted — killing stale helper.`);try{process.kill(e.pid,"SIGTERM")}catch{}try{U_(_)}catch{}return null}return o_("state ok pid=%d device=%s port=%d",e.pid,e.device,e.port),e}catch(e){return o_("readStateFile threw for %s: %o",_,e),null}}function ge(){let _=[];for(let e of ye()){let S=NS(e);if(S)_.push(S)}return _}function ps(_){LS(),ue(Ae(_.device),JSON.stringify(_,null,2)),o_("wrote state pid=%d device=%s port=%d",_.pid,_.device,_.port)}function Se(_){if(_){o_("clearState device=%s",_);try{U_(Ae(_))}catch{}}else{o_("clearState (all)");for(let e of ye())try{U_(e)}catch{}}}function py(){let _=P_.startsWith("/$bunfs/");if(!_&&X(P_))return P_;if(!_){let C=i_(D_,"../bin/serve-sim-bin");if(X(C))return C;throw Error(`serve-sim-bin not found. Run 'bun run build:swift' first.
96
96
  Checked: ${P_}, ${C}`)}let e=S_(P_),S=pS("sha256").update(e).digest("hex").slice(0,16),t=i_(ly(),"Library/Caches/serve-sim");Ne(t,{recursive:!0});let s=i_(t,`serve-sim-bin-${S}`);if(!X(s)){ue(s,e),Ny(s,493);try{k(`codesign -s - -f ${JSON.stringify(s)}`,{stdio:"ignore"})}catch{}}return s}function hs(){let _=null;try{_=k("xcode-select -p",{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{}if(!_)return process.env;let e=`${_}/Library/PrivateFrameworks`;return{...process.env,DYLD_FRAMEWORK_PATH:process.env.DYLD_FRAMEWORK_PATH?`${e}:${process.env.DYLD_FRAMEWORK_PATH}`:e}}function Ws(){try{let _=k("xcrun simctl list devices -j",{encoding:"utf-8"}),e=JSON.parse(_),S=Object.keys(e.devices).filter((t)=>/SimRuntime\.iOS-/i.test(t)).sort((t,s)=>{let C=(t.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number),i=(s.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number);return(i[0]??0)-(C[0]??0)||(i[1]??0)-(C[1]??0)});for(let t of S){let C=(e.devices[t]??[]).find((i)=>i.isAvailable!==!1&&/^iPhone\b/i.test(i.name));if(C)return{udid:C.udid,name:C.name}}}catch{}return null}function Ls(_){try{let e=k("xcrun simctl list devices -j",{encoding:"utf-8"}),S=JSON.parse(e);for(let t of Object.values(S.devices))for(let s of t)if(s.udid===_)return s.name}catch{}return null}function ds(_){try{let e=k("xcrun simctl list devices -j",{encoding:"utf-8"}),S=JSON.parse(e);for(let t of Object.values(S.devices))for(let s of t)if(s.udid===_)return s.state==="Booted"}catch{}return!1}function N_(_){try{return process.kill(_,0),!0}catch{return!1}}function VS(_){try{process.kill(_,"SIGTERM")}catch{return}let e=Date.now()+500;while(Date.now()<e)try{process.kill(_,0),L_(25)}catch{return}try{process.kill(_,"SIGKILL")}catch{}let S=Date.now()+500;while(Date.now()<S)try{process.kill(_,0),L_(25)}catch{return}}function Ly(_){try{let e=k(`lsof -ti tcp:${_}`,{encoding:"utf-8",stdio:"pipe"}).trim();if(!e)return[];let S=process.pid;return e.split(`
97
97
  `).map((t)=>parseInt(t,10)).filter((t)=>Number.isFinite(t)&&t!==S)}catch{return[]}}function hy(_){let e=Ly(_);if(e.length===0)return;console.log(`\x1B[90mPort ${_} busy, killing holder pid(s): ${e.join(", ")}\x1B[0m`);for(let S of e)try{process.kill(S,"SIGKILL")}catch{}L_(100)}function Wy(_){if(!ds(_))try{k(`xcrun simctl boot ${_}`,{encoding:"utf-8",stdio:"pipe"})}catch(e){let S=(e.stderr??e.message??"").toLowerCase();if(!S.includes("booted")&&!S.includes("current state"))throw Error(`Failed to boot device ${_}: ${e.stderr||e.message}`)}try{k("open -ga Simulator",{encoding:"utf-8",stdio:"pipe",timeout:3000})}catch{}}function dy(){let _=uy();for(let e of Object.values(_))for(let S of e??[])if(S.family==="IPv4"&&!S.internal)return S.address;return null}async function Gs(_){let e=new Set(ge().map((S)=>S.port));for(let S=_;S<_+100;S++){if(e.has(S))continue;if(await Tt(S))return S}throw Error(`No available port found in range ${_}-${_+99}`)}async function Gy(_){Wy(_);try{k(`xcrun simctl bootstatus ${_} -b`,{encoding:"utf-8",stdio:"pipe",timeout:60000})}catch(e){if(!ds(_))console.error(`Device ${_} failed to reach booted state: ${e.stderr||e.message}`),process.exit(1)}}async function Ps(_,e,S,t){let s=!1,C=Date.now();j("waitForHelperReady pid=%d url=%s",_,e);for(let R=0;R<30;R++){if(!t()){j("helper pid=%d died during /health polling (attempt %d)",_,R);break}try{if((await fetch(`${e}/health`)).ok){s=!0,j("helper pid=%d /health ok after %dms",_,Date.now()-C);break}}catch{}await new Promise((y)=>setTimeout(y,100))}if(!s)j("helper pid=%d /health never responded (%dms)",_,Date.now()-C);if(s){let R=Date.now(),y=R+8000,A=!1;while(Date.now()<y){if(await new Promise(($)=>setTimeout($,200)),!t()){j("helper pid=%d died while awaiting Capture started",_),s=!1;break}try{if(S_(S,"utf-8").includes("Capture started")){A=!0,j("helper pid=%d saw 'Capture started' after %dms",_,Date.now()-R);break}}catch{}}if(s&&!A)j("helper pid=%d ready but never logged 'Capture started' within %dms — stream may not produce frames",_,Date.now()-R)}let i="";try{i=S_(S,"utf-8").trim()}catch{}return{ready:s,log:i}}async function Py(_){let{helperPath:e,udid:S,port:t,host:s,logFile:C}=_,i=`http://${s}:${t}`;LS();let R=Ve(C,"w"),y=gS(e,[S,"--port",String(t)],{detached:!0,stdio:["ignore",R,R],env:hs()});y.unref(),le(R);let A=y.pid,$=!1;y.once("exit",()=>{$=!0});let{ready:o,log:B}=await Ps(A,i,C,()=>!$&&N_(A));return{ready:o,pid:A,exited:$||!N_(A),log:B}}async function My(_){let{helperPath:e,udid:S,port:t,host:s,logFile:C}=_,i=`http://${s}:${t}`;LS();let R=Ve(C,"w"),y=gS(e,[S,"--port",String(t)],{detached:!1,stdio:["ignore",R,R],env:hs()});le(R);let A=y.pid,$=!1;y.once("exit",()=>{$=!0});let{ready:o,log:B}=await Ps(A,i,C,()=>!$&&N_(A));return{ready:o,child:y,log:B}}async function Ms(_,e,S){j("startHelper udid=%s port=%d detach=%s",_,e,S.detach),await Gy(_);let t="127.0.0.1",s=py(),C=e_(r_,`server-${_}.log`);j("helper binary=%s logFile=%s",s,C);let i={helperPath:s,udid:_,port:e,host:t,logFile:C},R="",y=2;for(let $=1;$<=y;$++){if(j("spawn attempt %d/%d",$,y),hy(e),S.detach){let o=await Py(i);if(j("spawnHelperDetached result ready=%s pid=%d exited=%s",o.ready,o.pid,o.exited),o.ready){let B={pid:o.pid,port:e,device:_,url:`http://${t}:${e}`,streamUrl:`http://${t}:${e}/stream.mjpeg`,wsUrl:`ws://${t}:${e}/ws`};return ps(B),{pid:o.pid}}VS(o.pid),R=o.log}else{let o=await My(i);if(j("spawnHelperAttached result ready=%s pid=%d",o.ready,o.child.pid),o.ready){let B={pid:o.child.pid,port:e,device:_,url:`http://${t}:${e}`,streamUrl:`http://${t}:${e}/stream.mjpeg`,wsUrl:`ws://${t}:${e}/ws`};return ps(B),{pid:o.child.pid,child:o.child}}VS(o.child.pid),R=o.log}if($<y)await new Promise((o)=>setTimeout(o,500))}let A=R?`Helper failed:
98
98
  ${R}`:"Helper process failed to start";console.error(A),process.exit(1)}async function Uy(_,e,S){_S("follow devices=%o startPort=%d",_,e);let t=_.length>0?_.map(Y):(()=>{let A=C_();if(A)return[A];let $=Ws();if(!$)console.error("No device specified and no available iOS simulator found."),process.exit(1);if(!S)console.log(`No booted simulator — booting ${$.name}...`);return[$.udid]})(),s=new Map,C=[],i=e;for(let A of t){let $=t_(A);if($){if(!S){let v=Ls(A)??A;if(t.length>1)console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serve-sim",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Evan Bacon",