vibefast-cli 0.3.1 → 0.5.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/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +160 -39
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/platform.d.ts +3 -0
- package/dist/commands/platform.d.ts.map +1 -0
- package/dist/commands/platform.js +245 -0
- package/dist/commands/platform.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +190 -45
- package/src/commands/platform.ts +309 -0
- package/src/index.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { statusCommand } from './commands/status.js';
|
|
|
13
13
|
import { checklistCommand } from './commands/checklist.js';
|
|
14
14
|
import { envCommand } from './commands/env.js';
|
|
15
15
|
import { healthCommand } from './commands/health.js';
|
|
16
|
+
import { platformCommand } from './commands/platform.js';
|
|
16
17
|
import { log } from './core/log.js';
|
|
17
18
|
import { showCommandHint } from './core/errors.js';
|
|
18
19
|
const program = new Command();
|
|
@@ -28,6 +29,7 @@ program.addCommand(doctorCommand);
|
|
|
28
29
|
program.addCommand(listCommand);
|
|
29
30
|
program.addCommand(addCommand);
|
|
30
31
|
program.addCommand(removeCommand);
|
|
32
|
+
program.addCommand(platformCommand);
|
|
31
33
|
program.addCommand(statusCommand);
|
|
32
34
|
program.addCommand(checklistCommand);
|
|
33
35
|
program.addCommand(envCommand);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,IAAI,CAAC;KACV,WAAW,CAAC,oDAAoD,CAAC;KACjE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;AAEnC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAElC,wCAAwC;AACxC,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACjE,IAAI,YAAY,GAAG,EAAE,CAAC;AAErB,OAAO,CAAC,MAAc,CAAC,KAAK,GAAG,UAAS,KAAU,EAAE,GAAG,IAAW;IACjE,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC7B,YAAY,IAAI,GAAG,CAAC;IAEpB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE3B,8CAA8C;IAC9C,IAAI,GAAG,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,CAAC;QAC9C,2BAA2B;QAC3B,cAAc,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;QAE/B,WAAW;QACX,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACd,eAAe,CAAC,OAAO,CAAC,CAAC;QAEzB,yBAAyB;QACxB,OAAO,CAAC,MAAc,CAAC,KAAK,GAAG,cAAc,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4CAA4C;IAC5C,IAAI,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACrE,2BAA2B;QAC3B,cAAc,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;QAE/B,WAAW;QACX,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACd,eAAe,CAAC,OAAO,CAAC,CAAC;QAEzB,yBAAyB;QACxB,OAAO,CAAC,MAAc,CAAC,KAAK,GAAG,cAAc,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,cAAc,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { log } from '../core/log.js';
|
|
3
3
|
import { withSpinner } from '../core/spinner.js';
|
|
4
|
-
import {
|
|
4
|
+
import { promptSelectAsync, promptMultiSelectAsync, promptUser, promptYesNo } from '../core/prompt.js';
|
|
5
5
|
import { detectPackageManager } from '../core/detect.js';
|
|
6
6
|
import simpleGit from 'simple-git';
|
|
7
7
|
import { existsSync } from 'fs';
|
|
@@ -37,57 +37,85 @@ export const initCommand = new Command('init')
|
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Step 1: Choose
|
|
41
|
-
let
|
|
40
|
+
// Step 1: Choose starter type
|
|
41
|
+
let starterType: 'clean' | 'custom' = 'clean';
|
|
42
|
+
let selectedFeatures: string[] = [];
|
|
42
43
|
|
|
43
|
-
if (!options.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
if (!options.yes) {
|
|
45
|
+
starterType = await promptSelectAsync(
|
|
46
|
+
'How would you like to start?',
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
value: 'clean',
|
|
50
|
+
label: 'Clean',
|
|
51
|
+
description: 'Minimal setup - add features later with vf add',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 'custom',
|
|
55
|
+
label: 'Custom',
|
|
56
|
+
description: 'Choose features now - one-shot setup',
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
) as 'clean' | 'custom';
|
|
60
|
+
|
|
61
|
+
log.info(`Selected: ${starterType === 'clean' ? 'Clean starter' : 'Custom setup'}`);
|
|
62
|
+
log.plain('');
|
|
63
|
+
|
|
64
|
+
// If custom, let them choose features
|
|
65
|
+
if (starterType === 'custom') {
|
|
66
|
+
const { promptMultiSelectAsync } = await import('../core/prompt.js');
|
|
67
|
+
const { RECIPES, getRecipesByCategory } = await import('../core/recipes.js');
|
|
68
|
+
|
|
69
|
+
log.info('📦 Select features to include:');
|
|
70
|
+
log.plain('');
|
|
71
|
+
|
|
72
|
+
// Get native features
|
|
73
|
+
const nativeFeatures = getRecipesByCategory('feature').map(r => ({
|
|
74
|
+
value: r.name,
|
|
75
|
+
label: `${r.icon} ${r.name}`,
|
|
76
|
+
description: r.description,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
selectedFeatures = await promptMultiSelectAsync(
|
|
80
|
+
'Select features (use space to toggle, enter to confirm):',
|
|
81
|
+
nativeFeatures
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (selectedFeatures.length > 0) {
|
|
85
|
+
log.info(`Selected ${selectedFeatures.length} feature(s): ${selectedFeatures.join(', ')}`);
|
|
86
|
+
} else {
|
|
87
|
+
log.info('No features selected - starting with clean setup');
|
|
88
|
+
}
|
|
89
|
+
log.plain('');
|
|
65
90
|
}
|
|
91
|
+
} else {
|
|
92
|
+
log.info('Using default: Clean starter');
|
|
93
|
+
log.plain('');
|
|
66
94
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
|
|
96
|
+
// Always use clean branch for cloning
|
|
97
|
+
const branch = 'clean';
|
|
70
98
|
|
|
71
99
|
// Step 1.5: Choose platforms
|
|
72
100
|
let platforms: string[] = ['native', 'web'];
|
|
73
101
|
|
|
74
102
|
if (!options.yes) {
|
|
75
|
-
const platformChoice = await
|
|
103
|
+
const platformChoice = await promptSelectAsync(
|
|
76
104
|
'Which platforms do you want to include?',
|
|
77
105
|
[
|
|
78
106
|
{
|
|
79
|
-
name: 'Both (Native + Web)',
|
|
80
107
|
value: 'both',
|
|
81
|
-
|
|
108
|
+
label: 'Both (Native + Web)',
|
|
109
|
+
description: 'Full monorepo with mobile and web app',
|
|
82
110
|
},
|
|
83
111
|
{
|
|
84
|
-
name: 'Native Only (Expo)',
|
|
85
112
|
value: 'native',
|
|
113
|
+
label: 'Native Only (Expo)',
|
|
86
114
|
description: 'Mobile app only (iOS & Android)',
|
|
87
115
|
},
|
|
88
116
|
{
|
|
89
|
-
name: 'Web Only (Next.js)',
|
|
90
117
|
value: 'web',
|
|
118
|
+
label: 'Web Only (Next.js)',
|
|
91
119
|
description: 'Web app only',
|
|
92
120
|
},
|
|
93
121
|
]
|
|
@@ -114,10 +142,9 @@ export const initCommand = new Command('init')
|
|
|
114
142
|
let projectName = defaultName;
|
|
115
143
|
|
|
116
144
|
if (!options.yes) {
|
|
117
|
-
projectName = await
|
|
118
|
-
'Project name:'
|
|
119
|
-
|
|
120
|
-
);
|
|
145
|
+
projectName = await promptUser(
|
|
146
|
+
'Project name: '
|
|
147
|
+
) || defaultName;
|
|
121
148
|
}
|
|
122
149
|
|
|
123
150
|
// Validate project name
|
|
@@ -138,15 +165,13 @@ export const initCommand = new Command('init')
|
|
|
138
165
|
const repoUrl = 'https://github.com/mzafarr/vibefast-pro.git';
|
|
139
166
|
|
|
140
167
|
await withSpinner(
|
|
141
|
-
`Cloning VibeFast monorepo (
|
|
168
|
+
`Cloning VibeFast monorepo (clean starter)...`,
|
|
142
169
|
async () => {
|
|
143
170
|
const git = simpleGit();
|
|
144
171
|
const cloneOptions = ['--depth', '1'];
|
|
145
172
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
cloneOptions.unshift('--branch', branch, '--single-branch');
|
|
149
|
-
}
|
|
173
|
+
// Always use clean branch
|
|
174
|
+
cloneOptions.unshift('--branch', 'clean', '--single-branch');
|
|
150
175
|
|
|
151
176
|
try {
|
|
152
177
|
await git.clone(repoUrl, projectName, cloneOptions);
|
|
@@ -274,7 +299,7 @@ export const initCommand = new Command('init')
|
|
|
274
299
|
log.warn('pnpm is not installed');
|
|
275
300
|
log.plain('');
|
|
276
301
|
|
|
277
|
-
const shouldInstallPnpm = options.yes || await
|
|
302
|
+
const shouldInstallPnpm = options.yes || await promptYesNo(
|
|
278
303
|
'Would you like to install pnpm globally?',
|
|
279
304
|
true
|
|
280
305
|
);
|
|
@@ -357,7 +382,7 @@ export const initCommand = new Command('init')
|
|
|
357
382
|
log.plain(' • Configure Convex Auth');
|
|
358
383
|
log.plain('');
|
|
359
384
|
|
|
360
|
-
const runSetup = options.yes || await
|
|
385
|
+
const runSetup = options.yes || await promptYesNo(
|
|
361
386
|
'Would you like to run the starter setup now?',
|
|
362
387
|
true
|
|
363
388
|
);
|
|
@@ -398,6 +423,122 @@ export const initCommand = new Command('init')
|
|
|
398
423
|
log.plain('');
|
|
399
424
|
}
|
|
400
425
|
|
|
426
|
+
// Step 6.5: Install selected features (if custom setup)
|
|
427
|
+
if (starterType === 'custom' && selectedFeatures.length > 0) {
|
|
428
|
+
log.plain('');
|
|
429
|
+
log.info('📦 Installing selected features...');
|
|
430
|
+
log.plain('');
|
|
431
|
+
|
|
432
|
+
// We need to be in the project directory for feature installation
|
|
433
|
+
process.chdir(projectPath);
|
|
434
|
+
|
|
435
|
+
for (const featureName of selectedFeatures) {
|
|
436
|
+
try {
|
|
437
|
+
log.plain('');
|
|
438
|
+
log.plain('━'.repeat(60));
|
|
439
|
+
log.info(`Installing ${featureName}...`);
|
|
440
|
+
log.plain('');
|
|
441
|
+
|
|
442
|
+
// Import and use the add command logic
|
|
443
|
+
const { getToken } = await import('../core/auth.js');
|
|
444
|
+
const { fetchRecipe, downloadZip } = await import('../core/http.js');
|
|
445
|
+
const { extractZipSafe } = await import('../core/archive.js');
|
|
446
|
+
const { copyTree, readFileContent } = await import('../core/fsx.js');
|
|
447
|
+
const { addEntry } = await import('../core/journal.js');
|
|
448
|
+
const { hashFiles } = await import('../core/hash.js');
|
|
449
|
+
const { insertNavLinkNative } = await import('../core/codemod.js');
|
|
450
|
+
const { getDeviceInfo } = await import('../core/auth.js');
|
|
451
|
+
const { validateSignature } = await import('../core/validate.js');
|
|
452
|
+
const { getPaths } = await import('../core/paths.js');
|
|
453
|
+
|
|
454
|
+
const paths = getPaths();
|
|
455
|
+
const token = await getToken();
|
|
456
|
+
const device = await getDeviceInfo();
|
|
457
|
+
const config = await validateSignature(paths.signatureFile);
|
|
458
|
+
|
|
459
|
+
// Fetch recipe
|
|
460
|
+
const response = await fetchRecipe({
|
|
461
|
+
token: token!,
|
|
462
|
+
device,
|
|
463
|
+
feature: featureName,
|
|
464
|
+
target: 'native',
|
|
465
|
+
starter: { name: config.name, version: config.version },
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
if (!response.ok || (!response.signedUrl && !response.zipData)) {
|
|
469
|
+
log.warn(`⚠ Could not fetch ${featureName}: ${response.error || 'Unknown error'}`);
|
|
470
|
+
log.info(`You can install it later with: vf add ${featureName}`);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Download and extract
|
|
475
|
+
const zipPath = response.zipData
|
|
476
|
+
? await downloadZip(response.zipData, true)
|
|
477
|
+
: await downloadZip(response.signedUrl!);
|
|
478
|
+
|
|
479
|
+
const extractDir = zipPath.replace('.zip', '');
|
|
480
|
+
await extractZipSafe(zipPath, extractDir);
|
|
481
|
+
|
|
482
|
+
// Read manifest
|
|
483
|
+
const manifestPath = join(extractDir, 'recipe.json');
|
|
484
|
+
const manifestContent = await readFileContent(manifestPath);
|
|
485
|
+
const manifest = JSON.parse(manifestContent);
|
|
486
|
+
|
|
487
|
+
// Copy files
|
|
488
|
+
const copiedFiles: string[] = [];
|
|
489
|
+
const { resolve: resolvePath } = await import('path');
|
|
490
|
+
for (const copySpec of manifest.copy) {
|
|
491
|
+
const srcPath = resolvePath(extractDir, copySpec.from);
|
|
492
|
+
const destPath = resolvePath(projectPath, copySpec.to);
|
|
493
|
+
const result = await copyTree(srcPath, destPath, { force: true });
|
|
494
|
+
copiedFiles.push(...result.files);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Add navigation
|
|
498
|
+
let navInserted = false;
|
|
499
|
+
if (manifest.nav) {
|
|
500
|
+
navInserted = await insertNavLinkNative(paths.nativeNavFile, manifest.nav, {});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Hash and journal
|
|
504
|
+
const fileHashes = await hashFiles(copiedFiles);
|
|
505
|
+
const fileEntries = Array.from(fileHashes.entries()).map(([path, hash]) => ({
|
|
506
|
+
path,
|
|
507
|
+
hash,
|
|
508
|
+
}));
|
|
509
|
+
|
|
510
|
+
await addEntry(paths.journalFile, {
|
|
511
|
+
feature: manifest.name,
|
|
512
|
+
target: manifest.target,
|
|
513
|
+
files: fileEntries,
|
|
514
|
+
insertedNav: navInserted,
|
|
515
|
+
navHref: manifest.nav?.href,
|
|
516
|
+
navLabel: manifest.nav?.label,
|
|
517
|
+
ts: Date.now(),
|
|
518
|
+
manifest: {
|
|
519
|
+
version: manifest.version,
|
|
520
|
+
manualSteps: manifest.manualSteps,
|
|
521
|
+
env: manifest.env,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
log.success(`✓ ${featureName} installed successfully!`);
|
|
526
|
+
|
|
527
|
+
} catch (error: any) {
|
|
528
|
+
log.warn(`⚠ Failed to install ${featureName}: ${error.message}`);
|
|
529
|
+
log.info(`You can install it later with: vf add ${featureName}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Go back to original directory
|
|
534
|
+
process.chdir('..');
|
|
535
|
+
|
|
536
|
+
log.plain('');
|
|
537
|
+
log.plain('━'.repeat(60));
|
|
538
|
+
log.success(`✓ Installed ${selectedFeatures.length} feature(s)`);
|
|
539
|
+
log.plain('');
|
|
540
|
+
}
|
|
541
|
+
|
|
401
542
|
// Step 7: Login with license key (REQUIRED)
|
|
402
543
|
let licenseKey = options.license;
|
|
403
544
|
|
|
@@ -425,7 +566,7 @@ export const initCommand = new Command('init')
|
|
|
425
566
|
process.exit(1);
|
|
426
567
|
}
|
|
427
568
|
|
|
428
|
-
licenseKey = await
|
|
569
|
+
licenseKey = await promptUser('Enter your license key: ');
|
|
429
570
|
|
|
430
571
|
if (!licenseKey || licenseKey.trim() === '') {
|
|
431
572
|
log.error('License key cannot be empty');
|
|
@@ -456,7 +597,11 @@ export const initCommand = new Command('init')
|
|
|
456
597
|
log.plain('');
|
|
457
598
|
log.success('🎉 Project initialized successfully!');
|
|
458
599
|
log.plain('');
|
|
600
|
+
log.info(`Setup: ${starterType === 'clean' ? 'Clean' : 'Custom'}`);
|
|
459
601
|
log.info(`Platforms: ${platforms.join(' + ')}`);
|
|
602
|
+
if (starterType === 'custom' && selectedFeatures.length > 0) {
|
|
603
|
+
log.info(`Features: ${selectedFeatures.join(', ')}`);
|
|
604
|
+
}
|
|
460
605
|
log.plain('');
|
|
461
606
|
log.info('Next steps:');
|
|
462
607
|
log.plain(` 1. cd ${projectName}`);
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { log } from '../core/log.js';
|
|
3
|
+
import { withSpinner } from '../core/spinner.js';
|
|
4
|
+
import { promptSelectAsync } from '../core/prompt.js';
|
|
5
|
+
import { existsSync, rmSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import simpleGit from 'simple-git';
|
|
8
|
+
|
|
9
|
+
interface PlatformOptions {
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const platformCommand = new Command('platform')
|
|
14
|
+
.description('Manage platforms in your VibeFast project')
|
|
15
|
+
.addCommand(
|
|
16
|
+
new Command('add')
|
|
17
|
+
.description('Add a missing platform to your project')
|
|
18
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
19
|
+
.action(async (options: PlatformOptions) => {
|
|
20
|
+
try {
|
|
21
|
+
const paths = await import('../core/paths.js').then(m => m.getPaths());
|
|
22
|
+
|
|
23
|
+
// Check if we're in a VibeFast project
|
|
24
|
+
if (!existsSync(join(paths.cwd, '.vibefast'))) {
|
|
25
|
+
log.error('Not a VibeFast project. Run this from your VibeFast project root.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check what platforms currently exist
|
|
30
|
+
const hasNative = existsSync(join(paths.cwd, 'apps/native'));
|
|
31
|
+
const hasWeb = existsSync(join(paths.cwd, 'apps/web'));
|
|
32
|
+
|
|
33
|
+
if (hasNative && hasWeb) {
|
|
34
|
+
log.info('✓ Both platforms already exist');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Determine what can be added
|
|
39
|
+
const availablePlatforms = [];
|
|
40
|
+
if (!hasNative) availablePlatforms.push({ value: 'native', label: '📱 Native (Expo)' });
|
|
41
|
+
if (!hasWeb) availablePlatforms.push({ value: 'web', label: '🌐 Web (Next.js)' });
|
|
42
|
+
|
|
43
|
+
if (availablePlatforms.length === 0) {
|
|
44
|
+
log.info('✓ All platforms already exist');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log.plain('');
|
|
49
|
+
log.info('Available platforms to add:');
|
|
50
|
+
log.plain('');
|
|
51
|
+
|
|
52
|
+
const platformToAdd = await promptSelectAsync(
|
|
53
|
+
'Which platform would you like to add?',
|
|
54
|
+
availablePlatforms
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!platformToAdd) {
|
|
58
|
+
log.info('Cancelled');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
log.plain('');
|
|
63
|
+
log.info(`Adding ${platformToAdd === 'native' ? 'Native (Expo)' : 'Web (Next.js)'} platform...`);
|
|
64
|
+
log.plain('');
|
|
65
|
+
|
|
66
|
+
// Clone the platform from the clean branch
|
|
67
|
+
const repoUrl = 'https://github.com/mzafarr/vibefast-pro.git';
|
|
68
|
+
const tempDir = join(paths.cwd, '.temp-platform-clone');
|
|
69
|
+
|
|
70
|
+
await withSpinner(
|
|
71
|
+
`Cloning ${platformToAdd} platform...`,
|
|
72
|
+
async () => {
|
|
73
|
+
const git = simpleGit();
|
|
74
|
+
const cloneOptions = ['--depth', '1', '--branch', 'clean', '--single-branch', '--sparse'];
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await git.clone(repoUrl, tempDir, cloneOptions);
|
|
78
|
+
|
|
79
|
+
// Sparse checkout only the platform we need
|
|
80
|
+
const sparseGit = simpleGit(tempDir);
|
|
81
|
+
await sparseGit.raw(['sparse-checkout', 'set', `apps/${platformToAdd}`]);
|
|
82
|
+
} catch (error: any) {
|
|
83
|
+
throw new Error(`Failed to clone platform: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
successText: `✓ Platform cloned`,
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Copy the platform to the project
|
|
92
|
+
await withSpinner(
|
|
93
|
+
`Setting up ${platformToAdd} platform...`,
|
|
94
|
+
async () => {
|
|
95
|
+
const { copyFileSync } = await import('fs');
|
|
96
|
+
const { cp } = await import('fs/promises');
|
|
97
|
+
|
|
98
|
+
const sourcePath = join(tempDir, 'apps', platformToAdd);
|
|
99
|
+
const destPath = join(paths.cwd, 'apps', platformToAdd);
|
|
100
|
+
|
|
101
|
+
if (existsSync(sourcePath)) {
|
|
102
|
+
await cp(sourcePath, destPath, { recursive: true });
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(`Platform directory not found in clone`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Update package.json workspaces
|
|
108
|
+
const packageJsonPath = join(paths.cwd, 'package.json');
|
|
109
|
+
if (existsSync(packageJsonPath)) {
|
|
110
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
111
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
112
|
+
|
|
113
|
+
if (packageJson.workspaces && !packageJson.workspaces.includes(`apps/${platformToAdd}`)) {
|
|
114
|
+
packageJson.workspaces.push(`apps/${platformToAdd}`);
|
|
115
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update turbo.json
|
|
120
|
+
const turboJsonPath = join(paths.cwd, 'turbo.json');
|
|
121
|
+
if (existsSync(turboJsonPath)) {
|
|
122
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
123
|
+
const turboJson = JSON.parse(readFileSync(turboJsonPath, 'utf-8'));
|
|
124
|
+
|
|
125
|
+
if (turboJson.pipeline) {
|
|
126
|
+
// Add platform-specific tasks if they don't exist
|
|
127
|
+
if (platformToAdd === 'native') {
|
|
128
|
+
if (!turboJson.pipeline['dev:native']) {
|
|
129
|
+
turboJson.pipeline['dev:native'] = { cache: false };
|
|
130
|
+
}
|
|
131
|
+
if (!turboJson.pipeline['native:*']) {
|
|
132
|
+
turboJson.pipeline['native:*'] = { cache: false };
|
|
133
|
+
}
|
|
134
|
+
} else if (platformToAdd === 'web') {
|
|
135
|
+
if (!turboJson.pipeline['dev:web']) {
|
|
136
|
+
turboJson.pipeline['dev:web'] = { cache: false };
|
|
137
|
+
}
|
|
138
|
+
if (!turboJson.pipeline['web:*']) {
|
|
139
|
+
turboJson.pipeline['web:*'] = { cache: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
writeFileSync(turboJsonPath, JSON.stringify(turboJson, null, 2));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clean up temp directory
|
|
148
|
+
if (existsSync(tempDir)) {
|
|
149
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
successText: `✓ Platform setup complete`,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
log.plain('');
|
|
158
|
+
log.success(`✓ ${platformToAdd === 'native' ? 'Native' : 'Web'} platform added successfully!`);
|
|
159
|
+
log.plain('');
|
|
160
|
+
log.info('Next steps:');
|
|
161
|
+
log.plain(' 1. Install dependencies:');
|
|
162
|
+
log.plain(' pnpm install');
|
|
163
|
+
log.plain('');
|
|
164
|
+
if (platformToAdd === 'native') {
|
|
165
|
+
log.plain(' 2. Start developing:');
|
|
166
|
+
log.plain(' pnpm dev:native');
|
|
167
|
+
log.plain(' pnpm native:ios');
|
|
168
|
+
log.plain(' pnpm native:android');
|
|
169
|
+
} else {
|
|
170
|
+
log.plain(' 2. Start developing:');
|
|
171
|
+
log.plain(' pnpm dev:web');
|
|
172
|
+
}
|
|
173
|
+
log.plain('');
|
|
174
|
+
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
log.plain('');
|
|
177
|
+
log.error(`Failed to add platform: ${error.message}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
.addCommand(
|
|
183
|
+
new Command('remove')
|
|
184
|
+
.description('Remove a platform from your project')
|
|
185
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
186
|
+
.action(async (options: PlatformOptions) => {
|
|
187
|
+
try {
|
|
188
|
+
const paths = await import('../core/paths.js').then(m => m.getPaths());
|
|
189
|
+
|
|
190
|
+
// Check if we're in a VibeFast project
|
|
191
|
+
if (!existsSync(join(paths.cwd, '.vibefast'))) {
|
|
192
|
+
log.error('Not a VibeFast project. Run this from your VibeFast project root.');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check what platforms currently exist
|
|
197
|
+
const hasNative = existsSync(join(paths.cwd, 'apps/native'));
|
|
198
|
+
const hasWeb = existsSync(join(paths.cwd, 'apps/web'));
|
|
199
|
+
|
|
200
|
+
if (!hasNative && !hasWeb) {
|
|
201
|
+
log.error('No platforms found to remove');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Determine what can be removed
|
|
206
|
+
const removablePlatforms = [];
|
|
207
|
+
if (hasNative) removablePlatforms.push({ value: 'native', label: '📱 Native (Expo)' });
|
|
208
|
+
if (hasWeb) removablePlatforms.push({ value: 'web', label: '🌐 Web (Next.js)' });
|
|
209
|
+
|
|
210
|
+
if (removablePlatforms.length === 1) {
|
|
211
|
+
log.error('Cannot remove the only platform. You must have at least one platform.');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
log.plain('');
|
|
216
|
+
log.info('Platforms you can remove:');
|
|
217
|
+
log.plain('');
|
|
218
|
+
|
|
219
|
+
const platformToRemove = await promptSelectAsync(
|
|
220
|
+
'Which platform would you like to remove?',
|
|
221
|
+
removablePlatforms
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (!platformToRemove) {
|
|
225
|
+
log.info('Cancelled');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
log.plain('');
|
|
230
|
+
log.warn(`⚠ This will remove the ${platformToRemove === 'native' ? 'Native' : 'Web'} platform and all its files.`);
|
|
231
|
+
|
|
232
|
+
const { promptYesNo } = await import('../core/prompt.js');
|
|
233
|
+
const confirmed = options.yes || await promptYesNo('Are you sure?', false);
|
|
234
|
+
|
|
235
|
+
if (!confirmed) {
|
|
236
|
+
log.info('Cancelled');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
log.plain('');
|
|
241
|
+
|
|
242
|
+
await withSpinner(
|
|
243
|
+
`Removing ${platformToRemove} platform...`,
|
|
244
|
+
async () => {
|
|
245
|
+
const platformPath = join(paths.cwd, 'apps', platformToRemove);
|
|
246
|
+
|
|
247
|
+
if (existsSync(platformPath)) {
|
|
248
|
+
rmSync(platformPath, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Update package.json workspaces
|
|
252
|
+
const packageJsonPath = join(paths.cwd, 'package.json');
|
|
253
|
+
if (existsSync(packageJsonPath)) {
|
|
254
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
255
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
256
|
+
|
|
257
|
+
if (packageJson.workspaces) {
|
|
258
|
+
packageJson.workspaces = packageJson.workspaces.filter(
|
|
259
|
+
(ws: string) => !ws.includes(`apps/${platformToRemove}`)
|
|
260
|
+
);
|
|
261
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Update turbo.json
|
|
266
|
+
const turboJsonPath = join(paths.cwd, 'turbo.json');
|
|
267
|
+
if (existsSync(turboJsonPath)) {
|
|
268
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
269
|
+
const turboJson = JSON.parse(readFileSync(turboJsonPath, 'utf-8'));
|
|
270
|
+
|
|
271
|
+
if (turboJson.pipeline) {
|
|
272
|
+
const tasksToRemove: string[] = [];
|
|
273
|
+
|
|
274
|
+
if (platformToRemove === 'native') {
|
|
275
|
+
tasksToRemove.push('native:*', 'dev:native', 'build:native');
|
|
276
|
+
} else {
|
|
277
|
+
tasksToRemove.push('web:*', 'dev:web', 'build:web');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
tasksToRemove.forEach(task => {
|
|
281
|
+
if (turboJson.pipeline[task]) {
|
|
282
|
+
delete turboJson.pipeline[task];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
writeFileSync(turboJsonPath, JSON.stringify(turboJson, null, 2));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
successText: `✓ Platform removed`,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
log.plain('');
|
|
296
|
+
log.success(`✓ ${platformToRemove === 'native' ? 'Native' : 'Web'} platform removed successfully!`);
|
|
297
|
+
log.plain('');
|
|
298
|
+
log.info('Next steps:');
|
|
299
|
+
log.plain(' 1. Clean up dependencies:');
|
|
300
|
+
log.plain(' pnpm install');
|
|
301
|
+
log.plain('');
|
|
302
|
+
|
|
303
|
+
} catch (error: any) {
|
|
304
|
+
log.plain('');
|
|
305
|
+
log.error(`Failed to remove platform: ${error.message}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
);
|