vibefast-cli 0.5.0 → 0.5.1

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.
Files changed (77) hide show
  1. package/FEATURE-DEPENDENCY-SPEC.md +338 -0
  2. package/dist/__tests__/integration.test.d.ts +2 -0
  3. package/dist/__tests__/integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/integration.test.js +219 -0
  5. package/dist/__tests__/integration.test.js.map +1 -0
  6. package/dist/__tests__/recipes.test.d.ts +2 -0
  7. package/dist/__tests__/recipes.test.d.ts.map +1 -0
  8. package/dist/__tests__/recipes.test.js +143 -0
  9. package/dist/__tests__/recipes.test.js.map +1 -0
  10. package/dist/commands/__tests__/init.test.d.ts +2 -0
  11. package/dist/commands/__tests__/init.test.d.ts.map +1 -0
  12. package/dist/commands/__tests__/init.test.js +95 -0
  13. package/dist/commands/__tests__/init.test.js.map +1 -0
  14. package/dist/commands/__tests__/platform.test.d.ts +2 -0
  15. package/dist/commands/__tests__/platform.test.d.ts.map +1 -0
  16. package/dist/commands/__tests__/platform.test.js +123 -0
  17. package/dist/commands/__tests__/platform.test.js.map +1 -0
  18. package/dist/commands/add.d.ts.map +1 -1
  19. package/dist/commands/add.js +4 -5
  20. package/dist/commands/add.js.map +1 -1
  21. package/dist/core/journal.d.ts.map +1 -1
  22. package/dist/core/journal.js +36 -19
  23. package/dist/core/journal.js.map +1 -1
  24. package/dist/core/recipes.d.ts.map +1 -1
  25. package/dist/core/recipes.js +8 -39
  26. package/dist/core/recipes.js.map +1 -1
  27. package/package.json +1 -1
  28. package/recipes/ios-widget/recipe.json +78 -0
  29. package/recipes/ios-widget/targets/widget/AppIntent.swift +46 -0
  30. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
  31. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
  32. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
  33. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
  34. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
  35. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
  36. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
  37. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
  38. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
  39. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
  40. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
  41. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
  42. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
  43. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
  44. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  45. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
  46. package/recipes/ios-widget/targets/widget/CalorieTrackerWidget.swift +424 -0
  47. package/recipes/ios-widget/targets/widget/HabitTrackerWidget.swift +305 -0
  48. package/recipes/ios-widget/targets/widget/Info.plist +11 -0
  49. package/recipes/ios-widget/targets/widget/WidgetLiveActivity.swift +75 -0
  50. package/recipes/ios-widget/targets/widget/expo-target.config.js +10 -0
  51. package/recipes/ios-widget/targets/widget/generated.entitlements +5 -0
  52. package/recipes/ios-widget/targets/widget/index.swift +18 -0
  53. package/recipes/ios-widget/targets/widget/widgets.swift +96 -0
  54. package/recipes/ios-widget@latest.zip +0 -0
  55. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +74 -0
  56. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +25 -0
  57. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +23 -0
  58. package/recipes/payments/apps/native/src/features/payments/README.md +200 -0
  59. package/recipes/payments/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  60. package/recipes/payments/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  61. package/recipes/payments/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  62. package/recipes/payments/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  63. package/recipes/payments/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  64. package/recipes/payments/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  65. package/recipes/payments/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  66. package/recipes/payments/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  67. package/recipes/payments/apps/native/src/features/payments/index.ts +8 -0
  68. package/recipes/payments/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  69. package/recipes/payments/recipe.json +58 -0
  70. package/recipes/payments@latest.zip +0 -0
  71. package/src/__tests__/integration.test.ts +249 -0
  72. package/src/__tests__/recipes.test.ts +168 -0
  73. package/src/commands/__tests__/init.test.ts +112 -0
  74. package/src/commands/__tests__/platform.test.ts +141 -0
  75. package/src/commands/add.ts +4 -5
  76. package/src/core/journal.ts +42 -25
  77. package/src/core/recipes.ts +8 -40
@@ -0,0 +1,249 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { existsSync, rmSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ describe('VibeFast CLI Integration Tests', () => {
7
+ let testDir: string;
8
+
9
+ beforeEach(() => {
10
+ testDir = join(tmpdir(), `vibefast-integration-${Date.now()}`);
11
+ });
12
+
13
+ afterEach(() => {
14
+ if (existsSync(testDir)) {
15
+ rmSync(testDir, { recursive: true, force: true });
16
+ }
17
+ });
18
+
19
+ describe('Complete Workflows', () => {
20
+ it('should complete full init -> add features -> platform add workflow', async () => {
21
+ // 1. vf init (clean, native-only)
22
+ // 2. vf add chatbot
23
+ // 3. vf add image-generator
24
+ // 4. vf platform add (add web)
25
+ // 5. vf add quiz --target web
26
+ // Verify: All features installed correctly
27
+ // Verify: Both platforms exist
28
+ // Verify: Project is usable
29
+ expect(true).toBe(true);
30
+ });
31
+
32
+ it('should complete init with custom features workflow', async () => {
33
+ // 1. vf init (custom, select: chatbot, charts, image-generator)
34
+ // 2. Verify all features installed
35
+ // 3. Verify Vosk model downloaded (if wake-word selected)
36
+ // 4. Verify env vars detected
37
+ expect(true).toBe(true);
38
+ });
39
+
40
+ it('should complete platform removal workflow', async () => {
41
+ // 1. vf init (both platforms)
42
+ // 2. vf platform remove (remove web)
43
+ // 3. Verify web removed, native intact
44
+ // 4. vf platform add (add web back)
45
+ // 5. Verify web restored
46
+ expect(true).toBe(true);
47
+ });
48
+ });
49
+
50
+ describe('Feature Installation During Init', () => {
51
+ it('should install all selected features', async () => {
52
+ // Verify: chatbot files exist
53
+ // Verify: image-generator files exist
54
+ // Verify: charts files exist
55
+ // Verify: Navigation links added
56
+ // Verify: Dependencies listed
57
+ expect(true).toBe(true);
58
+ });
59
+
60
+ it('should handle feature installation failures', async () => {
61
+ // If one feature fails, others should continue
62
+ // Verify: Partial installation handled
63
+ // Verify: Error message shown
64
+ // Verify: User can retry
65
+ expect(true).toBe(true);
66
+ });
67
+
68
+ it('should auto-download Vosk for wake-word', async () => {
69
+ // Select wake-word feature
70
+ // Verify: Vosk model downloaded
71
+ // Verify: Model extracted correctly
72
+ // Verify: Model in correct location
73
+ expect(true).toBe(true);
74
+ });
75
+
76
+ it('should detect missing environment variables', async () => {
77
+ // Select chatbot (needs OPENAI_API_KEY)
78
+ // Verify: Missing vars detected
79
+ // Verify: User shown what's needed
80
+ // Verify: Links provided to get keys
81
+ expect(true).toBe(true);
82
+ });
83
+ });
84
+
85
+ describe('Configuration Management', () => {
86
+ it('should maintain valid package.json', async () => {
87
+ // After init, add, remove operations
88
+ // Verify: package.json is valid JSON
89
+ // Verify: workspaces array is correct
90
+ // Verify: No duplicate entries
91
+ expect(true).toBe(true);
92
+ });
93
+
94
+ it('should maintain valid turbo.json', async () => {
95
+ // After init, add, remove operations
96
+ // Verify: turbo.json is valid JSON
97
+ // Verify: pipeline tasks are correct
98
+ // Verify: No orphaned tasks
99
+ expect(true).toBe(true);
100
+ });
101
+
102
+ it('should handle concurrent operations safely', async () => {
103
+ // Run multiple operations in sequence
104
+ // Verify: No race conditions
105
+ // Verify: Configurations stay consistent
106
+ expect(true).toBe(true);
107
+ });
108
+ });
109
+
110
+ describe('User Input Handling', () => {
111
+ it('should handle back navigation in menus', async () => {
112
+ // In init menu, select back
113
+ // Verify: Goes to previous menu
114
+ // Verify: Can navigate back multiple times
115
+ expect(true).toBe(true);
116
+ });
117
+
118
+ it('should handle Ctrl+C gracefully', async () => {
119
+ // During init, press Ctrl+C
120
+ // Verify: Process exits cleanly
121
+ // Verify: No partial files left
122
+ expect(true).toBe(true);
123
+ });
124
+
125
+ it('should validate user inputs', async () => {
126
+ // Try invalid project names
127
+ // Try empty selections
128
+ // Verify: Shows error messages
129
+ // Verify: Prompts again
130
+ expect(true).toBe(true);
131
+ });
132
+
133
+ it('should handle --yes flag correctly', async () => {
134
+ // Run with --yes flag
135
+ // Verify: Skips all prompts
136
+ // Verify: Uses defaults
137
+ // Verify: Completes successfully
138
+ expect(true).toBe(true);
139
+ });
140
+
141
+ it('should handle --dry-run flag', async () => {
142
+ // Run vf add with --dry-run
143
+ // Verify: Shows what would happen
144
+ // Verify: No files created
145
+ // Verify: No changes made
146
+ expect(true).toBe(true);
147
+ });
148
+ });
149
+
150
+ describe('File System Operations', () => {
151
+ it('should create correct directory structure', async () => {
152
+ // After init
153
+ // Verify: apps/native exists (if selected)
154
+ // Verify: apps/web exists (if selected)
155
+ // Verify: packages/backend exists
156
+ // Verify: packages/ui exists
157
+ expect(true).toBe(true);
158
+ });
159
+
160
+ it('should handle file conflicts', async () => {
161
+ // Try to init in existing directory
162
+ // Verify: Shows error
163
+ // Verify: Doesn't overwrite
164
+ expect(true).toBe(true);
165
+ });
166
+
167
+ it('should clean up temporary files', async () => {
168
+ // After operations
169
+ // Verify: No .temp-* directories left
170
+ // Verify: No orphaned files
171
+ expect(true).toBe(true);
172
+ });
173
+
174
+ it('should handle permission errors', async () => {
175
+ // Try to write to read-only directory
176
+ // Verify: Shows error
177
+ // Verify: Doesn't crash
178
+ expect(true).toBe(true);
179
+ });
180
+ });
181
+
182
+ describe('Dependency Management', () => {
183
+ it('should install dependencies correctly', async () => {
184
+ // After init
185
+ // Verify: pnpm install runs
186
+ // Verify: node_modules created
187
+ // Verify: All deps installed
188
+ expect(true).toBe(true);
189
+ });
190
+
191
+ it('should handle missing pnpm', async () => {
192
+ // If pnpm not installed
193
+ // Verify: Offers to install
194
+ // Verify: Provides instructions
195
+ expect(true).toBe(true);
196
+ });
197
+
198
+ it('should update dependencies on platform add', async () => {
199
+ // After platform add
200
+ // Verify: New platform deps installed
201
+ // Verify: No conflicts
202
+ expect(true).toBe(true);
203
+ });
204
+ });
205
+
206
+ describe('Cross-Platform Paths', () => {
207
+ it('should handle Windows paths correctly', async () => {
208
+ // Verify: Path separators handled
209
+ // Verify: No hardcoded slashes
210
+ // Verify: Using path.join()
211
+ expect(true).toBe(true);
212
+ });
213
+
214
+ it('should handle Linux paths correctly', async () => {
215
+ // Verify: Paths work on Linux
216
+ // Verify: Permissions handled
217
+ expect(true).toBe(true);
218
+ });
219
+
220
+ it('should handle macOS paths correctly', async () => {
221
+ // Verify: Paths work on macOS
222
+ // Verify: Case sensitivity handled
223
+ expect(true).toBe(true);
224
+ });
225
+ });
226
+
227
+ describe('Error Recovery', () => {
228
+ it('should recover from network errors', async () => {
229
+ // Simulate network failure during download
230
+ // Verify: Shows error
231
+ // Verify: Suggests retry
232
+ expect(true).toBe(true);
233
+ });
234
+
235
+ it('should recover from corrupted downloads', async () => {
236
+ // Simulate corrupted zip file
237
+ // Verify: Shows error
238
+ // Verify: Cleans up
239
+ expect(true).toBe(true);
240
+ });
241
+
242
+ it('should recover from partial installations', async () => {
243
+ // Simulate failure mid-installation
244
+ // Verify: Can retry
245
+ // Verify: Doesn't duplicate
246
+ expect(true).toBe(true);
247
+ });
248
+ });
249
+ });
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { RECIPES, getRecipesByCategory, getRecipeInfo } from '../core/recipes.js';
3
+ import { existsSync } from 'fs';
4
+ import { join } from 'path';
5
+
6
+ describe('Recipe System', () => {
7
+ describe('Recipe Metadata', () => {
8
+ it('should have all 10 features defined', () => {
9
+ const features = getRecipesByCategory('feature');
10
+ expect(features).toHaveLength(10);
11
+ });
12
+
13
+ it('should include ios-widget in features', () => {
14
+ const features = getRecipesByCategory('feature');
15
+ const widget = features.find(f => f.name === 'ios-widget');
16
+
17
+ expect(widget).toBeDefined();
18
+ expect(widget?.name).toBe('ios-widget');
19
+ expect(widget?.description).toBe('iOS Home Screen widgets with Live Activities');
20
+ expect(widget?.icon).toBe('📱');
21
+ expect(widget?.category).toBe('feature');
22
+ });
23
+
24
+ it('should have all expected features', () => {
25
+ const features = getRecipesByCategory('feature');
26
+ const featureNames = features.map(f => f.name);
27
+
28
+ expect(featureNames).toContain('audio-recorder');
29
+ expect(featureNames).toContain('charts');
30
+ expect(featureNames).toContain('chatbot');
31
+ expect(featureNames).toContain('image-analysis');
32
+ expect(featureNames).toContain('image-generator');
33
+ expect(featureNames).toContain('quiz');
34
+ expect(featureNames).toContain('tracker-app');
35
+ expect(featureNames).toContain('voice-bot');
36
+ expect(featureNames).toContain('wake-word');
37
+ expect(featureNames).toContain('ios-widget');
38
+ });
39
+
40
+ it('should get recipe info by name', () => {
41
+ const widget = getRecipeInfo('ios-widget');
42
+ expect(widget).toBeDefined();
43
+ expect(widget?.name).toBe('ios-widget');
44
+ });
45
+
46
+ it('should return undefined for non-existent recipe', () => {
47
+ const fake = getRecipeInfo('non-existent-feature');
48
+ expect(fake).toBeUndefined();
49
+ });
50
+ });
51
+
52
+ describe('Recipe Files', () => {
53
+ const getRecipesDir = () => {
54
+ // When running from vibefast-cli directory
55
+ if (existsSync(join(process.cwd(), 'recipes'))) {
56
+ return join(process.cwd(), 'recipes');
57
+ }
58
+ // When running from monorepo root
59
+ return join(process.cwd(), 'vibefast-cli/recipes');
60
+ };
61
+
62
+ it('should have recipe.json for ios-widget', () => {
63
+ const recipesDir = getRecipesDir();
64
+ const recipePath = join(recipesDir, 'ios-widget/recipe.json');
65
+ expect(existsSync(recipePath)).toBe(true);
66
+ });
67
+
68
+ it('should have zip file for ios-widget', () => {
69
+ const recipesDir = getRecipesDir();
70
+ const zipPath = join(recipesDir, 'ios-widget@latest.zip');
71
+ expect(existsSync(zipPath)).toBe(true);
72
+ });
73
+
74
+ it('should have recipe.json for all features', () => {
75
+ const recipesDir = getRecipesDir();
76
+ const features = getRecipesByCategory('feature');
77
+
78
+ for (const feature of features) {
79
+ const recipePath = join(recipesDir, `${feature.name}/recipe.json`);
80
+ expect(existsSync(recipePath), `${feature.name} recipe.json should exist`).toBe(true);
81
+ }
82
+ });
83
+
84
+ it('should have zip files for all features', () => {
85
+ const recipesDir = getRecipesDir();
86
+ const features = getRecipesByCategory('feature');
87
+
88
+ for (const feature of features) {
89
+ const zipPath = join(recipesDir, `${feature.name}@latest.zip`);
90
+ expect(existsSync(zipPath), `${feature.name} zip should exist`).toBe(true);
91
+ }
92
+ });
93
+ });
94
+
95
+ describe('Recipe Content', () => {
96
+ const getRecipesDir = () => {
97
+ if (existsSync(join(process.cwd(), 'recipes'))) {
98
+ return join(process.cwd(), 'recipes');
99
+ }
100
+ return join(process.cwd(), 'vibefast-cli/recipes');
101
+ };
102
+
103
+ it('should have valid recipe.json for ios-widget', () => {
104
+ const { readFileSync } = require('fs');
105
+ const recipesDir = getRecipesDir();
106
+ const recipePath = join(recipesDir, 'ios-widget/recipe.json');
107
+ const content = readFileSync(recipePath, 'utf-8');
108
+ const recipe = JSON.parse(content);
109
+
110
+ expect(recipe.name).toBe('ios-widget');
111
+ expect(recipe.version).toBe('1.0.0');
112
+ expect(recipe.description).toBe('iOS Home Screen widgets with Live Activities');
113
+ expect(recipe.platforms).toContain('native');
114
+ expect(recipe.files).toBeDefined();
115
+ expect(recipe.files.length).toBeGreaterThan(0);
116
+ });
117
+
118
+ it('should have dependencies for ios-widget', () => {
119
+ const { readFileSync } = require('fs');
120
+ const recipesDir = getRecipesDir();
121
+ const recipePath = join(recipesDir, 'ios-widget/recipe.json');
122
+ const content = readFileSync(recipePath, 'utf-8');
123
+ const recipe = JSON.parse(content);
124
+
125
+ expect(recipe.dependencies).toBeDefined();
126
+ expect(recipe.dependencies.npm).toContain('@bacons/apple-targets');
127
+ });
128
+
129
+ it('should have manual steps for ios-widget', () => {
130
+ const { readFileSync } = require('fs');
131
+ const recipesDir = getRecipesDir();
132
+ const recipePath = join(recipesDir, 'ios-widget/recipe.json');
133
+ const content = readFileSync(recipePath, 'utf-8');
134
+ const recipe = JSON.parse(content);
135
+
136
+ expect(recipe.manualSteps).toBeDefined();
137
+ expect(recipe.manualSteps.length).toBeGreaterThan(0);
138
+
139
+ const pluginStep = recipe.manualSteps.find((s: any) => s.title.includes('Widget Plugin'));
140
+ expect(pluginStep).toBeDefined();
141
+ expect(pluginStep.file).toBe('apps/native/app.config.ts');
142
+ });
143
+
144
+ it('should have post-install instructions for ios-widget', () => {
145
+ const { readFileSync } = require('fs');
146
+ const recipesDir = getRecipesDir();
147
+ const recipePath = join(recipesDir, 'ios-widget/recipe.json');
148
+ const content = readFileSync(recipePath, 'utf-8');
149
+ const recipe = JSON.parse(content);
150
+
151
+ expect(recipe.postInstall).toBeDefined();
152
+ expect(recipe.postInstall.instructions).toBeDefined();
153
+ expect(recipe.postInstall.instructions.length).toBeGreaterThan(0);
154
+ });
155
+ });
156
+
157
+ describe('Recipe Categories', () => {
158
+ it('should only have feature category (no advanced-ui yet)', () => {
159
+ const advancedUI = getRecipesByCategory('advanced-ui');
160
+ expect(advancedUI).toHaveLength(0);
161
+ });
162
+
163
+ it('should have all recipes in RECIPES array', () => {
164
+ expect(RECIPES.length).toBeGreaterThan(0);
165
+ expect(RECIPES.length).toBe(10); // 10 features
166
+ });
167
+ });
168
+ });
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { existsSync, rmSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ // Mock the external dependencies
7
+ vi.mock('simple-git');
8
+ vi.mock('../core/log.js');
9
+ vi.mock('../core/spinner.js');
10
+ vi.mock('../core/prompt.js');
11
+
12
+ describe('vf init command', () => {
13
+ let testDir: string;
14
+
15
+ beforeEach(() => {
16
+ testDir = join(tmpdir(), `vibefast-test-${Date.now()}`);
17
+ });
18
+
19
+ afterEach(() => {
20
+ if (existsSync(testDir)) {
21
+ rmSync(testDir, { recursive: true, force: true });
22
+ }
23
+ });
24
+
25
+ describe('Clean Mode', () => {
26
+ it('should create clean native-only project', async () => {
27
+ // This would be tested with mocked git clone
28
+ // Verify: apps/native exists, apps/web doesn't
29
+ expect(true).toBe(true); // Placeholder
30
+ });
31
+
32
+ it('should create clean web-only project', async () => {
33
+ // Verify: apps/web exists, apps/native doesn't
34
+ expect(true).toBe(true);
35
+ });
36
+
37
+ it('should create clean both-platforms project', async () => {
38
+ // Verify: both apps/native and apps/web exist
39
+ expect(true).toBe(true);
40
+ });
41
+ });
42
+
43
+ describe('Custom Mode', () => {
44
+ it('should select and install features', async () => {
45
+ // Verify: selected features are installed
46
+ // Verify: feature files exist in correct locations
47
+ expect(true).toBe(true);
48
+ });
49
+
50
+ it('should handle feature installation failures gracefully', async () => {
51
+ // Verify: continues with other features if one fails
52
+ // Verify: shows error message
53
+ expect(true).toBe(true);
54
+ });
55
+
56
+ it('should auto-download Vosk model for wake-word', async () => {
57
+ // Verify: Vosk model is downloaded
58
+ // Verify: model is extracted to correct location
59
+ expect(true).toBe(true);
60
+ });
61
+ });
62
+
63
+ describe('Configuration Updates', () => {
64
+ it('should update package.json workspaces correctly', async () => {
65
+ // Verify: workspaces array contains correct platforms
66
+ expect(true).toBe(true);
67
+ });
68
+
69
+ it('should update turbo.json with platform tasks', async () => {
70
+ // Verify: turbo.json has correct pipeline tasks
71
+ expect(true).toBe(true);
72
+ });
73
+
74
+ it('should remove unwanted platform tasks from turbo.json', async () => {
75
+ // Verify: web tasks removed if web-only
76
+ // Verify: native tasks removed if native-only
77
+ expect(true).toBe(true);
78
+ });
79
+ });
80
+
81
+ describe('Environment Setup', () => {
82
+ it('should create .env files from examples', async () => {
83
+ // Verify: .env.local, .env.staging, .env.production created
84
+ expect(true).toBe(true);
85
+ });
86
+
87
+ it('should detect missing environment variables', async () => {
88
+ // Verify: shows missing vars for selected features
89
+ expect(true).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe('Error Handling', () => {
94
+ it('should handle invalid project names', async () => {
95
+ // Verify: error message shown
96
+ // Verify: process exits gracefully
97
+ expect(true).toBe(true);
98
+ });
99
+
100
+ it('should handle existing directory conflicts', async () => {
101
+ // Verify: error message shown
102
+ // Verify: doesn't overwrite
103
+ expect(true).toBe(true);
104
+ });
105
+
106
+ it('should handle missing license key', async () => {
107
+ // Verify: prompts for license key
108
+ // Verify: validates key format
109
+ expect(true).toBe(true);
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { existsSync, rmSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ vi.mock('simple-git');
7
+ vi.mock('../core/log.js');
8
+ vi.mock('../core/spinner.js');
9
+ vi.mock('../core/prompt.js');
10
+
11
+ describe('vf platform command', () => {
12
+ let testDir: string;
13
+
14
+ beforeEach(() => {
15
+ testDir = join(tmpdir(), `vibefast-platform-test-${Date.now()}`);
16
+ });
17
+
18
+ afterEach(() => {
19
+ if (existsSync(testDir)) {
20
+ rmSync(testDir, { recursive: true, force: true });
21
+ }
22
+ });
23
+
24
+ describe('platform add', () => {
25
+ it('should add web platform to native-only project', async () => {
26
+ // Setup: Create native-only project
27
+ // Action: vf platform add (choose web)
28
+ // Verify: apps/web exists
29
+ // Verify: package.json has both workspaces
30
+ // Verify: turbo.json has web tasks
31
+ expect(true).toBe(true);
32
+ });
33
+
34
+ it('should add native platform to web-only project', async () => {
35
+ // Setup: Create web-only project
36
+ // Action: vf platform add (choose native)
37
+ // Verify: apps/native exists
38
+ // Verify: package.json has both workspaces
39
+ // Verify: turbo.json has native tasks
40
+ expect(true).toBe(true);
41
+ });
42
+
43
+ it('should show error if both platforms exist', async () => {
44
+ // Setup: Create both-platform project
45
+ // Action: vf platform add
46
+ // Verify: Shows "Both platforms already exist"
47
+ expect(true).toBe(true);
48
+ });
49
+
50
+ it('should update configurations correctly', async () => {
51
+ // Verify: package.json workspaces updated
52
+ // Verify: turbo.json pipeline updated
53
+ // Verify: No duplicate entries
54
+ expect(true).toBe(true);
55
+ });
56
+
57
+ it('should handle clone failures gracefully', async () => {
58
+ // Verify: Shows error message
59
+ // Verify: No partial files left
60
+ // Verify: Cleans up temp directory
61
+ expect(true).toBe(true);
62
+ });
63
+ });
64
+
65
+ describe('platform remove', () => {
66
+ it('should remove web platform from both-platform project', async () => {
67
+ // Setup: Create both-platform project
68
+ // Action: vf platform remove (choose web)
69
+ // Verify: apps/web removed
70
+ // Verify: apps/native still exists
71
+ // Verify: package.json updated
72
+ // Verify: turbo.json updated
73
+ expect(true).toBe(true);
74
+ });
75
+
76
+ it('should remove native platform from both-platform project', async () => {
77
+ // Setup: Create both-platform project
78
+ // Action: vf platform remove (choose native)
79
+ // Verify: apps/native removed
80
+ // Verify: apps/web still exists
81
+ // Verify: Configurations updated
82
+ expect(true).toBe(true);
83
+ });
84
+
85
+ it('should prevent removing only platform', async () => {
86
+ // Setup: Create native-only project
87
+ // Action: vf platform remove
88
+ // Verify: Shows error "Cannot remove the only platform"
89
+ // Verify: No changes made
90
+ expect(true).toBe(true);
91
+ });
92
+
93
+ it('should require confirmation before removing', async () => {
94
+ // Setup: Create both-platform project
95
+ // Action: vf platform remove (choose web, then cancel)
96
+ // Verify: Platform not removed
97
+ // Verify: Shows confirmation prompt
98
+ expect(true).toBe(true);
99
+ });
100
+
101
+ it('should skip confirmation with --yes flag', async () => {
102
+ // Setup: Create both-platform project
103
+ // Action: vf platform remove --yes
104
+ // Verify: Platform removed without prompt
105
+ expect(true).toBe(true);
106
+ });
107
+
108
+ it('should clean up configurations', async () => {
109
+ // Verify: Removed platform tasks from turbo.json
110
+ // Verify: Removed platform from package.json workspaces
111
+ // Verify: No orphaned entries
112
+ expect(true).toBe(true);
113
+ });
114
+ });
115
+
116
+ describe('Error Handling', () => {
117
+ it('should handle non-VibeFast projects', async () => {
118
+ // Setup: Create regular directory
119
+ // Action: vf platform add
120
+ // Verify: Shows "Not a VibeFast project"
121
+ expect(true).toBe(true);
122
+ });
123
+
124
+ it('should handle missing .vibefast directory', async () => {
125
+ // Verify: Shows appropriate error
126
+ expect(true).toBe(true);
127
+ });
128
+
129
+ it('should handle corrupted package.json', async () => {
130
+ // Verify: Shows error
131
+ // Verify: Doesn't crash
132
+ expect(true).toBe(true);
133
+ });
134
+
135
+ it('should handle corrupted turbo.json', async () => {
136
+ // Verify: Shows error
137
+ // Verify: Doesn't crash
138
+ expect(true).toBe(true);
139
+ });
140
+ });
141
+ });
@@ -57,11 +57,10 @@ export const addCommand = new Command('add')
57
57
  log.plain('');
58
58
  log.info('Examples:');
59
59
  log.plain(' vf add # Interactive menu');
60
- log.plain(' vf add feature # Show all features');
61
- log.plain(' vf add integration # Show all integrations');
62
- log.plain(' vf add charts # Add specific feature');
63
- log.plain(' vf add sentry --target native');
64
- log.plain(' vf add chatbot --dry-run');
60
+ log.plain(' vf add chatbot # Add specific feature');
61
+ log.plain(' vf add chatbot --dry-run # Preview changes');
62
+ log.plain(' vf add chatbot --force # Force reinstall');
63
+ log.plain(' vf add chatbot --yes # Skip prompts');
65
64
  })
66
65
  .action(async (feature: string | undefined, options) => {
67
66
  try {