@camstack/agent 1.1.32 → 1.1.35
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/chunk-L34OOQI3.mjs +998 -0
- package/dist/chunk-L34OOQI3.mjs.map +1 -0
- package/dist/{chunk-CIITICY3.mjs → chunk-MMKRM4RT.mjs} +167 -36
- package/dist/chunk-MMKRM4RT.mjs.map +1 -0
- package/dist/cli.js +1173 -85
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +1123 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/starter.d.ts +2 -0
- package/dist/starter.js +1036 -0
- package/dist/starter.js.map +1 -0
- package/dist/starter.mjs +57 -0
- package/dist/starter.mjs.map +1 -0
- package/package.json +8 -3
- package/dist/chunk-CIITICY3.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +30,965 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// ../node-root/dist/index.js
|
|
34
|
+
var require_dist = __commonJS({
|
|
35
|
+
"../node-root/dist/index.js"(exports2, module2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
var __create2 = Object.create;
|
|
38
|
+
var __defProp2 = Object.defineProperty;
|
|
39
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
40
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
41
|
+
var __getProtoOf2 = Object.getPrototypeOf;
|
|
42
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
43
|
+
var __export2 = (target, all) => {
|
|
44
|
+
for (var name in all)
|
|
45
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
46
|
+
};
|
|
47
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
48
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
49
|
+
for (let key of __getOwnPropNames2(from))
|
|
50
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
51
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
52
|
+
}
|
|
53
|
+
return to;
|
|
54
|
+
};
|
|
55
|
+
var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
|
|
56
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
57
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
58
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
59
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
60
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
|
|
61
|
+
mod
|
|
62
|
+
));
|
|
63
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
64
|
+
var src_exports2 = {};
|
|
65
|
+
__export2(src_exports2, {
|
|
66
|
+
AGENT_ROOT_SPEC: () => AGENT_ROOT_SPEC2,
|
|
67
|
+
HOST_EXTERNAL_SPECIFIERS: () => HOST_EXTERNAL_SPECIFIERS,
|
|
68
|
+
HUB_ROOT_SPEC: () => HUB_ROOT_SPEC,
|
|
69
|
+
RESTART_INTENT_FILE: () => RESTART_INTENT_FILE,
|
|
70
|
+
RootUpdateService: () => RootUpdateService2,
|
|
71
|
+
SERVER_ROOT_DIRNAME: () => SERVER_ROOT_DIRNAME,
|
|
72
|
+
SERVER_ROOT_STATE_FILE: () => SERVER_ROOT_STATE_FILE,
|
|
73
|
+
clearRestartIntentMarker: () => clearRestartIntentMarker,
|
|
74
|
+
compareSemver: () => compareSemver,
|
|
75
|
+
detectWorkspaceRoot: () => detectWorkspaceRoot,
|
|
76
|
+
emptyServerRootState: () => emptyServerRootState,
|
|
77
|
+
isHostExternal: () => isHostExternal,
|
|
78
|
+
isServerRootState: () => isServerRootState,
|
|
79
|
+
minNodeMajorOf: () => minNodeMajorOf,
|
|
80
|
+
planBoot: () => planBoot,
|
|
81
|
+
readRestartIntentMarker: () => readRestartIntentMarker,
|
|
82
|
+
readServerRootState: () => readServerRootState,
|
|
83
|
+
registerActiveRootResolver: () => registerActiveRootResolver,
|
|
84
|
+
restartIntentMarkerPath: () => restartIntentMarkerPath,
|
|
85
|
+
rootEntryPath: () => rootEntryPath,
|
|
86
|
+
rootPackageDir: () => rootPackageDir,
|
|
87
|
+
runNodeStarter: () => runNodeStarter,
|
|
88
|
+
serverRootDir: () => serverRootDir2,
|
|
89
|
+
stateFilePath: () => stateFilePath,
|
|
90
|
+
validateVersionDir: () => validateVersionDir,
|
|
91
|
+
versionDir: () => versionDir,
|
|
92
|
+
versionsDir: () => versionsDir,
|
|
93
|
+
writeRestartIntentMarker: () => writeRestartIntentMarker2,
|
|
94
|
+
writeServerRootState: () => writeServerRootState
|
|
95
|
+
});
|
|
96
|
+
module2.exports = __toCommonJS2(src_exports2);
|
|
97
|
+
var HUB_ROOT_SPEC = {
|
|
98
|
+
packageName: "@camstack/server",
|
|
99
|
+
entryRelPath: ["dist", "launcher.js"]
|
|
100
|
+
};
|
|
101
|
+
var AGENT_ROOT_SPEC2 = {
|
|
102
|
+
packageName: "@camstack/agent",
|
|
103
|
+
entryRelPath: ["dist", "cli.js"]
|
|
104
|
+
};
|
|
105
|
+
var fs8 = __toESM2(require("fs"));
|
|
106
|
+
var path8 = __toESM2(require("path"));
|
|
107
|
+
var SERVER_ROOT_DIRNAME = "server-root";
|
|
108
|
+
var SERVER_ROOT_STATE_FILE = "state.json";
|
|
109
|
+
var RESTART_INTENT_FILE = ".restart-intent";
|
|
110
|
+
function emptyServerRootState() {
|
|
111
|
+
return {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
currentVersion: null,
|
|
114
|
+
previousVersion: null,
|
|
115
|
+
pendingBoot: null,
|
|
116
|
+
rolledBack: null
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function serverRootDir2(dataDir) {
|
|
120
|
+
return path8.join(dataDir, SERVER_ROOT_DIRNAME);
|
|
121
|
+
}
|
|
122
|
+
function versionsDir(rootDir) {
|
|
123
|
+
return path8.join(rootDir, "versions");
|
|
124
|
+
}
|
|
125
|
+
function versionDir(rootDir, version) {
|
|
126
|
+
return path8.join(versionsDir(rootDir), version);
|
|
127
|
+
}
|
|
128
|
+
function rootPackageDir(versionDirPath, spec) {
|
|
129
|
+
return path8.join(versionDirPath, "node_modules", ...spec.packageName.split("/"));
|
|
130
|
+
}
|
|
131
|
+
function rootEntryPath(versionDirPath, spec) {
|
|
132
|
+
return path8.join(rootPackageDir(versionDirPath, spec), ...spec.entryRelPath);
|
|
133
|
+
}
|
|
134
|
+
function stateFilePath(rootDir) {
|
|
135
|
+
return path8.join(rootDir, SERVER_ROOT_STATE_FILE);
|
|
136
|
+
}
|
|
137
|
+
function isNullableString(v) {
|
|
138
|
+
return v === null || typeof v === "string";
|
|
139
|
+
}
|
|
140
|
+
function isPendingBoot(v) {
|
|
141
|
+
if (typeof v !== "object" || v === null) return false;
|
|
142
|
+
const p = v;
|
|
143
|
+
return typeof p["version"] === "string" && isNullableString(p["fromVersion"]) && typeof p["requestedAtMs"] === "number" && typeof p["bootAttempts"] === "number";
|
|
144
|
+
}
|
|
145
|
+
function isRollbackInfo(v) {
|
|
146
|
+
if (typeof v !== "object" || v === null) return false;
|
|
147
|
+
const r = v;
|
|
148
|
+
return typeof r["fromVersion"] === "string" && isNullableString(r["toVersion"]) && typeof r["atMs"] === "number" && typeof r["reason"] === "string";
|
|
149
|
+
}
|
|
150
|
+
function isServerRootState(v) {
|
|
151
|
+
if (typeof v !== "object" || v === null) return false;
|
|
152
|
+
const s = v;
|
|
153
|
+
if (s["schemaVersion"] !== 1) return false;
|
|
154
|
+
if (!isNullableString(s["currentVersion"])) return false;
|
|
155
|
+
if (!isNullableString(s["previousVersion"])) return false;
|
|
156
|
+
if (s["pendingBoot"] !== null && !isPendingBoot(s["pendingBoot"])) return false;
|
|
157
|
+
if (s["rolledBack"] !== null && !isRollbackInfo(s["rolledBack"])) return false;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function readServerRootState(rootDir) {
|
|
161
|
+
try {
|
|
162
|
+
const raw = JSON.parse(fs8.readFileSync(stateFilePath(rootDir), "utf-8"));
|
|
163
|
+
return isServerRootState(raw) ? raw : null;
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function writeServerRootState(rootDir, state) {
|
|
169
|
+
fs8.mkdirSync(rootDir, { recursive: true });
|
|
170
|
+
const target = stateFilePath(rootDir);
|
|
171
|
+
const tmp = `${target}.tmp`;
|
|
172
|
+
fs8.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
173
|
+
fs8.renameSync(tmp, target);
|
|
174
|
+
}
|
|
175
|
+
function restartIntentMarkerPath(rootDir) {
|
|
176
|
+
return path8.join(rootDir, RESTART_INTENT_FILE);
|
|
177
|
+
}
|
|
178
|
+
function isRestartIntentMarker(v) {
|
|
179
|
+
if (typeof v !== "object" || v === null) return false;
|
|
180
|
+
const m = v;
|
|
181
|
+
return typeof m["requestedAtMs"] === "number" && typeof m["reason"] === "string";
|
|
182
|
+
}
|
|
183
|
+
function writeRestartIntentMarker2(rootDir, marker) {
|
|
184
|
+
fs8.mkdirSync(rootDir, { recursive: true });
|
|
185
|
+
const target = restartIntentMarkerPath(rootDir);
|
|
186
|
+
const tmp = `${target}.tmp`;
|
|
187
|
+
fs8.writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
|
|
188
|
+
fs8.renameSync(tmp, target);
|
|
189
|
+
}
|
|
190
|
+
function readRestartIntentMarker(rootDir) {
|
|
191
|
+
try {
|
|
192
|
+
const raw = JSON.parse(fs8.readFileSync(restartIntentMarkerPath(rootDir), "utf-8"));
|
|
193
|
+
return isRestartIntentMarker(raw) ? raw : null;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function clearRestartIntentMarker(rootDir) {
|
|
199
|
+
try {
|
|
200
|
+
fs8.rmSync(restartIntentMarkerPath(rootDir), { force: true });
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function minNodeMajorOf(enginesNode) {
|
|
205
|
+
const match = /(\d+)/.exec(enginesNode);
|
|
206
|
+
if (match === null) return null;
|
|
207
|
+
const major = Number.parseInt(match[1] ?? "", 10);
|
|
208
|
+
return Number.isNaN(major) ? null : major;
|
|
209
|
+
}
|
|
210
|
+
function validateVersionDir(rootDir, version, nodeMajor, spec) {
|
|
211
|
+
const vDir = versionDir(rootDir, version);
|
|
212
|
+
const entry = rootEntryPath(vDir, spec);
|
|
213
|
+
if (!fs8.existsSync(entry)) {
|
|
214
|
+
return `root entry missing: ${entry}`;
|
|
215
|
+
}
|
|
216
|
+
const pkgJsonPath = path8.join(rootPackageDir(vDir, spec), "package.json");
|
|
217
|
+
let pkg;
|
|
218
|
+
try {
|
|
219
|
+
const raw = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
|
|
220
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
221
|
+
return `package.json malformed: ${pkgJsonPath}`;
|
|
222
|
+
}
|
|
223
|
+
pkg = raw;
|
|
224
|
+
} catch {
|
|
225
|
+
return `package.json unreadable: ${pkgJsonPath}`;
|
|
226
|
+
}
|
|
227
|
+
if (pkg["name"] !== spec.packageName) {
|
|
228
|
+
return `package name mismatch: expected ${spec.packageName}, got ${String(pkg["name"])}`;
|
|
229
|
+
}
|
|
230
|
+
if (pkg["version"] !== version) {
|
|
231
|
+
return `package version mismatch: dir says ${version}, package.json says ${String(pkg["version"])}`;
|
|
232
|
+
}
|
|
233
|
+
const engines = pkg["engines"];
|
|
234
|
+
if (typeof engines === "object" && engines !== null) {
|
|
235
|
+
const enginesNode = engines["node"];
|
|
236
|
+
if (typeof enginesNode === "string") {
|
|
237
|
+
const minMajor = minNodeMajorOf(enginesNode);
|
|
238
|
+
if (minMajor !== null && nodeMajor < minMajor) {
|
|
239
|
+
return `engines.node "${enginesNode}" requires Node >= ${minMajor}, runtime is ${nodeMajor}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function planBoot(state, isValidVersion, now) {
|
|
246
|
+
if (state === null) {
|
|
247
|
+
return { kind: "baked", reason: "no server-root state", stateToWrite: null };
|
|
248
|
+
}
|
|
249
|
+
let next = state;
|
|
250
|
+
let changed = false;
|
|
251
|
+
if (next.pendingBoot !== null) {
|
|
252
|
+
const pending = next.pendingBoot;
|
|
253
|
+
if (pending.bootAttempts >= 1) {
|
|
254
|
+
next = {
|
|
255
|
+
...next,
|
|
256
|
+
pendingBoot: null,
|
|
257
|
+
rolledBack: {
|
|
258
|
+
fromVersion: pending.version,
|
|
259
|
+
toVersion: next.currentVersion,
|
|
260
|
+
atMs: now(),
|
|
261
|
+
reason: "probation boot did not reach ready \u2014 rolled back"
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
changed = true;
|
|
265
|
+
} else if (!isValidVersion(pending.version)) {
|
|
266
|
+
next = {
|
|
267
|
+
...next,
|
|
268
|
+
pendingBoot: null,
|
|
269
|
+
rolledBack: {
|
|
270
|
+
fromVersion: pending.version,
|
|
271
|
+
toVersion: next.currentVersion,
|
|
272
|
+
atMs: now(),
|
|
273
|
+
reason: "staged version failed validation"
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
changed = true;
|
|
277
|
+
} else {
|
|
278
|
+
return {
|
|
279
|
+
kind: "data-root",
|
|
280
|
+
version: pending.version,
|
|
281
|
+
probation: true,
|
|
282
|
+
stateToWrite: {
|
|
283
|
+
...next,
|
|
284
|
+
pendingBoot: { ...pending, bootAttempts: pending.bootAttempts + 1 }
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (next.currentVersion !== null) {
|
|
290
|
+
if (isValidVersion(next.currentVersion)) {
|
|
291
|
+
return {
|
|
292
|
+
kind: "data-root",
|
|
293
|
+
version: next.currentVersion,
|
|
294
|
+
probation: false,
|
|
295
|
+
stateToWrite: changed ? next : null
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const broken = next.currentVersion;
|
|
299
|
+
if (next.previousVersion !== null && isValidVersion(next.previousVersion)) {
|
|
300
|
+
const fallback = next.previousVersion;
|
|
301
|
+
return {
|
|
302
|
+
kind: "data-root",
|
|
303
|
+
version: fallback,
|
|
304
|
+
probation: false,
|
|
305
|
+
stateToWrite: {
|
|
306
|
+
...next,
|
|
307
|
+
currentVersion: fallback,
|
|
308
|
+
previousVersion: null,
|
|
309
|
+
rolledBack: {
|
|
310
|
+
fromVersion: broken,
|
|
311
|
+
toVersion: fallback,
|
|
312
|
+
atMs: now(),
|
|
313
|
+
reason: "active version failed validation"
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
kind: "baked",
|
|
320
|
+
reason: `active version ${broken} failed validation and no valid previous version exists`,
|
|
321
|
+
stateToWrite: {
|
|
322
|
+
...next,
|
|
323
|
+
currentVersion: null,
|
|
324
|
+
rolledBack: {
|
|
325
|
+
fromVersion: broken,
|
|
326
|
+
toVersion: null,
|
|
327
|
+
atMs: now(),
|
|
328
|
+
reason: "active version failed validation"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
kind: "baked",
|
|
335
|
+
reason: "no active data-root version",
|
|
336
|
+
stateToWrite: changed ? next : null
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function parse(version) {
|
|
340
|
+
const dashIdx = version.indexOf("-");
|
|
341
|
+
const base = dashIdx === -1 ? version : version.slice(0, dashIdx);
|
|
342
|
+
const prerelease = dashIdx === -1 ? null : version.slice(dashIdx + 1);
|
|
343
|
+
const nums = base.split(".").map((seg) => {
|
|
344
|
+
const n = Number.parseInt(seg, 10);
|
|
345
|
+
return Number.isNaN(n) ? 0 : n;
|
|
346
|
+
});
|
|
347
|
+
return { nums, prerelease };
|
|
348
|
+
}
|
|
349
|
+
function compareSemver(a, b) {
|
|
350
|
+
const pa = parse(a);
|
|
351
|
+
const pb = parse(b);
|
|
352
|
+
const len = Math.max(pa.nums.length, pb.nums.length);
|
|
353
|
+
for (let i = 0; i < len; i++) {
|
|
354
|
+
const na = pa.nums[i] ?? 0;
|
|
355
|
+
const nb = pb.nums[i] ?? 0;
|
|
356
|
+
if (na < nb) return -1;
|
|
357
|
+
if (na > nb) return 1;
|
|
358
|
+
}
|
|
359
|
+
if (pa.prerelease === null && pb.prerelease === null) return 0;
|
|
360
|
+
if (pa.prerelease === null) return 1;
|
|
361
|
+
if (pb.prerelease === null) return -1;
|
|
362
|
+
if (pa.prerelease < pb.prerelease) return -1;
|
|
363
|
+
if (pa.prerelease > pb.prerelease) return 1;
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
var fs22 = __toESM2(require("fs"));
|
|
367
|
+
var path22 = __toESM2(require("path"));
|
|
368
|
+
function detectWorkspaceRoot(fromDir) {
|
|
369
|
+
let dir = path22.resolve(fromDir);
|
|
370
|
+
for (; ; ) {
|
|
371
|
+
const pkgPath = path22.join(dir, "package.json");
|
|
372
|
+
try {
|
|
373
|
+
const raw = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
|
|
374
|
+
if (typeof raw === "object" && raw !== null && raw["workspaces"] !== void 0) {
|
|
375
|
+
return dir;
|
|
376
|
+
}
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
const parent = path22.dirname(dir);
|
|
380
|
+
if (parent === dir) return null;
|
|
381
|
+
dir = parent;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
var path32 = __toESM2(require("path"));
|
|
385
|
+
var import_node_url = require("url");
|
|
386
|
+
var HOST_EXTERNAL_SPECIFIERS = [
|
|
387
|
+
"@camstack/system",
|
|
388
|
+
"@camstack/shm-ring",
|
|
389
|
+
"@camstack/types",
|
|
390
|
+
"@camstack/sdk",
|
|
391
|
+
"zod",
|
|
392
|
+
"@trpc/server",
|
|
393
|
+
"@trpc/client",
|
|
394
|
+
"sharp",
|
|
395
|
+
"node-av"
|
|
396
|
+
];
|
|
397
|
+
function isHostExternal(specifier) {
|
|
398
|
+
return HOST_EXTERNAL_SPECIFIERS.some(
|
|
399
|
+
(name) => specifier === name || specifier.startsWith(`${name}/`)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
function registerActiveRootResolver(activeRootDir) {
|
|
403
|
+
const moduleApi = require("module");
|
|
404
|
+
const registerHooks = moduleApi["registerHooks"];
|
|
405
|
+
if (typeof registerHooks !== "function") {
|
|
406
|
+
console.warn(
|
|
407
|
+
"[starter] module.registerHooks unavailable (Node < 22.15) \u2014 host-external redirect skipped"
|
|
408
|
+
);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const anchorURL = (0, import_node_url.pathToFileURL)(
|
|
412
|
+
path32.join(activeRootDir, "node_modules", "__camstack_starter_anchor__.js")
|
|
413
|
+
).href;
|
|
414
|
+
const hooks = {
|
|
415
|
+
resolve: (specifier, context, nextResolve) => {
|
|
416
|
+
if (isHostExternal(specifier)) {
|
|
417
|
+
try {
|
|
418
|
+
return nextResolve(specifier, { ...context, parentURL: anchorURL });
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return nextResolve(specifier, context);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
registerHooks(hooks);
|
|
426
|
+
}
|
|
427
|
+
function runNodeStarter(options) {
|
|
428
|
+
const env = options.env ?? process.env;
|
|
429
|
+
const now = options.now ?? Date.now;
|
|
430
|
+
const registerResolver = options.registerResolver ?? registerActiveRootResolver;
|
|
431
|
+
const disabled = env[options.envNames.killSwitch] === "off";
|
|
432
|
+
const workspaceRoot = detectWorkspaceRoot(options.starterDir);
|
|
433
|
+
if (workspaceRoot !== null) {
|
|
434
|
+
console.log(
|
|
435
|
+
`[starter] workspace checkout detected at ${workspaceRoot} \u2014 using plain resolution`
|
|
436
|
+
);
|
|
437
|
+
env[options.envNames.bootMode] = "workspace";
|
|
438
|
+
options.loadEntry(options.seedEntry);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (disabled) {
|
|
442
|
+
console.log(`[starter] ${options.envNames.killSwitch}=off \u2014 booting the baked seed closure`);
|
|
443
|
+
env[options.envNames.bootMode] = "baked";
|
|
444
|
+
options.loadEntry(options.seedEntry);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const rootDir = serverRootDir2(options.dataDir);
|
|
448
|
+
const state = readServerRootState(rootDir);
|
|
449
|
+
const nodeMajor = options.nodeMajor ?? Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
450
|
+
const isValidVersion = (version) => {
|
|
451
|
+
const reason = validateVersionDir(rootDir, version, nodeMajor, options.spec);
|
|
452
|
+
if (reason !== null) console.warn(`[starter] version ${version} invalid: ${reason}`);
|
|
453
|
+
return reason === null;
|
|
454
|
+
};
|
|
455
|
+
const plan = planBoot(state, isValidVersion, now);
|
|
456
|
+
if (plan.stateToWrite !== null) {
|
|
457
|
+
try {
|
|
458
|
+
writeServerRootState(rootDir, plan.stateToWrite);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error("[starter] FAILED to persist server-root state \u2014 booting baked seed:", err);
|
|
461
|
+
env[options.envNames.bootMode] = "baked";
|
|
462
|
+
options.loadEntry(options.seedEntry);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (plan.stateToWrite.rolledBack !== null && (state?.rolledBack ?? null) !== plan.stateToWrite.rolledBack) {
|
|
466
|
+
const rb = plan.stateToWrite.rolledBack;
|
|
467
|
+
console.warn(
|
|
468
|
+
`[starter] ROLLED BACK ${rb.fromVersion} -> ${rb.toVersion ?? "baked seed"}: ${rb.reason}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (plan.kind === "data-root") {
|
|
473
|
+
const activeRootDir = versionDir(rootDir, plan.version);
|
|
474
|
+
const entry = rootEntryPath(activeRootDir, options.spec);
|
|
475
|
+
console.log(
|
|
476
|
+
`[starter] booting ${options.spec.packageName}@${plan.version} from ${activeRootDir}` + (plan.probation ? " (probation boot \u2014 health-check armed)" : "")
|
|
477
|
+
);
|
|
478
|
+
env[options.envNames.bootMode] = "data-root";
|
|
479
|
+
env[options.envNames.activeRoot] = activeRootDir;
|
|
480
|
+
env[options.envNames.activeVersion] = plan.version;
|
|
481
|
+
registerResolver(activeRootDir);
|
|
482
|
+
options.loadEntry(entry);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
console.log(`[starter] booting baked seed closure (${plan.reason})`);
|
|
486
|
+
env[options.envNames.bootMode] = "baked";
|
|
487
|
+
options.loadEntry(options.seedEntry);
|
|
488
|
+
}
|
|
489
|
+
var import_node_child_process = require("child_process");
|
|
490
|
+
var fs32 = __toESM2(require("fs"));
|
|
491
|
+
var path42 = __toESM2(require("path"));
|
|
492
|
+
var import_node_util = require("util");
|
|
493
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
494
|
+
function buildNpmRegistryArgs(registry) {
|
|
495
|
+
if (registry === void 0 || registry.length === 0) return [];
|
|
496
|
+
return ["--registry", registry, `--@camstack:registry=${registry}`];
|
|
497
|
+
}
|
|
498
|
+
var NPM_VIEW_TIMEOUT_MS = 2e4;
|
|
499
|
+
var NPM_INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
500
|
+
var RESTART_REASON_PREFIX = "server-update";
|
|
501
|
+
function readPackageVersion(pkgJsonPath) {
|
|
502
|
+
try {
|
|
503
|
+
const raw = JSON.parse(fs32.readFileSync(pkgJsonPath, "utf-8"));
|
|
504
|
+
if (typeof raw === "object" && raw !== null) {
|
|
505
|
+
const version = raw["version"];
|
|
506
|
+
if (typeof version === "string") return version;
|
|
507
|
+
}
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
var RootUpdateService2 = class _RootUpdateService {
|
|
513
|
+
spec;
|
|
514
|
+
envNames;
|
|
515
|
+
logger;
|
|
516
|
+
restartServerFn;
|
|
517
|
+
dataDir;
|
|
518
|
+
execNpm;
|
|
519
|
+
env;
|
|
520
|
+
now;
|
|
521
|
+
runningPackageJsonPath;
|
|
522
|
+
workspaceProbeDir;
|
|
523
|
+
checkCache = null;
|
|
524
|
+
inFlight = "idle";
|
|
525
|
+
constructor(options) {
|
|
526
|
+
this.spec = options.spec;
|
|
527
|
+
this.envNames = options.envNames;
|
|
528
|
+
this.logger = options.logger;
|
|
529
|
+
this.restartServerFn = options.restartServer;
|
|
530
|
+
this.dataDir = path42.resolve(options.dataDir);
|
|
531
|
+
this.execNpm = options.execNpm ?? (async (args, opts) => {
|
|
532
|
+
const { stdout } = await execFileAsync("npm", [...args], {
|
|
533
|
+
cwd: opts.cwd,
|
|
534
|
+
timeout: opts.timeoutMs
|
|
535
|
+
});
|
|
536
|
+
return { stdout };
|
|
537
|
+
});
|
|
538
|
+
this.env = options.env ?? process.env;
|
|
539
|
+
this.now = options.now ?? Date.now;
|
|
540
|
+
this.runningPackageJsonPath = options.runningPackageJsonPath;
|
|
541
|
+
this.workspaceProbeDir = options.workspaceProbeDir;
|
|
542
|
+
}
|
|
543
|
+
// ── Status ────────────────────────────────────────────────────────────
|
|
544
|
+
resolveBootMode() {
|
|
545
|
+
const raw = this.env[this.envNames.bootMode];
|
|
546
|
+
if (raw === "workspace" || raw === "baked" || raw === "data-root") return raw;
|
|
547
|
+
return detectWorkspaceRoot(this.workspaceProbeDir) !== null ? "workspace" : "baked";
|
|
548
|
+
}
|
|
549
|
+
runningVersion() {
|
|
550
|
+
return readPackageVersion(this.runningPackageJsonPath);
|
|
551
|
+
}
|
|
552
|
+
/** The running root package's identity — feeds registerNode / topology rows. */
|
|
553
|
+
getRunningRootPackage() {
|
|
554
|
+
return { name: this.spec.packageName, version: this.runningVersion() };
|
|
555
|
+
}
|
|
556
|
+
seedVersion() {
|
|
557
|
+
const seedDir = this.env[this.envNames.seedDir];
|
|
558
|
+
if (seedDir === void 0 || seedDir.length === 0) return null;
|
|
559
|
+
return readPackageVersion(path42.join(seedDir, "package.json"));
|
|
560
|
+
}
|
|
561
|
+
rootDir() {
|
|
562
|
+
return serverRootDir2(this.dataDir);
|
|
563
|
+
}
|
|
564
|
+
readState() {
|
|
565
|
+
return readServerRootState(this.rootDir()) ?? emptyServerRootState();
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Like `readState`, but distinguishes "state.json missing" (normal on a
|
|
569
|
+
* fresh node) from "state.json present but corrupt" — in the corrupt case
|
|
570
|
+
* the starter booted the baked seed while installed data-dir versions are
|
|
571
|
+
* silently ignored, which the status surface must make visible.
|
|
572
|
+
*/
|
|
573
|
+
readStateInfo() {
|
|
574
|
+
const rootDir = this.rootDir();
|
|
575
|
+
const raw = readServerRootState(rootDir);
|
|
576
|
+
if (raw !== null) return { state: raw, corrupt: false };
|
|
577
|
+
return { state: emptyServerRootState(), corrupt: fs32.existsSync(stateFilePath(rootDir)) };
|
|
578
|
+
}
|
|
579
|
+
updateState(state) {
|
|
580
|
+
if (this.inFlight === "checking") return "checking";
|
|
581
|
+
if (this.inFlight === "staging") return "staging";
|
|
582
|
+
if (state.pendingBoot !== null) {
|
|
583
|
+
const activeVersion = this.env[this.envNames.activeVersion] ?? null;
|
|
584
|
+
if (activeVersion !== null && activeVersion === state.pendingBoot.version) {
|
|
585
|
+
return "awaiting-confirmation";
|
|
586
|
+
}
|
|
587
|
+
return "pending-restart";
|
|
588
|
+
}
|
|
589
|
+
return "idle";
|
|
590
|
+
}
|
|
591
|
+
async getServerPackageStatus() {
|
|
592
|
+
const { state, corrupt } = this.readStateInfo();
|
|
593
|
+
const runningVersion = this.runningVersion();
|
|
594
|
+
const latestVersion = this.checkCache?.latestVersion ?? null;
|
|
595
|
+
const updateAvailable = latestVersion !== null && runningVersion !== null && compareSemver(latestVersion, runningVersion) > 0;
|
|
596
|
+
return {
|
|
597
|
+
packageName: this.spec.packageName,
|
|
598
|
+
runningVersion,
|
|
599
|
+
nodeRuntimeVersion: process.versions.node,
|
|
600
|
+
activeVersion: this.env[this.envNames.activeVersion] ?? state.currentVersion,
|
|
601
|
+
previousVersion: state.previousVersion,
|
|
602
|
+
seedVersion: this.seedVersion(),
|
|
603
|
+
latestVersion,
|
|
604
|
+
updateAvailable,
|
|
605
|
+
bootMode: this.resolveBootMode(),
|
|
606
|
+
updateState: this.updateState(state),
|
|
607
|
+
pendingVersion: state.pendingBoot?.version ?? null,
|
|
608
|
+
rolledBack: state.rolledBack,
|
|
609
|
+
stateFileCorrupt: corrupt,
|
|
610
|
+
lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
// ── Check ─────────────────────────────────────────────────────────────
|
|
614
|
+
async checkServerUpdate() {
|
|
615
|
+
const runningVersion = this.runningVersion();
|
|
616
|
+
if (this.inFlight !== "idle") {
|
|
617
|
+
return {
|
|
618
|
+
packageName: this.spec.packageName,
|
|
619
|
+
runningVersion,
|
|
620
|
+
latestVersion: this.checkCache?.latestVersion ?? null,
|
|
621
|
+
updateAvailable: false,
|
|
622
|
+
checkedAtMs: this.checkCache?.checkedAtMs ?? this.now(),
|
|
623
|
+
error: `busy: ${this.inFlight}`
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
this.inFlight = "checking";
|
|
627
|
+
try {
|
|
628
|
+
const registry = this.env["CAMSTACK_NPM_REGISTRY"];
|
|
629
|
+
const args = [
|
|
630
|
+
"view",
|
|
631
|
+
`${this.spec.packageName}@latest`,
|
|
632
|
+
"version",
|
|
633
|
+
...buildNpmRegistryArgs(registry)
|
|
634
|
+
];
|
|
635
|
+
const { stdout } = await this.execNpm(args, { timeoutMs: NPM_VIEW_TIMEOUT_MS });
|
|
636
|
+
const latestVersion = stdout.trim().length > 0 ? stdout.trim() : null;
|
|
637
|
+
this.checkCache = { latestVersion, checkedAtMs: this.now(), error: null };
|
|
638
|
+
const updateAvailable = latestVersion !== null && runningVersion !== null && compareSemver(latestVersion, runningVersion) > 0;
|
|
639
|
+
return {
|
|
640
|
+
packageName: this.spec.packageName,
|
|
641
|
+
runningVersion,
|
|
642
|
+
latestVersion,
|
|
643
|
+
updateAvailable,
|
|
644
|
+
checkedAtMs: this.checkCache.checkedAtMs,
|
|
645
|
+
error: null
|
|
646
|
+
};
|
|
647
|
+
} catch (err) {
|
|
648
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
649
|
+
this.checkCache = {
|
|
650
|
+
latestVersion: this.checkCache?.latestVersion ?? null,
|
|
651
|
+
checkedAtMs: this.now(),
|
|
652
|
+
error: message
|
|
653
|
+
};
|
|
654
|
+
this.logger.warn("root package update check failed", { meta: { error: message } });
|
|
655
|
+
return {
|
|
656
|
+
packageName: this.spec.packageName,
|
|
657
|
+
runningVersion,
|
|
658
|
+
latestVersion: this.checkCache.latestVersion,
|
|
659
|
+
updateAvailable: false,
|
|
660
|
+
checkedAtMs: this.checkCache.checkedAtMs,
|
|
661
|
+
error: message
|
|
662
|
+
};
|
|
663
|
+
} finally {
|
|
664
|
+
this.inFlight = "idle";
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// ── Apply ─────────────────────────────────────────────────────────────
|
|
668
|
+
async applyServerUpdate(input) {
|
|
669
|
+
const bootMode = this.resolveBootMode();
|
|
670
|
+
if (bootMode === "workspace") {
|
|
671
|
+
return {
|
|
672
|
+
accepted: false,
|
|
673
|
+
targetVersion: input.version ?? null,
|
|
674
|
+
restarting: false,
|
|
675
|
+
message: "Refused: running from a workspace checkout \u2014 the dev loop resolves code from the workspace, a data-root update would never be loaded."
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (this.inFlight !== "idle") {
|
|
679
|
+
return {
|
|
680
|
+
accepted: false,
|
|
681
|
+
targetVersion: input.version ?? null,
|
|
682
|
+
restarting: false,
|
|
683
|
+
message: `Refused: another operation is in flight (${this.inFlight}).`
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
const stateBefore = this.readState();
|
|
687
|
+
if (stateBefore.pendingBoot !== null) {
|
|
688
|
+
return {
|
|
689
|
+
accepted: false,
|
|
690
|
+
targetVersion: input.version ?? null,
|
|
691
|
+
restarting: false,
|
|
692
|
+
message: `Refused: version ${stateBefore.pendingBoot.version} is already staged and awaiting restart.`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
let target = input.version ?? null;
|
|
696
|
+
if (target === null) {
|
|
697
|
+
const check = await this.checkServerUpdate();
|
|
698
|
+
if (check.error !== null || check.latestVersion === null) {
|
|
699
|
+
return {
|
|
700
|
+
accepted: false,
|
|
701
|
+
targetVersion: null,
|
|
702
|
+
restarting: false,
|
|
703
|
+
message: `Refused: could not resolve the latest version (${check.error ?? "no version returned"}).`
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
target = check.latestVersion;
|
|
707
|
+
}
|
|
708
|
+
const runningVersion = this.runningVersion();
|
|
709
|
+
if (target === runningVersion) {
|
|
710
|
+
return {
|
|
711
|
+
accepted: false,
|
|
712
|
+
targetVersion: target,
|
|
713
|
+
restarting: false,
|
|
714
|
+
message: `Refused: ${this.spec.packageName}@${target} is already running.`
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
this.inFlight = "staging";
|
|
718
|
+
try {
|
|
719
|
+
await this.stageAndActivate(target);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
722
|
+
this.logger.error("root package update staging failed", {
|
|
723
|
+
meta: { targetVersion: target, error: message }
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
accepted: false,
|
|
727
|
+
targetVersion: target,
|
|
728
|
+
restarting: false,
|
|
729
|
+
message: `Staging failed: ${message}`
|
|
730
|
+
};
|
|
731
|
+
} finally {
|
|
732
|
+
this.inFlight = "idle";
|
|
733
|
+
}
|
|
734
|
+
const state = this.readState();
|
|
735
|
+
writeServerRootState(this.rootDir(), {
|
|
736
|
+
...state,
|
|
737
|
+
pendingBoot: {
|
|
738
|
+
version: target,
|
|
739
|
+
fromVersion: state.currentVersion,
|
|
740
|
+
requestedAtMs: this.now(),
|
|
741
|
+
bootAttempts: 0
|
|
742
|
+
},
|
|
743
|
+
rolledBack: null
|
|
744
|
+
});
|
|
745
|
+
this.logger.info("root package update staged \u2014 restarting to apply", {
|
|
746
|
+
meta: { targetVersion: target, fromVersion: state.currentVersion }
|
|
747
|
+
});
|
|
748
|
+
this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}`);
|
|
749
|
+
return {
|
|
750
|
+
accepted: true,
|
|
751
|
+
targetVersion: target,
|
|
752
|
+
restarting: true,
|
|
753
|
+
message: `Staged ${this.spec.packageName}@${target} \u2014 restarting to apply (probation boot with auto-rollback).`
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* npm-install the target closure into a SAME-FS staging dir inside
|
|
758
|
+
* `versions/`, validate it, then atomically rename it into place.
|
|
759
|
+
*/
|
|
760
|
+
async stageAndActivate(target) {
|
|
761
|
+
const rootDir = this.rootDir();
|
|
762
|
+
const vDir = versionsDir(rootDir);
|
|
763
|
+
fs32.mkdirSync(vDir, { recursive: true });
|
|
764
|
+
const stagingDir = path42.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`);
|
|
765
|
+
fs32.mkdirSync(stagingDir, { recursive: true });
|
|
766
|
+
try {
|
|
767
|
+
fs32.writeFileSync(
|
|
768
|
+
path42.join(stagingDir, "package.json"),
|
|
769
|
+
JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
|
|
770
|
+
"utf-8"
|
|
771
|
+
);
|
|
772
|
+
const registry = this.env["CAMSTACK_NPM_REGISTRY"];
|
|
773
|
+
await this.execNpm(
|
|
774
|
+
[
|
|
775
|
+
"install",
|
|
776
|
+
"--omit=dev",
|
|
777
|
+
"--no-audit",
|
|
778
|
+
"--no-fund",
|
|
779
|
+
"--loglevel=error",
|
|
780
|
+
`${this.spec.packageName}@${target}`,
|
|
781
|
+
...buildNpmRegistryArgs(registry)
|
|
782
|
+
],
|
|
783
|
+
{ cwd: stagingDir, timeoutMs: NPM_INSTALL_TIMEOUT_MS }
|
|
784
|
+
);
|
|
785
|
+
const entry = rootEntryPath(stagingDir, this.spec);
|
|
786
|
+
if (!fs32.existsSync(entry)) {
|
|
787
|
+
throw new Error(`staged closure is missing the root entry (${entry})`);
|
|
788
|
+
}
|
|
789
|
+
const stagedVersion = readPackageVersion(
|
|
790
|
+
path42.join(rootPackageDir(stagingDir, this.spec), "package.json")
|
|
791
|
+
);
|
|
792
|
+
if (stagedVersion !== target) {
|
|
793
|
+
throw new Error(
|
|
794
|
+
`staged closure version mismatch: expected ${target}, got ${stagedVersion ?? "unknown"}`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
const dest = versionDir(rootDir, target);
|
|
798
|
+
if (fs32.existsSync(dest)) {
|
|
799
|
+
const aside = `${dest}.evicted-${this.now()}`;
|
|
800
|
+
await fs32.promises.rename(dest, aside);
|
|
801
|
+
await fs32.promises.rm(aside, { recursive: true, force: true }).catch(() => void 0);
|
|
802
|
+
}
|
|
803
|
+
await fs32.promises.rename(stagingDir, dest);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
await fs32.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
|
|
806
|
+
throw err;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
// ── Rollback ──────────────────────────────────────────────────────────
|
|
810
|
+
async rollbackServerUpdate() {
|
|
811
|
+
if (this.inFlight !== "idle") {
|
|
812
|
+
return {
|
|
813
|
+
accepted: false,
|
|
814
|
+
targetVersion: null,
|
|
815
|
+
restarting: false,
|
|
816
|
+
message: `Refused: another operation is in flight (${this.inFlight}).`
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
const state = this.readState();
|
|
820
|
+
if (state.pendingBoot !== null) {
|
|
821
|
+
return {
|
|
822
|
+
accepted: false,
|
|
823
|
+
targetVersion: null,
|
|
824
|
+
restarting: false,
|
|
825
|
+
message: `Refused: version ${state.pendingBoot.version} is already staged and awaiting restart.`
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
if (state.currentVersion === null || state.previousVersion === null) {
|
|
829
|
+
return {
|
|
830
|
+
accepted: false,
|
|
831
|
+
targetVersion: state.previousVersion,
|
|
832
|
+
restarting: false,
|
|
833
|
+
message: "Refused: no previous version available to roll back to."
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
837
|
+
const invalidReason = validateVersionDir(
|
|
838
|
+
this.rootDir(),
|
|
839
|
+
state.previousVersion,
|
|
840
|
+
nodeMajor,
|
|
841
|
+
this.spec
|
|
842
|
+
);
|
|
843
|
+
if (invalidReason !== null) {
|
|
844
|
+
return {
|
|
845
|
+
accepted: false,
|
|
846
|
+
targetVersion: state.previousVersion,
|
|
847
|
+
restarting: false,
|
|
848
|
+
message: `Refused: previous version ${state.previousVersion} is not loadable (${invalidReason}).`
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
writeServerRootState(this.rootDir(), {
|
|
852
|
+
...state,
|
|
853
|
+
pendingBoot: {
|
|
854
|
+
version: state.previousVersion,
|
|
855
|
+
fromVersion: state.currentVersion,
|
|
856
|
+
requestedAtMs: this.now(),
|
|
857
|
+
bootAttempts: 0
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
this.logger.info("root package rollback requested \u2014 restarting", {
|
|
861
|
+
meta: { fromVersion: state.currentVersion, toVersion: state.previousVersion }
|
|
862
|
+
});
|
|
863
|
+
this.restartServerFn(
|
|
864
|
+
`${RESTART_REASON_PREFIX}: rollback to ${this.spec.packageName}@${state.previousVersion}`
|
|
865
|
+
);
|
|
866
|
+
return {
|
|
867
|
+
accepted: true,
|
|
868
|
+
targetVersion: state.previousVersion,
|
|
869
|
+
restarting: true,
|
|
870
|
+
message: `Rolling back to ${this.spec.packageName}@${state.previousVersion} \u2014 restarting.`
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
// ── Plain restart ─────────────────────────────────────────────────────
|
|
874
|
+
/**
|
|
875
|
+
* Plain process restart — no version change. Backs the `restartServer` cap
|
|
876
|
+
* method. Guards mirror apply/rollback: refuse while a stage is in flight
|
|
877
|
+
* (a concurrent npm install must not be interrupted) or while a version is
|
|
878
|
+
* already staged awaiting restart (a naive bounce would boot the OLD version
|
|
879
|
+
* and orphan the pending one — the operator should apply/rollback instead).
|
|
880
|
+
*/
|
|
881
|
+
restartNode() {
|
|
882
|
+
const runningVersion = this.runningVersion();
|
|
883
|
+
if (this.inFlight !== "idle") {
|
|
884
|
+
return {
|
|
885
|
+
accepted: false,
|
|
886
|
+
targetVersion: runningVersion,
|
|
887
|
+
restarting: false,
|
|
888
|
+
message: `Refused: another operation is in flight (${this.inFlight}).`
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
const state = this.readState();
|
|
892
|
+
if (state.pendingBoot !== null) {
|
|
893
|
+
return {
|
|
894
|
+
accepted: false,
|
|
895
|
+
targetVersion: runningVersion,
|
|
896
|
+
restarting: false,
|
|
897
|
+
message: `Refused: version ${state.pendingBoot.version} is staged and awaiting restart \u2014 apply or roll it back instead of a plain restart.`
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
this.logger.info("plain node restart requested", { meta: { runningVersion } });
|
|
901
|
+
this.restartServerFn(`${RESTART_REASON_PREFIX}: manual restart`);
|
|
902
|
+
return {
|
|
903
|
+
accepted: true,
|
|
904
|
+
targetVersion: runningVersion,
|
|
905
|
+
restarting: true,
|
|
906
|
+
message: "Restarting the node \u2014 the supervisor relaunches it on the same version."
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
// ── Boot health confirmation ──────────────────────────────────────────
|
|
910
|
+
/**
|
|
911
|
+
* Called by the post-boot path once the node is READY (hub: post-boot
|
|
912
|
+
* pipeline; agent: first acked `$hub.registerNode`). When THIS process is
|
|
913
|
+
* the probation boot of a pending version, promote it: `currentVersion` ←
|
|
914
|
+
* pending, `previousVersion` ← the version it replaced (N-1 retained for
|
|
915
|
+
* rollback), clear `pendingBoot` + `rolledBack`, and prune every version
|
|
916
|
+
* dir beyond {current, previous}. No-op otherwise.
|
|
917
|
+
*/
|
|
918
|
+
confirmBootHealthy() {
|
|
919
|
+
const state = readServerRootState(this.rootDir());
|
|
920
|
+
if (state === null || state.pendingBoot === null) return { promoted: null };
|
|
921
|
+
const activeVersion = this.env[this.envNames.activeVersion] ?? null;
|
|
922
|
+
if (activeVersion !== state.pendingBoot.version) {
|
|
923
|
+
return { promoted: null };
|
|
924
|
+
}
|
|
925
|
+
const promoted = {
|
|
926
|
+
...state,
|
|
927
|
+
currentVersion: state.pendingBoot.version,
|
|
928
|
+
previousVersion: state.pendingBoot.fromVersion,
|
|
929
|
+
pendingBoot: null,
|
|
930
|
+
rolledBack: null
|
|
931
|
+
};
|
|
932
|
+
writeServerRootState(this.rootDir(), promoted);
|
|
933
|
+
this.pruneVersions(
|
|
934
|
+
[promoted.currentVersion, promoted.previousVersion].filter(
|
|
935
|
+
(v) => typeof v === "string"
|
|
936
|
+
)
|
|
937
|
+
);
|
|
938
|
+
this.logger.info("root package update confirmed healthy", {
|
|
939
|
+
meta: {
|
|
940
|
+
version: promoted.currentVersion,
|
|
941
|
+
previousVersion: promoted.previousVersion
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return { promoted: promoted.currentVersion };
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Remove version dirs under `versions/` not in `keep`.
|
|
948
|
+
*
|
|
949
|
+
* Transient dot-entries (`.staging-*` / `.evicted-*`) are NOT pruned unless
|
|
950
|
+
* older than {@link RootUpdateService.STALE_TRANSIENT_MS}: the API starts
|
|
951
|
+
* serving BEFORE the post-boot `confirmBootHealthy()` runs, so a concurrent
|
|
952
|
+
* `applyServerUpdate` can be mid-npm-install into a fresh `.staging-*` dir
|
|
953
|
+
* when this prune fires — deleting it would corrupt that install. Fresh
|
|
954
|
+
* transient dirs are owned by their in-flight operation (which cleans up on
|
|
955
|
+
* failure); the staleness sweep only collects dirs orphaned by a crash.
|
|
956
|
+
*/
|
|
957
|
+
pruneVersions(keep) {
|
|
958
|
+
const vDir = versionsDir(this.rootDir());
|
|
959
|
+
let entries;
|
|
960
|
+
try {
|
|
961
|
+
entries = fs32.readdirSync(vDir);
|
|
962
|
+
} catch {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
for (const entry of entries) {
|
|
966
|
+
if (keep.includes(entry)) continue;
|
|
967
|
+
const full = path42.join(vDir, entry);
|
|
968
|
+
if (entry.startsWith(".")) {
|
|
969
|
+
try {
|
|
970
|
+
const ageMs = this.now() - fs32.statSync(full).mtimeMs;
|
|
971
|
+
if (ageMs < _RootUpdateService.STALE_TRANSIENT_MS) continue;
|
|
972
|
+
} catch {
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
fs32.rmSync(full, { recursive: true, force: true });
|
|
978
|
+
this.logger.debug("pruned server-root version dir", { meta: { entry } });
|
|
979
|
+
} catch (err) {
|
|
980
|
+
this.logger.warn("failed to prune server-root version dir", {
|
|
981
|
+
meta: { entry, error: err instanceof Error ? err.message : String(err) }
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */
|
|
987
|
+
static STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
|
|
30
992
|
// src/index.ts
|
|
31
993
|
var src_exports = {};
|
|
32
994
|
__export(src_exports, {
|
|
@@ -231,8 +1193,8 @@ function getLocalIps() {
|
|
|
231
1193
|
}
|
|
232
1194
|
async function withTimeout(p, ms, fallback) {
|
|
233
1195
|
let timer;
|
|
234
|
-
const timeout = new Promise((
|
|
235
|
-
timer = setTimeout(() =>
|
|
1196
|
+
const timeout = new Promise((resolve6) => {
|
|
1197
|
+
timer = setTimeout(() => resolve6(fallback), ms);
|
|
236
1198
|
});
|
|
237
1199
|
try {
|
|
238
1200
|
return await Promise.race([p, timeout]);
|
|
@@ -652,8 +1614,8 @@ function createAgentService(deps) {
|
|
|
652
1614
|
}
|
|
653
1615
|
|
|
654
1616
|
// src/agent-bootstrap.ts
|
|
655
|
-
var
|
|
656
|
-
var
|
|
1617
|
+
var fs7 = __toESM(require("fs"));
|
|
1618
|
+
var path7 = __toESM(require("path"));
|
|
657
1619
|
|
|
658
1620
|
// src/agent-http.ts
|
|
659
1621
|
var fs4 = __toESM(require("fs"));
|
|
@@ -969,15 +1931,98 @@ var import_system3 = require("@camstack/system");
|
|
|
969
1931
|
var import_types2 = require("@camstack/types");
|
|
970
1932
|
var import_types3 = require("@camstack/types");
|
|
971
1933
|
|
|
972
|
-
// src/agent-
|
|
1934
|
+
// src/agent-update-service.ts
|
|
973
1935
|
var fs5 = __toESM(require("fs"));
|
|
974
1936
|
var path5 = __toESM(require("path"));
|
|
1937
|
+
var import_node_root = __toESM(require_dist());
|
|
1938
|
+
var AGENT_RUNTIME_ADDON_ID = "agent-runtime";
|
|
1939
|
+
var AGENT_RESTART_GRACE_MS = 2e3;
|
|
1940
|
+
var AGENT_LOCAL_CONFIRM_GRACE_MS = 9e4;
|
|
1941
|
+
function armLocalConfirmFallback(onGrace, graceMs = AGENT_LOCAL_CONFIRM_GRACE_MS) {
|
|
1942
|
+
const timer = setTimeout(() => onGrace(), graceMs);
|
|
1943
|
+
const maybeUnref = timer.unref;
|
|
1944
|
+
if (typeof maybeUnref === "function") maybeUnref.call(timer);
|
|
1945
|
+
return () => clearTimeout(timer);
|
|
1946
|
+
}
|
|
1947
|
+
function resolveAgentPackageJsonPath(fromDir) {
|
|
1948
|
+
const candidates = [
|
|
1949
|
+
path5.resolve(fromDir, "..", "package.json"),
|
|
1950
|
+
path5.resolve(fromDir, "..", "..", "package.json")
|
|
1951
|
+
];
|
|
1952
|
+
for (const candidate of candidates) {
|
|
1953
|
+
try {
|
|
1954
|
+
const raw = JSON.parse(fs5.readFileSync(candidate, "utf-8"));
|
|
1955
|
+
if (raw.name === import_node_root.AGENT_ROOT_SPEC.packageName) return candidate;
|
|
1956
|
+
} catch {
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
return candidates[0] ?? path5.resolve(fromDir, "..", "package.json");
|
|
1960
|
+
}
|
|
1961
|
+
function scheduleAgentRestart(logger, requestedBy, dataDir) {
|
|
1962
|
+
if (dataDir !== void 0 && dataDir.length > 0) {
|
|
1963
|
+
try {
|
|
1964
|
+
(0, import_node_root.writeRestartIntentMarker)((0, import_node_root.serverRootDir)(dataDir), {
|
|
1965
|
+
requestedAtMs: Date.now(),
|
|
1966
|
+
reason: requestedBy
|
|
1967
|
+
});
|
|
1968
|
+
} catch (err) {
|
|
1969
|
+
logger.warn("failed to write restart-intent marker", {
|
|
1970
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
logger.warn("agent restart requested \u2014 exiting for supervisor relaunch", {
|
|
1975
|
+
meta: { requestedBy, graceMs: AGENT_RESTART_GRACE_MS }
|
|
1976
|
+
});
|
|
1977
|
+
const grace = setTimeout(() => {
|
|
1978
|
+
process.kill(process.pid, "SIGTERM");
|
|
1979
|
+
const hard = setTimeout(() => process.exit(0), 15e3);
|
|
1980
|
+
hard.unref();
|
|
1981
|
+
}, AGENT_RESTART_GRACE_MS);
|
|
1982
|
+
grace.unref();
|
|
1983
|
+
}
|
|
1984
|
+
var AgentUpdateService = class extends import_node_root.RootUpdateService {
|
|
1985
|
+
constructor(options) {
|
|
1986
|
+
super({
|
|
1987
|
+
spec: import_node_root.AGENT_ROOT_SPEC,
|
|
1988
|
+
envNames: {
|
|
1989
|
+
bootMode: "CAMSTACK_AGENT_BOOT_MODE",
|
|
1990
|
+
activeVersion: "CAMSTACK_AGENT_ACTIVE_VERSION",
|
|
1991
|
+
seedDir: "CAMSTACK_SEED_AGENT_DIR"
|
|
1992
|
+
},
|
|
1993
|
+
logger: options.logger,
|
|
1994
|
+
restartServer: options.restartAgent ?? ((requestedBy) => scheduleAgentRestart(options.logger, requestedBy, options.dataDir)),
|
|
1995
|
+
dataDir: options.dataDir,
|
|
1996
|
+
runningPackageJsonPath: options.runningPackageJsonPath ?? resolveAgentPackageJsonPath(__dirname),
|
|
1997
|
+
workspaceProbeDir: __dirname,
|
|
1998
|
+
execNpm: options.execNpm,
|
|
1999
|
+
env: options.env,
|
|
2000
|
+
now: options.now
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
function buildAgentServerManagementProvider(service) {
|
|
2005
|
+
return {
|
|
2006
|
+
getServerPackageStatus: () => service.getServerPackageStatus(),
|
|
2007
|
+
checkServerUpdate: () => service.checkServerUpdate(),
|
|
2008
|
+
applyServerUpdate: (input) => service.applyServerUpdate(input),
|
|
2009
|
+
rollbackServerUpdate: () => service.rollbackServerUpdate(),
|
|
2010
|
+
restartServer: () => Promise.resolve(service.restartNode())
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
function agentRuntimeManifestEntry() {
|
|
2014
|
+
return { addonId: AGENT_RUNTIME_ADDON_ID, capabilities: ["server-management"] };
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/agent-group-runner.ts
|
|
2018
|
+
var fs6 = __toESM(require("fs"));
|
|
2019
|
+
var path6 = __toESM(require("path"));
|
|
975
2020
|
var import_types = require("@camstack/types");
|
|
976
2021
|
function readPackageManifestAddons(dir) {
|
|
977
2022
|
try {
|
|
978
|
-
const pkgPath =
|
|
979
|
-
if (!
|
|
980
|
-
const raw = JSON.parse(
|
|
2023
|
+
const pkgPath = path6.join(dir, "package.json");
|
|
2024
|
+
if (!fs6.existsSync(pkgPath)) return null;
|
|
2025
|
+
const raw = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
981
2026
|
const packageName = typeof raw.name === "string" ? raw.name : "";
|
|
982
2027
|
const packageVersion = typeof raw.version === "string" ? raw.version : "0.0.0";
|
|
983
2028
|
const entries = Array.isArray(raw.camstack?.addons) ? raw.camstack.addons : [];
|
|
@@ -1148,7 +2193,7 @@ async function startAgent(configPath) {
|
|
|
1148
2193
|
const bundledDir = process.env["CAMSTACK_BUNDLED_ADDONS_DIR"];
|
|
1149
2194
|
let workspaceDir = null;
|
|
1150
2195
|
let resolvedSource = explicitSource;
|
|
1151
|
-
if (bundledDir &&
|
|
2196
|
+
if (bundledDir && fs7.existsSync(bundledDir)) {
|
|
1152
2197
|
workspaceDir = bundledDir;
|
|
1153
2198
|
resolvedSource = "local";
|
|
1154
2199
|
console.log(`[Agent] Using bundled addons from ${bundledDir}`);
|
|
@@ -1212,9 +2257,9 @@ async function startAgent(configPath) {
|
|
|
1212
2257
|
return discoveryMode ? "searching" : "disconnected";
|
|
1213
2258
|
}
|
|
1214
2259
|
function readConfigFile2(p) {
|
|
1215
|
-
if (!
|
|
2260
|
+
if (!fs7.existsSync(p)) return {};
|
|
1216
2261
|
try {
|
|
1217
|
-
return JSON.parse(
|
|
2262
|
+
return JSON.parse(fs7.readFileSync(p, "utf-8"));
|
|
1218
2263
|
} catch {
|
|
1219
2264
|
return {};
|
|
1220
2265
|
}
|
|
@@ -1244,6 +2289,7 @@ async function startAgent(configPath) {
|
|
|
1244
2289
|
const caps = addonCapMap.get(addonId) ?? [];
|
|
1245
2290
|
result.push({ addonId, capabilities: caps });
|
|
1246
2291
|
}
|
|
2292
|
+
result.push(agentRuntimeManifestEntry());
|
|
1247
2293
|
return result;
|
|
1248
2294
|
}
|
|
1249
2295
|
function aggregateManifest() {
|
|
@@ -1260,11 +2306,13 @@ async function startAgent(configPath) {
|
|
|
1260
2306
|
allNativeCaps.push(...childNativeCaps);
|
|
1261
2307
|
}
|
|
1262
2308
|
}
|
|
2309
|
+
const rootPackage = agentUpdateService.getRunningRootPackage();
|
|
1263
2310
|
return (0, import_system3.buildNodeManifest)(
|
|
1264
2311
|
config.nodeId,
|
|
1265
2312
|
allAddons,
|
|
1266
2313
|
allNativeCaps.length > 0 ? allNativeCaps : void 0,
|
|
1267
|
-
config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0
|
|
2314
|
+
config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0,
|
|
2315
|
+
rootPackage.version === null ? void 0 : { name: rootPackage.name, version: rootPackage.version }
|
|
1268
2316
|
);
|
|
1269
2317
|
}
|
|
1270
2318
|
function triggerUpwardRegistration() {
|
|
@@ -1276,7 +2324,9 @@ async function startAgent(configPath) {
|
|
|
1276
2324
|
signal: registerAbortController.signal,
|
|
1277
2325
|
log: (msg) => console.log(`[Agent] ${msg}`)
|
|
1278
2326
|
}
|
|
1279
|
-
).
|
|
2327
|
+
).then(() => {
|
|
2328
|
+
confirmAgentBootHealthy("hub-ack");
|
|
2329
|
+
}).catch((err) => {
|
|
1280
2330
|
if ((0, import_system3.isClusterSecretMismatchError)(err)) {
|
|
1281
2331
|
consoleLogger.error(
|
|
1282
2332
|
"hub registration rejected: cluster secret mismatch \u2014 correct CAMSTACK_CLUSTER_SECRET and restart the agent"
|
|
@@ -1306,11 +2356,41 @@ async function startAgent(configPath) {
|
|
|
1306
2356
|
import_types3.pipelineExecutorCapability,
|
|
1307
2357
|
import_types3.pipelineRunnerCapability,
|
|
1308
2358
|
import_types3.audioAnalyzerCapability,
|
|
1309
|
-
import_types3.platformProbeCapability
|
|
2359
|
+
import_types3.platformProbeCapability,
|
|
2360
|
+
import_types3.serverManagementCapability
|
|
1310
2361
|
];
|
|
1311
2362
|
for (const cap of agentCapabilities) {
|
|
1312
2363
|
capabilityRegistry.declareCapability(cap);
|
|
1313
2364
|
}
|
|
2365
|
+
const agentUpdateService = new AgentUpdateService({
|
|
2366
|
+
logger: consoleLogger,
|
|
2367
|
+
dataDir: config.dataDir
|
|
2368
|
+
});
|
|
2369
|
+
capabilityRegistry.registerProvider(
|
|
2370
|
+
"server-management",
|
|
2371
|
+
AGENT_RUNTIME_ADDON_ID,
|
|
2372
|
+
buildAgentServerManagementProvider(agentUpdateService)
|
|
2373
|
+
);
|
|
2374
|
+
let agentBootConfirmed = false;
|
|
2375
|
+
let cancelLocalConfirmFallback = null;
|
|
2376
|
+
const confirmAgentBootHealthy = (source) => {
|
|
2377
|
+
if (agentBootConfirmed) return;
|
|
2378
|
+
agentBootConfirmed = true;
|
|
2379
|
+
cancelLocalConfirmFallback?.();
|
|
2380
|
+
cancelLocalConfirmFallback = null;
|
|
2381
|
+
try {
|
|
2382
|
+
const confirm = agentUpdateService.confirmBootHealthy();
|
|
2383
|
+
if (confirm.promoted !== null) {
|
|
2384
|
+
consoleLogger.info(
|
|
2385
|
+
`agent root package update confirmed healthy (${source}): @camstack/agent@${confirm.promoted}`
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
consoleLogger.warn(
|
|
2390
|
+
`agent root boot confirmation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
};
|
|
1314
2394
|
const loggerFactory = (addonId) => agentLogManager.createLogger().withTags({ addonId });
|
|
1315
2395
|
const agentServiceSchema = createAgentService({
|
|
1316
2396
|
addonsDir: config.addonsDir,
|
|
@@ -1524,6 +2604,7 @@ async function startAgent(configPath) {
|
|
|
1524
2604
|
capabilityRegistry
|
|
1525
2605
|
);
|
|
1526
2606
|
triggerUpwardRegistration();
|
|
2607
|
+
cancelLocalConfirmFallback = armLocalConfirmFallback(() => confirmAgentBootHealthy("local-grace"));
|
|
1527
2608
|
let hubReachableFired = false;
|
|
1528
2609
|
broker.localBus.on("$node.connected", (data) => {
|
|
1529
2610
|
const node = data.node;
|
|
@@ -1547,6 +2628,8 @@ async function startAgent(configPath) {
|
|
|
1547
2628
|
const shutdown = async () => {
|
|
1548
2629
|
console.log("[Agent] Shutting down...");
|
|
1549
2630
|
registerAbortController.abort();
|
|
2631
|
+
cancelLocalConfirmFallback?.();
|
|
2632
|
+
cancelLocalConfirmFallback = null;
|
|
1550
2633
|
udsEventBridgeDispose?.();
|
|
1551
2634
|
udsEventBridgeDispose = null;
|
|
1552
2635
|
for (const [, entry] of loadedAddons) {
|
|
@@ -1707,7 +2790,7 @@ async function loadClusterCapableAddons(broker, config, capabilityRegistry, load
|
|
|
1707
2790
|
}
|
|
1708
2791
|
}
|
|
1709
2792
|
async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1710
|
-
if (!
|
|
2793
|
+
if (!fs7.existsSync(addonsDir)) return;
|
|
1711
2794
|
const packageDirs = resolveAddonPackageDirs(addonsDir);
|
|
1712
2795
|
const groupCandidates = [];
|
|
1713
2796
|
const inProcessDirs = /* @__PURE__ */ new Set();
|
|
@@ -1767,7 +2850,7 @@ async function loadDeployedAddons(broker, addonsDir, dataDir, loadedAddons, stor
|
|
|
1767
2850
|
}
|
|
1768
2851
|
}
|
|
1769
2852
|
async function loadInProcessDeployedAddons(broker, dirs, dataDir, loadedAddons, storageProvider, loggerFactory, capabilityRegistry) {
|
|
1770
|
-
const modelsDir =
|
|
2853
|
+
const modelsDir = path7.join(process.env["CAMSTACK_DATA"] ?? dataDir, "models");
|
|
1771
2854
|
const contextOptions = {
|
|
1772
2855
|
storageProvider,
|
|
1773
2856
|
addonConfig: { modelsDir },
|
|
@@ -1817,13 +2900,13 @@ async function loadInProcessDeployedAddons(broker, dirs, dataDir, loadedAddons,
|
|
|
1817
2900
|
}
|
|
1818
2901
|
function readAgentVersion() {
|
|
1819
2902
|
const candidates = [
|
|
1820
|
-
|
|
1821
|
-
|
|
2903
|
+
path7.resolve(__dirname, "..", "package.json"),
|
|
2904
|
+
path7.resolve(__dirname, "..", "..", "package.json")
|
|
1822
2905
|
];
|
|
1823
2906
|
for (const candidate of candidates) {
|
|
1824
2907
|
try {
|
|
1825
|
-
if (!
|
|
1826
|
-
const raw = JSON.parse(
|
|
2908
|
+
if (!fs7.existsSync(candidate)) continue;
|
|
2909
|
+
const raw = JSON.parse(fs7.readFileSync(candidate, "utf-8"));
|
|
1827
2910
|
if (raw.name === "@camstack/agent" && typeof raw.version === "string") return raw.version;
|
|
1828
2911
|
} catch {
|
|
1829
2912
|
}
|
|
@@ -1832,39 +2915,44 @@ function readAgentVersion() {
|
|
|
1832
2915
|
}
|
|
1833
2916
|
function isDir(p) {
|
|
1834
2917
|
try {
|
|
1835
|
-
return
|
|
2918
|
+
return fs7.statSync(p).isDirectory();
|
|
1836
2919
|
} catch {
|
|
1837
2920
|
return false;
|
|
1838
2921
|
}
|
|
1839
2922
|
}
|
|
1840
2923
|
function resolveAddonPackageDirs(addonsDir) {
|
|
1841
2924
|
const dirs = [];
|
|
1842
|
-
if (!
|
|
1843
|
-
for (const name of
|
|
1844
|
-
const full =
|
|
2925
|
+
if (!fs7.existsSync(addonsDir)) return dirs;
|
|
2926
|
+
for (const name of fs7.readdirSync(addonsDir)) {
|
|
2927
|
+
const full = path7.join(addonsDir, name);
|
|
1845
2928
|
if (name.startsWith("@") && isDir(full)) {
|
|
1846
|
-
for (const sub of
|
|
1847
|
-
const subFull =
|
|
1848
|
-
if (isDir(subFull) &&
|
|
2929
|
+
for (const sub of fs7.readdirSync(full)) {
|
|
2930
|
+
const subFull = path7.join(full, sub);
|
|
2931
|
+
if (isDir(subFull) && fs7.existsSync(path7.join(subFull, "package.json"))) {
|
|
1849
2932
|
dirs.push(subFull);
|
|
1850
2933
|
}
|
|
1851
2934
|
}
|
|
1852
|
-
} else if (isDir(full) &&
|
|
2935
|
+
} else if (isDir(full) && fs7.existsSync(path7.join(full, "package.json"))) {
|
|
1853
2936
|
dirs.push(full);
|
|
1854
2937
|
}
|
|
1855
2938
|
}
|
|
1856
2939
|
return dirs;
|
|
1857
2940
|
}
|
|
1858
2941
|
function buildAgentChildUdsManifest(nodeId, childId, caps) {
|
|
1859
|
-
const
|
|
2942
|
+
const capsByAddon = /* @__PURE__ */ new Map();
|
|
1860
2943
|
for (const cap of caps) {
|
|
1861
|
-
if (cap.deviceId
|
|
1862
|
-
|
|
1863
|
-
|
|
2944
|
+
if (cap.deviceId !== void 0) continue;
|
|
2945
|
+
const addonId = cap.addonId ?? childId;
|
|
2946
|
+
const set = capsByAddon.get(addonId) ?? /* @__PURE__ */ new Set();
|
|
2947
|
+
set.add(cap.capName);
|
|
2948
|
+
capsByAddon.set(addonId, set);
|
|
1864
2949
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2950
|
+
if (capsByAddon.size === 0) {
|
|
2951
|
+
capsByAddon.set(childId, /* @__PURE__ */ new Set());
|
|
2952
|
+
}
|
|
2953
|
+
const addons = [...capsByAddon.entries()].map(
|
|
2954
|
+
([addonId, capNames]) => ({ addonId, capabilities: [...capNames] })
|
|
2955
|
+
);
|
|
1868
2956
|
return { nodeId, addons };
|
|
1869
2957
|
}
|
|
1870
2958
|
var scriptName = process.argv[1] ?? "";
|