@exreve/exk 1.0.51 → 1.0.52

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.
@@ -0,0 +1,383 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ /** Cross-platform: run a shell command (sh -c on Unix, cmd /c on Windows) */
6
+ function shellSpawnOpts(command) {
7
+ if (process.platform === 'win32') {
8
+ return { shell: process.env.COMSPEC || 'cmd.exe', args: ['/c', command] };
9
+ }
10
+ return { shell: 'sh', args: ['-c', command] };
11
+ }
12
+ import Fastify from 'fastify';
13
+ import fastifyStatic from '@fastify/static';
14
+ // ============ Base Runner ============
15
+ export class BaseRunner {
16
+ app;
17
+ projectPath;
18
+ projectId;
19
+ callbacks;
20
+ stats;
21
+ process = null;
22
+ logFile;
23
+ constructor(app, projectPath, projectId, callbacks) {
24
+ this.app = app;
25
+ this.projectPath = projectPath;
26
+ this.projectId = projectId;
27
+ this.callbacks = callbacks;
28
+ this.stats = {
29
+ startTime: Date.now(),
30
+ requests: 0,
31
+ errors: 0,
32
+ };
33
+ this.logFile = path.join(os.homedir(), '.talk-to-code', 'app-logs', `${projectId}-${app.name}.log`);
34
+ }
35
+ async writeLog(type, data) {
36
+ const timestamp = new Date().toISOString();
37
+ const logLine = `[${timestamp}] [${type.toUpperCase()}] ${data}\n`;
38
+ try {
39
+ await fs.appendFile(this.logFile, logLine, 'utf-8');
40
+ }
41
+ catch (error) {
42
+ // Ignore log write errors
43
+ }
44
+ this.callbacks.onOutput?.({
45
+ type,
46
+ data,
47
+ timestamp: Date.now(),
48
+ });
49
+ }
50
+ updateStats() {
51
+ if (this.process) {
52
+ try {
53
+ const memUsage = process.memoryUsage();
54
+ this.stats.memoryUsage = memUsage;
55
+ }
56
+ catch {
57
+ // Ignore
58
+ }
59
+ }
60
+ this.callbacks.onStats?.(this.stats);
61
+ }
62
+ }
63
+ // ============ Static Frontend Runner ============
64
+ export class StaticFrontendRunner extends BaseRunner {
65
+ fastify = null;
66
+ port;
67
+ constructor(app, projectPath, projectId, callbacks) {
68
+ super(app, projectPath, projectId, callbacks);
69
+ this.port = app.port || 3000;
70
+ }
71
+ async start() {
72
+ try {
73
+ await this.writeLog('system', `Starting static frontend server for ${this.app.name} on port ${this.port}`);
74
+ // Determine build directory (use buildDir from config if provided)
75
+ const buildDirName = this.app.buildDir || 'dist';
76
+ const buildDir = this.app.directory
77
+ ? path.join(this.projectPath, this.app.directory, buildDirName)
78
+ : path.join(this.projectPath, buildDirName);
79
+ // Check if specified build dir exists, fallback to dist, build, or public
80
+ let staticDir = buildDir;
81
+ const dirsToTry = buildDirName !== 'dist' ? [buildDirName, 'dist', 'build', 'public'] : ['dist', 'build', 'public'];
82
+ let found = false;
83
+ for (const dirName of dirsToTry) {
84
+ const testDir = this.app.directory
85
+ ? path.join(this.projectPath, this.app.directory, dirName)
86
+ : path.join(this.projectPath, dirName);
87
+ try {
88
+ await fs.access(testDir);
89
+ staticDir = testDir;
90
+ found = true;
91
+ break;
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ }
97
+ if (!found) {
98
+ return {
99
+ success: false,
100
+ error: `No build directory found. Tried: ${dirsToTry.join(', ')}`
101
+ };
102
+ }
103
+ await this.writeLog('system', `Serving static files from: ${staticDir}`);
104
+ // Create Fastify instance
105
+ this.fastify = Fastify({
106
+ logger: {
107
+ level: 'info',
108
+ transport: {
109
+ target: 'pino-pretty',
110
+ options: {
111
+ translateTime: 'HH:MM:ss Z',
112
+ ignore: 'pid,hostname',
113
+ },
114
+ },
115
+ },
116
+ });
117
+ // Register static file serving
118
+ await this.fastify.register(fastifyStatic, {
119
+ root: staticDir,
120
+ prefix: '/',
121
+ });
122
+ // Add request logging middleware
123
+ this.fastify.addHook('onRequest', async (request, reply) => {
124
+ this.stats.requests++;
125
+ this.stats.lastRequestTime = Date.now();
126
+ this.updateStats();
127
+ await this.writeLog('stdout', `${request.method} ${request.url} - ${reply.statusCode}`);
128
+ });
129
+ // Add error handling
130
+ this.fastify.setErrorHandler(async (error, request, reply) => {
131
+ this.stats.errors++;
132
+ this.updateStats();
133
+ const errorMessage = error instanceof Error ? error.message : String(error);
134
+ await this.writeLog('stderr', `Error: ${errorMessage} - ${request.method} ${request.url}`);
135
+ reply.status(500).send({ error: 'Internal Server Error' });
136
+ });
137
+ // Start server
138
+ await this.fastify.listen({ port: this.port, host: '0.0.0.0' });
139
+ await this.writeLog('system', `Static server started successfully on port ${this.port}`);
140
+ // Create a mock process for compatibility
141
+ this.process = {
142
+ pid: process.pid,
143
+ kill: (_signal) => {
144
+ this.stop();
145
+ },
146
+ };
147
+ return {
148
+ success: true,
149
+ pid: process.pid,
150
+ };
151
+ }
152
+ catch (error) {
153
+ await this.writeLog('stderr', `Failed to start static server: ${error.message}`);
154
+ this.callbacks.onError?.(error.message);
155
+ return {
156
+ success: false,
157
+ error: error.message || 'Failed to start static server'
158
+ };
159
+ }
160
+ }
161
+ async stop() {
162
+ try {
163
+ if (this.fastify) {
164
+ await this.writeLog('system', `Stopping static server for ${this.app.name}`);
165
+ await this.fastify.close();
166
+ this.fastify = null;
167
+ await this.writeLog('system', `Static server stopped`);
168
+ this.callbacks.onExit?.(0);
169
+ return { success: true };
170
+ }
171
+ return { success: true };
172
+ }
173
+ catch (error) {
174
+ await this.writeLog('stderr', `Error stopping static server: ${error.message}`);
175
+ return {
176
+ success: false,
177
+ error: error.message || 'Failed to stop static server'
178
+ };
179
+ }
180
+ }
181
+ getStats() {
182
+ return {
183
+ ...this.stats,
184
+ memoryUsage: process.memoryUsage(),
185
+ };
186
+ }
187
+ }
188
+ // ============ Backend Runner (Express/Fastify wrapper) ============
189
+ export class BackendRunner extends BaseRunner {
190
+ // process is inherited from BaseRunner as protected
191
+ async start() {
192
+ try {
193
+ await this.writeLog('system', `Starting backend app: ${this.app.name}`);
194
+ // Determine working directory
195
+ const workingDir = this.app.directory
196
+ ? path.join(this.projectPath, this.app.directory)
197
+ : this.projectPath;
198
+ // Check if directory exists
199
+ try {
200
+ await fs.access(workingDir);
201
+ }
202
+ catch {
203
+ return {
204
+ success: false,
205
+ error: `App directory does not exist: ${this.app.directory || 'root'}`
206
+ };
207
+ }
208
+ // Prepare environment variables
209
+ const env = {
210
+ ...process.env,
211
+ ...this.app.env,
212
+ NODE_ENV: this.app.env?.NODE_ENV || process.env.NODE_ENV || 'development',
213
+ PORT: this.app.port?.toString() || process.env.PORT || '3000',
214
+ };
215
+ // Ensure log directory exists
216
+ const logDir = path.dirname(this.logFile);
217
+ await fs.mkdir(logDir, { recursive: true });
218
+ // Spawn the backend process (cross-platform shell)
219
+ const { shell, args } = shellSpawnOpts(this.app.startCommand);
220
+ this.process = spawn(shell, args, {
221
+ cwd: workingDir,
222
+ env,
223
+ stdio: ['ignore', 'pipe', 'pipe'],
224
+ detached: false,
225
+ });
226
+ if (!this.process.pid) {
227
+ return {
228
+ success: false,
229
+ error: 'Failed to spawn process'
230
+ };
231
+ }
232
+ await this.writeLog('system', `Backend process started with PID: ${this.process.pid}`);
233
+ // Capture stdout
234
+ this.process.stdout?.on('data', async (data) => {
235
+ const output = data.toString();
236
+ await this.writeLog('stdout', output);
237
+ });
238
+ // Capture stderr
239
+ this.process.stderr?.on('data', async (data) => {
240
+ const output = data.toString();
241
+ this.stats.errors++;
242
+ this.updateStats();
243
+ await this.writeLog('stderr', output);
244
+ });
245
+ // Handle process exit
246
+ this.process.on('exit', async (code) => {
247
+ await this.writeLog('system', `Process exited with code ${code}`);
248
+ this.process = null;
249
+ this.callbacks.onExit?.(code);
250
+ });
251
+ // Handle process errors
252
+ this.process.on('error', async (error) => {
253
+ this.stats.errors++;
254
+ this.updateStats();
255
+ await this.writeLog('stderr', `Process error: ${error.message}`);
256
+ this.callbacks.onError?.(error.message);
257
+ });
258
+ // Start stats collection interval
259
+ const statsInterval = setInterval(() => {
260
+ if (this.process) {
261
+ this.updateStats();
262
+ }
263
+ else {
264
+ clearInterval(statsInterval);
265
+ }
266
+ }, 5000);
267
+ return {
268
+ success: true,
269
+ pid: this.process.pid,
270
+ };
271
+ }
272
+ catch (error) {
273
+ await this.writeLog('stderr', `Failed to start backend: ${error.message}`);
274
+ this.callbacks.onError?.(error.message);
275
+ return {
276
+ success: false,
277
+ error: error.message || 'Failed to start backend'
278
+ };
279
+ }
280
+ }
281
+ async stop() {
282
+ try {
283
+ if (!this.process) {
284
+ return { success: true };
285
+ }
286
+ await this.writeLog('system', `Stopping backend app: ${this.app.name}`);
287
+ // Try custom stop command first
288
+ if (this.app.stopCommand) {
289
+ try {
290
+ const workingDir = this.app.directory
291
+ ? path.join(this.projectPath, this.app.directory)
292
+ : this.projectPath;
293
+ const { shell: stopShell, args: stopArgs } = shellSpawnOpts(this.app.stopCommand);
294
+ spawn(stopShell, stopArgs, {
295
+ cwd: workingDir,
296
+ stdio: 'ignore',
297
+ });
298
+ // Wait for graceful shutdown
299
+ await new Promise(resolve => setTimeout(resolve, 2000));
300
+ // If still running, kill it
301
+ if (this.process && this.process.pid) {
302
+ try {
303
+ process.kill(this.process.pid, 0); // Check if still alive
304
+ this.process.kill('SIGTERM');
305
+ await new Promise(resolve => setTimeout(resolve, 2000));
306
+ if (this.process && this.process.pid) {
307
+ try {
308
+ process.kill(this.process.pid, 0);
309
+ this.process.kill('SIGKILL');
310
+ }
311
+ catch {
312
+ // Process already dead
313
+ }
314
+ }
315
+ }
316
+ catch {
317
+ // Process already dead
318
+ }
319
+ }
320
+ }
321
+ catch (error) {
322
+ // Fall back to killing process
323
+ if (this.process) {
324
+ this.process.kill('SIGTERM');
325
+ await new Promise(resolve => setTimeout(resolve, 2000));
326
+ if (this.process) {
327
+ this.process.kill('SIGKILL');
328
+ }
329
+ }
330
+ }
331
+ }
332
+ else {
333
+ // Default: kill the process
334
+ this.process.kill('SIGTERM');
335
+ await new Promise(resolve => setTimeout(resolve, 2000));
336
+ if (this.process) {
337
+ this.process.kill('SIGKILL');
338
+ }
339
+ }
340
+ this.process = null;
341
+ await this.writeLog('system', `Backend app stopped`);
342
+ return { success: true };
343
+ }
344
+ catch (error) {
345
+ await this.writeLog('stderr', `Error stopping backend: ${error.message}`);
346
+ return {
347
+ success: false,
348
+ error: error.message || 'Failed to stop backend'
349
+ };
350
+ }
351
+ }
352
+ getStats() {
353
+ return {
354
+ ...this.stats,
355
+ memoryUsage: this.process ? process.memoryUsage() : undefined,
356
+ };
357
+ }
358
+ }
359
+ // ============ Runner Factory ============
360
+ export function createRunner(app, projectPath, projectId, callbacks) {
361
+ // Use explicit appType if provided, otherwise detect
362
+ if (app.appType === 'static-frontend') {
363
+ return new StaticFrontendRunner(app, projectPath, projectId, callbacks);
364
+ }
365
+ if (app.appType === 'backend') {
366
+ return new BackendRunner(app, projectPath, projectId, callbacks);
367
+ }
368
+ // Auto-detect based on framework and other indicators
369
+ const framework = app.framework?.toLowerCase() || '';
370
+ const type = app.type?.toLowerCase() || '';
371
+ // Static frontend apps
372
+ if (framework.includes('react') ||
373
+ framework.includes('vue') ||
374
+ framework.includes('angular') ||
375
+ framework.includes('svelte') ||
376
+ (framework.includes('next') && type === 'http') ||
377
+ app.name.toLowerCase().includes('frontend') ||
378
+ app.name.toLowerCase().includes('client')) {
379
+ return new StaticFrontendRunner(app, projectPath, projectId, callbacks);
380
+ }
381
+ // Backend apps (Express, Fastify, etc.)
382
+ return new BackendRunner(app, projectPath, projectId, callbacks);
383
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Cloudflared Handlers Module
3
+ *
4
+ * Handles cloudflared:check, cloudflared:sync, cloudflared:login,
5
+ * cloudflared:regenerate operations.
6
+ */
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { spawn, execSync } from 'child_process';
11
+ export function registerCloudflaredHandlers(socket, foreground) {
12
+ socket.on('cloudflared:check:request', async () => {
13
+ try {
14
+ let installed = false;
15
+ let hasCert = false;
16
+ try {
17
+ execSync('which cloudflared', { stdio: 'ignore' });
18
+ installed = true;
19
+ const certPath = path.join(os.homedir(), '.cloudflared', 'cert.pem');
20
+ try {
21
+ const stats = await fs.stat(certPath);
22
+ hasCert = stats.isFile();
23
+ if (foreground && hasCert) {
24
+ console.log(`✓ Found cert.pem at ${certPath}`);
25
+ }
26
+ }
27
+ catch (err) {
28
+ hasCert = false;
29
+ if (foreground) {
30
+ console.log(`✗ cert.pem not found at ${certPath}: ${err.message}`);
31
+ }
32
+ }
33
+ }
34
+ catch {
35
+ installed = false;
36
+ }
37
+ socket.emit('cloudflared:check:response', { installed, hasCert });
38
+ if (foreground) {
39
+ console.log(`Cloudflared check: installed=${installed}, hasCert=${hasCert}`);
40
+ }
41
+ }
42
+ catch (error) {
43
+ socket.emit('cloudflared:check:response', { installed: false, hasCert: false });
44
+ }
45
+ });
46
+ socket.on('cloudflared:sync:request', async () => {
47
+ try {
48
+ const certPath = path.join(os.homedir(), '.cloudflared', 'cert.pem');
49
+ if (foreground) {
50
+ console.log(`Syncing credentials from ${certPath}`);
51
+ }
52
+ const certContent = await fs.readFile(certPath, 'utf-8');
53
+ const tokenMatch = certContent.match(/-----BEGIN ARGO TUNNEL TOKEN-----\s*([\s\S]*?)\s*-----END ARGO TUNNEL TOKEN-----/);
54
+ if (tokenMatch && tokenMatch[1]) {
55
+ const tokenBase64 = tokenMatch[1].replace(/\s/g, '');
56
+ const tokenJson = Buffer.from(tokenBase64, 'base64').toString('utf-8');
57
+ const tokenData = JSON.parse(tokenJson);
58
+ const apiToken = tokenData.apiToken;
59
+ const accountId = tokenData.accountID;
60
+ const zoneId = tokenData.zoneID;
61
+ if (foreground) {
62
+ console.log(`✓ Extracted credentials: accountId=${accountId}, zoneId=${zoneId}`);
63
+ }
64
+ socket.emit('cloudflared:sync:complete', {
65
+ accountId,
66
+ accountName: undefined,
67
+ apiToken,
68
+ zoneId
69
+ });
70
+ }
71
+ else {
72
+ const error = 'Failed to extract token from cert.pem';
73
+ if (foreground) {
74
+ console.error(`✗ ${error}`);
75
+ }
76
+ socket.emit('cloudflared:sync:error', { error });
77
+ }
78
+ }
79
+ catch (error) {
80
+ const errorMsg = `Failed to read cert.pem: ${error.message}`;
81
+ if (foreground) {
82
+ console.error(`✗ ${errorMsg}`);
83
+ }
84
+ socket.emit('cloudflared:sync:error', { error: errorMsg });
85
+ }
86
+ });
87
+ socket.on('cloudflared:login:request', async () => {
88
+ try {
89
+ // Check if cloudflared is installed
90
+ try {
91
+ execSync('which cloudflared', { stdio: 'ignore' });
92
+ }
93
+ catch {
94
+ socket.emit('cloudflared:login:error', { error: 'cloudflared is not installed' });
95
+ return;
96
+ }
97
+ const loginProcess = spawn('cloudflared', ['tunnel', 'login'], {
98
+ stdio: ['ignore', 'pipe', 'pipe']
99
+ });
100
+ let stdout = '';
101
+ let stderr = '';
102
+ let urlEmitted = false;
103
+ let alreadyLoggedIn = false;
104
+ let certPath = null;
105
+ const extractCertPath = (text) => {
106
+ const pathMatch = text.match(/existing certificate at\s+([^\s]+)/i) ||
107
+ text.match(/certificate at\s+([^\s]+)/i) ||
108
+ text.match(/cert\.pem.*?at\s+([^\s]+)/i);
109
+ return pathMatch ? pathMatch[1] : null;
110
+ };
111
+ const extractLoginUrl = (text) => {
112
+ const urlPatterns = [
113
+ /https:\/\/dash\.cloudflare\.com\/argotunnel[^\s\)]+/g,
114
+ /https:\/\/[^\s\)]+cloudflareaccess\.org[^\s\)]+/g,
115
+ /https:\/\/[^\s\)]+cloudflare\.com[^\s\)]+/g
116
+ ];
117
+ for (const pattern of urlPatterns) {
118
+ const matches = text.match(pattern);
119
+ if (matches && matches.length > 0) {
120
+ return matches[0];
121
+ }
122
+ }
123
+ return null;
124
+ };
125
+ loginProcess.stdout.on('data', (data) => {
126
+ const text = data.toString();
127
+ stdout += text;
128
+ if (text.includes('existing certificate') || text.includes('cert.pem which login would overwrite')) {
129
+ alreadyLoggedIn = true;
130
+ const extractedPath = extractCertPath(text);
131
+ if (extractedPath) {
132
+ certPath = extractedPath;
133
+ }
134
+ }
135
+ if (!alreadyLoggedIn && !urlEmitted) {
136
+ const url = extractLoginUrl(text);
137
+ if (url) {
138
+ urlEmitted = true;
139
+ socket.emit('cloudflared:login:url', { loginUrl: url });
140
+ }
141
+ }
142
+ });
143
+ loginProcess.stderr.on('data', (data) => {
144
+ const text = data.toString();
145
+ stderr += text;
146
+ if (text.includes('existing certificate') || text.includes('cert.pem which login would overwrite')) {
147
+ alreadyLoggedIn = true;
148
+ const extractedPath = extractCertPath(text);
149
+ if (extractedPath) {
150
+ certPath = extractedPath;
151
+ }
152
+ }
153
+ if (!alreadyLoggedIn && !urlEmitted) {
154
+ const url = extractLoginUrl(text);
155
+ if (url) {
156
+ urlEmitted = true;
157
+ socket.emit('cloudflared:login:url', { loginUrl: url });
158
+ }
159
+ }
160
+ });
161
+ loginProcess.on('close', async (code) => {
162
+ if (alreadyLoggedIn && certPath) {
163
+ // Already logged in - extract credentials from existing cert
164
+ try {
165
+ if (foreground) {
166
+ console.log(`Already logged in, extracting credentials from ${certPath}`);
167
+ }
168
+ const certContent = await fs.readFile(certPath, 'utf-8');
169
+ const tokenMatch = certContent.match(/-----BEGIN ARGO TUNNEL TOKEN-----\s*([\s\S]*?)\s*-----END ARGO TUNNEL TOKEN-----/);
170
+ if (tokenMatch && tokenMatch[1]) {
171
+ const tokenBase64 = tokenMatch[1].replace(/\s/g, '');
172
+ const tokenJson = Buffer.from(tokenBase64, 'base64').toString('utf-8');
173
+ const tokenData = JSON.parse(tokenJson);
174
+ if (foreground) {
175
+ console.log(`✓ Extracted credentials from existing cert`);
176
+ }
177
+ socket.emit('cloudflared:login:complete', {
178
+ accountId: tokenData.accountID,
179
+ accountName: undefined,
180
+ apiToken: tokenData.apiToken,
181
+ zoneId: tokenData.zoneID
182
+ });
183
+ }
184
+ else {
185
+ const error = 'Failed to extract token from existing cert.pem';
186
+ if (foreground) {
187
+ console.error(`✗ ${error}`);
188
+ }
189
+ socket.emit('cloudflared:login:error', { error });
190
+ }
191
+ }
192
+ catch (error) {
193
+ const errorMsg = `Failed to read cert.pem: ${error.message}`;
194
+ if (foreground) {
195
+ console.error(`✗ ${errorMsg}`);
196
+ }
197
+ socket.emit('cloudflared:login:error', { error: errorMsg });
198
+ }
199
+ }
200
+ else if (code === 0 && !alreadyLoggedIn) {
201
+ // Login completed successfully - wait a moment then extract credentials
202
+ if (foreground) {
203
+ console.log('Login completed, extracting credentials...');
204
+ }
205
+ setTimeout(async () => {
206
+ try {
207
+ const certFilePath = path.join(os.homedir(), '.cloudflared', 'cert.pem');
208
+ const certContent = await fs.readFile(certFilePath, 'utf-8');
209
+ const tokenMatch = certContent.match(/-----BEGIN ARGO TUNNEL TOKEN-----\s*([\s\S]*?)\s*-----END ARGO TUNNEL TOKEN-----/);
210
+ if (tokenMatch && tokenMatch[1]) {
211
+ const tokenBase64 = tokenMatch[1].replace(/\s/g, '');
212
+ const tokenJson = Buffer.from(tokenBase64, 'base64').toString('utf-8');
213
+ const tokenData = JSON.parse(tokenJson);
214
+ if (foreground) {
215
+ console.log(`✓ Extracted credentials after login`);
216
+ }
217
+ socket.emit('cloudflared:login:complete', {
218
+ accountId: tokenData.accountID,
219
+ accountName: undefined,
220
+ apiToken: tokenData.apiToken,
221
+ zoneId: tokenData.zoneID
222
+ });
223
+ }
224
+ else {
225
+ const error = 'Failed to extract token from cert.pem after login';
226
+ if (foreground) {
227
+ console.error(`✗ ${error}`);
228
+ }
229
+ socket.emit('cloudflared:login:error', { error });
230
+ }
231
+ }
232
+ catch (error) {
233
+ const errorMsg = `Failed to read cert.pem after login: ${error.message}`;
234
+ if (foreground) {
235
+ console.error(`✗ ${errorMsg}`);
236
+ }
237
+ socket.emit('cloudflared:login:error', { error: errorMsg });
238
+ }
239
+ }, 1000);
240
+ }
241
+ else if (!alreadyLoggedIn) {
242
+ const error = `Login failed with code ${code}: ${stderr || stdout}`;
243
+ if (foreground) {
244
+ console.error(`✗ ${error}`);
245
+ }
246
+ socket.emit('cloudflared:login:error', { error });
247
+ }
248
+ });
249
+ loginProcess.on('error', (error) => {
250
+ socket.emit('cloudflared:login:error', { error: error.message });
251
+ });
252
+ }
253
+ catch (error) {
254
+ socket.emit('cloudflared:login:error', { error: error.message });
255
+ }
256
+ });
257
+ socket.on('cloudflared:regenerate:request', async () => {
258
+ try {
259
+ const certPath = path.join(os.homedir(), '.cloudflared', 'cert.pem');
260
+ // Delete existing cert.pem
261
+ try {
262
+ await fs.unlink(certPath);
263
+ if (foreground) {
264
+ console.log(`✓ Deleted existing cert.pem`);
265
+ }
266
+ }
267
+ catch (error) {
268
+ if (foreground && error.code !== 'ENOENT') {
269
+ console.log(`Note: Could not delete cert.pem: ${error.message}`);
270
+ }
271
+ }
272
+ // Emit success - frontend will then trigger login
273
+ socket.emit('cloudflared:regenerate:complete', {});
274
+ }
275
+ catch (error) {
276
+ socket.emit('cloudflared:regenerate:error', { error: error.message });
277
+ }
278
+ });
279
+ }