@camstack/server 0.1.7 → 0.2.0
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.
- package/package.json +11 -9
- package/src/__tests__/addon-install-e2e.test.ts +0 -1
- package/src/__tests__/addon-pages-e2e.test.ts +40 -18
- package/src/__tests__/addon-settings-router.spec.ts +6 -1
- package/src/__tests__/addon-upload.spec.ts +91 -29
- package/src/__tests__/agent-registry.spec.ts +26 -9
- package/src/__tests__/agent-status-page.spec.ts +1 -3
- package/src/__tests__/auth-session-cookie.test.ts +28 -1
- package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
- package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +206 -0
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +292 -0
- package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
- package/src/__tests__/cap-route-adapter.spec.ts +28 -15
- package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +177 -0
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +137 -0
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
- package/src/__tests__/cap-routers/harness.ts +11 -7
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
- package/src/__tests__/capability-e2e.test.ts +9 -11
- package/src/__tests__/cli-e2e.test.ts +80 -59
- package/src/__tests__/core-cap-bridge.spec.ts +3 -1
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
- package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
- package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
- package/src/__tests__/framework-allowlist.spec.ts +5 -4
- package/src/__tests__/https-e2e.test.ts +12 -6
- package/src/__tests__/lifecycle-e2e.test.ts +60 -11
- package/src/__tests__/live-events-subscription.spec.ts +17 -18
- package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
- package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +265 -5
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
- package/src/__tests__/native-cap-route.spec.ts +42 -19
- package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
- package/src/__tests__/singleton-contention.test.ts +23 -11
- package/src/__tests__/streaming-diagnostic.test.ts +156 -53
- package/src/__tests__/streaming-scale.test.ts +69 -35
- package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
- package/src/agent-status-page.ts +4 -3
- package/src/api/__tests__/addons-custom.spec.ts +22 -8
- package/src/api/__tests__/capabilities.router.test.ts +18 -9
- package/src/api/addon-upload.ts +46 -15
- package/src/api/addons-custom.router.ts +7 -6
- package/src/api/auth-whoami.ts +3 -1
- package/src/api/bridge-addons.router.ts +3 -1
- package/src/api/capabilities.router.ts +117 -78
- package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
- package/src/api/core/__tests__/integration-markers.spec.ts +10 -0
- package/src/api/core/addon-settings.router.ts +4 -1
- package/src/api/core/agents.router.ts +52 -53
- package/src/api/core/auth.router.ts +55 -36
- package/src/api/core/bulk-update-coordinator.ts +25 -22
- package/src/api/core/cap-providers.ts +459 -166
- package/src/api/core/capabilities.router.ts +30 -23
- package/src/api/core/hwaccel.router.ts +37 -10
- package/src/api/core/live-events.router.ts +16 -9
- package/src/api/core/logs.router.ts +58 -25
- package/src/api/core/notifications.router.ts +2 -1
- package/src/api/core/repl.router.ts +1 -3
- package/src/api/core/settings-backend.router.ts +68 -70
- package/src/api/core/system-events.router.ts +41 -32
- package/src/api/health/health.routes.ts +7 -13
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
- package/src/api/oauth2/consent-page.ts +4 -3
- package/src/api/oauth2/oauth2-routes.ts +41 -12
- package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
- package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
- package/src/api/trpc/cap-mount-helpers.ts +64 -44
- package/src/api/trpc/cap-route-error-formatter.ts +17 -9
- package/src/api/trpc/client-ip.ts +17 -0
- package/src/api/trpc/core-cap-bridge.ts +3 -1
- package/src/api/trpc/generated-cap-mounts.ts +801 -286
- package/src/api/trpc/generated-cap-routers.ts +5723 -719
- package/src/api/trpc/scope-access.ts +7 -7
- package/src/api/trpc/trpc.context.ts +7 -4
- package/src/api/trpc/trpc.middleware.ts +4 -2
- package/src/api/trpc/trpc.router.ts +117 -48
- package/src/auth/session-cookie.ts +10 -0
- package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
- package/src/boot/boot-config.ts +103 -122
- package/src/boot/integration-id-backfill.ts +109 -0
- package/src/boot/post-boot.service.ts +5 -3
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
- package/src/core/addon/addon-call-gateway.ts +20 -6
- package/src/core/addon/addon-package.service.ts +183 -89
- package/src/core/addon/addon-registry.service.ts +1212 -1267
- package/src/core/addon/addon-row-manifest.ts +29 -0
- package/src/core/addon/addon-search.service.ts +2 -1
- package/src/core/addon/addon-settings-provider.ts +27 -7
- package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
- package/src/core/addon-pages/addon-pages.service.ts +3 -1
- package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
- package/src/core/agent/agent-registry.service.ts +60 -38
- package/src/core/auth/auth.service.spec.ts +6 -8
- package/src/core/config/config.service.spec.ts +1 -1
- package/src/core/events/event-bus.service.spec.ts +44 -21
- package/src/core/events/event-bus.service.ts +5 -1
- package/src/core/feature/feature.service.spec.ts +4 -1
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
- package/src/core/logging/logging.service.spec.ts +61 -21
- package/src/core/logging/logging.service.ts +19 -5
- package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
- package/src/core/moleculer/cap-call-fn.ts +5 -1
- package/src/core/moleculer/cap-route-authority.ts +18 -6
- package/src/core/moleculer/moleculer.service.ts +145 -29
- package/src/core/network/network-quality.service.spec.ts +7 -1
- package/src/core/notification/notification-wrapper.service.ts +1 -3
- package/src/core/notification/toast-wrapper.service.ts +1 -5
- package/src/core/repl/repl-engine.service.spec.ts +66 -39
- package/src/core/repl/repl-engine.service.ts +11 -12
- package/src/core/storage/storage-location-manager.spec.ts +12 -3
- package/src/core/streaming/stream-probe.service.ts +22 -13
- package/src/core/topology/topology-emitter.service.ts +5 -1
- package/src/launcher.ts +14 -9
- package/src/main.ts +658 -495
- package/src/manual-boot.ts +133 -154
- package/tsconfig.json +20 -8
- package/src/core/storage/settings-store.spec.ts +0 -213
- package/src/core/storage/settings-store.ts +0 -2
- package/src/core/storage/sql-schema.spec.ts +0 -140
- package/src/core/storage/sql-schema.ts +0 -3
|
@@ -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<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
55
|
-
|
|
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(
|
|
61
|
-
|
|
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: () => ({
|
|
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 = (
|
|
145
|
-
id,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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(() => {
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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(() => {
|
|
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
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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) =>
|
|
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: () =>
|
|
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[] = [
|
|
67
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
135
|
-
'
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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']
|