@nclamvn/vibecode-cli 2.2.0 → 3.0.0
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/bin/vibecode.js +101 -2
- package/package.json +3 -1
- package/src/commands/config.js +42 -4
- package/src/commands/deploy.js +728 -0
- package/src/commands/favorite.js +412 -0
- package/src/commands/feedback.js +473 -0
- package/src/commands/go.js +170 -4
- package/src/commands/history.js +249 -0
- package/src/commands/images.js +465 -0
- package/src/commands/preview.js +554 -0
- package/src/commands/voice.js +580 -0
- package/src/commands/watch.js +3 -20
- package/src/index.js +49 -2
- package/src/services/image-service.js +513 -0
- package/src/utils/history.js +357 -0
- package/src/utils/notifications.js +343 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Preview Command
|
|
3
|
+
// Auto-start dev server and open browser
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { spawn, exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import net from 'net';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
|
|
16
|
+
// Store running processes
|
|
17
|
+
const runningProcesses = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Preview command entry point
|
|
21
|
+
*/
|
|
22
|
+
export async function previewCommand(options = {}) {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
|
|
25
|
+
// Stop preview
|
|
26
|
+
if (options.stop) {
|
|
27
|
+
return stopPreview(cwd);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if it's a valid project
|
|
31
|
+
const projectType = await detectProjectType(cwd);
|
|
32
|
+
|
|
33
|
+
if (!projectType) {
|
|
34
|
+
console.log(chalk.red(`
|
|
35
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
36
|
+
│ ❌ NOT A VALID PROJECT │
|
|
37
|
+
│ │
|
|
38
|
+
│ No package.json found or unsupported project type. │
|
|
39
|
+
│ │
|
|
40
|
+
│ Supported: Next.js, React, Vue, Vite, Express │
|
|
41
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
42
|
+
`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find available port
|
|
47
|
+
const requestedPort = parseInt(options.port) || 3000;
|
|
48
|
+
const port = await findAvailablePort(requestedPort);
|
|
49
|
+
|
|
50
|
+
console.log(chalk.cyan(`
|
|
51
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
52
|
+
│ 🚀 VIBECODE PREVIEW │
|
|
53
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
54
|
+
`));
|
|
55
|
+
|
|
56
|
+
console.log(chalk.gray(` Project: ${path.basename(cwd)}`));
|
|
57
|
+
console.log(chalk.gray(` Type: ${projectType.name}`));
|
|
58
|
+
console.log(chalk.gray(` Port: ${port}\n`));
|
|
59
|
+
|
|
60
|
+
// Step 1: Install dependencies if needed
|
|
61
|
+
const needsInstall = await checkNeedsInstall(cwd);
|
|
62
|
+
|
|
63
|
+
if (needsInstall) {
|
|
64
|
+
console.log(chalk.yellow(' 📦 Installing dependencies...\n'));
|
|
65
|
+
try {
|
|
66
|
+
await installDependencies(cwd);
|
|
67
|
+
console.log(chalk.green(' ✅ Dependencies installed\n'));
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.log(chalk.red(` ❌ Install failed: ${error.message}\n`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 2: Start dev server
|
|
75
|
+
console.log(chalk.yellow(' 🔧 Starting dev server...\n'));
|
|
76
|
+
|
|
77
|
+
let serverProcess;
|
|
78
|
+
try {
|
|
79
|
+
serverProcess = await startDevServer(cwd, projectType, port);
|
|
80
|
+
runningProcesses.set(cwd, serverProcess);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.log(chalk.red(` ❌ Failed to start server: ${error.message}\n`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Step 3: Wait for server to be ready
|
|
87
|
+
const isReady = await waitForServer(port);
|
|
88
|
+
|
|
89
|
+
if (!isReady) {
|
|
90
|
+
console.log(chalk.yellow(' ⚠️ Server may still be starting...\n'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const url = `http://localhost:${port}`;
|
|
94
|
+
|
|
95
|
+
// Step 4: Display success
|
|
96
|
+
console.log(chalk.green(`
|
|
97
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
98
|
+
│ ✅ PREVIEW READY │
|
|
99
|
+
│ │
|
|
100
|
+
│ 🌐 Local: ${url.padEnd(49)}│
|
|
101
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
102
|
+
`));
|
|
103
|
+
|
|
104
|
+
// Step 5: Show QR code for mobile
|
|
105
|
+
if (options.qr) {
|
|
106
|
+
await showQRCode(port);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 6: Open browser
|
|
110
|
+
if (options.open !== false) {
|
|
111
|
+
try {
|
|
112
|
+
await openBrowser(url);
|
|
113
|
+
console.log(chalk.green(' ✅ Opened in browser\n'));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.log(chalk.yellow(` ⚠️ Could not open browser: ${error.message}\n`));
|
|
116
|
+
console.log(chalk.gray(` Open manually: ${url}\n`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 7: Show controls
|
|
121
|
+
console.log(chalk.gray(` Controls:
|
|
122
|
+
${chalk.cyan('vibecode preview --stop')} Stop server
|
|
123
|
+
${chalk.cyan('Ctrl+C')} Stop server
|
|
124
|
+
`));
|
|
125
|
+
|
|
126
|
+
// Handle graceful shutdown
|
|
127
|
+
process.on('SIGINT', () => {
|
|
128
|
+
console.log(chalk.yellow('\n\n Stopping server...'));
|
|
129
|
+
if (serverProcess) {
|
|
130
|
+
serverProcess.kill();
|
|
131
|
+
}
|
|
132
|
+
console.log(chalk.green(' ✅ Server stopped\n'));
|
|
133
|
+
process.exit(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Keep process running unless detached
|
|
137
|
+
if (!options.detach) {
|
|
138
|
+
await new Promise(() => {}); // Keep alive
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detect project type from package.json
|
|
144
|
+
*/
|
|
145
|
+
async function detectProjectType(cwd) {
|
|
146
|
+
try {
|
|
147
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
148
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
149
|
+
const pkg = JSON.parse(pkgContent);
|
|
150
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
151
|
+
const scripts = pkg.scripts || {};
|
|
152
|
+
|
|
153
|
+
if (deps.next) {
|
|
154
|
+
return {
|
|
155
|
+
name: 'Next.js',
|
|
156
|
+
devScript: 'dev',
|
|
157
|
+
defaultPort: 3000,
|
|
158
|
+
portFlag: '-p'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (deps.vite) {
|
|
163
|
+
return {
|
|
164
|
+
name: 'Vite',
|
|
165
|
+
devScript: 'dev',
|
|
166
|
+
defaultPort: 5173,
|
|
167
|
+
portFlag: '--port'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (deps['react-scripts']) {
|
|
172
|
+
return {
|
|
173
|
+
name: 'Create React App',
|
|
174
|
+
devScript: 'start',
|
|
175
|
+
defaultPort: 3000,
|
|
176
|
+
portEnv: 'PORT'
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (deps.vue || deps['@vue/cli-service']) {
|
|
181
|
+
return {
|
|
182
|
+
name: 'Vue',
|
|
183
|
+
devScript: scripts.dev ? 'dev' : 'serve',
|
|
184
|
+
defaultPort: 8080,
|
|
185
|
+
portFlag: '--port'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (deps.nuxt) {
|
|
190
|
+
return {
|
|
191
|
+
name: 'Nuxt',
|
|
192
|
+
devScript: 'dev',
|
|
193
|
+
defaultPort: 3000,
|
|
194
|
+
portFlag: '--port'
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (deps.svelte || deps['@sveltejs/kit']) {
|
|
199
|
+
return {
|
|
200
|
+
name: 'SvelteKit',
|
|
201
|
+
devScript: 'dev',
|
|
202
|
+
defaultPort: 5173,
|
|
203
|
+
portFlag: '--port'
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (deps.express || deps.fastify || deps.koa || deps.hono) {
|
|
208
|
+
return {
|
|
209
|
+
name: 'Node.js Server',
|
|
210
|
+
devScript: scripts.dev ? 'dev' : 'start',
|
|
211
|
+
defaultPort: 3000,
|
|
212
|
+
portEnv: 'PORT'
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Generic with dev script
|
|
217
|
+
if (scripts.dev) {
|
|
218
|
+
return {
|
|
219
|
+
name: 'Generic',
|
|
220
|
+
devScript: 'dev',
|
|
221
|
+
defaultPort: 3000,
|
|
222
|
+
portEnv: 'PORT'
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (scripts.start) {
|
|
227
|
+
return {
|
|
228
|
+
name: 'Generic',
|
|
229
|
+
devScript: 'start',
|
|
230
|
+
defaultPort: 3000,
|
|
231
|
+
portEnv: 'PORT'
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null;
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if node_modules needs to be installed
|
|
243
|
+
*/
|
|
244
|
+
async function checkNeedsInstall(cwd) {
|
|
245
|
+
const nodeModulesPath = path.join(cwd, 'node_modules');
|
|
246
|
+
try {
|
|
247
|
+
await fs.access(nodeModulesPath);
|
|
248
|
+
// Check if node_modules has content
|
|
249
|
+
const contents = await fs.readdir(nodeModulesPath);
|
|
250
|
+
return contents.length < 5; // Likely incomplete install
|
|
251
|
+
} catch {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Install dependencies
|
|
258
|
+
*/
|
|
259
|
+
async function installDependencies(cwd) {
|
|
260
|
+
return new Promise((resolve, reject) => {
|
|
261
|
+
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
262
|
+
let i = 0;
|
|
263
|
+
|
|
264
|
+
const interval = setInterval(() => {
|
|
265
|
+
process.stdout.write(`\r ${spinner[i++ % spinner.length]} Installing...`);
|
|
266
|
+
}, 100);
|
|
267
|
+
|
|
268
|
+
const child = spawn('npm', ['install'], {
|
|
269
|
+
cwd,
|
|
270
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
271
|
+
shell: true
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
let stderr = '';
|
|
275
|
+
child.stderr.on('data', (data) => {
|
|
276
|
+
stderr += data.toString();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
child.on('close', (code) => {
|
|
280
|
+
clearInterval(interval);
|
|
281
|
+
process.stdout.write('\r \r');
|
|
282
|
+
|
|
283
|
+
if (code === 0) {
|
|
284
|
+
resolve();
|
|
285
|
+
} else {
|
|
286
|
+
reject(new Error(stderr || `npm install failed with code ${code}`));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
child.on('error', (err) => {
|
|
291
|
+
clearInterval(interval);
|
|
292
|
+
reject(err);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Start development server
|
|
299
|
+
*/
|
|
300
|
+
async function startDevServer(cwd, projectType, port) {
|
|
301
|
+
const env = { ...process.env };
|
|
302
|
+
|
|
303
|
+
// Set port via environment variable if needed
|
|
304
|
+
if (projectType.portEnv) {
|
|
305
|
+
env[projectType.portEnv] = String(port);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Build the command
|
|
309
|
+
const args = ['run', projectType.devScript];
|
|
310
|
+
|
|
311
|
+
// Add port flag if supported
|
|
312
|
+
if (projectType.portFlag) {
|
|
313
|
+
args.push('--', projectType.portFlag, String(port));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const child = spawn('npm', args, {
|
|
317
|
+
cwd,
|
|
318
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
319
|
+
shell: true,
|
|
320
|
+
env,
|
|
321
|
+
detached: false
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Log output for debugging
|
|
325
|
+
child.stdout.on('data', (data) => {
|
|
326
|
+
const output = data.toString().trim();
|
|
327
|
+
// Show ready messages
|
|
328
|
+
if (output.toLowerCase().includes('ready') ||
|
|
329
|
+
output.toLowerCase().includes('started') ||
|
|
330
|
+
output.toLowerCase().includes('listening') ||
|
|
331
|
+
output.toLowerCase().includes('compiled')) {
|
|
332
|
+
console.log(chalk.gray(` ${output.substring(0, 60)}`));
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
child.stderr.on('data', (data) => {
|
|
337
|
+
const output = data.toString().trim();
|
|
338
|
+
// Only show actual errors, not warnings
|
|
339
|
+
if (output.toLowerCase().includes('error') &&
|
|
340
|
+
!output.toLowerCase().includes('warning')) {
|
|
341
|
+
console.log(chalk.red(` ${output.substring(0, 60)}`));
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
child.on('error', (error) => {
|
|
346
|
+
console.log(chalk.red(` Server error: ${error.message}`));
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return child;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Wait for server to be ready by checking port
|
|
354
|
+
*/
|
|
355
|
+
async function waitForServer(port, maxAttempts = 30) {
|
|
356
|
+
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
357
|
+
let i = 0;
|
|
358
|
+
|
|
359
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
360
|
+
process.stdout.write(`\r ${spinner[i++ % spinner.length]} Waiting for server...`);
|
|
361
|
+
|
|
362
|
+
const isReady = await checkPort(port);
|
|
363
|
+
|
|
364
|
+
if (isReady) {
|
|
365
|
+
process.stdout.write('\r ✅ Server ready \n');
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
await sleep(1000);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
process.stdout.write('\r ⚠️ Server taking longer than expected\n');
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Check if a port is in use (server is running)
|
|
378
|
+
*/
|
|
379
|
+
function checkPort(port) {
|
|
380
|
+
return new Promise((resolve) => {
|
|
381
|
+
const socket = new net.Socket();
|
|
382
|
+
|
|
383
|
+
socket.setTimeout(1000);
|
|
384
|
+
|
|
385
|
+
socket.on('connect', () => {
|
|
386
|
+
socket.destroy();
|
|
387
|
+
resolve(true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
socket.on('timeout', () => {
|
|
391
|
+
socket.destroy();
|
|
392
|
+
resolve(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
socket.on('error', () => {
|
|
396
|
+
socket.destroy();
|
|
397
|
+
resolve(false);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
socket.connect(port, '127.0.0.1');
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Find an available port starting from the given port
|
|
406
|
+
*/
|
|
407
|
+
async function findAvailablePort(startPort) {
|
|
408
|
+
let port = startPort;
|
|
409
|
+
|
|
410
|
+
while (port < startPort + 100) {
|
|
411
|
+
const inUse = await checkPort(port);
|
|
412
|
+
if (!inUse) {
|
|
413
|
+
return port;
|
|
414
|
+
}
|
|
415
|
+
port++;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return startPort;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Open URL in default browser
|
|
423
|
+
*/
|
|
424
|
+
async function openBrowser(url) {
|
|
425
|
+
try {
|
|
426
|
+
const open = (await import('open')).default;
|
|
427
|
+
await open(url);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// Fallback for different OS
|
|
430
|
+
const { platform } = process;
|
|
431
|
+
const commands = {
|
|
432
|
+
darwin: `open "${url}"`,
|
|
433
|
+
win32: `start "" "${url}"`,
|
|
434
|
+
linux: `xdg-open "${url}"`
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
if (commands[platform]) {
|
|
438
|
+
await execAsync(commands[platform]);
|
|
439
|
+
} else {
|
|
440
|
+
throw new Error('Unsupported platform');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Show QR code for mobile access
|
|
447
|
+
*/
|
|
448
|
+
async function showQRCode(port) {
|
|
449
|
+
try {
|
|
450
|
+
// Get local IP
|
|
451
|
+
const nets = os.networkInterfaces();
|
|
452
|
+
let localIP = 'localhost';
|
|
453
|
+
|
|
454
|
+
for (const name of Object.keys(nets)) {
|
|
455
|
+
for (const netInterface of nets[name]) {
|
|
456
|
+
if (netInterface.family === 'IPv4' && !netInterface.internal) {
|
|
457
|
+
localIP = netInterface.address;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (localIP !== 'localhost') break;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const networkUrl = `http://${localIP}:${port}`;
|
|
465
|
+
|
|
466
|
+
// Try to generate QR code
|
|
467
|
+
try {
|
|
468
|
+
const QRCode = (await import('qrcode')).default;
|
|
469
|
+
const qrString = await QRCode.toString(networkUrl, {
|
|
470
|
+
type: 'terminal',
|
|
471
|
+
small: true
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
console.log(chalk.cyan('\n 📱 Scan to open on mobile:\n'));
|
|
475
|
+
// Indent QR code
|
|
476
|
+
const indentedQR = qrString.split('\n').map(line => ' ' + line).join('\n');
|
|
477
|
+
console.log(indentedQR);
|
|
478
|
+
console.log(chalk.gray(`\n Network: ${networkUrl}\n`));
|
|
479
|
+
} catch {
|
|
480
|
+
// QR code package not available, just show URL
|
|
481
|
+
console.log(chalk.cyan('\n 📱 Mobile access:\n'));
|
|
482
|
+
console.log(chalk.white(` ${networkUrl}\n`));
|
|
483
|
+
console.log(chalk.gray(' (Install qrcode for QR: npm i qrcode)\n'));
|
|
484
|
+
}
|
|
485
|
+
} catch (error) {
|
|
486
|
+
console.log(chalk.yellow(` ⚠️ Could not get network info: ${error.message}\n`));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Stop running preview server
|
|
492
|
+
*/
|
|
493
|
+
async function stopPreview(cwd) {
|
|
494
|
+
const runningProcess = runningProcesses.get(cwd);
|
|
495
|
+
|
|
496
|
+
if (runningProcess) {
|
|
497
|
+
runningProcess.kill();
|
|
498
|
+
runningProcesses.delete(cwd);
|
|
499
|
+
console.log(chalk.green('\n ✅ Preview server stopped\n'));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Try to kill any process on common dev ports
|
|
504
|
+
console.log(chalk.yellow('\n Attempting to stop dev servers...\n'));
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const { platform } = process;
|
|
508
|
+
|
|
509
|
+
if (platform === 'win32') {
|
|
510
|
+
// Windows
|
|
511
|
+
await execAsync('netstat -ano | findstr :3000 | findstr LISTENING').catch(() => {});
|
|
512
|
+
} else {
|
|
513
|
+
// Unix-like
|
|
514
|
+
await execAsync('lsof -ti:3000 | xargs kill -9 2>/dev/null || true');
|
|
515
|
+
await execAsync('lsof -ti:3001 | xargs kill -9 2>/dev/null || true');
|
|
516
|
+
await execAsync('lsof -ti:5173 | xargs kill -9 2>/dev/null || true');
|
|
517
|
+
await execAsync('lsof -ti:8080 | xargs kill -9 2>/dev/null || true');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
console.log(chalk.green(' ✅ Dev servers stopped\n'));
|
|
521
|
+
} catch {
|
|
522
|
+
console.log(chalk.gray(' No running servers found.\n'));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Sleep helper
|
|
528
|
+
*/
|
|
529
|
+
function sleep(ms) {
|
|
530
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Auto preview for use after builds
|
|
535
|
+
* Called from go.js after successful build
|
|
536
|
+
*/
|
|
537
|
+
export async function autoPreview(projectPath, options = {}) {
|
|
538
|
+
const originalCwd = process.cwd();
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
process.chdir(projectPath);
|
|
542
|
+
await previewCommand({
|
|
543
|
+
open: true,
|
|
544
|
+
qr: options.qr || false,
|
|
545
|
+
port: options.port,
|
|
546
|
+
detach: true, // Don't block in auto mode
|
|
547
|
+
...options
|
|
548
|
+
});
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.log(chalk.yellow(` ⚠️ Preview failed: ${error.message}\n`));
|
|
551
|
+
} finally {
|
|
552
|
+
process.chdir(originalCwd);
|
|
553
|
+
}
|
|
554
|
+
}
|