@camstack/kernel 0.1.1
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-installer-RE7RJMVP.mjs +8 -0
- package/dist/addon-installer-RE7RJMVP.mjs.map +1 -0
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BJTO5JO5.mjs.map +1 -0
- package/dist/chunk-REHQJR3P.mjs +387 -0
- package/dist/chunk-REHQJR3P.mjs.map +1 -0
- package/dist/chunk-S5YWNNPK.mjs +135 -0
- package/dist/chunk-S5YWNNPK.mjs.map +1 -0
- package/dist/index.d.mts +641 -0
- package/dist/index.d.ts +641 -0
- package/dist/index.js +1833 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1290 -0
- package/dist/index.mjs.map +1 -0
- package/dist/worker/addon-worker-entry.d.mts +2 -0
- package/dist/worker/addon-worker-entry.d.ts +2 -0
- package/dist/worker/addon-worker-entry.js +688 -0
- package/dist/worker/addon-worker-entry.js.map +1 -0
- package/dist/worker/addon-worker-entry.mjs +194 -0
- package/dist/worker/addon-worker-entry.mjs.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
// src/fs-utils.ts
|
|
33
|
+
function ensureDir(dirPath) {
|
|
34
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
function copyDirRecursive(src, dest) {
|
|
37
|
+
ensureDir(dest);
|
|
38
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const srcPath = path.join(src, entry.name);
|
|
41
|
+
const destPath = path.join(dest, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
copyDirRecursive(srcPath, destPath);
|
|
44
|
+
} else {
|
|
45
|
+
fs.copyFileSync(srcPath, destPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function stripCamstackDeps(pkg) {
|
|
50
|
+
const result = { ...pkg };
|
|
51
|
+
for (const depType of ["dependencies", "peerDependencies", "devDependencies"]) {
|
|
52
|
+
const deps = result[depType];
|
|
53
|
+
if (deps) {
|
|
54
|
+
const filtered = {};
|
|
55
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
56
|
+
if (!name.startsWith("@camstack/")) {
|
|
57
|
+
filtered[name] = version;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
result[depType] = Object.keys(filtered).length > 0 ? filtered : void 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
delete result.devDependencies;
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
67
|
+
const files = pkgJson.files;
|
|
68
|
+
if (!files) return;
|
|
69
|
+
for (const fileEntry of files) {
|
|
70
|
+
if (fileEntry === "dist" || fileEntry.includes("*")) continue;
|
|
71
|
+
const srcPath = path.join(sourceDir, fileEntry);
|
|
72
|
+
if (!fs.existsSync(srcPath)) continue;
|
|
73
|
+
const destPath = path.join(destDir, fileEntry);
|
|
74
|
+
const stat = fs.statSync(srcPath);
|
|
75
|
+
if (stat.isDirectory()) {
|
|
76
|
+
copyDirRecursive(srcPath, destPath);
|
|
77
|
+
} else if (stat.isFile()) {
|
|
78
|
+
ensureDir(path.dirname(destPath));
|
|
79
|
+
fs.copyFileSync(srcPath, destPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isSourceNewer(packageDir) {
|
|
84
|
+
const srcDir = path.join(packageDir, "src");
|
|
85
|
+
const distDir = path.join(packageDir, "dist");
|
|
86
|
+
if (!fs.existsSync(srcDir) || !fs.existsSync(distDir)) return true;
|
|
87
|
+
try {
|
|
88
|
+
const distMtime = fs.statSync(distDir).mtimeMs;
|
|
89
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true, recursive: true });
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (!entry.isFile()) continue;
|
|
92
|
+
const filePath = path.join(entry.parentPath ?? entry.path, entry.name);
|
|
93
|
+
if (fs.statSync(filePath).mtimeMs > distMtime) return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
} catch {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function ensureLibraryBuilt(packageName, packagesDir) {
|
|
101
|
+
const dirName = packageName.replace("@camstack/", "");
|
|
102
|
+
const sourceDir = path.join(packagesDir, dirName);
|
|
103
|
+
if (!fs.existsSync(sourceDir)) return;
|
|
104
|
+
const distDir = path.join(sourceDir, "dist");
|
|
105
|
+
const hasIndex = fs.existsSync(path.join(distDir, "index.js")) || fs.existsSync(path.join(distDir, "index.mjs"));
|
|
106
|
+
if (hasIndex) return;
|
|
107
|
+
console.warn(`[ensureLibraryBuilt] ${packageName} has no dist/ \u2014 run 'npm run build' first`);
|
|
108
|
+
}
|
|
109
|
+
var import_node_child_process2, fs, path;
|
|
110
|
+
var init_fs_utils = __esm({
|
|
111
|
+
"src/fs-utils.ts"() {
|
|
112
|
+
"use strict";
|
|
113
|
+
import_node_child_process2 = require("child_process");
|
|
114
|
+
fs = __toESM(require("fs"));
|
|
115
|
+
path = __toESM(require("path"));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// src/addon-installer.ts
|
|
120
|
+
var addon_installer_exports = {};
|
|
121
|
+
__export(addon_installer_exports, {
|
|
122
|
+
AddonInstaller: () => AddonInstaller
|
|
123
|
+
});
|
|
124
|
+
var import_node_child_process3, import_node_util, fs2, path2, os, execFileAsync, AddonInstaller;
|
|
125
|
+
var init_addon_installer = __esm({
|
|
126
|
+
"src/addon-installer.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
import_node_child_process3 = require("child_process");
|
|
129
|
+
import_node_util = require("util");
|
|
130
|
+
fs2 = __toESM(require("fs"));
|
|
131
|
+
path2 = __toESM(require("path"));
|
|
132
|
+
os = __toESM(require("os"));
|
|
133
|
+
init_fs_utils();
|
|
134
|
+
execFileAsync = (0, import_node_util.promisify)(import_node_child_process3.execFile);
|
|
135
|
+
AddonInstaller = class _AddonInstaller {
|
|
136
|
+
addonsDir;
|
|
137
|
+
registry;
|
|
138
|
+
workspacePackagesDir;
|
|
139
|
+
constructor(config) {
|
|
140
|
+
this.addonsDir = config.addonsDir;
|
|
141
|
+
this.registry = config.registry;
|
|
142
|
+
this.workspacePackagesDir = config.workspacePackagesDir;
|
|
143
|
+
}
|
|
144
|
+
/** Required addon packages that must be installed for the server to function */
|
|
145
|
+
static REQUIRED_PACKAGES = [
|
|
146
|
+
"@camstack/core",
|
|
147
|
+
"@camstack/addon-pipeline",
|
|
148
|
+
"@camstack/addon-vision",
|
|
149
|
+
"@camstack/addon-admin-ui",
|
|
150
|
+
"@camstack/addon-webrtc-adaptive",
|
|
151
|
+
"@camstack/addon-pipeline-analysis",
|
|
152
|
+
"@camstack/addon-scene-intelligence",
|
|
153
|
+
"@camstack/addon-advanced-notifier"
|
|
154
|
+
];
|
|
155
|
+
/** Ensure the addons directory exists */
|
|
156
|
+
async initialize() {
|
|
157
|
+
ensureDir(this.addonsDir);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Ensure all required packages are installed in the addons directory.
|
|
161
|
+
* This replaces the standalone first-boot-installer.ts.
|
|
162
|
+
*/
|
|
163
|
+
async ensureRequiredPackages() {
|
|
164
|
+
ensureDir(this.addonsDir);
|
|
165
|
+
if (this.workspacePackagesDir) {
|
|
166
|
+
console.log(`[AddonInstaller] Workspace detected: ${this.workspacePackagesDir}`);
|
|
167
|
+
ensureLibraryBuilt("@camstack/kernel", this.workspacePackagesDir);
|
|
168
|
+
ensureLibraryBuilt("@camstack/types", this.workspacePackagesDir);
|
|
169
|
+
}
|
|
170
|
+
for (const packageName of _AddonInstaller.REQUIRED_PACKAGES) {
|
|
171
|
+
const addonDir = path2.join(this.addonsDir, packageName);
|
|
172
|
+
const pkgJsonPath = path2.join(addonDir, "package.json");
|
|
173
|
+
if (fs2.existsSync(pkgJsonPath)) {
|
|
174
|
+
if (!this.workspacePackagesDir) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const srcPkgDir = this.findWorkspacePackage(packageName);
|
|
178
|
+
if (srcPkgDir && !isSourceNewer(srcPkgDir)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
if (this.workspacePackagesDir) {
|
|
184
|
+
const pkgDir = this.findWorkspacePackage(packageName);
|
|
185
|
+
if (pkgDir) {
|
|
186
|
+
await this.installFromWorkspace(packageName);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await this.installFromNpm(packageName);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
193
|
+
if (packageName === "@camstack/core") {
|
|
194
|
+
throw new Error(`Required package ${packageName} failed to install: ${msg}`);
|
|
195
|
+
}
|
|
196
|
+
console.error(`[AddonInstaller] Failed to install ${packageName}: ${msg}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
findWorkspacePackage(packageName) {
|
|
201
|
+
if (!this.workspacePackagesDir) return null;
|
|
202
|
+
const shortName = packageName.replace("@camstack/", "");
|
|
203
|
+
for (const dirName of [shortName, `addon-${shortName}`, shortName.replace("addon-", "")]) {
|
|
204
|
+
const candidate = path2.join(this.workspacePackagesDir, dirName);
|
|
205
|
+
if (fs2.existsSync(path2.join(candidate, "package.json"))) {
|
|
206
|
+
try {
|
|
207
|
+
const pkg = JSON.parse(fs2.readFileSync(path2.join(candidate, "package.json"), "utf-8"));
|
|
208
|
+
if (pkg.name === packageName) return candidate;
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
216
|
+
async installFromTgz(tgzPath) {
|
|
217
|
+
const tmpDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "camstack-addon-install-"));
|
|
218
|
+
try {
|
|
219
|
+
await execFileAsync("tar", ["-xzf", tgzPath, "-C", tmpDir], { timeout: 3e4 });
|
|
220
|
+
const extractedDir = path2.join(tmpDir, "package");
|
|
221
|
+
const pkgJsonPath = fs2.existsSync(path2.join(extractedDir, "package.json")) ? path2.join(extractedDir, "package.json") : path2.join(tmpDir, "package.json");
|
|
222
|
+
if (!fs2.existsSync(pkgJsonPath)) {
|
|
223
|
+
throw new Error("No package.json found in tgz archive");
|
|
224
|
+
}
|
|
225
|
+
const pkgJson = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
|
|
226
|
+
if (!pkgJson.camstack?.addons) {
|
|
227
|
+
throw new Error(`Package ${pkgJson.name} has no camstack.addons manifest`);
|
|
228
|
+
}
|
|
229
|
+
const targetDir = path2.join(this.addonsDir, pkgJson.name);
|
|
230
|
+
fs2.rmSync(targetDir, { recursive: true, force: true });
|
|
231
|
+
ensureDir(targetDir);
|
|
232
|
+
const sourceDir = path2.dirname(pkgJsonPath);
|
|
233
|
+
fs2.copyFileSync(pkgJsonPath, path2.join(targetDir, "package.json"));
|
|
234
|
+
const sourceDist = path2.join(sourceDir, "dist");
|
|
235
|
+
if (fs2.existsSync(sourceDist)) {
|
|
236
|
+
copyDirRecursive(sourceDist, path2.join(targetDir, "dist"));
|
|
237
|
+
}
|
|
238
|
+
copyExtraFileDirs(pkgJson, sourceDir, targetDir);
|
|
239
|
+
return { name: pkgJson.name, version: pkgJson.version };
|
|
240
|
+
} finally {
|
|
241
|
+
fs2.rmSync(tmpDir, { recursive: true, force: true });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Install addon — prefers workspace if available, falls back to npm.
|
|
246
|
+
* This ensures dev builds (with vite output, etc.) are used when in workspace mode.
|
|
247
|
+
*/
|
|
248
|
+
async install(packageName, version) {
|
|
249
|
+
if (this.workspacePackagesDir) {
|
|
250
|
+
const workspaceDirName = packageName.replace("@camstack/", "");
|
|
251
|
+
const sourceDir = path2.join(this.workspacePackagesDir, workspaceDirName);
|
|
252
|
+
if (fs2.existsSync(path2.join(sourceDir, "package.json"))) {
|
|
253
|
+
return this.installFromWorkspace(packageName);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return this.installFromNpm(packageName, version);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Install addon from workspace by copying package.json + dist/ to addons dir.
|
|
260
|
+
* Does NOT build — expects dist/ to already exist (run `npm run build` separately).
|
|
261
|
+
*/
|
|
262
|
+
async installFromWorkspace(packageName) {
|
|
263
|
+
if (!this.workspacePackagesDir) {
|
|
264
|
+
throw new Error("Workspace packages directory not configured");
|
|
265
|
+
}
|
|
266
|
+
const workspaceDirName = packageName.replace("@camstack/", "");
|
|
267
|
+
const sourceDir = path2.join(this.workspacePackagesDir, workspaceDirName);
|
|
268
|
+
const sourcePkgJson = path2.join(sourceDir, "package.json");
|
|
269
|
+
if (!fs2.existsSync(sourcePkgJson)) {
|
|
270
|
+
throw new Error(`Workspace package not found: ${sourceDir}`);
|
|
271
|
+
}
|
|
272
|
+
const distDir = path2.join(sourceDir, "dist");
|
|
273
|
+
if (!fs2.existsSync(distDir)) {
|
|
274
|
+
throw new Error(`${packageName} has no dist/ directory \u2014 run 'npm run build' first`);
|
|
275
|
+
}
|
|
276
|
+
const targetDir = path2.join(this.addonsDir, packageName);
|
|
277
|
+
fs2.rmSync(targetDir, { recursive: true, force: true });
|
|
278
|
+
ensureDir(targetDir);
|
|
279
|
+
const pkgData = JSON.parse(fs2.readFileSync(sourcePkgJson, "utf-8"));
|
|
280
|
+
const strippedPkg = stripCamstackDeps(pkgData);
|
|
281
|
+
fs2.writeFileSync(path2.join(targetDir, "package.json"), JSON.stringify(strippedPkg, null, 2));
|
|
282
|
+
copyDirRecursive(distDir, path2.join(targetDir, "dist"));
|
|
283
|
+
copyExtraFileDirs(pkgData, sourceDir, targetDir);
|
|
284
|
+
try {
|
|
285
|
+
await execFileAsync("npm", ["install", "--omit=dev", "--ignore-scripts=false"], {
|
|
286
|
+
cwd: targetDir,
|
|
287
|
+
timeout: 12e4
|
|
288
|
+
});
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
return { name: pkgData.name, version: pkgData.version };
|
|
292
|
+
}
|
|
293
|
+
/** Install addon from npm (download tgz, then extract) */
|
|
294
|
+
async installFromNpm(packageName, version) {
|
|
295
|
+
const tmpDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "camstack-addon-npm-"));
|
|
296
|
+
const packageSpec = version ? `${packageName}@${version}` : packageName;
|
|
297
|
+
const args = ["pack", packageSpec, "--pack-destination", tmpDir];
|
|
298
|
+
if (this.registry) {
|
|
299
|
+
args.push("--registry", this.registry);
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const { stdout } = await execFileAsync("npm", args, {
|
|
303
|
+
timeout: 12e4
|
|
304
|
+
});
|
|
305
|
+
const tgzFiles = fs2.readdirSync(tmpDir).filter((f) => f.endsWith(".tgz"));
|
|
306
|
+
if (tgzFiles.length === 0) {
|
|
307
|
+
throw new Error(`npm pack produced no tgz file for ${packageSpec}. stdout: ${stdout.trim()}`);
|
|
308
|
+
}
|
|
309
|
+
const tgzPath = path2.join(tmpDir, tgzFiles[0]);
|
|
310
|
+
const result = await this.installFromTgz(tgzPath);
|
|
311
|
+
return result;
|
|
312
|
+
} finally {
|
|
313
|
+
fs2.rmSync(tmpDir, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Uninstall addon (delete directory) */
|
|
317
|
+
async uninstall(packageName) {
|
|
318
|
+
const addonDir = path2.join(this.addonsDir, packageName);
|
|
319
|
+
if (fs2.existsSync(addonDir)) {
|
|
320
|
+
fs2.rmSync(addonDir, { recursive: true, force: true });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const legacyDir = path2.join(this.addonsDir, packageName.replace(/^@[^/]+\//, ""));
|
|
324
|
+
if (fs2.existsSync(legacyDir)) {
|
|
325
|
+
fs2.rmSync(legacyDir, { recursive: true, force: true });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** List installed addons (directories with package.json containing camstack.addons) */
|
|
329
|
+
listInstalled() {
|
|
330
|
+
if (!fs2.existsSync(this.addonsDir)) return [];
|
|
331
|
+
return fs2.readdirSync(this.addonsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
332
|
+
const pkgPath = path2.join(this.addonsDir, d.name, "package.json");
|
|
333
|
+
if (!fs2.existsSync(pkgPath)) return null;
|
|
334
|
+
try {
|
|
335
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
336
|
+
if (!pkg.camstack?.addons) return null;
|
|
337
|
+
return { name: pkg.name, version: pkg.version, dir: path2.join(this.addonsDir, d.name) };
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}).filter((item) => item !== null);
|
|
342
|
+
}
|
|
343
|
+
/** Check if an addon is installed */
|
|
344
|
+
isInstalled(packageName) {
|
|
345
|
+
if (fs2.existsSync(path2.join(this.addonsDir, packageName, "package.json"))) return true;
|
|
346
|
+
const legacy = packageName.replace(/^@[^/]+\//, "");
|
|
347
|
+
return fs2.existsSync(path2.join(this.addonsDir, legacy, "package.json"));
|
|
348
|
+
}
|
|
349
|
+
/** Get installed package info */
|
|
350
|
+
getInstalledPackage(packageName) {
|
|
351
|
+
let pkgPath = path2.join(this.addonsDir, packageName, "package.json");
|
|
352
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
353
|
+
pkgPath = path2.join(this.addonsDir, packageName.replace(/^@[^/]+\//, ""), "package.json");
|
|
354
|
+
}
|
|
355
|
+
if (!fs2.existsSync(pkgPath)) return null;
|
|
356
|
+
try {
|
|
357
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
358
|
+
return { name: pkg.name, version: pkg.version, dir: path2.dirname(pkgPath) };
|
|
359
|
+
} catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/worker/addon-worker-entry.ts
|
|
368
|
+
var os2 = __toESM(require("os"));
|
|
369
|
+
var fs3 = __toESM(require("fs"));
|
|
370
|
+
var path3 = __toESM(require("path"));
|
|
371
|
+
|
|
372
|
+
// src/worker/worker-process-manager.ts
|
|
373
|
+
var import_node_child_process = require("child_process");
|
|
374
|
+
var WorkerProcessManager = class {
|
|
375
|
+
constructor(sendToMain) {
|
|
376
|
+
this.sendToMain = sendToMain;
|
|
377
|
+
}
|
|
378
|
+
processes = /* @__PURE__ */ new Map();
|
|
379
|
+
async spawn(config) {
|
|
380
|
+
const child = (0, import_node_child_process.spawn)(config.command, [...config.args ?? []], {
|
|
381
|
+
cwd: config.cwd,
|
|
382
|
+
env: config.env ? { ...process.env, ...config.env } : void 0,
|
|
383
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
384
|
+
});
|
|
385
|
+
const managed = new ManagedProcess(child, config, this.sendToMain);
|
|
386
|
+
this.processes.set(child.pid, managed);
|
|
387
|
+
this.sendToMain({
|
|
388
|
+
type: "SUB_PROCESS_SPAWNED",
|
|
389
|
+
pid: child.pid,
|
|
390
|
+
name: config.name,
|
|
391
|
+
command: config.command
|
|
392
|
+
});
|
|
393
|
+
child.on("exit", (code) => {
|
|
394
|
+
this.sendToMain({
|
|
395
|
+
type: "SUB_PROCESS_EXITED",
|
|
396
|
+
pid: child.pid,
|
|
397
|
+
code
|
|
398
|
+
});
|
|
399
|
+
if (config.autoRestart && managed.restartCount < (config.maxRestarts ?? 3)) {
|
|
400
|
+
managed.restartCount++;
|
|
401
|
+
setTimeout(() => {
|
|
402
|
+
this.spawn(config).catch(() => {
|
|
403
|
+
});
|
|
404
|
+
}, 2e3);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
return managed;
|
|
408
|
+
}
|
|
409
|
+
listProcesses() {
|
|
410
|
+
return [...this.processes.values()].map((p) => p.toInfo());
|
|
411
|
+
}
|
|
412
|
+
getWorkerStats() {
|
|
413
|
+
const mem = process.memoryUsage();
|
|
414
|
+
const cpu = process.cpuUsage();
|
|
415
|
+
return {
|
|
416
|
+
pid: process.pid,
|
|
417
|
+
cpuPercent: (cpu.user + cpu.system) / 1e6,
|
|
418
|
+
memoryRss: mem.rss,
|
|
419
|
+
heapUsed: mem.heapUsed,
|
|
420
|
+
uptimeSeconds: Math.round(process.uptime()),
|
|
421
|
+
restartCount: 0,
|
|
422
|
+
state: "running"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
async killAll() {
|
|
426
|
+
for (const [, proc] of this.processes) {
|
|
427
|
+
proc.kill("SIGTERM");
|
|
428
|
+
}
|
|
429
|
+
this.processes.clear();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var ManagedProcess = class {
|
|
433
|
+
constructor(child, config, sendToMain) {
|
|
434
|
+
this.child = child;
|
|
435
|
+
this.config = config;
|
|
436
|
+
this.sendToMain = sendToMain;
|
|
437
|
+
this.pid = child.pid;
|
|
438
|
+
this.name = config.name;
|
|
439
|
+
child.on("exit", (code) => {
|
|
440
|
+
for (const handler of this.exitHandlers) handler(code);
|
|
441
|
+
});
|
|
442
|
+
child.on("error", (err) => {
|
|
443
|
+
for (const handler of this.errorHandlers) handler(err);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
pid;
|
|
447
|
+
name;
|
|
448
|
+
restartCount = 0;
|
|
449
|
+
startedAt = Date.now();
|
|
450
|
+
exitHandlers = [];
|
|
451
|
+
errorHandlers = [];
|
|
452
|
+
getStats() {
|
|
453
|
+
return {
|
|
454
|
+
pid: this.pid,
|
|
455
|
+
cpuPercent: 0,
|
|
456
|
+
memoryRss: 0,
|
|
457
|
+
uptimeSeconds: Math.round((Date.now() - this.startedAt) / 1e3),
|
|
458
|
+
restartCount: this.restartCount,
|
|
459
|
+
state: this.child.exitCode !== null ? "stopped" : "running"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
write(data) {
|
|
463
|
+
this.child.stdin?.write(data);
|
|
464
|
+
}
|
|
465
|
+
get stdout() {
|
|
466
|
+
return this.child.stdout;
|
|
467
|
+
}
|
|
468
|
+
get stderr() {
|
|
469
|
+
return this.child.stderr;
|
|
470
|
+
}
|
|
471
|
+
kill(signal = "SIGTERM") {
|
|
472
|
+
this.child.kill(signal);
|
|
473
|
+
}
|
|
474
|
+
wait() {
|
|
475
|
+
if (this.child.exitCode !== null) {
|
|
476
|
+
return Promise.resolve({ code: this.child.exitCode, signal: null });
|
|
477
|
+
}
|
|
478
|
+
return new Promise((resolve2) => {
|
|
479
|
+
this.child.on("exit", (code, signal) => {
|
|
480
|
+
resolve2({ code, signal });
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
onExit(handler) {
|
|
485
|
+
this.exitHandlers.push(handler);
|
|
486
|
+
}
|
|
487
|
+
onError(handler) {
|
|
488
|
+
this.errorHandlers.push(handler);
|
|
489
|
+
}
|
|
490
|
+
toInfo() {
|
|
491
|
+
return {
|
|
492
|
+
pid: this.pid,
|
|
493
|
+
name: this.name,
|
|
494
|
+
command: this.config.command,
|
|
495
|
+
state: this.child.exitCode !== null ? "stopped" : "running",
|
|
496
|
+
cpuPercent: 0,
|
|
497
|
+
memoryRss: 0,
|
|
498
|
+
uptimeSeconds: Math.round((Date.now() - this.startedAt) / 1e3)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/worker/addon-worker-entry.ts
|
|
504
|
+
async function boot() {
|
|
505
|
+
const hubUrl = process.env.CAMSTACK_WORKER_HUB_URL;
|
|
506
|
+
const token = process.env.CAMSTACK_WORKER_TOKEN ?? "";
|
|
507
|
+
const addonId = process.env.CAMSTACK_ADDON_ID;
|
|
508
|
+
const addonDir = process.env.CAMSTACK_ADDON_DIR;
|
|
509
|
+
const dataDir = process.env.CAMSTACK_DATA_DIR ?? `/tmp/camstack-worker-${addonId}`;
|
|
510
|
+
const addonConfig = JSON.parse(process.env.CAMSTACK_ADDON_CONFIG ?? "{}");
|
|
511
|
+
const locationPaths = JSON.parse(process.env.CAMSTACK_LOCATION_PATHS ?? "{}");
|
|
512
|
+
if (!hubUrl || !addonId || !addonDir) {
|
|
513
|
+
console.error("[worker] Missing required env vars: CAMSTACK_WORKER_HUB_URL, CAMSTACK_ADDON_ID, CAMSTACK_ADDON_DIR");
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
const workerId = `worker-${addonId}-${process.pid}`;
|
|
517
|
+
const { createTRPCClient, createWSClient, wsLink } = await import("@trpc/client");
|
|
518
|
+
const superjson = (await import("superjson")).default;
|
|
519
|
+
const wsClient = createWSClient({
|
|
520
|
+
url: hubUrl,
|
|
521
|
+
connectionParams: () => ({ token })
|
|
522
|
+
});
|
|
523
|
+
const api = createTRPCClient({
|
|
524
|
+
links: [wsLink({ client: wsClient, transformer: superjson })]
|
|
525
|
+
});
|
|
526
|
+
try {
|
|
527
|
+
await api.agent.register.mutate({
|
|
528
|
+
id: workerId,
|
|
529
|
+
name: addonId,
|
|
530
|
+
capabilities: [],
|
|
531
|
+
platform: os2.platform(),
|
|
532
|
+
arch: os2.arch(),
|
|
533
|
+
cpuCores: os2.cpus().length,
|
|
534
|
+
memoryMB: Math.round(os2.totalmem() / 1024 / 1024),
|
|
535
|
+
pythonRuntimes: [],
|
|
536
|
+
installedAddons: [addonId],
|
|
537
|
+
taskTypes: [],
|
|
538
|
+
host: os2.hostname(),
|
|
539
|
+
port: 0,
|
|
540
|
+
httpPort: 0
|
|
541
|
+
});
|
|
542
|
+
console.log(`[worker:${addonId}] Registered with hub as ${workerId}`);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
console.error(`[worker:${addonId}] Failed to register:`, err instanceof Error ? err.message : err);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
function createScopedLogger(scope) {
|
|
548
|
+
const sendLog = (level, message, context2) => {
|
|
549
|
+
if (process.send) {
|
|
550
|
+
process.send({ type: "LOG", level, message, context: context2 });
|
|
551
|
+
} else {
|
|
552
|
+
const prefix = `[${scope.join(":")}]`;
|
|
553
|
+
if (level === "error") console.error(prefix, message, context2 ?? "");
|
|
554
|
+
else if (level === "warn") console.warn(prefix, message, context2 ?? "");
|
|
555
|
+
else if (level === "debug") console.debug(prefix, message, context2 ?? "");
|
|
556
|
+
else console.log(prefix, message, context2 ?? "");
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
return {
|
|
560
|
+
info: (msg, ctx) => sendLog("info", msg, ctx),
|
|
561
|
+
warn: (msg, ctx) => sendLog("warn", msg, ctx),
|
|
562
|
+
error: (msg, ctx) => sendLog("error", msg, ctx),
|
|
563
|
+
debug: (msg, ctx) => sendLog("debug", msg, ctx),
|
|
564
|
+
child: (childScope) => createScopedLogger([...scope, childScope])
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const logger = createScopedLogger([addonId]);
|
|
568
|
+
const processManager = new WorkerProcessManager(() => {
|
|
569
|
+
});
|
|
570
|
+
const pkgJsonPath = path3.join(addonDir, "package.json");
|
|
571
|
+
const pkgJson = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
|
|
572
|
+
const manifest = pkgJson.camstack;
|
|
573
|
+
if (!manifest?.addons?.length) {
|
|
574
|
+
console.error(`[worker:${addonId}] No camstack addon manifest in ${pkgJsonPath}`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
const declaration = manifest.addons.find((a) => a.id === addonId);
|
|
578
|
+
if (!declaration) {
|
|
579
|
+
console.error(`[worker:${addonId}] Addon "${addonId}" not found in manifest`);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
const entryFile = declaration.entry.replace(/^\.\//, "").replace(/^src\//, "dist/").replace(/\.ts$/, ".js");
|
|
583
|
+
let entryPath = path3.resolve(addonDir, entryFile);
|
|
584
|
+
if (!fs3.existsSync(entryPath)) {
|
|
585
|
+
const cjsPath = entryPath.replace(/\.js$/, ".cjs");
|
|
586
|
+
if (fs3.existsSync(cjsPath)) entryPath = cjsPath;
|
|
587
|
+
}
|
|
588
|
+
const mod = await import(entryPath);
|
|
589
|
+
const firstKey = Object.keys(mod)[0];
|
|
590
|
+
const AddonClass = mod.default?.default ?? mod.default ?? (firstKey ? mod[firstKey] : void 0);
|
|
591
|
+
if (typeof AddonClass !== "function") {
|
|
592
|
+
console.error(`[worker:${addonId}] No addon class found in ${entryPath}`);
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
const addon = new AddonClass();
|
|
596
|
+
const context = {
|
|
597
|
+
id: addonId,
|
|
598
|
+
api,
|
|
599
|
+
logger,
|
|
600
|
+
addonConfig,
|
|
601
|
+
process: processManager,
|
|
602
|
+
dataDir,
|
|
603
|
+
locationPaths,
|
|
604
|
+
// Legacy fields — stubs for backward compat during migration
|
|
605
|
+
eventBus: { emit: () => {
|
|
606
|
+
}, subscribe: () => () => {
|
|
607
|
+
}, getRecent: () => [] },
|
|
608
|
+
storage: {},
|
|
609
|
+
config: { get: () => void 0, set: async () => {
|
|
610
|
+
} }
|
|
611
|
+
};
|
|
612
|
+
await addon.initialize(context);
|
|
613
|
+
console.log(`[worker:${addonId}] Addon initialized`);
|
|
614
|
+
const heartbeatInterval = setInterval(async () => {
|
|
615
|
+
try {
|
|
616
|
+
const mem = process.memoryUsage();
|
|
617
|
+
const subProcs = processManager.listProcesses().map((p) => ({
|
|
618
|
+
pid: p.pid,
|
|
619
|
+
name: p.name,
|
|
620
|
+
command: p.command,
|
|
621
|
+
state: p.state,
|
|
622
|
+
cpuPercent: p.cpuPercent,
|
|
623
|
+
memoryRss: p.memoryRss,
|
|
624
|
+
uptimeSeconds: p.uptimeSeconds
|
|
625
|
+
}));
|
|
626
|
+
await api.agent.heartbeat.mutate({
|
|
627
|
+
agentId: workerId,
|
|
628
|
+
cpuPercent: 0,
|
|
629
|
+
memoryUsedMB: Math.round(mem.rss / 1024 / 1024),
|
|
630
|
+
activeTasks: 0,
|
|
631
|
+
subProcesses: subProcs
|
|
632
|
+
});
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
}, 1e4);
|
|
636
|
+
try {
|
|
637
|
+
;
|
|
638
|
+
api.agent.onTaskAssignment.subscribe(void 0, {
|
|
639
|
+
onData: async (task) => {
|
|
640
|
+
try {
|
|
641
|
+
let result;
|
|
642
|
+
if (task.taskType === "addon.install") {
|
|
643
|
+
const { AddonInstaller: AddonInstaller2 } = await Promise.resolve().then(() => (init_addon_installer(), addon_installer_exports));
|
|
644
|
+
const installer = new AddonInstaller2({ addonsDir: path3.dirname(addonDir) });
|
|
645
|
+
const payload = task.payload;
|
|
646
|
+
result = await installer.installFromNpm(payload.package, payload.version);
|
|
647
|
+
} else if (typeof addon.handleTask === "function") {
|
|
648
|
+
result = await addon.handleTask(task.taskType, task.payload);
|
|
649
|
+
}
|
|
650
|
+
await api.agent.reportTaskResult.mutate({
|
|
651
|
+
taskId: task.taskId,
|
|
652
|
+
success: true,
|
|
653
|
+
result
|
|
654
|
+
});
|
|
655
|
+
} catch (err) {
|
|
656
|
+
await api.agent.reportTaskResult.mutate({
|
|
657
|
+
taskId: task.taskId,
|
|
658
|
+
success: false,
|
|
659
|
+
error: err instanceof Error ? err.message : String(err)
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
const shutdown = async () => {
|
|
667
|
+
clearInterval(heartbeatInterval);
|
|
668
|
+
await processManager.killAll();
|
|
669
|
+
await addon.shutdown();
|
|
670
|
+
wsClient.close();
|
|
671
|
+
process.exit(0);
|
|
672
|
+
};
|
|
673
|
+
process.on("SIGTERM", () => shutdown().catch(() => process.exit(1)));
|
|
674
|
+
process.on("SIGINT", () => shutdown().catch(() => process.exit(1)));
|
|
675
|
+
process.send?.({ type: "READY" });
|
|
676
|
+
}
|
|
677
|
+
process.on("uncaughtException", (err) => {
|
|
678
|
+
console.error("[worker] Uncaught exception:", err.message, err.stack);
|
|
679
|
+
});
|
|
680
|
+
process.on("unhandledRejection", (reason) => {
|
|
681
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
682
|
+
console.error("[worker] Unhandled rejection:", msg);
|
|
683
|
+
});
|
|
684
|
+
boot().catch((err) => {
|
|
685
|
+
console.error("[worker] Boot failed:", err);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
});
|
|
688
|
+
//# sourceMappingURL=addon-worker-entry.js.map
|