@meshxdata/fops 0.1.35 → 0.1.37

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/fops.mjs CHANGED
@@ -37,6 +37,19 @@ function loadProjectEnv() {
37
37
  }
38
38
  process.env[key] = value;
39
39
  }
40
+ // Save the directory of the found .env as projectRoot so future runs from any
41
+ // directory (e.g. ~) can find credentials via findProjectRoot() in client.js.
42
+ try {
43
+ const home = process.env.HOME || process.env.USERPROFILE || "";
44
+ const fopsJsonPath = path.join(home, ".fops.json");
45
+ const foundRoot = path.dirname(envPath);
46
+ let cfg = {};
47
+ try { cfg = JSON.parse(fs.readFileSync(fopsJsonPath, "utf8")); } catch {}
48
+ if (cfg.projectRoot !== foundRoot) {
49
+ cfg.projectRoot = foundRoot;
50
+ fs.writeFileSync(fopsJsonPath, JSON.stringify(cfg, null, 2) + "\n");
51
+ }
52
+ } catch { /* non-fatal */ }
40
53
  break; // use the first .env found
41
54
  } catch { /* ignore read errors */ }
42
55
  }
@@ -48,15 +61,32 @@ loadProjectEnv();
48
61
  // whenever ONNX was loaded; otherwise it uses regular process.exit.
49
62
  const _realExit = process.exit.bind(process);
50
63
  function safeExit(code) {
51
- // Defer hard exit when native addons (ONNX, better-sqlite3) were used so C++ destructors
52
- // don't run during exit and trigger mutex/abort.
53
- if (globalThis.__onnxLoaded || globalThis.__onnxDone || globalThis.__sqliteStoreUsed) {
54
- process.exitCode = code;
64
+ if (globalThis.__exitScheduled) return;
65
+ globalThis.__exitScheduled = true;
66
+
67
+ process.exitCode = code ?? 0;
68
+
69
+ if (globalThis.__onnxLoaded) {
70
+ // ONNX C++ destructors call abort() — bypass them with SIGKILL.
71
+ try { fs.closeSync(2); } catch {}
55
72
  globalThis.__onnxDone = true;
56
- globalThis.__sqliteStoreUsed = false;
73
+ if (code === 0 || code == null) {
74
+ // Unref'd: let event loop drain naturally; SIGKILL only if handles linger.
75
+ setTimeout(() => process.kill(process.pid, "SIGKILL"), 400).unref();
76
+ } else {
77
+ // Ref'd: ensure we exit even if event loop is stuck.
78
+ setTimeout(() => process.kill(process.pid, "SIGKILL"), 100);
79
+ }
57
80
  return;
58
81
  }
59
- _realExit(code);
82
+
83
+ // ONNX was never loaded — safe to exit normally without SIGKILL.
84
+ // Use reallyExit to bypass lingering handles (Ink, MCP) without crashing.
85
+ if (typeof process.reallyExit === "function") {
86
+ process.reallyExit(code ?? 0);
87
+ } else {
88
+ _realExit(code ?? 0);
89
+ }
60
90
  }
61
91
  // Intercept all process.exit() calls so plugin code (which can't import safeExit)
62
92
  // also avoids the ONNX C++ destructor abort.
@@ -141,16 +171,9 @@ program.parseAsync().then(() => {
141
171
  // Without this, commands hang because MCP connections / ink refs keep the event loop alive.
142
172
  const code = typeof process.exitCode === "number" ? process.exitCode : 0;
143
173
  setTimeout(() => {
144
- if (globalThis.__onnxDone) return;
174
+ if (globalThis.__exitScheduled) return;
145
175
  safeExit(code);
146
176
  }, 200);
147
-
148
- // Fallback: if ONNX native handles keep the event loop alive after safeExit
149
- // set the exit code, force-terminate via SIGKILL (avoids atexit / C++ destructor crash).
150
- // The timer is unref'd so it won't itself prevent a clean exit.
151
- if (globalThis.__onnxLoaded || globalThis.__onnxDone) {
152
- setTimeout(() => process.kill(process.pid, "SIGKILL"), 300).unref();
153
- }
154
177
  }).catch((err) => {
155
178
  console.error("Command failed:", err?.message || err);
156
179
  if (err?.stack) console.error(err.stack);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshxdata/fops",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "CLI to install and manage data mesh platforms",
5
5
  "keywords": [
6
6
  "fops",