@tongil_kim/clautunnel 1.6.0 → 1.6.2

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/index.js CHANGED
@@ -3000,9 +3000,24 @@ var MobileServerManager = class {
3000
3000
  let needsInstall = false;
3001
3001
  try {
3002
3002
  execSync2("which ngrok", { stdio: "pipe" });
3003
+ try {
3004
+ const configOutput = execSync2("ngrok config check", {
3005
+ stdio: "pipe",
3006
+ timeout: 5e3
3007
+ }).toString();
3008
+ if (!configOutput.toLowerCase().includes("valid")) {
3009
+ issues.push(
3010
+ "ngrok authtoken is not configured.\n 1. Sign up at https://ngrok.com\n 2. Copy your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken\n 3. Run: ngrok config add-authtoken <your-token>"
3011
+ );
3012
+ }
3013
+ } catch {
3014
+ issues.push(
3015
+ "ngrok authtoken is not configured.\n 1. Sign up at https://ngrok.com\n 2. Copy your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken\n 3. Run: ngrok config add-authtoken <your-token>"
3016
+ );
3017
+ }
3003
3018
  } catch {
3004
3019
  issues.push(
3005
- "ngrok is not installed.\n Install: brew install ngrok\n Sign up: https://ngrok.com\n Auth: ngrok config add-authtoken <your-token>"
3020
+ "ngrok is not installed.\n Install: brew install ngrok\n Then configure your account:\n 1. Sign up at https://ngrok.com\n 2. Copy your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken\n 3. Run: ngrok config add-authtoken <your-token>"
3006
3021
  );
3007
3022
  }
3008
3023
  const nodeModulesPath = join5(this.mobileProjectPath, "node_modules");
@@ -3107,28 +3122,58 @@ var MobileServerManager = class {
3107
3122
  }
3108
3123
  }
3109
3124
  async startNgrok() {
3125
+ try {
3126
+ execSync2("killall ngrok", { stdio: "pipe" });
3127
+ await this.sleep(500);
3128
+ } catch {
3129
+ }
3110
3130
  this.ensureLogDir();
3111
3131
  this.ngrokLogStream = createWriteStream(join5(this.logDir, "ngrok.log"));
3132
+ let stderrData = "";
3112
3133
  this.ngrokProcess = spawn2("ngrok", ["http", String(this.expoPort)], {
3113
3134
  stdio: ["ignore", "pipe", "pipe"],
3114
3135
  detached: false
3115
3136
  });
3116
3137
  this.ngrokProcess.stdout?.pipe(this.ngrokLogStream);
3117
- this.ngrokProcess.stderr?.pipe(this.ngrokLogStream);
3138
+ this.ngrokProcess.stderr?.on("data", (chunk) => {
3139
+ stderrData += chunk.toString();
3140
+ this.ngrokLogStream?.write(chunk);
3141
+ });
3118
3142
  this.ngrokProcess.on("error", () => {
3119
3143
  });
3120
3144
  for (let i = 0; i < 10; i++) {
3121
3145
  await this.sleep(1e3);
3146
+ if (this.ngrokProcess.exitCode !== null) {
3147
+ break;
3148
+ }
3122
3149
  const url = await this.getNgrokTunnelUrl();
3123
3150
  if (url) {
3124
3151
  this.tunnelUrl = url;
3125
3152
  return url;
3126
3153
  }
3127
3154
  }
3155
+ this.ngrokError = this.diagnoseNgrokFailure(stderrData);
3128
3156
  this.killProcess(this.ngrokProcess);
3129
3157
  this.ngrokProcess = null;
3130
3158
  return null;
3131
3159
  }
3160
+ /** Last ngrok error diagnosis (available after startNgrok fails) */
3161
+ ngrokError = null;
3162
+ diagnoseNgrokFailure(stderr) {
3163
+ const lower = stderr.toLowerCase();
3164
+ if (lower.includes("authtoken") || lower.includes("err_ngrok_105") || lower.includes("authentication")) {
3165
+ return "ngrok authentication failed.\n Your authtoken may be invalid or expired.\n 1. Get a new token at https://dashboard.ngrok.com/get-started/your-authtoken\n 2. Run: ngrok config add-authtoken <your-token>";
3166
+ }
3167
+ if (lower.includes("tunnel session limit") || lower.includes("err_ngrok_108")) {
3168
+ return "ngrok free plan session limit reached.\n Free accounts allow 1 tunnel at a time.\n Close other ngrok tunnels or upgrade your plan at https://ngrok.com/pricing";
3169
+ }
3170
+ if (lower.includes("tcp dial") || lower.includes("connection refused")) {
3171
+ return `ngrok could not connect to localhost:${this.expoPort}.
3172
+ This is usually a timing issue \u2014 the tunnel started before Expo was ready.`;
3173
+ }
3174
+ return `ngrok tunnel failed to start.
3175
+ Check logs for details: ${join5(this.logDir, "ngrok.log")}`;
3176
+ }
3132
3177
  async startExpo(tunnelUrl) {
3133
3178
  this.ensureLogDir();
3134
3179
  this.expoLogStream = createWriteStream(join5(this.logDir, "expo.log"));
@@ -3219,7 +3264,7 @@ var MobileServerManager = class {
3219
3264
  this.onProgress("Starting ngrok tunnel...");
3220
3265
  const tunnelUrl = await this.startNgrok();
3221
3266
  if (!tunnelUrl) {
3222
- return { started: false, error: "Failed to start ngrok tunnel" };
3267
+ return { started: false, error: this.ngrokError ?? "Failed to start ngrok tunnel" };
3223
3268
  }
3224
3269
  this.onProgress("Starting Expo server...");
3225
3270
  const expoStarted = await this.startExpo(tunnelUrl);
@@ -3230,6 +3275,13 @@ var MobileServerManager = class {
3230
3275
  const host = tunnelUrl.replace(/^https?:\/\//, "");
3231
3276
  const expoUrl = `exp://${host}:443`;
3232
3277
  console.log("");
3278
+ console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
3279
+ console.log(" \u2502 Expo Go is required to open this QR code. \u2502");
3280
+ console.log(" \u2502 iOS: https://apps.apple.com/app/id982107779\u2502");
3281
+ console.log(" \u2502 Android: https://play.google.com/store/apps/ \u2502");
3282
+ console.log(" \u2502 details?id=host.exp.exponent \u2502");
3283
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
3284
+ console.log("");
3233
3285
  console.log(" Scan with Expo Go:");
3234
3286
  qrcode.generate(expoUrl, { small: true }, (code) => {
3235
3287
  for (const line of code.split("\n")) {