@camstack/server 1.2.138 → 1.2.139
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/agent/agent-update-service.js +9 -13
- package/dist/core/addon/addon-package.service.js +19 -26
- package/dist/core/update-availability-emitter.js +36 -26
- package/dist/core/updates/restart-marker.js +38 -0
- package/dist/manual-boot.js +1 -1
- package/dist/server-root/index.js +7 -1
- package/package.json +7 -7
|
@@ -70,6 +70,7 @@ const system_exec_npm_js_1 = require("../core/server-update/system-exec-npm.js")
|
|
|
70
70
|
const update_availability_emitter_js_1 = require("../core/update-availability-emitter.js");
|
|
71
71
|
const update_availability_store_js_1 = require("../core/update-availability-store.js");
|
|
72
72
|
const index_js_1 = require("../server-root/index.js");
|
|
73
|
+
const restart_marker_js_1 = require("../core/updates/restart-marker.js");
|
|
73
74
|
/**
|
|
74
75
|
* The synthetic addonId the agent's OWN runtime registers infra providers
|
|
75
76
|
* under (no addon owns them — the bootstrap does). Appears in the agent's
|
|
@@ -168,8 +169,8 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
|
|
|
168
169
|
seedDir: 'CAMSTACK_SEED_SERVER_DIR',
|
|
169
170
|
},
|
|
170
171
|
logger: options.logger,
|
|
171
|
-
restartServer: (requestedBy) => {
|
|
172
|
-
writeAgentRestartMarker(options.dataDir, requestedBy);
|
|
172
|
+
restartServer: (requestedBy, update) => {
|
|
173
|
+
writeAgentRestartMarker(options.dataDir, requestedBy, update);
|
|
173
174
|
restartAgent(requestedBy);
|
|
174
175
|
},
|
|
175
176
|
dataDir: options.dataDir,
|
|
@@ -253,20 +254,15 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
exports.AgentUpdateService = AgentUpdateService;
|
|
256
|
-
function writeAgentRestartMarker(dataDir, requestedBy) {
|
|
257
|
-
const toVersion = /@([^@\s]+)$/.exec(requestedBy)?.[1];
|
|
257
|
+
function writeAgentRestartMarker(dataDir, requestedBy, update) {
|
|
258
258
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
// Same builder as the hub: an agent's "Server aggiornato" notification must
|
|
260
|
+
// name both versions too — see `core/updates/restart-marker.ts`.
|
|
261
|
+
(0, system_1.writePendingRestart)(dataDir, (0, restart_marker_js_1.buildRestartMarker)({
|
|
261
262
|
requestedAt: Date.now(),
|
|
262
263
|
requestedBy,
|
|
263
|
-
...(
|
|
264
|
-
|
|
265
|
-
packageName: '@camstack/server',
|
|
266
|
-
...(toVersion !== undefined ? { toVersion } : {}),
|
|
267
|
-
}
|
|
268
|
-
: {}),
|
|
269
|
-
});
|
|
264
|
+
...(update !== undefined ? { update } : {}),
|
|
265
|
+
}));
|
|
270
266
|
}
|
|
271
267
|
catch {
|
|
272
268
|
// Marker write must never block the restart.
|
|
@@ -47,6 +47,7 @@ const system_1 = require("@camstack/system");
|
|
|
47
47
|
const types_1 = require("@camstack/types");
|
|
48
48
|
const update_availability_emitter_js_1 = require("../update-availability-emitter.js");
|
|
49
49
|
const update_check_scheduler_js_1 = require("../updates/update-check-scheduler.js");
|
|
50
|
+
const restart_marker_js_1 = require("../updates/restart-marker.js");
|
|
50
51
|
const package_dir_utils_js_1 = require("./package-dir-utils.js");
|
|
51
52
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
52
53
|
/**
|
|
@@ -1105,28 +1106,25 @@ class AddonPackageService {
|
|
|
1105
1106
|
/**
|
|
1106
1107
|
* Gracefully restart the server process.
|
|
1107
1108
|
*
|
|
1108
|
-
* Writes
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1109
|
+
* Writes the restart marker so the post-boot service can emit
|
|
1110
|
+
* `system.restart-completed` on the next boot (powers the admin-UI
|
|
1111
|
+
* "Server restarted" toast AND the operator's "Server aggiornato"
|
|
1112
|
+
* notification). `update` is passed by `RootUpdateService` for an
|
|
1113
|
+
* update-driven restart and is the ONLY source of the version being left
|
|
1114
|
+
* behind — see `updates/restart-marker.ts`. Emits `system.restarting`
|
|
1111
1115
|
* with the same payload so subscribers can show a reconnect overlay
|
|
1112
1116
|
* immediately. Then delegates to `scheduleSelfRestart` which picks
|
|
1113
1117
|
* the right exit path (Electron `app.relaunch` vs supervisor-driven
|
|
1114
1118
|
* `process.exit`).
|
|
1115
1119
|
*/
|
|
1116
|
-
restartServer(requestedBy) {
|
|
1120
|
+
restartServer(requestedBy, update) {
|
|
1117
1121
|
this.logger.info('Server restart requested -- initiating graceful shutdown');
|
|
1118
|
-
|
|
1119
|
-
const payload = {
|
|
1120
|
-
kind: isServerUpdateRequest(requestedBy) ? 'framework-update' : 'manual',
|
|
1122
|
+
// Both versions, one builder, shared with the agent — see `restart-marker.ts`.
|
|
1123
|
+
const payload = (0, restart_marker_js_1.buildRestartMarker)({
|
|
1121
1124
|
requestedAt: Date.now(),
|
|
1122
1125
|
...(requestedBy !== undefined ? { requestedBy } : {}),
|
|
1123
|
-
...(
|
|
1124
|
-
|
|
1125
|
-
packageName: '@camstack/server',
|
|
1126
|
-
...(toVersion !== undefined ? { toVersion } : {}),
|
|
1127
|
-
}
|
|
1128
|
-
: {}),
|
|
1129
|
-
};
|
|
1126
|
+
...(update !== undefined ? { update } : {}),
|
|
1127
|
+
});
|
|
1130
1128
|
try {
|
|
1131
1129
|
(0, system_1.writePendingRestart)(this.resolveDataDir(), payload);
|
|
1132
1130
|
}
|
|
@@ -1682,22 +1680,26 @@ class AddonPackageService {
|
|
|
1682
1680
|
* re-announces them on the next good poll.
|
|
1683
1681
|
*/
|
|
1684
1682
|
publishAddonAvailability(updates, failures, nodeId) {
|
|
1683
|
+
// The hub's own sweep passes no nodeId — but its rows are about a NODE
|
|
1684
|
+
// like any other, and a list whose lines cannot be attributed is what made
|
|
1685
|
+
// three announcements look like one.
|
|
1686
|
+
const scope = nodeId ?? this.resolveNodeId();
|
|
1685
1687
|
const candidates = updates.map((update) => ({
|
|
1686
1688
|
target: 'addon',
|
|
1687
1689
|
packageName: update.name,
|
|
1688
1690
|
currentVersion: update.currentVersion,
|
|
1689
1691
|
latestVersion: update.latestVersion,
|
|
1690
|
-
|
|
1692
|
+
nodeId: scope,
|
|
1691
1693
|
}));
|
|
1692
1694
|
if (failures > 0) {
|
|
1693
1695
|
this.logger.warn('Addon update check incomplete — availability published as partial', {
|
|
1694
|
-
tags: { nodeId:
|
|
1696
|
+
tags: { nodeId: scope },
|
|
1695
1697
|
meta: { failures, published: candidates.length },
|
|
1696
1698
|
});
|
|
1697
1699
|
this.updateAvailability.publishCandidates(candidates);
|
|
1698
1700
|
return;
|
|
1699
1701
|
}
|
|
1700
|
-
this.updateAvailability.publishSnapshot('addon', candidates,
|
|
1702
|
+
this.updateAvailability.publishSnapshot('addon', candidates, scope);
|
|
1701
1703
|
}
|
|
1702
1704
|
/** Fetch npm search results for camstack addon packages (cached 5 min) */
|
|
1703
1705
|
async fetchSearchFromNpm() {
|
|
@@ -1860,15 +1862,6 @@ class AddonPackageService {
|
|
|
1860
1862
|
}
|
|
1861
1863
|
}
|
|
1862
1864
|
exports.AddonPackageService = AddonPackageService;
|
|
1863
|
-
function isServerUpdateRequest(requestedBy) {
|
|
1864
|
-
return requestedBy !== undefined && requestedBy.startsWith('server-update:');
|
|
1865
|
-
}
|
|
1866
|
-
function toVersionFromRestartReason(requestedBy) {
|
|
1867
|
-
if (requestedBy === undefined)
|
|
1868
|
-
return undefined;
|
|
1869
|
-
const match = /@([^@\s]+)$/.exec(requestedBy);
|
|
1870
|
-
return match?.[1];
|
|
1871
|
-
}
|
|
1872
1865
|
// ---------------------------------------------------------------------------
|
|
1873
1866
|
// Tarball extraction helper (exported for regression tests)
|
|
1874
1867
|
// ---------------------------------------------------------------------------
|
|
@@ -7,10 +7,16 @@ const types_1 = require("@camstack/types");
|
|
|
7
7
|
* `latestVersion` (or first-sees a package/node that has one). Repeated polls
|
|
8
8
|
* of the same latests are silent.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* One event carries the entire currently-available list **of one node** — the
|
|
11
|
+
* scope that changed, never the union across nodes. The Notification Center
|
|
12
|
+
* turns that into "2 aggiornamenti addon disponibili su little-unraid" with
|
|
13
|
+
* that node's packages under it.
|
|
14
|
+
*
|
|
15
|
+
* The union was the shape until 2026-08-19, and on a three-node cluster one
|
|
16
|
+
* 6-hourly sweep sent the operator THREE pushes that each listed hub +
|
|
17
|
+
* MinidiGianluca + little-unraid and named no node: every scope's change
|
|
18
|
+
* re-emitted the whole held map, so the notifications differed by one line and
|
|
19
|
+
* read as identical. A list is only actionable if it belongs to somebody.
|
|
14
20
|
*
|
|
15
21
|
* A successful empty snapshot clears prior state for that scope; failed checks
|
|
16
22
|
* should not call this method.
|
|
@@ -67,22 +73,22 @@ class UpdateAvailabilityEmitter {
|
|
|
67
73
|
this.persistIfDirty();
|
|
68
74
|
if (!latestChanged)
|
|
69
75
|
return;
|
|
70
|
-
this.emitList(target);
|
|
76
|
+
this.emitList(target, scope);
|
|
71
77
|
}
|
|
72
78
|
/** Publish a partial candidate set without treating omissions as up-to-date. */
|
|
73
79
|
publishCandidates(candidates) {
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// Only the (target, node) pairs that actually moved are announced — a
|
|
81
|
+
// partial sweep of one node must not re-announce another node's list.
|
|
82
|
+
const changed = new Map();
|
|
76
83
|
for (const candidate of candidates) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
if (!this.note(candidate))
|
|
85
|
+
continue;
|
|
86
|
+
const scope = scopeOf(candidate);
|
|
87
|
+
changed.set(`${candidate.target}:${scope}`, { target: candidate.target, scope });
|
|
80
88
|
}
|
|
81
89
|
this.persistIfDirty();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const target of targets)
|
|
85
|
-
this.emitList(target);
|
|
90
|
+
for (const { target, scope } of changed.values())
|
|
91
|
+
this.emitList(target, scope);
|
|
86
92
|
}
|
|
87
93
|
note(candidate) {
|
|
88
94
|
const key = this.key(candidate);
|
|
@@ -111,21 +117,24 @@ class UpdateAvailabilityEmitter {
|
|
|
111
117
|
held: Object.fromEntries(this.held),
|
|
112
118
|
});
|
|
113
119
|
}
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
/** Announce ONE scope's full list. `scope` is the node id, `hub` for the hub. */
|
|
121
|
+
emitList(target, scope) {
|
|
122
|
+
const list = [...this.held.values()].filter((candidate) => candidate.target === target && scopeOf(candidate) === scope);
|
|
116
123
|
if (list.length === 0)
|
|
117
124
|
return;
|
|
125
|
+
// Every row names its node, the hub's included: the row a caller published
|
|
126
|
+
// without a nodeId is still ABOUT a node, and an unlabelled line in a
|
|
127
|
+
// cluster-wide list is unreadable.
|
|
118
128
|
const packages = list.map((candidate) => ({
|
|
119
129
|
packageName: candidate.packageName,
|
|
120
130
|
currentVersion: candidate.currentVersion,
|
|
121
131
|
latestVersion: candidate.latestVersion,
|
|
122
|
-
|
|
132
|
+
nodeId: scope,
|
|
123
133
|
}));
|
|
124
|
-
const nodeIds = unique(packages.map((pkg) => pkg.nodeId).filter((id) => id !== undefined));
|
|
125
134
|
const head = list[0];
|
|
126
135
|
this.eventBus.emit({
|
|
127
|
-
id: `update.available:${target}:${packages
|
|
128
|
-
.map((pkg) => `${pkg.
|
|
136
|
+
id: `update.available:${target}:${scope}:${packages
|
|
137
|
+
.map((pkg) => `${pkg.packageName}@${pkg.latestVersion}`)
|
|
129
138
|
.toSorted()
|
|
130
139
|
.join(',')}`,
|
|
131
140
|
timestamp: new Date(),
|
|
@@ -137,21 +146,22 @@ class UpdateAvailabilityEmitter {
|
|
|
137
146
|
currentVersion: head.currentVersion,
|
|
138
147
|
latestVersion: head.latestVersion,
|
|
139
148
|
packages,
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
nodeId: scope,
|
|
150
|
+
nodeIds: [scope],
|
|
142
151
|
},
|
|
143
152
|
});
|
|
144
153
|
}
|
|
145
154
|
key(candidate) {
|
|
146
|
-
return `${candidate.target}:${candidate
|
|
155
|
+
return `${candidate.target}:${scopeOf(candidate)}:${candidate.packageName}`;
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
158
|
exports.UpdateAvailabilityEmitter = UpdateAvailabilityEmitter;
|
|
159
|
+
/** The node an availability row belongs to. A row without one is the hub's. */
|
|
160
|
+
function scopeOf(candidate) {
|
|
161
|
+
return candidate.nodeId ?? 'hub';
|
|
162
|
+
}
|
|
150
163
|
function stampNode(candidate, nodeId) {
|
|
151
164
|
if (nodeId === undefined || candidate.nodeId !== undefined)
|
|
152
165
|
return candidate;
|
|
153
166
|
return { ...candidate, nodeId };
|
|
154
167
|
}
|
|
155
|
-
function unique(values) {
|
|
156
|
-
return [...new Set(values)];
|
|
157
|
-
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRestartMarker = buildRestartMarker;
|
|
4
|
+
exports.isServerUpdateReason = isServerUpdateReason;
|
|
5
|
+
exports.versionFromReason = versionFromReason;
|
|
6
|
+
/** Reason-string prefix every `RootUpdateService`-driven restart carries. */
|
|
7
|
+
const SERVER_UPDATE_PREFIX = 'server-update:';
|
|
8
|
+
/** The root package a version-less `server-update:` reason is about. */
|
|
9
|
+
const ROOT_PACKAGE_NAME = '@camstack/server';
|
|
10
|
+
function buildRestartMarker(input) {
|
|
11
|
+
const { requestedBy, update, requestedAt } = input;
|
|
12
|
+
const toVersion = update?.toVersion ?? versionFromReason(requestedBy);
|
|
13
|
+
const isUpdate = update !== undefined || (isServerUpdateReason(requestedBy) && toVersion !== undefined);
|
|
14
|
+
if (!isUpdate) {
|
|
15
|
+
return {
|
|
16
|
+
kind: 'manual',
|
|
17
|
+
requestedAt,
|
|
18
|
+
...(requestedBy !== undefined ? { requestedBy } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
kind: 'framework-update',
|
|
23
|
+
requestedAt,
|
|
24
|
+
...(requestedBy !== undefined ? { requestedBy } : {}),
|
|
25
|
+
packageName: update?.packageName ?? ROOT_PACKAGE_NAME,
|
|
26
|
+
...(update?.fromVersion !== undefined ? { fromVersion: update.fromVersion } : {}),
|
|
27
|
+
...(toVersion !== undefined ? { toVersion } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function isServerUpdateReason(requestedBy) {
|
|
31
|
+
return requestedBy !== undefined && requestedBy.startsWith(SERVER_UPDATE_PREFIX);
|
|
32
|
+
}
|
|
33
|
+
/** `server-update: @camstack/server@1.2.138` → `1.2.138`; `… manual restart` → none. */
|
|
34
|
+
function versionFromReason(requestedBy) {
|
|
35
|
+
if (requestedBy === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
return /@([^@\s]+)$/.exec(requestedBy)?.[1];
|
|
38
|
+
}
|
package/dist/manual-boot.js
CHANGED
|
@@ -238,7 +238,7 @@ async function bootManual(opts) {
|
|
|
238
238
|
// AddonPackageService.restartServer for the marker + reconnect toast.
|
|
239
239
|
const serverUpdateService = new server_update_service_1.ServerUpdateService({
|
|
240
240
|
logger: loggingService.createLogger('ServerUpdate'),
|
|
241
|
-
restartServer: (requestedBy) => addonPackageService.restartServer(requestedBy),
|
|
241
|
+
restartServer: (requestedBy, update) => addonPackageService.restartServer(requestedBy, update),
|
|
242
242
|
eventBus: eventBusService,
|
|
243
243
|
updateAvailabilityStore: new update_availability_store_js_1.FileUpdateAvailabilityStore(availabilityDataDir, 'server-update', loggingService.createLogger('UpdateAvailability')),
|
|
244
244
|
});
|
|
@@ -1216,7 +1216,13 @@ var require_dist = __commonJS({
|
|
|
1216
1216
|
fromVersion: runningVersion
|
|
1217
1217
|
}
|
|
1218
1218
|
});
|
|
1219
|
-
this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}
|
|
1219
|
+
this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}`, {
|
|
1220
|
+
packageName: this.spec.packageName,
|
|
1221
|
+
...runningVersion !== null ? {
|
|
1222
|
+
fromVersion: runningVersion
|
|
1223
|
+
} : {},
|
|
1224
|
+
toVersion: target
|
|
1225
|
+
});
|
|
1220
1226
|
return {
|
|
1221
1227
|
accepted: true,
|
|
1222
1228
|
targetVersion: target,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.139",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
36
|
+
"@camstack/addon-admin-ui": "1.2.84",
|
|
37
37
|
"@camstack/addon-agent-ui": "1.2.24",
|
|
38
38
|
"@camstack/addon-auth": "1.2.26",
|
|
39
39
|
"@camstack/addon-decoder-nodeav": "1.2.22",
|
|
40
40
|
"@camstack/addon-notifiers": "1.2.27",
|
|
41
|
-
"@camstack/addon-pipeline": "1.2.
|
|
41
|
+
"@camstack/addon-pipeline": "1.2.102",
|
|
42
42
|
"@camstack/addon-pipeline-orchestrator": "1.2.84",
|
|
43
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
43
|
+
"@camstack/addon-post-analysis": "1.2.102",
|
|
44
44
|
"@camstack/sdk": "1.2.25",
|
|
45
|
-
"@camstack/system": "1.2.
|
|
46
|
-
"@camstack/types": "1.2.
|
|
47
|
-
"@camstack/ui-library": "1.2.
|
|
45
|
+
"@camstack/system": "1.2.110",
|
|
46
|
+
"@camstack/types": "1.2.92",
|
|
47
|
+
"@camstack/ui-library": "1.2.63",
|
|
48
48
|
"@fastify/compress": "^9.0.0",
|
|
49
49
|
"@fastify/cookie": "^11.0.2",
|
|
50
50
|
"@fastify/cors": "^11.2.0",
|