@camstack/server 0.1.8 → 0.2.1

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.
Files changed (125) hide show
  1. package/package.json +9 -7
  2. package/src/__tests__/addon-install-e2e.test.ts +0 -1
  3. package/src/__tests__/addon-pages-e2e.test.ts +40 -18
  4. package/src/__tests__/addon-settings-router.spec.ts +6 -1
  5. package/src/__tests__/addon-upload.spec.ts +91 -29
  6. package/src/__tests__/agent-registry.spec.ts +26 -9
  7. package/src/__tests__/agent-status-page.spec.ts +1 -3
  8. package/src/__tests__/auth-session-cookie.test.ts +28 -1
  9. package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
  10. package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
  11. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +24 -4
  12. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
  13. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
  14. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +64 -15
  15. package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
  16. package/src/__tests__/cap-route-adapter.spec.ts +28 -15
  17. package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
  18. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
  19. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +14 -6
  20. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
  21. package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
  22. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +11 -6
  23. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
  24. package/src/__tests__/cap-routers/harness.ts +11 -7
  25. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
  26. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
  27. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
  28. package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
  29. package/src/__tests__/capability-e2e.test.ts +9 -11
  30. package/src/__tests__/cli-e2e.test.ts +80 -59
  31. package/src/__tests__/core-cap-bridge.spec.ts +3 -1
  32. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
  33. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
  34. package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
  35. package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
  36. package/src/__tests__/framework-allowlist.spec.ts +5 -4
  37. package/src/__tests__/https-e2e.test.ts +12 -6
  38. package/src/__tests__/lifecycle-e2e.test.ts +60 -11
  39. package/src/__tests__/live-events-subscription.spec.ts +17 -18
  40. package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
  41. package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
  42. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +71 -17
  43. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
  44. package/src/__tests__/native-cap-route.spec.ts +42 -19
  45. package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
  46. package/src/__tests__/singleton-contention.test.ts +23 -11
  47. package/src/__tests__/streaming-diagnostic.test.ts +156 -53
  48. package/src/__tests__/streaming-scale.test.ts +69 -35
  49. package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
  50. package/src/agent-status-page.ts +4 -3
  51. package/src/api/__tests__/addons-custom.spec.ts +22 -8
  52. package/src/api/__tests__/capabilities.router.test.ts +18 -9
  53. package/src/api/addon-upload.ts +46 -15
  54. package/src/api/addons-custom.router.ts +7 -6
  55. package/src/api/auth-whoami.ts +3 -1
  56. package/src/api/bridge-addons.router.ts +3 -1
  57. package/src/api/capabilities.router.ts +117 -78
  58. package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
  59. package/src/api/core/addon-settings.router.ts +4 -1
  60. package/src/api/core/agents.router.ts +52 -53
  61. package/src/api/core/auth.router.ts +55 -36
  62. package/src/api/core/bulk-update-coordinator.ts +25 -22
  63. package/src/api/core/cap-providers.ts +346 -202
  64. package/src/api/core/capabilities.router.ts +30 -23
  65. package/src/api/core/hwaccel.router.ts +37 -10
  66. package/src/api/core/live-events.router.ts +16 -9
  67. package/src/api/core/logs.router.ts +54 -25
  68. package/src/api/core/notifications.router.ts +2 -1
  69. package/src/api/core/repl.router.ts +1 -3
  70. package/src/api/core/settings-backend.router.ts +68 -70
  71. package/src/api/core/system-events.router.ts +41 -32
  72. package/src/api/health/health.routes.ts +7 -13
  73. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
  74. package/src/api/oauth2/consent-page.ts +4 -3
  75. package/src/api/oauth2/oauth2-routes.ts +41 -12
  76. package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
  77. package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
  78. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +10 -2
  79. package/src/api/trpc/cap-mount-helpers.ts +64 -55
  80. package/src/api/trpc/cap-route-error-formatter.ts +17 -9
  81. package/src/api/trpc/core-cap-bridge.ts +3 -1
  82. package/src/api/trpc/generated-cap-mounts.ts +593 -351
  83. package/src/api/trpc/generated-cap-routers.ts +3680 -579
  84. package/src/api/trpc/scope-access.ts +7 -7
  85. package/src/api/trpc/trpc.context.ts +7 -4
  86. package/src/api/trpc/trpc.middleware.ts +4 -2
  87. package/src/api/trpc/trpc.router.ts +79 -46
  88. package/src/auth/session-cookie.ts +10 -0
  89. package/src/boot/__tests__/integration-id-backfill.spec.ts +21 -6
  90. package/src/boot/boot-config.ts +103 -122
  91. package/src/boot/post-boot.service.ts +5 -3
  92. package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
  93. package/src/core/addon/addon-call-gateway.ts +20 -6
  94. package/src/core/addon/addon-package.service.ts +183 -89
  95. package/src/core/addon/addon-registry.service.ts +1163 -1305
  96. package/src/core/addon/addon-search.service.ts +2 -1
  97. package/src/core/addon/addon-settings-provider.ts +27 -7
  98. package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
  99. package/src/core/addon-pages/addon-pages.service.ts +3 -1
  100. package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
  101. package/src/core/agent/agent-registry.service.ts +60 -38
  102. package/src/core/auth/auth.service.spec.ts +6 -8
  103. package/src/core/config/config.service.spec.ts +1 -1
  104. package/src/core/events/event-bus.service.spec.ts +44 -21
  105. package/src/core/events/event-bus.service.ts +5 -1
  106. package/src/core/feature/feature.service.spec.ts +4 -1
  107. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
  108. package/src/core/logging/logging.service.spec.ts +61 -21
  109. package/src/core/logging/logging.service.ts +12 -3
  110. package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
  111. package/src/core/moleculer/cap-call-fn.ts +5 -1
  112. package/src/core/moleculer/cap-route-authority.ts +18 -6
  113. package/src/core/moleculer/moleculer.service.ts +120 -32
  114. package/src/core/network/network-quality.service.spec.ts +6 -1
  115. package/src/core/notification/notification-wrapper.service.ts +1 -3
  116. package/src/core/notification/toast-wrapper.service.ts +1 -5
  117. package/src/core/repl/repl-engine.service.spec.ts +66 -39
  118. package/src/core/repl/repl-engine.service.ts +11 -12
  119. package/src/core/storage/storage-location-manager.spec.ts +12 -3
  120. package/src/core/streaming/stream-probe.service.ts +22 -13
  121. package/src/core/topology/topology-emitter.service.ts +5 -1
  122. package/src/launcher.ts +14 -9
  123. package/src/main.ts +602 -531
  124. package/src/manual-boot.ts +133 -154
  125. package/tsconfig.json +20 -8
@@ -14,7 +14,7 @@ import { ReplEngineService } from './repl-engine.service'
14
14
  import { SystemEventBus } from '@camstack/core'
15
15
  import type { ReplSessionContext } from '@camstack/core'
16
16
  import type { DeviceBinding } from '@camstack/types'
17
- import { DeviceType } from '@camstack/types'
17
+ import { DeviceType, EventCategory } from '@camstack/types'
18
18
 
19
19
  // ── In-process broker api fixture ─────────────────────────────────────
20
20
  //
@@ -26,19 +26,22 @@ import { DeviceType } from '@camstack/types'
26
26
  interface BrokerApiState {
27
27
  bindings: Map<number, DeviceBinding>
28
28
  snapshots: Record<string, Record<string, Record<string, unknown>>>
29
- devices: Map<number, {
30
- id: number
31
- stableId: string
32
- addonId: string
33
- type: DeviceType
34
- name: string
35
- parentDeviceId: number | null
36
- role: string | null
37
- online: boolean
38
- features: string[]
39
- isCamera: boolean
40
- config: Record<string, unknown>
41
- }>
29
+ devices: Map<
30
+ number,
31
+ {
32
+ id: number
33
+ stableId: string
34
+ addonId: string
35
+ type: DeviceType
36
+ name: string
37
+ parentDeviceId: number | null
38
+ role: string | null
39
+ online: boolean
40
+ features: string[]
41
+ isCamera: boolean
42
+ config: Record<string, unknown>
43
+ }
44
+ >
42
45
  }
43
46
 
44
47
  function makeBrokerApi(state: BrokerApiState): unknown {
@@ -51,14 +54,18 @@ function makeBrokerApi(state: BrokerApiState): unknown {
51
54
  query: vi.fn(async () => Array.from(state.devices.values())),
52
55
  },
53
56
  getBindings: {
54
- query: vi.fn(async ({ deviceId }: { deviceId: number }) =>
55
- state.bindings.get(deviceId) ?? { deviceId, entries: [] }),
57
+ query: vi.fn(
58
+ async ({ deviceId }: { deviceId: number }) =>
59
+ state.bindings.get(deviceId) ?? { deviceId, entries: [] },
60
+ ),
56
61
  },
57
62
  },
58
63
  deviceState: {
59
64
  getCapSlice: {
60
- query: vi.fn(async ({ deviceId, capName }: { deviceId: number; capName: string }) =>
61
- state.snapshots[String(deviceId)]?.[capName] ?? null),
65
+ query: vi.fn(
66
+ async ({ deviceId, capName }: { deviceId: number; capName: string }) =>
67
+ state.snapshots[String(deviceId)]?.[capName] ?? null,
68
+ ),
62
69
  },
63
70
  getAllSnapshots: {
64
71
  query: vi.fn(async () => state.snapshots),
@@ -123,7 +130,13 @@ function makeHarness(seed?: Partial<BrokerApiState>): Harness {
123
130
  } as any
124
131
 
125
132
  const loggingService = {
126
- createLogger: () => ({ info: vi.fn(), child: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn() }),
133
+ createLogger: () => ({
134
+ info: vi.fn(),
135
+ child: vi.fn(),
136
+ debug: vi.fn(),
137
+ warn: vi.fn(),
138
+ error: vi.fn(),
139
+ }),
127
140
  } as any
128
141
 
129
142
  const service = new ReplEngineService(addonRegistry, eventBusService, loggingService)
@@ -141,20 +154,24 @@ const mkBinding = (deviceId: number, capNames: string[]): DeviceBinding => ({
141
154
  })),
142
155
  })
143
156
 
144
- const mkDevice = (id: number, overrides: Partial<BrokerApiState['devices'] extends Map<number, infer V> ? V : never> = {}): BrokerApiState['devices'] extends Map<number, infer V> ? V : never => ({
145
- id,
146
- stableId: `stable-${id}`,
147
- addonId: 'addon-test',
148
- type: DeviceType.Camera,
149
- name: `Device ${id}`,
150
- parentDeviceId: null,
151
- role: null,
152
- online: true,
153
- features: [],
154
- isCamera: true,
155
- config: {},
156
- ...overrides,
157
- } as any)
157
+ const mkDevice = (
158
+ id: number,
159
+ overrides: Partial<BrokerApiState['devices'] extends Map<number, infer V> ? V : never> = {},
160
+ ): BrokerApiState['devices'] extends Map<number, infer V> ? V : never =>
161
+ ({
162
+ id,
163
+ stableId: `stable-${id}`,
164
+ addonId: 'addon-test',
165
+ type: DeviceType.Camera,
166
+ name: `Device ${id}`,
167
+ parentDeviceId: null,
168
+ role: null,
169
+ online: true,
170
+ features: [],
171
+ isCamera: true,
172
+ config: {},
173
+ ...overrides,
174
+ }) as any
158
175
 
159
176
  const SYSTEM_CTX: ReplSessionContext = { scope: { type: 'system' }, variables: {} }
160
177
 
@@ -162,7 +179,9 @@ const SYSTEM_CTX: ReplSessionContext = { scope: { type: 'system' }, variables: {
162
179
 
163
180
  describe('ReplEngineService — basic eval', () => {
164
181
  let h: Harness
165
- beforeEach(() => { h = makeHarness() })
182
+ beforeEach(() => {
183
+ h = makeHarness()
184
+ })
166
185
 
167
186
  it('evaluates simple arithmetic expressions', async () => {
168
187
  const r = await h.service.execute('1 + 1', SYSTEM_CTX)
@@ -220,7 +239,10 @@ describe('ReplEngineService — system scope sandbox', () => {
220
239
  })
221
240
 
222
241
  it('sm.getDeviceById returns a typed proxy with sync state', async () => {
223
- const r = await h.service.execute('sm.getDeviceById(1).state.battery.value.sleeping', SYSTEM_CTX)
242
+ const r = await h.service.execute(
243
+ 'sm.getDeviceById(1).state.battery.value.sleeping',
244
+ SYSTEM_CTX,
245
+ )
224
246
  expect(r.type).toBe('value')
225
247
  expect(r.output).toBe('true')
226
248
  })
@@ -309,7 +331,7 @@ describe('ReplEngineService — device scope sandbox', () => {
309
331
  })
310
332
 
311
333
  describe('ReplEngineService — SystemManager warm-boot resilience', () => {
312
- it('warm-boot does NOT hang on `live.onEvent` (regression — broker can\'t route)', async () => {
334
+ it("warm-boot does NOT hang on `live.onEvent` (regression — broker can't route)", async () => {
313
335
  // The bug: `getBrokerApi().live.onEvent.subscribe(...)` polls
314
336
  // forever for a Moleculer service that doesn't exist. The fix
315
337
  // injects a direct EventBus adapter for `live` so SM init never
@@ -363,21 +385,26 @@ describe('ReplEngineService — SystemManager warm-boot resilience', () => {
363
385
  h.eventBus.emit({
364
386
  id: 'test-1',
365
387
  timestamp: Date.now(),
366
- category: 'device.state-changed',
388
+ category: EventCategory.DeviceStateChanged,
367
389
  source: { type: 'device', id: 1, deviceId: 1 },
368
390
  data: { deviceId: 1, capName: 'battery', slice: { sleeping: false, percentage: 90 } },
369
391
  })
370
392
  // Microtask flush.
371
393
  await new Promise((r) => setTimeout(r, 10))
372
394
 
373
- const r = await h.service.execute('sm.getDeviceById(1).state.battery.value.sleeping', SYSTEM_CTX)
395
+ const r = await h.service.execute(
396
+ 'sm.getDeviceById(1).state.battery.value.sleeping',
397
+ SYSTEM_CTX,
398
+ )
374
399
  expect(r.output).toBe('false')
375
400
  })
376
401
  })
377
402
 
378
403
  describe('ReplEngineService — error paths', () => {
379
404
  let h: Harness
380
- beforeEach(() => { h = makeHarness() })
405
+ beforeEach(() => {
406
+ h = makeHarness()
407
+ })
381
408
 
382
409
  it('returns error when accessing undefined variables', async () => {
383
410
  const r = await h.service.execute('undefinedVariable.foo', SYSTEM_CTX)
@@ -35,16 +35,13 @@ export class ReplEngineService extends ReplEngine {
35
35
  live: {
36
36
  onEvent: {
37
37
  subscribe: (input, opts) => {
38
- const off = eventBus.subscribe(
39
- { category: input.category },
40
- (evt) => {
41
- try {
42
- opts.onData({ data: evt.data })
43
- } catch (err) {
44
- opts.onError?.(err)
45
- }
46
- },
47
- )
38
+ const off = eventBus.subscribe({ category: input.category }, (evt) => {
39
+ try {
40
+ opts.onData({ data: evt.data })
41
+ } catch (err) {
42
+ opts.onError?.(err)
43
+ }
44
+ })
48
45
  return { unsubscribe: off }
49
46
  },
50
47
  },
@@ -96,7 +93,8 @@ export class ReplEngineService extends ReplEngine {
96
93
  integrations: async () => (await integrationRegistry?.listIntegrations()) ?? [],
97
94
  addons: () => addonRegistry.listAddons(),
98
95
  getDevice: (id: number) => deviceRegistry.getById(id),
99
- getIntegration: async (id: string) => (await integrationRegistry?.getIntegration(id)) ?? null,
96
+ getIntegration: async (id: string) =>
97
+ (await integrationRegistry?.getIntegration(id)) ?? null,
100
98
  getSystemMirror: () => Promise.resolve(sm),
101
99
  }
102
100
  },
@@ -131,7 +129,8 @@ export class ReplEngineService extends ReplEngine {
131
129
  const deviceRegistry = addonRegistry.getDeviceRegistry()
132
130
  const devices = deviceRegistry.getAllForAddon(addonId)
133
131
  return {
134
- getIntegration: () => integrationRegistry?.getIntegration(addonId) ?? Promise.resolve(null),
132
+ getIntegration: () =>
133
+ integrationRegistry?.getIntegration(addonId) ?? Promise.resolve(null),
135
134
  devices,
136
135
  }
137
136
  },
@@ -63,8 +63,15 @@ describe('StorageLocationManager', () => {
63
63
  it('getLocationNames returns all 6 location names', async () => {
64
64
  await manager.initializeDefaults()
65
65
  const names = manager.getLocationNames()
66
- const expected: StorageLocationName[] = ['data', 'media', 'recordings', 'models', 'cache', 'logs']
67
- expect(names.sort()).toEqual(expected.sort())
66
+ const expected: StorageLocationName[] = [
67
+ 'data',
68
+ 'media',
69
+ 'recordings',
70
+ 'models',
71
+ 'cache',
72
+ 'logs',
73
+ ]
74
+ expect(names.toSorted()).toEqual(expected.toSorted())
68
75
  })
69
76
 
70
77
  it('resolve joins subpath correctly within a location', async () => {
@@ -116,6 +123,8 @@ describe('StorageLocationManager', () => {
116
123
  })
117
124
 
118
125
  it('resolve on uninitialized location throws error', () => {
119
- expect(() => manager.resolve('media', 'file.mp4')).toThrow('Storage location "media" not initialized')
126
+ expect(() => manager.resolve('media', 'file.mp4')).toThrow(
127
+ 'Storage location "media" not initialized',
128
+ )
120
129
  })
121
130
  })
@@ -25,9 +25,7 @@ export class StreamProbeService {
25
25
  private readonly logger: IScopedLogger
26
26
  private readonly cache = new Map<string, CacheEntry>()
27
27
 
28
- constructor(
29
- loggingService: LoggingService,
30
- ) {
28
+ constructor(loggingService: LoggingService) {
31
29
  this.logger = loggingService.createLogger('StreamProbeService')
32
30
  }
33
31
 
@@ -55,7 +53,10 @@ export class StreamProbeService {
55
53
  * Generic field probe: given a field key and value, decides how to probe.
56
54
  * Stream fields (stream_*) → ffprobe, other URLs → HTTP HEAD check.
57
55
  */
58
- async probeField(key: string, value: unknown): Promise<{ status: 'ok' | 'error'; labels?: string[]; error?: string }> {
56
+ async probeField(
57
+ key: string,
58
+ value: unknown,
59
+ ): Promise<{ status: 'ok' | 'error'; labels?: string[]; error?: string }> {
59
60
  const url = String(value ?? '').trim()
60
61
  if (!url) {
61
62
  return { status: 'error', error: 'No URL provided' }
@@ -131,15 +132,23 @@ export class StreamProbeService {
131
132
  // Query first video + first audio stream in one ffprobe pass.
132
133
  // `-show_streams` returns every stream in the container; we filter
133
134
  // by codec_type when parsing. Fields cover both kinds.
134
- const { stdout } = await execFileAsync('ffprobe', [
135
- '-v', 'error',
136
- '-rtsp_transport', 'tcp',
137
- '-timeout', '5000000',
138
- '-show_entries',
139
- 'stream=codec_type,codec_name,profile,width,height,r_frame_rate,bit_rate,sample_rate,channels',
140
- '-of', 'json',
141
- url,
142
- ], { timeout: PROBE_TIMEOUT_MS })
135
+ const { stdout } = await execFileAsync(
136
+ 'ffprobe',
137
+ [
138
+ '-v',
139
+ 'error',
140
+ '-rtsp_transport',
141
+ 'tcp',
142
+ '-timeout',
143
+ '5000000',
144
+ '-show_entries',
145
+ 'stream=codec_type,codec_name,profile,width,height,r_frame_rate,bit_rate,sample_rate,channels',
146
+ '-of',
147
+ 'json',
148
+ url,
149
+ ],
150
+ { timeout: PROBE_TIMEOUT_MS },
151
+ )
143
152
 
144
153
  return this.parseOutput(stdout)
145
154
  } catch (err) {
@@ -66,7 +66,11 @@ export class TopologyEmitterService {
66
66
  if (this.debounceTimer) clearTimeout(this.debounceTimer)
67
67
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer)
68
68
  for (const unsub of this.unsubscribers) {
69
- try { unsub() } catch { /* idempotent */ }
69
+ try {
70
+ unsub()
71
+ } catch {
72
+ /* idempotent */
73
+ }
70
74
  }
71
75
  this.unsubscribers.length = 0
72
76
  }
package/src/launcher.ts CHANGED
@@ -92,8 +92,8 @@ async function applyPendingRestore(dataDir: string): Promise<void> {
92
92
  if (p === ARCHIVE_MANIFEST_NAME || p === `./${ARCHIVE_MANIFEST_NAME}`) return false
93
93
  if (!allowedPrefixes) return true
94
94
  const normalized = p.replace(/^\.\//, '')
95
- return allowedPrefixes.some((prefix) =>
96
- normalized === prefix || normalized.startsWith(`${prefix}/`),
95
+ return allowedPrefixes.some(
96
+ (prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),
97
97
  )
98
98
  },
99
99
  })
@@ -125,7 +125,9 @@ function readConfigYaml(dataDir: string): unknown {
125
125
  try {
126
126
  return yaml.load(fs.readFileSync(found, 'utf-8')) ?? {}
127
127
  } catch (err) {
128
- console.warn(`[launcher] Could not parse ${found}: ${err instanceof Error ? err.message : String(err)}`)
128
+ console.warn(
129
+ `[launcher] Could not parse ${found}: ${err instanceof Error ? err.message : String(err)}`,
130
+ )
129
131
  return null
130
132
  }
131
133
  }
@@ -164,7 +166,9 @@ function readBootstrapRequiredAddons(dataDir: string): readonly string[] | null
164
166
 
165
167
  const validation = bootstrapSchema.safeParse(raw)
166
168
  if (!validation.success) {
167
- console.warn(`[launcher] config.yaml failed bootstrapSchema validation: ${validation.error.message}`)
169
+ console.warn(
170
+ `[launcher] config.yaml failed bootstrapSchema validation: ${validation.error.message}`,
171
+ )
168
172
  return null
169
173
  }
170
174
 
@@ -209,8 +213,8 @@ async function launch(): Promise<void> {
209
213
  workspaceDir = detectWorkspacePackagesDir(__dirname)
210
214
  if (workspaceDir === null) {
211
215
  console.warn(
212
- `[launcher] installSource=${explicitSource} requested but no workspace `
213
- + `packages/ dir found from ${__dirname} — falling back to 'npm'`,
216
+ `[launcher] installSource=${explicitSource} requested but no workspace ` +
217
+ `packages/ dir found from ${__dirname} — falling back to 'npm'`,
214
218
  )
215
219
  resolvedSource = 'npm'
216
220
  }
@@ -228,7 +232,9 @@ async function launch(): Promise<void> {
228
232
  // npm. Default (undefined) → fall back to the in-kernel constant.
229
233
  const bootstrapRequired = readBootstrapRequiredAddons(dataDir)
230
234
  if (bootstrapRequired !== null) {
231
- console.log(`[launcher] bootstrap.requiredAddons from config.yaml: ${bootstrapRequired.length} packages`)
235
+ console.log(
236
+ `[launcher] bootstrap.requiredAddons from config.yaml: ${bootstrapRequired.length} packages`,
237
+ )
232
238
  await installer.ensureRequiredPackages(bootstrapRequired)
233
239
  } else {
234
240
  await installer.ensureRequiredPackages()
@@ -258,8 +264,7 @@ async function launch(): Promise<void> {
258
264
  // etc); the server-local node_modules has direct deps. Listing
259
265
  // both covers every realistic resolution.
260
266
  const workspaceRootNodeModules = path.resolve(serverDir, '..', '..', 'node_modules')
261
- const extraNodePaths = [nodeModulesDir, workspaceRootNodeModules]
262
- .filter((p) => fs.existsSync(p))
267
+ const extraNodePaths = [nodeModulesDir, workspaceRootNodeModules].filter((p) => fs.existsSync(p))
263
268
  if (extraNodePaths.length > 0) {
264
269
  const sep = process.platform === 'win32' ? ';' : ':'
265
270
  process.env['NODE_PATH'] = process.env['NODE_PATH']