@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.
Files changed (135) hide show
  1. package/package.json +11 -9
  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 +206 -0
  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 +292 -0
  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 +177 -0
  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 +137 -0
  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 +265 -5
  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/__tests__/integration-markers.spec.ts +10 -0
  60. package/src/api/core/addon-settings.router.ts +4 -1
  61. package/src/api/core/agents.router.ts +52 -53
  62. package/src/api/core/auth.router.ts +55 -36
  63. package/src/api/core/bulk-update-coordinator.ts +25 -22
  64. package/src/api/core/cap-providers.ts +459 -166
  65. package/src/api/core/capabilities.router.ts +30 -23
  66. package/src/api/core/hwaccel.router.ts +37 -10
  67. package/src/api/core/live-events.router.ts +16 -9
  68. package/src/api/core/logs.router.ts +58 -25
  69. package/src/api/core/notifications.router.ts +2 -1
  70. package/src/api/core/repl.router.ts +1 -3
  71. package/src/api/core/settings-backend.router.ts +68 -70
  72. package/src/api/core/system-events.router.ts +41 -32
  73. package/src/api/health/health.routes.ts +7 -13
  74. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
  75. package/src/api/oauth2/consent-page.ts +4 -3
  76. package/src/api/oauth2/oauth2-routes.ts +41 -12
  77. package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
  78. package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
  79. package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
  80. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
  81. package/src/api/trpc/cap-mount-helpers.ts +64 -44
  82. package/src/api/trpc/cap-route-error-formatter.ts +17 -9
  83. package/src/api/trpc/client-ip.ts +17 -0
  84. package/src/api/trpc/core-cap-bridge.ts +3 -1
  85. package/src/api/trpc/generated-cap-mounts.ts +801 -286
  86. package/src/api/trpc/generated-cap-routers.ts +5723 -719
  87. package/src/api/trpc/scope-access.ts +7 -7
  88. package/src/api/trpc/trpc.context.ts +7 -4
  89. package/src/api/trpc/trpc.middleware.ts +4 -2
  90. package/src/api/trpc/trpc.router.ts +117 -48
  91. package/src/auth/session-cookie.ts +10 -0
  92. package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
  93. package/src/boot/boot-config.ts +103 -122
  94. package/src/boot/integration-id-backfill.ts +109 -0
  95. package/src/boot/post-boot.service.ts +5 -3
  96. package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
  97. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
  98. package/src/core/addon/addon-call-gateway.ts +20 -6
  99. package/src/core/addon/addon-package.service.ts +183 -89
  100. package/src/core/addon/addon-registry.service.ts +1212 -1267
  101. package/src/core/addon/addon-row-manifest.ts +29 -0
  102. package/src/core/addon/addon-search.service.ts +2 -1
  103. package/src/core/addon/addon-settings-provider.ts +27 -7
  104. package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
  105. package/src/core/addon-pages/addon-pages.service.ts +3 -1
  106. package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
  107. package/src/core/agent/agent-registry.service.ts +60 -38
  108. package/src/core/auth/auth.service.spec.ts +6 -8
  109. package/src/core/config/config.service.spec.ts +1 -1
  110. package/src/core/events/event-bus.service.spec.ts +44 -21
  111. package/src/core/events/event-bus.service.ts +5 -1
  112. package/src/core/feature/feature.service.spec.ts +4 -1
  113. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
  114. package/src/core/logging/logging.service.spec.ts +61 -21
  115. package/src/core/logging/logging.service.ts +19 -5
  116. package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
  117. package/src/core/moleculer/cap-call-fn.ts +5 -1
  118. package/src/core/moleculer/cap-route-authority.ts +18 -6
  119. package/src/core/moleculer/moleculer.service.ts +145 -29
  120. package/src/core/network/network-quality.service.spec.ts +7 -1
  121. package/src/core/notification/notification-wrapper.service.ts +1 -3
  122. package/src/core/notification/toast-wrapper.service.ts +1 -5
  123. package/src/core/repl/repl-engine.service.spec.ts +66 -39
  124. package/src/core/repl/repl-engine.service.ts +11 -12
  125. package/src/core/storage/storage-location-manager.spec.ts +12 -3
  126. package/src/core/streaming/stream-probe.service.ts +22 -13
  127. package/src/core/topology/topology-emitter.service.ts +5 -1
  128. package/src/launcher.ts +14 -9
  129. package/src/main.ts +658 -495
  130. package/src/manual-boot.ts +133 -154
  131. package/tsconfig.json +20 -8
  132. package/src/core/storage/settings-store.spec.ts +0 -213
  133. package/src/core/storage/settings-store.ts +0 -2
  134. package/src/core/storage/sql-schema.spec.ts +0 -140
  135. package/src/core/storage/sql-schema.ts +0 -3
@@ -18,7 +18,7 @@ import type {
18
18
  PackageVersionInfo,
19
19
  AutoUpdateChannel,
20
20
  } from '@camstack/types'
21
- import { EventCategory , errMsg } from '@camstack/types'
21
+ import { EventCategory, errMsg } from '@camstack/types'
22
22
  import {
23
23
  AddonInstaller,
24
24
  detectWorkspacePackagesDir,
@@ -74,7 +74,9 @@ function readJsonObject(filePath: string): Record<string, unknown> | null {
74
74
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
75
75
  return { ...parsed }
76
76
  }
77
- } catch { /* ignore */ }
77
+ } catch {
78
+ /* ignore */
79
+ }
78
80
  return null
79
81
  }
80
82
 
@@ -239,9 +241,13 @@ export class AddonPackageService {
239
241
  /** Install an addon from npm registry */
240
242
  async installFromNpm(name: string, version?: string): Promise<{ name: string; version: string }> {
241
243
  this.requireInstaller()
242
- this.logger.info(`installFromNpm: ${name}@${version ?? 'latest'}`, { meta: { addonsDir: this.installer!['addonsDir'] as string } })
244
+ this.logger.info(`installFromNpm: ${name}@${version ?? 'latest'}`, {
245
+ meta: { addonsDir: this.installer!['addonsDir'] as string },
246
+ })
243
247
  const result = await this.installer!.installFromNpm(name, version)
244
- this.logger.info('installFromNpm result', { meta: { name: result.name, version: result.version } })
248
+ this.logger.info('installFromNpm result', {
249
+ meta: { name: result.name, version: result.version },
250
+ })
245
251
  return result
246
252
  }
247
253
 
@@ -249,7 +255,9 @@ export class AddonPackageService {
249
255
  async installFromWorkspace(name: string): Promise<{ name: string; version: string }> {
250
256
  this.requireInstaller()
251
257
  const result = await this.installer!.install(name)
252
- this.logger.info('Installed from workspace', { meta: { name: result.name, version: result.version } })
258
+ this.logger.info('Installed from workspace', {
259
+ meta: { name: result.name, version: result.version },
260
+ })
253
261
  return result
254
262
  }
255
263
 
@@ -264,14 +272,18 @@ export class AddonPackageService {
264
272
  try {
265
273
  fs.writeFileSync(path.join(targetDir, '.install-source'), 'upload')
266
274
  } catch (err) {
267
- this.logger.debug('Non-fatal: failed to write .install-source marker', { meta: { error: errMsg(err) } })
275
+ this.logger.debug('Non-fatal: failed to write .install-source marker', {
276
+ meta: { error: errMsg(err) },
277
+ })
268
278
  }
269
279
  this.installer!.manifest.upsert(result.name, {
270
280
  version: result.version,
271
281
  source: 'upload',
272
282
  })
273
283
 
274
- this.logger.info('Installed from upload', { meta: { name: result.name, version: result.version } })
284
+ this.logger.info('Installed from upload', {
285
+ meta: { name: result.name, version: result.version },
286
+ })
275
287
  return result
276
288
  }
277
289
 
@@ -359,7 +371,9 @@ export class AddonPackageService {
359
371
 
360
372
  try {
361
373
  const installResult = await this.installFromNpm(packageName, version)
362
- this.logger.info('npm install complete', { meta: { name: installResult.name, version: installResult.version } })
374
+ this.logger.info('npm install complete', {
375
+ meta: { name: installResult.name, version: installResult.version },
376
+ })
363
377
  } catch (err) {
364
378
  const msg = errMsg(err)
365
379
  this.logger.error('npm install failed', { meta: { packageName, error: msg } })
@@ -412,7 +426,9 @@ export class AddonPackageService {
412
426
 
413
427
  try {
414
428
  const result = await this.installFromWorkspace(packageName)
415
- this.logger.info('Workspace install complete', { meta: { name: result.name, version: result.version } })
429
+ this.logger.info('Workspace install complete', {
430
+ meta: { name: result.name, version: result.version },
431
+ })
416
432
  } catch (err) {
417
433
  const msg = errMsg(err)
418
434
  this.logger.error('Workspace install failed', { meta: { error: msg } })
@@ -422,7 +438,11 @@ export class AddonPackageService {
422
438
 
423
439
  try {
424
440
  await this.reloadPackages()
425
- } catch (err) { this.logger.warn('Non-fatal: failed to reload packages after workspace install', { meta: { error: errMsg(err) } }) }
441
+ } catch (err) {
442
+ this.logger.warn('Non-fatal: failed to reload packages after workspace install', {
443
+ meta: { error: errMsg(err) },
444
+ })
445
+ }
426
446
 
427
447
  let loaded: string[] = []
428
448
  let failed: string[] = []
@@ -468,8 +488,16 @@ export class AddonPackageService {
468
488
  throw new Error(`Uninstall failed: ${msg}`, { cause: err })
469
489
  }
470
490
 
471
- await this.reloadPackages().catch((err) => { this.logger.warn('Non-fatal: failed to reload packages after uninstall', { meta: { error: errMsg(err) } }) })
472
- await this.addonRegistry.loadNewAddons().catch((err) => { this.logger.warn('Non-fatal: failed to load new addons after uninstall', { meta: { error: errMsg(err) } }) })
491
+ await this.reloadPackages().catch((err) => {
492
+ this.logger.warn('Non-fatal: failed to reload packages after uninstall', {
493
+ meta: { error: errMsg(err) },
494
+ })
495
+ })
496
+ await this.addonRegistry.loadNewAddons().catch((err) => {
497
+ this.logger.warn('Non-fatal: failed to load new addons after uninstall', {
498
+ meta: { error: errMsg(err) },
499
+ })
500
+ })
473
501
 
474
502
  this.toastService.broadcast({
475
503
  title: 'Addon Uninstalled',
@@ -516,7 +544,9 @@ export class AddonPackageService {
516
544
  installed: installed.has(name),
517
545
  })
518
546
  }
519
- } catch (err) { this.logger.warn('Failed to read workspace directory', { meta: { error: errMsg(err) } }) }
547
+ } catch (err) {
548
+ this.logger.warn('Failed to read workspace directory', { meta: { error: errMsg(err) } })
549
+ }
520
550
 
521
551
  return results
522
552
  }
@@ -528,7 +558,9 @@ export class AddonPackageService {
528
558
  /** List all installed addon packages */
529
559
  listInstalled(): InstalledPackage[] {
530
560
  if (!this.installer) {
531
- throw new Error('AddonInstaller is not available — cannot list installed packages. Ensure @camstack/kernel is installed and the addons directory is accessible')
561
+ throw new Error(
562
+ 'AddonInstaller is not available — cannot list installed packages. Ensure @camstack/kernel is installed and the addons directory is accessible',
563
+ )
532
564
  }
533
565
  return this.installer.listInstalled()
534
566
  }
@@ -597,7 +629,16 @@ export class AddonPackageService {
597
629
  const addonUpdates = await this.checkAddonPackageUpdates()
598
630
  updates.push(...addonUpdates)
599
631
 
600
- this.logger.info('Found package updates', { meta: { count: updates.length, updates: updates.map((u) => ({ name: u.name, currentVersion: u.currentVersion, latestVersion: u.latestVersion })) } })
632
+ this.logger.info('Found package updates', {
633
+ meta: {
634
+ count: updates.length,
635
+ updates: updates.map((u) => ({
636
+ name: u.name,
637
+ currentVersion: u.currentVersion,
638
+ latestVersion: u.latestVersion,
639
+ })),
640
+ },
641
+ })
601
642
 
602
643
  this.cachedUpdates = {
603
644
  updates,
@@ -730,7 +771,9 @@ export class AddonPackageService {
730
771
  })
731
772
 
732
773
  if (!response.ok) {
733
- this.logger.debug('Registry returned non-ok status', { meta: { name, status: response.status } })
774
+ this.logger.debug('Registry returned non-ok status', {
775
+ meta: { name, status: response.status },
776
+ })
734
777
  return []
735
778
  }
736
779
 
@@ -748,17 +791,15 @@ export class AddonPackageService {
748
791
  tagsByVersion.set(verStr, [...existing, tag])
749
792
  }
750
793
 
751
- const result: PackageVersionInfo[] = Object.entries(versions).map(
752
- ([ver, rawMeta]) => {
753
- const meta = asRecord(rawMeta)
754
- return {
755
- version: ver,
756
- publishedAt: asString(time[ver]),
757
- deprecated: typeof meta['deprecated'] === 'string' ? meta['deprecated'] : undefined,
758
- distTags: tagsByVersion.get(ver) ?? [],
759
- }
760
- },
761
- )
794
+ const result: PackageVersionInfo[] = Object.entries(versions).map(([ver, rawMeta]) => {
795
+ const meta = asRecord(rawMeta)
796
+ return {
797
+ version: ver,
798
+ publishedAt: asString(time[ver]),
799
+ deprecated: typeof meta['deprecated'] === 'string' ? meta['deprecated'] : undefined,
800
+ distTags: tagsByVersion.get(ver) ?? [],
801
+ }
802
+ })
762
803
 
763
804
  // Sort by published date descending (newest first)
764
805
  result.sort((a, b) => {
@@ -844,7 +885,9 @@ export class AddonPackageService {
844
885
  // Emit addon.updated lifecycle event
845
886
  this.addonRegistry.emitUpdateEvent(name, previousVersion, updatedVersion)
846
887
 
847
- this.logger.info('Addon package updated, triggering hot-reload', { meta: { name, updatedVersion } })
888
+ this.logger.info('Addon package updated, triggering hot-reload', {
889
+ meta: { name, updatedVersion },
890
+ })
848
891
  const addonId = this.extractAddonId(name)
849
892
  if (addonId) {
850
893
  try {
@@ -857,10 +900,10 @@ export class AddonPackageService {
857
900
  }
858
901
  } catch (reloadError: unknown) {
859
902
  const msg = errMsg(reloadError)
860
- this.logger.warn(
861
- 'Hot-reload failed for addon — backup retained for rollback',
862
- { tags: { addonId }, meta: { error: msg, backupDir: result.backupDir } },
863
- )
903
+ this.logger.warn('Hot-reload failed for addon — backup retained for rollback', {
904
+ tags: { addonId },
905
+ meta: { error: msg, backupDir: result.backupDir },
906
+ })
864
907
  }
865
908
  }
866
909
 
@@ -887,7 +930,9 @@ export class AddonPackageService {
887
930
  // Invalidate cache after update
888
931
  this.cachedUpdates = null
889
932
 
890
- this.logger.info('Core package updated -- restart required', { meta: { name, updatedVersion } })
933
+ this.logger.info('Core package updated -- restart required', {
934
+ meta: { name, updatedVersion },
935
+ })
891
936
  this.sendUpdateNotification(name, updatedVersion)
892
937
  return { success: true, version: updatedVersion, requiresRestart: true }
893
938
  } catch (error: unknown) {
@@ -921,9 +966,12 @@ export class AddonPackageService {
921
966
  writePendingRestart(this.resolveDataDir(), payload)
922
967
  } catch (err) {
923
968
  // Marker write failure shouldn't block the restart — log + continue.
924
- this.logger.warn('Failed to write restart marker; restart will proceed without completion toast', {
925
- meta: { error: errMsg(err) },
926
- })
969
+ this.logger.warn(
970
+ 'Failed to write restart marker; restart will proceed without completion toast',
971
+ {
972
+ meta: { error: errMsg(err) },
973
+ },
974
+ )
927
975
  }
928
976
 
929
977
  this.eventBusService.emit({
@@ -954,28 +1002,37 @@ export class AddonPackageService {
954
1002
  * snapshot returns in roughly the slowest individual lookup, not
955
1003
  * the sum.
956
1004
  */
957
- async listFrameworkPackages(): Promise<readonly {
958
- packageName: string
959
- currentVersion: string
960
- latestVersion: string | null
961
- hasUpdate: boolean
962
- description?: string
963
- }[]> {
1005
+ async listFrameworkPackages(): Promise<
1006
+ readonly {
1007
+ packageName: string
1008
+ currentVersion: string
1009
+ latestVersion: string | null
1010
+ hasUpdate: boolean
1011
+ description?: string
1012
+ }[]
1013
+ > {
964
1014
  const registry = process.env['CAMSTACK_NPM_REGISTRY']
965
1015
 
966
1016
  const rows = await Promise.all(
967
1017
  FRAMEWORK_PACKAGE_ALLOWLIST.map(async (packageName) => {
968
1018
  const manifest = readResolvedPackageManifest(packageName)
969
- const currentVersion = manifest !== null && typeof manifest['version'] === 'string'
970
- ? manifest['version']
971
- : 'unknown'
972
- const description = manifest !== null && typeof manifest['description'] === 'string'
973
- ? manifest['description']
974
- : undefined
1019
+ const currentVersion =
1020
+ manifest !== null && typeof manifest['version'] === 'string'
1021
+ ? manifest['version']
1022
+ : 'unknown'
1023
+ const description =
1024
+ manifest !== null && typeof manifest['description'] === 'string'
1025
+ ? manifest['description']
1026
+ : undefined
975
1027
 
976
1028
  let latestVersion: string | null = null
977
1029
  try {
978
- const args = ['view', `${packageName}@latest`, 'version', ...buildNpmRegistryArgs(registry)]
1030
+ const args = [
1031
+ 'view',
1032
+ `${packageName}@latest`,
1033
+ 'version',
1034
+ ...buildNpmRegistryArgs(registry),
1035
+ ]
979
1036
  const { stdout } = await execFileAsync('npm', args, { timeout: 15_000 })
980
1037
  const trimmed = stdout.trim()
981
1038
  latestVersion = trimmed.length > 0 ? trimmed : null
@@ -985,9 +1042,8 @@ export class AddonPackageService {
985
1042
  })
986
1043
  }
987
1044
 
988
- const hasUpdate = latestVersion !== null
989
- && currentVersion !== 'unknown'
990
- && latestVersion !== currentVersion
1045
+ const hasUpdate =
1046
+ latestVersion !== null && currentVersion !== 'unknown' && latestVersion !== currentVersion
991
1047
 
992
1048
  return {
993
1049
  packageName,
@@ -1038,11 +1094,16 @@ export class AddonPackageService {
1038
1094
 
1039
1095
  const appRoot = resolveFrameworkPackageAppRoot(packageName, this.logger)
1040
1096
  const fromManifest = readResolvedPackageManifest(packageName)
1041
- const fromVersion = fromManifest !== null && typeof fromManifest['version'] === 'string'
1042
- ? fromManifest['version']
1043
- : 'unknown'
1097
+ const fromVersion =
1098
+ fromManifest !== null && typeof fromManifest['version'] === 'string'
1099
+ ? fromManifest['version']
1100
+ : 'unknown'
1044
1101
  const requestedVersion = input.version ?? 'latest'
1045
- const toVersion = await resolveNpmVersion(packageName, requestedVersion, process.env['CAMSTACK_NPM_REGISTRY'])
1102
+ const toVersion = await resolveNpmVersion(
1103
+ packageName,
1104
+ requestedVersion,
1105
+ process.env['CAMSTACK_NPM_REGISTRY'],
1106
+ )
1046
1107
  const spec = `${packageName}@${toVersion}`
1047
1108
 
1048
1109
  this.logger.info('updateFrameworkPackage: installing', {
@@ -1050,7 +1111,14 @@ export class AddonPackageService {
1050
1111
  })
1051
1112
 
1052
1113
  const registry = process.env['CAMSTACK_NPM_REGISTRY']
1053
- const args = ['install', '--prefix', appRoot, spec, '--no-save', ...buildNpmRegistryArgs(registry)]
1114
+ const args = [
1115
+ 'install',
1116
+ '--prefix',
1117
+ appRoot,
1118
+ spec,
1119
+ '--no-save',
1120
+ ...buildNpmRegistryArgs(registry),
1121
+ ]
1054
1122
  await execFileAsync('npm', args, { timeout: 180_000 })
1055
1123
 
1056
1124
  if (input.deferRestart === true) {
@@ -1140,7 +1208,10 @@ export class AddonPackageService {
1140
1208
  }
1141
1209
 
1142
1210
  /** Set global auto-update settings and restart the timer */
1143
- async setAutoUpdateSettings(channel: 'off' | 'latest' | 'beta', intervalSeconds?: number): Promise<void> {
1211
+ async setAutoUpdateSettings(
1212
+ channel: 'off' | 'latest' | 'beta',
1213
+ intervalSeconds?: number,
1214
+ ): Promise<void> {
1144
1215
  this.autoUpdateConfig = {
1145
1216
  ...this.autoUpdateConfig,
1146
1217
  global: {
@@ -1158,7 +1229,10 @@ export class AddonPackageService {
1158
1229
  }
1159
1230
 
1160
1231
  /** Set per-addon auto-update override */
1161
- async setAddonAutoUpdate(addonId: string, channel: 'off' | 'latest' | 'beta' | 'inherit'): Promise<void> {
1232
+ async setAddonAutoUpdate(
1233
+ addonId: string,
1234
+ channel: 'off' | 'latest' | 'beta' | 'inherit',
1235
+ ): Promise<void> {
1162
1236
  const newOverrides = { ...this.autoUpdateConfig.overrides }
1163
1237
  if (channel === 'inherit') {
1164
1238
  delete newOverrides[addonId]
@@ -1183,10 +1257,12 @@ export class AddonPackageService {
1183
1257
  }
1184
1258
 
1185
1259
  const intervalMs = this.autoUpdateConfig.global.intervalSeconds * 1000
1186
- this.logger.info(
1187
- 'Auto-update scheduled',
1188
- { meta: { intervalSeconds: this.autoUpdateConfig.global.intervalSeconds, channel: this.autoUpdateConfig.global.channel } },
1189
- )
1260
+ this.logger.info('Auto-update scheduled', {
1261
+ meta: {
1262
+ intervalSeconds: this.autoUpdateConfig.global.intervalSeconds,
1263
+ channel: this.autoUpdateConfig.global.channel,
1264
+ },
1265
+ })
1190
1266
 
1191
1267
  this.autoUpdateTimer = setInterval(() => {
1192
1268
  this.runAutoUpdate().catch((err) => {
@@ -1226,23 +1302,25 @@ export class AddonPackageService {
1226
1302
  const distTags = asRecord(data['dist-tags'])
1227
1303
 
1228
1304
  // Determine target version based on channel
1229
- const targetVersion = effectiveChannel === 'beta'
1230
- ? asString(distTags['beta']) || asString(distTags['latest'])
1231
- : asString(distTags['latest'])
1305
+ const targetVersion =
1306
+ effectiveChannel === 'beta'
1307
+ ? asString(distTags['beta']) || asString(distTags['latest'])
1308
+ : asString(distTags['latest'])
1232
1309
 
1233
1310
  if (!targetVersion || targetVersion === pkg.version) continue
1234
1311
 
1235
- this.logger.info(
1236
- 'Auto-updating package',
1237
- { meta: { name: pkg.name, currentVersion: pkg.version, targetVersion, channel: effectiveChannel } },
1238
- )
1312
+ this.logger.info('Auto-updating package', {
1313
+ meta: {
1314
+ name: pkg.name,
1315
+ currentVersion: pkg.version,
1316
+ targetVersion,
1317
+ channel: effectiveChannel,
1318
+ },
1319
+ })
1239
1320
  await this.updatePackage(pkg.name, targetVersion)
1240
1321
  updatedCount++
1241
1322
  } catch (err) {
1242
- this.logger.warn(
1243
- 'Auto-update failed',
1244
- { meta: { name: pkg.name, error: errMsg(err) } },
1245
- )
1323
+ this.logger.warn('Auto-update failed', { meta: { name: pkg.name, error: errMsg(err) } })
1246
1324
  }
1247
1325
  }
1248
1326
 
@@ -1271,7 +1349,8 @@ export class AddonPackageService {
1271
1349
  return {
1272
1350
  global: {
1273
1351
  channel: validChannel,
1274
- intervalSeconds: typeof global['intervalSeconds'] === 'number' ? global['intervalSeconds'] : 3600,
1352
+ intervalSeconds:
1353
+ typeof global['intervalSeconds'] === 'number' ? global['intervalSeconds'] : 3600,
1275
1354
  },
1276
1355
  overrides: Object.fromEntries(
1277
1356
  Object.entries(asRecord(raw['overrides'])).map(([k, v]) => {
@@ -1285,7 +1364,9 @@ export class AddonPackageService {
1285
1364
  }
1286
1365
  }
1287
1366
  } catch (err) {
1288
- this.logger.debug('Corrupt auto-update config, falling back to defaults', { meta: { error: errMsg(err) } })
1367
+ this.logger.debug('Corrupt auto-update config, falling back to defaults', {
1368
+ meta: { error: errMsg(err) },
1369
+ })
1289
1370
  }
1290
1371
  return { global: { channel: 'off', intervalSeconds: 21600 }, overrides: {} }
1291
1372
  }
@@ -1380,7 +1461,9 @@ export class AddonPackageService {
1380
1461
  }
1381
1462
  } catch (error: unknown) {
1382
1463
  const msg = errMsg(error)
1383
- this.logger.debug('Failed to check updates for addon', { meta: { pkgJsonPath, error: msg } })
1464
+ this.logger.debug('Failed to check updates for addon', {
1465
+ meta: { pkgJsonPath, error: msg },
1466
+ })
1384
1467
  }
1385
1468
  }
1386
1469
 
@@ -1417,7 +1500,9 @@ export class AddonPackageService {
1417
1500
  signal: AbortSignal.timeout(AddonPackageService.REGISTRY_TIMEOUT_MS),
1418
1501
  })
1419
1502
  if (!response.ok) {
1420
- this.logger.debug('Registry returned non-ok status', { meta: { packageName, status: response.status } })
1503
+ this.logger.debug('Registry returned non-ok status', {
1504
+ meta: { packageName, status: response.status },
1505
+ })
1421
1506
  return null
1422
1507
  }
1423
1508
  const data = await fetchJsonObject(response)
@@ -1432,11 +1517,15 @@ export class AddonPackageService {
1432
1517
 
1433
1518
  /** Fetch npm search results for camstack addon packages (cached 5 min) */
1434
1519
  private async fetchSearchFromNpm(): Promise<readonly NpmSearchResult[]> {
1435
- if (this.searchCache && Date.now() - this.searchCache.timestamp < AddonPackageService.SEARCH_CACHE_TTL_MS) {
1520
+ if (
1521
+ this.searchCache &&
1522
+ Date.now() - this.searchCache.timestamp < AddonPackageService.SEARCH_CACHE_TTL_MS
1523
+ ) {
1436
1524
  return this.searchCache.results
1437
1525
  }
1438
1526
 
1439
- const url = 'https://registry.npmjs.org/-/v1/search?text=keywords:camstack+keywords:addon&size=250'
1527
+ const url =
1528
+ 'https://registry.npmjs.org/-/v1/search?text=keywords:camstack+keywords:addon&size=250'
1440
1529
 
1441
1530
  try {
1442
1531
  const response = await fetch(url, {
@@ -1497,7 +1586,9 @@ export class AddonPackageService {
1497
1586
  if (v) return v
1498
1587
  }
1499
1588
  } catch (err) {
1500
- this.logger.debug('Version lookup via fs failed, trying require.resolve', { meta: { packageName, error: errMsg(err) } })
1589
+ this.logger.debug('Version lookup via fs failed, trying require.resolve', {
1590
+ meta: { packageName, error: errMsg(err) },
1591
+ })
1501
1592
  }
1502
1593
 
1503
1594
  try {
@@ -1505,7 +1596,9 @@ export class AddonPackageService {
1505
1596
  const pkgJson = readJsonObject(pkgJsonPath)
1506
1597
  return asString(pkgJson?.['version'], '0.0.0')
1507
1598
  } catch (err) {
1508
- this.logger.debug('Could not resolve version, returning 0.0.0', { meta: { packageName, error: errMsg(err) } })
1599
+ this.logger.debug('Could not resolve version, returning 0.0.0', {
1600
+ meta: { packageName, error: errMsg(err) },
1601
+ })
1509
1602
  return '0.0.0'
1510
1603
  }
1511
1604
  }
@@ -1535,7 +1628,6 @@ export class AddonPackageService {
1535
1628
  return path.resolve(dataPath, 'addons')
1536
1629
  }
1537
1630
 
1538
-
1539
1631
  /** Throw if installer was not initialised */
1540
1632
  private requireInstaller(): void {
1541
1633
  if (!this.installer) {
@@ -1606,10 +1698,7 @@ function buildNpmRegistryArgs(registry: string | undefined): readonly string[] {
1606
1698
  * `npm install --prefix` side-effects into an isolated temp dir instead of
1607
1699
  * the workspace's `server/backend/node_modules/`. Never set in production.
1608
1700
  */
1609
- function resolveFrameworkPackageAppRoot(
1610
- packageName: string,
1611
- logger: IScopedLogger,
1612
- ): string {
1701
+ function resolveFrameworkPackageAppRoot(packageName: string, logger: IScopedLogger): string {
1613
1702
  const override = process.env['CAMSTACK_FRAMEWORK_APP_ROOT_OVERRIDE']
1614
1703
  if (override !== undefined && override.length > 0) {
1615
1704
  return override
@@ -1675,7 +1764,12 @@ async function resolveNpmVersion(
1675
1764
  versionSpec: string,
1676
1765
  registry: string | undefined,
1677
1766
  ): Promise<string> {
1678
- const args = ['view', `${packageName}@${versionSpec}`, 'version', ...buildNpmRegistryArgs(registry)]
1767
+ const args = [
1768
+ 'view',
1769
+ `${packageName}@${versionSpec}`,
1770
+ 'version',
1771
+ ...buildNpmRegistryArgs(registry),
1772
+ ]
1679
1773
  try {
1680
1774
  const { stdout } = await execFileAsync('npm', args, { timeout: 30_000 })
1681
1775
  const trimmed = stdout.trim()