@nclamvn/vibecode-cli 2.2.1 → 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 +86 -0
- package/package.json +1 -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 +128 -0
- package/src/commands/history.js +249 -0
- package/src/commands/images.js +465 -0
- package/src/commands/voice.js +580 -0
- package/src/commands/watch.js +3 -20
- package/src/index.js +46 -1
- 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,728 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Deploy Command
|
|
3
|
+
// One-command deployment to cloud platforms
|
|
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 inquirer from 'inquirer';
|
|
12
|
+
import { notifyDeployComplete } from '../utils/notifications.js';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
|
|
16
|
+
// Supported deployment platforms
|
|
17
|
+
const PLATFORMS = {
|
|
18
|
+
vercel: {
|
|
19
|
+
name: 'Vercel',
|
|
20
|
+
icon: '▲',
|
|
21
|
+
cli: 'vercel',
|
|
22
|
+
installCmd: 'npm install -g vercel',
|
|
23
|
+
deployCmd: 'vercel',
|
|
24
|
+
previewCmd: 'vercel',
|
|
25
|
+
prodCmd: 'vercel --prod',
|
|
26
|
+
loginCmd: 'vercel login',
|
|
27
|
+
whoamiCmd: 'vercel whoami',
|
|
28
|
+
configFile: 'vercel.json',
|
|
29
|
+
recommended: true
|
|
30
|
+
},
|
|
31
|
+
netlify: {
|
|
32
|
+
name: 'Netlify',
|
|
33
|
+
icon: '◆',
|
|
34
|
+
cli: 'netlify',
|
|
35
|
+
installCmd: 'npm install -g netlify-cli',
|
|
36
|
+
deployCmd: 'netlify deploy',
|
|
37
|
+
previewCmd: 'netlify deploy',
|
|
38
|
+
prodCmd: 'netlify deploy --prod',
|
|
39
|
+
loginCmd: 'netlify login',
|
|
40
|
+
whoamiCmd: 'netlify status',
|
|
41
|
+
configFile: 'netlify.toml',
|
|
42
|
+
recommended: false
|
|
43
|
+
},
|
|
44
|
+
'github-pages': {
|
|
45
|
+
name: 'GitHub Pages',
|
|
46
|
+
icon: '⬡',
|
|
47
|
+
cli: 'gh',
|
|
48
|
+
installCmd: 'npm install -g gh-pages && brew install gh',
|
|
49
|
+
deployCmd: 'npx gh-pages -d dist',
|
|
50
|
+
previewCmd: null,
|
|
51
|
+
prodCmd: 'npx gh-pages -d dist',
|
|
52
|
+
loginCmd: 'gh auth login',
|
|
53
|
+
whoamiCmd: 'gh auth status',
|
|
54
|
+
configFile: null,
|
|
55
|
+
recommended: false,
|
|
56
|
+
requiresBuild: true,
|
|
57
|
+
buildCmd: 'npm run build'
|
|
58
|
+
},
|
|
59
|
+
railway: {
|
|
60
|
+
name: 'Railway',
|
|
61
|
+
icon: '🚂',
|
|
62
|
+
cli: 'railway',
|
|
63
|
+
installCmd: 'npm install -g @railway/cli',
|
|
64
|
+
deployCmd: 'railway up',
|
|
65
|
+
previewCmd: 'railway up',
|
|
66
|
+
prodCmd: 'railway up',
|
|
67
|
+
loginCmd: 'railway login',
|
|
68
|
+
whoamiCmd: 'railway whoami',
|
|
69
|
+
configFile: 'railway.json',
|
|
70
|
+
recommended: false
|
|
71
|
+
},
|
|
72
|
+
render: {
|
|
73
|
+
name: 'Render',
|
|
74
|
+
icon: '🎨',
|
|
75
|
+
cli: 'render',
|
|
76
|
+
installCmd: 'npm install -g render-cli',
|
|
77
|
+
deployCmd: 'render deploy',
|
|
78
|
+
previewCmd: 'render deploy',
|
|
79
|
+
prodCmd: 'render deploy',
|
|
80
|
+
loginCmd: 'render login',
|
|
81
|
+
whoamiCmd: 'render whoami',
|
|
82
|
+
configFile: 'render.yaml',
|
|
83
|
+
recommended: false
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Deploy command entry point
|
|
89
|
+
*/
|
|
90
|
+
export async function deployCommand(options = {}) {
|
|
91
|
+
const cwd = process.cwd();
|
|
92
|
+
|
|
93
|
+
// Check deployment status
|
|
94
|
+
if (options.status) {
|
|
95
|
+
return showDeploymentStatus(cwd);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Show deployment history
|
|
99
|
+
if (options.history) {
|
|
100
|
+
return showDeploymentHistory(cwd);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Detect platform from options
|
|
104
|
+
let platform = null;
|
|
105
|
+
if (options.vercel) platform = 'vercel';
|
|
106
|
+
else if (options.netlify) platform = 'netlify';
|
|
107
|
+
else if (options.githubPages) platform = 'github-pages';
|
|
108
|
+
else if (options.railway) platform = 'railway';
|
|
109
|
+
else if (options.render) platform = 'render';
|
|
110
|
+
|
|
111
|
+
// Interactive mode if no platform specified
|
|
112
|
+
if (!platform) {
|
|
113
|
+
platform = await selectPlatform();
|
|
114
|
+
if (!platform) return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const platformConfig = PLATFORMS[platform];
|
|
118
|
+
|
|
119
|
+
console.log(chalk.cyan(`
|
|
120
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
121
|
+
│ 🚀 VIBECODE DEPLOY │
|
|
122
|
+
│ │
|
|
123
|
+
│ Platform: ${(platformConfig.icon + ' ' + platformConfig.name).padEnd(51)}│
|
|
124
|
+
│ │
|
|
125
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
126
|
+
`));
|
|
127
|
+
|
|
128
|
+
// Step 1: Check if project is valid
|
|
129
|
+
const isValidProject = await checkProject(cwd);
|
|
130
|
+
if (!isValidProject) {
|
|
131
|
+
console.log(chalk.red(' ❌ No package.json found. Is this a valid project?\n'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Step 2: Check if CLI is installed
|
|
136
|
+
console.log(chalk.gray(' Checking prerequisites...\n'));
|
|
137
|
+
const cliInstalled = await checkCLI(platformConfig.cli);
|
|
138
|
+
|
|
139
|
+
if (!cliInstalled) {
|
|
140
|
+
console.log(chalk.yellow(` ⚠️ ${platformConfig.name} CLI not found.\n`));
|
|
141
|
+
|
|
142
|
+
const { install } = await inquirer.prompt([{
|
|
143
|
+
type: 'confirm',
|
|
144
|
+
name: 'install',
|
|
145
|
+
message: `Install ${platformConfig.name} CLI?`,
|
|
146
|
+
default: true
|
|
147
|
+
}]);
|
|
148
|
+
|
|
149
|
+
if (install) {
|
|
150
|
+
const installed = await installCLI(platformConfig);
|
|
151
|
+
if (!installed) return;
|
|
152
|
+
} else {
|
|
153
|
+
console.log(chalk.gray(`\n Install manually: ${platformConfig.installCmd}\n`));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
console.log(chalk.green(` ✓ ${platformConfig.name} CLI installed`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Step 3: Check authentication
|
|
161
|
+
const isLoggedIn = await checkAuth(platformConfig);
|
|
162
|
+
|
|
163
|
+
if (!isLoggedIn) {
|
|
164
|
+
console.log(chalk.yellow('\n 🔐 Authentication required.\n'));
|
|
165
|
+
const authenticated = await authenticate(platformConfig);
|
|
166
|
+
if (!authenticated) {
|
|
167
|
+
console.log(chalk.red(' ❌ Authentication failed.\n'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
console.log(chalk.green(` ✓ Authenticated`));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 4: Check/Create config
|
|
175
|
+
await ensureConfig(cwd, platform, platformConfig, options);
|
|
176
|
+
|
|
177
|
+
// Step 5: Build if needed
|
|
178
|
+
if (platformConfig.requiresBuild) {
|
|
179
|
+
console.log(chalk.yellow('\n 📦 Building project...\n'));
|
|
180
|
+
const buildSuccess = await buildProject(cwd, platformConfig);
|
|
181
|
+
if (!buildSuccess) {
|
|
182
|
+
console.log(chalk.red(' ❌ Build failed. Fix errors and try again.\n'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Step 6: Deploy
|
|
188
|
+
console.log(chalk.yellow('\n 🚀 Deploying...\n'));
|
|
189
|
+
|
|
190
|
+
const isProduction = !options.preview;
|
|
191
|
+
const result = await deploy(cwd, platformConfig, isProduction, options);
|
|
192
|
+
|
|
193
|
+
// Step 7: Save deployment info
|
|
194
|
+
await saveDeploymentInfo(cwd, platform, result);
|
|
195
|
+
|
|
196
|
+
// Step 8: Send notification if enabled
|
|
197
|
+
if (options.notify) {
|
|
198
|
+
notifyDeployComplete(result.success, platformConfig.name, result.url);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Step 9: Show result
|
|
202
|
+
showDeploymentResult(result, platformConfig);
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Interactive platform selection
|
|
209
|
+
*/
|
|
210
|
+
async function selectPlatform() {
|
|
211
|
+
console.log(chalk.cyan(`
|
|
212
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
213
|
+
│ 🚀 VIBECODE DEPLOY │
|
|
214
|
+
│ │
|
|
215
|
+
│ Deploy your project to the cloud │
|
|
216
|
+
│ │
|
|
217
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
218
|
+
`));
|
|
219
|
+
|
|
220
|
+
const choices = Object.entries(PLATFORMS).map(([key, config]) => ({
|
|
221
|
+
name: `${config.icon} ${config.name}${config.recommended ? chalk.green(' (Recommended)') : ''}`,
|
|
222
|
+
value: key
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
choices.push({ name: '👋 Cancel', value: null });
|
|
226
|
+
|
|
227
|
+
const { platform } = await inquirer.prompt([{
|
|
228
|
+
type: 'list',
|
|
229
|
+
name: 'platform',
|
|
230
|
+
message: 'Select deployment platform:',
|
|
231
|
+
choices
|
|
232
|
+
}]);
|
|
233
|
+
|
|
234
|
+
return platform;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if project is valid
|
|
239
|
+
*/
|
|
240
|
+
async function checkProject(cwd) {
|
|
241
|
+
try {
|
|
242
|
+
await fs.access(path.join(cwd, 'package.json'));
|
|
243
|
+
return true;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if CLI tool is installed
|
|
251
|
+
*/
|
|
252
|
+
async function checkCLI(cli) {
|
|
253
|
+
try {
|
|
254
|
+
await execAsync(`which ${cli}`);
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
257
|
+
// Also try npx for some tools
|
|
258
|
+
try {
|
|
259
|
+
await execAsync(`npx ${cli} --version`);
|
|
260
|
+
return true;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Install CLI tool
|
|
269
|
+
*/
|
|
270
|
+
async function installCLI(platformConfig) {
|
|
271
|
+
console.log(chalk.yellow(`\n 📦 Installing ${platformConfig.name} CLI...\n`));
|
|
272
|
+
|
|
273
|
+
return new Promise((resolve) => {
|
|
274
|
+
const child = spawn('sh', ['-c', platformConfig.installCmd], {
|
|
275
|
+
stdio: 'inherit'
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
child.on('close', (code) => {
|
|
279
|
+
if (code === 0) {
|
|
280
|
+
console.log(chalk.green(`\n ✅ ${platformConfig.name} CLI installed\n`));
|
|
281
|
+
resolve(true);
|
|
282
|
+
} else {
|
|
283
|
+
console.log(chalk.red(`\n ❌ Installation failed.`));
|
|
284
|
+
console.log(chalk.gray(` Try manually: ${platformConfig.installCmd}\n`));
|
|
285
|
+
resolve(false);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
child.on('error', () => {
|
|
290
|
+
console.log(chalk.red(`\n ❌ Installation failed.`));
|
|
291
|
+
resolve(false);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check if user is authenticated
|
|
298
|
+
*/
|
|
299
|
+
async function checkAuth(platformConfig) {
|
|
300
|
+
if (!platformConfig.whoamiCmd) return true;
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await execAsync(platformConfig.whoamiCmd);
|
|
304
|
+
return true;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Authenticate with platform
|
|
312
|
+
*/
|
|
313
|
+
async function authenticate(platformConfig) {
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
const child = spawn('sh', ['-c', platformConfig.loginCmd], {
|
|
316
|
+
stdio: 'inherit'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
child.on('close', (code) => {
|
|
320
|
+
if (code === 0) {
|
|
321
|
+
console.log(chalk.green('\n ✅ Authentication complete\n'));
|
|
322
|
+
resolve(true);
|
|
323
|
+
} else {
|
|
324
|
+
resolve(false);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
child.on('error', () => {
|
|
329
|
+
resolve(false);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Ensure deployment config exists
|
|
336
|
+
*/
|
|
337
|
+
async function ensureConfig(cwd, platform, platformConfig, options) {
|
|
338
|
+
if (!platformConfig.configFile) return;
|
|
339
|
+
|
|
340
|
+
const configPath = path.join(cwd, platformConfig.configFile);
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
await fs.access(configPath);
|
|
344
|
+
console.log(chalk.green(` ✓ ${platformConfig.configFile} found`));
|
|
345
|
+
} catch {
|
|
346
|
+
console.log(chalk.yellow(` 📝 Creating ${platformConfig.configFile}...`));
|
|
347
|
+
|
|
348
|
+
const config = await generateConfig(cwd, platform, options);
|
|
349
|
+
|
|
350
|
+
if (platform === 'netlify') {
|
|
351
|
+
// Netlify uses TOML format
|
|
352
|
+
const toml = generateNetlifyToml(config);
|
|
353
|
+
await fs.writeFile(configPath, toml);
|
|
354
|
+
} else {
|
|
355
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(chalk.green(` ✅ ${platformConfig.configFile} created`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Generate platform config based on project type
|
|
364
|
+
*/
|
|
365
|
+
async function generateConfig(cwd, platform, options) {
|
|
366
|
+
// Detect project type
|
|
367
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
368
|
+
let framework = 'static';
|
|
369
|
+
let buildCommand = 'npm run build';
|
|
370
|
+
let outputDirectory = 'dist';
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
374
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
375
|
+
|
|
376
|
+
if (deps.next) {
|
|
377
|
+
framework = 'nextjs';
|
|
378
|
+
outputDirectory = '.next';
|
|
379
|
+
buildCommand = 'next build';
|
|
380
|
+
} else if (deps.vite) {
|
|
381
|
+
framework = 'vite';
|
|
382
|
+
outputDirectory = 'dist';
|
|
383
|
+
} else if (deps['react-scripts']) {
|
|
384
|
+
framework = 'create-react-app';
|
|
385
|
+
outputDirectory = 'build';
|
|
386
|
+
} else if (deps.vue || deps['@vue/cli-service']) {
|
|
387
|
+
framework = 'vue';
|
|
388
|
+
outputDirectory = 'dist';
|
|
389
|
+
} else if (deps.nuxt) {
|
|
390
|
+
framework = 'nuxt';
|
|
391
|
+
outputDirectory = '.output';
|
|
392
|
+
} else if (deps['@sveltejs/kit']) {
|
|
393
|
+
framework = 'sveltekit';
|
|
394
|
+
outputDirectory = 'build';
|
|
395
|
+
}
|
|
396
|
+
} catch {}
|
|
397
|
+
|
|
398
|
+
switch (platform) {
|
|
399
|
+
case 'vercel':
|
|
400
|
+
return {
|
|
401
|
+
version: 2,
|
|
402
|
+
framework,
|
|
403
|
+
buildCommand,
|
|
404
|
+
outputDirectory,
|
|
405
|
+
...(options.domain && { alias: [options.domain] })
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
case 'netlify':
|
|
409
|
+
return {
|
|
410
|
+
build: {
|
|
411
|
+
command: buildCommand,
|
|
412
|
+
publish: outputDirectory
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
case 'railway':
|
|
417
|
+
return {
|
|
418
|
+
build: {
|
|
419
|
+
builder: 'NIXPACKS'
|
|
420
|
+
},
|
|
421
|
+
deploy: {
|
|
422
|
+
startCommand: 'npm start'
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
case 'render':
|
|
427
|
+
return {
|
|
428
|
+
services: [{
|
|
429
|
+
type: 'web',
|
|
430
|
+
name: path.basename(cwd),
|
|
431
|
+
env: 'node',
|
|
432
|
+
buildCommand,
|
|
433
|
+
startCommand: 'npm start'
|
|
434
|
+
}]
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
default:
|
|
438
|
+
return {};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Generate Netlify TOML config
|
|
444
|
+
*/
|
|
445
|
+
function generateNetlifyToml(config) {
|
|
446
|
+
return `[build]
|
|
447
|
+
command = "${config.build?.command || 'npm run build'}"
|
|
448
|
+
publish = "${config.build?.publish || 'dist'}"
|
|
449
|
+
|
|
450
|
+
[[redirects]]
|
|
451
|
+
from = "/*"
|
|
452
|
+
to = "/index.html"
|
|
453
|
+
status = 200
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Build project before deployment
|
|
459
|
+
*/
|
|
460
|
+
async function buildProject(cwd, platformConfig) {
|
|
461
|
+
return new Promise((resolve) => {
|
|
462
|
+
const buildCmd = platformConfig.buildCmd || 'npm run build';
|
|
463
|
+
|
|
464
|
+
const child = spawn('sh', ['-c', buildCmd], {
|
|
465
|
+
cwd,
|
|
466
|
+
stdio: 'inherit'
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
child.on('close', (code) => {
|
|
470
|
+
if (code === 0) {
|
|
471
|
+
console.log(chalk.green(' ✅ Build complete'));
|
|
472
|
+
resolve(true);
|
|
473
|
+
} else {
|
|
474
|
+
resolve(false);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
child.on('error', () => {
|
|
479
|
+
resolve(false);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Execute deployment
|
|
486
|
+
*/
|
|
487
|
+
async function deploy(cwd, platformConfig, isProduction, options) {
|
|
488
|
+
return new Promise((resolve) => {
|
|
489
|
+
let cmd = isProduction ? platformConfig.prodCmd : platformConfig.previewCmd;
|
|
490
|
+
|
|
491
|
+
// Add domain if specified
|
|
492
|
+
if (options.domain && platformConfig.name === 'Vercel') {
|
|
493
|
+
cmd = `${cmd} --alias ${options.domain}`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let output = '';
|
|
497
|
+
let deployUrl = '';
|
|
498
|
+
|
|
499
|
+
const child = spawn('sh', ['-c', cmd], {
|
|
500
|
+
cwd,
|
|
501
|
+
stdio: ['inherit', 'pipe', 'pipe']
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
child.stdout.on('data', (data) => {
|
|
505
|
+
const text = data.toString();
|
|
506
|
+
output += text;
|
|
507
|
+
process.stdout.write(text);
|
|
508
|
+
|
|
509
|
+
// Extract deployment URL from various formats
|
|
510
|
+
const urlPatterns = [
|
|
511
|
+
/https:\/\/[a-zA-Z0-9-]+\.vercel\.app[^\s]*/,
|
|
512
|
+
/https:\/\/[a-zA-Z0-9-]+\.netlify\.app[^\s]*/,
|
|
513
|
+
/https:\/\/[a-zA-Z0-9-]+\.github\.io[^\s]*/,
|
|
514
|
+
/https:\/\/[a-zA-Z0-9-]+\.up\.railway\.app[^\s]*/,
|
|
515
|
+
/https:\/\/[a-zA-Z0-9-]+\.onrender\.com[^\s]*/,
|
|
516
|
+
/https:\/\/[^\s]+/
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
for (const pattern of urlPatterns) {
|
|
520
|
+
const match = text.match(pattern);
|
|
521
|
+
if (match) {
|
|
522
|
+
deployUrl = match[0].replace(/['")\]]+$/, ''); // Clean trailing chars
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
child.stderr.on('data', (data) => {
|
|
529
|
+
const text = data.toString();
|
|
530
|
+
output += text;
|
|
531
|
+
process.stderr.write(text);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
child.on('close', (code) => {
|
|
535
|
+
resolve({
|
|
536
|
+
success: code === 0,
|
|
537
|
+
url: deployUrl,
|
|
538
|
+
output,
|
|
539
|
+
isProduction,
|
|
540
|
+
timestamp: new Date().toISOString()
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
child.on('error', (error) => {
|
|
545
|
+
resolve({
|
|
546
|
+
success: false,
|
|
547
|
+
url: '',
|
|
548
|
+
output: error.message,
|
|
549
|
+
isProduction,
|
|
550
|
+
timestamp: new Date().toISOString()
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Save deployment info to history
|
|
558
|
+
*/
|
|
559
|
+
async function saveDeploymentInfo(cwd, platform, result) {
|
|
560
|
+
const vibecodeDir = path.join(cwd, '.vibecode');
|
|
561
|
+
const deploymentsPath = path.join(vibecodeDir, 'deployments.json');
|
|
562
|
+
|
|
563
|
+
await fs.mkdir(vibecodeDir, { recursive: true });
|
|
564
|
+
|
|
565
|
+
let deployments = [];
|
|
566
|
+
try {
|
|
567
|
+
const existing = await fs.readFile(deploymentsPath, 'utf-8');
|
|
568
|
+
deployments = JSON.parse(existing);
|
|
569
|
+
} catch {}
|
|
570
|
+
|
|
571
|
+
deployments.unshift({
|
|
572
|
+
platform,
|
|
573
|
+
url: result.url,
|
|
574
|
+
isProduction: result.isProduction,
|
|
575
|
+
timestamp: result.timestamp,
|
|
576
|
+
success: result.success
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Keep last 20 deployments
|
|
580
|
+
deployments = deployments.slice(0, 20);
|
|
581
|
+
|
|
582
|
+
await fs.writeFile(deploymentsPath, JSON.stringify(deployments, null, 2));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Show deployment result
|
|
587
|
+
*/
|
|
588
|
+
function showDeploymentResult(result, platformConfig) {
|
|
589
|
+
if (result.success) {
|
|
590
|
+
const urlDisplay = result.url || 'Check console output above';
|
|
591
|
+
const urlPadded = urlDisplay.length > 50 ? urlDisplay.substring(0, 47) + '...' : urlDisplay;
|
|
592
|
+
|
|
593
|
+
console.log(chalk.green(`
|
|
594
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
595
|
+
│ ✅ DEPLOYMENT SUCCESSFUL │
|
|
596
|
+
│ │
|
|
597
|
+
│ ${platformConfig.icon} Platform: ${platformConfig.name.padEnd(49)}│
|
|
598
|
+
│ 🌐 URL: ${urlPadded.padEnd(53)}│
|
|
599
|
+
│ 📅 Time: ${new Date().toLocaleString().padEnd(52)}│
|
|
600
|
+
│ 🏷️ Type: ${(result.isProduction ? 'Production' : 'Preview').padEnd(52)}│
|
|
601
|
+
│ │
|
|
602
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
603
|
+
`));
|
|
604
|
+
|
|
605
|
+
if (result.url) {
|
|
606
|
+
console.log(chalk.cyan(` 🔗 ${result.url}\n`));
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
console.log(chalk.red(`
|
|
610
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
611
|
+
│ ❌ DEPLOYMENT FAILED │
|
|
612
|
+
│ │
|
|
613
|
+
│ Check the error output above for details. │
|
|
614
|
+
│ │
|
|
615
|
+
│ Common fixes: │
|
|
616
|
+
│ • Run 'npm run build' to check for build errors │
|
|
617
|
+
│ • Check ${(platformConfig.configFile || 'configuration').padEnd(45)}│
|
|
618
|
+
│ • Verify authentication: ${platformConfig.loginCmd.padEnd(35)}│
|
|
619
|
+
│ │
|
|
620
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
621
|
+
`));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Show deployment status
|
|
627
|
+
*/
|
|
628
|
+
async function showDeploymentStatus(cwd) {
|
|
629
|
+
console.log(chalk.cyan(`
|
|
630
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
631
|
+
│ 📊 DEPLOYMENT STATUS │
|
|
632
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
633
|
+
`));
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
const deploymentsPath = path.join(cwd, '.vibecode', 'deployments.json');
|
|
637
|
+
const deployments = JSON.parse(await fs.readFile(deploymentsPath, 'utf-8'));
|
|
638
|
+
|
|
639
|
+
if (deployments.length === 0) {
|
|
640
|
+
console.log(chalk.gray(' No deployments found.\n'));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const latest = deployments[0];
|
|
645
|
+
const platformConfig = PLATFORMS[latest.platform];
|
|
646
|
+
|
|
647
|
+
console.log(chalk.white.bold(' Latest Deployment:\n'));
|
|
648
|
+
console.log(chalk.gray(` Platform: ${platformConfig?.icon || ''} ${latest.platform}`));
|
|
649
|
+
console.log(chalk.gray(` URL: ${latest.url || 'N/A'}`));
|
|
650
|
+
console.log(chalk.gray(` Type: ${latest.isProduction ? 'Production' : 'Preview'}`));
|
|
651
|
+
console.log(chalk.gray(` Time: ${new Date(latest.timestamp).toLocaleString()}`));
|
|
652
|
+
console.log(chalk.gray(` Status: ${latest.success ? chalk.green('Success') : chalk.red('Failed')}`));
|
|
653
|
+
console.log('');
|
|
654
|
+
|
|
655
|
+
if (latest.url) {
|
|
656
|
+
console.log(chalk.cyan(` 🔗 ${latest.url}\n`));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
} catch {
|
|
660
|
+
console.log(chalk.gray(' No deployment history found.\n'));
|
|
661
|
+
console.log(chalk.gray(` Run ${chalk.cyan('vibecode deploy')} to deploy your project.\n`));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Show deployment history
|
|
667
|
+
*/
|
|
668
|
+
async function showDeploymentHistory(cwd) {
|
|
669
|
+
console.log(chalk.cyan(`
|
|
670
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
671
|
+
│ 📜 DEPLOYMENT HISTORY │
|
|
672
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
673
|
+
`));
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
const deploymentsPath = path.join(cwd, '.vibecode', 'deployments.json');
|
|
677
|
+
const deployments = JSON.parse(await fs.readFile(deploymentsPath, 'utf-8'));
|
|
678
|
+
|
|
679
|
+
if (deployments.length === 0) {
|
|
680
|
+
console.log(chalk.gray(' No deployments found.\n'));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
console.log(chalk.gray(' Status Platform Type Date'));
|
|
685
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────'));
|
|
686
|
+
|
|
687
|
+
for (const dep of deployments.slice(0, 10)) {
|
|
688
|
+
const platformConfig = PLATFORMS[dep.platform];
|
|
689
|
+
const status = dep.success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
690
|
+
const type = dep.isProduction ? 'prod ' : 'preview';
|
|
691
|
+
const date = new Date(dep.timestamp).toLocaleDateString();
|
|
692
|
+
const icon = platformConfig?.icon || ' ';
|
|
693
|
+
|
|
694
|
+
console.log(`${status} ${icon} ${dep.platform.padEnd(12)} ${type} ${date}`);
|
|
695
|
+
if (dep.url) {
|
|
696
|
+
console.log(chalk.gray(` ${dep.url}`));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
console.log('');
|
|
700
|
+
|
|
701
|
+
} catch {
|
|
702
|
+
console.log(chalk.gray(' No deployment history found.\n'));
|
|
703
|
+
console.log(chalk.gray(` Run ${chalk.cyan('vibecode deploy')} to deploy your project.\n`));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Auto-deploy for use in go command
|
|
709
|
+
*/
|
|
710
|
+
export async function autoDeploy(projectPath, options = {}) {
|
|
711
|
+
const originalCwd = process.cwd();
|
|
712
|
+
|
|
713
|
+
try {
|
|
714
|
+
process.chdir(projectPath);
|
|
715
|
+
|
|
716
|
+
return await deployCommand({
|
|
717
|
+
vercel: options.platform !== 'netlify',
|
|
718
|
+
netlify: options.platform === 'netlify',
|
|
719
|
+
preview: options.preview || false,
|
|
720
|
+
domain: options.domain
|
|
721
|
+
});
|
|
722
|
+
} catch (error) {
|
|
723
|
+
console.log(chalk.yellow(` ⚠️ Deploy failed: ${error.message}\n`));
|
|
724
|
+
return { success: false };
|
|
725
|
+
} finally {
|
|
726
|
+
process.chdir(originalCwd);
|
|
727
|
+
}
|
|
728
|
+
}
|