@camstack/server 1.0.2 → 1.0.3
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/dist/api/addon-upload.js +17 -3
- package/dist/api/bridge-addons.router.js +1 -1
- package/dist/api/capabilities.router.js +3 -3
- package/dist/api/core/cap-providers.js +3 -3
- package/dist/api/core/logs.router.js +1 -1
- package/dist/api/core/notifications.router.js +1 -1
- package/dist/api/core/repl.router.js +1 -1
- package/dist/api/health/health.routes.js +7 -2
- package/dist/api/trpc/cap-route-error-formatter.js +3 -3
- package/dist/api/trpc/core-cap-bridge.js +2 -2
- package/dist/api/trpc/generated-cap-routers.js +4 -4
- package/dist/boot/boot-config.js +2 -2
- package/dist/boot/post-boot.service.js +2 -2
- package/dist/core/addon/addon-call-gateway.js +1 -1
- package/dist/core/addon/addon-package.service.js +37 -43
- package/dist/core/addon/addon-registry.service.js +61 -51
- package/dist/core/addon-bridge/addon-bridge.service.js +6 -6
- package/dist/core/addon-pages/addon-pages.service.js +1 -1
- package/dist/core/auth/auth.service.js +2 -2
- package/dist/core/config/config.schema.js +4 -4
- package/dist/core/config/config.service.js +2 -2
- package/dist/core/events/event-bus.service.js +2 -2
- package/dist/core/feature/feature.service.js +2 -2
- package/dist/core/lifecycle/lifecycle-state-machine.js +3 -3
- package/dist/core/logging/log-ring-buffer.js +3 -3
- package/dist/core/logging/logging.service.js +3 -3
- package/dist/core/logging/scoped-logger.js +3 -3
- package/dist/core/moleculer/cap-route-authority.js +2 -2
- package/dist/core/moleculer/moleculer.service.js +22 -22
- package/dist/core/network/network-quality.service.js +2 -2
- package/dist/core/notification/notification-wrapper.service.js +2 -2
- package/dist/core/notification/toast-wrapper.service.js +2 -2
- package/dist/core/repl/repl-engine.service.js +2 -2
- package/dist/core/storage/fs-storage-backend.js +3 -3
- package/dist/core/storage/storage-location-manager.js +3 -3
- package/dist/core/storage/storage.service.js +2 -2
- package/dist/launcher.js +18 -12
- package/dist/main.js +7 -4
- package/package.json +2 -3
package/dist/api/addon-upload.js
CHANGED
|
@@ -322,9 +322,23 @@ async function installToHub(reply, addonBridge, addonRegistry, addonPackageServi
|
|
|
322
322
|
.listAddons()
|
|
323
323
|
.filter((row) => row.manifest.packageName === result.name)
|
|
324
324
|
.map((row) => row.manifest.id);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
//
|
|
325
|
+
// Must honour CAMSTACK_ADDONS_DIR — the runtime installs/loads addons
|
|
326
|
+
// from there (e.g. /data/node_modules), NOT from CAMSTACK_DATA/addons.
|
|
327
|
+
// Deriving the dir from CAMSTACK_DATA alone pointed the marker write at a
|
|
328
|
+
// non-existent path → unguarded ENOENT → HTTP 500, which aborted the
|
|
329
|
+
// handler before loadNewAddons()/restartAddon() ran, so uploads landed on
|
|
330
|
+
// disk but never hot-reloaded. Mirrors AddonPackageService.resolveAddonsDir().
|
|
331
|
+
const dataDir = path.resolve(process.env['CAMSTACK_DATA'] ?? 'camstack-data');
|
|
332
|
+
const addonsDir = process.env['CAMSTACK_ADDONS_DIR'] ?? path.resolve(dataDir, 'addons');
|
|
333
|
+
try {
|
|
334
|
+
fs.writeFileSync(path.join(addonsDir, result.name, '.install-source'), 'upload');
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
logger.debug('Non-fatal: failed to write .install-source marker', {
|
|
338
|
+
meta: { error: err instanceof Error ? err.message : String(err) },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// Framework / system packages (`camstack.system: true`, e.g. @camstack/system)
|
|
328
342
|
// ship hub builtins (sqlite-settings, filesystem-storage, …) that CANNOT be
|
|
329
343
|
// hot-reloaded in place — an in-process `restartAddon` leaves them
|
|
330
344
|
// uninitialized ("SqliteSettingsBackend not initialized"), breaking auth +
|
|
@@ -56,7 +56,7 @@ function createBridgeAddonsRouter(bridge, addonSearch, addonRegistry, toastServi
|
|
|
56
56
|
// (stream-broker, detection-pipeline, motion-wasm, decoders,
|
|
57
57
|
// audio) all live in @camstack/addon-pipeline.
|
|
58
58
|
const REQUIRED = new Set([
|
|
59
|
-
'@camstack/
|
|
59
|
+
'@camstack/system',
|
|
60
60
|
'@camstack/addon-pipeline',
|
|
61
61
|
'@camstack/addon-pipeline-orchestrator',
|
|
62
62
|
'@camstack/addon-post-analysis',
|
|
@@ -4,7 +4,7 @@ exports.createCapabilitiesRouter = createCapabilitiesRouter;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const server_1 = require("@trpc/server");
|
|
6
6
|
const trpc_middleware_1 = require("./trpc/trpc.middleware");
|
|
7
|
-
const
|
|
7
|
+
const system_1 = require("@camstack/system");
|
|
8
8
|
const collection_preference_1 = require("./core/collection-preference");
|
|
9
9
|
// ─── Zod Schemas ───────────────────────────────────────────────────────────
|
|
10
10
|
const setPreferenceInput = zod_1.z.discriminatedUnion('mode', [
|
|
@@ -43,7 +43,7 @@ function createCapabilitiesRouter(addonRegistry, configService, loggingService)
|
|
|
43
43
|
return caps.map((cap) => ({
|
|
44
44
|
...cap,
|
|
45
45
|
providerDetails: getProviderDetails(cap.providers),
|
|
46
|
-
isInfra: (0,
|
|
46
|
+
isInfra: (0, system_1.isInfraCapability)(cap.name),
|
|
47
47
|
}));
|
|
48
48
|
}),
|
|
49
49
|
// ─── Get current preference for a capability ──────────────────────
|
|
@@ -98,7 +98,7 @@ function createCapabilitiesRouter(addonRegistry, configService, loggingService)
|
|
|
98
98
|
message: `Provider "${addonId}" is not registered for "${capability}"`,
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
const requiresRestart = (0,
|
|
101
|
+
const requiresRestart = (0, system_1.isInfraCapability)(capability);
|
|
102
102
|
if (!requiresRestart) {
|
|
103
103
|
// Hot-swap at runtime
|
|
104
104
|
await registry.setActiveSingleton(capability, addonId);
|
|
@@ -72,7 +72,7 @@ const node_util_1 = require("node:util");
|
|
|
72
72
|
const node_crypto_1 = require("node:crypto");
|
|
73
73
|
const server_1 = require("@trpc/server");
|
|
74
74
|
const types_1 = require("@camstack/types");
|
|
75
|
-
const
|
|
75
|
+
const system_1 = require("@camstack/system");
|
|
76
76
|
const integration_id_backfill_1 = require("../../boot/integration-id-backfill");
|
|
77
77
|
const collection_preference_js_1 = require("./collection-preference.js");
|
|
78
78
|
const bulk_update_coordinator_js_1 = require("./bulk-update-coordinator.js");
|
|
@@ -445,7 +445,7 @@ function buildNodesProvider(agentRegistry, moleculer, addonRegistry) {
|
|
|
445
445
|
return result;
|
|
446
446
|
},
|
|
447
447
|
getCapUsageGraph: async (input) => {
|
|
448
|
-
const reg = (0,
|
|
448
|
+
const reg = (0, system_1.getCapUsageRegistry)();
|
|
449
449
|
return reg.getGraph({ windowSeconds: input.windowSeconds, nowMs: Date.now() });
|
|
450
450
|
},
|
|
451
451
|
setProcessLogLevel: async (input) => {
|
|
@@ -942,7 +942,7 @@ function buildAddonsProvider(ar, ps, ls, moleculer, configService, ctx, eb) {
|
|
|
942
942
|
},
|
|
943
943
|
logger: ls.createLogger('bulk-update'),
|
|
944
944
|
});
|
|
945
|
-
const frameworkAllowSet = new Set(addon_package_service_js_1.
|
|
945
|
+
const frameworkAllowSet = new Set([addon_package_service_js_1.SYSTEM_PACKAGE]);
|
|
946
946
|
return {
|
|
947
947
|
list: async () => {
|
|
948
948
|
const rollbackable = ps.getRollbackablePackages();
|
|
@@ -5,7 +5,7 @@ exports.createLogsRouter = createLogsRouter;
|
|
|
5
5
|
* Logs router — fixed core API (not a capability).
|
|
6
6
|
*
|
|
7
7
|
* Queries and streams log entries via `LoggingService` (thin NestJS DI
|
|
8
|
-
* wrapper over `LogManager` from `@camstack/
|
|
8
|
+
* wrapper over `LogManager` from `@camstack/system`). Admin can read
|
|
9
9
|
* everything; viewers/protected users only see entries matching their
|
|
10
10
|
* allowed providers scope (filtered in `query`).
|
|
11
11
|
*/
|
|
@@ -5,7 +5,7 @@ exports.createNotificationsRouter = createNotificationsRouter;
|
|
|
5
5
|
* Notifications router — fixed core API (not a capability).
|
|
6
6
|
*
|
|
7
7
|
* Routes events by category to registered outputs. The NotificationService
|
|
8
|
-
* is a singleton from @camstack/
|
|
8
|
+
* is a singleton from @camstack/system; individual outputs are still provided
|
|
9
9
|
* by addons via the `notification-output` capability collection.
|
|
10
10
|
*/
|
|
11
11
|
const zod_1 = require("zod");
|
|
@@ -5,7 +5,7 @@ exports.createReplRouter = createReplRouter;
|
|
|
5
5
|
* REPL router — fixed core API (not a capability).
|
|
6
6
|
*
|
|
7
7
|
* The REPL engine is a single NestJS-backed service in the backend
|
|
8
|
-
* (`ReplEngineService` extends `ReplEngine` from `@camstack/
|
|
8
|
+
* (`ReplEngineService` extends `ReplEngine` from `@camstack/system`).
|
|
9
9
|
* Only super_admin can execute code.
|
|
10
10
|
*/
|
|
11
11
|
const zod_1 = require("zod");
|
|
@@ -14,7 +14,7 @@ async function buildHubHealth(deps, proc = process) {
|
|
|
14
14
|
const totalMem = memUsage.heapTotal + memUsage.external + memUsage.arrayBuffers;
|
|
15
15
|
const memoryPercent = totalMem > 0 ? Math.round((memUsage.heapUsed / totalMem) * 100) : 0;
|
|
16
16
|
return {
|
|
17
|
-
ok:
|
|
17
|
+
ok: deps.isReady(),
|
|
18
18
|
nodeId: 'hub',
|
|
19
19
|
version: deps.hubVersion,
|
|
20
20
|
uptimeSeconds: Math.round(proc.uptime()),
|
|
@@ -39,7 +39,12 @@ async function fetchAgentHealth(deps, nodeId) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
function registerHealthRoutes(fastify, deps) {
|
|
42
|
-
fastify.get('/health', async () =>
|
|
42
|
+
fastify.get('/health', async (_req, reply) => {
|
|
43
|
+
const health = await buildHubHealth(deps);
|
|
44
|
+
if (!health.ok)
|
|
45
|
+
reply.status(503);
|
|
46
|
+
return health;
|
|
47
|
+
});
|
|
43
48
|
fastify.get('/health/agents', async () => {
|
|
44
49
|
const nodes = await deps.agentRegistry.listNodes();
|
|
45
50
|
return {
|
|
@@ -16,7 +16,7 @@ exports.formatTrpcError = formatTrpcError;
|
|
|
16
16
|
*
|
|
17
17
|
* The formatter is EXPORTED for unit testing (no side-effects, pure function).
|
|
18
18
|
*/
|
|
19
|
-
const
|
|
19
|
+
const system_1 = require("@camstack/system");
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
// Type guards
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -56,7 +56,7 @@ function extractCapRouteError(err) {
|
|
|
56
56
|
for (let depth = 0; depth < 8; depth++) {
|
|
57
57
|
if (current === null || current === undefined)
|
|
58
58
|
return null;
|
|
59
|
-
if (current instanceof
|
|
59
|
+
if (current instanceof system_1.CapRouteError) {
|
|
60
60
|
return current;
|
|
61
61
|
}
|
|
62
62
|
// Duck-type fallback: err.name + err.reason field present
|
|
@@ -83,7 +83,7 @@ function extractCapRouteError(err) {
|
|
|
83
83
|
const rawCapName = Reflect.get(current, 'capName');
|
|
84
84
|
const capName = typeof rawCapName === 'string' ? rawCapName : '(unknown)';
|
|
85
85
|
// Build a minimal object with the same shape — enough for the formatter.
|
|
86
|
-
const synthetic = Object.assign(new
|
|
86
|
+
const synthetic = Object.assign(new system_1.CapRouteError(capName, undefined, {
|
|
87
87
|
reason,
|
|
88
88
|
rejected,
|
|
89
89
|
...(typeof nodeId === 'string' ? { nodeId } : {}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildCoreCapService = buildCoreCapService;
|
|
4
|
-
const
|
|
4
|
+
const system_1 = require("@camstack/system");
|
|
5
5
|
const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
6
6
|
const trpc_context_js_1 = require("./trpc.context.js");
|
|
7
7
|
/**
|
|
@@ -111,5 +111,5 @@ function buildCoreCapService(appRouter) {
|
|
|
111
111
|
const method = path.slice(dot + 1);
|
|
112
112
|
actions.push({ actionName: `${toKebab(namespace)}.${method}`, invoke });
|
|
113
113
|
}
|
|
114
|
-
return (0,
|
|
114
|
+
return (0, system_1.createCoreCapService)({ actions });
|
|
115
115
|
}
|
|
@@ -140,7 +140,7 @@ exports.createCapRouter_zoneAnalytics = createCapRouter_zoneAnalytics;
|
|
|
140
140
|
exports.createCapRouter_zoneRules = createCapRouter_zoneRules;
|
|
141
141
|
exports.createCapRouter_zones = createCapRouter_zones;
|
|
142
142
|
const server_1 = require("@trpc/server");
|
|
143
|
-
const
|
|
143
|
+
const system_1 = require("@camstack/system");
|
|
144
144
|
const zod_1 = require("zod");
|
|
145
145
|
const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
146
146
|
const types_1 = require("@camstack/types");
|
|
@@ -259,7 +259,7 @@ function requireCapProvider(name, getProvider) {
|
|
|
259
259
|
throw new server_1.TRPCError({
|
|
260
260
|
code: 'PRECONDITION_FAILED',
|
|
261
261
|
message: `Capability "${name}" provider not available`,
|
|
262
|
-
cause: new
|
|
262
|
+
cause: new system_1.CapRouteError(name, undefined, {
|
|
263
263
|
reason: 'no-provider',
|
|
264
264
|
rejected: [{ kind: 'hub-in-process', why: 'no provider bound for this cap' }],
|
|
265
265
|
}),
|
|
@@ -285,7 +285,7 @@ function resolveProvider(capName, nodeId, getLocal, createProxy) {
|
|
|
285
285
|
throw new server_1.TRPCError({
|
|
286
286
|
code: 'PRECONDITION_FAILED',
|
|
287
287
|
message: `Capability "${capName}" provider not available`,
|
|
288
|
-
cause: new
|
|
288
|
+
cause: new system_1.CapRouteError(capName, undefined, {
|
|
289
289
|
reason: 'no-provider',
|
|
290
290
|
rejected: [{ kind: 'hub-in-process', why: 'no provider bound for this cap/device' }],
|
|
291
291
|
}),
|
|
@@ -302,7 +302,7 @@ function resolveProvider(capName, nodeId, getLocal, createProxy) {
|
|
|
302
302
|
throw new server_1.TRPCError({
|
|
303
303
|
code: 'PRECONDITION_FAILED',
|
|
304
304
|
message: `Capability "${capName}" not available on node "${nodeId}"`,
|
|
305
|
-
cause: new
|
|
305
|
+
cause: new system_1.CapRouteError(capName, undefined, {
|
|
306
306
|
reason: 'no-provider',
|
|
307
307
|
nodeId,
|
|
308
308
|
rejected: [{ kind: 'remote-moleculer', why: `cap not available on node "${nodeId}"` }],
|
package/dist/boot/boot-config.js
CHANGED
|
@@ -208,10 +208,10 @@ async function setupInfra(configPath, bootstrapConfig) {
|
|
|
208
208
|
// --- Phase 2c: TLS certificate setup ---
|
|
209
209
|
let tlsOptions;
|
|
210
210
|
if (config.tls.enabled) {
|
|
211
|
-
// Use require() instead of import() — the ESM build of @camstack/
|
|
211
|
+
// Use require() instead of import() — the ESM build of @camstack/system has
|
|
212
212
|
// broken chunks with require("fs") when leaked .js files exist in core/src/.
|
|
213
213
|
// CJS build works correctly and tsx supports require().
|
|
214
|
-
const core = require('@camstack/
|
|
214
|
+
const core = require('@camstack/system');
|
|
215
215
|
const { ensureTlsCert, loadTlsCert } = core;
|
|
216
216
|
if (config.tls.certPath && config.tls.keyPath) {
|
|
217
217
|
// User-provided cert
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PostBootService = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
const
|
|
5
|
+
const system_1 = require("@camstack/system");
|
|
6
6
|
const types_1 = require("@camstack/types");
|
|
7
7
|
class PostBootService {
|
|
8
8
|
eventBus;
|
|
@@ -57,7 +57,7 @@ class PostBootService {
|
|
|
57
57
|
this.emitRestartCompletedIfPending(dataPath);
|
|
58
58
|
}
|
|
59
59
|
emitRestartCompletedIfPending(dataDir) {
|
|
60
|
-
const marker = (0,
|
|
60
|
+
const marker = (0, system_1.readPendingRestart)(dataDir);
|
|
61
61
|
if (marker === null)
|
|
62
62
|
return;
|
|
63
63
|
const payload = {
|
|
@@ -11,7 +11,7 @@ class AddonCallGateway {
|
|
|
11
11
|
* Classify where an addon runs. `nodeId: 'hub'` from a caller means "the hub
|
|
12
12
|
* cluster", NOT "force in-process" — a forked hub-local addon is still a
|
|
13
13
|
* `hub-local-child` (UDS), never the in-process path (which is only the
|
|
14
|
-
* `@camstack/
|
|
14
|
+
* `@camstack/system` builtins that have no forked runner).
|
|
15
15
|
*/
|
|
16
16
|
classify(addonId, explicitNodeId) {
|
|
17
17
|
const resolved = this.deps.resolveNode(addonId);
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AddonPackageService = exports.
|
|
36
|
+
exports.AddonPackageService = exports.SYSTEM_PACKAGE = void 0;
|
|
37
37
|
exports.isFrameworkPackage = isFrameworkPackage;
|
|
38
38
|
const fs = __importStar(require("node:fs"));
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
@@ -42,30 +42,17 @@ const node_child_process_1 = require("node:child_process");
|
|
|
42
42
|
const node_util_1 = require("node:util");
|
|
43
43
|
const node_crypto_1 = require("node:crypto");
|
|
44
44
|
const types_1 = require("@camstack/types");
|
|
45
|
-
const
|
|
45
|
+
const system_1 = require("@camstack/system");
|
|
46
46
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Mirror of `camstack.system: true` in
|
|
53
|
-
* packages/{types,kernel,core,sdk,ui-library,shm-ring}/package.json
|
|
48
|
+
* The single host-provided framework package. As of 2026-06-22 the framework is
|
|
49
|
+
* one bundle: @camstack/system (kernel + core; types/sdk inlined at runtime,
|
|
50
|
+
* shm-ring is a private native dep). There is no multi-entry allowlist.
|
|
54
51
|
*/
|
|
55
|
-
exports.
|
|
56
|
-
|
|
57
|
-
'@camstack/kernel',
|
|
58
|
-
'@camstack/core',
|
|
59
|
-
'@camstack/sdk',
|
|
60
|
-
'@camstack/ui-library',
|
|
61
|
-
// Phase 5 / D9 — the cross-platform shared-memory frame plane. A system
|
|
62
|
-
// package (native N-API module + `FrameRing`) the stream-broker / decoder /
|
|
63
|
-
// frame consumers all depend on; it can't be uninstalled.
|
|
64
|
-
'@camstack/shm-ring',
|
|
65
|
-
];
|
|
66
|
-
/** Test-only: exported for spec parity check against the manifest. */
|
|
52
|
+
exports.SYSTEM_PACKAGE = '@camstack/system';
|
|
53
|
+
/** Returns true only for the single host-provided framework package. */
|
|
67
54
|
function isFrameworkPackage(packageName) {
|
|
68
|
-
return exports.
|
|
55
|
+
return packageName === exports.SYSTEM_PACKAGE;
|
|
69
56
|
}
|
|
70
57
|
// ---------------------------------------------------------------------------
|
|
71
58
|
// Typed JSON helpers
|
|
@@ -111,7 +98,7 @@ function asRecord(value) {
|
|
|
111
98
|
// Constants
|
|
112
99
|
// ---------------------------------------------------------------------------
|
|
113
100
|
/** Core packages that live in the server's own node_modules */
|
|
114
|
-
const CORE_MANAGED_PACKAGES = ['@camstack/
|
|
101
|
+
const CORE_MANAGED_PACKAGES = ['@camstack/system', '@camstack/types'];
|
|
115
102
|
class AddonPackageService {
|
|
116
103
|
loggingService;
|
|
117
104
|
eventBusService;
|
|
@@ -120,7 +107,7 @@ class AddonPackageService {
|
|
|
120
107
|
notificationService;
|
|
121
108
|
toastService;
|
|
122
109
|
logger;
|
|
123
|
-
/** AddonInstaller from @camstack/
|
|
110
|
+
/** AddonInstaller from @camstack/system (may be null if kernel unavailable) */
|
|
124
111
|
installer = null;
|
|
125
112
|
/** AddonLoader for reloadPackages (may be null) */
|
|
126
113
|
loader = null;
|
|
@@ -155,9 +142,8 @@ class AddonPackageService {
|
|
|
155
142
|
// Initialize installer eagerly (no async needed).
|
|
156
143
|
// Ensures install/uninstall works before full module init completes.
|
|
157
144
|
try {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const workspacePackagesDir = (0, kernel_1.detectWorkspacePackagesDir)(__dirname);
|
|
145
|
+
const addonsDir = this.resolveAddonsDir();
|
|
146
|
+
const workspacePackagesDir = (0, system_1.detectWorkspacePackagesDir)(__dirname);
|
|
161
147
|
// `CAMSTACK_NPM_REGISTRY` override exists primarily for the e2e
|
|
162
148
|
// harness — it points the installer at a sandboxed verdaccio so
|
|
163
149
|
// the CLI-vs-npm version-interaction tests don't hit the public
|
|
@@ -166,12 +152,12 @@ class AddonPackageService {
|
|
|
166
152
|
// AddonInstaller). Both the `npm install` shell-out AND the
|
|
167
153
|
// metadata HTTPS fetch honour this value.
|
|
168
154
|
const registry = process.env['CAMSTACK_NPM_REGISTRY'];
|
|
169
|
-
this.installer = new
|
|
155
|
+
this.installer = new system_1.AddonInstaller({
|
|
170
156
|
addonsDir,
|
|
171
157
|
workspacePackagesDir: workspacePackagesDir ?? undefined,
|
|
172
158
|
...(registry ? { registry } : {}),
|
|
173
159
|
});
|
|
174
|
-
(0,
|
|
160
|
+
(0, system_1.ensureDir)(addonsDir);
|
|
175
161
|
}
|
|
176
162
|
catch (error) {
|
|
177
163
|
const msg = (0, types_1.errMsg)(error);
|
|
@@ -480,7 +466,7 @@ class AddonPackageService {
|
|
|
480
466
|
/** List all installed addon packages */
|
|
481
467
|
listInstalled() {
|
|
482
468
|
if (!this.installer) {
|
|
483
|
-
throw new Error('AddonInstaller is not available — cannot list installed packages. Ensure @camstack/
|
|
469
|
+
throw new Error('AddonInstaller is not available — cannot list installed packages. Ensure @camstack/system is installed and the addons directory is accessible');
|
|
484
470
|
}
|
|
485
471
|
return this.installer.listInstalled();
|
|
486
472
|
}
|
|
@@ -494,14 +480,14 @@ class AddonPackageService {
|
|
|
494
480
|
*
|
|
495
481
|
* Fallback: if the registry hasn't loaded yet (early-boot install
|
|
496
482
|
* paths, tests with stub registries), fall back to the legacy list
|
|
497
|
-
* so we don't accidentally allow uninstalling `@camstack/
|
|
483
|
+
* so we don't accidentally allow uninstalling `@camstack/system` etc.
|
|
498
484
|
* during a window where protection metadata isn't yet readable.
|
|
499
485
|
*/
|
|
500
486
|
isProtected(name) {
|
|
501
487
|
const fromRegistry = this.addonRegistry?.isPackageProtected?.(name);
|
|
502
488
|
if (typeof fromRegistry === 'boolean')
|
|
503
489
|
return fromRegistry;
|
|
504
|
-
return
|
|
490
|
+
return system_1.AddonInstaller.REQUIRED_PACKAGES.includes(name);
|
|
505
491
|
}
|
|
506
492
|
/**
|
|
507
493
|
* Set of package names with a pending pre-update backup on disk —
|
|
@@ -804,7 +790,7 @@ class AddonPackageService {
|
|
|
804
790
|
// hub/node_modules/@camstack/ — addons resolve everything from
|
|
805
791
|
// their own inlined deps.
|
|
806
792
|
const addonsDir = this.resolveAddonsDir();
|
|
807
|
-
const { installPackageFromNpmSync } = await Promise.resolve().then(() => __importStar(require('@camstack/
|
|
793
|
+
const { installPackageFromNpmSync } = await Promise.resolve().then(() => __importStar(require('@camstack/system')));
|
|
808
794
|
const dirName = name.replace(/^@camstack\//, '');
|
|
809
795
|
const targetDir = path.join(addonsDir, dirName);
|
|
810
796
|
const packageSpec = version ? `${name}@${version}` : name;
|
|
@@ -843,7 +829,7 @@ class AddonPackageService {
|
|
|
843
829
|
...(requestedBy !== undefined ? { requestedBy } : {}),
|
|
844
830
|
};
|
|
845
831
|
try {
|
|
846
|
-
(0,
|
|
832
|
+
(0, system_1.writePendingRestart)(this.resolveDataDir(), payload);
|
|
847
833
|
}
|
|
848
834
|
catch (err) {
|
|
849
835
|
// Marker write failure shouldn't block the restart — log + continue.
|
|
@@ -859,7 +845,7 @@ class AddonPackageService {
|
|
|
859
845
|
data: payload,
|
|
860
846
|
});
|
|
861
847
|
// 10s grace gives in-flight requests time to drain.
|
|
862
|
-
(0,
|
|
848
|
+
(0, system_1.scheduleSelfRestart)({ delayMs: 10_000 });
|
|
863
849
|
}
|
|
864
850
|
// =========================================================================
|
|
865
851
|
// Framework live-update
|
|
@@ -878,7 +864,7 @@ class AddonPackageService {
|
|
|
878
864
|
*/
|
|
879
865
|
async listFrameworkPackages() {
|
|
880
866
|
const registry = process.env['CAMSTACK_NPM_REGISTRY'];
|
|
881
|
-
const rows = await Promise.all(exports.
|
|
867
|
+
const rows = await Promise.all([exports.SYSTEM_PACKAGE].map(async (packageName) => {
|
|
882
868
|
const manifest = readResolvedPackageManifest(packageName);
|
|
883
869
|
const currentVersion = manifest !== null && typeof manifest['version'] === 'string'
|
|
884
870
|
? manifest['version']
|
|
@@ -932,8 +918,8 @@ class AddonPackageService {
|
|
|
932
918
|
*/
|
|
933
919
|
async updateFrameworkPackage(input) {
|
|
934
920
|
const { packageName } = input;
|
|
935
|
-
if (
|
|
936
|
-
throw new Error(`updateFrameworkPackage: '${packageName}' is not a framework package. Allowed: ${exports.
|
|
921
|
+
if (packageName !== exports.SYSTEM_PACKAGE) {
|
|
922
|
+
throw new Error(`updateFrameworkPackage: '${packageName}' is not a framework package. Allowed: ${exports.SYSTEM_PACKAGE}`);
|
|
937
923
|
}
|
|
938
924
|
const appRoot = resolveFrameworkPackageAppRoot(packageName, this.logger);
|
|
939
925
|
const fromManifest = readResolvedPackageManifest(packageName);
|
|
@@ -971,7 +957,7 @@ class AddonPackageService {
|
|
|
971
957
|
...(input.requestedBy !== undefined ? { requestedBy: input.requestedBy } : {}),
|
|
972
958
|
};
|
|
973
959
|
try {
|
|
974
|
-
(0,
|
|
960
|
+
(0, system_1.writePendingRestart)(this.resolveDataDir(), markerPayload);
|
|
975
961
|
}
|
|
976
962
|
catch (err) {
|
|
977
963
|
// The npm install already completed — the restart will still
|
|
@@ -987,7 +973,7 @@ class AddonPackageService {
|
|
|
987
973
|
category: types_1.EventCategory.SystemRestarting,
|
|
988
974
|
data: markerPayload,
|
|
989
975
|
});
|
|
990
|
-
(0,
|
|
976
|
+
(0, system_1.scheduleSelfRestart)({ delayMs: 500 });
|
|
991
977
|
return { packageName, fromVersion, toVersion, restartingAt };
|
|
992
978
|
}
|
|
993
979
|
// =========================================================================
|
|
@@ -996,10 +982,9 @@ class AddonPackageService {
|
|
|
996
982
|
/** Re-discover addons from the addons directory (call after install/uninstall) */
|
|
997
983
|
async reloadPackages() {
|
|
998
984
|
try {
|
|
999
|
-
const kernel = await Promise.resolve().then(() => __importStar(require('@camstack/
|
|
985
|
+
const kernel = await Promise.resolve().then(() => __importStar(require('@camstack/system')));
|
|
1000
986
|
this.loader = new kernel.AddonLoader(this.logger.child('AddonLoader'));
|
|
1001
|
-
const
|
|
1002
|
-
const addonsDir = path.resolve(dataDir, 'addons');
|
|
987
|
+
const addonsDir = this.resolveAddonsDir();
|
|
1003
988
|
await this.loader.loadFromDirectory(addonsDir);
|
|
1004
989
|
this.logger.info('Reloaded', { meta: { count: this.loader.listAddons().length } });
|
|
1005
990
|
}
|
|
@@ -1402,13 +1387,22 @@ class AddonPackageService {
|
|
|
1402
1387
|
}
|
|
1403
1388
|
/** Resolve the addons directory from config or fall back to default */
|
|
1404
1389
|
resolveAddonsDir() {
|
|
1390
|
+
// Must match where the runtime LOADS addons (launcher.ts +
|
|
1391
|
+
// addon-registry.service.ts honor CAMSTACK_ADDONS_DIR). If this service
|
|
1392
|
+
// installed/reloaded from a different dir than the runtime loads, a
|
|
1393
|
+
// post-install reload re-registers the hub node from an INCOMPLETE set
|
|
1394
|
+
// (registerNode replaces the cap set atomically) — dropping in-process
|
|
1395
|
+
// builtins like device-manager/addon-pages/addon-widgets.
|
|
1396
|
+
const envDir = process.env.CAMSTACK_ADDONS_DIR;
|
|
1397
|
+
if (envDir && envDir.trim().length > 0)
|
|
1398
|
+
return path.resolve(envDir);
|
|
1405
1399
|
const dataPath = this.configService.get('server.dataPath') ?? 'camstack-data';
|
|
1406
1400
|
return path.resolve(dataPath, 'addons');
|
|
1407
1401
|
}
|
|
1408
1402
|
/** Throw if installer was not initialised */
|
|
1409
1403
|
requireInstaller() {
|
|
1410
1404
|
if (!this.installer) {
|
|
1411
|
-
throw new Error('AddonInstaller is not available -- @camstack/
|
|
1405
|
+
throw new Error('AddonInstaller is not available -- @camstack/system may not be installed');
|
|
1412
1406
|
}
|
|
1413
1407
|
}
|
|
1414
1408
|
/** Send notification and toast for a successful package update */
|