@camstack/server 1.2.695 → 1.2.696

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.
@@ -4,6 +4,9 @@ exports.PostBootService = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const system_1 = require("@camstack/system");
6
6
  const types_1 = require("@camstack/types");
7
+ const release_identity_js_1 = require("../core/release-identity.js");
8
+ const node_update_announcement_js_1 = require("../core/updates/node-update-announcement.js");
9
+ const index_js_1 = require("../server-root/index.js");
7
10
  const reconcile_lifecycle_jobs_js_1 = require("./reconcile-lifecycle-jobs.js");
8
11
  class PostBootService {
9
12
  eventBus;
@@ -56,6 +59,9 @@ class PostBootService {
56
59
  // restart, …). `readPendingRestart` clears the marker atomically so
57
60
  // we never re-fire on a crash-loop boot.
58
61
  this.emitRestartCompletedIfPending(dataPath);
62
+ // A container that came up on a different image than its previous boot
63
+ // says so (`node-updating`): the old container is gone before this runs.
64
+ this.announceImageChangeIfAny(dataPath);
59
65
  // A certificate that changed underneath the operator is an alert, not
60
66
  // a boot line they will never scroll back to (D227).
61
67
  this.emitTlsCertChangedIfReissued(tlsCert);
@@ -97,6 +103,25 @@ class PostBootService {
97
103
  data: payload,
98
104
  });
99
105
  }
106
+ announceImageChangeIfAny(dataDir) {
107
+ try {
108
+ (0, node_update_announcement_js_1.announceProductVersionChange)({
109
+ dataDir,
110
+ productVersion: (0, release_identity_js_1.blankToNull)(process.env[release_identity_js_1.PRODUCT_VERSION_ENV]),
111
+ underDesktopShell: (process.env[index_js_1.ELECTRON_APP_VERSION_ENV] ?? '').length > 0,
112
+ bus: this.eventBus,
113
+ sourceId: 'system',
114
+ nodeId: process.env['CAMSTACK_NODE_ID'] ?? 'hub',
115
+ logger: this.logger,
116
+ });
117
+ }
118
+ catch (err) {
119
+ // Telemetry (D8): never block the boot.
120
+ this.logger.warn('image change check failed', {
121
+ meta: { error: err instanceof Error ? err.message : String(err) },
122
+ });
123
+ }
124
+ }
100
125
  emitRestartCompletedIfPending(dataDir) {
101
126
  const marker = (0, system_1.readPendingRestart)(dataDir);
102
127
  if (marker === null)
@@ -53,6 +53,7 @@ exports.ServerUpdateService = void 0;
53
53
  */
54
54
  const path = __importStar(require("node:path"));
55
55
  const index_js_1 = require("../../server-root/index.js");
56
+ const node_update_announcement_js_1 = require("../updates/node-update-announcement.js");
56
57
  const update_availability_emitter_js_1 = require("../update-availability-emitter.js");
57
58
  const system_ensure_prebuilds_js_1 = require("./system-ensure-prebuilds.js");
58
59
  const system_apply_closure_patches_js_1 = require("./system-apply-closure-patches.js");
@@ -103,6 +104,24 @@ class ServerUpdateService extends index_js_1.RootUpdateService {
103
104
  applyClosurePatches: options.applyClosurePatches ?? (0, system_apply_closure_patches_js_1.buildSystemApplyClosurePatches)(options.logger),
104
105
  env: options.env,
105
106
  now: options.now,
107
+ ...(options.readWrapperProbes !== undefined
108
+ ? { readWrapperProbes: options.readWrapperProbes }
109
+ : {}),
110
+ // The shell is about to replace itself, and this node with it: tell the
111
+ // operator now (D723's `node-updating`), not after the node is gone.
112
+ onDesktopInstallRequested: (info) => {
113
+ const bus = options.eventBus;
114
+ if (bus === undefined)
115
+ return;
116
+ (0, node_update_announcement_js_1.emitNodeUpdateAnnouncement)(bus, 'server-update-service',
117
+ // The node, as `AddonPackageService.restartServer` names it (D723).
118
+ options.env?.['CAMSTACK_NODE_ID'] ?? process.env['CAMSTACK_NODE_ID'] ?? nodeId, (0, node_update_announcement_js_1.buildNodeUpdateAnnouncement)({
119
+ what: 'desktop-shell',
120
+ fromVersion: info.fromVersion,
121
+ ...(info.toVersion !== null ? { toVersion: info.toVersion } : {}),
122
+ requestedAt: Date.now(),
123
+ }));
124
+ },
106
125
  ...(options.updatePreflight !== undefined
107
126
  ? { updatePreflight: options.updatePreflight }
108
127
  : {}),
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildNodeUpdateAnnouncement = buildNodeUpdateAnnouncement;
37
+ exports.emitNodeUpdateAnnouncement = emitNodeUpdateAnnouncement;
38
+ exports.detectProductVersionChange = detectProductVersionChange;
39
+ exports.announceProductVersionChange = announceProductVersionChange;
40
+ /**
41
+ * The announcements of an update that is NOT the `@camstack/server` closure
42
+ * (D723 covered that one): the Electron shell replacing itself, and a container
43
+ * that booted on a different image than its previous boot.
44
+ *
45
+ * Both travel as the same `system.restarting` payload the closure update uses,
46
+ * so the notification centre's one split (`restartIsUpdate`) turns them into
47
+ * `node-updating` with no new kind and no new event. `what` says which part is
48
+ * being replaced; `packageName` is its operator-facing name.
49
+ */
50
+ const fs = __importStar(require("node:fs"));
51
+ const path = __importStar(require("node:path"));
52
+ const node_crypto_1 = require("node:crypto");
53
+ const types_1 = require("@camstack/types");
54
+ /** Operator-facing name of what each non-closure update replaces. */
55
+ const UPDATE_TARGET_LABEL = {
56
+ 'desktop-shell': 'CamStack desktop app',
57
+ 'container-image': 'CamStack container image',
58
+ };
59
+ /** Where a boot records the product version it ran, to compare with the next boot. */
60
+ const LAST_BOOT_PRODUCT_VERSION_FILE = 'last-boot-product-version';
61
+ function buildNodeUpdateAnnouncement(input) {
62
+ return {
63
+ kind: 'framework-update',
64
+ what: input.what,
65
+ packageName: UPDATE_TARGET_LABEL[input.what],
66
+ ...(input.fromVersion !== undefined ? { fromVersion: input.fromVersion } : {}),
67
+ ...(input.toVersion !== undefined ? { toVersion: input.toVersion } : {}),
68
+ requestedAt: input.requestedAt,
69
+ };
70
+ }
71
+ /** Emit the announcement through the node's event bus (telemetry, D8: never throws). */
72
+ function emitNodeUpdateAnnouncement(bus, sourceId, nodeId, payload) {
73
+ bus.emit({
74
+ id: (0, node_crypto_1.randomUUID)(),
75
+ timestamp: new Date(),
76
+ source: { type: 'core', id: sourceId },
77
+ category: types_1.EventCategory.SystemRestarting,
78
+ data: { ...payload, nodeId },
79
+ });
80
+ }
81
+ /**
82
+ * Compare the product version this boot runs (`CAMSTACK_PRODUCT_VERSION`, set
83
+ * by the image) with the one the previous boot recorded, then record this one.
84
+ * The record is ONE small file in the data dir: the image is replaced under the
85
+ * node, so the data dir is the only thing that remembers the old one.
86
+ *
87
+ * `null` for the first boot, an unchanged version, or an unknown current
88
+ * version (a dev workspace leaves it unset — absence, not a change; the record
89
+ * is left alone then).
90
+ */
91
+ function detectProductVersionChange(dataDir, current) {
92
+ if (current === null)
93
+ return null;
94
+ const file = path.join(dataDir, LAST_BOOT_PRODUCT_VERSION_FILE);
95
+ let previous = null;
96
+ try {
97
+ const raw = fs.readFileSync(file, 'utf-8').trim();
98
+ previous = raw.length > 0 ? raw : null;
99
+ }
100
+ catch {
101
+ previous = null;
102
+ }
103
+ if (previous === current)
104
+ return null;
105
+ fs.mkdirSync(dataDir, { recursive: true });
106
+ fs.writeFileSync(file, `${current}\n`, 'utf-8');
107
+ return previous === null ? null : { from: previous, to: current };
108
+ }
109
+ /**
110
+ * At boot: when the container runs a different image than the previous boot,
111
+ * say so as a `node-updating`. The old container is gone before this process
112
+ * exists, so this is the earliest the change can be known. The record is
113
+ * updated either way; under the desktop shell nothing is announced (the shell
114
+ * announces its own install, and its product version moves with it).
115
+ */
116
+ function announceProductVersionChange(input) {
117
+ const change = detectProductVersionChange(input.dataDir, input.productVersion);
118
+ if (change === null || input.underDesktopShell)
119
+ return;
120
+ input.logger.info('container image changed since the previous boot', {
121
+ meta: { nodeId: input.nodeId, from: change.from, to: change.to },
122
+ });
123
+ emitNodeUpdateAnnouncement(input.bus, input.sourceId, input.nodeId, buildNodeUpdateAnnouncement({
124
+ what: 'container-image',
125
+ fromVersion: change.from,
126
+ toVersion: change.to,
127
+ requestedAt: (input.now ?? Date.now)(),
128
+ }));
129
+ }
@@ -1239,6 +1239,7 @@ var require_dist = __commonJS({
1239
1239
  env;
1240
1240
  readWrapperProbesFn;
1241
1241
  now;
1242
+ onDesktopInstallRequested;
1242
1243
  sleep;
1243
1244
  runningPackageJsonPath;
1244
1245
  workspaceProbeDir;
@@ -1282,6 +1283,7 @@ var require_dist = __commonJS({
1282
1283
  this.env = options.env ?? process.env;
1283
1284
  this.readWrapperProbesFn = options.readWrapperProbes ?? readWrapperProbes;
1284
1285
  this.now = options.now ?? Date.now;
1286
+ this.onDesktopInstallRequested = options.onDesktopInstallRequested;
1285
1287
  this.sleep = options.sleep ?? ((ms) => new Promise((resolve3) => {
1286
1288
  setTimeout(resolve3, ms);
1287
1289
  }));
@@ -1952,6 +1954,19 @@ var require_dist = __commonJS({
1952
1954
  updateVersion: desktopApp.update.version
1953
1955
  }
1954
1956
  });
1957
+ try {
1958
+ this.onDesktopInstallRequested?.({
1959
+ fromVersion: desktopApp.version,
1960
+ toVersion: desktopApp.update.version
1961
+ });
1962
+ } catch (err) {
1963
+ this.logger.warn("desktop update announcement failed", {
1964
+ tags: this.logTags,
1965
+ meta: {
1966
+ error: err instanceof Error ? err.message : String(err)
1967
+ }
1968
+ });
1969
+ }
1955
1970
  return {
1956
1971
  accepted: true,
1957
1972
  reason: null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.695",
3
+ "version": "1.2.696",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,20 +50,20 @@
50
50
  ]
51
51
  },
52
52
  "dependencies": {
53
- "@camstack/addon-admin-ui": "1.2.522",
54
- "@camstack/addon-agent-ui": "1.2.295",
53
+ "@camstack/addon-admin-ui": "1.2.523",
54
+ "@camstack/addon-agent-ui": "1.2.296",
55
55
  "@camstack/addon-auth": "1.2.170",
56
- "@camstack/addon-benchmark": "1.2.294",
56
+ "@camstack/addon-benchmark": "1.2.295",
57
57
  "@camstack/addon-decoder-nodeav": "1.2.144",
58
58
  "@camstack/addon-notifiers": "1.2.623",
59
59
  "@camstack/addon-pipeline": "1.2.634",
60
60
  "@camstack/addon-pipeline-orchestrator": "1.2.640",
61
- "@camstack/addon-post-analysis": "1.2.651",
61
+ "@camstack/addon-post-analysis": "1.2.652",
62
62
  "@camstack/develop": "0.1.39",
63
63
  "@camstack/sdk": "1.2.154",
64
- "@camstack/system": "1.2.521",
65
- "@camstack/types": "1.2.447",
66
- "@camstack/ui-library": "1.2.353",
64
+ "@camstack/system": "1.2.522",
65
+ "@camstack/types": "1.2.448",
66
+ "@camstack/ui-library": "1.2.354",
67
67
  "@fastify/compress": "^9.0.0",
68
68
  "@fastify/cookie": "^11.0.2",
69
69
  "@fastify/cors": "^11.2.0",