vibefast-cli 0.4.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.
- package/FEATURE-DEPENDENCY-SPEC.md +338 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +219 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/recipes.test.d.ts +2 -0
- package/dist/__tests__/recipes.test.d.ts.map +1 -0
- package/dist/__tests__/recipes.test.js +143 -0
- package/dist/__tests__/recipes.test.js.map +1 -0
- package/dist/commands/__tests__/init.test.d.ts +2 -0
- package/dist/commands/__tests__/init.test.d.ts.map +1 -0
- package/dist/commands/__tests__/init.test.js +95 -0
- package/dist/commands/__tests__/init.test.js.map +1 -0
- package/dist/commands/__tests__/platform.test.d.ts +2 -0
- package/dist/commands/__tests__/platform.test.d.ts.map +1 -0
- package/dist/commands/__tests__/platform.test.js +123 -0
- package/dist/commands/__tests__/platform.test.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +4 -5
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -12
- 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/core/journal.d.ts.map +1 -1
- package/dist/core/journal.js +36 -19
- package/dist/core/journal.js.map +1 -1
- package/dist/core/recipes.d.ts.map +1 -1
- package/dist/core/recipes.js +8 -39
- package/dist/core/recipes.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/recipes/ios-widget/recipe.json +78 -0
- package/recipes/ios-widget/targets/widget/AppIntent.swift +46 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/CalorieTrackerWidget.swift +424 -0
- package/recipes/ios-widget/targets/widget/HabitTrackerWidget.swift +305 -0
- package/recipes/ios-widget/targets/widget/Info.plist +11 -0
- package/recipes/ios-widget/targets/widget/WidgetLiveActivity.swift +75 -0
- package/recipes/ios-widget/targets/widget/expo-target.config.js +10 -0
- package/recipes/ios-widget/targets/widget/generated.entitlements +5 -0
- package/recipes/ios-widget/targets/widget/index.swift +18 -0
- package/recipes/ios-widget/targets/widget/widgets.swift +96 -0
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +74 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +25 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +23 -0
- package/recipes/payments/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments/recipe.json +58 -0
- package/recipes/payments@latest.zip +0 -0
- package/src/__tests__/integration.test.ts +249 -0
- package/src/__tests__/recipes.test.ts +168 -0
- package/src/commands/__tests__/init.test.ts +112 -0
- package/src/commands/__tests__/platform.test.ts +141 -0
- package/src/commands/add.ts +4 -5
- package/src/commands/init.ts +14 -15
- package/src/commands/platform.ts +309 -0
- package/src/core/journal.ts +42 -25
- package/src/core/recipes.ts +8 -40
- package/src/index.ts +2 -0
|
@@ -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
|
+
});
|
package/src/commands/add.ts
CHANGED
|
@@ -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
|
|
61
|
-
log.plain(' vf add
|
|
62
|
-
log.plain(' vf add
|
|
63
|
-
log.plain(' vf add
|
|
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 {
|