episoda 0.2.45 → 0.2.47

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.
@@ -821,14 +821,25 @@ var require_git_executor = __commonJS({
821
821
  const baseBranch = command.baseBranch || "main";
822
822
  try {
823
823
  let stdout;
824
+ let comparisonSucceeded = false;
824
825
  try {
825
- const result = await execAsync2(`git log ${baseBranch}.."${command.branch}" --pretty=format:"%H|%an|%ae|%aI|%s" -n ${limit} --`, { cwd, timeout: options?.timeout || 1e4 });
826
+ const result = await execAsync2(`git log origin/${baseBranch}.."${command.branch}" --pretty=format:"%H|%an|%ae|%aI|%s" -n ${limit} --`, { cwd, timeout: options?.timeout || 1e4 });
826
827
  stdout = result.stdout;
827
- } catch (error) {
828
+ comparisonSucceeded = true;
829
+ } catch {
828
830
  try {
829
- const result = await execAsync2(`git log "${command.branch}" --pretty=format:"%H|%an|%ae|%aI|%s" -n ${limit} --`, { cwd, timeout: options?.timeout || 1e4 });
831
+ const result = await execAsync2(`git log ${baseBranch}.."${command.branch}" --pretty=format:"%H|%an|%ae|%aI|%s" -n ${limit} --`, { cwd, timeout: options?.timeout || 1e4 });
830
832
  stdout = result.stdout;
831
- } catch (branchError) {
833
+ comparisonSucceeded = true;
834
+ } catch {
835
+ stdout = "";
836
+ comparisonSucceeded = false;
837
+ }
838
+ }
839
+ if (!comparisonSucceeded) {
840
+ try {
841
+ await execAsync2(`git rev-parse --verify "${command.branch}"`, { cwd, timeout: options?.timeout || 5e3 });
842
+ } catch {
832
843
  return {
833
844
  success: false,
834
845
  error: "BRANCH_NOT_FOUND",
@@ -2682,7 +2693,7 @@ var require_package = __commonJS({
2682
2693
  "package.json"(exports2, module2) {
2683
2694
  module2.exports = {
2684
2695
  name: "episoda",
2685
- version: "0.2.45",
2696
+ version: "0.2.47",
2686
2697
  description: "CLI tool for Episoda local development workflow orchestration",
2687
2698
  main: "dist/index.js",
2688
2699
  types: "dist/index.d.ts",
@@ -4096,14 +4107,11 @@ var TUNNEL_PID_DIR = path7.join(os2.homedir(), ".episoda", "tunnels");
4096
4107
  var TUNNEL_TIMEOUTS = {
4097
4108
  /** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
4098
4109
  NAMED_TUNNEL_CONNECT: 6e4,
4099
- /** Time to wait for Quick Tunnel connection (simpler, faster connection) */
4100
- QUICK_TUNNEL_CONNECT: 3e4,
4101
4110
  /** Time to wait for cloudflared process to start before giving up */
4102
4111
  PROCESS_START: 1e4,
4103
4112
  /** Grace period after starting cloudflared before checking status */
4104
4113
  STARTUP_GRACE: 2e3
4105
4114
  };
4106
- var TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
4107
4115
  var DEFAULT_RECONNECT_CONFIG = {
4108
4116
  maxRetries: 5,
4109
4117
  initialDelayMs: 1e3,
@@ -4558,151 +4566,21 @@ var TunnelManager = class extends import_events.EventEmitter {
4558
4566
  });
4559
4567
  }
4560
4568
  /**
4561
- * EP948: Route to the appropriate tunnel process method based on mode
4569
+ * EP948: Start tunnel process (Named Tunnels only)
4570
+ * EP1020: Removed Quick Tunnel fallback - Named Tunnels are the only supported mode
4562
4571
  */
4563
4572
  async startTunnelProcess(options, existingState) {
4564
- const mode = options.mode || "named";
4565
- if (mode === "named" && options.tunnelToken) {
4566
- return this.startNamedTunnelProcess(options, existingState);
4567
- }
4568
- console.log(`[Tunnel] EP948: Using Quick Tunnel mode for ${options.moduleUid}`);
4569
- return this.startQuickTunnelProcess(options, existingState);
4570
- }
4571
- /**
4572
- * EP672-9: Internal method to start the tunnel process (Quick Tunnel mode)
4573
- * Separated from startTunnel to support reconnection
4574
- */
4575
- async startQuickTunnelProcess(options, existingState) {
4576
- const { moduleUid, port = 3e3, onUrl, onStatusChange } = options;
4577
- if (!this.cloudflaredPath) {
4578
- try {
4579
- this.cloudflaredPath = await ensureCloudflared();
4580
- } catch (error) {
4581
- const errorMessage = error instanceof Error ? error.message : String(error);
4582
- return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
4583
- }
4584
- }
4585
- return new Promise((resolve3) => {
4586
- const tunnelInfo = {
4587
- moduleUid,
4588
- url: "",
4589
- port,
4590
- status: "starting",
4591
- startedAt: /* @__PURE__ */ new Date(),
4592
- process: null
4593
- // Will be set below
4594
- };
4595
- const process2 = (0, import_child_process6.spawn)(this.cloudflaredPath, [
4596
- "tunnel",
4597
- "--url",
4598
- `http://localhost:${port}`
4599
- ], {
4600
- stdio: ["ignore", "pipe", "pipe"]
4601
- });
4602
- tunnelInfo.process = process2;
4603
- tunnelInfo.pid = process2.pid;
4604
- if (process2.pid) {
4605
- this.writePidFile(moduleUid, process2.pid);
4606
- }
4607
- const state = existingState || {
4608
- info: tunnelInfo,
4609
- options,
4610
- intentionallyStopped: false,
4611
- retryCount: 0,
4612
- retryTimeoutId: null
4613
- };
4614
- state.info = tunnelInfo;
4615
- this.tunnelStates.set(moduleUid, state);
4616
- let urlFound = false;
4617
- let stdoutBuffer = "";
4618
- let stderrBuffer = "";
4619
- const parseOutput = (data) => {
4620
- if (urlFound) return;
4621
- const match = data.match(TUNNEL_URL_REGEX);
4622
- if (match) {
4623
- urlFound = true;
4624
- tunnelInfo.url = match[0];
4625
- tunnelInfo.status = "connected";
4626
- onStatusChange?.("connected");
4627
- onUrl?.(tunnelInfo.url);
4628
- this.emitEvent({
4629
- type: "started",
4630
- moduleUid,
4631
- url: tunnelInfo.url
4632
- });
4633
- resolve3({ success: true, url: tunnelInfo.url });
4634
- }
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."
4635
4578
  };
4636
- process2.stdout?.on("data", (data) => {
4637
- stdoutBuffer += data.toString();
4638
- parseOutput(stdoutBuffer);
4639
- });
4640
- process2.stderr?.on("data", (data) => {
4641
- stderrBuffer += data.toString();
4642
- parseOutput(stderrBuffer);
4643
- });
4644
- process2.on("exit", (code, signal) => {
4645
- const wasConnected = tunnelInfo.status === "connected";
4646
- tunnelInfo.status = "disconnected";
4647
- const currentState = this.tunnelStates.get(moduleUid);
4648
- if (!urlFound) {
4649
- const errorMsg = `Tunnel process exited with code ${code}`;
4650
- tunnelInfo.status = "error";
4651
- tunnelInfo.error = errorMsg;
4652
- if (currentState && !currentState.intentionallyStopped) {
4653
- this.attemptReconnect(moduleUid);
4654
- } else {
4655
- this.tunnelStates.delete(moduleUid);
4656
- onStatusChange?.("error", errorMsg);
4657
- this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4658
- }
4659
- resolve3({ success: false, error: errorMsg });
4660
- } else if (wasConnected) {
4661
- if (currentState && !currentState.intentionallyStopped) {
4662
- console.log(`[Tunnel] ${moduleUid} crashed unexpectedly, attempting reconnect...`);
4663
- onStatusChange?.("reconnecting");
4664
- this.attemptReconnect(moduleUid);
4665
- } else {
4666
- this.tunnelStates.delete(moduleUid);
4667
- onStatusChange?.("disconnected");
4668
- this.emitEvent({ type: "stopped", moduleUid });
4669
- }
4670
- }
4671
- });
4672
- process2.on("error", (error) => {
4673
- tunnelInfo.status = "error";
4674
- tunnelInfo.error = error.message;
4675
- const currentState = this.tunnelStates.get(moduleUid);
4676
- if (currentState && !currentState.intentionallyStopped) {
4677
- this.attemptReconnect(moduleUid);
4678
- } else {
4679
- this.tunnelStates.delete(moduleUid);
4680
- onStatusChange?.("error", error.message);
4681
- this.emitEvent({ type: "error", moduleUid, error: error.message });
4682
- }
4683
- if (!urlFound) {
4684
- resolve3({ success: false, error: error.message });
4685
- }
4686
- });
4687
- setTimeout(() => {
4688
- if (!urlFound) {
4689
- process2.kill();
4690
- const errorMsg = "Tunnel startup timed out after 30 seconds";
4691
- tunnelInfo.status = "error";
4692
- tunnelInfo.error = errorMsg;
4693
- const currentState = this.tunnelStates.get(moduleUid);
4694
- if (currentState && !currentState.intentionallyStopped) {
4695
- this.attemptReconnect(moduleUid);
4696
- } else {
4697
- this.tunnelStates.delete(moduleUid);
4698
- onStatusChange?.("error", errorMsg);
4699
- this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4700
- }
4701
- resolve3({ success: false, error: errorMsg });
4702
- }
4703
- }, TUNNEL_TIMEOUTS.QUICK_TUNNEL_CONNECT);
4704
- });
4579
+ }
4580
+ return this.startNamedTunnelProcess(options, existingState);
4705
4581
  }
4582
+ // EP1020: startQuickTunnelProcess removed - Quick Tunnels no longer supported
4583
+ // All tunnels now use Named Tunnels via Cloudflare API
4706
4584
  /**
4707
4585
  * Start a tunnel for a module
4708
4586
  *
@@ -4771,8 +4649,11 @@ var TunnelManager = class extends import_events.EventEmitter {
4771
4649
  previewUrl: provisionResult.tunnel.preview_url
4772
4650
  };
4773
4651
  } else {
4774
- console.warn(`[Tunnel] EP948: Named Tunnel provisioning failed: ${provisionResult.error}. Falling back to Quick Tunnel.`);
4775
- resolvedOptions = { ...resolvedOptions, mode: "quick" };
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
+ };
4776
4657
  }
4777
4658
  }
4778
4659
  return this.startTunnelProcess(resolvedOptions);
@@ -9254,18 +9135,6 @@ var Daemon = class _Daemon {
9254
9135
  const result2 = await previewManager.restartPreview(moduleUid);
9255
9136
  if (result2.success && result2.previewUrl) {
9256
9137
  console.log(`[Daemon] EP833: Preview restarted for ${moduleUid}: ${result2.previewUrl}`);
9257
- try {
9258
- await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
9259
- method: "POST",
9260
- body: JSON.stringify({
9261
- tunnel_url: result2.previewUrl,
9262
- tunnel_error: null,
9263
- restart_reason: "health_check_failure"
9264
- })
9265
- });
9266
- } catch (e) {
9267
- console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`);
9268
- }
9269
9138
  } else {
9270
9139
  console.error(`[Daemon] EP833: Preview restart failed for ${moduleUid}: ${result2.error}`);
9271
9140
  }
@@ -9290,18 +9159,6 @@ var Daemon = class _Daemon {
9290
9159
  });
9291
9160
  if (result.success && result.previewUrl) {
9292
9161
  console.log(`[Daemon] EP833: Preview started for ${moduleUid}: ${result.previewUrl}`);
9293
- try {
9294
- await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
9295
- method: "POST",
9296
- body: JSON.stringify({
9297
- tunnel_url: result.previewUrl,
9298
- tunnel_error: null,
9299
- restart_reason: "health_check_failure"
9300
- })
9301
- });
9302
- } catch (e) {
9303
- console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`);
9304
- }
9305
9162
  } else {
9306
9163
  console.error(`[Daemon] EP833: Preview start failed for ${moduleUid}: ${result.error}`);
9307
9164
  }