episoda 0.2.46 → 0.2.48
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.
|
@@ -2693,7 +2693,7 @@ var require_package = __commonJS({
|
|
|
2693
2693
|
"package.json"(exports2, module2) {
|
|
2694
2694
|
module2.exports = {
|
|
2695
2695
|
name: "episoda",
|
|
2696
|
-
version: "0.2.
|
|
2696
|
+
version: "0.2.48",
|
|
2697
2697
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2698
2698
|
main: "dist/index.js",
|
|
2699
2699
|
types: "dist/index.d.ts",
|
|
@@ -4107,14 +4107,11 @@ var TUNNEL_PID_DIR = path7.join(os2.homedir(), ".episoda", "tunnels");
|
|
|
4107
4107
|
var TUNNEL_TIMEOUTS = {
|
|
4108
4108
|
/** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
|
|
4109
4109
|
NAMED_TUNNEL_CONNECT: 6e4,
|
|
4110
|
-
/** Time to wait for Quick Tunnel connection (simpler, faster connection) */
|
|
4111
|
-
QUICK_TUNNEL_CONNECT: 3e4,
|
|
4112
4110
|
/** Time to wait for cloudflared process to start before giving up */
|
|
4113
4111
|
PROCESS_START: 1e4,
|
|
4114
4112
|
/** Grace period after starting cloudflared before checking status */
|
|
4115
4113
|
STARTUP_GRACE: 2e3
|
|
4116
4114
|
};
|
|
4117
|
-
var TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
4118
4115
|
var DEFAULT_RECONNECT_CONFIG = {
|
|
4119
4116
|
maxRetries: 5,
|
|
4120
4117
|
initialDelayMs: 1e3,
|
|
@@ -4569,151 +4566,21 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4569
4566
|
});
|
|
4570
4567
|
}
|
|
4571
4568
|
/**
|
|
4572
|
-
* EP948:
|
|
4569
|
+
* EP948: Start tunnel process (Named Tunnels only)
|
|
4570
|
+
* EP1020: Removed Quick Tunnel fallback - Named Tunnels are the only supported mode
|
|
4573
4571
|
*/
|
|
4574
4572
|
async startTunnelProcess(options, existingState) {
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
return
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
return this.startQuickTunnelProcess(options, existingState);
|
|
4581
|
-
}
|
|
4582
|
-
/**
|
|
4583
|
-
* EP672-9: Internal method to start the tunnel process (Quick Tunnel mode)
|
|
4584
|
-
* Separated from startTunnel to support reconnection
|
|
4585
|
-
*/
|
|
4586
|
-
async startQuickTunnelProcess(options, existingState) {
|
|
4587
|
-
const { moduleUid, port = 3e3, onUrl, onStatusChange } = options;
|
|
4588
|
-
if (!this.cloudflaredPath) {
|
|
4589
|
-
try {
|
|
4590
|
-
this.cloudflaredPath = await ensureCloudflared();
|
|
4591
|
-
} catch (error) {
|
|
4592
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4593
|
-
return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4596
|
-
return new Promise((resolve3) => {
|
|
4597
|
-
const tunnelInfo = {
|
|
4598
|
-
moduleUid,
|
|
4599
|
-
url: "",
|
|
4600
|
-
port,
|
|
4601
|
-
status: "starting",
|
|
4602
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
4603
|
-
process: null
|
|
4604
|
-
// Will be set below
|
|
4605
|
-
};
|
|
4606
|
-
const process2 = (0, import_child_process6.spawn)(this.cloudflaredPath, [
|
|
4607
|
-
"tunnel",
|
|
4608
|
-
"--url",
|
|
4609
|
-
`http://localhost:${port}`
|
|
4610
|
-
], {
|
|
4611
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4612
|
-
});
|
|
4613
|
-
tunnelInfo.process = process2;
|
|
4614
|
-
tunnelInfo.pid = process2.pid;
|
|
4615
|
-
if (process2.pid) {
|
|
4616
|
-
this.writePidFile(moduleUid, process2.pid);
|
|
4617
|
-
}
|
|
4618
|
-
const state = existingState || {
|
|
4619
|
-
info: tunnelInfo,
|
|
4620
|
-
options,
|
|
4621
|
-
intentionallyStopped: false,
|
|
4622
|
-
retryCount: 0,
|
|
4623
|
-
retryTimeoutId: null
|
|
4624
|
-
};
|
|
4625
|
-
state.info = tunnelInfo;
|
|
4626
|
-
this.tunnelStates.set(moduleUid, state);
|
|
4627
|
-
let urlFound = false;
|
|
4628
|
-
let stdoutBuffer = "";
|
|
4629
|
-
let stderrBuffer = "";
|
|
4630
|
-
const parseOutput = (data) => {
|
|
4631
|
-
if (urlFound) return;
|
|
4632
|
-
const match = data.match(TUNNEL_URL_REGEX);
|
|
4633
|
-
if (match) {
|
|
4634
|
-
urlFound = true;
|
|
4635
|
-
tunnelInfo.url = match[0];
|
|
4636
|
-
tunnelInfo.status = "connected";
|
|
4637
|
-
onStatusChange?.("connected");
|
|
4638
|
-
onUrl?.(tunnelInfo.url);
|
|
4639
|
-
this.emitEvent({
|
|
4640
|
-
type: "started",
|
|
4641
|
-
moduleUid,
|
|
4642
|
-
url: tunnelInfo.url
|
|
4643
|
-
});
|
|
4644
|
-
resolve3({ success: true, url: tunnelInfo.url });
|
|
4645
|
-
}
|
|
4573
|
+
if (!options.tunnelToken) {
|
|
4574
|
+
console.error(`[Tunnel] EP1020: No tunnel token available for ${options.moduleUid}`);
|
|
4575
|
+
return {
|
|
4576
|
+
success: false,
|
|
4577
|
+
error: "Named Tunnel token required. Quick Tunnels are no longer supported."
|
|
4646
4578
|
};
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
parseOutput(stdoutBuffer);
|
|
4650
|
-
});
|
|
4651
|
-
process2.stderr?.on("data", (data) => {
|
|
4652
|
-
stderrBuffer += data.toString();
|
|
4653
|
-
parseOutput(stderrBuffer);
|
|
4654
|
-
});
|
|
4655
|
-
process2.on("exit", (code, signal) => {
|
|
4656
|
-
const wasConnected = tunnelInfo.status === "connected";
|
|
4657
|
-
tunnelInfo.status = "disconnected";
|
|
4658
|
-
const currentState = this.tunnelStates.get(moduleUid);
|
|
4659
|
-
if (!urlFound) {
|
|
4660
|
-
const errorMsg = `Tunnel process exited with code ${code}`;
|
|
4661
|
-
tunnelInfo.status = "error";
|
|
4662
|
-
tunnelInfo.error = errorMsg;
|
|
4663
|
-
if (currentState && !currentState.intentionallyStopped) {
|
|
4664
|
-
this.attemptReconnect(moduleUid);
|
|
4665
|
-
} else {
|
|
4666
|
-
this.tunnelStates.delete(moduleUid);
|
|
4667
|
-
onStatusChange?.("error", errorMsg);
|
|
4668
|
-
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
4669
|
-
}
|
|
4670
|
-
resolve3({ success: false, error: errorMsg });
|
|
4671
|
-
} else if (wasConnected) {
|
|
4672
|
-
if (currentState && !currentState.intentionallyStopped) {
|
|
4673
|
-
console.log(`[Tunnel] ${moduleUid} crashed unexpectedly, attempting reconnect...`);
|
|
4674
|
-
onStatusChange?.("reconnecting");
|
|
4675
|
-
this.attemptReconnect(moduleUid);
|
|
4676
|
-
} else {
|
|
4677
|
-
this.tunnelStates.delete(moduleUid);
|
|
4678
|
-
onStatusChange?.("disconnected");
|
|
4679
|
-
this.emitEvent({ type: "stopped", moduleUid });
|
|
4680
|
-
}
|
|
4681
|
-
}
|
|
4682
|
-
});
|
|
4683
|
-
process2.on("error", (error) => {
|
|
4684
|
-
tunnelInfo.status = "error";
|
|
4685
|
-
tunnelInfo.error = error.message;
|
|
4686
|
-
const currentState = this.tunnelStates.get(moduleUid);
|
|
4687
|
-
if (currentState && !currentState.intentionallyStopped) {
|
|
4688
|
-
this.attemptReconnect(moduleUid);
|
|
4689
|
-
} else {
|
|
4690
|
-
this.tunnelStates.delete(moduleUid);
|
|
4691
|
-
onStatusChange?.("error", error.message);
|
|
4692
|
-
this.emitEvent({ type: "error", moduleUid, error: error.message });
|
|
4693
|
-
}
|
|
4694
|
-
if (!urlFound) {
|
|
4695
|
-
resolve3({ success: false, error: error.message });
|
|
4696
|
-
}
|
|
4697
|
-
});
|
|
4698
|
-
setTimeout(() => {
|
|
4699
|
-
if (!urlFound) {
|
|
4700
|
-
process2.kill();
|
|
4701
|
-
const errorMsg = "Tunnel startup timed out after 30 seconds";
|
|
4702
|
-
tunnelInfo.status = "error";
|
|
4703
|
-
tunnelInfo.error = errorMsg;
|
|
4704
|
-
const currentState = this.tunnelStates.get(moduleUid);
|
|
4705
|
-
if (currentState && !currentState.intentionallyStopped) {
|
|
4706
|
-
this.attemptReconnect(moduleUid);
|
|
4707
|
-
} else {
|
|
4708
|
-
this.tunnelStates.delete(moduleUid);
|
|
4709
|
-
onStatusChange?.("error", errorMsg);
|
|
4710
|
-
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
4711
|
-
}
|
|
4712
|
-
resolve3({ success: false, error: errorMsg });
|
|
4713
|
-
}
|
|
4714
|
-
}, TUNNEL_TIMEOUTS.QUICK_TUNNEL_CONNECT);
|
|
4715
|
-
});
|
|
4579
|
+
}
|
|
4580
|
+
return this.startNamedTunnelProcess(options, existingState);
|
|
4716
4581
|
}
|
|
4582
|
+
// EP1020: startQuickTunnelProcess removed - Quick Tunnels no longer supported
|
|
4583
|
+
// All tunnels now use Named Tunnels via Cloudflare API
|
|
4717
4584
|
/**
|
|
4718
4585
|
* Start a tunnel for a module
|
|
4719
4586
|
*
|
|
@@ -4782,8 +4649,11 @@ var TunnelManager = class extends import_events.EventEmitter {
|
|
|
4782
4649
|
previewUrl: provisionResult.tunnel.preview_url
|
|
4783
4650
|
};
|
|
4784
4651
|
} else {
|
|
4785
|
-
console.
|
|
4786
|
-
|
|
4652
|
+
console.error(`[Tunnel] EP1020: Named Tunnel provisioning failed for ${moduleUid}: ${provisionResult.error}`);
|
|
4653
|
+
return {
|
|
4654
|
+
success: false,
|
|
4655
|
+
error: `Named Tunnel provisioning failed: ${provisionResult.error}`
|
|
4656
|
+
};
|
|
4787
4657
|
}
|
|
4788
4658
|
}
|
|
4789
4659
|
return this.startTunnelProcess(resolvedOptions);
|
|
@@ -7640,9 +7510,7 @@ var Daemon = class _Daemon {
|
|
|
7640
7510
|
// EP833: Track consecutive health check failures per tunnel
|
|
7641
7511
|
this.tunnelHealthFailures = /* @__PURE__ */ new Map();
|
|
7642
7512
|
// 3 second timeout for health checks
|
|
7643
|
-
//
|
|
7644
|
-
this.lastReportedHealthStatus = /* @__PURE__ */ new Map();
|
|
7645
|
-
// moduleUid -> status
|
|
7513
|
+
// EP1020: lastReportedHealthStatus removed - health columns dropped from database
|
|
7646
7514
|
// EP837: Prevent concurrent commit syncs (backpressure guard)
|
|
7647
7515
|
this.commitSyncInProgress = false;
|
|
7648
7516
|
// EP843: Per-module mutex for tunnel operations
|
|
@@ -7874,7 +7742,6 @@ var Daemon = class _Daemon {
|
|
|
7874
7742
|
await tunnelManager.stopTunnel(moduleUid);
|
|
7875
7743
|
await stopDevServer(moduleUid);
|
|
7876
7744
|
await clearTunnelUrl(moduleUid);
|
|
7877
|
-
this.lastReportedHealthStatus.delete(moduleUid);
|
|
7878
7745
|
this.tunnelHealthFailures.delete(moduleUid);
|
|
7879
7746
|
console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
|
|
7880
7747
|
return { success: true };
|
|
@@ -9191,7 +9058,6 @@ var Daemon = class _Daemon {
|
|
|
9191
9058
|
const isHealthy = await this.checkTunnelHealth(tunnel);
|
|
9192
9059
|
if (isHealthy) {
|
|
9193
9060
|
this.tunnelHealthFailures.delete(tunnel.moduleUid);
|
|
9194
|
-
await this.reportTunnelHealth(tunnel.moduleUid, "healthy", config);
|
|
9195
9061
|
} else {
|
|
9196
9062
|
const failures = (this.tunnelHealthFailures.get(tunnel.moduleUid) || 0) + 1;
|
|
9197
9063
|
this.tunnelHealthFailures.set(tunnel.moduleUid, failures);
|
|
@@ -9202,7 +9068,6 @@ var Daemon = class _Daemon {
|
|
|
9202
9068
|
await this.restartTunnel(tunnel.moduleUid, tunnel.port);
|
|
9203
9069
|
});
|
|
9204
9070
|
this.tunnelHealthFailures.delete(tunnel.moduleUid);
|
|
9205
|
-
await this.reportTunnelHealth(tunnel.moduleUid, "unhealthy", config);
|
|
9206
9071
|
}
|
|
9207
9072
|
}
|
|
9208
9073
|
}
|
|
@@ -9265,18 +9130,6 @@ var Daemon = class _Daemon {
|
|
|
9265
9130
|
const result2 = await previewManager.restartPreview(moduleUid);
|
|
9266
9131
|
if (result2.success && result2.previewUrl) {
|
|
9267
9132
|
console.log(`[Daemon] EP833: Preview restarted for ${moduleUid}: ${result2.previewUrl}`);
|
|
9268
|
-
try {
|
|
9269
|
-
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
|
|
9270
|
-
method: "POST",
|
|
9271
|
-
body: JSON.stringify({
|
|
9272
|
-
tunnel_url: result2.previewUrl,
|
|
9273
|
-
tunnel_error: null,
|
|
9274
|
-
restart_reason: "health_check_failure"
|
|
9275
|
-
})
|
|
9276
|
-
});
|
|
9277
|
-
} catch (e) {
|
|
9278
|
-
console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`);
|
|
9279
|
-
}
|
|
9280
9133
|
} else {
|
|
9281
9134
|
console.error(`[Daemon] EP833: Preview restart failed for ${moduleUid}: ${result2.error}`);
|
|
9282
9135
|
}
|
|
@@ -9301,18 +9154,6 @@ var Daemon = class _Daemon {
|
|
|
9301
9154
|
});
|
|
9302
9155
|
if (result.success && result.previewUrl) {
|
|
9303
9156
|
console.log(`[Daemon] EP833: Preview started for ${moduleUid}: ${result.previewUrl}`);
|
|
9304
|
-
try {
|
|
9305
|
-
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
|
|
9306
|
-
method: "POST",
|
|
9307
|
-
body: JSON.stringify({
|
|
9308
|
-
tunnel_url: result.previewUrl,
|
|
9309
|
-
tunnel_error: null,
|
|
9310
|
-
restart_reason: "health_check_failure"
|
|
9311
|
-
})
|
|
9312
|
-
});
|
|
9313
|
-
} catch (e) {
|
|
9314
|
-
console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`);
|
|
9315
|
-
}
|
|
9316
9157
|
} else {
|
|
9317
9158
|
console.error(`[Daemon] EP833: Preview start failed for ${moduleUid}: ${result.error}`);
|
|
9318
9159
|
}
|
|
@@ -9320,33 +9161,8 @@ var Daemon = class _Daemon {
|
|
|
9320
9161
|
console.error(`[Daemon] EP833: Error restarting preview for ${moduleUid}:`, error);
|
|
9321
9162
|
}
|
|
9322
9163
|
}
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
* EP904: Use fetchWithAuth for token refresh
|
|
9326
|
-
* EP911: Only report when status CHANGES to reduce DB writes
|
|
9327
|
-
*/
|
|
9328
|
-
async reportTunnelHealth(moduleUid, healthStatus, config) {
|
|
9329
|
-
if (!config.access_token) {
|
|
9330
|
-
return;
|
|
9331
|
-
}
|
|
9332
|
-
const lastStatus = this.lastReportedHealthStatus.get(moduleUid);
|
|
9333
|
-
if (lastStatus === healthStatus) {
|
|
9334
|
-
return;
|
|
9335
|
-
}
|
|
9336
|
-
const apiUrl = config.api_url || "https://episoda.dev";
|
|
9337
|
-
try {
|
|
9338
|
-
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/health`, {
|
|
9339
|
-
method: "PATCH",
|
|
9340
|
-
body: JSON.stringify({
|
|
9341
|
-
tunnel_health_status: healthStatus,
|
|
9342
|
-
tunnel_last_health_check: (/* @__PURE__ */ new Date()).toISOString()
|
|
9343
|
-
})
|
|
9344
|
-
});
|
|
9345
|
-
this.lastReportedHealthStatus.set(moduleUid, healthStatus);
|
|
9346
|
-
} catch (error) {
|
|
9347
|
-
console.warn(`[Daemon] EP833: Failed to report health for ${moduleUid}:`, error instanceof Error ? error.message : error);
|
|
9348
|
-
}
|
|
9349
|
-
}
|
|
9164
|
+
// EP1020: reportTunnelHealth() removed - tunnel_health_status and tunnel_last_health_check columns dropped
|
|
9165
|
+
// Health checking (checkTunnelHealth) and auto-restart logic preserved for detecting dead tunnels
|
|
9350
9166
|
/**
|
|
9351
9167
|
* EP833: Kill processes matching a pattern
|
|
9352
9168
|
* Used to clean up orphaned cloudflared processes
|