@camstack/system 1.0.6 → 1.0.8
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/addon-runner.js +40 -23
- package/dist/addon-runner.mjs +20 -4
- package/dist/addon-utils.d.ts +20 -0
- package/dist/addon-utils.js +11 -0
- package/dist/addon-utils.mjs +3 -0
- package/dist/builtins/device-manager/device-manager.addon.js +8 -8
- package/dist/builtins/device-manager/device-manager.addon.mjs +8 -8
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +8 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +50 -3
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +50 -3
- package/dist/builtins/platform-probe/index.js +27 -139
- package/dist/builtins/platform-probe/index.mjs +28 -140
- package/dist/builtins/platform-probe/platform-scorer.d.ts +17 -10
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
- package/dist/custom-action-registry-BEXwC-oo.mjs +38 -0
- package/dist/custom-action-registry-vLYEFTtv.js +43 -0
- package/dist/index.js +129 -779
- package/dist/index.mjs +100 -750
- package/dist/kernel/config-manager.d.ts +4 -4
- package/dist/kernel/fs-utils.d.ts +16 -6
- package/dist/kernel/index.d.ts +1 -1
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -1
- package/dist/kernel/moleculer/readiness-context.d.ts +2 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +10 -0
- package/dist/{manifest-python-deps-B4BmMoGT.js → manifest-python-deps-BWURo7dc.js} +62 -88
- package/dist/{manifest-python-deps-CXbKrOdk.mjs → manifest-python-deps-BcrTzHH_.mjs} +55 -75
- package/dist/model-download-service-C7AjBsX9.mjs +668 -0
- package/dist/model-download-service-JtVQtbb6.js +752 -0
- package/dist/process/resource-monitor.d.ts +9 -0
- package/dist/{resource-monitor-ClDGFyf6.mjs → resource-monitor-BkP504Vq.mjs} +20 -1
- package/dist/{resource-monitor-IIEanuJt.js → resource-monitor-DNNomR-i.js} +21 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const
|
|
3
|
+
const require_model_download_service = require("./model-download-service-JtVQtbb6.js");
|
|
4
|
+
const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
|
|
4
5
|
const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
|
|
5
6
|
const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
|
|
6
7
|
const require_builtins_sqlite_storage_index = require("./builtins/sqlite-storage/index.js");
|
|
@@ -19,7 +20,8 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
|
|
|
19
20
|
require("./builtins/local-auth/index.js");
|
|
20
21
|
const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
|
|
21
22
|
require("./builtins/device-manager/index.js");
|
|
22
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
23
|
+
const require_manifest_python_deps = require("./manifest-python-deps-BWURo7dc.js");
|
|
24
|
+
const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
|
|
23
25
|
const require_graceful_fs$1 = require("./graceful-fs-lg19SZNz.js");
|
|
24
26
|
let _camstack_types_node = require("@camstack/types/node");
|
|
25
27
|
let node_http = require("node:http");
|
|
@@ -39,6 +41,7 @@ let node_os = require("node:os");
|
|
|
39
41
|
node_os = require_chunk.__toESM(node_os);
|
|
40
42
|
let node_fs_promises = require("node:fs/promises");
|
|
41
43
|
let zod = require("zod");
|
|
44
|
+
let _camstack_types_addon = require("@camstack/types/addon");
|
|
42
45
|
let fs = require("fs");
|
|
43
46
|
fs = require_chunk.__toESM(fs, 1);
|
|
44
47
|
let node_events = require("node:events");
|
|
@@ -79,216 +82,6 @@ var EventBus = class {
|
|
|
79
82
|
}
|
|
80
83
|
};
|
|
81
84
|
//#endregion
|
|
82
|
-
//#region src/http/authenticated-file-server.ts
|
|
83
|
-
/**
|
|
84
|
-
* Authenticated data-plane file-server — a shared host primitive for addons that
|
|
85
|
-
* must serve files (media segments, scrub-frame stores, exported clips) directly
|
|
86
|
-
* to browsers/players, node-direct (NOT through the hub tRPC, per the recording
|
|
87
|
-
* design's "no media bytes traverse the hub").
|
|
88
|
-
*
|
|
89
|
-
* It bundles the three things every such server needs so addons don't re-roll
|
|
90
|
-
* them: CORS (the data plane is cross-origin from the admin-ui — a native
|
|
91
|
-
* `<video src>` is exempt but hls.js / WebCodecs fetch via XHR and are blocked
|
|
92
|
-
* without it), HTTP `Range` (seeking / partial fetch), and a SCOPED TOKEN check.
|
|
93
|
-
*
|
|
94
|
-
* The token rides as the FIRST path segment (`<urlPrefix><token>/<relPath>`) so
|
|
95
|
-
* a player resolving a manifest's RELATIVE child URIs carries it on every
|
|
96
|
-
* sub-request automatically — no per-URI rewriting, no cookies/headers a dumb
|
|
97
|
-
* player can't set. The caller supplies `verifyToken(token, relPath)`; the
|
|
98
|
-
* primitive stays auth-scheme-agnostic.
|
|
99
|
-
*
|
|
100
|
-
* Pure helpers (`parseTokenizedUrl`, `resolveFilePath`, `contentTypeFor`,
|
|
101
|
-
* `parseRangeHeader`) are exported for unit testing; `createAuthenticatedFileServer`
|
|
102
|
-
* is the thin `node:http` glue.
|
|
103
|
-
*/
|
|
104
|
-
function corsHeaders(origin) {
|
|
105
|
-
return {
|
|
106
|
-
"access-control-allow-origin": origin,
|
|
107
|
-
"access-control-allow-methods": "GET, HEAD, OPTIONS",
|
|
108
|
-
"access-control-allow-headers": "range",
|
|
109
|
-
"access-control-expose-headers": "content-length, content-range, accept-ranges",
|
|
110
|
-
"access-control-max-age": "86400"
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Split a request URL (`<urlPrefix><token>/<relPath>`) into its token + rel
|
|
115
|
-
* path. Returns null for anything outside the prefix or missing either part.
|
|
116
|
-
* `relPath` is URL-decoded; a decode failure → null.
|
|
117
|
-
*/
|
|
118
|
-
function parseTokenizedUrl(urlPrefix, urlPath) {
|
|
119
|
-
if (!urlPath.startsWith(urlPrefix)) return null;
|
|
120
|
-
const after = urlPath.slice(urlPrefix.length);
|
|
121
|
-
const slash = after.indexOf("/");
|
|
122
|
-
if (slash <= 0) return null;
|
|
123
|
-
const token = after.slice(0, slash);
|
|
124
|
-
let rel;
|
|
125
|
-
try {
|
|
126
|
-
rel = decodeURIComponent(after.slice(slash + 1));
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
if (rel.length === 0) return null;
|
|
131
|
-
return {
|
|
132
|
-
token,
|
|
133
|
-
rel
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
138
|
-
* path stays within (traversal guard). The handler serves the first candidate
|
|
139
|
-
* that exists. Rejects a path that escapes every root.
|
|
140
|
-
*/
|
|
141
|
-
function resolveFilePath(roots, rel) {
|
|
142
|
-
if (rel.length === 0) return { error: "forbidden" };
|
|
143
|
-
const candidates = [];
|
|
144
|
-
for (const rootDir of roots) {
|
|
145
|
-
const root = node_path.default.resolve(rootDir);
|
|
146
|
-
const abs = node_path.default.resolve(root, rel);
|
|
147
|
-
if (abs === root || abs.startsWith(root + node_path.default.sep)) candidates.push(abs);
|
|
148
|
-
}
|
|
149
|
-
if (candidates.length === 0) return { error: "forbidden" };
|
|
150
|
-
return { candidates };
|
|
151
|
-
}
|
|
152
|
-
var DEFAULT_CONTENT_TYPES = {
|
|
153
|
-
".m3u8": "application/vnd.apple.mpegurl",
|
|
154
|
-
".m4s": "video/mp4",
|
|
155
|
-
".mp4": "video/mp4",
|
|
156
|
-
".idx": "application/octet-stream",
|
|
157
|
-
".html": "text/html; charset=utf-8",
|
|
158
|
-
".js": "application/javascript; charset=utf-8",
|
|
159
|
-
".mjs": "application/javascript; charset=utf-8",
|
|
160
|
-
".css": "text/css; charset=utf-8",
|
|
161
|
-
".json": "application/json; charset=utf-8",
|
|
162
|
-
".map": "application/json; charset=utf-8",
|
|
163
|
-
".svg": "image/svg+xml",
|
|
164
|
-
".png": "image/png",
|
|
165
|
-
".jpg": "image/jpeg",
|
|
166
|
-
".jpeg": "image/jpeg",
|
|
167
|
-
".ico": "image/x-icon",
|
|
168
|
-
".woff2": "font/woff2",
|
|
169
|
-
".woff": "font/woff",
|
|
170
|
-
".ttf": "font/ttf"
|
|
171
|
-
};
|
|
172
|
-
function contentTypeFor(filePath, overrides) {
|
|
173
|
-
const ext = node_path.default.extname(filePath).toLowerCase();
|
|
174
|
-
return overrides?.[ext] ?? DEFAULT_CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
175
|
-
}
|
|
176
|
-
/** Parse an HTTP `Range` header against a known size. Null = serve whole file. */
|
|
177
|
-
function parseRangeHeader(header, size) {
|
|
178
|
-
if (!header) return null;
|
|
179
|
-
const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
180
|
-
if (!m) return null;
|
|
181
|
-
const [, rawStart, rawEnd] = m;
|
|
182
|
-
if (rawStart === "" && rawEnd === "") return null;
|
|
183
|
-
let start;
|
|
184
|
-
let end;
|
|
185
|
-
if (rawStart === "") {
|
|
186
|
-
const n = Number(rawEnd);
|
|
187
|
-
if (n <= 0) return null;
|
|
188
|
-
start = Math.max(0, size - n);
|
|
189
|
-
end = size - 1;
|
|
190
|
-
} else {
|
|
191
|
-
start = Number(rawStart);
|
|
192
|
-
end = rawEnd === "" ? size - 1 : Number(rawEnd);
|
|
193
|
-
}
|
|
194
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || start >= size) return null;
|
|
195
|
-
return {
|
|
196
|
-
start,
|
|
197
|
-
end: Math.min(end, size - 1)
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Start an authenticated file-server. Returns the bound port so the caller can
|
|
202
|
-
* build URLs. `getRoots`/`verifyToken` are invoked per request, so they always
|
|
203
|
-
* reflect the live config.
|
|
204
|
-
*/
|
|
205
|
-
async function createAuthenticatedFileServer(opts) {
|
|
206
|
-
const cors = corsHeaders(opts.corsOrigin ?? "*");
|
|
207
|
-
const server = (0, node_http.createServer)((req, res) => {
|
|
208
|
-
handle(opts, cors, req, res);
|
|
209
|
-
});
|
|
210
|
-
await new Promise((resolve, reject) => {
|
|
211
|
-
server.once("error", reject);
|
|
212
|
-
server.listen(opts.port, () => resolve());
|
|
213
|
-
});
|
|
214
|
-
const addr = server.address();
|
|
215
|
-
return {
|
|
216
|
-
port: typeof addr === "object" && addr ? addr.port : opts.port,
|
|
217
|
-
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
async function handle(opts, cors, req, res) {
|
|
221
|
-
if (req.method === "OPTIONS") {
|
|
222
|
-
res.writeHead(204, cors).end();
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
226
|
-
res.writeHead(405, cors).end();
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const urlPath = (req.url ?? "").split("?")[0] ?? "";
|
|
230
|
-
const parsed = parseTokenizedUrl(opts.urlPrefix, urlPath);
|
|
231
|
-
if (!parsed) {
|
|
232
|
-
res.writeHead(403, cors).end();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (!opts.verifyToken(parsed.token, parsed.rel)) {
|
|
236
|
-
res.writeHead(401, cors).end();
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const resolved = resolveFilePath(opts.getRoots(), parsed.rel);
|
|
240
|
-
if ("error" in resolved) {
|
|
241
|
-
res.writeHead(403, cors).end();
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
let absPath = null;
|
|
245
|
-
let size = 0;
|
|
246
|
-
for (const candidate of resolved.candidates) try {
|
|
247
|
-
const st = await node_fs.promises.stat(candidate);
|
|
248
|
-
if (st.isFile()) {
|
|
249
|
-
absPath = candidate;
|
|
250
|
-
size = st.size;
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
} catch {}
|
|
254
|
-
if (absPath === null) {
|
|
255
|
-
res.writeHead(404, cors).end();
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const baseHeaders = {
|
|
259
|
-
...cors,
|
|
260
|
-
"content-type": contentTypeFor(absPath, opts.contentTypes),
|
|
261
|
-
"accept-ranges": "bytes",
|
|
262
|
-
"cache-control": "no-cache"
|
|
263
|
-
};
|
|
264
|
-
const range = parseRangeHeader(req.headers.range, size);
|
|
265
|
-
if (range) {
|
|
266
|
-
res.writeHead(206, {
|
|
267
|
-
...baseHeaders,
|
|
268
|
-
"content-range": `bytes ${range.start}-${range.end}/${size}`,
|
|
269
|
-
"content-length": String(range.end - range.start + 1)
|
|
270
|
-
});
|
|
271
|
-
if (req.method === "HEAD") {
|
|
272
|
-
res.end();
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
(0, node_fs.createReadStream)(absPath, {
|
|
276
|
-
start: range.start,
|
|
277
|
-
end: range.end
|
|
278
|
-
}).pipe(res);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
res.writeHead(200, {
|
|
282
|
-
...baseHeaders,
|
|
283
|
-
"content-length": String(size)
|
|
284
|
-
});
|
|
285
|
-
if (req.method === "HEAD") {
|
|
286
|
-
res.end();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
(0, node_fs.createReadStream)(absPath).pipe(res);
|
|
290
|
-
}
|
|
291
|
-
//#endregion
|
|
292
85
|
//#region src/http/data-plane-registry.ts
|
|
293
86
|
function trimSlashes(s) {
|
|
294
87
|
return s.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
@@ -389,459 +182,8 @@ function proxyToUpstream(opts) {
|
|
|
389
182
|
clientReq.pipe(upstream);
|
|
390
183
|
}
|
|
391
184
|
//#endregion
|
|
392
|
-
//#region src/http/file-data-plane.ts
|
|
393
|
-
/**
|
|
394
|
-
* A ready-made data-plane request handler that serves files from a set of roots
|
|
395
|
-
* with HTTP `Range`, for addons whose data-plane is "stream files off disk"
|
|
396
|
-
* (recording playback, scrub-frame stores, exported clips).
|
|
397
|
-
*
|
|
398
|
-
* This is the addon-side handler the addon hands to `ctx.dataPlane.serve({ handler })`.
|
|
399
|
-
* It receives the REAL Node `req`/`res`, resolves the request path against the
|
|
400
|
-
* addon's roots (traversal-guarded), and streams the file. NO token and NO CORS:
|
|
401
|
-
* the hub already authenticated the caller and the data plane is same-origin
|
|
402
|
-
* through the hub's port. Reuses the shared Range/content-type/traversal helpers
|
|
403
|
-
* so there is one implementation across the standalone file-server and this.
|
|
404
|
-
*/
|
|
405
|
-
/** Build a `(req, res)` handler that serves `getRoots()` files with Range. */
|
|
406
|
-
function createFileDataPlaneHandler(opts) {
|
|
407
|
-
return async (req, res) => {
|
|
408
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
409
|
-
res.writeHead(405).end();
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
const urlPath = (req.url ?? "/").split("?")[0] ?? "/";
|
|
413
|
-
let rel;
|
|
414
|
-
try {
|
|
415
|
-
rel = decodeURIComponent(urlPath.replace(/^\/+/, ""));
|
|
416
|
-
} catch {
|
|
417
|
-
res.writeHead(400).end();
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (rel.length === 0) {
|
|
421
|
-
res.writeHead(404).end();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const resolved = resolveFilePath(opts.getRoots(), rel);
|
|
425
|
-
if ("error" in resolved) {
|
|
426
|
-
res.writeHead(403).end();
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
let absPath = null;
|
|
430
|
-
let size = 0;
|
|
431
|
-
for (const candidate of resolved.candidates) try {
|
|
432
|
-
const st = await node_fs.promises.stat(candidate);
|
|
433
|
-
if (st.isFile()) {
|
|
434
|
-
absPath = candidate;
|
|
435
|
-
size = st.size;
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
} catch {}
|
|
439
|
-
if (absPath === null) {
|
|
440
|
-
res.writeHead(404).end();
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
const baseHeaders = {
|
|
444
|
-
"content-type": contentTypeFor(absPath, opts.contentTypes),
|
|
445
|
-
"accept-ranges": "bytes",
|
|
446
|
-
"cache-control": "no-cache"
|
|
447
|
-
};
|
|
448
|
-
const range = parseRangeHeader(req.headers.range, size);
|
|
449
|
-
if (range) {
|
|
450
|
-
res.writeHead(206, {
|
|
451
|
-
...baseHeaders,
|
|
452
|
-
"content-range": `bytes ${range.start}-${range.end}/${size}`,
|
|
453
|
-
"content-length": String(range.end - range.start + 1)
|
|
454
|
-
});
|
|
455
|
-
if (req.method === "HEAD") {
|
|
456
|
-
res.end();
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
(0, node_fs.createReadStream)(absPath, {
|
|
460
|
-
start: range.start,
|
|
461
|
-
end: range.end
|
|
462
|
-
}).pipe(res);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
res.writeHead(200, {
|
|
466
|
-
...baseHeaders,
|
|
467
|
-
"content-length": String(size)
|
|
468
|
-
});
|
|
469
|
-
if (req.method === "HEAD") {
|
|
470
|
-
res.end();
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
(0, node_fs.createReadStream)(absPath).pipe(res);
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
//#endregion
|
|
477
|
-
//#region src/download/model-downloader.ts
|
|
478
|
-
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
479
|
-
function buildHeaders(url) {
|
|
480
|
-
const headers = { "User-Agent": "CamStack/1.0" };
|
|
481
|
-
const hfToken = process.env["HF_TOKEN"] ?? process.env["HUGGING_FACE_HUB_TOKEN"];
|
|
482
|
-
if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
|
|
483
|
-
return headers;
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Download a single file from a URL to a destination path.
|
|
487
|
-
* Uses native fetch() (Node 22+) which handles redirects natively.
|
|
488
|
-
* Streams to disk with optional progress callback.
|
|
489
|
-
* Returns the destination path. Skips download if file already exists.
|
|
490
|
-
*/
|
|
491
|
-
async function downloadFile(url, destPath, onProgress) {
|
|
492
|
-
if (node_fs.existsSync(destPath)) return destPath;
|
|
493
|
-
node_fs.mkdirSync(node_path.dirname(destPath), { recursive: true });
|
|
494
|
-
const tmpPath = destPath + ".downloading";
|
|
495
|
-
try {
|
|
496
|
-
const response = await fetch(url, {
|
|
497
|
-
redirect: "follow",
|
|
498
|
-
headers: buildHeaders(url)
|
|
499
|
-
});
|
|
500
|
-
if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
|
|
501
|
-
if (!response.body) throw new Error(`No response body from ${url}`);
|
|
502
|
-
const total = parseInt(response.headers.get("content-length") ?? "0", 10);
|
|
503
|
-
let downloaded = 0;
|
|
504
|
-
const fileStream = node_fs.createWriteStream(tmpPath);
|
|
505
|
-
const reader = response.body.getReader();
|
|
506
|
-
try {
|
|
507
|
-
for (;;) {
|
|
508
|
-
const { done, value } = await reader.read();
|
|
509
|
-
if (done || !value) break;
|
|
510
|
-
fileStream.write(value);
|
|
511
|
-
downloaded += value.length;
|
|
512
|
-
onProgress?.(downloaded, total);
|
|
513
|
-
}
|
|
514
|
-
} finally {
|
|
515
|
-
fileStream.end();
|
|
516
|
-
await new Promise((resolve, reject) => {
|
|
517
|
-
fileStream.on("finish", resolve);
|
|
518
|
-
fileStream.on("error", reject);
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
node_fs.renameSync(tmpPath, destPath);
|
|
522
|
-
return destPath;
|
|
523
|
-
} catch (err) {
|
|
524
|
-
try {
|
|
525
|
-
node_fs.unlinkSync(tmpPath);
|
|
526
|
-
} catch {}
|
|
527
|
-
throw err;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Fetch JSON from a URL using native fetch().
|
|
532
|
-
*/
|
|
533
|
-
async function fetchJson(url) {
|
|
534
|
-
const response = await fetch(url, {
|
|
535
|
-
redirect: "follow",
|
|
536
|
-
headers: buildHeaders(url)
|
|
537
|
-
});
|
|
538
|
-
if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
539
|
-
return response.json();
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Download a model with fallback URLs and optional SHA256 verification.
|
|
543
|
-
* Legacy API preserved for backward compatibility -- delegates to downloadFile().
|
|
544
|
-
*/
|
|
545
|
-
async function downloadModel(options) {
|
|
546
|
-
const { url, fallbackUrls = [], destDir, filename, expectedSha256, onProgress } = options;
|
|
547
|
-
const fname = filename ?? url.split("/").pop() ?? "model.bin";
|
|
548
|
-
const destPath = node_path.join(destDir, fname);
|
|
549
|
-
if (node_fs.existsSync(destPath)) return {
|
|
550
|
-
filePath: destPath,
|
|
551
|
-
downloadedBytes: 0,
|
|
552
|
-
fromCache: true
|
|
553
|
-
};
|
|
554
|
-
node_fs.mkdirSync(destDir, { recursive: true });
|
|
555
|
-
const urls = [url, ...fallbackUrls];
|
|
556
|
-
let lastError = null;
|
|
557
|
-
for (const tryUrl of urls) try {
|
|
558
|
-
await downloadFile(tryUrl, destPath, onProgress);
|
|
559
|
-
if (expectedSha256) {
|
|
560
|
-
const hash = await computeSha256(destPath);
|
|
561
|
-
if (hash !== expectedSha256) {
|
|
562
|
-
node_fs.unlinkSync(destPath);
|
|
563
|
-
throw new Error(`SHA256 mismatch: expected ${expectedSha256}, got ${hash}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return {
|
|
567
|
-
filePath: destPath,
|
|
568
|
-
downloadedBytes: node_fs.statSync(destPath).size,
|
|
569
|
-
fromCache: false
|
|
570
|
-
};
|
|
571
|
-
} catch (e) {
|
|
572
|
-
lastError = e;
|
|
573
|
-
if (node_fs.existsSync(destPath)) node_fs.unlinkSync(destPath);
|
|
574
|
-
}
|
|
575
|
-
throw lastError ?? /* @__PURE__ */ new Error(`Failed to download model from ${url}`);
|
|
576
|
-
}
|
|
577
|
-
async function computeSha256(filePath) {
|
|
578
|
-
return new Promise((resolve, reject) => {
|
|
579
|
-
const hash = (0, node_crypto.createHash)("sha256");
|
|
580
|
-
const stream = node_fs.createReadStream(filePath);
|
|
581
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
582
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
583
|
-
stream.on("error", reject);
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Download every file in a HuggingFace directory bundle (e.g.,
|
|
588
|
-
* `.mlpackage` / OpenVINO IR pair) atomically. `knownFiles` lists the
|
|
589
|
-
* relative paths inside the directory; the function fetches each from
|
|
590
|
-
* `${url}/${file}` and renames the staging directory only on full
|
|
591
|
-
* success. Mirrors `ModelDownloadService.downloadDirectory` but
|
|
592
|
-
* exposed as a standalone for catalog-less callers.
|
|
593
|
-
*/
|
|
594
|
-
async function downloadDirectory(url, destDir, knownFiles, onProgress) {
|
|
595
|
-
const match = url.match(/huggingface\.co\/([^/]+\/[^/]+)\/resolve\/main\/(.+)/);
|
|
596
|
-
if (!match) throw new Error(`Cannot parse HuggingFace URL: ${url}`);
|
|
597
|
-
const [, repo, dirPath] = match;
|
|
598
|
-
const files = (knownFiles ?? []).map((f) => ({
|
|
599
|
-
relativePath: f,
|
|
600
|
-
fileUrl: `https://huggingface.co/${repo}/resolve/main/${dirPath}/${f}`
|
|
601
|
-
}));
|
|
602
|
-
if (files.length === 0) throw new Error(`Directory bundle requires explicit \`files\` list (got none for ${url})`);
|
|
603
|
-
const tmpDir = destDir + ".downloading";
|
|
604
|
-
node_fs.rmSync(tmpDir, {
|
|
605
|
-
recursive: true,
|
|
606
|
-
force: true
|
|
607
|
-
});
|
|
608
|
-
node_fs.mkdirSync(tmpDir, { recursive: true });
|
|
609
|
-
let totalDownloaded = 0;
|
|
610
|
-
try {
|
|
611
|
-
for (const file of files) {
|
|
612
|
-
const destPath = node_path.join(tmpDir, file.relativePath);
|
|
613
|
-
node_fs.mkdirSync(node_path.dirname(destPath), { recursive: true });
|
|
614
|
-
await downloadFile(file.fileUrl, destPath, (downloaded, _total) => {
|
|
615
|
-
onProgress?.(totalDownloaded + downloaded, void 0);
|
|
616
|
-
});
|
|
617
|
-
totalDownloaded += node_fs.statSync(destPath).size;
|
|
618
|
-
}
|
|
619
|
-
node_fs.rmSync(destDir, {
|
|
620
|
-
recursive: true,
|
|
621
|
-
force: true
|
|
622
|
-
});
|
|
623
|
-
node_fs.renameSync(tmpDir, destDir);
|
|
624
|
-
} catch (err) {
|
|
625
|
-
node_fs.rmSync(tmpDir, {
|
|
626
|
-
recursive: true,
|
|
627
|
-
force: true
|
|
628
|
-
});
|
|
629
|
-
throw err;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Resolve a `ModelCatalogEntry` against `modelsDir`: download model file
|
|
634
|
-
* (or directory bundle) + extra files (labels JSON, charset dict, …),
|
|
635
|
-
* skip if already on disk. Returns the local model path.
|
|
636
|
-
*/
|
|
637
|
-
async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
638
|
-
const formatEntry = entry.formats[format];
|
|
639
|
-
if (!formatEntry) throw new Error(`Model "${entry.id}" has no ${format} format. Available: ${Object.keys(entry.formats).join(", ")}`);
|
|
640
|
-
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, node_path.join(modelsDir, extra.filename));
|
|
641
|
-
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
642
|
-
const modelPath = node_path.join(modelsDir, filename);
|
|
643
|
-
if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory && !node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
|
|
644
|
-
recursive: true,
|
|
645
|
-
force: true
|
|
646
|
-
});
|
|
647
|
-
else return modelPath;
|
|
648
|
-
node_fs.mkdirSync(modelsDir, { recursive: true });
|
|
649
|
-
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
650
|
-
else await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
651
|
-
return modelPath;
|
|
652
|
-
}
|
|
653
|
-
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
654
|
-
function getModelFilePath(modelsDir, entry, format) {
|
|
655
|
-
const formatEntry = entry.formats[format];
|
|
656
|
-
if (!formatEntry) return null;
|
|
657
|
-
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
658
|
-
return node_path.join(modelsDir, filename);
|
|
659
|
-
}
|
|
660
|
-
/** True iff the model file (or `Manifest.json` for directory bundles) exists and is non-empty. */
|
|
661
|
-
function isModelDownloaded(modelsDir, entry, format) {
|
|
662
|
-
const formatEntry = entry.formats[format];
|
|
663
|
-
if (!formatEntry) return false;
|
|
664
|
-
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
665
|
-
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
666
|
-
if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
|
|
667
|
-
return node_fs.statSync(modelPath).size > 0;
|
|
668
|
-
}
|
|
669
|
-
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
670
|
-
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
671
|
-
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
672
|
-
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
673
|
-
if (entry.formats[format]?.isDirectory) node_fs.rmSync(modelPath, {
|
|
674
|
-
recursive: true,
|
|
675
|
-
force: true
|
|
676
|
-
});
|
|
677
|
-
else node_fs.unlinkSync(modelPath);
|
|
678
|
-
return true;
|
|
679
|
-
}
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region src/download/model-download-service.ts
|
|
682
|
-
/**
|
|
683
|
-
* Unified model download service.
|
|
684
|
-
*
|
|
685
|
-
* Handles downloading model files and extra files (labels, dicts) from a
|
|
686
|
-
* catalog of ModelCatalogEntry items. Supports single-file models and
|
|
687
|
-
* directory bundles (e.g., .mlpackage for CoreML).
|
|
688
|
-
*
|
|
689
|
-
* Addons use this via `context.models.ensure(modelId, format)`.
|
|
690
|
-
*/
|
|
691
|
-
var ModelDownloadService = class {
|
|
692
|
-
modelsDir;
|
|
693
|
-
onProgress;
|
|
694
|
-
catalog;
|
|
695
|
-
constructor(modelsDir, catalog, onProgress) {
|
|
696
|
-
this.modelsDir = modelsDir;
|
|
697
|
-
this.onProgress = onProgress;
|
|
698
|
-
const map = /* @__PURE__ */ new Map();
|
|
699
|
-
for (const entry of catalog) map.set(entry.id, entry);
|
|
700
|
-
this.catalog = map;
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Ensure a model (and its extra files) is downloaded.
|
|
704
|
-
* Returns the local filesystem path to the model file/directory.
|
|
705
|
-
*/
|
|
706
|
-
async ensure(modelId, format) {
|
|
707
|
-
const entry = this.catalog.get(modelId);
|
|
708
|
-
if (!entry) throw new Error(`ModelDownloadService: unknown model "${modelId}"`);
|
|
709
|
-
const selectedFormat = format ?? this.pickDefaultFormat(entry);
|
|
710
|
-
const formatEntry = entry.formats[selectedFormat];
|
|
711
|
-
if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
|
|
712
|
-
await this.ensureExtraFiles(modelId);
|
|
713
|
-
const modelPath = this.modelFilePath(entry, selectedFormat);
|
|
714
|
-
if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory) if (!node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
|
|
715
|
-
recursive: true,
|
|
716
|
-
force: true
|
|
717
|
-
});
|
|
718
|
-
else return modelPath;
|
|
719
|
-
else return modelPath;
|
|
720
|
-
node_fs.mkdirSync(this.modelsDir, { recursive: true });
|
|
721
|
-
if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
|
|
722
|
-
else await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
|
|
723
|
-
return modelPath;
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Ensure extra files for a model are downloaded.
|
|
727
|
-
* Returns the local paths of all extra files.
|
|
728
|
-
*/
|
|
729
|
-
async ensureExtraFiles(modelId) {
|
|
730
|
-
const entry = this.catalog.get(modelId);
|
|
731
|
-
if (!entry) throw new Error(`ModelDownloadService: unknown model "${modelId}"`);
|
|
732
|
-
const extras = entry.extraFiles;
|
|
733
|
-
if (!extras || extras.length === 0) return [];
|
|
734
|
-
const paths = [];
|
|
735
|
-
for (const extra of extras) {
|
|
736
|
-
const destPath = node_path.join(this.modelsDir, extra.filename);
|
|
737
|
-
await downloadFile(extra.url, destPath);
|
|
738
|
-
paths.push(destPath);
|
|
739
|
-
}
|
|
740
|
-
return paths;
|
|
741
|
-
}
|
|
742
|
-
/** Absolute path to the shared models directory. */
|
|
743
|
-
getModelsDir() {
|
|
744
|
-
return this.modelsDir;
|
|
745
|
-
}
|
|
746
|
-
/** Check if a model file is already present on disk. */
|
|
747
|
-
isDownloaded(modelId, format) {
|
|
748
|
-
const entry = this.catalog.get(modelId);
|
|
749
|
-
if (!entry) return false;
|
|
750
|
-
const selectedFormat = format ?? this.pickDefaultFormat(entry);
|
|
751
|
-
const formatEntry = entry.formats[selectedFormat];
|
|
752
|
-
if (!formatEntry) return false;
|
|
753
|
-
const modelPath = this.modelFilePath(entry, selectedFormat);
|
|
754
|
-
if (!node_fs.existsSync(modelPath)) return false;
|
|
755
|
-
if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
|
|
756
|
-
return node_fs.statSync(modelPath).size > 0;
|
|
757
|
-
}
|
|
758
|
-
/** Get the catalog entry for a model by ID. */
|
|
759
|
-
getEntry(modelId) {
|
|
760
|
-
return this.catalog.get(modelId);
|
|
761
|
-
}
|
|
762
|
-
pickDefaultFormat(entry) {
|
|
763
|
-
for (const fmt of [
|
|
764
|
-
"onnx",
|
|
765
|
-
"coreml",
|
|
766
|
-
"openvino",
|
|
767
|
-
"tflite",
|
|
768
|
-
"pt"
|
|
769
|
-
]) if (entry.formats[fmt]) return fmt;
|
|
770
|
-
const first = Object.keys(entry.formats)[0];
|
|
771
|
-
if (first) return first;
|
|
772
|
-
throw new Error(`ModelDownloadService: model "${entry.id}" has no formats`);
|
|
773
|
-
}
|
|
774
|
-
modelFilePath(entry, format) {
|
|
775
|
-
const formatEntry = entry.formats[format];
|
|
776
|
-
if (!formatEntry) throw new Error(`Model ${entry.id} has no ${format} format`);
|
|
777
|
-
const urlParts = formatEntry.url.split("/");
|
|
778
|
-
const filename = urlParts[urlParts.length - 1] ?? `${entry.id}.${format}`;
|
|
779
|
-
return node_path.join(this.modelsDir, filename);
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Download a directory bundle (e.g., .mlpackage) from HuggingFace.
|
|
783
|
-
* ATOMIC: downloads to temp dir, renames only on complete success.
|
|
784
|
-
*/
|
|
785
|
-
async downloadDirectory(url, destDir, knownFiles, _modelId) {
|
|
786
|
-
const match = url.match(/huggingface\.co\/([^/]+\/[^/]+)\/resolve\/main\/(.+)/);
|
|
787
|
-
if (!match) throw new Error(`Cannot parse HuggingFace URL: ${url}`);
|
|
788
|
-
const [, repo, dirPath] = match;
|
|
789
|
-
let filesToDownload;
|
|
790
|
-
if (knownFiles && knownFiles.length > 0) filesToDownload = knownFiles.map((f) => ({
|
|
791
|
-
relativePath: f,
|
|
792
|
-
fileUrl: `https://huggingface.co/${repo}/resolve/main/${dirPath}/${f}`
|
|
793
|
-
}));
|
|
794
|
-
else {
|
|
795
|
-
const hfFiles = await this.listHfFiles(repo, dirPath);
|
|
796
|
-
if (hfFiles.length === 0) throw new Error(`No files found in HuggingFace directory: ${dirPath}`);
|
|
797
|
-
filesToDownload = hfFiles.map((f) => ({
|
|
798
|
-
relativePath: f.path.substring(dirPath.length + 1),
|
|
799
|
-
fileUrl: `https://huggingface.co/${repo}/resolve/main/${f.path}`
|
|
800
|
-
}));
|
|
801
|
-
}
|
|
802
|
-
const tmpDir = destDir + ".downloading";
|
|
803
|
-
node_fs.rmSync(tmpDir, {
|
|
804
|
-
recursive: true,
|
|
805
|
-
force: true
|
|
806
|
-
});
|
|
807
|
-
node_fs.mkdirSync(tmpDir, { recursive: true });
|
|
808
|
-
try {
|
|
809
|
-
for (const file of filesToDownload) {
|
|
810
|
-
const destPath = node_path.join(tmpDir, file.relativePath);
|
|
811
|
-
node_fs.mkdirSync(node_path.dirname(destPath), { recursive: true });
|
|
812
|
-
await downloadFile(file.fileUrl, destPath);
|
|
813
|
-
}
|
|
814
|
-
node_fs.rmSync(destDir, {
|
|
815
|
-
recursive: true,
|
|
816
|
-
force: true
|
|
817
|
-
});
|
|
818
|
-
node_fs.renameSync(tmpDir, destDir);
|
|
819
|
-
} catch (err) {
|
|
820
|
-
node_fs.rmSync(tmpDir, {
|
|
821
|
-
recursive: true,
|
|
822
|
-
force: true
|
|
823
|
-
});
|
|
824
|
-
throw err;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
/** Recursively list all files in a HuggingFace directory via API. */
|
|
828
|
-
async listHfFiles(repo, dirPath) {
|
|
829
|
-
const entries = await fetchJson(`https://huggingface.co/api/models/${repo}/tree/main/${dirPath}`);
|
|
830
|
-
const files = [];
|
|
831
|
-
for (const entry of entries) if (entry.type === "file") files.push({
|
|
832
|
-
path: entry.path,
|
|
833
|
-
size: entry.size ?? entry.lfs?.size ?? 0
|
|
834
|
-
});
|
|
835
|
-
else if (entry.type === "directory") {
|
|
836
|
-
const subFiles = await this.listHfFiles(repo, entry.path);
|
|
837
|
-
files.push(...subFiles);
|
|
838
|
-
}
|
|
839
|
-
return files;
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
//#endregion
|
|
843
185
|
//#region src/python/python-env-manager.ts
|
|
844
|
-
var execFileAsync$
|
|
186
|
+
var execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
|
|
845
187
|
var PythonEnvManager = class {
|
|
846
188
|
venvPath;
|
|
847
189
|
cachedProbe = null;
|
|
@@ -851,12 +193,12 @@ var PythonEnvManager = class {
|
|
|
851
193
|
async probe() {
|
|
852
194
|
if (this.cachedProbe) return this.cachedProbe;
|
|
853
195
|
for (const cmd of ["python3", "python"]) try {
|
|
854
|
-
const { stdout } = await execFileAsync$
|
|
196
|
+
const { stdout } = await execFileAsync$3(cmd, ["--version"]);
|
|
855
197
|
const version = stdout.trim().replace("Python ", "");
|
|
856
198
|
const major = parseInt(version.split(".")[0] ?? "0", 10);
|
|
857
199
|
const minor = parseInt(version.split(".")[1] ?? "0", 10);
|
|
858
200
|
if (major < 3 || major === 3 && minor < 10) continue;
|
|
859
|
-
const { stdout: pathOut } = await execFileAsync$
|
|
201
|
+
const { stdout: pathOut } = await execFileAsync$3(cmd, ["-c", "import sys; print(sys.executable)"]);
|
|
860
202
|
this.cachedProbe = {
|
|
861
203
|
available: true,
|
|
862
204
|
version,
|
|
@@ -872,13 +214,13 @@ var PythonEnvManager = class {
|
|
|
872
214
|
async ensure(options) {
|
|
873
215
|
const probe = await this.probe();
|
|
874
216
|
if (!probe.available || !probe.path) throw new Error("Python 3.10+ is required but not found on this system");
|
|
875
|
-
if (!node_fs.existsSync(node_path.join(this.venvPath, "bin", "python"))) await execFileAsync$
|
|
217
|
+
if (!node_fs.existsSync(node_path.join(this.venvPath, "bin", "python"))) await execFileAsync$3(probe.path, [
|
|
876
218
|
"-m",
|
|
877
219
|
"venv",
|
|
878
220
|
this.venvPath
|
|
879
221
|
]);
|
|
880
222
|
const venvPython = node_path.join(this.venvPath, "bin", "python");
|
|
881
|
-
if (options.packages.length > 0) await execFileAsync$
|
|
223
|
+
if (options.packages.length > 0) await execFileAsync$3(venvPython, [
|
|
882
224
|
"-m",
|
|
883
225
|
"pip",
|
|
884
226
|
"install",
|
|
@@ -2077,12 +1419,11 @@ var StorageManager = class {
|
|
|
2077
1419
|
return namespace ? this.createNamespacedLocation(location, namespace) : location;
|
|
2078
1420
|
}
|
|
2079
1421
|
createLegacyShim() {
|
|
2080
|
-
const self = this;
|
|
2081
1422
|
return {
|
|
2082
1423
|
async initialize() {},
|
|
2083
1424
|
async shutdown() {},
|
|
2084
|
-
getLocation(name) {
|
|
2085
|
-
return
|
|
1425
|
+
getLocation: (name) => {
|
|
1426
|
+
return this.getLocation(name);
|
|
2086
1427
|
}
|
|
2087
1428
|
};
|
|
2088
1429
|
}
|
|
@@ -2358,7 +1699,7 @@ function matchPath(pattern, path) {
|
|
|
2358
1699
|
}
|
|
2359
1700
|
//#endregion
|
|
2360
1701
|
//#region src/tls/cert-manager.ts
|
|
2361
|
-
var execFileAsync$
|
|
1702
|
+
var execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
|
|
2362
1703
|
/**
|
|
2363
1704
|
* Ensure a self-signed TLS certificate exists in the given directory.
|
|
2364
1705
|
* Generates one if missing. Returns paths to cert and key files.
|
|
@@ -2395,7 +1736,7 @@ async function ensureTlsCert(dataDir, options) {
|
|
|
2395
1736
|
for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
|
|
2396
1737
|
for (const ip of sanIps) sanParts.push(`IP:${ip}`);
|
|
2397
1738
|
const sanString = sanParts.join(",");
|
|
2398
|
-
await execFileAsync$
|
|
1739
|
+
await execFileAsync$2("openssl", [
|
|
2399
1740
|
"req",
|
|
2400
1741
|
"-x509",
|
|
2401
1742
|
"-newkey",
|
|
@@ -3228,6 +2569,7 @@ var AddonEngineManager = class {
|
|
|
3228
2569
|
};
|
|
3229
2570
|
//#endregion
|
|
3230
2571
|
//#region src/kernel/fs-utils.ts
|
|
2572
|
+
var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
|
|
3231
2573
|
/**
|
|
3232
2574
|
* Ensure a directory exists (recursive).
|
|
3233
2575
|
* Single source of truth — replaces scattered mkdirSync calls.
|
|
@@ -3236,18 +2578,20 @@ function ensureDir(dirPath) {
|
|
|
3236
2578
|
node_fs.mkdirSync(dirPath, { recursive: true });
|
|
3237
2579
|
}
|
|
3238
2580
|
/**
|
|
3239
|
-
* Copy a directory recursively.
|
|
3240
|
-
*
|
|
2581
|
+
* Copy a directory recursively — ASYNC so it never blocks the event loop.
|
|
2582
|
+
*
|
|
2583
|
+
* The hub installs addons into `/data/addons`, which on Unraid is a slow
|
|
2584
|
+
* shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
|
|
2585
|
+
* blocked the Node event loop for the whole copy of a large bundle (e.g.
|
|
2586
|
+
* `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
|
|
2587
|
+
* (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
|
|
2588
|
+
* `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
|
|
3241
2589
|
*/
|
|
3242
|
-
function copyDirRecursive(src, dest) {
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
const destPath = node_path.join(dest, entry.name);
|
|
3248
|
-
if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
3249
|
-
else node_fs.copyFileSync(srcPath, destPath);
|
|
3250
|
-
}
|
|
2590
|
+
async function copyDirRecursive(src, dest) {
|
|
2591
|
+
await node_fs.promises.cp(src, dest, {
|
|
2592
|
+
recursive: true,
|
|
2593
|
+
force: true
|
|
2594
|
+
});
|
|
3251
2595
|
}
|
|
3252
2596
|
/**
|
|
3253
2597
|
* Strip @camstack/* dependencies and devDependencies from a package.json object.
|
|
@@ -3277,7 +2621,7 @@ function stripCamstackDeps(pkg) {
|
|
|
3277
2621
|
* Copies directories (not individual files) from source to destination.
|
|
3278
2622
|
* Skips "dist" (already handled) and glob patterns.
|
|
3279
2623
|
*/
|
|
3280
|
-
function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
2624
|
+
async function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
3281
2625
|
const rawFiles = pkgJson.files;
|
|
3282
2626
|
if (!Array.isArray(rawFiles)) return;
|
|
3283
2627
|
for (const fileEntry of rawFiles) {
|
|
@@ -3286,11 +2630,11 @@ function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
|
3286
2630
|
const srcPath = node_path.join(sourceDir, fileEntry);
|
|
3287
2631
|
if (!node_fs.existsSync(srcPath)) continue;
|
|
3288
2632
|
const destPath = node_path.join(destDir, fileEntry);
|
|
3289
|
-
const stat = node_fs.
|
|
3290
|
-
if (stat.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
2633
|
+
const stat = await node_fs.promises.stat(srcPath);
|
|
2634
|
+
if (stat.isDirectory()) await copyDirRecursive(srcPath, destPath);
|
|
3291
2635
|
else if (stat.isFile()) {
|
|
3292
2636
|
ensureDir(node_path.dirname(destPath));
|
|
3293
|
-
node_fs.
|
|
2637
|
+
await node_fs.promises.copyFile(srcPath, destPath);
|
|
3294
2638
|
}
|
|
3295
2639
|
}
|
|
3296
2640
|
}
|
|
@@ -3335,12 +2679,16 @@ function ensureLibraryBuilt(packageName, packagesDir) {
|
|
|
3335
2679
|
/**
|
|
3336
2680
|
* Install a single npm package into a target directory (package.json + dist/).
|
|
3337
2681
|
* No validation on camstack.addons -- works for any @camstack/* package.
|
|
3338
|
-
*
|
|
2682
|
+
*
|
|
2683
|
+
* ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
|
|
2684
|
+
* live node never blocks the event loop (the `npm pack` + tar extract + copy to
|
|
2685
|
+
* the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
|
|
2686
|
+
* `copyDirRecursive` for the wedge this prevents.
|
|
3339
2687
|
*/
|
|
3340
|
-
function
|
|
3341
|
-
const tmpDir = node_fs.
|
|
2688
|
+
async function installPackageFromNpm(packageName, targetDir) {
|
|
2689
|
+
const tmpDir = await node_fs.promises.mkdtemp(node_path.join(node_os.tmpdir(), "camstack-install-"));
|
|
3342
2690
|
try {
|
|
3343
|
-
const
|
|
2691
|
+
const { stdout } = await execFileAsync$1("npm", [
|
|
3344
2692
|
"pack",
|
|
3345
2693
|
packageName,
|
|
3346
2694
|
"--pack-destination",
|
|
@@ -3348,12 +2696,13 @@ function installPackageFromNpmSync(packageName, targetDir) {
|
|
|
3348
2696
|
], {
|
|
3349
2697
|
timeout: 12e4,
|
|
3350
2698
|
encoding: "utf-8"
|
|
3351
|
-
})
|
|
2699
|
+
});
|
|
2700
|
+
const tgzFilename = stdout.trim().split("\n").pop()?.trim();
|
|
3352
2701
|
if (!tgzFilename) throw new Error("npm pack produced no output");
|
|
3353
2702
|
const tgzPath = node_path.join(tmpDir, tgzFilename);
|
|
3354
2703
|
const extractDir = node_path.join(tmpDir, "extracted");
|
|
3355
2704
|
ensureDir(extractDir);
|
|
3356
|
-
|
|
2705
|
+
await execFileAsync$1("tar", [
|
|
3357
2706
|
"-xzf",
|
|
3358
2707
|
tgzPath,
|
|
3359
2708
|
"-C",
|
|
@@ -3361,20 +2710,20 @@ function installPackageFromNpmSync(packageName, targetDir) {
|
|
|
3361
2710
|
], { timeout: 3e4 });
|
|
3362
2711
|
const packageSubDir = node_path.join(extractDir, "package");
|
|
3363
2712
|
const srcPkgJsonDir = node_fs.existsSync(node_path.join(packageSubDir, "package.json")) ? packageSubDir : extractDir;
|
|
3364
|
-
node_fs.
|
|
2713
|
+
await node_fs.promises.rm(targetDir, {
|
|
3365
2714
|
recursive: true,
|
|
3366
2715
|
force: true
|
|
3367
2716
|
});
|
|
3368
2717
|
ensureDir(targetDir);
|
|
3369
|
-
node_fs.
|
|
2718
|
+
await node_fs.promises.copyFile(node_path.join(srcPkgJsonDir, "package.json"), node_path.join(targetDir, "package.json"));
|
|
3370
2719
|
const distSrc = node_path.join(srcPkgJsonDir, "dist");
|
|
3371
|
-
if (node_fs.existsSync(distSrc)) copyDirRecursive(distSrc, node_path.join(targetDir, "dist"));
|
|
2720
|
+
if (node_fs.existsSync(distSrc)) await copyDirRecursive(distSrc, node_path.join(targetDir, "dist"));
|
|
3372
2721
|
try {
|
|
3373
|
-
const npmPkg = (0, _camstack_types.asJsonObject)((0, _camstack_types.parseJsonUnknown)(node_fs.
|
|
3374
|
-
if (npmPkg) copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
|
|
2722
|
+
const npmPkg = (0, _camstack_types.asJsonObject)((0, _camstack_types.parseJsonUnknown)(await node_fs.promises.readFile(node_path.join(srcPkgJsonDir, "package.json"), "utf-8")));
|
|
2723
|
+
if (npmPkg) await copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
|
|
3375
2724
|
} catch {}
|
|
3376
2725
|
} finally {
|
|
3377
|
-
node_fs.
|
|
2726
|
+
await node_fs.promises.rm(tmpDir, {
|
|
3378
2727
|
recursive: true,
|
|
3379
2728
|
force: true
|
|
3380
2729
|
});
|
|
@@ -3824,15 +3173,15 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3824
3173
|
await this.ensureBuilt(packageName, sourceDir, pkgData);
|
|
3825
3174
|
const distDir = node_path.join(sourceDir, "dist");
|
|
3826
3175
|
if (!node_fs.existsSync(distDir)) throw new Error(`${packageName} has no dist/ after build`);
|
|
3827
|
-
node_fs.
|
|
3176
|
+
await node_fs.promises.rm(targetDir, {
|
|
3828
3177
|
recursive: true,
|
|
3829
3178
|
force: true
|
|
3830
3179
|
});
|
|
3831
3180
|
ensureDir(targetDir);
|
|
3832
|
-
node_fs.
|
|
3833
|
-
copyDirRecursive(distDir, node_path.join(targetDir, "dist"));
|
|
3834
|
-
copyExtraFileDirs(pkgData, sourceDir, targetDir);
|
|
3835
|
-
node_fs.
|
|
3181
|
+
await node_fs.promises.writeFile(node_path.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
|
|
3182
|
+
await copyDirRecursive(distDir, node_path.join(targetDir, "dist"));
|
|
3183
|
+
await copyExtraFileDirs(pkgData, sourceDir, targetDir);
|
|
3184
|
+
await node_fs.promises.writeFile(node_path.join(targetDir, ".install-source"), "local");
|
|
3836
3185
|
const localPkgVersion = (0, _camstack_types.asString)(pkgData.version, "0.0.0");
|
|
3837
3186
|
this.manifest.upsert(packageName, {
|
|
3838
3187
|
version: localPkgVersion,
|
|
@@ -3952,17 +3301,17 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3952
3301
|
if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
|
|
3953
3302
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
3954
3303
|
const targetDir = node_path.join(this.addonsDir, pkgView.name);
|
|
3955
|
-
node_fs.
|
|
3304
|
+
await node_fs.promises.rm(targetDir, {
|
|
3956
3305
|
recursive: true,
|
|
3957
3306
|
force: true
|
|
3958
3307
|
});
|
|
3959
3308
|
ensureDir(targetDir);
|
|
3960
3309
|
const sourceDir = node_path.dirname(pkgJsonPath);
|
|
3961
3310
|
const strippedManifest = stripCamstackDeps(pkgView.raw);
|
|
3962
|
-
node_fs.
|
|
3311
|
+
await node_fs.promises.writeFile(node_path.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
|
|
3963
3312
|
const sourceDist = node_path.join(sourceDir, "dist");
|
|
3964
|
-
if (node_fs.existsSync(sourceDist)) copyDirRecursive(sourceDist, node_path.join(targetDir, "dist"));
|
|
3965
|
-
copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
|
|
3313
|
+
if (node_fs.existsSync(sourceDist)) await copyDirRecursive(sourceDist, node_path.join(targetDir, "dist"));
|
|
3314
|
+
await copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
|
|
3966
3315
|
const strippedRuntimeDeps = strippedManifest["dependencies"];
|
|
3967
3316
|
if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
|
|
3968
3317
|
this.logger.info(`${pkgView.name} — installing runtime dependencies`, { meta: { targetDir } });
|
|
@@ -3986,7 +3335,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3986
3335
|
try {
|
|
3987
3336
|
await require_manifest_python_deps.installManifestNativeDeps(targetDir, pkgView.raw, this.logger, this.registry);
|
|
3988
3337
|
} catch (nativeErr) {
|
|
3989
|
-
node_fs.
|
|
3338
|
+
await node_fs.promises.rm(targetDir, {
|
|
3990
3339
|
recursive: true,
|
|
3991
3340
|
force: true
|
|
3992
3341
|
});
|
|
@@ -4001,7 +3350,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4001
3350
|
version: pkgView.version
|
|
4002
3351
|
};
|
|
4003
3352
|
} finally {
|
|
4004
|
-
node_fs.
|
|
3353
|
+
await node_fs.promises.rm(tmpDir, {
|
|
4005
3354
|
recursive: true,
|
|
4006
3355
|
force: true
|
|
4007
3356
|
});
|
|
@@ -4769,7 +4118,7 @@ var CapabilityRegistry = class CapabilityRegistry {
|
|
|
4769
4118
|
const bare = this.bareAddonId(addonId);
|
|
4770
4119
|
const nodeMap = this.singletonNodeOverrides.get(capabilityName);
|
|
4771
4120
|
if (nodeMap) {
|
|
4772
|
-
for (const [nodeId, ov] of
|
|
4121
|
+
for (const [nodeId, ov] of Array.from(nodeMap)) if (ov === bare) nodeMap.delete(nodeId);
|
|
4773
4122
|
if (nodeMap.size === 0) this.singletonNodeOverrides.delete(capabilityName);
|
|
4774
4123
|
}
|
|
4775
4124
|
this.logger.info("Provider unregistered from capability", {
|
|
@@ -8031,7 +7380,7 @@ var ConfigManager = class ConfigManager {
|
|
|
8031
7380
|
constructor(configPath) {
|
|
8032
7381
|
this.configPath = configPath;
|
|
8033
7382
|
const rawYaml = this.loadYaml();
|
|
8034
|
-
const merged = this.applyEnvOverrides((0,
|
|
7383
|
+
const merged = this.applyEnvOverrides((0, _camstack_types_addon.asJsonObject)(rawYaml) ?? {});
|
|
8035
7384
|
this.bootstrapConfig = bootstrapSchema.parse(merged);
|
|
8036
7385
|
this.warnDefaultCredentials();
|
|
8037
7386
|
const dataPath = this.bootstrapConfig.server.dataPath ?? "camstack-data";
|
|
@@ -8046,20 +7395,20 @@ var ConfigManager = class ConfigManager {
|
|
|
8046
7395
|
setSettingsStore(store) {
|
|
8047
7396
|
this.settingsStore = store;
|
|
8048
7397
|
}
|
|
8049
|
-
get(
|
|
8050
|
-
return this.resolveConfigValue(
|
|
7398
|
+
get(configPath) {
|
|
7399
|
+
return this.resolveConfigValue(configPath);
|
|
8051
7400
|
}
|
|
8052
|
-
resolveConfigValue(
|
|
8053
|
-
const bootstrapValue = this.getFromBootstrap(
|
|
7401
|
+
resolveConfigValue(configPath) {
|
|
7402
|
+
const bootstrapValue = this.getFromBootstrap(configPath);
|
|
8054
7403
|
if (bootstrapValue !== void 0) return bootstrapValue;
|
|
8055
7404
|
if (this.settingsStore !== null) {
|
|
8056
|
-
const storeValue = this.settingsStore.getSystem(
|
|
7405
|
+
const storeValue = this.settingsStore.getSystem(configPath);
|
|
8057
7406
|
if (storeValue !== void 0) return storeValue;
|
|
8058
|
-
const nested = this.getNestedFromSystemSettings(
|
|
7407
|
+
const nested = this.getNestedFromSystemSettings(configPath);
|
|
8059
7408
|
if (nested !== null) return nested;
|
|
8060
7409
|
}
|
|
8061
|
-
if (
|
|
8062
|
-
return this.getFromRuntimeDefaults(
|
|
7410
|
+
if (configPath in _camstack_types.RUNTIME_DEFAULTS) return _camstack_types.RUNTIME_DEFAULTS[configPath];
|
|
7411
|
+
return this.getFromRuntimeDefaults(configPath) ?? void 0;
|
|
8063
7412
|
}
|
|
8064
7413
|
/**
|
|
8065
7414
|
* Write a value to the settings-store.
|
|
@@ -8086,9 +7435,9 @@ var ConfigManager = class ConfigManager {
|
|
|
8086
7435
|
*/
|
|
8087
7436
|
getSection(section) {
|
|
8088
7437
|
const merged = {};
|
|
8089
|
-
const defaults = (0,
|
|
7438
|
+
const defaults = (0, _camstack_types_addon.asJsonObject)(this.getFromRuntimeDefaults(section));
|
|
8090
7439
|
if (defaults) Object.assign(merged, defaults);
|
|
8091
|
-
const bootstrapSection = (0,
|
|
7440
|
+
const bootstrapSection = (0, _camstack_types_addon.asJsonObject)({ ...this.bootstrapConfig }[section]);
|
|
8092
7441
|
if (bootstrapSection) Object.assign(merged, bootstrapSection);
|
|
8093
7442
|
if (this.settingsStore !== null) {
|
|
8094
7443
|
const nested = this.getNestedFromSystemSettings(section);
|
|
@@ -8107,7 +7456,7 @@ var ConfigManager = class ConfigManager {
|
|
|
8107
7456
|
/** Read all config for an addon from addon_settings. */
|
|
8108
7457
|
getAddonConfig(addonId) {
|
|
8109
7458
|
if (this.settingsStore !== null) return this.settingsStore.getAllAddon(addonId);
|
|
8110
|
-
return (0,
|
|
7459
|
+
return (0, _camstack_types_addon.asJsonObject)(this.getFromBootstrap(`addons.${addonId}`)) ?? {};
|
|
8111
7460
|
}
|
|
8112
7461
|
/** Write (bulk-replace) config for an addon to addon_settings. */
|
|
8113
7462
|
setAddonConfig(addonId, config) {
|
|
@@ -8275,8 +7624,8 @@ var ConfigManager = class ConfigManager {
|
|
|
8275
7624
|
};
|
|
8276
7625
|
this.saveRuntimeState();
|
|
8277
7626
|
}
|
|
8278
|
-
getBootstrap(
|
|
8279
|
-
return this.getFromBootstrap(
|
|
7627
|
+
getBootstrap(configPath) {
|
|
7628
|
+
return this.getFromBootstrap(configPath);
|
|
8280
7629
|
}
|
|
8281
7630
|
/** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
|
|
8282
7631
|
get features() {
|
|
@@ -8345,8 +7694,8 @@ var ConfigManager = class ConfigManager {
|
|
|
8345
7694
|
update(section, data) {
|
|
8346
7695
|
if (!ConfigManager.BOOTSTRAP_SECTIONS.has(section)) throw new Error(`[ConfigManager] Section "${section}" is a runtime setting — use setSection() to persist via the settings-store, not update() which writes to config.yaml`);
|
|
8347
7696
|
let raw = {};
|
|
8348
|
-
if (node_fs.existsSync(this.configPath)) raw = (0,
|
|
8349
|
-
const existing = (0,
|
|
7697
|
+
if (node_fs.existsSync(this.configPath)) raw = (0, _camstack_types_addon.asJsonObject)(load$1(node_fs.readFileSync(this.configPath, "utf-8"))) ?? {};
|
|
7698
|
+
const existing = (0, _camstack_types_addon.asJsonObject)(raw[section]) ?? {};
|
|
8350
7699
|
raw[section] = {
|
|
8351
7700
|
...existing,
|
|
8352
7701
|
...data
|
|
@@ -8366,8 +7715,8 @@ var ConfigManager = class ConfigManager {
|
|
|
8366
7715
|
* Deep-set a value in a nested plain object using a dot-notation path.
|
|
8367
7716
|
* Returns a new object (immutable).
|
|
8368
7717
|
*/
|
|
8369
|
-
setNested(obj,
|
|
8370
|
-
const [head, ...rest] =
|
|
7718
|
+
setNested(obj, configPath, value) {
|
|
7719
|
+
const [head, ...rest] = configPath.split(".");
|
|
8371
7720
|
if (!head) return obj;
|
|
8372
7721
|
if (rest.length === 0) return {
|
|
8373
7722
|
...obj,
|
|
@@ -8407,8 +7756,8 @@ var ConfigManager = class ConfigManager {
|
|
|
8407
7756
|
warnDefaultCredentials() {
|
|
8408
7757
|
if (this.bootstrapConfig.auth.adminPassword === "changeme") console.warn("[ConfigManager] Warning: Using default admin password \"changeme\". Set auth.adminPassword in your config.yaml or the CAMSTACK_ADMIN_PASS env var.");
|
|
8409
7758
|
}
|
|
8410
|
-
getFromBootstrap(
|
|
8411
|
-
const keys =
|
|
7759
|
+
getFromBootstrap(configPath) {
|
|
7760
|
+
const keys = configPath.split(".");
|
|
8412
7761
|
let current = this.bootstrapConfig;
|
|
8413
7762
|
for (const key of keys) {
|
|
8414
7763
|
if (!isRecord(current)) return void 0;
|
|
@@ -8416,8 +7765,8 @@ var ConfigManager = class ConfigManager {
|
|
|
8416
7765
|
}
|
|
8417
7766
|
return current;
|
|
8418
7767
|
}
|
|
8419
|
-
getFromRuntimeDefaults(
|
|
8420
|
-
const prefix =
|
|
7768
|
+
getFromRuntimeDefaults(configPath) {
|
|
7769
|
+
const prefix = configPath + ".";
|
|
8421
7770
|
const result = {};
|
|
8422
7771
|
let found = false;
|
|
8423
7772
|
for (const [key, value] of Object.entries(_camstack_types.RUNTIME_DEFAULTS)) if (key.startsWith(prefix)) {
|
|
@@ -8432,10 +7781,10 @@ var ConfigManager = class ConfigManager {
|
|
|
8432
7781
|
* e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
|
|
8433
7782
|
* Returns an object keyed by the sub-key, or undefined if nothing is found.
|
|
8434
7783
|
*/
|
|
8435
|
-
getNestedFromSystemSettings(
|
|
7784
|
+
getNestedFromSystemSettings(configPath) {
|
|
8436
7785
|
if (this.settingsStore === null) return null;
|
|
8437
7786
|
const all = this.settingsStore.getAllSystem();
|
|
8438
|
-
const prefix =
|
|
7787
|
+
const prefix = configPath + ".";
|
|
8439
7788
|
const result = {};
|
|
8440
7789
|
let found = false;
|
|
8441
7790
|
for (const [key, value] of Object.entries(all)) if (key.startsWith(prefix)) {
|
|
@@ -8447,13 +7796,13 @@ var ConfigManager = class ConfigManager {
|
|
|
8447
7796
|
loadRuntimeState() {
|
|
8448
7797
|
if (!node_fs.existsSync(this.runtimeStatePath)) return EMPTY_RUNTIME_STATE;
|
|
8449
7798
|
try {
|
|
8450
|
-
const parsed = (0,
|
|
7799
|
+
const parsed = (0, _camstack_types_addon.asJsonObject)((0, _camstack_types_addon.parseJsonUnknown)(node_fs.readFileSync(this.runtimeStatePath, "utf-8")));
|
|
8451
7800
|
if (parsed === null) return EMPTY_RUNTIME_STATE;
|
|
8452
|
-
const systemActivation = (0,
|
|
8453
|
-
const deviceActivationRaw = (0,
|
|
7801
|
+
const systemActivation = (0, _camstack_types_addon.asJsonObject)(parsed.systemActivation) ?? {};
|
|
7802
|
+
const deviceActivationRaw = (0, _camstack_types_addon.asJsonObject)(parsed.deviceActivation) ?? {};
|
|
8454
7803
|
const deviceActivation = {};
|
|
8455
7804
|
for (const [deviceId, entry] of Object.entries(deviceActivationRaw)) {
|
|
8456
|
-
const nested = (0,
|
|
7805
|
+
const nested = (0, _camstack_types_addon.asJsonObject)(entry);
|
|
8457
7806
|
if (nested === null) continue;
|
|
8458
7807
|
const bools = {};
|
|
8459
7808
|
for (const [k, v] of Object.entries(nested)) if (typeof v === "boolean") bools[k] = v;
|
|
@@ -28768,8 +28117,8 @@ var require_utils$3 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, mod
|
|
|
28768
28117
|
* @param {*} value
|
|
28769
28118
|
* @returns {Object}
|
|
28770
28119
|
*/
|
|
28771
|
-
dotSet(obj, path$
|
|
28772
|
-
const parts = path$
|
|
28120
|
+
dotSet(obj, path$44, value) {
|
|
28121
|
+
const parts = path$44.split(".");
|
|
28773
28122
|
const part = parts.shift();
|
|
28774
28123
|
if (part && parts.length > 0) {
|
|
28775
28124
|
if (!Object.prototype.hasOwnProperty.call(obj, part)) obj[part] = {};
|
|
@@ -28778,7 +28127,7 @@ var require_utils$3 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, mod
|
|
|
28778
28127
|
obj[part] = utils.dotSet(obj[part], parts.join("."), value);
|
|
28779
28128
|
return obj;
|
|
28780
28129
|
}
|
|
28781
|
-
obj[path$
|
|
28130
|
+
obj[path$44] = value;
|
|
28782
28131
|
return obj;
|
|
28783
28132
|
},
|
|
28784
28133
|
/**
|
|
@@ -42944,8 +42293,8 @@ var require_utimes = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, modu
|
|
|
42944
42293
|
else if (timestamp instanceof Date) return /* @__PURE__ */ new Date(Math.floor(timestamp.getTime() / 1e3) * 1e3);
|
|
42945
42294
|
else throw new Error("fs-extra: timeRemoveMillis() unknown parameter type");
|
|
42946
42295
|
}
|
|
42947
|
-
function utimesMillis(path$
|
|
42948
|
-
fs.open(path$
|
|
42296
|
+
function utimesMillis(path$42, atime, mtime, callback) {
|
|
42297
|
+
fs.open(path$42, "r+", (err, fd) => {
|
|
42949
42298
|
if (err) return callback(err);
|
|
42950
42299
|
fs.futimes(fd, atime, mtime, (futimesErr) => {
|
|
42951
42300
|
fs.close(fd, (closeErr) => {
|
|
@@ -42954,8 +42303,8 @@ var require_utimes = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, modu
|
|
|
42954
42303
|
});
|
|
42955
42304
|
});
|
|
42956
42305
|
}
|
|
42957
|
-
function utimesMillisSync(path$
|
|
42958
|
-
const fd = fs.openSync(path$
|
|
42306
|
+
function utimesMillisSync(path$43, atime, mtime) {
|
|
42307
|
+
const fd = fs.openSync(path$43, "r+");
|
|
42959
42308
|
fs.futimesSync(fd, atime, mtime);
|
|
42960
42309
|
return fs.closeSync(fd);
|
|
42961
42310
|
}
|
|
@@ -43958,10 +43307,10 @@ var require_jsonfile$1 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
|
|
|
43958
43307
|
}
|
|
43959
43308
|
if (typeof options === "string") options = { encoding: options };
|
|
43960
43309
|
options = options || {};
|
|
43961
|
-
var fs$
|
|
43310
|
+
var fs$19 = options.fs || _fs;
|
|
43962
43311
|
var shouldThrow = true;
|
|
43963
43312
|
if ("throws" in options) shouldThrow = options.throws;
|
|
43964
|
-
fs$
|
|
43313
|
+
fs$19.readFile(file, options, function(err, data) {
|
|
43965
43314
|
if (err) return callback(err);
|
|
43966
43315
|
data = stripBom(data);
|
|
43967
43316
|
var obj;
|
|
@@ -43979,11 +43328,11 @@ var require_jsonfile$1 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
|
|
|
43979
43328
|
function readFileSync(file, options) {
|
|
43980
43329
|
options = options || {};
|
|
43981
43330
|
if (typeof options === "string") options = { encoding: options };
|
|
43982
|
-
var fs$
|
|
43331
|
+
var fs$20 = options.fs || _fs;
|
|
43983
43332
|
var shouldThrow = true;
|
|
43984
43333
|
if ("throws" in options) shouldThrow = options.throws;
|
|
43985
43334
|
try {
|
|
43986
|
-
var content = fs$
|
|
43335
|
+
var content = fs$20.readFileSync(file, options);
|
|
43987
43336
|
content = stripBom(content);
|
|
43988
43337
|
return JSON.parse(content, options.reviver);
|
|
43989
43338
|
} catch (err) {
|
|
@@ -44008,7 +43357,7 @@ var require_jsonfile$1 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
|
|
|
44008
43357
|
options = {};
|
|
44009
43358
|
}
|
|
44010
43359
|
options = options || {};
|
|
44011
|
-
var fs$
|
|
43360
|
+
var fs$21 = options.fs || _fs;
|
|
44012
43361
|
var str = "";
|
|
44013
43362
|
try {
|
|
44014
43363
|
str = stringify(obj, options);
|
|
@@ -44016,13 +43365,13 @@ var require_jsonfile$1 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
|
|
|
44016
43365
|
if (callback) callback(err, null);
|
|
44017
43366
|
return;
|
|
44018
43367
|
}
|
|
44019
|
-
fs$
|
|
43368
|
+
fs$21.writeFile(file, str, options, callback);
|
|
44020
43369
|
}
|
|
44021
43370
|
function writeFileSync(file, obj, options) {
|
|
44022
43371
|
options = options || {};
|
|
44023
|
-
var fs$
|
|
43372
|
+
var fs$22 = options.fs || _fs;
|
|
44024
43373
|
var str = stringify(obj, options);
|
|
44025
|
-
return fs$
|
|
43374
|
+
return fs$22.writeFileSync(file, str, options);
|
|
44026
43375
|
}
|
|
44027
43376
|
function stripBom(content) {
|
|
44028
43377
|
if (Buffer.isBuffer(content)) content = content.toString("utf8");
|
|
@@ -86620,11 +85969,11 @@ var require_main = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module
|
|
|
86620
85969
|
}
|
|
86621
85970
|
let lastError;
|
|
86622
85971
|
const parsedAll = {};
|
|
86623
|
-
for (const path$
|
|
86624
|
-
const parsed = DotenvModule.parse(fs$4.readFileSync(path$
|
|
85972
|
+
for (const path$41 of optionPaths) try {
|
|
85973
|
+
const parsed = DotenvModule.parse(fs$4.readFileSync(path$41, { encoding }));
|
|
86625
85974
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
86626
85975
|
} catch (e) {
|
|
86627
|
-
if (debug) _debug(`failed to load ${path$
|
|
85976
|
+
if (debug) _debug(`failed to load ${path$41} ${e.message}`);
|
|
86628
85977
|
lastError = e;
|
|
86629
85978
|
}
|
|
86630
85979
|
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
@@ -91596,7 +90945,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
91596
90945
|
respawned.restartCount = prevRestartCount + 1;
|
|
91597
90946
|
});
|
|
91598
90947
|
restarted.push(name);
|
|
91599
|
-
} catch
|
|
90948
|
+
} catch {
|
|
91600
90949
|
failed.push(name);
|
|
91601
90950
|
}
|
|
91602
90951
|
return {
|
|
@@ -91991,8 +91340,8 @@ var LifecycleJobEngine = class {
|
|
|
91991
91340
|
} finally {
|
|
91992
91341
|
clearTimeout(timer);
|
|
91993
91342
|
}
|
|
91994
|
-
if (packages.length === 0) throw new Error("stageFramework returned no packages");
|
|
91995
91343
|
const [firstPackage] = packages;
|
|
91344
|
+
if (!firstPackage) throw new Error("stageFramework returned no packages");
|
|
91996
91345
|
this.advance(job.jobId, task, "staged", { stagedPath: firstPackage.stagedPath });
|
|
91997
91346
|
requestFrameworkSwap({
|
|
91998
91347
|
jobId: job.jobId,
|
|
@@ -92015,14 +91364,15 @@ var LifecycleJobEngine = class {
|
|
|
92015
91364
|
*/
|
|
92016
91365
|
async fetchAddonsBounded(job, addonTasks) {
|
|
92017
91366
|
const limit = Math.max(1, this.deps.fetchConcurrency ?? DEFAULT_FETCH_CONCURRENCY);
|
|
92018
|
-
const outcomes =
|
|
91367
|
+
const outcomes = Array.from({ length: addonTasks.length });
|
|
92019
91368
|
let nextIndex = 0;
|
|
92020
91369
|
const worker = async () => {
|
|
92021
91370
|
for (;;) {
|
|
92022
91371
|
const index = nextIndex;
|
|
92023
91372
|
nextIndex += 1;
|
|
92024
|
-
|
|
92025
|
-
|
|
91373
|
+
const addonTask = addonTasks[index];
|
|
91374
|
+
if (addonTask === void 0) return;
|
|
91375
|
+
outcomes[index] = await this.fetchAddonTask(job, addonTask);
|
|
92026
91376
|
}
|
|
92027
91377
|
};
|
|
92028
91378
|
const workerCount = Math.min(limit, addonTasks.length);
|
|
@@ -92174,7 +91524,7 @@ exports.ConfigManager = ConfigManager;
|
|
|
92174
91524
|
exports.ConfigStore = require_builtins_sqlite_storage_index.ConfigStore$1;
|
|
92175
91525
|
exports.ConsoleDestination = require_builtins_console_logging_index.ConsoleDestination$1;
|
|
92176
91526
|
exports.ConsoleLoggingAddon = require_builtins_console_logging_index.ConsoleLoggingAddon$1;
|
|
92177
|
-
exports.CustomActionRegistry =
|
|
91527
|
+
exports.CustomActionRegistry = require_custom_action_registry.CustomActionRegistry;
|
|
92178
91528
|
exports.DEFAULT_DATA_PATH = DEFAULT_DATA_PATH;
|
|
92179
91529
|
exports.DataPlaneRegistry = DataPlaneRegistry;
|
|
92180
91530
|
exports.DeviceManagerAddon = require_builtins_device_manager_device_manager_addon.DeviceManagerAddon;
|
|
@@ -92206,7 +91556,7 @@ exports.LocalChildClient = require_manifest_python_deps.LocalChildClient;
|
|
|
92206
91556
|
exports.LocalChildRegistry = require_manifest_python_deps.LocalChildRegistry;
|
|
92207
91557
|
exports.LogManager = LogManager;
|
|
92208
91558
|
exports.LogRingBuffer = LogRingBuffer;
|
|
92209
|
-
exports.ModelDownloadService = ModelDownloadService;
|
|
91559
|
+
exports.ModelDownloadService = require_model_download_service.ModelDownloadService;
|
|
92210
91560
|
exports.NATIVE_PROVIDER_SERVICE_INFIX = require_manifest_python_deps.NATIVE_PROVIDER_SERVICE_INFIX;
|
|
92211
91561
|
exports.NativeMetricsAddon = require_builtins_native_metrics_native_metrics_addon.default;
|
|
92212
91562
|
exports.NativeMetricsProvider = require_builtins_native_metrics_native_metrics_addon.NativeMetricsProvider;
|
|
@@ -92286,16 +91636,16 @@ exports.capServiceName = require_manifest_python_deps.capServiceName;
|
|
|
92286
91636
|
exports.classifyCapRoute = require_manifest_python_deps.classifyCapRoute;
|
|
92287
91637
|
exports.clearPendingRestart = clearPendingRestart;
|
|
92288
91638
|
exports.clusterSecretMatches = clusterSecretMatches;
|
|
92289
|
-
exports.contentTypeFor = contentTypeFor;
|
|
91639
|
+
exports.contentTypeFor = require_model_download_service.contentTypeFor;
|
|
92290
91640
|
exports.copyDirRecursive = copyDirRecursive;
|
|
92291
91641
|
exports.copyExtraFileDirs = copyExtraFileDirs;
|
|
92292
91642
|
exports.createAddonContext = require_manifest_python_deps.createAddonContext;
|
|
92293
91643
|
exports.createAddonService = require_manifest_python_deps.createAddonService;
|
|
92294
|
-
exports.createAuthenticatedFileServer = createAuthenticatedFileServer;
|
|
91644
|
+
exports.createAuthenticatedFileServer = require_model_download_service.createAuthenticatedFileServer;
|
|
92295
91645
|
exports.createBroker = createBroker;
|
|
92296
91646
|
exports.createBrokerDeviceManagerApi = require_manifest_python_deps.createBrokerDeviceManagerApi;
|
|
92297
91647
|
exports.createCoreCapService = createCoreCapService;
|
|
92298
|
-
exports.createFileDataPlaneHandler = createFileDataPlaneHandler;
|
|
91648
|
+
exports.createFileDataPlaneHandler = require_model_download_service.createFileDataPlaneHandler;
|
|
92299
91649
|
exports.createHubService = createHubService;
|
|
92300
91650
|
exports.createHwAccelService = require_manifest_python_deps.createHwAccelService;
|
|
92301
91651
|
exports.createKernelHwAccel = require_manifest_python_deps.createKernelHwAccel;
|
|
@@ -92311,7 +91661,7 @@ exports.createUdsEventBridge = require_manifest_python_deps.createUdsEventBridge
|
|
|
92311
91661
|
exports.createUdsEventBus = require_manifest_python_deps.createUdsEventBus;
|
|
92312
91662
|
exports.createUdsLogger = require_manifest_python_deps.createUdsLogger;
|
|
92313
91663
|
exports.createUdsLoggerWithControl = require_manifest_python_deps.createUdsLoggerWithControl;
|
|
92314
|
-
exports.deleteModelFromDisk = deleteModelFromDisk;
|
|
91664
|
+
exports.deleteModelFromDisk = require_model_download_service.deleteModelFromDisk;
|
|
92315
91665
|
exports.deriveAgentListenPort = deriveAgentListenPort;
|
|
92316
91666
|
exports.describeProviderKindDrift = describeProviderKindDrift;
|
|
92317
91667
|
exports.detectWorkspacePackagesDir = detectWorkspacePackagesDir;
|
|
@@ -92321,8 +91671,8 @@ Object.defineProperty(exports, "downloadBinary", {
|
|
|
92321
91671
|
return _camstack_types_node.downloadBinary;
|
|
92322
91672
|
}
|
|
92323
91673
|
});
|
|
92324
|
-
exports.downloadFile = downloadFile;
|
|
92325
|
-
exports.downloadModel = downloadModel;
|
|
91674
|
+
exports.downloadFile = require_model_download_service.downloadFile;
|
|
91675
|
+
exports.downloadModel = require_model_download_service.downloadModel;
|
|
92326
91676
|
Object.defineProperty(exports, "emitDownForOwnedCaps", {
|
|
92327
91677
|
enumerable: true,
|
|
92328
91678
|
get: function() {
|
|
@@ -92344,7 +91694,7 @@ Object.defineProperty(exports, "ensureFfmpeg", {
|
|
|
92344
91694
|
}
|
|
92345
91695
|
});
|
|
92346
91696
|
exports.ensureLibraryBuilt = ensureLibraryBuilt;
|
|
92347
|
-
exports.ensureModel = ensureModel;
|
|
91697
|
+
exports.ensureModel = require_model_download_service.ensureModel;
|
|
92348
91698
|
Object.defineProperty(exports, "ensurePython", {
|
|
92349
91699
|
enumerable: true,
|
|
92350
91700
|
get: function() {
|
|
@@ -92352,7 +91702,7 @@ Object.defineProperty(exports, "ensurePython", {
|
|
|
92352
91702
|
}
|
|
92353
91703
|
});
|
|
92354
91704
|
exports.ensureTlsCert = ensureTlsCert;
|
|
92355
|
-
exports.fetchJson = fetchJson;
|
|
91705
|
+
exports.fetchJson = require_model_download_service.fetchJson;
|
|
92356
91706
|
Object.defineProperty(exports, "findInPath", {
|
|
92357
91707
|
enumerable: true,
|
|
92358
91708
|
get: function() {
|
|
@@ -92368,7 +91718,7 @@ Object.defineProperty(exports, "getFfmpegDownloadUrl", {
|
|
|
92368
91718
|
return _camstack_types_node.getFfmpegDownloadUrl;
|
|
92369
91719
|
}
|
|
92370
91720
|
});
|
|
92371
|
-
exports.getModelFilePath = getModelFilePath;
|
|
91721
|
+
exports.getModelFilePath = require_model_download_service.getModelFilePath;
|
|
92372
91722
|
exports.getOrInitReadinessRegistry = require_manifest_python_deps.getOrInitReadinessRegistry;
|
|
92373
91723
|
exports.getOrInitReadinessRegistryForClient = require_manifest_python_deps.getOrInitReadinessRegistryForClient;
|
|
92374
91724
|
exports.getPidStats = require_resource_monitor.getPidStats;
|
|
@@ -92390,7 +91740,7 @@ exports.getWorkerDeviceRegistry = require_manifest_python_deps.getWorkerDeviceRe
|
|
|
92390
91740
|
exports.hashClusterSecret = hashClusterSecret;
|
|
92391
91741
|
exports.installManifestNativeDeps = require_manifest_python_deps.installManifestNativeDeps;
|
|
92392
91742
|
exports.installManifestPythonDeps = require_manifest_python_deps.installManifestPythonDeps;
|
|
92393
|
-
exports.
|
|
91743
|
+
exports.installPackageFromNpm = installPackageFromNpm;
|
|
92394
91744
|
Object.defineProperty(exports, "installPythonPackages", {
|
|
92395
91745
|
enumerable: true,
|
|
92396
91746
|
get: function() {
|
|
@@ -92407,15 +91757,15 @@ exports.ipcChildLink = require_manifest_python_deps.ipcChildLink;
|
|
|
92407
91757
|
exports.ipcParentLink = require_manifest_python_deps.ipcParentLink;
|
|
92408
91758
|
exports.isClusterSecretMismatchError = isClusterSecretMismatchError;
|
|
92409
91759
|
exports.isInfraCapability = isInfraCapability;
|
|
92410
|
-
exports.isModelDownloaded = isModelDownloaded;
|
|
91760
|
+
exports.isModelDownloaded = require_model_download_service.isModelDownloaded;
|
|
92411
91761
|
exports.isSourceNewer = isSourceNewer;
|
|
92412
91762
|
exports.loadTlsCert = loadTlsCert;
|
|
92413
91763
|
exports.localEndpointPath = require_manifest_python_deps.localEndpointPath;
|
|
92414
91764
|
exports.localProviderLink = require_manifest_python_deps.localProviderLink;
|
|
92415
91765
|
exports.mountNativeCapService = require_manifest_python_deps.mountNativeCapService;
|
|
92416
91766
|
exports.parseCapAction = require_manifest_python_deps.parseCapAction;
|
|
92417
|
-
exports.parseRangeHeader = parseRangeHeader;
|
|
92418
|
-
exports.parseTokenizedUrl = parseTokenizedUrl;
|
|
91767
|
+
exports.parseRangeHeader = require_model_download_service.parseRangeHeader;
|
|
91768
|
+
exports.parseTokenizedUrl = require_model_download_service.parseTokenizedUrl;
|
|
92419
91769
|
exports.proxyToUpstream = proxyToUpstream;
|
|
92420
91770
|
exports.readPendingRestart = readPendingRestart;
|
|
92421
91771
|
Object.defineProperty(exports, "readinessKey", {
|
|
@@ -92425,7 +91775,7 @@ Object.defineProperty(exports, "readinessKey", {
|
|
|
92425
91775
|
}
|
|
92426
91776
|
});
|
|
92427
91777
|
exports.registerEventBusService = require_manifest_python_deps.registerEventBusService;
|
|
92428
|
-
exports.resolveFilePath = resolveFilePath;
|
|
91778
|
+
exports.resolveFilePath = require_model_download_service.resolveFilePath;
|
|
92429
91779
|
exports.resolveHwAccel = require_manifest_python_deps.resolveHwAccel;
|
|
92430
91780
|
exports.scheduleSelfRestart = scheduleSelfRestart;
|
|
92431
91781
|
Object.defineProperty(exports, "scopeKey", {
|