@camstack/server 1.2.702 → 1.2.703
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/CHANGELOG.md +13 -0
- package/dist/api/core/cap-providers.js +13 -0
- package/dist/api/core/integration-secrets.js +16 -1
- package/dist/api/trpc/generated-cap-mounts.js +2 -0
- package/dist/api/trpc/generated-cap-routers.js +105 -0
- package/dist/core/addon/addon-package.service.js +19 -3
- package/dist/core/addon/addon-registry.service.js +41 -8
- package/dist/core/addon/addon-settings-provider.js +53 -0
- package/dist/core/addon/staged-update-applier.js +15 -0
- package/dist/manual-boot.js +6 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.703] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- The hub exposes the network scan engine, saved scan subnets and discovery-mode classification calls
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Integration settings accept an owner-only skipRestart so an addon can save its own settings without a respawn
|
|
19
|
+
- An updated addon shows its new manifest (icon, name, capabilities) without restarting the hub
|
|
20
|
+
- A staged addon update that fails to swap now fails its update task instead of reporting it done
|
|
21
|
+
- Hub: settings dispatch can ask an addon which keys it still declares and remove a stale one
|
|
22
|
+
|
|
10
23
|
## [1.2.702] - 2026-10-30
|
|
11
24
|
|
|
12
25
|
### Changed
|
|
@@ -1212,6 +1212,19 @@ function buildIntegrationsProvider(ar, eb, loggingService, capabilityRegistry) {
|
|
|
1212
1212
|
// The integration's own addon writing back is not the operator.
|
|
1213
1213
|
operatorWrite: typeof Reflect.get(input, 'callerAddonId') !== 'string',
|
|
1214
1214
|
});
|
|
1215
|
+
// Only the integration's own addon may skip the respawn: it reconciles
|
|
1216
|
+
// the change in-process, so an unrelated caller cannot leave an addon
|
|
1217
|
+
// running on settings it never reloaded.
|
|
1218
|
+
const callerAddon = Reflect.get(input, 'callerAddonId');
|
|
1219
|
+
if (input.skipRestart === true &&
|
|
1220
|
+
typeof callerAddon === 'string' &&
|
|
1221
|
+
(callerAddon === integration.addonId || ar.runnerIdFor(integration.addonId) === callerAddon)) {
|
|
1222
|
+
logger.info('persisted — the owner applies it in-process, no restart', {
|
|
1223
|
+
tags: { addonId: integration.addonId },
|
|
1224
|
+
meta: { phase: 'setSettings' },
|
|
1225
|
+
});
|
|
1226
|
+
return { success: true };
|
|
1227
|
+
}
|
|
1215
1228
|
logger.info('persisted — restarting addon', {
|
|
1216
1229
|
tags: { addonId: integration.addonId },
|
|
1217
1230
|
meta: { phase: 'setSettings' },
|
|
@@ -75,7 +75,22 @@ async function readIntegrationSettings(deps, input) {
|
|
|
75
75
|
// a marker becomes its "set" view, and so does a suspect plaintext.
|
|
76
76
|
const out = {};
|
|
77
77
|
for (const [key, value] of Object.entries(stored)) {
|
|
78
|
-
out[key] =
|
|
78
|
+
out[key] = suspects.has(key) ? (0, types_1.toSecretFieldView)(value) : toFormView(value);
|
|
79
79
|
}
|
|
80
80
|
return out;
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* A marker anywhere in a stored value becomes its "set" view: a secret inside
|
|
84
|
+
* an array row (`boards[id].encryptionKey`) is stored as a marker in the row,
|
|
85
|
+
* and the admin form must not receive the marker itself.
|
|
86
|
+
*/
|
|
87
|
+
function toFormView(value) {
|
|
88
|
+
if ((0, types_1.isSecretMarker)(value))
|
|
89
|
+
return (0, types_1.toSecretFieldView)(value);
|
|
90
|
+
if (Array.isArray(value))
|
|
91
|
+
return value.map(toFormView);
|
|
92
|
+
if (typeof value === 'object' && value !== null) {
|
|
93
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toFormView(v)]));
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
@@ -254,6 +254,8 @@ function mountAllCaps(services) {
|
|
|
254
254
|
...first,
|
|
255
255
|
getDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'getDevices'),
|
|
256
256
|
discoverDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'discoverDevices'),
|
|
257
|
+
getDiscoveryModes: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'getDiscoveryModes'),
|
|
258
|
+
classifyDiscoveryHits: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'classifyDiscoveryHits'),
|
|
257
259
|
};
|
|
258
260
|
}, remoteCapProxy),
|
|
259
261
|
deviceRemovalHook: (0, generated_cap_routers_js_1.createCapRouter_deviceRemovalHook)((_ctx, addonId) => {
|
|
@@ -648,6 +648,22 @@ function createCapRouter_addonSettings(getProvider, _createRemoteProxy) {
|
|
|
648
648
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
649
649
|
return p.getIntegrationSettings(input);
|
|
650
650
|
}),
|
|
651
|
+
getDeclaredSettingsKeys: trpc_middleware_js_1.adminProcedure
|
|
652
|
+
.input(types_10.addonSettingsCapability.methods.getDeclaredSettingsKeys.input.loose())
|
|
653
|
+
.output(types_10.addonSettingsCapability.methods.getDeclaredSettingsKeys.output)
|
|
654
|
+
.query(async ({ input, ctx }) => {
|
|
655
|
+
const p = requireCapProvider('addon-settings', () => getProvider(ctx));
|
|
656
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
657
|
+
return p.getDeclaredSettingsKeys(input);
|
|
658
|
+
}),
|
|
659
|
+
unsetStoredSettingsKeys: trpc_middleware_js_1.adminProcedure
|
|
660
|
+
.input(types_10.addonSettingsCapability.methods.unsetStoredSettingsKeys.input.loose())
|
|
661
|
+
.output(types_10.addonSettingsCapability.methods.unsetStoredSettingsKeys.output)
|
|
662
|
+
.mutation(async ({ input, ctx }) => {
|
|
663
|
+
const p = requireCapProvider('addon-settings', () => getProvider(ctx));
|
|
664
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
665
|
+
return p.unsetStoredSettingsKeys(input);
|
|
666
|
+
}),
|
|
651
667
|
getDeviceSettings: trpc_middleware_js_1.protectedProcedure
|
|
652
668
|
.input(types_10.addonSettingsCapability.methods.getDeviceSettings.input.loose())
|
|
653
669
|
.output(types_10.addonSettingsCapability.methods.getDeviceSettings.output)
|
|
@@ -4361,6 +4377,24 @@ function createCapRouter_deviceProvider(getProvider, createRemoteProxy) {
|
|
|
4361
4377
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4362
4378
|
return p.discoverDevices(methodInput);
|
|
4363
4379
|
}),
|
|
4380
|
+
getDiscoveryModes: trpc_middleware_js_1.protectedProcedure
|
|
4381
|
+
.input(types_55.deviceProviderCapability.methods.getDiscoveryModes.input.loose())
|
|
4382
|
+
.output(types_55.deviceProviderCapability.methods.getDiscoveryModes.output)
|
|
4383
|
+
.query(async ({ input, ctx }) => {
|
|
4384
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
4385
|
+
const p = resolveProvider('device-provider', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
4386
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4387
|
+
return p.getDiscoveryModes(methodInput);
|
|
4388
|
+
}),
|
|
4389
|
+
classifyDiscoveryHits: trpc_middleware_js_1.adminProcedure
|
|
4390
|
+
.input(types_55.deviceProviderCapability.methods.classifyDiscoveryHits.input.loose())
|
|
4391
|
+
.output(types_55.deviceProviderCapability.methods.classifyDiscoveryHits.output)
|
|
4392
|
+
.mutation(async ({ input, ctx }) => {
|
|
4393
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
4394
|
+
const p = resolveProvider('device-provider', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
4395
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4396
|
+
return p.classifyDiscoveryHits(methodInput);
|
|
4397
|
+
}),
|
|
4364
4398
|
getDiscoveryParamsSchema: trpc_middleware_js_1.protectedProcedure
|
|
4365
4399
|
.input(types_55.deviceProviderCapability.methods.getDiscoveryParamsSchema.input.loose())
|
|
4366
4400
|
.output(types_55.deviceProviderCapability.methods.getDiscoveryParamsSchema.output)
|
|
@@ -5433,6 +5467,16 @@ function createCapRouter_keychain(getProvider, createRemoteProxy) {
|
|
|
5433
5467
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
5434
5468
|
return p.deleteDeviceScope(methodInput);
|
|
5435
5469
|
}),
|
|
5470
|
+
releaseRecordRefs: trpc_middleware_js_1.adminProcedure
|
|
5471
|
+
.input(types_70.keychainCapability.methods.releaseRecordRefs.input.loose())
|
|
5472
|
+
.output(types_70.keychainCapability.methods.releaseRecordRefs.output)
|
|
5473
|
+
.mutation(async ({ input, ctx }) => {
|
|
5474
|
+
const parsedInput = ctx.req !== undefined ? (0, types_3.withoutCallerStamp)(input) : input;
|
|
5475
|
+
const { nodeId, ...methodInput } = parsedInput;
|
|
5476
|
+
const p = resolveProvider('keychain', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
5477
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
5478
|
+
return p.releaseRecordRefs(methodInput);
|
|
5479
|
+
}),
|
|
5436
5480
|
listMeta: trpc_middleware_js_1.adminProcedure
|
|
5437
5481
|
.input(types_70.keychainCapability.methods.listMeta.input.loose())
|
|
5438
5482
|
.output(types_70.keychainCapability.methods.listMeta.output)
|
|
@@ -6070,6 +6114,31 @@ function createCapRouter_localNetwork(getProvider, createRemoteProxy) {
|
|
|
6070
6114
|
const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
6071
6115
|
return p.useStoredUploadedCertificate();
|
|
6072
6116
|
}),
|
|
6117
|
+
scanNetwork: trpc_middleware_js_1.adminProcedure
|
|
6118
|
+
.input(types_76.localNetworkCapability.methods.scanNetwork.input.loose())
|
|
6119
|
+
.output(types_76.localNetworkCapability.methods.scanNetwork.output)
|
|
6120
|
+
.mutation(async ({ input, ctx }) => {
|
|
6121
|
+
const { nodeId, ...methodInput } = input;
|
|
6122
|
+
const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
6123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
6124
|
+
return p.scanNetwork(methodInput);
|
|
6125
|
+
}),
|
|
6126
|
+
getScanSubnets: trpc_middleware_js_1.adminProcedure
|
|
6127
|
+
.input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
|
|
6128
|
+
.output(types_76.localNetworkCapability.methods.getScanSubnets.output)
|
|
6129
|
+
.query(async ({ input, ctx }) => {
|
|
6130
|
+
const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
6131
|
+
return p.getScanSubnets();
|
|
6132
|
+
}),
|
|
6133
|
+
setScanSubnets: trpc_middleware_js_1.adminProcedure
|
|
6134
|
+
.input(types_76.localNetworkCapability.methods.setScanSubnets.input.loose())
|
|
6135
|
+
.output(types_76.localNetworkCapability.methods.setScanSubnets.output)
|
|
6136
|
+
.mutation(async ({ input, ctx }) => {
|
|
6137
|
+
const { nodeId, ...methodInput } = input;
|
|
6138
|
+
const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
6139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
6140
|
+
return p.setScanSubnets(methodInput);
|
|
6141
|
+
}),
|
|
6073
6142
|
revertToGeneratedCertificate: trpc_middleware_js_1.adminProcedure
|
|
6074
6143
|
.input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
|
|
6075
6144
|
.output(types_76.localNetworkCapability.methods.revertToGeneratedCertificate.output)
|
|
@@ -11616,6 +11685,42 @@ function createCapRouter_storageAdmin(getProvider, createRemoteProxy) {
|
|
|
11616
11685
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11617
11686
|
return p.readRecords(methodInput);
|
|
11618
11687
|
}),
|
|
11688
|
+
staleDataReport: trpc_middleware_js_1.adminProcedure
|
|
11689
|
+
.input(types_133.storageAdminCapability.methods.staleDataReport.input.loose())
|
|
11690
|
+
.output(types_133.storageAdminCapability.methods.staleDataReport.output)
|
|
11691
|
+
.query(async ({ input, ctx }) => {
|
|
11692
|
+
const { nodeId, ...methodInput } = input;
|
|
11693
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
11694
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11695
|
+
return p.staleDataReport(methodInput);
|
|
11696
|
+
}),
|
|
11697
|
+
scanStaleData: trpc_middleware_js_1.adminProcedure
|
|
11698
|
+
.input(types_133.storageAdminCapability.methods.scanStaleData.input.loose())
|
|
11699
|
+
.output(types_133.storageAdminCapability.methods.scanStaleData.output)
|
|
11700
|
+
.mutation(async ({ input, ctx }) => {
|
|
11701
|
+
const { nodeId, ...methodInput } = input;
|
|
11702
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
11703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11704
|
+
return p.scanStaleData(methodInput);
|
|
11705
|
+
}),
|
|
11706
|
+
dropOrphanedKind: trpc_middleware_js_1.adminProcedure
|
|
11707
|
+
.input(types_133.storageAdminCapability.methods.dropOrphanedKind.input.loose())
|
|
11708
|
+
.output(types_133.storageAdminCapability.methods.dropOrphanedKind.output)
|
|
11709
|
+
.mutation(async ({ input, ctx }) => {
|
|
11710
|
+
const { nodeId, ...methodInput } = input;
|
|
11711
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
11712
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11713
|
+
return p.dropOrphanedKind(methodInput);
|
|
11714
|
+
}),
|
|
11715
|
+
unsetStaleSettingsKey: trpc_middleware_js_1.adminProcedure
|
|
11716
|
+
.input(types_133.storageAdminCapability.methods.unsetStaleSettingsKey.input.loose())
|
|
11717
|
+
.output(types_133.storageAdminCapability.methods.unsetStaleSettingsKey.output)
|
|
11718
|
+
.mutation(async ({ input, ctx }) => {
|
|
11719
|
+
const { nodeId, ...methodInput } = input;
|
|
11720
|
+
const p = resolveProvider('storage-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
11721
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11722
|
+
return p.unsetStaleSettingsKey(methodInput);
|
|
11723
|
+
}),
|
|
11619
11724
|
});
|
|
11620
11725
|
}
|
|
11621
11726
|
function createCapRouter_storageEvictable(getProvider, createRemoteProxy) {
|
|
@@ -344,7 +344,7 @@ class AddonPackageService {
|
|
|
344
344
|
// Refresh registry + emit a synthetic 'updated' lifecycle event
|
|
345
345
|
// (the version went BACK, but downstream consumers care about the
|
|
346
346
|
// version change itself, not the direction).
|
|
347
|
-
this.
|
|
347
|
+
await this.refreshRegistryManifest(name, rolledBackTo);
|
|
348
348
|
this.addonRegistry.emitUpdateEvent(name, previousVersion, rolledBackTo);
|
|
349
349
|
try {
|
|
350
350
|
const restart = await this.addonRegistry.restartPackage(name);
|
|
@@ -1103,7 +1103,7 @@ class AddonPackageService {
|
|
|
1103
1103
|
}
|
|
1104
1104
|
updatedVersion = result.version;
|
|
1105
1105
|
// Update version in registry so UI reflects the change immediately
|
|
1106
|
-
this.
|
|
1106
|
+
await this.refreshRegistryManifest(name, updatedVersion);
|
|
1107
1107
|
// Emit addon.updated lifecycle event
|
|
1108
1108
|
this.addonRegistry.emitUpdateEvent(name, previousVersion, updatedVersion);
|
|
1109
1109
|
this.logger.info('Addon package updated, triggering hot-reload', {
|
|
@@ -1183,7 +1183,7 @@ class AddonPackageService {
|
|
|
1183
1183
|
const r = await addonInstaller.applyUpdateFromStaged(name, version, stagedPath);
|
|
1184
1184
|
const updatedVersion = r.version;
|
|
1185
1185
|
// Update version in registry so UI reflects the change immediately
|
|
1186
|
-
this.
|
|
1186
|
+
await this.refreshRegistryManifest(name, updatedVersion);
|
|
1187
1187
|
// Emit addon.updated lifecycle event
|
|
1188
1188
|
this.addonRegistry.emitUpdateEvent(name, previousVersion, updatedVersion);
|
|
1189
1189
|
this.logger.info('Staged addon swap complete, triggering hot-reload', {
|
|
@@ -1963,6 +1963,22 @@ class AddonPackageService {
|
|
|
1963
1963
|
});
|
|
1964
1964
|
return null;
|
|
1965
1965
|
}
|
|
1966
|
+
/**
|
|
1967
|
+
* After a swap on disk, make `addons.list` serve the NEW manifest (icon, name,
|
|
1968
|
+
* capabilities), not just the new version. A failed re-read falls back to the
|
|
1969
|
+
* version-only refresh and is logged with the package name.
|
|
1970
|
+
*/
|
|
1971
|
+
async refreshRegistryManifest(name, version) {
|
|
1972
|
+
try {
|
|
1973
|
+
await this.addonRegistry.refreshPackageFromDisk(name);
|
|
1974
|
+
}
|
|
1975
|
+
catch (error) {
|
|
1976
|
+
this.logger.warn('Manifest refresh from disk failed; only the version was updated', {
|
|
1977
|
+
meta: { name, error: (0, types_2.errMsg)(error) },
|
|
1978
|
+
});
|
|
1979
|
+
this.addonRegistry.refreshPackageVersion(name, version);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1966
1982
|
/** Only allow @camstack/* scoped packages */
|
|
1967
1983
|
isAllowedPackage(name) {
|
|
1968
1984
|
return name.startsWith('@camstack/');
|
|
@@ -1747,6 +1747,46 @@ class AddonRegistryService {
|
|
|
1747
1747
|
});
|
|
1748
1748
|
}
|
|
1749
1749
|
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Copy the freshly scanned package metadata (version, name, bundle, the
|
|
1752
|
+
* declaration `listAddons()` overlays, and the declared capabilities) onto an
|
|
1753
|
+
* already-registered entry. The live `entry.addon` instance and
|
|
1754
|
+
* `entry.initialized` stay untouched so `restartAddon` still shuts the old
|
|
1755
|
+
* instance down cleanly.
|
|
1756
|
+
*/
|
|
1757
|
+
applyDiskMetadata(existing, registered) {
|
|
1758
|
+
existing.packageVersion = registered.packageVersion;
|
|
1759
|
+
existing.packageName = registered.packageName;
|
|
1760
|
+
existing.packageDisplayName = registered.packageDisplayName;
|
|
1761
|
+
if (registered.bundle !== undefined) {
|
|
1762
|
+
existing.bundle = registered.bundle;
|
|
1763
|
+
}
|
|
1764
|
+
existing.declaration = registered.declaration;
|
|
1765
|
+
existing.declaredCapabilities = (registered.declaration.capabilities ?? []).map((c) => typeof c === 'string' ? { name: c, mode: 'singleton' } : c);
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Re-read ONE installed package from `<addonsDir>/<packageName>` and refresh
|
|
1769
|
+
* the metadata of its registered addons (declaration incl. icon/name,
|
|
1770
|
+
* version, bundle, capabilities). Called after an update or rollback swapped
|
|
1771
|
+
* the package on disk: without it `listAddons()` keeps serving the manifest
|
|
1772
|
+
* captured at boot. Never registers a new addon (that is `loadNewAddons`).
|
|
1773
|
+
*/
|
|
1774
|
+
async refreshPackageFromDisk(packageName) {
|
|
1775
|
+
const dataDir = path.resolve(process.env.CAMSTACK_DATA ?? 'camstack-data');
|
|
1776
|
+
const addonsDir = process.env.CAMSTACK_ADDONS_DIR ?? path.resolve(dataDir, 'addons');
|
|
1777
|
+
const freshLoader = new system_1.AddonLoader(this.loggingService.createLogger('AddonLoader'));
|
|
1778
|
+
await freshLoader.loadFromAddonDir(path.join(addonsDir, packageName));
|
|
1779
|
+
for (const registered of freshLoader.listAddons()) {
|
|
1780
|
+
const existing = this.addonEntries.get(registered.declaration.id);
|
|
1781
|
+
if (!existing || existing.packageName !== packageName)
|
|
1782
|
+
continue;
|
|
1783
|
+
this.applyDiskMetadata(existing, registered);
|
|
1784
|
+
this.logger.info('Refreshed addon manifest from disk', {
|
|
1785
|
+
tags: { addonId: registered.declaration.id },
|
|
1786
|
+
meta: { packageName, version: registered.packageVersion },
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1750
1790
|
/**
|
|
1751
1791
|
* Refresh the package version for all addons from a given package.
|
|
1752
1792
|
* Call this after an update to ensure the UI shows the new version.
|
|
@@ -2419,14 +2459,7 @@ class AddonRegistryService {
|
|
|
2419
2459
|
continue;
|
|
2420
2460
|
}
|
|
2421
2461
|
if (existing) {
|
|
2422
|
-
existing
|
|
2423
|
-
existing.packageName = registered.packageName;
|
|
2424
|
-
existing.packageDisplayName = registered.packageDisplayName;
|
|
2425
|
-
if (registered.bundle !== undefined) {
|
|
2426
|
-
existing.bundle = registered.bundle;
|
|
2427
|
-
}
|
|
2428
|
-
existing.declaration = registered.declaration;
|
|
2429
|
-
existing.declaredCapabilities = (registered.declaration.capabilities ?? []).map((c) => typeof c === 'string' ? { name: c, mode: 'singleton' } : c);
|
|
2462
|
+
this.applyDiskMetadata(existing, registered);
|
|
2430
2463
|
continue;
|
|
2431
2464
|
}
|
|
2432
2465
|
// Skip agent-only addons on the hub. Same rule as the boot loader
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAddonSettingsProvider = createAddonSettingsProvider;
|
|
4
|
+
/**
|
|
5
|
+
* Hub-side singleton provider for the `addon-settings` capability.
|
|
6
|
+
*
|
|
7
|
+
* Resolves `addonId` and delegates the settings call. In-process hub builtins
|
|
8
|
+
* are called directly; every FORKED addon (hub-local child over UDS, or remote
|
|
9
|
+
* agent over Moleculer) routes through the shared {@link AddonCallGateway} —
|
|
10
|
+
* the SAME centralised router routes/custom-actions use. Settings used to live
|
|
11
|
+
* on its own Moleculer-only path here, which is why forked hub-local addons'
|
|
12
|
+
* panels went empty after the UDS migration; that path is gone.
|
|
13
|
+
*/
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const DeclaredKeysAnswer = zod_1.z.object({ keys: zod_1.z.array(zod_1.z.string()) });
|
|
16
|
+
const UnsetKeysAnswer = zod_1.z.object({
|
|
17
|
+
removed: zod_1.z.array(zod_1.z.string()),
|
|
18
|
+
refused: zod_1.z.array(zod_1.z.object({ key: zod_1.z.string(), reason: zod_1.z.string() })),
|
|
19
|
+
secretsCleared: zod_1.z.number(),
|
|
20
|
+
});
|
|
4
21
|
function reshapeForOutput(schema) {
|
|
5
22
|
return {
|
|
6
23
|
tabs: schema.tabs
|
|
@@ -97,6 +114,42 @@ function createAddonSettingsProvider(deps) {
|
|
|
97
114
|
}
|
|
98
115
|
return forkedGet(input.addonId, 'getIntegrationSettings', { ...(input.nodeId ? { nodeId: input.nodeId } : {}) }, input.nodeId);
|
|
99
116
|
},
|
|
117
|
+
async getDeclaredSettingsKeys(input) {
|
|
118
|
+
try {
|
|
119
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
120
|
+
const addon = getAddon(input.addonId);
|
|
121
|
+
if (!addon || typeof addon.getDeclaredSettingsKeys !== 'function')
|
|
122
|
+
return null;
|
|
123
|
+
const answer = await addon.getDeclaredSettingsKeys();
|
|
124
|
+
return { keys: [...answer.keys] };
|
|
125
|
+
}
|
|
126
|
+
const raw = await gateway.callForked(input.addonId, { target: 'settings', method: 'getDeclaredSettingsKeys', args: {} }, input.nodeId);
|
|
127
|
+
const parsed = DeclaredKeysAnswer.safeParse(raw);
|
|
128
|
+
return parsed.success ? parsed.data : null;
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// An addon that does not answer is `unknown` to the audit, never stale.
|
|
132
|
+
if (isServiceNotFoundError(err))
|
|
133
|
+
return null;
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
async unsetStoredSettingsKeys(input) {
|
|
138
|
+
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
139
|
+
const addon = getAddon(input.addonId);
|
|
140
|
+
if (!addon || typeof addon.unsetStoredSettingsKeys !== 'function') {
|
|
141
|
+
throw new Error(`Addon "${input.addonId}" does not implement unsetStoredSettingsKeys`);
|
|
142
|
+
}
|
|
143
|
+
const outcome = await addon.unsetStoredSettingsKeys(input.keys);
|
|
144
|
+
return {
|
|
145
|
+
removed: [...outcome.removed],
|
|
146
|
+
refused: outcome.refused.map((r) => ({ key: r.key, reason: r.reason })),
|
|
147
|
+
secretsCleared: outcome.secretsCleared,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const raw = await gateway.callForked(input.addonId, { target: 'settings', method: 'unsetStoredSettingsKeys', args: { keys: input.keys } }, input.nodeId);
|
|
151
|
+
return UnsetKeysAnswer.parse(raw);
|
|
152
|
+
},
|
|
100
153
|
async getDeviceSettings(input) {
|
|
101
154
|
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
102
155
|
const addon = getAddon(input.addonId);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyStagedOrThrow = applyStagedOrThrow;
|
|
4
|
+
/**
|
|
5
|
+
* The lifecycle engine marks a task `done` whenever its apply callback returns,
|
|
6
|
+
* and `applyStagedAddonUpdate` reports a failed or refused swap as
|
|
7
|
+
* `{ success: false }` instead of throwing. This adapter turns that result into
|
|
8
|
+
* a throw so the task ends `failed` with the reason.
|
|
9
|
+
*/
|
|
10
|
+
async function applyStagedOrThrow(target, input) {
|
|
11
|
+
const result = await target.applyStagedAddonUpdate(input.name, input.version, input.stagedPath);
|
|
12
|
+
if (!result.success) {
|
|
13
|
+
throw new Error(`Staged update of ${input.name}@${input.version} failed: ${result.error ?? 'unknown error'}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/manual-boot.js
CHANGED
|
@@ -44,6 +44,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
44
44
|
};
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.bootManual = bootManual;
|
|
47
|
+
const staged_update_applier_js_1 = require("./core/addon/staged-update-applier.js");
|
|
47
48
|
const node_crypto_1 = require("node:crypto");
|
|
48
49
|
const path = __importStar(require("node:path"));
|
|
49
50
|
const system_1 = require("@camstack/system");
|
|
@@ -265,7 +266,11 @@ async function bootManual(opts) {
|
|
|
265
266
|
fetchTarball: (name, version, signal) => addonPackageService.fetchAddonTarball(name, version, signal),
|
|
266
267
|
extract: (tgz, destDir) => addonPackageService.extractTarball(tgz, destDir),
|
|
267
268
|
applyAddonUpdate: async ({ name, version, stagedPath }) => {
|
|
268
|
-
await
|
|
269
|
+
await (0, staged_update_applier_js_1.applyStagedOrThrow)(addonPackageService, {
|
|
270
|
+
name,
|
|
271
|
+
version,
|
|
272
|
+
stagedPath,
|
|
273
|
+
});
|
|
269
274
|
},
|
|
270
275
|
emit: (kind, job, entry) => {
|
|
271
276
|
if (kind === 'progress') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.703",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
54
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
55
|
-
"@camstack/addon-auth": "1.2.
|
|
56
|
-
"@camstack/addon-benchmark": "1.2.
|
|
53
|
+
"@camstack/addon-admin-ui": "1.2.527",
|
|
54
|
+
"@camstack/addon-agent-ui": "1.2.299",
|
|
55
|
+
"@camstack/addon-auth": "1.2.172",
|
|
56
|
+
"@camstack/addon-benchmark": "1.2.298",
|
|
57
57
|
"@camstack/addon-decoder-nodeav": "1.2.144",
|
|
58
58
|
"@camstack/addon-notifiers": "1.2.623",
|
|
59
59
|
"@camstack/addon-pipeline": "1.2.635",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"@camstack/addon-post-analysis": "1.2.653",
|
|
62
62
|
"@camstack/develop": "0.1.41",
|
|
63
63
|
"@camstack/sdk": "1.2.154",
|
|
64
|
-
"@camstack/system": "1.2.
|
|
65
|
-
"@camstack/types": "1.2.
|
|
66
|
-
"@camstack/ui-library": "1.2.
|
|
64
|
+
"@camstack/system": "1.2.525",
|
|
65
|
+
"@camstack/types": "1.2.452",
|
|
66
|
+
"@camstack/ui-library": "1.2.357",
|
|
67
67
|
"@fastify/compress": "^9.0.0",
|
|
68
68
|
"@fastify/cookie": "^11.0.2",
|
|
69
69
|
"@fastify/cors": "^11.2.0",
|