episoda 0.2.13 → 0.2.15
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/daemon/daemon-process.js +106 -11
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1718,6 +1718,18 @@ var require_websocket_client = __commonJS({
|
|
|
1718
1718
|
this.isIntentionalDisconnect = false;
|
|
1719
1719
|
this.lastConnectAttemptTime = Date.now();
|
|
1720
1720
|
this.lastErrorCode = void 0;
|
|
1721
|
+
if (this.ws) {
|
|
1722
|
+
try {
|
|
1723
|
+
this.ws.removeAllListeners();
|
|
1724
|
+
this.ws.terminate();
|
|
1725
|
+
} catch {
|
|
1726
|
+
}
|
|
1727
|
+
this.ws = void 0;
|
|
1728
|
+
}
|
|
1729
|
+
if (this.reconnectTimeout) {
|
|
1730
|
+
clearTimeout(this.reconnectTimeout);
|
|
1731
|
+
this.reconnectTimeout = void 0;
|
|
1732
|
+
}
|
|
1721
1733
|
return new Promise((resolve2, reject) => {
|
|
1722
1734
|
const connectionTimeout = setTimeout(() => {
|
|
1723
1735
|
if (this.ws) {
|
|
@@ -1827,6 +1839,32 @@ var require_websocket_client = __commonJS({
|
|
|
1827
1839
|
}
|
|
1828
1840
|
this.eventHandlers.get(event).push(handler);
|
|
1829
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* EP812: Register a one-time event handler (removes itself after first call)
|
|
1844
|
+
* @param event - Event type
|
|
1845
|
+
* @param handler - Handler function
|
|
1846
|
+
*/
|
|
1847
|
+
once(event, handler) {
|
|
1848
|
+
const onceHandler = (message) => {
|
|
1849
|
+
this.off(event, onceHandler);
|
|
1850
|
+
handler(message);
|
|
1851
|
+
};
|
|
1852
|
+
this.on(event, onceHandler);
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* EP812: Remove an event handler
|
|
1856
|
+
* @param event - Event type
|
|
1857
|
+
* @param handler - Handler function to remove
|
|
1858
|
+
*/
|
|
1859
|
+
off(event, handler) {
|
|
1860
|
+
const handlers = this.eventHandlers.get(event);
|
|
1861
|
+
if (handlers) {
|
|
1862
|
+
const index = handlers.indexOf(handler);
|
|
1863
|
+
if (index !== -1) {
|
|
1864
|
+
handlers.splice(index, 1);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1830
1868
|
/**
|
|
1831
1869
|
* Send a message to the server
|
|
1832
1870
|
* @param message - Client message to send
|
|
@@ -1920,6 +1958,10 @@ var require_websocket_client = __commonJS({
|
|
|
1920
1958
|
console.log("[EpisodaClient] Intentional disconnect - not reconnecting");
|
|
1921
1959
|
return;
|
|
1922
1960
|
}
|
|
1961
|
+
if (this.reconnectTimeout) {
|
|
1962
|
+
console.log("[EpisodaClient] Reconnection already scheduled, skipping duplicate");
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1923
1965
|
if (this.heartbeatTimer) {
|
|
1924
1966
|
clearInterval(this.heartbeatTimer);
|
|
1925
1967
|
this.heartbeatTimer = void 0;
|
|
@@ -2555,11 +2597,13 @@ async function startDaemon() {
|
|
|
2555
2597
|
if (!fs2.existsSync(daemonScript)) {
|
|
2556
2598
|
throw new Error(`Daemon script not found: ${daemonScript}. Make sure CLI is built.`);
|
|
2557
2599
|
}
|
|
2600
|
+
const logPath = path2.join(configDir, "daemon.log");
|
|
2601
|
+
const logFd = fs2.openSync(logPath, "a");
|
|
2558
2602
|
const child = (0, import_child_process.spawn)("node", [daemonScript], {
|
|
2559
2603
|
detached: true,
|
|
2560
2604
|
// Run independently of parent
|
|
2561
|
-
stdio: "ignore",
|
|
2562
|
-
//
|
|
2605
|
+
stdio: ["ignore", logFd, logFd],
|
|
2606
|
+
// EP813: Redirect stdout/stderr to log file
|
|
2563
2607
|
env: {
|
|
2564
2608
|
...process.env,
|
|
2565
2609
|
EPISODA_DAEMON_MODE: "1"
|
|
@@ -2754,9 +2798,38 @@ async function devCommand(options = {}) {
|
|
|
2754
2798
|
status.info("This will authenticate with episoda.dev and configure the CLI.");
|
|
2755
2799
|
process.exit(1);
|
|
2756
2800
|
}
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2801
|
+
let needsRestart = false;
|
|
2802
|
+
const existingPid = isDaemonRunning();
|
|
2803
|
+
if (existingPid) {
|
|
2804
|
+
const reachable2 = await isDaemonReachable();
|
|
2805
|
+
if (reachable2) {
|
|
2806
|
+
try {
|
|
2807
|
+
const health = await verifyHealth();
|
|
2808
|
+
if (health.healthyConnections > 0) {
|
|
2809
|
+
status.debug(`Daemon already running and healthy (PID: ${existingPid})`);
|
|
2810
|
+
} else if (health.staleConnections > 0) {
|
|
2811
|
+
status.info("Daemon has stale connections, restarting...");
|
|
2812
|
+
needsRestart = true;
|
|
2813
|
+
} else {
|
|
2814
|
+
status.debug(`Daemon running but no connections (PID: ${existingPid})`);
|
|
2815
|
+
}
|
|
2816
|
+
} catch {
|
|
2817
|
+
status.debug("Health check failed, restarting daemon...");
|
|
2818
|
+
needsRestart = true;
|
|
2819
|
+
}
|
|
2820
|
+
} else {
|
|
2821
|
+
status.debug("Daemon not reachable via IPC, restarting...");
|
|
2822
|
+
needsRestart = true;
|
|
2823
|
+
}
|
|
2824
|
+
} else {
|
|
2825
|
+
needsRestart = false;
|
|
2826
|
+
}
|
|
2827
|
+
if (needsRestart) {
|
|
2828
|
+
const killedCount = killAllEpisodaProcesses();
|
|
2829
|
+
if (killedCount > 0) {
|
|
2830
|
+
status.info(`Cleaned up ${killedCount} stale process${killedCount > 1 ? "es" : ""}`);
|
|
2831
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
2832
|
+
}
|
|
2760
2833
|
}
|
|
2761
2834
|
const serverUrl = config.api_url || process.env.EPISODA_API_URL || "https://episoda.dev";
|
|
2762
2835
|
let projectPath;
|
|
@@ -2908,6 +2981,12 @@ async function runDevServer(command, cwd, autoRestart) {
|
|
|
2908
2981
|
setTimeout(() => {
|
|
2909
2982
|
startServer();
|
|
2910
2983
|
}, 2e3);
|
|
2984
|
+
} else if (!shuttingDown) {
|
|
2985
|
+
status.info("");
|
|
2986
|
+
status.info("Dev server stopped, but daemon remains connected.");
|
|
2987
|
+
status.info("Git operations will still be executed by Episoda.");
|
|
2988
|
+
status.info("Press Ctrl+C to disconnect.");
|
|
2989
|
+
status.info("");
|
|
2911
2990
|
}
|
|
2912
2991
|
});
|
|
2913
2992
|
devProcess.on("error", (error) => {
|