@camstack/server 1.2.8 → 1.2.10
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/core/cap-providers.js +35 -14
- package/dist/api/trpc/cap-router-runtime.js +6 -0
- package/dist/api/trpc/generated-cap-mounts.js +1 -2
- package/dist/api/trpc/generated-cap-routers.js +792 -754
- package/dist/core/addon/addon-package.service.js +63 -2
- package/dist/core/addon/addon-registry.service.js +8 -4
- package/package.json +9 -10
- package/dist/api/addons-custom.router.js +0 -91
|
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AddonPackageService = exports.FRAMEWORK_PACKAGES = exports.SYSTEM_PACKAGE = void 0;
|
|
36
|
+
exports.AddonPackageService = exports.FRAMEWORK_PACKAGES = exports.AUTO_UPDATE_EXCLUDED_PACKAGES = exports.SYSTEM_PACKAGE = void 0;
|
|
37
|
+
exports.isVersionNewer = isVersionNewer;
|
|
37
38
|
exports.isFrameworkPackage = isFrameworkPackage;
|
|
38
39
|
exports.extractTgzStripped = extractTgzStripped;
|
|
39
40
|
const fs = __importStar(require("node:fs"));
|
|
@@ -51,6 +52,53 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
|
51
52
|
* export because `lifecycle-job-runner` + several tests reference it directly.
|
|
52
53
|
*/
|
|
53
54
|
exports.SYSTEM_PACKAGE = '@camstack/system';
|
|
55
|
+
/**
|
|
56
|
+
* Packages the npm auto-updater must NEVER touch: the framework/system tier
|
|
57
|
+
* ships exclusively via `applyServerUpdate` (single-copy collapse). Listed
|
|
58
|
+
* explicitly (not via a manifest flag) because the updater runs against
|
|
59
|
+
* `listInstalled()` package names before any manifest is loaded.
|
|
60
|
+
*/
|
|
61
|
+
exports.AUTO_UPDATE_EXCLUDED_PACKAGES = new Set([
|
|
62
|
+
exports.SYSTEM_PACKAGE,
|
|
63
|
+
'@camstack/types',
|
|
64
|
+
'@camstack/kernel',
|
|
65
|
+
'@camstack/core',
|
|
66
|
+
'@camstack/sdk',
|
|
67
|
+
'@camstack/ui-library',
|
|
68
|
+
'@camstack/server',
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* True when `target` is a STRICTLY newer semver than `current`. Minimal
|
|
72
|
+
* x.y.z(-prerelease) comparator — our registry versions are plain publishes,
|
|
73
|
+
* and a dedicated semver dependency isn't declared in this package. Numeric
|
|
74
|
+
* triples compare first; a prerelease ranks BELOW its release (1.2.6-dev <
|
|
75
|
+
* 1.2.6); when both carry prereleases they compare lexicographically.
|
|
76
|
+
* Unparseable versions are never "newer" (fail safe: no update).
|
|
77
|
+
*/
|
|
78
|
+
function isVersionNewer(target, current) {
|
|
79
|
+
const parse = (v) => {
|
|
80
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(v.trim());
|
|
81
|
+
if (!m)
|
|
82
|
+
return null;
|
|
83
|
+
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
|
|
84
|
+
};
|
|
85
|
+
const t = parse(target);
|
|
86
|
+
const c = parse(current);
|
|
87
|
+
if (!t || !c)
|
|
88
|
+
return false;
|
|
89
|
+
for (let i = 0; i < 3; i += 1) {
|
|
90
|
+
if (t.nums[i] !== c.nums[i])
|
|
91
|
+
return t.nums[i] > c.nums[i];
|
|
92
|
+
}
|
|
93
|
+
// Same triple: release > prerelease; two prereleases compare lexically.
|
|
94
|
+
if (t.pre === null && c.pre !== null)
|
|
95
|
+
return true;
|
|
96
|
+
if (t.pre !== null && c.pre === null)
|
|
97
|
+
return false;
|
|
98
|
+
if (t.pre !== null && c.pre !== null)
|
|
99
|
+
return t.pre > c.pre;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
54
102
|
/**
|
|
55
103
|
* Host-provided framework packages that take a special path through
|
|
56
104
|
* `camstack deploy` (the addon-upload route) instead of the normal
|
|
@@ -1145,6 +1193,13 @@ class AddonPackageService {
|
|
|
1145
1193
|
const targets = [];
|
|
1146
1194
|
for (const pkg of installed) {
|
|
1147
1195
|
try {
|
|
1196
|
+
// Framework packages NEVER auto-update from npm — they ship via
|
|
1197
|
+
// `applyServerUpdate` (single-copy collapse). The engine already
|
|
1198
|
+
// skipped a swept framework target; excluding it here also kills the
|
|
1199
|
+
// useless candidate churn that participated in the 2026-07-22 hub
|
|
1200
|
+
// wedge (mass staged swap right after a publish train).
|
|
1201
|
+
if (exports.AUTO_UPDATE_EXCLUDED_PACKAGES.has(pkg.name))
|
|
1202
|
+
continue;
|
|
1148
1203
|
// Determine effective channel for this addon
|
|
1149
1204
|
const addonId = pkg.name.replace('@camstack/addon-', '').replace('@camstack/', '');
|
|
1150
1205
|
const override = this.autoUpdateConfig.overrides[addonId];
|
|
@@ -1165,7 +1220,13 @@ class AddonPackageService {
|
|
|
1165
1220
|
const targetVersion = effectiveChannel === 'beta'
|
|
1166
1221
|
? asString(distTags['beta']) || asString(distTags['latest'])
|
|
1167
1222
|
: asString(distTags['latest']);
|
|
1168
|
-
|
|
1223
|
+
// FORWARD-ONLY: update only when the registry advertises a STRICTLY
|
|
1224
|
+
// NEWER semver. The old `!==` comparison happily "updated" BACKWARD —
|
|
1225
|
+
// on 2026-07-22 it replaced same-day CLI-deployed builds (labelled
|
|
1226
|
+
// with the older workspace version) with the freshly published npm
|
|
1227
|
+
// artifacts, rolling back live fixes. A dev build whose label equals
|
|
1228
|
+
// or exceeds the npm tag is left alone.
|
|
1229
|
+
if (!targetVersion || !isVersionNewer(targetVersion, pkg.version))
|
|
1169
1230
|
continue;
|
|
1170
1231
|
this.logger.info('Auto-update candidate', {
|
|
1171
1232
|
meta: {
|
|
@@ -1225,11 +1225,11 @@ class AddonRegistryService {
|
|
|
1225
1225
|
if (initResult?.customActions && initResult.actionHandlers) {
|
|
1226
1226
|
const handlers = initResult.actionHandlers;
|
|
1227
1227
|
try {
|
|
1228
|
-
this.customActionRegistry.registerAddon(id, initResult.customActions, (action, input) => {
|
|
1228
|
+
this.customActionRegistry.registerAddon(id, initResult.customActions, (action, input, caller) => {
|
|
1229
1229
|
const fn = handlers[action];
|
|
1230
1230
|
if (!fn)
|
|
1231
1231
|
throw new Error(`addon '${id}' has no handler for custom action '${action}'`);
|
|
1232
|
-
return fn(input);
|
|
1232
|
+
return fn(input, caller);
|
|
1233
1233
|
});
|
|
1234
1234
|
}
|
|
1235
1235
|
catch (err) {
|
|
@@ -2987,7 +2987,7 @@ class AddonRegistryService {
|
|
|
2987
2987
|
});
|
|
2988
2988
|
return;
|
|
2989
2989
|
}
|
|
2990
|
-
this.customActionRegistry.registerAddon(addonId, catalog, (action, input) => this.dispatchForkedCustomAction(addonId, action, input));
|
|
2990
|
+
this.customActionRegistry.registerAddon(addonId, catalog, (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
|
|
2991
2991
|
this.logger.info('Runner addon custom actions registered', {
|
|
2992
2992
|
tags: { addonId },
|
|
2993
2993
|
meta: { runnerId },
|
|
@@ -2999,11 +2999,15 @@ class AddonRegistryService {
|
|
|
2999
2999
|
* routing + the child-availability error; there is no broker fallback after
|
|
3000
3000
|
* the per-addon Moleculer broker was removed.
|
|
3001
3001
|
*/
|
|
3002
|
-
async dispatchForkedCustomAction(addonId, action, input) {
|
|
3002
|
+
async dispatchForkedCustomAction(addonId, action, input, caller) {
|
|
3003
3003
|
return this.addonCallGateway.callForked(addonId, {
|
|
3004
3004
|
target: 'custom',
|
|
3005
3005
|
action,
|
|
3006
3006
|
args: input,
|
|
3007
|
+
// Additive: only carry `caller` when the hub actually forwarded one (the
|
|
3008
|
+
// router populates it exclusively for a `caller:'required'` action of an
|
|
3009
|
+
// authenticated request). Never blanket; never from client input.
|
|
3010
|
+
...(caller ? { caller } : {}),
|
|
3007
3011
|
});
|
|
3008
3012
|
}
|
|
3009
3013
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -33,20 +33,19 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
37
|
-
"@camstack/addon-advanced-notifier": "1.2.4",
|
|
36
|
+
"@camstack/addon-admin-ui": "1.2.7",
|
|
38
37
|
"@camstack/addon-agent-ui": "1.2.4",
|
|
39
38
|
"@camstack/addon-auth": "1.2.4",
|
|
40
39
|
"@camstack/addon-decoder-nodeav": "1.2.4",
|
|
41
|
-
"@camstack/addon-notifiers": "1.2.
|
|
42
|
-
"@camstack/addon-pipeline": "1.2.
|
|
43
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
44
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
40
|
+
"@camstack/addon-notifiers": "1.2.5",
|
|
41
|
+
"@camstack/addon-pipeline": "1.2.8",
|
|
42
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.9",
|
|
43
|
+
"@camstack/addon-post-analysis": "1.2.8",
|
|
45
44
|
"@camstack/sdk": "1.2.4",
|
|
46
45
|
"@camstack/shm-ring": "1.1.4",
|
|
47
|
-
"@camstack/system": "1.2.
|
|
48
|
-
"@camstack/types": "1.2.
|
|
49
|
-
"@camstack/ui-library": "1.2.
|
|
46
|
+
"@camstack/system": "1.2.9",
|
|
47
|
+
"@camstack/types": "1.2.9",
|
|
48
|
+
"@camstack/ui-library": "1.2.6",
|
|
50
49
|
"@fastify/compress": "^9.0.0",
|
|
51
50
|
"@fastify/cookie": "^11.0.2",
|
|
52
51
|
"@fastify/cors": "^11.2.0",
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAddonsCustomProcedures = createAddonsCustomProcedures;
|
|
4
|
-
/**
|
|
5
|
-
* `api.addons.custom` — generic dispatcher for addon-defined custom actions.
|
|
6
|
-
*
|
|
7
|
-
* Task 7.2 of the device-proxy redesign. Addons declare a catalog of
|
|
8
|
-
* custom actions at boot via `AddonInitResult.customActions` + a single
|
|
9
|
-
* `handleCustomAction(action, input)` handler. The catalog is registered
|
|
10
|
-
* with a per-process `CustomActionRegistry` (Task 7.1). This endpoint is
|
|
11
|
-
* the single tRPC entry point that resolves an `(addonId, action)` pair,
|
|
12
|
-
* validates input + output against the action's Zod schemas, enforces the
|
|
13
|
-
* action's declared auth level, and dispatches to the addon handler.
|
|
14
|
-
*
|
|
15
|
-
* The factory returns a record of procedures (not a router) so the caller
|
|
16
|
-
* can spread it into the existing `addons` namespace:
|
|
17
|
-
*
|
|
18
|
-
* trpcRouter({
|
|
19
|
-
* ...existingAddonsProcedures,
|
|
20
|
-
* ...createAddonsCustomProcedures({ getCustomActionRegistry: ... }),
|
|
21
|
-
* })
|
|
22
|
-
*
|
|
23
|
-
* This avoids `mergeRouters` (which requires sharing the `t` instance
|
|
24
|
-
* across modules) while still mounting the procedure at `api.addons.custom`.
|
|
25
|
-
*/
|
|
26
|
-
const zod_1 = require("zod");
|
|
27
|
-
const server_1 = require("@trpc/server");
|
|
28
|
-
const trpc_middleware_js_1 = require("./trpc/trpc.middleware.js");
|
|
29
|
-
/**
|
|
30
|
-
* Build the procedure record for the `custom` endpoint.
|
|
31
|
-
*
|
|
32
|
-
* The OUTER procedure is `protectedProcedure` — every caller must be
|
|
33
|
-
* authenticated. The INNER per-action auth declared in `spec.auth` is
|
|
34
|
-
* enforced manually by `ensureAuth` because the auth level is not known
|
|
35
|
-
* until after the registry lookup.
|
|
36
|
-
*/
|
|
37
|
-
function createAddonsCustomProcedures(deps) {
|
|
38
|
-
return {
|
|
39
|
-
custom: trpc_middleware_js_1.protectedProcedure
|
|
40
|
-
.input(zod_1.z.object({
|
|
41
|
-
addonId: zod_1.z.string().min(1),
|
|
42
|
-
action: zod_1.z.string().min(1),
|
|
43
|
-
input: zod_1.z.unknown(),
|
|
44
|
-
}))
|
|
45
|
-
.output(zod_1.z.unknown())
|
|
46
|
-
.mutation(async ({ input, ctx }) => {
|
|
47
|
-
const registry = deps.getCustomActionRegistry();
|
|
48
|
-
const entry = registry.resolve(input.addonId, input.action);
|
|
49
|
-
if (!entry) {
|
|
50
|
-
throw new server_1.TRPCError({
|
|
51
|
-
code: 'NOT_FOUND',
|
|
52
|
-
message: `addon '${input.addonId}' has no custom action '${input.action}'`,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
// Per-action authorization. The outer procedure already requires
|
|
56
|
-
// authentication; here we additionally enforce the declared role
|
|
57
|
-
// when it's stricter than 'protected'.
|
|
58
|
-
ensureAuth(ctx, entry.spec.auth);
|
|
59
|
-
// Validate input against the action's declared Zod schema.
|
|
60
|
-
const parsedInput = entry.spec.input.parse(input.input);
|
|
61
|
-
// Dispatch through the addon handler.
|
|
62
|
-
const result = await entry.handler(parsedInput);
|
|
63
|
-
// Validate the addon's output. Crash-early on misbehaving addons.
|
|
64
|
-
return entry.spec.output.parse(result);
|
|
65
|
-
}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Enforce the action's declared auth level.
|
|
70
|
-
*
|
|
71
|
-
* Mirrors the role checks performed by `protectedProcedure` and
|
|
72
|
-
* `adminProcedure` in trpc.middleware.ts:
|
|
73
|
-
* - public: no auth
|
|
74
|
-
* - protected: any authenticated user
|
|
75
|
-
* - admin: isAdmin only (scoped tokens bounce)
|
|
76
|
-
*/
|
|
77
|
-
function ensureAuth(ctx, level) {
|
|
78
|
-
if (level === 'public')
|
|
79
|
-
return;
|
|
80
|
-
if (!ctx.user) {
|
|
81
|
-
throw new server_1.TRPCError({ code: 'UNAUTHORIZED' });
|
|
82
|
-
}
|
|
83
|
-
if (level === 'protected')
|
|
84
|
-
return;
|
|
85
|
-
if (level === 'admin') {
|
|
86
|
-
if (!ctx.user.isAdmin) {
|
|
87
|
-
throw new server_1.TRPCError({ code: 'FORBIDDEN', message: 'custom action requires admin' });
|
|
88
|
-
}
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
}
|