@camstack/system 1.2.2 → 1.2.4

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.
Files changed (51) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +6 -2
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +6 -2
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  21. package/dist/builtins/local-network/local-network.addon.js +1 -1
  22. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  23. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  24. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  25. package/dist/builtins/platform-probe/index.js +1 -1
  26. package/dist/builtins/platform-probe/index.mjs +1 -1
  27. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  28. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  29. package/dist/builtins/snapshot/index.js +1 -1
  30. package/dist/builtins/snapshot/index.mjs +1 -1
  31. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  32. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  33. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  34. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  35. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  36. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/system-config/system-config.addon.js +1 -1
  38. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  39. package/dist/builtins/winston-logging/index.js +1 -1
  40. package/dist/builtins/winston-logging/index.mjs +1 -1
  41. package/dist/{dist-DTTTjPCL.mjs → dist-72yufD8v.mjs} +2 -1
  42. package/dist/{dist-CkdNC8US.js → dist-CQZxGJ8u.js} +2 -1
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js +289 -12
  45. package/dist/index.mjs +286 -14
  46. package/dist/kernel/deps/ensure-addon-natives.d.ts +41 -0
  47. package/dist/kernel/deps/ensure-native-prebuilds.d.ts +40 -0
  48. package/dist/kernel/deps/manifest-native-deps.d.ts +17 -0
  49. package/dist/{manifest-python-deps-s_-uULmp.mjs → manifest-python-deps-6__QF75g.mjs} +88 -2
  50. package/dist/{manifest-python-deps-DRq5MreI.js → manifest-python-deps-Cd7ocUBi.js} +93 -1
  51. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { At as EventCategory$1, Dt as resolveCapMount, Et as readinessKey, F as kebabToCamel, I as lifecycleJobSchema, K as procedureAuthKey, M as isCollectionArrayMethod, N as isObjectInput, O as extractNestedAddonId, Ot as scopeKey, P as isVoidInput, Tt as parseJsonUnknown$1, U as objectInputDeclaresAddonId, _t as asString$1, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, j as isArrayOutputSchema, o as METHOD_ACCESS_MAP, ot as DATAPLANE_SECRET_HEADER$1, p as addonSettingsCapability, pt as ReadinessTimeoutError, s as RUNTIME_DEFAULTS, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, wt as parseJsonObject, xt as expandCapMethods, yt as emitDownForOwnedCaps, z as looseSchema } from "./dist-DTTTjPCL.mjs";
1
+ import { At as EventCategory$1, Dt as resolveCapMount, Et as readinessKey, F as kebabToCamel, I as lifecycleJobSchema, K as procedureAuthKey, M as isCollectionArrayMethod, N as isObjectInput, O as extractNestedAddonId, Ot as scopeKey, P as isVoidInput, Tt as parseJsonUnknown$1, U as objectInputDeclaresAddonId, _t as asString$1, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, j as isArrayOutputSchema, o as METHOD_ACCESS_MAP, ot as DATAPLANE_SECRET_HEADER$1, p as addonSettingsCapability, pt as ReadinessTimeoutError, s as RUNTIME_DEFAULTS, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, wt as parseJsonObject, xt as expandCapMethods, yt as emitDownForOwnedCaps, z as looseSchema } from "./dist-72yufD8v.mjs";
2
2
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
3
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveAddonClass, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveNpmInvocation, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, xt as runNpm, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-s_-uULmp.mjs";
3
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-6__QF75g.mjs";
4
4
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Le3l0qS2.mjs";
5
5
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
6
6
  import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -29,7 +29,7 @@ import { accessSync, constants, existsSync, mkdirSync, readFileSync } from "node
29
29
  import * as path$39 from "node:path";
30
30
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
31
31
  import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
32
- import { asJsonObject, parseJsonUnknown } from "@camstack/types/addon";
32
+ import { asJsonObject, errMsg, parseJsonUnknown } from "@camstack/types/addon";
33
33
  import { execFile, spawn } from "node:child_process";
34
34
  import * as util$10 from "node:util";
35
35
  import { promisify } from "node:util";
@@ -176,7 +176,7 @@ function proxyToUpstream(opts) {
176
176
  }
177
177
  //#endregion
178
178
  //#region src/python/python-env-manager.ts
179
- var execFileAsync$3 = promisify(execFile);
179
+ var execFileAsync$4 = promisify(execFile);
180
180
  var PythonEnvManager = class {
181
181
  venvPath;
182
182
  cachedProbe = null;
@@ -186,12 +186,12 @@ var PythonEnvManager = class {
186
186
  async probe() {
187
187
  if (this.cachedProbe) return this.cachedProbe;
188
188
  for (const cmd of ["python3", "python"]) try {
189
- const { stdout } = await execFileAsync$3(cmd, ["--version"]);
189
+ const { stdout } = await execFileAsync$4(cmd, ["--version"]);
190
190
  const version = stdout.trim().replace("Python ", "");
191
191
  const major = parseInt(version.split(".")[0] ?? "0", 10);
192
192
  const minor = parseInt(version.split(".")[1] ?? "0", 10);
193
193
  if (major < 3 || major === 3 && minor < 10) continue;
194
- const { stdout: pathOut } = await execFileAsync$3(cmd, ["-c", "import sys; print(sys.executable)"]);
194
+ const { stdout: pathOut } = await execFileAsync$4(cmd, ["-c", "import sys; print(sys.executable)"]);
195
195
  this.cachedProbe = {
196
196
  available: true,
197
197
  version,
@@ -207,13 +207,13 @@ var PythonEnvManager = class {
207
207
  async ensure(options) {
208
208
  const probe = await this.probe();
209
209
  if (!probe.available || !probe.path) throw new Error("Python 3.10+ is required but not found on this system");
210
- if (!fs$17.existsSync(path$39.join(this.venvPath, "bin", "python"))) await execFileAsync$3(probe.path, [
210
+ if (!fs$17.existsSync(path$39.join(this.venvPath, "bin", "python"))) await execFileAsync$4(probe.path, [
211
211
  "-m",
212
212
  "venv",
213
213
  this.venvPath
214
214
  ]);
215
215
  const venvPython = path$39.join(this.venvPath, "bin", "python");
216
- if (options.packages.length > 0) await execFileAsync$3(venvPython, [
216
+ if (options.packages.length > 0) await execFileAsync$4(venvPython, [
217
217
  "-m",
218
218
  "pip",
219
219
  "install",
@@ -1684,7 +1684,7 @@ function matchPath(pattern, path) {
1684
1684
  }
1685
1685
  //#endregion
1686
1686
  //#region src/tls/cert-manager.ts
1687
- var execFileAsync$2 = promisify(execFile);
1687
+ var execFileAsync$3 = promisify(execFile);
1688
1688
  /**
1689
1689
  * Ensure a self-signed TLS certificate exists in the given directory.
1690
1690
  * Generates one if missing. Returns paths to cert and key files.
@@ -1721,7 +1721,7 @@ async function ensureTlsCert(dataDir, options) {
1721
1721
  for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
1722
1722
  for (const ip of sanIps) sanParts.push(`IP:${ip}`);
1723
1723
  const sanString = sanParts.join(",");
1724
- await execFileAsync$2("openssl", [
1724
+ await execFileAsync$3("openssl", [
1725
1725
  "req",
1726
1726
  "-x509",
1727
1727
  "-newkey",
@@ -2589,7 +2589,7 @@ var AddonEngineManager = class {
2589
2589
  };
2590
2590
  //#endregion
2591
2591
  //#region src/kernel/fs-utils.ts
2592
- var execFileAsync$1 = promisify(execFile);
2592
+ var execFileAsync$2 = promisify(execFile);
2593
2593
  /**
2594
2594
  * Ensure a directory exists (recursive).
2595
2595
  * Single source of truth — replaces scattered mkdirSync calls.
@@ -2773,7 +2773,7 @@ function ensureLibraryBuilt(packageName, packagesDir) {
2773
2773
  async function installPackageFromNpm(packageName, targetDir) {
2774
2774
  const tmpDir = await fs$17.promises.mkdtemp(path$39.join(os$18.tmpdir(), "camstack-install-"));
2775
2775
  try {
2776
- const { stdout } = await execFileAsync$1("npm", [
2776
+ const { stdout } = await execFileAsync$2("npm", [
2777
2777
  "pack",
2778
2778
  packageName,
2779
2779
  "--pack-destination",
@@ -2787,7 +2787,7 @@ async function installPackageFromNpm(packageName, targetDir) {
2787
2787
  const tgzPath = path$39.join(tmpDir, tgzFilename);
2788
2788
  const extractDir = path$39.join(tmpDir, "extracted");
2789
2789
  ensureDir(extractDir);
2790
- await execFileAsync$1("tar", [
2790
+ await execFileAsync$2("tar", [
2791
2791
  "-xzf",
2792
2792
  tgzPath,
2793
2793
  "-C",
@@ -3022,6 +3022,268 @@ var AddonManifest = class {
3022
3022
  }
3023
3023
  };
3024
3024
  //#endregion
3025
+ //#region src/kernel/deps/ensure-native-prebuilds.ts
3026
+ /**
3027
+ * `ensureNativePrebuilds` — the post-install native-prebuild ENSURE step the
3028
+ * shared `RootUpdateService` runs while staging a `@camstack/server` closure,
3029
+ * BEFORE its `findMissingNativePrebuilds` validator (in `@camstack/node-root`)
3030
+ * decides whether to arm the swap.
3031
+ *
3032
+ * Why this exists (Mac desktop incident, 2026-07-21): a runtime
3033
+ * `applyServerUpdate` on a packaged desktop stages the closure with
3034
+ * `npm install @camstack/server@X` under the app's BUNDLED bare Node. The one
3035
+ * TRADITIONAL (non-N-API) native, `better-sqlite3`, resolves its `.node` via
3036
+ * its `install` script `prebuild-install || node-gyp rebuild`. On a user's Mac
3037
+ * with no build toolchain (and where the auto `prebuild-install` does not
3038
+ * target the bundled Node's ABI/arch the way the DMG build's EXPLICIT
3039
+ * `prebuild-install --runtime node --target <bundled> --arch <arch>` call does)
3040
+ * that leaves the module SOURCE-ONLY → the closure has no `better-sqlite3`
3041
+ * `.node` → the validator refuses the swap ("staged closure missing native
3042
+ * prebuilds: better-sqlite3"). The docker image never hits this: its Node ABI
3043
+ * has a published prebuild the auto path fetches cleanly, and it carries a
3044
+ * build toolchain fallback.
3045
+ *
3046
+ * This mirrors the DMG build's explicit re-fetch (`scripts/stage-deps.mjs`)
3047
+ * into the runtime staging path: for each traditional native that is present
3048
+ * in the closure but missing its compiled `.node`, fetch the prebuild for the
3049
+ * RUNNING Node ABI/arch (the same Node that will load the swapped closure).
3050
+ *
3051
+ * N-API natives (`sharp`, `node-av`) are ABI-stable and ship their binaries as
3052
+ * platform SIBLING packages that npm fetches reliably with no compile step, so
3053
+ * they are not in scope here — a genuine miss there is a broken install the
3054
+ * validator should still reject, not something a prebuild re-fetch can heal.
3055
+ *
3056
+ * Injected into the update service from the construction site so the zero-dep
3057
+ * `@camstack/node-root` layer never depends on npm/@camstack/system, and the
3058
+ * fetch is unit-testable with a fake fetcher.
3059
+ */
3060
+ var execFileAsync$1 = promisify(execFile);
3061
+ /** Bounded recursive `.node` scan depth inside a module dir. */
3062
+ var NATIVE_SCAN_DEPTH = 4;
3063
+ /** Timeout for a single `prebuild-install` fetch. */
3064
+ var PREBUILD_FETCH_TIMEOUT_MS = 12e4;
3065
+ /**
3066
+ * Traditional (compile-or-prebuild) natives an installed closure must carry a
3067
+ * `.node` for. Only these are ENSURE-able by a prebuild re-fetch; N-API natives
3068
+ * are excluded (see file header).
3069
+ */
3070
+ var TRADITIONAL_NATIVE_PACKAGES = ["better-sqlite3"];
3071
+ /** Bounded recursive scan for ANY `*.node` file under `dir`. */
3072
+ function hasDotNode(dir, maxDepth) {
3073
+ let entries;
3074
+ try {
3075
+ entries = fs$17.readdirSync(dir, { withFileTypes: true });
3076
+ } catch {
3077
+ return false;
3078
+ }
3079
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".node")) return true;
3080
+ if (maxDepth <= 0) return false;
3081
+ for (const entry of entries) if (entry.isDirectory() && hasDotNode(path$39.join(dir, entry.name), maxDepth - 1)) return true;
3082
+ return false;
3083
+ }
3084
+ /** Locate the bundled `prebuild-install` bin.js inside the staged closure. */
3085
+ function resolvePrebuildInstallBin(closureDir, moduleDir) {
3086
+ return [path$39.join(moduleDir, "node_modules", "prebuild-install", "bin.js"), path$39.join(closureDir, "node_modules", "prebuild-install", "bin.js")].find((c) => fs$17.existsSync(c)) ?? null;
3087
+ }
3088
+ /**
3089
+ * Default fetcher: run the closure's own `prebuild-install` under the CURRENT
3090
+ * Node (`process.execPath`) — no reliance on `npx`/`npm` on PATH (a packaged
3091
+ * app ships a bare Node). Explicitly targets the running ABI/arch, mirroring
3092
+ * `scripts/stage-deps.mjs`.
3093
+ */
3094
+ function makeDefaultFetch(closureDir, logger) {
3095
+ return async (moduleDir, target) => {
3096
+ const bin = resolvePrebuildInstallBin(closureDir, moduleDir);
3097
+ if (bin === null) throw new Error(`prebuild-install not found in the staged closure for ${moduleDir}`);
3098
+ logger.info("fetching native prebuild via prebuild-install", { meta: {
3099
+ moduleDir,
3100
+ bin,
3101
+ nodeVersion: target.nodeVersion,
3102
+ arch: target.arch
3103
+ } });
3104
+ await execFileAsync$1(process.execPath, [
3105
+ bin,
3106
+ "--runtime",
3107
+ "node",
3108
+ "--target",
3109
+ target.nodeVersion,
3110
+ "--arch",
3111
+ target.arch
3112
+ ], {
3113
+ cwd: moduleDir,
3114
+ timeout: PREBUILD_FETCH_TIMEOUT_MS
3115
+ });
3116
+ };
3117
+ }
3118
+ /**
3119
+ * Ensure every traditional native in `<closureDir>/node_modules` carries its
3120
+ * compiled `.node`, fetching the prebuild for the running ABI when missing.
3121
+ *
3122
+ * A NO-OP when nothing is missing (docker / tests) and when a native package is
3123
+ * absent from the closure. Never throws for a single package's fetch failure —
3124
+ * it logs and continues so the downstream `findMissingNativePrebuilds`
3125
+ * validator makes the final swap/abort decision. Returns the packages whose
3126
+ * prebuild was successfully fetched (for logging/tests).
3127
+ */
3128
+ async function ensureNativePrebuilds(closureDir, options) {
3129
+ const nm = path$39.join(closureDir, "node_modules");
3130
+ const target = options.target ?? {
3131
+ nodeVersion: process.versions.node,
3132
+ arch: process.arch
3133
+ };
3134
+ const packages = options.packages ?? TRADITIONAL_NATIVE_PACKAGES;
3135
+ const fetchPrebuild = options.fetchPrebuild ?? makeDefaultFetch(closureDir, options.logger);
3136
+ const fetched = [];
3137
+ for (const pkg of packages) {
3138
+ const moduleDir = path$39.join(nm, pkg);
3139
+ if (!fs$17.existsSync(moduleDir)) continue;
3140
+ if (hasDotNode(moduleDir, 4)) continue;
3141
+ options.logger.warn("staged closure native prebuild missing — fetching for running ABI", { meta: {
3142
+ pkg,
3143
+ moduleDir,
3144
+ nodeVersion: target.nodeVersion,
3145
+ arch: target.arch
3146
+ } });
3147
+ try {
3148
+ await fetchPrebuild(moduleDir, target);
3149
+ } catch (err) {
3150
+ options.logger.error("native prebuild fetch failed (validator will decide)", { meta: {
3151
+ pkg,
3152
+ error: errMsg(err)
3153
+ } });
3154
+ continue;
3155
+ }
3156
+ if (hasDotNode(moduleDir, 4)) {
3157
+ fetched.push(pkg);
3158
+ options.logger.info("native prebuild fetched", { meta: { pkg } });
3159
+ } else options.logger.error("native prebuild still missing after fetch", { meta: { pkg } });
3160
+ }
3161
+ return fetched;
3162
+ }
3163
+ //#endregion
3164
+ //#region src/kernel/deps/ensure-addon-natives.ts
3165
+ /**
3166
+ * `ensureAddonNativePrebuilds` — the POST-install native-prebuild ENSURE pass an
3167
+ * addon install runs on the INSTALLED addon dir AFTER `npm install`.
3168
+ *
3169
+ * Why this exists (Mac desktop first-boot FATAL, 2026-07-21): `installCopy`
3170
+ * copies `dist/`, plants prebuilt natives from the bundled addon copy
3171
+ * (`copyBundledNativeModules`), then `npm install`s the addon's runtime deps.
3172
+ * On a TOOLCHAIN-LESS host that `npm install` RE-CREATES the traditional native
3173
+ * `better-sqlite3` SOURCE-ONLY — clobbering the good `.node` the pre-install
3174
+ * copy just planted — and the runner later dies at addon init with a cryptic
3175
+ * "Could not locate the bindings file". The copy-BEFORE-install fix was
3176
+ * empirically insufficient: npm's own install in the copied dir replaces the
3177
+ * satisfied dep without its binary.
3178
+ *
3179
+ * This pass runs LAST, so it always sees the final on-disk state npm left, and
3180
+ * per referenced traditional native still lacking its compiled `.node`:
3181
+ * 1. bundled-copy first — if the bundled addon copy carries the matching
3182
+ * `.node` (same machine → same platform/arch/ABI), re-plant it DIRECTLY.
3183
+ * Cheapest + offline-safe; runs BEFORE any network fetch.
3184
+ * 2. fetch fallback — otherwise fetch the prebuild for the running ABI via the
3185
+ * shared `ensureNativePrebuilds` pointed at the addon dir.
3186
+ * 3. FAIL LOUD — if the native is STILL source-only after both, log a clear
3187
+ * error naming module + addon and THROW so the install fails at install
3188
+ * time rather than at addon init.
3189
+ *
3190
+ * N-API natives (`sharp`, `node-av`) ship their binaries as ABI-stable platform
3191
+ * SIBLING packages npm fetches without a compile step, so they are excluded from
3192
+ * the default set — a miss there is a broken install, not something a prebuild
3193
+ * re-fetch heals (same rationale as `ensureNativePrebuilds`).
3194
+ */
3195
+ /**
3196
+ * Re-plant `<srcModuleDir>` → `<dstModuleDir>` when the source carries a `.node`.
3197
+ * Returns whether the destination ends up with a compiled binary.
3198
+ */
3199
+ function replantFromBundled(srcModuleDir, dstModuleDir, hasNative) {
3200
+ if (!hasNative(srcModuleDir)) return false;
3201
+ try {
3202
+ fs$17.rmSync(dstModuleDir, {
3203
+ recursive: true,
3204
+ force: true
3205
+ });
3206
+ fs$17.mkdirSync(path$39.dirname(dstModuleDir), { recursive: true });
3207
+ fs$17.cpSync(srcModuleDir, dstModuleDir, {
3208
+ recursive: true,
3209
+ dereference: true
3210
+ });
3211
+ } catch {
3212
+ return false;
3213
+ }
3214
+ return hasNative(dstModuleDir);
3215
+ }
3216
+ /**
3217
+ * Ensure every referenced traditional native in `<addonDir>/node_modules`
3218
+ * carries its compiled `.node` after the addon's `npm install`. Bundled-copy
3219
+ * first (offline), prebuild-fetch fallback, LOUD throw on a residual miss.
3220
+ *
3221
+ * SKIPS a native absent from the addon's own `node_modules` (it resolves via a
3222
+ * hoisted host copy — not this addon's concern). A NO-OP in docker / dev where
3223
+ * npm produced a working `.node` (already-present). Returns the per-native
3224
+ * outcomes for logging/tests.
3225
+ *
3226
+ * @throws if any required native is still source-only after copy + fetch.
3227
+ */
3228
+ async function ensureAddonNativePrebuilds(addonDir, options) {
3229
+ const packages = options.packages ?? TRADITIONAL_NATIVE_PACKAGES;
3230
+ const hasNative = options.hasNative ?? ((dir) => hasDotNode(dir, 4));
3231
+ const nm = path$39.join(addonDir, "node_modules");
3232
+ const results = [];
3233
+ const missing = [];
3234
+ for (const pkg of packages) {
3235
+ const moduleDir = path$39.join(nm, pkg);
3236
+ if (!fs$17.existsSync(moduleDir)) continue;
3237
+ if (hasNative(moduleDir)) {
3238
+ results.push({
3239
+ pkg,
3240
+ outcome: "already-present"
3241
+ });
3242
+ continue;
3243
+ }
3244
+ if (options.bundledSourceDir) {
3245
+ const src = path$39.join(options.bundledSourceDir, "node_modules", pkg);
3246
+ if (replantFromBundled(src, moduleDir, hasNative)) {
3247
+ options.logger.info("addon native prebuild re-planted from bundled copy (offline)", { meta: {
3248
+ addon: options.addonName,
3249
+ pkg,
3250
+ src,
3251
+ moduleDir
3252
+ } });
3253
+ results.push({
3254
+ pkg,
3255
+ outcome: "copied"
3256
+ });
3257
+ continue;
3258
+ }
3259
+ }
3260
+ if ((await ensureNativePrebuilds(addonDir, {
3261
+ logger: options.logger,
3262
+ packages: [pkg],
3263
+ ...options.target ? { target: options.target } : {},
3264
+ ...options.fetchPrebuild ? { fetchPrebuild: options.fetchPrebuild } : {}
3265
+ })).includes(pkg)) {
3266
+ results.push({
3267
+ pkg,
3268
+ outcome: "fetched"
3269
+ });
3270
+ continue;
3271
+ }
3272
+ options.logger.error("addon native prebuild MISSING after ensure — module has no compiled .node", { meta: {
3273
+ addon: options.addonName,
3274
+ pkg,
3275
+ moduleDir
3276
+ } });
3277
+ results.push({
3278
+ pkg,
3279
+ outcome: "missing"
3280
+ });
3281
+ missing.push(pkg);
3282
+ }
3283
+ if (missing.length > 0) throw new Error(`${options.addonName} — required native module(s) missing a compiled .node after install (bundled-copy + prebuild-fetch both failed): ${missing.join(", ")}`);
3284
+ return results;
3285
+ }
3286
+ //#endregion
3025
3287
  //#region src/kernel/addon-installer.ts
3026
3288
  var execFileAsync = promisify(execFile);
3027
3289
  function parseInstallSource(value) {
@@ -3310,6 +3572,7 @@ var AddonInstaller = class AddonInstaller {
3310
3572
  await fs$17.promises.writeFile(path$39.join(targetDir, "package.json"), JSON.stringify(stripBundledDeps(pkgData), null, 2));
3311
3573
  await copyDirRecursive(distDir, path$39.join(targetDir, "dist"));
3312
3574
  await copyExtraFileDirs(pkgData, sourceDir, targetDir);
3575
+ copyBundledNativeModules(sourceDir, targetDir, this.logger);
3313
3576
  await fs$17.promises.writeFile(path$39.join(targetDir, ".install-source"), "local");
3314
3577
  const localPkgVersion = asString$1(pkgData.version, "0.0.0");
3315
3578
  this.manifest.upsert(packageName, {
@@ -3331,6 +3594,11 @@ var AddonInstaller = class AddonInstaller {
3331
3594
  } catch (err) {
3332
3595
  this.logger.warn(`${packageName} — native deps install failed (continuing)`, { meta: { error: errMsg$1(err) } });
3333
3596
  }
3597
+ await ensureAddonNativePrebuilds(targetDir, {
3598
+ addonName: packageName,
3599
+ logger: this.logger,
3600
+ bundledSourceDir: sourceDir
3601
+ });
3334
3602
  this.logger.info(`${packageName} — copied from ${sourceDir}`);
3335
3603
  return {
3336
3604
  name: pkgData.name,
@@ -3517,6 +3785,10 @@ var AddonInstaller = class AddonInstaller {
3517
3785
  } catch (nativeErr) {
3518
3786
  throw new Error(`${pkgView.name} — native deps install failed: ${errMsg$1(nativeErr)}`, { cause: nativeErr });
3519
3787
  }
3788
+ await ensureAddonNativePrebuilds(pkgDir, {
3789
+ addonName: pkgView.name,
3790
+ logger: this.logger
3791
+ });
3520
3792
  await this.evictInstallDir(targetDir);
3521
3793
  ensureDir(path$39.dirname(targetDir));
3522
3794
  await fs$17.promises.rename(pkgDir, targetDir);
@@ -93389,4 +93661,4 @@ var LifecycleJobEngine = class {
93389
93661
  }
93390
93662
  };
93391
93663
  //#endregion
93392
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
93664
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -0,0 +1,41 @@
1
+ import { IScopedLogger } from '@camstack/types';
2
+ import { PrebuildFetchFn, PrebuildTarget } from './ensure-native-prebuilds.js';
3
+ /** Per-native outcome of the ensure pass. */
4
+ export type NativeEnsureOutcome = 'already-present' | 'copied' | 'fetched' | 'missing';
5
+ export interface NativeEnsureResult {
6
+ readonly pkg: string;
7
+ readonly outcome: NativeEnsureOutcome;
8
+ }
9
+ export interface EnsureAddonNativePrebuildsOptions {
10
+ /** Addon package name — for loud error messages naming module + addon. */
11
+ readonly addonName: string;
12
+ readonly logger: IScopedLogger;
13
+ /**
14
+ * Bundled addon copy dir (the `installCopy` `sourceDir` / a
15
+ * `CAMSTACK_BUNDLED_ADDONS_DIR/<addon>` copy) whose own
16
+ * `node_modules/<pkg>` may carry a prebuilt `.node` for THIS machine.
17
+ * Copied in directly before any network fetch. Absent on the npm/tgz path.
18
+ */
19
+ readonly bundledSourceDir?: string;
20
+ /** Running ABI/arch the fetched prebuild must match. Defaults to the process. */
21
+ readonly target?: PrebuildTarget;
22
+ /** Native package set. Defaults to `TRADITIONAL_NATIVE_PACKAGES`. */
23
+ readonly packages?: readonly string[];
24
+ /** Override the fetcher (tests). Production uses the bundled `prebuild-install`. */
25
+ readonly fetchPrebuild?: PrebuildFetchFn;
26
+ /** Override the `.node` presence scan (tests). */
27
+ readonly hasNative?: (moduleDir: string) => boolean;
28
+ }
29
+ /**
30
+ * Ensure every referenced traditional native in `<addonDir>/node_modules`
31
+ * carries its compiled `.node` after the addon's `npm install`. Bundled-copy
32
+ * first (offline), prebuild-fetch fallback, LOUD throw on a residual miss.
33
+ *
34
+ * SKIPS a native absent from the addon's own `node_modules` (it resolves via a
35
+ * hoisted host copy — not this addon's concern). A NO-OP in docker / dev where
36
+ * npm produced a working `.node` (already-present). Returns the per-native
37
+ * outcomes for logging/tests.
38
+ *
39
+ * @throws if any required native is still source-only after copy + fetch.
40
+ */
41
+ export declare function ensureAddonNativePrebuilds(addonDir: string, options: EnsureAddonNativePrebuildsOptions): Promise<readonly NativeEnsureResult[]>;
@@ -0,0 +1,40 @@
1
+ import { IScopedLogger } from '@camstack/types';
2
+ /** Bounded recursive `.node` scan depth inside a module dir. */
3
+ export declare const NATIVE_SCAN_DEPTH = 4;
4
+ /**
5
+ * Traditional (compile-or-prebuild) natives an installed closure must carry a
6
+ * `.node` for. Only these are ENSURE-able by a prebuild re-fetch; N-API natives
7
+ * are excluded (see file header).
8
+ */
9
+ export declare const TRADITIONAL_NATIVE_PACKAGES: readonly string[];
10
+ /** The running Node ABI/arch the fetched prebuild must match. */
11
+ export interface PrebuildTarget {
12
+ /** Node version, e.g. `process.versions.node` (`24.16.0`). */
13
+ readonly nodeVersion: string;
14
+ /** CPU arch, e.g. `process.arch` (`arm64` | `x64`). */
15
+ readonly arch: string;
16
+ }
17
+ /** Fetch a `<moduleDir>`'s prebuild for `target`. Test seam. */
18
+ export type PrebuildFetchFn = (moduleDir: string, target: PrebuildTarget) => Promise<void>;
19
+ export interface EnsureNativePrebuildsOptions {
20
+ readonly logger: IScopedLogger;
21
+ /** Defaults to the running process (`process.versions.node` / `process.arch`). */
22
+ readonly target?: PrebuildTarget;
23
+ /** Override the native package set (tests). */
24
+ readonly packages?: readonly string[];
25
+ /** Override the fetcher (tests). Production uses the bundled `prebuild-install`. */
26
+ readonly fetchPrebuild?: PrebuildFetchFn;
27
+ }
28
+ /** Bounded recursive scan for ANY `*.node` file under `dir`. */
29
+ export declare function hasDotNode(dir: string, maxDepth: number): boolean;
30
+ /**
31
+ * Ensure every traditional native in `<closureDir>/node_modules` carries its
32
+ * compiled `.node`, fetching the prebuild for the running ABI when missing.
33
+ *
34
+ * A NO-OP when nothing is missing (docker / tests) and when a native package is
35
+ * absent from the closure. Never throws for a single package's fetch failure —
36
+ * it logs and continues so the downstream `findMissingNativePrebuilds`
37
+ * validator makes the final swap/abort decision. Returns the packages whose
38
+ * prebuild was successfully fetched (for logging/tests).
39
+ */
40
+ export declare function ensureNativePrebuilds(closureDir: string, options: EnsureNativePrebuildsOptions): Promise<readonly string[]>;
@@ -23,3 +23,20 @@ import { IScopedLogger } from '@camstack/types';
23
23
  * otherwise).
24
24
  */
25
25
  export declare function installManifestNativeDeps(addonDir: string, pkgRaw: Record<string, unknown>, logger: IScopedLogger, registry?: string, npmCacheDir?: string): Promise<void>;
26
+ /**
27
+ * Propagate PREBUILT native modules from a bundled addon copy's own
28
+ * `node_modules` (the packaged-desktop 'local' source at
29
+ * `<resources>/addons/<addon>`) into the freshly-copied target
30
+ * (`<data>/addons/@camstack/<addon>`), BEFORE the target's `npm install` runs.
31
+ *
32
+ * Why (Mac desktop first-boot FATAL, 2026-07-21): `installCopy` copies only
33
+ * `dist/` then `npm install`s the addon's runtime deps. On a toolchain-less Mac
34
+ * that install leaves the traditional native `better-sqlite3` SOURCE-ONLY in
35
+ * `<addon>/node_modules` — a broken copy that then SHADOWS the good hoisted
36
+ * `.node` NODE_PATH would otherwise resolve, crashing the runner with "Could
37
+ * not locate the bindings file". Seeding the prebuilt copy here makes the npm
38
+ * install treat the native as satisfied (no re-run of `prebuild-install`), so
39
+ * the good `.node` stays in place. A no-op in dev / docker where the source
40
+ * carries no bundled natives.
41
+ */
42
+ export declare function copyBundledNativeModules(sourceDir: string, targetDir: string, logger: IScopedLogger): void;
@@ -1,4 +1,4 @@
1
- import { q as readNodePin } from "./dist-DTTTjPCL.mjs";
1
+ import { q as readNodePin } from "./dist-72yufD8v.mjs";
2
2
  import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
3
3
  import { createServer } from "node:http";
4
4
  import * as fs from "node:fs";
@@ -437,6 +437,92 @@ function copyPrebuiltNativeDep(name, addonDir, logger) {
437
437
  }
438
438
  return false;
439
439
  }
440
+ /**
441
+ * Native packages the packaged desktop ships PREBUILT inside each bundled addon
442
+ * copy (`scripts/stage-deps.mjs` plants them), keyed to the platform SIBLING
443
+ * scopes that hold the N-API binary. The traditional `better-sqlite3` carries
444
+ * its `.node` in its own dir; `sharp`/`node-av` carry it in a `@img`/`@seydx`
445
+ * sibling.
446
+ */
447
+ var BUNDLED_NATIVE_SIBLING_SCOPES = {
448
+ "better-sqlite3": [],
449
+ sharp: ["@img"],
450
+ "node-av": ["@seydx"]
451
+ };
452
+ /** Bounded recursive scan for ANY `*.node` under `dir`. */
453
+ function hasAnyDotNode(dir, maxDepth) {
454
+ let entries;
455
+ try {
456
+ entries = fs.readdirSync(dir, { withFileTypes: true });
457
+ } catch {
458
+ return false;
459
+ }
460
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".node")) return true;
461
+ if (maxDepth <= 0) return false;
462
+ for (const entry of entries) if (entry.isDirectory() && hasAnyDotNode(path$1.join(dir, entry.name), maxDepth - 1)) return true;
463
+ return false;
464
+ }
465
+ /** Copy `<src>` → `<dst>` when `<src>` carries a `.node`. Best-effort. */
466
+ function copyPrebuiltModuleDir(src, dst, label, logger) {
467
+ if (!hasAnyDotNode(src, 5)) return;
468
+ if (hasAnyDotNode(dst, 5)) return;
469
+ try {
470
+ fs.rmSync(dst, {
471
+ recursive: true,
472
+ force: true
473
+ });
474
+ fs.mkdirSync(path$1.dirname(dst), { recursive: true });
475
+ fs.cpSync(src, dst, {
476
+ recursive: true,
477
+ dereference: true
478
+ });
479
+ logger.info("Prebuilt native module propagated from bundled addon copy", { meta: {
480
+ module: label,
481
+ src,
482
+ dst
483
+ } });
484
+ } catch (err) {
485
+ logger.warn("Prebuilt native module propagation failed (npm install will retry)", { meta: {
486
+ module: label,
487
+ src,
488
+ error: errMsg(err)
489
+ } });
490
+ }
491
+ }
492
+ /**
493
+ * Propagate PREBUILT native modules from a bundled addon copy's own
494
+ * `node_modules` (the packaged-desktop 'local' source at
495
+ * `<resources>/addons/<addon>`) into the freshly-copied target
496
+ * (`<data>/addons/@camstack/<addon>`), BEFORE the target's `npm install` runs.
497
+ *
498
+ * Why (Mac desktop first-boot FATAL, 2026-07-21): `installCopy` copies only
499
+ * `dist/` then `npm install`s the addon's runtime deps. On a toolchain-less Mac
500
+ * that install leaves the traditional native `better-sqlite3` SOURCE-ONLY in
501
+ * `<addon>/node_modules` — a broken copy that then SHADOWS the good hoisted
502
+ * `.node` NODE_PATH would otherwise resolve, crashing the runner with "Could
503
+ * not locate the bindings file". Seeding the prebuilt copy here makes the npm
504
+ * install treat the native as satisfied (no re-run of `prebuild-install`), so
505
+ * the good `.node` stays in place. A no-op in dev / docker where the source
506
+ * carries no bundled natives.
507
+ */
508
+ function copyBundledNativeModules(sourceDir, targetDir, logger) {
509
+ const srcNm = path$1.join(sourceDir, "node_modules");
510
+ if (!fs.existsSync(srcNm)) return;
511
+ const dstNm = path$1.join(targetDir, "node_modules");
512
+ for (const [name, scopes] of Object.entries(BUNDLED_NATIVE_SIBLING_SCOPES)) {
513
+ copyPrebuiltModuleDir(path$1.join(srcNm, name), path$1.join(dstNm, name), name, logger);
514
+ for (const scope of scopes) {
515
+ const scopeSrc = path$1.join(srcNm, scope);
516
+ let siblings;
517
+ try {
518
+ siblings = fs.readdirSync(scopeSrc);
519
+ } catch {
520
+ continue;
521
+ }
522
+ for (const sibling of siblings) copyPrebuiltModuleDir(path$1.join(scopeSrc, sibling), path$1.join(dstNm, scope, sibling), `${scope}/${sibling}`, logger);
523
+ }
524
+ }
525
+ }
440
526
  /** A package dir is "built" when it carries a compiled `.node` or a prebuilds tree. */
441
527
  function hasBuiltBinary(pkgDir) {
442
528
  if (!fs.existsSync(path$1.join(pkgDir, "package.json"))) return false;
@@ -7320,4 +7406,4 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
7320
7406
  await deps.installPythonRequirements(reqAbs);
7321
7407
  }
7322
7408
  //#endregion
7323
- export { buildNativeCapProxy as $, createHubCapForwardService as A, AGENT_CAP_FWD_ACTION as B, brokerTransportLink as C, createAddonDataPlaneFacility as Ct, localProviderLink as D, ipcParentLink as E, createUdsLogger as F, callWithServiceDiscovery as G, CapRouteResolver as H, createUdsLoggerWithControl as I, UdsLocalTransportServer as J, createLocalTransport as K, LocalChildClient as L, createUdsEventBridge as M, createUdsEventBus as N, HUB_CAP_FWD_ACTION as O, udsChildLogToWorkerEntry as P, encodeFrame as Q, LocalChildRegistry as R, brokerCallForCap as S, resolveAddonClass as St, ipcChildLink as T, CapRouteError as U, AGENT_CAP_FWD_SERVICE as V, classifyCapRoute as W, localEndpointPath as X, SocketChannel as Y, FrameDecoder as Z, createKernelHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, resolveNpmInvocation as bt, setHubConnected as c, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, capBareAction as dt, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, capServiceName as ft, AddonDepsManager as g, DeviceRegistry as gt, subscribePassthrough as h, serializeTypedArrays as ht, createUdsAddonContext as i, mountNativeCapService as it, createParentUnownedCallHandler as j, HUB_CAP_FWD_SERVICE as k, EVENT_TOPIC_PREFIX as l, capActionName as lt, setNodeEventInterest as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, createAddonService as ot, registerEventBusService as p, parseCapAction as pt, UdsLocalTransportClient as q, createAddonContext as r, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, validateProviderRegistrations as st, installManifestPythonDeps as t, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, capActionSuffix as ut, resolveHwAccel as v, CapabilityUnavailableError as vt, buildLinkChain as w, getCapUsageRegistry as x, runNpm as xt, CapUsageRegistry as y, installManifestNativeDeps as yt, UDS_NO_ROUTE_PREFIX as z };
7409
+ export { buildNativeCapProxy as $, createHubCapForwardService as A, AGENT_CAP_FWD_ACTION as B, brokerTransportLink as C, resolveAddonClass as Ct, localProviderLink as D, ipcParentLink as E, createUdsLogger as F, callWithServiceDiscovery as G, CapRouteResolver as H, createUdsLoggerWithControl as I, UdsLocalTransportServer as J, createLocalTransport as K, LocalChildClient as L, createUdsEventBridge as M, createUdsEventBus as N, HUB_CAP_FWD_ACTION as O, udsChildLogToWorkerEntry as P, encodeFrame as Q, LocalChildRegistry as R, brokerCallForCap as S, runNpm as St, ipcChildLink as T, CapRouteError as U, AGENT_CAP_FWD_SERVICE as V, classifyCapRoute as W, localEndpointPath as X, SocketChannel as Y, FrameDecoder as Z, createKernelHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, installManifestNativeDeps as bt, setHubConnected as c, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, capBareAction as dt, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, capServiceName as ft, AddonDepsManager as g, DeviceRegistry as gt, subscribePassthrough as h, serializeTypedArrays as ht, createUdsAddonContext as i, mountNativeCapService as it, createParentUnownedCallHandler as j, HUB_CAP_FWD_SERVICE as k, EVENT_TOPIC_PREFIX as l, capActionName as lt, setNodeEventInterest as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, createAddonService as ot, registerEventBusService as p, parseCapAction as pt, UdsLocalTransportClient as q, createAddonContext as r, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, validateProviderRegistrations as st, installManifestPythonDeps as t, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, capActionSuffix as ut, resolveHwAccel as v, CapabilityUnavailableError as vt, buildLinkChain as w, createAddonDataPlaneFacility as wt, getCapUsageRegistry as x, resolveNpmInvocation as xt, CapUsageRegistry as y, copyBundledNativeModules as yt, UDS_NO_ROUTE_PREFIX as z };