@camstack/agent 1.1.43 → 1.1.44
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 → chunk-Y6BUFOWG.mjs} +236 -67
- package/dist/chunk-Y6BUFOWG.mjs.map +1 -0
- package/dist/{chunk-MMKRM4RT.mjs → chunk-Z7VZQCFX.mjs} +60 -2
- package/dist/chunk-Z7VZQCFX.mjs.map +1 -0
- package/dist/cli.js +297 -70
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/index.js +293 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/starter.js +235 -66
- package/dist/starter.js.map +1 -1
- package/dist/starter.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-L34OOQI3.mjs.map +0 -1
- package/dist/chunk-MMKRM4RT.mjs.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,9 @@ var require_dist = __commonJS({
|
|
|
60
60
|
var src_exports = {};
|
|
61
61
|
__export(src_exports, {
|
|
62
62
|
AGENT_ROOT_SPEC: () => AGENT_ROOT_SPEC2,
|
|
63
|
+
DEV_UPLOADS_DIRNAME: () => DEV_UPLOADS_DIRNAME,
|
|
64
|
+
DEV_UPLOADS_KEEP_COUNT: () => DEV_UPLOADS_KEEP_COUNT,
|
|
65
|
+
DEV_UPLOAD_MANIFEST_FILE: () => DEV_UPLOAD_MANIFEST_FILE,
|
|
63
66
|
HOST_EXTERNAL_SPECIFIERS: () => HOST_EXTERNAL_SPECIFIERS,
|
|
64
67
|
HUB_ROOT_SPEC: () => HUB_ROOT_SPEC,
|
|
65
68
|
RESTART_INTENT_FILE: () => RESTART_INTENT_FILE,
|
|
@@ -69,11 +72,17 @@ var require_dist = __commonJS({
|
|
|
69
72
|
clearRestartIntentMarker: () => clearRestartIntentMarker,
|
|
70
73
|
compareSemver: () => compareSemver,
|
|
71
74
|
detectWorkspaceRoot: () => detectWorkspaceRoot,
|
|
75
|
+
devChannelEpoch: () => devChannelEpoch,
|
|
76
|
+
devUploadManifestPath: () => devUploadManifestPath,
|
|
77
|
+
devUploadVersionDir: () => devUploadVersionDir,
|
|
78
|
+
devUploadsDir: () => devUploadsDir,
|
|
72
79
|
emptyServerRootState: () => emptyServerRootState,
|
|
80
|
+
isDevChannelVersion: () => isDevChannelVersion,
|
|
73
81
|
isHostExternal: () => isHostExternal,
|
|
74
82
|
isServerRootState: () => isServerRootState,
|
|
75
83
|
minNodeMajorOf: () => minNodeMajorOf,
|
|
76
84
|
planBoot: () => planBoot,
|
|
85
|
+
readDevUploadManifest: () => readDevUploadManifest,
|
|
77
86
|
readRestartIntentMarker: () => readRestartIntentMarker,
|
|
78
87
|
readServerRootState: () => readServerRootState,
|
|
79
88
|
registerActiveRootResolver: () => registerActiveRootResolver,
|
|
@@ -86,10 +95,58 @@ var require_dist = __commonJS({
|
|
|
86
95
|
validateVersionDir: () => validateVersionDir,
|
|
87
96
|
versionDir: () => versionDir,
|
|
88
97
|
versionsDir: () => versionsDir,
|
|
98
|
+
writeDevUploadManifest: () => writeDevUploadManifest,
|
|
89
99
|
writeRestartIntentMarker: () => writeRestartIntentMarker2,
|
|
90
100
|
writeServerRootState: () => writeServerRootState
|
|
91
101
|
});
|
|
92
102
|
module2.exports = __toCommonJS(src_exports);
|
|
103
|
+
var fs8 = __toESM2(require("fs"));
|
|
104
|
+
var path9 = __toESM2(require("path"));
|
|
105
|
+
var DEV_UPLOADS_DIRNAME = "dev-uploads";
|
|
106
|
+
var DEV_UPLOAD_MANIFEST_FILE = "manifest.json";
|
|
107
|
+
var DEV_UPLOADS_KEEP_COUNT = 3;
|
|
108
|
+
var DEV_CHANNEL_VERSION_RE = /-dev\.\d+$/;
|
|
109
|
+
function isDevChannelVersion(version) {
|
|
110
|
+
return DEV_CHANNEL_VERSION_RE.test(version);
|
|
111
|
+
}
|
|
112
|
+
function devChannelEpoch(version) {
|
|
113
|
+
const match = /-dev\.(\d+)$/.exec(version);
|
|
114
|
+
if (match === null) return null;
|
|
115
|
+
const epoch = Number.parseInt(match[1] ?? "", 10);
|
|
116
|
+
return Number.isNaN(epoch) ? null : epoch;
|
|
117
|
+
}
|
|
118
|
+
function devUploadsDir(rootDir) {
|
|
119
|
+
return path9.join(rootDir, DEV_UPLOADS_DIRNAME);
|
|
120
|
+
}
|
|
121
|
+
function devUploadVersionDir(rootDir, version) {
|
|
122
|
+
return path9.join(devUploadsDir(rootDir), version);
|
|
123
|
+
}
|
|
124
|
+
function devUploadManifestPath(versionDirPath) {
|
|
125
|
+
return path9.join(versionDirPath, DEV_UPLOAD_MANIFEST_FILE);
|
|
126
|
+
}
|
|
127
|
+
function isDevUploadManifest(v) {
|
|
128
|
+
if (typeof v !== "object" || v === null) return false;
|
|
129
|
+
const m = v;
|
|
130
|
+
if (typeof m["version"] !== "string") return false;
|
|
131
|
+
const packages = m["packages"];
|
|
132
|
+
if (typeof packages !== "object" || packages === null || Array.isArray(packages)) return false;
|
|
133
|
+
return Object.values(packages).every((filename) => typeof filename === "string");
|
|
134
|
+
}
|
|
135
|
+
function readDevUploadManifest(versionDirPath) {
|
|
136
|
+
try {
|
|
137
|
+
const raw = JSON.parse(fs8.readFileSync(devUploadManifestPath(versionDirPath), "utf-8"));
|
|
138
|
+
return isDevUploadManifest(raw) ? raw : null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function writeDevUploadManifest(versionDirPath, manifest) {
|
|
144
|
+
fs8.mkdirSync(versionDirPath, { recursive: true });
|
|
145
|
+
const target = devUploadManifestPath(versionDirPath);
|
|
146
|
+
const tmp = `${target}.tmp`;
|
|
147
|
+
fs8.writeFileSync(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
148
|
+
fs8.renameSync(tmp, target);
|
|
149
|
+
}
|
|
93
150
|
var HUB_ROOT_SPEC = {
|
|
94
151
|
packageName: "@camstack/server",
|
|
95
152
|
entryRelPath: ["dist", "launcher.js"]
|
|
@@ -98,8 +155,8 @@ var require_dist = __commonJS({
|
|
|
98
155
|
packageName: "@camstack/agent",
|
|
99
156
|
entryRelPath: ["dist", "cli.js"]
|
|
100
157
|
};
|
|
101
|
-
var
|
|
102
|
-
var
|
|
158
|
+
var fs22 = __toESM2(require("fs"));
|
|
159
|
+
var path22 = __toESM2(require("path"));
|
|
103
160
|
var SERVER_ROOT_DIRNAME = "server-root";
|
|
104
161
|
var SERVER_ROOT_STATE_FILE = "state.json";
|
|
105
162
|
var RESTART_INTENT_FILE = ".restart-intent";
|
|
@@ -113,22 +170,22 @@ var require_dist = __commonJS({
|
|
|
113
170
|
};
|
|
114
171
|
}
|
|
115
172
|
function serverRootDir2(dataDir) {
|
|
116
|
-
return
|
|
173
|
+
return path22.join(dataDir, SERVER_ROOT_DIRNAME);
|
|
117
174
|
}
|
|
118
175
|
function versionsDir(rootDir) {
|
|
119
|
-
return
|
|
176
|
+
return path22.join(rootDir, "versions");
|
|
120
177
|
}
|
|
121
178
|
function versionDir(rootDir, version) {
|
|
122
|
-
return
|
|
179
|
+
return path22.join(versionsDir(rootDir), version);
|
|
123
180
|
}
|
|
124
181
|
function rootPackageDir(versionDirPath, spec) {
|
|
125
|
-
return
|
|
182
|
+
return path22.join(versionDirPath, "node_modules", ...spec.packageName.split("/"));
|
|
126
183
|
}
|
|
127
184
|
function rootEntryPath(versionDirPath, spec) {
|
|
128
|
-
return
|
|
185
|
+
return path22.join(rootPackageDir(versionDirPath, spec), ...spec.entryRelPath);
|
|
129
186
|
}
|
|
130
187
|
function stateFilePath(rootDir) {
|
|
131
|
-
return
|
|
188
|
+
return path22.join(rootDir, SERVER_ROOT_STATE_FILE);
|
|
132
189
|
}
|
|
133
190
|
function isNullableString(v) {
|
|
134
191
|
return v === null || typeof v === "string";
|
|
@@ -155,21 +212,21 @@ var require_dist = __commonJS({
|
|
|
155
212
|
}
|
|
156
213
|
function readServerRootState(rootDir) {
|
|
157
214
|
try {
|
|
158
|
-
const raw = JSON.parse(
|
|
215
|
+
const raw = JSON.parse(fs22.readFileSync(stateFilePath(rootDir), "utf-8"));
|
|
159
216
|
return isServerRootState(raw) ? raw : null;
|
|
160
217
|
} catch {
|
|
161
218
|
return null;
|
|
162
219
|
}
|
|
163
220
|
}
|
|
164
221
|
function writeServerRootState(rootDir, state) {
|
|
165
|
-
|
|
222
|
+
fs22.mkdirSync(rootDir, { recursive: true });
|
|
166
223
|
const target = stateFilePath(rootDir);
|
|
167
224
|
const tmp = `${target}.tmp`;
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
fs22.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
226
|
+
fs22.renameSync(tmp, target);
|
|
170
227
|
}
|
|
171
228
|
function restartIntentMarkerPath(rootDir) {
|
|
172
|
-
return
|
|
229
|
+
return path22.join(rootDir, RESTART_INTENT_FILE);
|
|
173
230
|
}
|
|
174
231
|
function isRestartIntentMarker(v) {
|
|
175
232
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -177,15 +234,15 @@ var require_dist = __commonJS({
|
|
|
177
234
|
return typeof m["requestedAtMs"] === "number" && typeof m["reason"] === "string";
|
|
178
235
|
}
|
|
179
236
|
function writeRestartIntentMarker2(rootDir, marker) {
|
|
180
|
-
|
|
237
|
+
fs22.mkdirSync(rootDir, { recursive: true });
|
|
181
238
|
const target = restartIntentMarkerPath(rootDir);
|
|
182
239
|
const tmp = `${target}.tmp`;
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
fs22.writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
|
|
241
|
+
fs22.renameSync(tmp, target);
|
|
185
242
|
}
|
|
186
243
|
function readRestartIntentMarker(rootDir) {
|
|
187
244
|
try {
|
|
188
|
-
const raw = JSON.parse(
|
|
245
|
+
const raw = JSON.parse(fs22.readFileSync(restartIntentMarkerPath(rootDir), "utf-8"));
|
|
189
246
|
return isRestartIntentMarker(raw) ? raw : null;
|
|
190
247
|
} catch {
|
|
191
248
|
return null;
|
|
@@ -193,7 +250,7 @@ var require_dist = __commonJS({
|
|
|
193
250
|
}
|
|
194
251
|
function clearRestartIntentMarker(rootDir) {
|
|
195
252
|
try {
|
|
196
|
-
|
|
253
|
+
fs22.rmSync(restartIntentMarkerPath(rootDir), { force: true });
|
|
197
254
|
} catch {
|
|
198
255
|
}
|
|
199
256
|
}
|
|
@@ -206,13 +263,13 @@ var require_dist = __commonJS({
|
|
|
206
263
|
function validateVersionDir(rootDir, version, nodeMajor, spec) {
|
|
207
264
|
const vDir = versionDir(rootDir, version);
|
|
208
265
|
const entry = rootEntryPath(vDir, spec);
|
|
209
|
-
if (!
|
|
266
|
+
if (!fs22.existsSync(entry)) {
|
|
210
267
|
return `root entry missing: ${entry}`;
|
|
211
268
|
}
|
|
212
|
-
const pkgJsonPath =
|
|
269
|
+
const pkgJsonPath = path22.join(rootPackageDir(vDir, spec), "package.json");
|
|
213
270
|
let pkg;
|
|
214
271
|
try {
|
|
215
|
-
const raw = JSON.parse(
|
|
272
|
+
const raw = JSON.parse(fs22.readFileSync(pkgJsonPath, "utf-8"));
|
|
216
273
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
217
274
|
return `package.json malformed: ${pkgJsonPath}`;
|
|
218
275
|
}
|
|
@@ -359,25 +416,25 @@ var require_dist = __commonJS({
|
|
|
359
416
|
if (pa.prerelease > pb.prerelease) return 1;
|
|
360
417
|
return 0;
|
|
361
418
|
}
|
|
362
|
-
var
|
|
363
|
-
var
|
|
419
|
+
var fs32 = __toESM2(require("fs"));
|
|
420
|
+
var path32 = __toESM2(require("path"));
|
|
364
421
|
function detectWorkspaceRoot(fromDir) {
|
|
365
|
-
let dir =
|
|
422
|
+
let dir = path32.resolve(fromDir);
|
|
366
423
|
for (; ; ) {
|
|
367
|
-
const pkgPath =
|
|
424
|
+
const pkgPath = path32.join(dir, "package.json");
|
|
368
425
|
try {
|
|
369
|
-
const raw = JSON.parse(
|
|
426
|
+
const raw = JSON.parse(fs32.readFileSync(pkgPath, "utf-8"));
|
|
370
427
|
if (typeof raw === "object" && raw !== null && raw["workspaces"] !== void 0) {
|
|
371
428
|
return dir;
|
|
372
429
|
}
|
|
373
430
|
} catch {
|
|
374
431
|
}
|
|
375
|
-
const parent =
|
|
432
|
+
const parent = path32.dirname(dir);
|
|
376
433
|
if (parent === dir) return null;
|
|
377
434
|
dir = parent;
|
|
378
435
|
}
|
|
379
436
|
}
|
|
380
|
-
var
|
|
437
|
+
var path42 = __toESM2(require("path"));
|
|
381
438
|
var import_node_url = require("url");
|
|
382
439
|
var HOST_EXTERNAL_SPECIFIERS = [
|
|
383
440
|
"@camstack/system",
|
|
@@ -405,7 +462,7 @@ var require_dist = __commonJS({
|
|
|
405
462
|
return;
|
|
406
463
|
}
|
|
407
464
|
const anchorURL = (0, import_node_url.pathToFileURL)(
|
|
408
|
-
|
|
465
|
+
path42.join(activeRootDir, "node_modules", "__camstack_starter_anchor__.js")
|
|
409
466
|
).href;
|
|
410
467
|
const hooks = {
|
|
411
468
|
resolve: (specifier, context, nextResolve) => {
|
|
@@ -483,8 +540,8 @@ var require_dist = __commonJS({
|
|
|
483
540
|
options.loadEntry(options.seedEntry);
|
|
484
541
|
}
|
|
485
542
|
var import_node_child_process = require("child_process");
|
|
486
|
-
var
|
|
487
|
-
var
|
|
543
|
+
var fs42 = __toESM2(require("fs"));
|
|
544
|
+
var path52 = __toESM2(require("path"));
|
|
488
545
|
var import_node_util = require("util");
|
|
489
546
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
490
547
|
function buildNpmRegistryArgs(registry) {
|
|
@@ -496,7 +553,7 @@ var require_dist = __commonJS({
|
|
|
496
553
|
var RESTART_REASON_PREFIX = "server-update";
|
|
497
554
|
function readPackageVersion(pkgJsonPath) {
|
|
498
555
|
try {
|
|
499
|
-
const raw = JSON.parse(
|
|
556
|
+
const raw = JSON.parse(fs42.readFileSync(pkgJsonPath, "utf-8"));
|
|
500
557
|
if (typeof raw === "object" && raw !== null) {
|
|
501
558
|
const version = raw["version"];
|
|
502
559
|
if (typeof version === "string") return version;
|
|
@@ -523,7 +580,7 @@ var require_dist = __commonJS({
|
|
|
523
580
|
this.envNames = options.envNames;
|
|
524
581
|
this.logger = options.logger;
|
|
525
582
|
this.restartServerFn = options.restartServer;
|
|
526
|
-
this.dataDir =
|
|
583
|
+
this.dataDir = path52.resolve(options.dataDir);
|
|
527
584
|
this.execNpm = options.execNpm ?? (async (args2, opts) => {
|
|
528
585
|
const { stdout } = await execFileAsync("npm", [...args2], {
|
|
529
586
|
cwd: opts.cwd,
|
|
@@ -552,7 +609,7 @@ var require_dist = __commonJS({
|
|
|
552
609
|
seedVersion() {
|
|
553
610
|
const seedDir = this.env[this.envNames.seedDir];
|
|
554
611
|
if (seedDir === void 0 || seedDir.length === 0) return null;
|
|
555
|
-
return readPackageVersion(
|
|
612
|
+
return readPackageVersion(path52.join(seedDir, "package.json"));
|
|
556
613
|
}
|
|
557
614
|
rootDir() {
|
|
558
615
|
return serverRootDir2(this.dataDir);
|
|
@@ -570,7 +627,7 @@ var require_dist = __commonJS({
|
|
|
570
627
|
const rootDir = this.rootDir();
|
|
571
628
|
const raw = readServerRootState(rootDir);
|
|
572
629
|
if (raw !== null) return { state: raw, corrupt: false };
|
|
573
|
-
return { state: emptyServerRootState(), corrupt:
|
|
630
|
+
return { state: emptyServerRootState(), corrupt: fs42.existsSync(stateFilePath(rootDir)) };
|
|
574
631
|
}
|
|
575
632
|
updateState(state) {
|
|
576
633
|
if (this.inFlight === "checking") return "checking";
|
|
@@ -710,6 +767,17 @@ var require_dist = __commonJS({
|
|
|
710
767
|
message: `Refused: ${this.spec.packageName}@${target} is already running.`
|
|
711
768
|
};
|
|
712
769
|
}
|
|
770
|
+
if (isDevChannelVersion(target)) {
|
|
771
|
+
const uploadsDir = devUploadVersionDir(this.rootDir(), target);
|
|
772
|
+
if (!fs42.existsSync(uploadsDir)) {
|
|
773
|
+
return {
|
|
774
|
+
accepted: false,
|
|
775
|
+
targetVersion: target,
|
|
776
|
+
restarting: false,
|
|
777
|
+
message: `Refused: dev-channel version ${target} has no uploaded tarballs on this node (${uploadsDir} is missing). Dev server deploys are hub-only in this phase \u2014 upload via \`camstack deploy-server\` against this node first.`
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
713
781
|
this.inFlight = "staging";
|
|
714
782
|
try {
|
|
715
783
|
await this.stageAndActivate(target);
|
|
@@ -756,34 +824,40 @@ var require_dist = __commonJS({
|
|
|
756
824
|
async stageAndActivate(target) {
|
|
757
825
|
const rootDir = this.rootDir();
|
|
758
826
|
const vDir = versionsDir(rootDir);
|
|
759
|
-
|
|
760
|
-
const stagingDir =
|
|
761
|
-
|
|
827
|
+
fs42.mkdirSync(vDir, { recursive: true });
|
|
828
|
+
const stagingDir = path52.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`);
|
|
829
|
+
fs42.mkdirSync(stagingDir, { recursive: true });
|
|
762
830
|
try {
|
|
763
|
-
|
|
764
|
-
path42.join(stagingDir, "package.json"),
|
|
765
|
-
JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
|
|
766
|
-
"utf-8"
|
|
767
|
-
);
|
|
831
|
+
const devDir = devUploadVersionDir(rootDir, target);
|
|
768
832
|
const registry = this.env["CAMSTACK_NPM_REGISTRY"];
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
"
|
|
773
|
-
|
|
774
|
-
"
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
833
|
+
const installArgs = ["install", "--omit=dev", "--no-audit", "--no-fund", "--loglevel=error"];
|
|
834
|
+
if (fs42.existsSync(devDir)) {
|
|
835
|
+
fs42.writeFileSync(
|
|
836
|
+
path52.join(stagingDir, "package.json"),
|
|
837
|
+
JSON.stringify(this.buildDevUploadsPackageJson(devDir, target), null, 2),
|
|
838
|
+
"utf-8"
|
|
839
|
+
);
|
|
840
|
+
await this.execNpm([...installArgs, ...buildNpmRegistryArgs(registry)], {
|
|
841
|
+
cwd: stagingDir,
|
|
842
|
+
timeoutMs: NPM_INSTALL_TIMEOUT_MS
|
|
843
|
+
});
|
|
844
|
+
} else {
|
|
845
|
+
fs42.writeFileSync(
|
|
846
|
+
path52.join(stagingDir, "package.json"),
|
|
847
|
+
JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
|
|
848
|
+
"utf-8"
|
|
849
|
+
);
|
|
850
|
+
await this.execNpm(
|
|
851
|
+
[...installArgs, `${this.spec.packageName}@${target}`, ...buildNpmRegistryArgs(registry)],
|
|
852
|
+
{ cwd: stagingDir, timeoutMs: NPM_INSTALL_TIMEOUT_MS }
|
|
853
|
+
);
|
|
854
|
+
}
|
|
781
855
|
const entry = rootEntryPath(stagingDir, this.spec);
|
|
782
|
-
if (!
|
|
856
|
+
if (!fs42.existsSync(entry)) {
|
|
783
857
|
throw new Error(`staged closure is missing the root entry (${entry})`);
|
|
784
858
|
}
|
|
785
859
|
const stagedVersion = readPackageVersion(
|
|
786
|
-
|
|
860
|
+
path52.join(rootPackageDir(stagingDir, this.spec), "package.json")
|
|
787
861
|
);
|
|
788
862
|
if (stagedVersion !== target) {
|
|
789
863
|
throw new Error(
|
|
@@ -791,17 +865,61 @@ var require_dist = __commonJS({
|
|
|
791
865
|
);
|
|
792
866
|
}
|
|
793
867
|
const dest = versionDir(rootDir, target);
|
|
794
|
-
if (
|
|
868
|
+
if (fs42.existsSync(dest)) {
|
|
795
869
|
const aside = `${dest}.evicted-${this.now()}`;
|
|
796
|
-
await
|
|
797
|
-
await
|
|
870
|
+
await fs42.promises.rename(dest, aside);
|
|
871
|
+
await fs42.promises.rm(aside, { recursive: true, force: true }).catch(() => void 0);
|
|
798
872
|
}
|
|
799
|
-
await
|
|
873
|
+
await fs42.promises.rename(stagingDir, dest);
|
|
800
874
|
} catch (err) {
|
|
801
|
-
await
|
|
875
|
+
await fs42.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
|
|
802
876
|
throw err;
|
|
803
877
|
}
|
|
804
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Synthetic staging package.json for the dev server-deploy channel:
|
|
881
|
+
* `dependencies` carries the root package as a `file:` ref to its uploaded
|
|
882
|
+
* tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz
|
|
883
|
+
* so any transitive occurrence resolves to the uploaded copy, never npm.
|
|
884
|
+
* Fail-closed: a missing/invalid manifest, a manifest for a different
|
|
885
|
+
* version, a missing root package, or a listed-but-absent tgz all throw
|
|
886
|
+
* (surfaced by `applyServerUpdate` as a staging failure).
|
|
887
|
+
*/
|
|
888
|
+
buildDevUploadsPackageJson(devDir, target) {
|
|
889
|
+
const manifest = readDevUploadManifest(devDir);
|
|
890
|
+
if (manifest === null) {
|
|
891
|
+
throw new Error(`dev-uploads manifest missing or invalid in ${devDir}`);
|
|
892
|
+
}
|
|
893
|
+
if (manifest.version !== target) {
|
|
894
|
+
throw new Error(
|
|
895
|
+
`dev-uploads manifest version mismatch: dir is for ${target}, manifest says ${manifest.version}`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
const rootTgz = manifest.packages[this.spec.packageName];
|
|
899
|
+
if (rootTgz === void 0) {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`dev-uploads for ${target} do not include the root package ${this.spec.packageName}`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
const fileRef = (filename) => {
|
|
905
|
+
const abs = path52.join(devDir, filename);
|
|
906
|
+
if (!fs42.existsSync(abs)) {
|
|
907
|
+
throw new Error(`dev-uploads tarball listed in the manifest is missing: ${abs}`);
|
|
908
|
+
}
|
|
909
|
+
return `file:${abs}`;
|
|
910
|
+
};
|
|
911
|
+
const overrides = {};
|
|
912
|
+
for (const [pkgName, filename] of Object.entries(manifest.packages)) {
|
|
913
|
+
if (pkgName === this.spec.packageName) continue;
|
|
914
|
+
overrides[pkgName] = fileRef(filename);
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
name: "camstack-node-root",
|
|
918
|
+
private: true,
|
|
919
|
+
dependencies: { [this.spec.packageName]: fileRef(rootTgz) },
|
|
920
|
+
...Object.keys(overrides).length > 0 ? { overrides } : {}
|
|
921
|
+
};
|
|
922
|
+
}
|
|
805
923
|
// ── Rollback ──────────────────────────────────────────────────────────
|
|
806
924
|
async rollbackServerUpdate() {
|
|
807
925
|
if (this.inFlight !== "idle") {
|
|
@@ -931,6 +1049,7 @@ var require_dist = __commonJS({
|
|
|
931
1049
|
(v) => typeof v === "string"
|
|
932
1050
|
)
|
|
933
1051
|
);
|
|
1052
|
+
this.sweepDevUploads(DEV_UPLOADS_KEEP_COUNT);
|
|
934
1053
|
this.logger.info("root package update confirmed healthy", {
|
|
935
1054
|
meta: {
|
|
936
1055
|
version: promoted.currentVersion,
|
|
@@ -954,23 +1073,23 @@ var require_dist = __commonJS({
|
|
|
954
1073
|
const vDir = versionsDir(this.rootDir());
|
|
955
1074
|
let entries;
|
|
956
1075
|
try {
|
|
957
|
-
entries =
|
|
1076
|
+
entries = fs42.readdirSync(vDir);
|
|
958
1077
|
} catch {
|
|
959
1078
|
return;
|
|
960
1079
|
}
|
|
961
1080
|
for (const entry of entries) {
|
|
962
1081
|
if (keep.includes(entry)) continue;
|
|
963
|
-
const full =
|
|
1082
|
+
const full = path52.join(vDir, entry);
|
|
964
1083
|
if (entry.startsWith(".")) {
|
|
965
1084
|
try {
|
|
966
|
-
const ageMs = this.now() -
|
|
1085
|
+
const ageMs = this.now() - fs42.statSync(full).mtimeMs;
|
|
967
1086
|
if (ageMs < _RootUpdateService.STALE_TRANSIENT_MS) continue;
|
|
968
1087
|
} catch {
|
|
969
1088
|
continue;
|
|
970
1089
|
}
|
|
971
1090
|
}
|
|
972
1091
|
try {
|
|
973
|
-
|
|
1092
|
+
fs42.rmSync(full, { recursive: true, force: true });
|
|
974
1093
|
this.logger.debug("pruned server-root version dir", { meta: { entry } });
|
|
975
1094
|
} catch (err) {
|
|
976
1095
|
this.logger.warn("failed to prune server-root version dir", {
|
|
@@ -979,6 +1098,56 @@ var require_dist = __commonJS({
|
|
|
979
1098
|
}
|
|
980
1099
|
}
|
|
981
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Promote-time sweep of `server-root/dev-uploads/`: keep only the
|
|
1103
|
+
* `keepCount` most recent entries (ordered by the self-describing
|
|
1104
|
+
* `-dev.<epochSeconds>` suffix; non-dev-shaped residue counts as oldest).
|
|
1105
|
+
* Uses the same rename-aside graveyard pattern as the addon installer —
|
|
1106
|
+
* a rename is a metadata op that succeeds even when a tarball is held
|
|
1107
|
+
* open, and residue in the graveyard is re-swept on the next promote.
|
|
1108
|
+
*/
|
|
1109
|
+
sweepDevUploads(keepCount) {
|
|
1110
|
+
const dir = devUploadsDir(this.rootDir());
|
|
1111
|
+
let entries;
|
|
1112
|
+
try {
|
|
1113
|
+
entries = fs42.readdirSync(dir);
|
|
1114
|
+
} catch {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const graveyard = path52.join(dir, ".sweeping");
|
|
1118
|
+
try {
|
|
1119
|
+
for (const residue of fs42.readdirSync(graveyard)) {
|
|
1120
|
+
try {
|
|
1121
|
+
fs42.rmSync(path52.join(graveyard, residue), { recursive: true, force: true });
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
const versions = entries.filter((name) => !name.startsWith("."));
|
|
1128
|
+
const byNewestFirst = [...versions].sort(
|
|
1129
|
+
(a, b) => (devChannelEpoch(b) ?? -1) - (devChannelEpoch(a) ?? -1)
|
|
1130
|
+
);
|
|
1131
|
+
const doomed = byNewestFirst.slice(keepCount);
|
|
1132
|
+
if (doomed.length === 0) return;
|
|
1133
|
+
fs42.mkdirSync(graveyard, { recursive: true });
|
|
1134
|
+
for (const entry of doomed) {
|
|
1135
|
+
const aside = path52.join(graveyard, `${entry}-${this.now()}`);
|
|
1136
|
+
try {
|
|
1137
|
+
fs42.renameSync(path52.join(dir, entry), aside);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
this.logger.warn("failed to move dev-uploads entry aside for sweep", {
|
|
1140
|
+
meta: { entry, error: err instanceof Error ? err.message : String(err) }
|
|
1141
|
+
});
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
fs42.rmSync(aside, { recursive: true, force: true });
|
|
1146
|
+
this.logger.debug("swept dev-uploads entry", { meta: { entry } });
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
982
1151
|
/** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */
|
|
983
1152
|
static STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
|
|
984
1153
|
};
|
|
@@ -1059,6 +1228,39 @@ function deriveHubUrlFromRegistry(nodes) {
|
|
|
1059
1228
|
return `https://${host}:${HUB_API_PORT}`;
|
|
1060
1229
|
}
|
|
1061
1230
|
|
|
1231
|
+
// src/agent-http-auth.ts
|
|
1232
|
+
var import_node_crypto = require("crypto");
|
|
1233
|
+
function isLoopbackAddress(addr) {
|
|
1234
|
+
if (!addr) return false;
|
|
1235
|
+
if (addr === "::1") return true;
|
|
1236
|
+
const v4 = addr.startsWith("::ffff:") ? addr.slice("::ffff:".length) : addr;
|
|
1237
|
+
const parts = v4.split(".");
|
|
1238
|
+
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
1239
|
+
return parts.every((p) => /^\d{1,3}$/.test(p));
|
|
1240
|
+
}
|
|
1241
|
+
function bearerMatchesSecret(authorization, secret) {
|
|
1242
|
+
if (secret.length === 0) return false;
|
|
1243
|
+
if (!authorization?.startsWith("Bearer ")) return false;
|
|
1244
|
+
const token = Buffer.from(authorization.slice("Bearer ".length));
|
|
1245
|
+
const expected = Buffer.from(secret);
|
|
1246
|
+
return token.length === expected.length && (0, import_node_crypto.timingSafeEqual)(token, expected);
|
|
1247
|
+
}
|
|
1248
|
+
function isAuthorizedAgentRequest(input, clusterSecret) {
|
|
1249
|
+
if (isLoopbackAddress(input.remoteAddress)) return true;
|
|
1250
|
+
if (clusterSecret === null || clusterSecret.length === 0) return false;
|
|
1251
|
+
return bearerMatchesSecret(input.authorization, clusterSecret);
|
|
1252
|
+
}
|
|
1253
|
+
function isPairingRequest(method, urlPath) {
|
|
1254
|
+
const pathOnly = urlPath.split("?")[0] ?? urlPath;
|
|
1255
|
+
if (method === "GET") {
|
|
1256
|
+
return pathOnly === "/api/agent/status" || pathOnly === "/api/agent/config" || pathOnly === "/api/agent/discovered-nodes";
|
|
1257
|
+
}
|
|
1258
|
+
if (method === "POST") {
|
|
1259
|
+
return pathOnly === "/api/agent/config" || pathOnly === "/api/agent/restart";
|
|
1260
|
+
}
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1062
1264
|
// src/agent-http.ts
|
|
1063
1265
|
function compareVersions(a, b) {
|
|
1064
1266
|
if (a === null && b === null) return 0;
|
|
@@ -1147,7 +1349,32 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
1147
1349
|
const app = (0, import_fastify.default)({ logger: false });
|
|
1148
1350
|
const cors = await import("@fastify/cors");
|
|
1149
1351
|
await app.register(cors.default);
|
|
1352
|
+
const currentSecret = () => {
|
|
1353
|
+
const raw = readConfigFile(config.configPath);
|
|
1354
|
+
return typeof raw.secret === "string" && raw.secret.length > 0 ? raw.secret : null;
|
|
1355
|
+
};
|
|
1356
|
+
app.addHook("onRequest", async (req, reply) => {
|
|
1357
|
+
const pathOnly = req.url.split("?")[0] ?? req.url;
|
|
1358
|
+
const isProtected = pathOnly.startsWith("/api/") || pathOnly.startsWith("/health/");
|
|
1359
|
+
if (!isProtected) return;
|
|
1360
|
+
const secret = currentSecret();
|
|
1361
|
+
const authInput = {
|
|
1362
|
+
remoteAddress: req.socket.remoteAddress ?? void 0,
|
|
1363
|
+
...typeof req.headers.authorization === "string" ? { authorization: req.headers.authorization } : {}
|
|
1364
|
+
};
|
|
1365
|
+
if (isAuthorizedAgentRequest(authInput, secret)) return;
|
|
1366
|
+
if (secret === null && isPairingRequest(req.method, pathOnly)) return;
|
|
1367
|
+
return reply.status(401).send({ ok: false, error: "unauthorized" });
|
|
1368
|
+
});
|
|
1150
1369
|
app.get("/health", async (_req, reply) => {
|
|
1370
|
+
try {
|
|
1371
|
+
await getBroker().call("$agent.health");
|
|
1372
|
+
return { ok: true };
|
|
1373
|
+
} catch {
|
|
1374
|
+
return reply.status(503).send({ ok: false });
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
app.get("/health/details", async (_req, reply) => {
|
|
1151
1378
|
try {
|
|
1152
1379
|
const result = await getBroker().call("$agent.health");
|
|
1153
1380
|
return result;
|
|
@@ -1466,7 +1693,7 @@ var import_moleculer = require("moleculer");
|
|
|
1466
1693
|
// src/agent-deploy-swap.ts
|
|
1467
1694
|
var fs4 = __toESM(require("fs"));
|
|
1468
1695
|
var path4 = __toESM(require("path"));
|
|
1469
|
-
var
|
|
1696
|
+
var import_node_crypto2 = require("crypto");
|
|
1470
1697
|
function rmrf(target) {
|
|
1471
1698
|
fs4.rmSync(target, { recursive: true, force: true });
|
|
1472
1699
|
}
|
|
@@ -1490,7 +1717,7 @@ async function applyDeployedBundle(input) {
|
|
|
1490
1717
|
const liveParent = path4.dirname(liveDir);
|
|
1491
1718
|
const liveBase = path4.basename(liveDir);
|
|
1492
1719
|
fs4.mkdirSync(liveParent, { recursive: true });
|
|
1493
|
-
const token = (0,
|
|
1720
|
+
const token = (0, import_node_crypto2.randomBytes)(6).toString("hex");
|
|
1494
1721
|
const nextDir = path4.join(liveParent, `.${liveBase}.next.${token}`);
|
|
1495
1722
|
const backupDir = path4.join(liveParent, `.${liveBase}.backup.${token}`);
|
|
1496
1723
|
fs4.mkdirSync(nextDir, { recursive: true });
|
|
@@ -1539,7 +1766,7 @@ async function applyModelDistribution(seam, params) {
|
|
|
1539
1766
|
}
|
|
1540
1767
|
|
|
1541
1768
|
// src/fetch-bundle-from-hub.ts
|
|
1542
|
-
var
|
|
1769
|
+
var import_node_crypto3 = require("crypto");
|
|
1543
1770
|
var import_undici = require("undici");
|
|
1544
1771
|
async function fetchBundleFromHub(opts) {
|
|
1545
1772
|
const authHeader = { Authorization: `Bearer ${opts.token}`, "accept-encoding": "identity" };
|
|
@@ -1554,7 +1781,7 @@ async function fetchBundleFromHub(opts) {
|
|
|
1554
1781
|
if (buffer.length !== opts.bytes) {
|
|
1555
1782
|
throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
|
|
1556
1783
|
}
|
|
1557
|
-
const sha = (0,
|
|
1784
|
+
const sha = (0, import_node_crypto3.createHash)("sha256").update(buffer).digest("hex");
|
|
1558
1785
|
if (sha !== opts.sha256) {
|
|
1559
1786
|
throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
|
|
1560
1787
|
}
|