@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.
- package/package.json +9 -7
- 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 +24 -4
- 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 +64 -15
- 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 +14 -6
- 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 +11 -6
- 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 +71 -17
- 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/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 +346 -202
- 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 +54 -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__/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 +10 -2
- package/src/api/trpc/cap-mount-helpers.ts +64 -55
- package/src/api/trpc/cap-route-error-formatter.ts +17 -9
- package/src/api/trpc/core-cap-bridge.ts +3 -1
- package/src/api/trpc/generated-cap-mounts.ts +593 -351
- package/src/api/trpc/generated-cap-routers.ts +3680 -579
- 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 +79 -46
- package/src/auth/session-cookie.ts +10 -0
- package/src/boot/__tests__/integration-id-backfill.spec.ts +21 -6
- package/src/boot/boot-config.ts +103 -122
- 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/addon-call-gateway.ts +20 -6
- package/src/core/addon/addon-package.service.ts +183 -89
- package/src/core/addon/addon-registry.service.ts +1163 -1305
- 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 +12 -3
- 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 +120 -32
- package/src/core/network/network-quality.service.spec.ts +6 -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 +602 -531
- package/src/manual-boot.ts +133 -154
- package/tsconfig.json +20 -8
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
PackageVersionInfo,
|
|
19
19
|
AutoUpdateChannel,
|
|
20
20
|
} from '@camstack/types'
|
|
21
|
-
import { EventCategory
|
|
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 {
|
|
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'}`, {
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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) {
|
|
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) => {
|
|
472
|
-
|
|
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) {
|
|
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(
|
|
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', {
|
|
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', {
|
|
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
|
-
(
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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', {
|
|
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
|
-
|
|
862
|
-
|
|
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', {
|
|
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(
|
|
925
|
-
|
|
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<
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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 =
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
1042
|
-
|
|
1043
|
-
|
|
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(
|
|
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 = [
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1188
|
-
|
|
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 =
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
-
|
|
1237
|
-
|
|
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:
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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 (
|
|
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 =
|
|
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', {
|
|
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', {
|
|
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 = [
|
|
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()
|