@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.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { createRequire as __cr } from 'node:module'; const require = globalThis.require ?? __cr(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
startAgent
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-Z7VZQCFX.mjs";
|
|
6
|
+
import "./chunk-Y6BUFOWG.mjs";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { existsSync } from "fs";
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,9 @@ var require_dist = __commonJS({
|
|
|
64
64
|
var src_exports2 = {};
|
|
65
65
|
__export2(src_exports2, {
|
|
66
66
|
AGENT_ROOT_SPEC: () => AGENT_ROOT_SPEC2,
|
|
67
|
+
DEV_UPLOADS_DIRNAME: () => DEV_UPLOADS_DIRNAME,
|
|
68
|
+
DEV_UPLOADS_KEEP_COUNT: () => DEV_UPLOADS_KEEP_COUNT,
|
|
69
|
+
DEV_UPLOAD_MANIFEST_FILE: () => DEV_UPLOAD_MANIFEST_FILE,
|
|
67
70
|
HOST_EXTERNAL_SPECIFIERS: () => HOST_EXTERNAL_SPECIFIERS,
|
|
68
71
|
HUB_ROOT_SPEC: () => HUB_ROOT_SPEC,
|
|
69
72
|
RESTART_INTENT_FILE: () => RESTART_INTENT_FILE,
|
|
@@ -73,11 +76,17 @@ var require_dist = __commonJS({
|
|
|
73
76
|
clearRestartIntentMarker: () => clearRestartIntentMarker,
|
|
74
77
|
compareSemver: () => compareSemver,
|
|
75
78
|
detectWorkspaceRoot: () => detectWorkspaceRoot,
|
|
79
|
+
devChannelEpoch: () => devChannelEpoch,
|
|
80
|
+
devUploadManifestPath: () => devUploadManifestPath,
|
|
81
|
+
devUploadVersionDir: () => devUploadVersionDir,
|
|
82
|
+
devUploadsDir: () => devUploadsDir,
|
|
76
83
|
emptyServerRootState: () => emptyServerRootState,
|
|
84
|
+
isDevChannelVersion: () => isDevChannelVersion,
|
|
77
85
|
isHostExternal: () => isHostExternal,
|
|
78
86
|
isServerRootState: () => isServerRootState,
|
|
79
87
|
minNodeMajorOf: () => minNodeMajorOf,
|
|
80
88
|
planBoot: () => planBoot,
|
|
89
|
+
readDevUploadManifest: () => readDevUploadManifest,
|
|
81
90
|
readRestartIntentMarker: () => readRestartIntentMarker,
|
|
82
91
|
readServerRootState: () => readServerRootState,
|
|
83
92
|
registerActiveRootResolver: () => registerActiveRootResolver,
|
|
@@ -90,10 +99,58 @@ var require_dist = __commonJS({
|
|
|
90
99
|
validateVersionDir: () => validateVersionDir,
|
|
91
100
|
versionDir: () => versionDir,
|
|
92
101
|
versionsDir: () => versionsDir,
|
|
102
|
+
writeDevUploadManifest: () => writeDevUploadManifest,
|
|
93
103
|
writeRestartIntentMarker: () => writeRestartIntentMarker2,
|
|
94
104
|
writeServerRootState: () => writeServerRootState
|
|
95
105
|
});
|
|
96
106
|
module2.exports = __toCommonJS2(src_exports2);
|
|
107
|
+
var fs8 = __toESM2(require("fs"));
|
|
108
|
+
var path8 = __toESM2(require("path"));
|
|
109
|
+
var DEV_UPLOADS_DIRNAME = "dev-uploads";
|
|
110
|
+
var DEV_UPLOAD_MANIFEST_FILE = "manifest.json";
|
|
111
|
+
var DEV_UPLOADS_KEEP_COUNT = 3;
|
|
112
|
+
var DEV_CHANNEL_VERSION_RE = /-dev\.\d+$/;
|
|
113
|
+
function isDevChannelVersion(version) {
|
|
114
|
+
return DEV_CHANNEL_VERSION_RE.test(version);
|
|
115
|
+
}
|
|
116
|
+
function devChannelEpoch(version) {
|
|
117
|
+
const match = /-dev\.(\d+)$/.exec(version);
|
|
118
|
+
if (match === null) return null;
|
|
119
|
+
const epoch = Number.parseInt(match[1] ?? "", 10);
|
|
120
|
+
return Number.isNaN(epoch) ? null : epoch;
|
|
121
|
+
}
|
|
122
|
+
function devUploadsDir(rootDir) {
|
|
123
|
+
return path8.join(rootDir, DEV_UPLOADS_DIRNAME);
|
|
124
|
+
}
|
|
125
|
+
function devUploadVersionDir(rootDir, version) {
|
|
126
|
+
return path8.join(devUploadsDir(rootDir), version);
|
|
127
|
+
}
|
|
128
|
+
function devUploadManifestPath(versionDirPath) {
|
|
129
|
+
return path8.join(versionDirPath, DEV_UPLOAD_MANIFEST_FILE);
|
|
130
|
+
}
|
|
131
|
+
function isDevUploadManifest(v) {
|
|
132
|
+
if (typeof v !== "object" || v === null) return false;
|
|
133
|
+
const m = v;
|
|
134
|
+
if (typeof m["version"] !== "string") return false;
|
|
135
|
+
const packages = m["packages"];
|
|
136
|
+
if (typeof packages !== "object" || packages === null || Array.isArray(packages)) return false;
|
|
137
|
+
return Object.values(packages).every((filename) => typeof filename === "string");
|
|
138
|
+
}
|
|
139
|
+
function readDevUploadManifest(versionDirPath) {
|
|
140
|
+
try {
|
|
141
|
+
const raw = JSON.parse(fs8.readFileSync(devUploadManifestPath(versionDirPath), "utf-8"));
|
|
142
|
+
return isDevUploadManifest(raw) ? raw : null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function writeDevUploadManifest(versionDirPath, manifest) {
|
|
148
|
+
fs8.mkdirSync(versionDirPath, { recursive: true });
|
|
149
|
+
const target = devUploadManifestPath(versionDirPath);
|
|
150
|
+
const tmp = `${target}.tmp`;
|
|
151
|
+
fs8.writeFileSync(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
152
|
+
fs8.renameSync(tmp, target);
|
|
153
|
+
}
|
|
97
154
|
var HUB_ROOT_SPEC = {
|
|
98
155
|
packageName: "@camstack/server",
|
|
99
156
|
entryRelPath: ["dist", "launcher.js"]
|
|
@@ -102,8 +159,8 @@ var require_dist = __commonJS({
|
|
|
102
159
|
packageName: "@camstack/agent",
|
|
103
160
|
entryRelPath: ["dist", "cli.js"]
|
|
104
161
|
};
|
|
105
|
-
var
|
|
106
|
-
var
|
|
162
|
+
var fs22 = __toESM2(require("fs"));
|
|
163
|
+
var path22 = __toESM2(require("path"));
|
|
107
164
|
var SERVER_ROOT_DIRNAME = "server-root";
|
|
108
165
|
var SERVER_ROOT_STATE_FILE = "state.json";
|
|
109
166
|
var RESTART_INTENT_FILE = ".restart-intent";
|
|
@@ -117,22 +174,22 @@ var require_dist = __commonJS({
|
|
|
117
174
|
};
|
|
118
175
|
}
|
|
119
176
|
function serverRootDir2(dataDir) {
|
|
120
|
-
return
|
|
177
|
+
return path22.join(dataDir, SERVER_ROOT_DIRNAME);
|
|
121
178
|
}
|
|
122
179
|
function versionsDir(rootDir) {
|
|
123
|
-
return
|
|
180
|
+
return path22.join(rootDir, "versions");
|
|
124
181
|
}
|
|
125
182
|
function versionDir(rootDir, version) {
|
|
126
|
-
return
|
|
183
|
+
return path22.join(versionsDir(rootDir), version);
|
|
127
184
|
}
|
|
128
185
|
function rootPackageDir(versionDirPath, spec) {
|
|
129
|
-
return
|
|
186
|
+
return path22.join(versionDirPath, "node_modules", ...spec.packageName.split("/"));
|
|
130
187
|
}
|
|
131
188
|
function rootEntryPath(versionDirPath, spec) {
|
|
132
|
-
return
|
|
189
|
+
return path22.join(rootPackageDir(versionDirPath, spec), ...spec.entryRelPath);
|
|
133
190
|
}
|
|
134
191
|
function stateFilePath(rootDir) {
|
|
135
|
-
return
|
|
192
|
+
return path22.join(rootDir, SERVER_ROOT_STATE_FILE);
|
|
136
193
|
}
|
|
137
194
|
function isNullableString(v) {
|
|
138
195
|
return v === null || typeof v === "string";
|
|
@@ -159,21 +216,21 @@ var require_dist = __commonJS({
|
|
|
159
216
|
}
|
|
160
217
|
function readServerRootState(rootDir) {
|
|
161
218
|
try {
|
|
162
|
-
const raw = JSON.parse(
|
|
219
|
+
const raw = JSON.parse(fs22.readFileSync(stateFilePath(rootDir), "utf-8"));
|
|
163
220
|
return isServerRootState(raw) ? raw : null;
|
|
164
221
|
} catch {
|
|
165
222
|
return null;
|
|
166
223
|
}
|
|
167
224
|
}
|
|
168
225
|
function writeServerRootState(rootDir, state) {
|
|
169
|
-
|
|
226
|
+
fs22.mkdirSync(rootDir, { recursive: true });
|
|
170
227
|
const target = stateFilePath(rootDir);
|
|
171
228
|
const tmp = `${target}.tmp`;
|
|
172
|
-
|
|
173
|
-
|
|
229
|
+
fs22.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
230
|
+
fs22.renameSync(tmp, target);
|
|
174
231
|
}
|
|
175
232
|
function restartIntentMarkerPath(rootDir) {
|
|
176
|
-
return
|
|
233
|
+
return path22.join(rootDir, RESTART_INTENT_FILE);
|
|
177
234
|
}
|
|
178
235
|
function isRestartIntentMarker(v) {
|
|
179
236
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -181,15 +238,15 @@ var require_dist = __commonJS({
|
|
|
181
238
|
return typeof m["requestedAtMs"] === "number" && typeof m["reason"] === "string";
|
|
182
239
|
}
|
|
183
240
|
function writeRestartIntentMarker2(rootDir, marker) {
|
|
184
|
-
|
|
241
|
+
fs22.mkdirSync(rootDir, { recursive: true });
|
|
185
242
|
const target = restartIntentMarkerPath(rootDir);
|
|
186
243
|
const tmp = `${target}.tmp`;
|
|
187
|
-
|
|
188
|
-
|
|
244
|
+
fs22.writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
|
|
245
|
+
fs22.renameSync(tmp, target);
|
|
189
246
|
}
|
|
190
247
|
function readRestartIntentMarker(rootDir) {
|
|
191
248
|
try {
|
|
192
|
-
const raw = JSON.parse(
|
|
249
|
+
const raw = JSON.parse(fs22.readFileSync(restartIntentMarkerPath(rootDir), "utf-8"));
|
|
193
250
|
return isRestartIntentMarker(raw) ? raw : null;
|
|
194
251
|
} catch {
|
|
195
252
|
return null;
|
|
@@ -197,7 +254,7 @@ var require_dist = __commonJS({
|
|
|
197
254
|
}
|
|
198
255
|
function clearRestartIntentMarker(rootDir) {
|
|
199
256
|
try {
|
|
200
|
-
|
|
257
|
+
fs22.rmSync(restartIntentMarkerPath(rootDir), { force: true });
|
|
201
258
|
} catch {
|
|
202
259
|
}
|
|
203
260
|
}
|
|
@@ -210,13 +267,13 @@ var require_dist = __commonJS({
|
|
|
210
267
|
function validateVersionDir(rootDir, version, nodeMajor, spec) {
|
|
211
268
|
const vDir = versionDir(rootDir, version);
|
|
212
269
|
const entry = rootEntryPath(vDir, spec);
|
|
213
|
-
if (!
|
|
270
|
+
if (!fs22.existsSync(entry)) {
|
|
214
271
|
return `root entry missing: ${entry}`;
|
|
215
272
|
}
|
|
216
|
-
const pkgJsonPath =
|
|
273
|
+
const pkgJsonPath = path22.join(rootPackageDir(vDir, spec), "package.json");
|
|
217
274
|
let pkg;
|
|
218
275
|
try {
|
|
219
|
-
const raw = JSON.parse(
|
|
276
|
+
const raw = JSON.parse(fs22.readFileSync(pkgJsonPath, "utf-8"));
|
|
220
277
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
221
278
|
return `package.json malformed: ${pkgJsonPath}`;
|
|
222
279
|
}
|
|
@@ -363,25 +420,25 @@ var require_dist = __commonJS({
|
|
|
363
420
|
if (pa.prerelease > pb.prerelease) return 1;
|
|
364
421
|
return 0;
|
|
365
422
|
}
|
|
366
|
-
var
|
|
367
|
-
var
|
|
423
|
+
var fs32 = __toESM2(require("fs"));
|
|
424
|
+
var path32 = __toESM2(require("path"));
|
|
368
425
|
function detectWorkspaceRoot(fromDir) {
|
|
369
|
-
let dir =
|
|
426
|
+
let dir = path32.resolve(fromDir);
|
|
370
427
|
for (; ; ) {
|
|
371
|
-
const pkgPath =
|
|
428
|
+
const pkgPath = path32.join(dir, "package.json");
|
|
372
429
|
try {
|
|
373
|
-
const raw = JSON.parse(
|
|
430
|
+
const raw = JSON.parse(fs32.readFileSync(pkgPath, "utf-8"));
|
|
374
431
|
if (typeof raw === "object" && raw !== null && raw["workspaces"] !== void 0) {
|
|
375
432
|
return dir;
|
|
376
433
|
}
|
|
377
434
|
} catch {
|
|
378
435
|
}
|
|
379
|
-
const parent =
|
|
436
|
+
const parent = path32.dirname(dir);
|
|
380
437
|
if (parent === dir) return null;
|
|
381
438
|
dir = parent;
|
|
382
439
|
}
|
|
383
440
|
}
|
|
384
|
-
var
|
|
441
|
+
var path42 = __toESM2(require("path"));
|
|
385
442
|
var import_node_url = require("url");
|
|
386
443
|
var HOST_EXTERNAL_SPECIFIERS = [
|
|
387
444
|
"@camstack/system",
|
|
@@ -409,7 +466,7 @@ var require_dist = __commonJS({
|
|
|
409
466
|
return;
|
|
410
467
|
}
|
|
411
468
|
const anchorURL = (0, import_node_url.pathToFileURL)(
|
|
412
|
-
|
|
469
|
+
path42.join(activeRootDir, "node_modules", "__camstack_starter_anchor__.js")
|
|
413
470
|
).href;
|
|
414
471
|
const hooks = {
|
|
415
472
|
resolve: (specifier, context, nextResolve) => {
|
|
@@ -487,8 +544,8 @@ var require_dist = __commonJS({
|
|
|
487
544
|
options.loadEntry(options.seedEntry);
|
|
488
545
|
}
|
|
489
546
|
var import_node_child_process = require("child_process");
|
|
490
|
-
var
|
|
491
|
-
var
|
|
547
|
+
var fs42 = __toESM2(require("fs"));
|
|
548
|
+
var path52 = __toESM2(require("path"));
|
|
492
549
|
var import_node_util = require("util");
|
|
493
550
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
494
551
|
function buildNpmRegistryArgs(registry) {
|
|
@@ -500,7 +557,7 @@ var require_dist = __commonJS({
|
|
|
500
557
|
var RESTART_REASON_PREFIX = "server-update";
|
|
501
558
|
function readPackageVersion(pkgJsonPath) {
|
|
502
559
|
try {
|
|
503
|
-
const raw = JSON.parse(
|
|
560
|
+
const raw = JSON.parse(fs42.readFileSync(pkgJsonPath, "utf-8"));
|
|
504
561
|
if (typeof raw === "object" && raw !== null) {
|
|
505
562
|
const version = raw["version"];
|
|
506
563
|
if (typeof version === "string") return version;
|
|
@@ -527,7 +584,7 @@ var require_dist = __commonJS({
|
|
|
527
584
|
this.envNames = options.envNames;
|
|
528
585
|
this.logger = options.logger;
|
|
529
586
|
this.restartServerFn = options.restartServer;
|
|
530
|
-
this.dataDir =
|
|
587
|
+
this.dataDir = path52.resolve(options.dataDir);
|
|
531
588
|
this.execNpm = options.execNpm ?? (async (args, opts) => {
|
|
532
589
|
const { stdout } = await execFileAsync("npm", [...args], {
|
|
533
590
|
cwd: opts.cwd,
|
|
@@ -556,7 +613,7 @@ var require_dist = __commonJS({
|
|
|
556
613
|
seedVersion() {
|
|
557
614
|
const seedDir = this.env[this.envNames.seedDir];
|
|
558
615
|
if (seedDir === void 0 || seedDir.length === 0) return null;
|
|
559
|
-
return readPackageVersion(
|
|
616
|
+
return readPackageVersion(path52.join(seedDir, "package.json"));
|
|
560
617
|
}
|
|
561
618
|
rootDir() {
|
|
562
619
|
return serverRootDir2(this.dataDir);
|
|
@@ -574,7 +631,7 @@ var require_dist = __commonJS({
|
|
|
574
631
|
const rootDir = this.rootDir();
|
|
575
632
|
const raw = readServerRootState(rootDir);
|
|
576
633
|
if (raw !== null) return { state: raw, corrupt: false };
|
|
577
|
-
return { state: emptyServerRootState(), corrupt:
|
|
634
|
+
return { state: emptyServerRootState(), corrupt: fs42.existsSync(stateFilePath(rootDir)) };
|
|
578
635
|
}
|
|
579
636
|
updateState(state) {
|
|
580
637
|
if (this.inFlight === "checking") return "checking";
|
|
@@ -714,6 +771,17 @@ var require_dist = __commonJS({
|
|
|
714
771
|
message: `Refused: ${this.spec.packageName}@${target} is already running.`
|
|
715
772
|
};
|
|
716
773
|
}
|
|
774
|
+
if (isDevChannelVersion(target)) {
|
|
775
|
+
const uploadsDir = devUploadVersionDir(this.rootDir(), target);
|
|
776
|
+
if (!fs42.existsSync(uploadsDir)) {
|
|
777
|
+
return {
|
|
778
|
+
accepted: false,
|
|
779
|
+
targetVersion: target,
|
|
780
|
+
restarting: false,
|
|
781
|
+
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.`
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
717
785
|
this.inFlight = "staging";
|
|
718
786
|
try {
|
|
719
787
|
await this.stageAndActivate(target);
|
|
@@ -760,34 +828,40 @@ var require_dist = __commonJS({
|
|
|
760
828
|
async stageAndActivate(target) {
|
|
761
829
|
const rootDir = this.rootDir();
|
|
762
830
|
const vDir = versionsDir(rootDir);
|
|
763
|
-
|
|
764
|
-
const stagingDir =
|
|
765
|
-
|
|
831
|
+
fs42.mkdirSync(vDir, { recursive: true });
|
|
832
|
+
const stagingDir = path52.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`);
|
|
833
|
+
fs42.mkdirSync(stagingDir, { recursive: true });
|
|
766
834
|
try {
|
|
767
|
-
|
|
768
|
-
path42.join(stagingDir, "package.json"),
|
|
769
|
-
JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
|
|
770
|
-
"utf-8"
|
|
771
|
-
);
|
|
835
|
+
const devDir = devUploadVersionDir(rootDir, target);
|
|
772
836
|
const registry = this.env["CAMSTACK_NPM_REGISTRY"];
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
"
|
|
777
|
-
|
|
778
|
-
"
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
837
|
+
const installArgs = ["install", "--omit=dev", "--no-audit", "--no-fund", "--loglevel=error"];
|
|
838
|
+
if (fs42.existsSync(devDir)) {
|
|
839
|
+
fs42.writeFileSync(
|
|
840
|
+
path52.join(stagingDir, "package.json"),
|
|
841
|
+
JSON.stringify(this.buildDevUploadsPackageJson(devDir, target), null, 2),
|
|
842
|
+
"utf-8"
|
|
843
|
+
);
|
|
844
|
+
await this.execNpm([...installArgs, ...buildNpmRegistryArgs(registry)], {
|
|
845
|
+
cwd: stagingDir,
|
|
846
|
+
timeoutMs: NPM_INSTALL_TIMEOUT_MS
|
|
847
|
+
});
|
|
848
|
+
} else {
|
|
849
|
+
fs42.writeFileSync(
|
|
850
|
+
path52.join(stagingDir, "package.json"),
|
|
851
|
+
JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
|
|
852
|
+
"utf-8"
|
|
853
|
+
);
|
|
854
|
+
await this.execNpm(
|
|
855
|
+
[...installArgs, `${this.spec.packageName}@${target}`, ...buildNpmRegistryArgs(registry)],
|
|
856
|
+
{ cwd: stagingDir, timeoutMs: NPM_INSTALL_TIMEOUT_MS }
|
|
857
|
+
);
|
|
858
|
+
}
|
|
785
859
|
const entry = rootEntryPath(stagingDir, this.spec);
|
|
786
|
-
if (!
|
|
860
|
+
if (!fs42.existsSync(entry)) {
|
|
787
861
|
throw new Error(`staged closure is missing the root entry (${entry})`);
|
|
788
862
|
}
|
|
789
863
|
const stagedVersion = readPackageVersion(
|
|
790
|
-
|
|
864
|
+
path52.join(rootPackageDir(stagingDir, this.spec), "package.json")
|
|
791
865
|
);
|
|
792
866
|
if (stagedVersion !== target) {
|
|
793
867
|
throw new Error(
|
|
@@ -795,17 +869,61 @@ var require_dist = __commonJS({
|
|
|
795
869
|
);
|
|
796
870
|
}
|
|
797
871
|
const dest = versionDir(rootDir, target);
|
|
798
|
-
if (
|
|
872
|
+
if (fs42.existsSync(dest)) {
|
|
799
873
|
const aside = `${dest}.evicted-${this.now()}`;
|
|
800
|
-
await
|
|
801
|
-
await
|
|
874
|
+
await fs42.promises.rename(dest, aside);
|
|
875
|
+
await fs42.promises.rm(aside, { recursive: true, force: true }).catch(() => void 0);
|
|
802
876
|
}
|
|
803
|
-
await
|
|
877
|
+
await fs42.promises.rename(stagingDir, dest);
|
|
804
878
|
} catch (err) {
|
|
805
|
-
await
|
|
879
|
+
await fs42.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
|
|
806
880
|
throw err;
|
|
807
881
|
}
|
|
808
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Synthetic staging package.json for the dev server-deploy channel:
|
|
885
|
+
* `dependencies` carries the root package as a `file:` ref to its uploaded
|
|
886
|
+
* tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz
|
|
887
|
+
* so any transitive occurrence resolves to the uploaded copy, never npm.
|
|
888
|
+
* Fail-closed: a missing/invalid manifest, a manifest for a different
|
|
889
|
+
* version, a missing root package, or a listed-but-absent tgz all throw
|
|
890
|
+
* (surfaced by `applyServerUpdate` as a staging failure).
|
|
891
|
+
*/
|
|
892
|
+
buildDevUploadsPackageJson(devDir, target) {
|
|
893
|
+
const manifest = readDevUploadManifest(devDir);
|
|
894
|
+
if (manifest === null) {
|
|
895
|
+
throw new Error(`dev-uploads manifest missing or invalid in ${devDir}`);
|
|
896
|
+
}
|
|
897
|
+
if (manifest.version !== target) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
`dev-uploads manifest version mismatch: dir is for ${target}, manifest says ${manifest.version}`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
const rootTgz = manifest.packages[this.spec.packageName];
|
|
903
|
+
if (rootTgz === void 0) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`dev-uploads for ${target} do not include the root package ${this.spec.packageName}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
const fileRef = (filename) => {
|
|
909
|
+
const abs = path52.join(devDir, filename);
|
|
910
|
+
if (!fs42.existsSync(abs)) {
|
|
911
|
+
throw new Error(`dev-uploads tarball listed in the manifest is missing: ${abs}`);
|
|
912
|
+
}
|
|
913
|
+
return `file:${abs}`;
|
|
914
|
+
};
|
|
915
|
+
const overrides = {};
|
|
916
|
+
for (const [pkgName, filename] of Object.entries(manifest.packages)) {
|
|
917
|
+
if (pkgName === this.spec.packageName) continue;
|
|
918
|
+
overrides[pkgName] = fileRef(filename);
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
name: "camstack-node-root",
|
|
922
|
+
private: true,
|
|
923
|
+
dependencies: { [this.spec.packageName]: fileRef(rootTgz) },
|
|
924
|
+
...Object.keys(overrides).length > 0 ? { overrides } : {}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
809
927
|
// ── Rollback ──────────────────────────────────────────────────────────
|
|
810
928
|
async rollbackServerUpdate() {
|
|
811
929
|
if (this.inFlight !== "idle") {
|
|
@@ -935,6 +1053,7 @@ var require_dist = __commonJS({
|
|
|
935
1053
|
(v) => typeof v === "string"
|
|
936
1054
|
)
|
|
937
1055
|
);
|
|
1056
|
+
this.sweepDevUploads(DEV_UPLOADS_KEEP_COUNT);
|
|
938
1057
|
this.logger.info("root package update confirmed healthy", {
|
|
939
1058
|
meta: {
|
|
940
1059
|
version: promoted.currentVersion,
|
|
@@ -958,23 +1077,23 @@ var require_dist = __commonJS({
|
|
|
958
1077
|
const vDir = versionsDir(this.rootDir());
|
|
959
1078
|
let entries;
|
|
960
1079
|
try {
|
|
961
|
-
entries =
|
|
1080
|
+
entries = fs42.readdirSync(vDir);
|
|
962
1081
|
} catch {
|
|
963
1082
|
return;
|
|
964
1083
|
}
|
|
965
1084
|
for (const entry of entries) {
|
|
966
1085
|
if (keep.includes(entry)) continue;
|
|
967
|
-
const full =
|
|
1086
|
+
const full = path52.join(vDir, entry);
|
|
968
1087
|
if (entry.startsWith(".")) {
|
|
969
1088
|
try {
|
|
970
|
-
const ageMs = this.now() -
|
|
1089
|
+
const ageMs = this.now() - fs42.statSync(full).mtimeMs;
|
|
971
1090
|
if (ageMs < _RootUpdateService.STALE_TRANSIENT_MS) continue;
|
|
972
1091
|
} catch {
|
|
973
1092
|
continue;
|
|
974
1093
|
}
|
|
975
1094
|
}
|
|
976
1095
|
try {
|
|
977
|
-
|
|
1096
|
+
fs42.rmSync(full, { recursive: true, force: true });
|
|
978
1097
|
this.logger.debug("pruned server-root version dir", { meta: { entry } });
|
|
979
1098
|
} catch (err) {
|
|
980
1099
|
this.logger.warn("failed to prune server-root version dir", {
|
|
@@ -983,6 +1102,56 @@ var require_dist = __commonJS({
|
|
|
983
1102
|
}
|
|
984
1103
|
}
|
|
985
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Promote-time sweep of `server-root/dev-uploads/`: keep only the
|
|
1107
|
+
* `keepCount` most recent entries (ordered by the self-describing
|
|
1108
|
+
* `-dev.<epochSeconds>` suffix; non-dev-shaped residue counts as oldest).
|
|
1109
|
+
* Uses the same rename-aside graveyard pattern as the addon installer —
|
|
1110
|
+
* a rename is a metadata op that succeeds even when a tarball is held
|
|
1111
|
+
* open, and residue in the graveyard is re-swept on the next promote.
|
|
1112
|
+
*/
|
|
1113
|
+
sweepDevUploads(keepCount) {
|
|
1114
|
+
const dir = devUploadsDir(this.rootDir());
|
|
1115
|
+
let entries;
|
|
1116
|
+
try {
|
|
1117
|
+
entries = fs42.readdirSync(dir);
|
|
1118
|
+
} catch {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const graveyard = path52.join(dir, ".sweeping");
|
|
1122
|
+
try {
|
|
1123
|
+
for (const residue of fs42.readdirSync(graveyard)) {
|
|
1124
|
+
try {
|
|
1125
|
+
fs42.rmSync(path52.join(graveyard, residue), { recursive: true, force: true });
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
} catch {
|
|
1130
|
+
}
|
|
1131
|
+
const versions = entries.filter((name) => !name.startsWith("."));
|
|
1132
|
+
const byNewestFirst = [...versions].sort(
|
|
1133
|
+
(a, b) => (devChannelEpoch(b) ?? -1) - (devChannelEpoch(a) ?? -1)
|
|
1134
|
+
);
|
|
1135
|
+
const doomed = byNewestFirst.slice(keepCount);
|
|
1136
|
+
if (doomed.length === 0) return;
|
|
1137
|
+
fs42.mkdirSync(graveyard, { recursive: true });
|
|
1138
|
+
for (const entry of doomed) {
|
|
1139
|
+
const aside = path52.join(graveyard, `${entry}-${this.now()}`);
|
|
1140
|
+
try {
|
|
1141
|
+
fs42.renameSync(path52.join(dir, entry), aside);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
this.logger.warn("failed to move dev-uploads entry aside for sweep", {
|
|
1144
|
+
meta: { entry, error: err instanceof Error ? err.message : String(err) }
|
|
1145
|
+
});
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
fs42.rmSync(aside, { recursive: true, force: true });
|
|
1150
|
+
this.logger.debug("swept dev-uploads entry", { meta: { entry } });
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
986
1155
|
/** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */
|
|
987
1156
|
static STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
|
|
988
1157
|
};
|
|
@@ -1681,6 +1850,39 @@ function deriveHubUrlFromRegistry(nodes) {
|
|
|
1681
1850
|
return `https://${host}:${HUB_API_PORT}`;
|
|
1682
1851
|
}
|
|
1683
1852
|
|
|
1853
|
+
// src/agent-http-auth.ts
|
|
1854
|
+
var import_node_crypto3 = require("crypto");
|
|
1855
|
+
function isLoopbackAddress(addr) {
|
|
1856
|
+
if (!addr) return false;
|
|
1857
|
+
if (addr === "::1") return true;
|
|
1858
|
+
const v4 = addr.startsWith("::ffff:") ? addr.slice("::ffff:".length) : addr;
|
|
1859
|
+
const parts = v4.split(".");
|
|
1860
|
+
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
1861
|
+
return parts.every((p) => /^\d{1,3}$/.test(p));
|
|
1862
|
+
}
|
|
1863
|
+
function bearerMatchesSecret(authorization, secret) {
|
|
1864
|
+
if (secret.length === 0) return false;
|
|
1865
|
+
if (!authorization?.startsWith("Bearer ")) return false;
|
|
1866
|
+
const token = Buffer.from(authorization.slice("Bearer ".length));
|
|
1867
|
+
const expected = Buffer.from(secret);
|
|
1868
|
+
return token.length === expected.length && (0, import_node_crypto3.timingSafeEqual)(token, expected);
|
|
1869
|
+
}
|
|
1870
|
+
function isAuthorizedAgentRequest(input, clusterSecret) {
|
|
1871
|
+
if (isLoopbackAddress(input.remoteAddress)) return true;
|
|
1872
|
+
if (clusterSecret === null || clusterSecret.length === 0) return false;
|
|
1873
|
+
return bearerMatchesSecret(input.authorization, clusterSecret);
|
|
1874
|
+
}
|
|
1875
|
+
function isPairingRequest(method, urlPath) {
|
|
1876
|
+
const pathOnly = urlPath.split("?")[0] ?? urlPath;
|
|
1877
|
+
if (method === "GET") {
|
|
1878
|
+
return pathOnly === "/api/agent/status" || pathOnly === "/api/agent/config" || pathOnly === "/api/agent/discovered-nodes";
|
|
1879
|
+
}
|
|
1880
|
+
if (method === "POST") {
|
|
1881
|
+
return pathOnly === "/api/agent/config" || pathOnly === "/api/agent/restart";
|
|
1882
|
+
}
|
|
1883
|
+
return false;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1684
1886
|
// src/agent-http.ts
|
|
1685
1887
|
function compareVersions(a, b) {
|
|
1686
1888
|
if (a === null && b === null) return 0;
|
|
@@ -1769,7 +1971,32 @@ async function createAgentHttpServer(getBroker, config) {
|
|
|
1769
1971
|
const app = (0, import_fastify.default)({ logger: false });
|
|
1770
1972
|
const cors = await import("@fastify/cors");
|
|
1771
1973
|
await app.register(cors.default);
|
|
1974
|
+
const currentSecret = () => {
|
|
1975
|
+
const raw = readConfigFile(config.configPath);
|
|
1976
|
+
return typeof raw.secret === "string" && raw.secret.length > 0 ? raw.secret : null;
|
|
1977
|
+
};
|
|
1978
|
+
app.addHook("onRequest", async (req, reply) => {
|
|
1979
|
+
const pathOnly = req.url.split("?")[0] ?? req.url;
|
|
1980
|
+
const isProtected = pathOnly.startsWith("/api/") || pathOnly.startsWith("/health/");
|
|
1981
|
+
if (!isProtected) return;
|
|
1982
|
+
const secret = currentSecret();
|
|
1983
|
+
const authInput = {
|
|
1984
|
+
remoteAddress: req.socket.remoteAddress ?? void 0,
|
|
1985
|
+
...typeof req.headers.authorization === "string" ? { authorization: req.headers.authorization } : {}
|
|
1986
|
+
};
|
|
1987
|
+
if (isAuthorizedAgentRequest(authInput, secret)) return;
|
|
1988
|
+
if (secret === null && isPairingRequest(req.method, pathOnly)) return;
|
|
1989
|
+
return reply.status(401).send({ ok: false, error: "unauthorized" });
|
|
1990
|
+
});
|
|
1772
1991
|
app.get("/health", async (_req, reply) => {
|
|
1992
|
+
try {
|
|
1993
|
+
await getBroker().call("$agent.health");
|
|
1994
|
+
return { ok: true };
|
|
1995
|
+
} catch {
|
|
1996
|
+
return reply.status(503).send({ ok: false });
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
app.get("/health/details", async (_req, reply) => {
|
|
1773
2000
|
try {
|
|
1774
2001
|
const result = await getBroker().call("$agent.health");
|
|
1775
2002
|
return result;
|