vibefast-cli 1.2.1 → 1.3.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/README.md +30 -95
- package/dist/__tests__/recipes.test.js +94 -91
- package/dist/__tests__/recipes.test.js.map +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +301 -125
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +85 -44
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +13 -4
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +118 -26
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +202 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +61 -3
- package/dist/commands/remove.js.map +1 -1
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +20 -18
- package/dist/core/auth.js.map +1 -1
- package/dist/core/codemod.d.ts +33 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +116 -0
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +24 -7
- package/dist/core/detect.js.map +1 -1
- package/dist/core/journal.d.ts +1 -0
- package/dist/core/journal.d.ts.map +1 -1
- package/dist/core/journal.js.map +1 -1
- package/dist/core/recipes.d.ts.map +1 -1
- package/dist/core/recipes.js +25 -7
- package/dist/core/recipes.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/architecture.md +50 -0
- package/docs/commands.md +78 -0
- package/docs/contributing.md +27 -0
- package/docs/quickstart.md +50 -0
- package/docs/recipes.md +57 -0
- package/docs/troubleshooting.md +31 -0
- package/package.json +2 -2
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/demo.tsx +445 -0
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/timeline-view.tsx +355 -0
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/types.ts +31 -0
- package/recipes/0/recipe.json +18 -0
- package/recipes/animated-chip/apps/native/src/components/advanced-ui/chip/demo.tsx +2 -1
- package/recipes/animated-chip/recipe.json +5 -2
- package/recipes/animated-chip-native@latest.zip +0 -0
- package/recipes/animated-chip@latest.zip +0 -0
- package/recipes/animated-switch/apps/native/src/components/advanced-ui/switch/demo.tsx +1 -1
- package/recipes/animated-switch/recipe.json +5 -2
- package/recipes/animated-switch-native@latest.zip +0 -0
- package/recipes/animated-switch@latest.zip +0 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -2
- package/recipes/audio-recorder/recipe.json +7 -2
- package/recipes/audio-recorder-native@latest.zip +0 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -1
- package/recipes/audio-recorder-supabase/recipe.json +12 -16
- package/recipes/audio-recorder-supabase-native@latest.zip +0 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +3 -1
- package/recipes/charts/recipe.json +13 -4
- package/recipes/charts-native@latest.zip +0 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +4 -4
- package/recipes/chatbot/recipe.json +3 -40
- package/recipes/chatbot-native@latest.zip +0 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +4 -1
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
- package/recipes/chatbot-supabase/recipe.json +3 -42
- package/recipes/chatbot-supabase-native@latest.zip +0 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/glowing-button/recipe.json +6 -2
- package/recipes/glowing-button-native@latest.zip +0 -0
- package/recipes/glowing-button@latest.zip +0 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/index.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/results.tsx +2 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +2 -2
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/camera.tsx +72 -65
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +65 -47
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading-screen.tsx +43 -2
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading.tsx +34 -1
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +83 -2
- package/recipes/image-analysis/recipe.json +11 -19
- package/recipes/image-analysis-native@latest.zip +0 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/index.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/results.tsx +2 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
- package/recipes/image-analysis-supabase/recipe.json +10 -37
- package/recipes/image-analysis-supabase-native@latest.zip +0 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-analyzer/apps/native/src/app/(root)/(protected)/image-analyzer/index.tsx +2 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/recipe.json +8 -18
- package/recipes/image-generator-native@latest.zip +0 -0
- package/recipes/image-generator-supabase/recipe.json +6 -35
- package/recipes/image-generator-supabase-native@latest.zip +0 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +18 -119
- package/recipes/ios-widget-native@latest.zip +0 -0
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/number-stepper/apps/native/src/components/advanced-ui/stepper/demo.tsx +1 -1
- package/recipes/number-stepper/recipe.json +5 -2
- package/recipes/number-stepper-native@latest.zip +0 -0
- package/recipes/number-stepper@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +11 -18
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +9 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +8 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +4 -3
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +7 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +8 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +5 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +5 -4
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +7 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +4 -3
- package/recipes/onboarding/recipe.json +9 -6
- package/recipes/onboarding-native@latest.zip +0 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/apps/native/src/app/paywall/index.tsx +74 -0
- package/recipes/payments/apps/native/src/app/paywall/local.tsx +25 -0
- package/recipes/payments/apps/native/src/app/paywall/remote.tsx +23 -0
- package/recipes/payments/packages/backend/convex/payments.ts +21 -3
- package/recipes/payments/recipe.json +14 -34
- package/recipes/payments-native@latest.zip +0 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/index.tsx +74 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/local.tsx +25 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/remote.tsx +23 -0
- package/recipes/payments-supabase/recipe.json +16 -23
- package/recipes/payments-supabase-native@latest.zip +0 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/posthog/apps/native/src/components/analytics/navigation-tracker.tsx +14 -0
- package/recipes/posthog/apps/native/src/lib/hooks/use-navigation-analytics.ts +44 -0
- package/recipes/posthog/apps/native/src/providers/posthog-provider.tsx +51 -0
- package/recipes/posthog/recipe.json +60 -0
- package/recipes/posthog-native@latest.zip +0 -0
- package/recipes/progress-circle/apps/native/src/components/advanced-ui/progress-bars/progress-circle-page.tsx +1 -1
- package/recipes/progress-circle/recipe.json +5 -2
- package/recipes/progress-circle-native@latest.zip +0 -0
- package/recipes/progress-circle@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/recipe.json +9 -6
- package/recipes/quiz-native@latest.zip +0 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/screen-kits/apps/native/src/app/screen-kits/_layout.tsx +12 -0
- package/recipes/screen-kits/apps/native/src/app/screen-kits/index.tsx +114 -0
- package/recipes/screen-kits/apps/native/src/features/screen-kits/index.ts +1 -0
- package/recipes/screen-kits/apps/native/src/features/screen-kits/types.ts +28 -0
- package/recipes/screen-kits/recipe.json +26 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/_layout.tsx +12 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/home.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/index.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-complete.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-fail.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/skill-tree.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/duo-button.tsx +174 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/skill-button.tsx +186 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/xp-header.tsx +115 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/constants.ts +89 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/index.ts +3 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/home-screen.tsx +225 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-complete-screen.tsx +485 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-fail-screen.tsx +105 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-screen.tsx +384 -0
- package/recipes/screen-kits-duolingo/recipe.json +58 -0
- package/recipes/screen-kits-duolingo-native@latest.zip +0 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/_layout.tsx +45 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/asset-detail.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/notifications.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/receive.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/send.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/swap.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/wallet.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/ActionButtons.tsx +78 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/AssetRow.tsx +94 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/BalanceCard.tsx +118 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/constants.ts +85 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/asset-detail.tsx +378 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/notifications.tsx +210 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/receive-modal.tsx +317 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/send-modal.tsx +420 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/swap-modal.tsx +363 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/wallet-dashboard.tsx +281 -0
- package/recipes/screen-kits-finance/recipe.json +46 -0
- package/recipes/screen-kits-finance-native@latest.zip +0 -0
- package/recipes/screen-kits-fitness/apps/native/assets/sounds/timer-beep.wav +0 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/_layout.tsx +10 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/index.tsx +6 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/timer.tsx +3 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/workout.tsx +3 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-components.tsx +500 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-settings-modal.tsx +352 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/workout-card.tsx +105 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/constants.ts +189 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/hooks/use-timer.ts +307 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/index.ts +1 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/timer-screen.tsx +278 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/workout-dashboard.tsx +350 -0
- package/recipes/screen-kits-fitness/recipe.json +63 -0
- package/recipes/screen-kits-fitness-native@latest.zip +0 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/habits.tsx +1 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/kanban.tsx +1 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/routes.ts +4 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/AddTaskModal.tsx +246 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/DraggableTaskCard.tsx +92 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/KanbanColumn.tsx +238 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/TaskCard.tsx +144 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/add-habit-modal.tsx +271 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/constants.ts +295 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/kanban-utils.ts +62 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/habit-tracker.tsx +1160 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/kanban-board.tsx +432 -0
- package/recipes/screen-kits-habits/recipe.json +52 -0
- package/recipes/screen-kits-habits-native@latest.zip +0 -0
- package/recipes/screen-kits-native@latest.zip +0 -0
- package/recipes/sentry/apps/native/src/providers/sentry-provider.tsx +64 -0
- package/recipes/sentry/recipe.json +39 -0
- package/recipes/sentry-native@latest.zip +0 -0
- package/recipes/swipe-slider/apps/native/src/components/advanced-ui/sliders/swipe-slider-page.tsx +1 -1
- package/recipes/swipe-slider/recipe.json +5 -2
- package/recipes/swipe-slider-native@latest.zip +0 -0
- package/recipes/swipe-slider@latest.zip +0 -0
- package/recipes/timeline/apps/native/src/components/advanced-ui/timeline/demo.tsx +2 -1
- package/recipes/timeline/recipe.json +5 -2
- package/recipes/timeline-native@latest.zip +0 -0
- package/recipes/timeline@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/recipe.json +10 -7
- package/recipes/tracker-app-native@latest.zip +0 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +8 -31
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +56 -0
- package/recipes/voice-bot/recipe.json +31 -7
- package/recipes/voice-bot-native@latest.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/apps/native/src/app/{(root)/(protected)/test-wake-word.tsx → test-wake-word.tsx} +43 -4
- package/recipes/wake-word/recipe.json +16 -26
- package/recipes/wake-word-native@latest.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/scripts/create-advanced-ui-recipes.sh +46 -19
- package/scripts/create-recipes.mjs +471 -117
- package/scripts/package-recipes.mjs +76 -0
- package/scripts/publish-all.sh +6 -2
- package/CHANGELOG.md +0 -198
- package/docs/archive/AUTO-DETECT-DEPS.md +0 -607
- package/docs/archive/FINAL-PACKAGE-STRATEGY.md +0 -583
- package/docs/archive/FINAL-SIMPLE-PLAN.md +0 -487
- package/docs/archive/FINAL-STATUS.md +0 -144
- package/docs/archive/FLOW-DIAGRAM.md +0 -1629
- package/docs/archive/GOTCHAS-AND-RISKS.md +0 -801
- package/docs/archive/IMPLEMENTATION-PLAN.md +0 -1360
- package/docs/archive/PLAN.md +0 -453
- package/docs/archive/PRODUCTION-READINESS.md +0 -684
- package/docs/archive/PRODUCTION-TEST-RESULTS.md +0 -465
- package/docs/archive/SIMPLIFIED-PLAN.md +0 -578
- package/docs/archive/STATUS.md +0 -199
- package/docs/archive/SUCCESS.md +0 -259
- package/docs/archive/TEST-SUMMARY.md +0 -261
- package/docs/archive/TESTING-CHECKLIST.md +0 -450
- package/docs/archive/USER-MODIFICATIONS.md +0 -448
- package/docs/decisions.md +0 -55
- package/docs/manual-testing.md +0 -91
- package/docs/next-steps.md +0 -12
- package/recipes/README.md +0 -156
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +0 -369
- package/recipes/chatbot/apps/native/src/api-client/chatbot.ts +0 -83
- package/recipes/chatbot/packages/backend/convex/agents.ts +0 -115
- package/recipes/chatbot/packages/backend/convex/tools/index.ts +0 -18
- package/recipes/chatbot/packages/backend/convex/tools/knowledgeRetrieval.ts +0 -97
- package/recipes/chatbot/packages/backend/convex/tools/tavilySearch.ts +0 -83
- package/recipes/chatbot/packages/backend/convex/tools/userProfile.ts +0 -72
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +0 -515
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +0 -243
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +0 -327
- package/recipes/image-analysis/apps/native/src/api-client/image-analyzer.ts +0 -62
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +0 -132
- package/recipes/image-generator/apps/native/src/api-client/image-generator.ts +0 -34
- package/recipes/payments/apps/native/src/api-client/payments.ts +0 -44
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +0 -201
- package/recipes/posthog.json +0 -47
- package/recipes/revenuecat.json +0 -43
- package/recipes/sentry.json +0 -47
- package/recipes/wake-word/apps/native/assets/vosk-model/README.md +0 -103
- package/recipes/wake-word/apps/native/scripts/download-vosk-model.mjs +0 -127
- /package/recipes/{audio-recorder/apps/native/src/app/(root)/(protected) → audio-recorder-supabase/apps/native/src/app}/audio-recorder/index.tsx +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/AppIntent.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/CalorieTrackerWidget.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/HabitTrackerWidget.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Info.plist +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/WidgetLiveActivity.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/expo-target.config.js +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/generated.entitlements +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/index.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/widgets.swift +0 -0
|
@@ -1,1360 +0,0 @@
|
|
|
1
|
-
# 🔧 Technical Implementation Plan
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This is the **exact technical plan** for making VibeFast CLI production-ready. No hand-waving, just concrete steps with code examples and gotchas.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Phase 1: Interactive Confirmation (Priority 1) 🔴
|
|
10
|
-
|
|
11
|
-
### Goal
|
|
12
|
-
When files exist, ask user y/n before overwriting. Show what will be lost.
|
|
13
|
-
|
|
14
|
-
### Technical Approach
|
|
15
|
-
|
|
16
|
-
#### Step 1.1: Add readline-sync for prompts
|
|
17
|
-
|
|
18
|
-
**Why not built-in readline?**
|
|
19
|
-
- Built-in readline is async and complex
|
|
20
|
-
- readline-sync is simpler for CLI prompts
|
|
21
|
-
- Works in both interactive and non-interactive modes
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
cd vibefast-cli
|
|
25
|
-
npm install readline-sync
|
|
26
|
-
npm install --save-dev @types/readline-sync
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### Step 1.2: Create prompt utility
|
|
30
|
-
|
|
31
|
-
**File:** `src/core/prompt.ts`
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import readlineSync from 'readline-sync';
|
|
35
|
-
|
|
36
|
-
export function promptUser(question: string): string {
|
|
37
|
-
// Check if we're in a non-interactive environment (CI, piped input)
|
|
38
|
-
if (!process.stdin.isTTY) {
|
|
39
|
-
return 'n'; // Default to no in non-interactive mode
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return readlineSync.question(question);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function promptYesNo(question: string, defaultYes = false): boolean {
|
|
46
|
-
const answer = promptUser(question);
|
|
47
|
-
const normalized = answer.toLowerCase().trim();
|
|
48
|
-
|
|
49
|
-
if (normalized === '') {
|
|
50
|
-
return defaultYes;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return normalized === 'y' || normalized === 'yes';
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Gotcha:** CI environments don't have TTY, so we need to handle that.
|
|
58
|
-
|
|
59
|
-
#### Step 1.3: Update copyTree to detect conflicts
|
|
60
|
-
|
|
61
|
-
**File:** `src/core/fsx.ts`
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
export interface CopyResult {
|
|
65
|
-
files: string[];
|
|
66
|
-
conflicts: string[];
|
|
67
|
-
skipped: string[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function copyTree(
|
|
71
|
-
src: string,
|
|
72
|
-
dest: string,
|
|
73
|
-
options?: {
|
|
74
|
-
dryRun?: boolean;
|
|
75
|
-
force?: boolean;
|
|
76
|
-
interactive?: boolean;
|
|
77
|
-
}
|
|
78
|
-
): Promise<CopyResult> {
|
|
79
|
-
const copied: string[] = [];
|
|
80
|
-
const conflicts: string[] = [];
|
|
81
|
-
const skipped: string[] = [];
|
|
82
|
-
|
|
83
|
-
async function copyRecursive(srcPath: string, destPath: string) {
|
|
84
|
-
const stats = await stat(srcPath);
|
|
85
|
-
|
|
86
|
-
if (stats.isDirectory()) {
|
|
87
|
-
if (!options?.dryRun) {
|
|
88
|
-
await ensureDir(destPath);
|
|
89
|
-
}
|
|
90
|
-
const entries = await readdir(srcPath);
|
|
91
|
-
for (const entry of entries) {
|
|
92
|
-
await copyRecursive(join(srcPath, entry), join(destPath, entry));
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
const destExists = await exists(destPath);
|
|
96
|
-
|
|
97
|
-
if (destExists) {
|
|
98
|
-
conflicts.push(destPath);
|
|
99
|
-
|
|
100
|
-
// If not force and not interactive, throw error
|
|
101
|
-
if (!options?.force && !options?.interactive) {
|
|
102
|
-
throw new Error(`File exists: ${destPath}. Use --force to overwrite.`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// If interactive and not dry-run, ask user
|
|
106
|
-
if (options?.interactive && !options?.dryRun) {
|
|
107
|
-
const { promptYesNo } = await import('./prompt.js');
|
|
108
|
-
const shouldOverwrite = promptYesNo(
|
|
109
|
-
`Overwrite ${destPath}? (y/N): `,
|
|
110
|
-
false
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (!shouldOverwrite) {
|
|
114
|
-
skipped.push(destPath);
|
|
115
|
-
return; // Skip this file
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!options?.dryRun) {
|
|
121
|
-
await ensureDir(dirname(destPath));
|
|
122
|
-
await copyFile(srcPath, destPath);
|
|
123
|
-
}
|
|
124
|
-
copied.push(destPath);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await copyRecursive(src, dest);
|
|
129
|
-
return { files: copied, conflicts, skipped };
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#### Step 1.4: Update add command to use interactive mode
|
|
135
|
-
|
|
136
|
-
**File:** `src/commands/add.ts`
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
// After checking if already installed
|
|
140
|
-
if (existing && !options.force) {
|
|
141
|
-
log.warn(`${feature} is already installed for ${target}`);
|
|
142
|
-
log.info('Use --force to reinstall');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// NEW: Scan for conflicts BEFORE downloading
|
|
147
|
-
if (!options.dryRun && !options.force) {
|
|
148
|
-
log.info('Checking for file conflicts...');
|
|
149
|
-
|
|
150
|
-
// We need to know what files will be copied
|
|
151
|
-
// Problem: We don't have the recipe yet!
|
|
152
|
-
// Solution: Download recipe first, check conflicts, then ask
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Copy files with interactive mode
|
|
156
|
-
const result = await copyTree(srcPath, destPath, {
|
|
157
|
-
dryRun: options.dryRun,
|
|
158
|
-
force: options.force,
|
|
159
|
-
interactive: !options.force && !options.dryRun, // Enable interactive if not force
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Show results
|
|
163
|
-
if (result.conflicts.length > 0) {
|
|
164
|
-
if (options.dryRun) {
|
|
165
|
-
log.warn(`⚠ ${result.conflicts.length} files will be overwritten:`);
|
|
166
|
-
result.conflicts.slice(0, 5).forEach(f => {
|
|
167
|
-
const relativePath = f.replace(paths.cwd + '/', '');
|
|
168
|
-
log.plain(` • ${relativePath}`);
|
|
169
|
-
});
|
|
170
|
-
if (result.conflicts.length > 5) {
|
|
171
|
-
log.plain(` ... and ${result.conflicts.length - 5} more`);
|
|
172
|
-
}
|
|
173
|
-
log.warn('⚠ Make sure you have committed your changes to Git!');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (result.skipped.length > 0) {
|
|
178
|
-
log.info(`ℹ Skipped ${result.skipped.length} files (user chose not to overwrite)`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
copiedFiles.push(...result.files);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
**Gotcha:** We need the recipe to know what files will be copied, but we don't want to download it if user will cancel. Solution: Download first, show conflicts, then proceed.
|
|
185
|
-
|
|
186
|
-
#### Step 1.5: Add --yes flag for automation
|
|
187
|
-
|
|
188
|
-
**File:** `src/commands/add.ts`
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
export const addCommand = new Command('add')
|
|
192
|
-
.description('Add a VibeFast feature to your project')
|
|
193
|
-
.argument('<feature>', 'Feature name to install')
|
|
194
|
-
.option('--target <target>', 'Target platform (native or web)', 'native')
|
|
195
|
-
.option('--dry-run', 'Preview changes without applying')
|
|
196
|
-
.option('--force', 'Overwrite existing files without asking')
|
|
197
|
-
.option('--yes', 'Answer yes to all prompts (for automation)')
|
|
198
|
-
.action(async (feature: string, options) => {
|
|
199
|
-
// ...
|
|
200
|
-
const interactive = !options.force && !options.dryRun && !options.yes;
|
|
201
|
-
// ...
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
**Testing:**
|
|
206
|
-
```bash
|
|
207
|
-
# Interactive mode (asks for each file)
|
|
208
|
-
$ vf add charts
|
|
209
|
-
|
|
210
|
-
# Force mode (overwrites all)
|
|
211
|
-
$ vf add charts --force
|
|
212
|
-
|
|
213
|
-
# Automation mode (assumes yes)
|
|
214
|
-
$ vf add charts --yes
|
|
215
|
-
|
|
216
|
-
# Dry run (shows what would happen)
|
|
217
|
-
$ vf add charts --dry-run
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Phase 2: Package Dependency Management (Priority 2) 🔴
|
|
223
|
-
|
|
224
|
-
### Goal
|
|
225
|
-
Detect required packages, show them to user, optionally install them.
|
|
226
|
-
|
|
227
|
-
### The Hard Part: How to Know What Packages Are Needed?
|
|
228
|
-
|
|
229
|
-
**Option A: Parse imports from files** ❌
|
|
230
|
-
```typescript
|
|
231
|
-
// Parse all .ts/.tsx files and extract imports
|
|
232
|
-
import { Chart } from 'react-native-chart-kit'; // Need to detect this
|
|
233
|
-
```
|
|
234
|
-
**Problems:**
|
|
235
|
-
- Regex parsing is fragile
|
|
236
|
-
- Need to parse TypeScript AST (complex)
|
|
237
|
-
- Slow for large features
|
|
238
|
-
- Can't detect dynamic imports
|
|
239
|
-
- Can't determine versions
|
|
240
|
-
|
|
241
|
-
**Option B: Add dependencies to recipe.json** ✅
|
|
242
|
-
```json
|
|
243
|
-
{
|
|
244
|
-
"name": "charts",
|
|
245
|
-
"dependencies": {
|
|
246
|
-
"npm": {
|
|
247
|
-
"react-native-chart-kit": "^6.12.0",
|
|
248
|
-
"react-native-svg": "^13.9.0"
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
**Advantages:**
|
|
254
|
-
- Explicit and clear
|
|
255
|
-
- Recipe creator knows exact versions
|
|
256
|
-
- Fast (no parsing needed)
|
|
257
|
-
- Can specify peer dependencies
|
|
258
|
-
- Can add install instructions
|
|
259
|
-
|
|
260
|
-
**Decision: Use Option B** - Recipe creators must specify dependencies manually.
|
|
261
|
-
|
|
262
|
-
### Technical Approach
|
|
263
|
-
|
|
264
|
-
#### Step 2.1: Update recipe.json schema
|
|
265
|
-
|
|
266
|
-
**File:** `src/commands/add.ts`
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
interface RecipeManifest {
|
|
270
|
-
name: string;
|
|
271
|
-
version: string;
|
|
272
|
-
description: string;
|
|
273
|
-
copy: Array<{ from: string; to: string }>;
|
|
274
|
-
nav: { href: string; label: string };
|
|
275
|
-
target: 'native' | 'web';
|
|
276
|
-
|
|
277
|
-
// NEW: Dependencies
|
|
278
|
-
dependencies?: {
|
|
279
|
-
npm?: Record<string, string>;
|
|
280
|
-
expo?: Record<string, string>;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
// NEW: Post-install instructions
|
|
284
|
-
postInstall?: {
|
|
285
|
-
message?: string;
|
|
286
|
-
commands?: string[];
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
#### Step 2.2: Detect package manager
|
|
292
|
-
|
|
293
|
-
**File:** `src/core/packageManager.ts`
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { exists } from './fsx.js';
|
|
297
|
-
import { join } from 'path';
|
|
298
|
-
|
|
299
|
-
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
300
|
-
|
|
301
|
-
export async function detectPackageManager(cwd: string): Promise<PackageManager> {
|
|
302
|
-
// Check lock files
|
|
303
|
-
if (await exists(join(cwd, 'yarn.lock'))) {
|
|
304
|
-
return 'yarn';
|
|
305
|
-
}
|
|
306
|
-
if (await exists(join(cwd, 'pnpm-lock.yaml'))) {
|
|
307
|
-
return 'pnpm';
|
|
308
|
-
}
|
|
309
|
-
if (await exists(join(cwd, 'bun.lockb'))) {
|
|
310
|
-
return 'bun';
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Default to npm
|
|
314
|
-
return 'npm';
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function getInstallCommand(
|
|
318
|
-
pm: PackageManager,
|
|
319
|
-
packages: string[]
|
|
320
|
-
): string {
|
|
321
|
-
const pkgList = packages.join(' ');
|
|
322
|
-
|
|
323
|
-
switch (pm) {
|
|
324
|
-
case 'yarn':
|
|
325
|
-
return `yarn add ${pkgList}`;
|
|
326
|
-
case 'pnpm':
|
|
327
|
-
return `pnpm add ${pkgList}`;
|
|
328
|
-
case 'bun':
|
|
329
|
-
return `bun add ${pkgList}`;
|
|
330
|
-
default:
|
|
331
|
-
return `npm install ${pkgList}`;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**Gotcha:** Monorepos might have multiple package.json files. We need to install in the right workspace.
|
|
337
|
-
|
|
338
|
-
#### Step 2.3: Install packages function
|
|
339
|
-
|
|
340
|
-
**File:** `src/core/packageManager.ts`
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
import { executeBash } from './shell.js';
|
|
344
|
-
|
|
345
|
-
export async function installPackages(
|
|
346
|
-
packages: Record<string, string>,
|
|
347
|
-
cwd: string
|
|
348
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
349
|
-
const pm = await detectPackageManager(cwd);
|
|
350
|
-
|
|
351
|
-
// Convert to array of package@version
|
|
352
|
-
const pkgList = Object.entries(packages).map(
|
|
353
|
-
([name, version]) => `${name}@${version}`
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
const command = getInstallCommand(pm, pkgList);
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
log.info(`Running: ${command}`);
|
|
360
|
-
const result = await executeBash(command, { cwd });
|
|
361
|
-
|
|
362
|
-
if (result.exitCode !== 0) {
|
|
363
|
-
return {
|
|
364
|
-
success: false,
|
|
365
|
-
error: result.stderr || 'Installation failed'
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return { success: true };
|
|
370
|
-
} catch (error: any) {
|
|
371
|
-
return {
|
|
372
|
-
success: false,
|
|
373
|
-
error: error.message
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
**File:** `src/core/shell.ts` (NEW)
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
import { exec } from 'child_process';
|
|
383
|
-
import { promisify } from 'util';
|
|
384
|
-
|
|
385
|
-
const execAsync = promisify(exec);
|
|
386
|
-
|
|
387
|
-
export async function executeBash(
|
|
388
|
-
command: string,
|
|
389
|
-
options?: { cwd?: string }
|
|
390
|
-
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
391
|
-
try {
|
|
392
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
393
|
-
cwd: options?.cwd,
|
|
394
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
return { stdout, stderr, exitCode: 0 };
|
|
398
|
-
} catch (error: any) {
|
|
399
|
-
return {
|
|
400
|
-
stdout: error.stdout || '',
|
|
401
|
-
stderr: error.stderr || '',
|
|
402
|
-
exitCode: error.code || 1,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
#### Step 2.4: Update add command to handle dependencies
|
|
410
|
-
|
|
411
|
-
**File:** `src/commands/add.ts`
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
// After copying files and adding watermarks
|
|
415
|
-
|
|
416
|
-
// Handle dependencies
|
|
417
|
-
if (manifest.dependencies && !options.dryRun) {
|
|
418
|
-
const npmDeps = manifest.dependencies.npm || {};
|
|
419
|
-
const expoDeps = manifest.dependencies.expo || {};
|
|
420
|
-
const allDeps = { ...npmDeps, ...expoDeps };
|
|
421
|
-
|
|
422
|
-
if (Object.keys(allDeps).length > 0) {
|
|
423
|
-
log.plain('');
|
|
424
|
-
log.warn('⚠ This feature requires additional packages:');
|
|
425
|
-
log.plain('');
|
|
426
|
-
|
|
427
|
-
Object.entries(allDeps).forEach(([pkg, version]) => {
|
|
428
|
-
log.plain(` • ${pkg}@${version}`);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
log.plain('');
|
|
432
|
-
|
|
433
|
-
// Detect package manager
|
|
434
|
-
const pm = await detectPackageManager(paths.cwd);
|
|
435
|
-
const pkgList = Object.entries(allDeps).map(
|
|
436
|
-
([name, version]) => `${name}@${version}`
|
|
437
|
-
);
|
|
438
|
-
const installCmd = getInstallCommand(pm, pkgList);
|
|
439
|
-
|
|
440
|
-
log.info('📦 Install with:');
|
|
441
|
-
log.plain(` ${installCmd}`);
|
|
442
|
-
log.plain('');
|
|
443
|
-
|
|
444
|
-
// Ask if user wants to install now
|
|
445
|
-
if (!options.skipInstall && !options.yes) {
|
|
446
|
-
const shouldInstall = promptYesNo(
|
|
447
|
-
'Install packages now? (Y/n): ',
|
|
448
|
-
true // Default to yes
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
if (shouldInstall) {
|
|
452
|
-
log.info('Installing packages...');
|
|
453
|
-
const result = await installPackages(allDeps, paths.cwd);
|
|
454
|
-
|
|
455
|
-
if (result.success) {
|
|
456
|
-
log.success('✓ Packages installed successfully!');
|
|
457
|
-
} else {
|
|
458
|
-
log.error(`✗ Package installation failed: ${result.error}`);
|
|
459
|
-
log.info('You can install them manually with:');
|
|
460
|
-
log.plain(` ${installCmd}`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
} else if (options.yes) {
|
|
464
|
-
// Auto-install in --yes mode
|
|
465
|
-
log.info('Installing packages...');
|
|
466
|
-
const result = await installPackages(allDeps, paths.cwd);
|
|
467
|
-
|
|
468
|
-
if (!result.success) {
|
|
469
|
-
log.error(`✗ Package installation failed: ${result.error}`);
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Post-install instructions
|
|
477
|
-
if (manifest.postInstall && !options.dryRun) {
|
|
478
|
-
log.plain('');
|
|
479
|
-
log.info('📝 Post-install steps:');
|
|
480
|
-
|
|
481
|
-
if (manifest.postInstall.message) {
|
|
482
|
-
log.plain(` ${manifest.postInstall.message}`);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (manifest.postInstall.commands) {
|
|
486
|
-
log.plain('');
|
|
487
|
-
log.info('Run these commands:');
|
|
488
|
-
manifest.postInstall.commands.forEach(cmd => {
|
|
489
|
-
log.plain(` $ ${cmd}`);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
#### Step 2.5: Add --skip-install flag
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
export const addCommand = new Command('add')
|
|
499
|
-
.description('Add a VibeFast feature to your project')
|
|
500
|
-
.argument('<feature>', 'Feature name to install')
|
|
501
|
-
.option('--target <target>', 'Target platform (native or web)', 'native')
|
|
502
|
-
.option('--dry-run', 'Preview changes without applying')
|
|
503
|
-
.option('--force', 'Overwrite existing files without asking')
|
|
504
|
-
.option('--yes', 'Answer yes to all prompts')
|
|
505
|
-
.option('--skip-install', 'Skip package installation')
|
|
506
|
-
.action(async (feature: string, options) => {
|
|
507
|
-
// ...
|
|
508
|
-
});
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
**Testing:**
|
|
512
|
-
```bash
|
|
513
|
-
# Interactive (asks to install)
|
|
514
|
-
$ vf add charts
|
|
515
|
-
|
|
516
|
-
# Auto-install
|
|
517
|
-
$ vf add charts --yes
|
|
518
|
-
|
|
519
|
-
# Skip install
|
|
520
|
-
$ vf add charts --skip-install
|
|
521
|
-
|
|
522
|
-
# Dry run (shows packages but doesn't install)
|
|
523
|
-
$ vf add charts --dry-run
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
**Gotcha:** Monorepo workspaces - need to detect if we should install in root or workspace.
|
|
527
|
-
|
|
528
|
-
#### Step 2.6: Handle monorepo workspaces
|
|
529
|
-
|
|
530
|
-
**File:** `src/core/packageManager.ts`
|
|
531
|
-
|
|
532
|
-
```typescript
|
|
533
|
-
export async function findPackageJsonDir(startDir: string): Promise<string> {
|
|
534
|
-
let currentDir = startDir;
|
|
535
|
-
|
|
536
|
-
while (currentDir !== '/') {
|
|
537
|
-
const pkgJsonPath = join(currentDir, 'package.json');
|
|
538
|
-
|
|
539
|
-
if (await exists(pkgJsonPath)) {
|
|
540
|
-
// Check if this is a workspace root or a workspace package
|
|
541
|
-
const pkgJson = JSON.parse(await readFileContent(pkgJsonPath));
|
|
542
|
-
|
|
543
|
-
// If it has workspaces, it's the root
|
|
544
|
-
if (pkgJson.workspaces) {
|
|
545
|
-
return currentDir;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Otherwise, keep looking up
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
currentDir = dirname(currentDir);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Fallback to start directory
|
|
555
|
-
return startDir;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
export async function installPackages(
|
|
559
|
-
packages: Record<string, string>,
|
|
560
|
-
cwd: string
|
|
561
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
562
|
-
// Find the correct directory to run install in
|
|
563
|
-
const installDir = await findPackageJsonDir(cwd);
|
|
564
|
-
|
|
565
|
-
const pm = await detectPackageManager(installDir);
|
|
566
|
-
|
|
567
|
-
// ... rest of the function
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
## Phase 3: Manual Steps Support (Priority 3) 🟡
|
|
574
|
-
|
|
575
|
-
### Goal
|
|
576
|
-
For features like Sentry, PostHog, etc., show manual configuration steps.
|
|
577
|
-
|
|
578
|
-
### Technical Approach
|
|
579
|
-
|
|
580
|
-
#### Step 3.1: Extend recipe.json schema
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
interface RecipeManifest {
|
|
584
|
-
// ... existing fields
|
|
585
|
-
|
|
586
|
-
// NEW: Environment variables needed
|
|
587
|
-
env?: {
|
|
588
|
-
required?: Array<{
|
|
589
|
-
key: string;
|
|
590
|
-
description: string;
|
|
591
|
-
example: string;
|
|
592
|
-
link?: string;
|
|
593
|
-
}>;
|
|
594
|
-
optional?: Array<{
|
|
595
|
-
key: string;
|
|
596
|
-
description: string;
|
|
597
|
-
example: string;
|
|
598
|
-
default?: string;
|
|
599
|
-
}>;
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// NEW: Manual steps
|
|
603
|
-
manualSteps?: Array<{
|
|
604
|
-
step: number;
|
|
605
|
-
title: string;
|
|
606
|
-
description: string;
|
|
607
|
-
file?: string;
|
|
608
|
-
action?: 'add' | 'modify' | 'create';
|
|
609
|
-
content?: string;
|
|
610
|
-
link?: string;
|
|
611
|
-
command?: string;
|
|
612
|
-
}>;
|
|
613
|
-
}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
#### Step 3.2: Display manual steps
|
|
617
|
-
|
|
618
|
-
**File:** `src/commands/add.ts`
|
|
619
|
-
|
|
620
|
-
```typescript
|
|
621
|
-
// After installation completes
|
|
622
|
-
|
|
623
|
-
if (manifest.manualSteps && !options.dryRun) {
|
|
624
|
-
log.plain('');
|
|
625
|
-
log.warn('⚠ MANUAL STEPS REQUIRED:');
|
|
626
|
-
log.plain('');
|
|
627
|
-
log.plain('This feature requires some manual configuration:');
|
|
628
|
-
log.plain('');
|
|
629
|
-
|
|
630
|
-
manifest.manualSteps.forEach(step => {
|
|
631
|
-
log.plain('┌─────────────────────────────────────────────────────────────┐');
|
|
632
|
-
log.plain(`│ Step ${step.step}: ${step.title.padEnd(52)} │`);
|
|
633
|
-
log.plain('│ │');
|
|
634
|
-
|
|
635
|
-
// Wrap description to fit in box
|
|
636
|
-
const descLines = wrapText(step.description, 59);
|
|
637
|
-
descLines.forEach(line => {
|
|
638
|
-
log.plain(`│ ${line.padEnd(59)} │`);
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
if (step.file) {
|
|
642
|
-
log.plain('│ │');
|
|
643
|
-
log.plain(`│ 📝 File: ${step.file.padEnd(51)} │`);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (step.content) {
|
|
647
|
-
log.plain('│ │');
|
|
648
|
-
log.plain(`│ Add: ${step.content.padEnd(54)} │`);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (step.link) {
|
|
652
|
-
log.plain('│ │');
|
|
653
|
-
log.plain(`│ 🔗 ${step.link.padEnd(57)} │`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (step.command) {
|
|
657
|
-
log.plain('│ │');
|
|
658
|
-
log.plain(`│ $ ${step.command.padEnd(57)} │`);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
log.plain('└─────────────────────────────────────────────────────────────┘');
|
|
662
|
-
log.plain('');
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
log.info('💡 Tip: Run \'vf checklist ${manifest.name}\' to see these steps again.');
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Environment variables
|
|
669
|
-
if (manifest.env?.required && !options.dryRun) {
|
|
670
|
-
log.plain('');
|
|
671
|
-
log.warn('⚠ REQUIRED ENVIRONMENT VARIABLES:');
|
|
672
|
-
log.plain('');
|
|
673
|
-
|
|
674
|
-
manifest.env.required.forEach(envVar => {
|
|
675
|
-
log.plain(` ${envVar.key}`);
|
|
676
|
-
log.plain(` ${envVar.description}`);
|
|
677
|
-
log.plain(` Example: ${envVar.example}`);
|
|
678
|
-
if (envVar.link) {
|
|
679
|
-
log.plain(` Get it: ${envVar.link}`);
|
|
680
|
-
}
|
|
681
|
-
log.plain('');
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
log.info('Add these to your .env file');
|
|
685
|
-
}
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
**File:** `src/core/log.ts` (add helper)
|
|
689
|
-
|
|
690
|
-
```typescript
|
|
691
|
-
export function wrapText(text: string, maxWidth: number): string[] {
|
|
692
|
-
const words = text.split(' ');
|
|
693
|
-
const lines: string[] = [];
|
|
694
|
-
let currentLine = '';
|
|
695
|
-
|
|
696
|
-
words.forEach(word => {
|
|
697
|
-
if ((currentLine + word).length <= maxWidth) {
|
|
698
|
-
currentLine += (currentLine ? ' ' : '') + word;
|
|
699
|
-
} else {
|
|
700
|
-
if (currentLine) lines.push(currentLine);
|
|
701
|
-
currentLine = word;
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
if (currentLine) lines.push(currentLine);
|
|
706
|
-
|
|
707
|
-
return lines;
|
|
708
|
-
}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
#### Step 3.3: Add vf checklist command
|
|
712
|
-
|
|
713
|
-
**File:** `src/commands/checklist.ts` (NEW)
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
import { Command } from 'commander';
|
|
717
|
-
import { log } from '../core/log.js';
|
|
718
|
-
import { getPaths } from '../core/paths.js';
|
|
719
|
-
import { getEntry } from '../core/journal.js';
|
|
720
|
-
|
|
721
|
-
export const checklistCommand = new Command('checklist')
|
|
722
|
-
.description('Show manual setup steps for an installed feature')
|
|
723
|
-
.argument('<feature>', 'Feature name')
|
|
724
|
-
.action(async (feature: string) => {
|
|
725
|
-
try {
|
|
726
|
-
const paths = getPaths();
|
|
727
|
-
|
|
728
|
-
// Check if feature is installed
|
|
729
|
-
const entry = await getEntry(paths.journalFile, feature, 'native');
|
|
730
|
-
if (!entry) {
|
|
731
|
-
log.error(`${feature} is not installed`);
|
|
732
|
-
log.info('Run "vf list" to see installed features');
|
|
733
|
-
process.exit(1);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Problem: We don't have the recipe manifest in the journal!
|
|
737
|
-
// Solution: Store it in the journal when installing
|
|
738
|
-
|
|
739
|
-
if (!entry.manifest?.manualSteps) {
|
|
740
|
-
log.info(`${feature} has no manual setup steps`);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Display the steps (same code as in add.ts)
|
|
745
|
-
log.info(`Manual setup steps for ${feature}:`);
|
|
746
|
-
log.plain('');
|
|
747
|
-
|
|
748
|
-
// ... display logic
|
|
749
|
-
|
|
750
|
-
} catch (error: any) {
|
|
751
|
-
log.error(`Failed to show checklist: ${error.message}`);
|
|
752
|
-
process.exit(1);
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
**Gotcha:** We need to store the manifest in the journal so we can show it later.
|
|
758
|
-
|
|
759
|
-
#### Step 3.4: Update journal to store manifest
|
|
760
|
-
|
|
761
|
-
**File:** `src/core/journal.ts`
|
|
762
|
-
|
|
763
|
-
```typescript
|
|
764
|
-
export interface JournalEntry {
|
|
765
|
-
feature: string;
|
|
766
|
-
target: 'native' | 'web';
|
|
767
|
-
files: string[];
|
|
768
|
-
insertedNav: boolean;
|
|
769
|
-
navHref?: string;
|
|
770
|
-
navLabel?: string;
|
|
771
|
-
ts: number;
|
|
772
|
-
|
|
773
|
-
// NEW: Store manifest for later reference
|
|
774
|
-
manifest?: {
|
|
775
|
-
version: string;
|
|
776
|
-
manualSteps?: any[];
|
|
777
|
-
env?: any;
|
|
778
|
-
postInstall?: any;
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
**File:** `src/commands/add.ts`
|
|
784
|
-
|
|
785
|
-
```typescript
|
|
786
|
-
// Update journal
|
|
787
|
-
if (!options.dryRun) {
|
|
788
|
-
await addEntry(paths.journalFile, {
|
|
789
|
-
feature: manifest.name,
|
|
790
|
-
target: manifest.target,
|
|
791
|
-
files: copiedFiles,
|
|
792
|
-
insertedNav: navInserted,
|
|
793
|
-
navHref,
|
|
794
|
-
navLabel,
|
|
795
|
-
ts: Date.now(),
|
|
796
|
-
|
|
797
|
-
// NEW: Store relevant manifest data
|
|
798
|
-
manifest: {
|
|
799
|
-
version: manifest.version,
|
|
800
|
-
manualSteps: manifest.manualSteps,
|
|
801
|
-
env: manifest.env,
|
|
802
|
-
postInstall: manifest.postInstall,
|
|
803
|
-
},
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
---
|
|
810
|
-
|
|
811
|
-
## Phase 4: Modification Detection (Priority 4) 🟡
|
|
812
|
-
|
|
813
|
-
### Goal
|
|
814
|
-
Warn users before deleting files they've modified.
|
|
815
|
-
|
|
816
|
-
### Technical Approach
|
|
817
|
-
|
|
818
|
-
#### Step 4.1: Hash files during installation
|
|
819
|
-
|
|
820
|
-
**File:** `src/core/hash.ts` (NEW)
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
823
|
-
import { createHash } from 'crypto';
|
|
824
|
-
import { readFile } from 'fs/promises';
|
|
825
|
-
|
|
826
|
-
export async function hashFile(filePath: string): Promise<string> {
|
|
827
|
-
try {
|
|
828
|
-
const content = await readFile(filePath);
|
|
829
|
-
return createHash('sha256').update(content).digest('hex');
|
|
830
|
-
} catch (error) {
|
|
831
|
-
// File doesn't exist or can't be read
|
|
832
|
-
return '';
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
export async function hashFiles(
|
|
837
|
-
filePaths: string[]
|
|
838
|
-
): Promise<Map<string, string>> {
|
|
839
|
-
const hashes = new Map<string, string>();
|
|
840
|
-
|
|
841
|
-
await Promise.all(
|
|
842
|
-
filePaths.map(async (path) => {
|
|
843
|
-
const hash = await hashFile(path);
|
|
844
|
-
hashes.set(path, hash);
|
|
845
|
-
})
|
|
846
|
-
);
|
|
847
|
-
|
|
848
|
-
return hashes;
|
|
849
|
-
}
|
|
850
|
-
```
|
|
851
|
-
|
|
852
|
-
#### Step 4.2: Update journal to store hashes
|
|
853
|
-
|
|
854
|
-
**File:** `src/core/journal.ts`
|
|
855
|
-
|
|
856
|
-
```typescript
|
|
857
|
-
export interface JournalEntry {
|
|
858
|
-
feature: string;
|
|
859
|
-
target: 'native' | 'web';
|
|
860
|
-
|
|
861
|
-
// CHANGE: files is now an array of objects with hashes
|
|
862
|
-
files: Array<{
|
|
863
|
-
path: string;
|
|
864
|
-
hash: string;
|
|
865
|
-
}>;
|
|
866
|
-
|
|
867
|
-
insertedNav: boolean;
|
|
868
|
-
navHref?: string;
|
|
869
|
-
navLabel?: string;
|
|
870
|
-
ts: number;
|
|
871
|
-
manifest?: any;
|
|
872
|
-
}
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
**Gotcha:** This is a breaking change! Need migration for existing journals.
|
|
876
|
-
|
|
877
|
-
#### Step 4.3: Add migration for old journals
|
|
878
|
-
|
|
879
|
-
**File:** `src/core/journal.ts`
|
|
880
|
-
|
|
881
|
-
```typescript
|
|
882
|
-
export async function readJournal(path: string): Promise<Journal> {
|
|
883
|
-
try {
|
|
884
|
-
const content = await readFileContent(path);
|
|
885
|
-
const journal = JSON.parse(content) as Journal;
|
|
886
|
-
|
|
887
|
-
// Migrate old format to new format
|
|
888
|
-
journal.entries = await Promise.all(
|
|
889
|
-
journal.entries.map(async (entry) => {
|
|
890
|
-
// Check if files is old format (array of strings)
|
|
891
|
-
if (entry.files.length > 0 && typeof entry.files[0] === 'string') {
|
|
892
|
-
// Migrate to new format
|
|
893
|
-
const fileHashes = await hashFiles(entry.files as any);
|
|
894
|
-
|
|
895
|
-
entry.files = Array.from(fileHashes.entries()).map(([path, hash]) => ({
|
|
896
|
-
path,
|
|
897
|
-
hash,
|
|
898
|
-
}));
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
return entry;
|
|
902
|
-
})
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
return journal;
|
|
906
|
-
} catch (err: any) {
|
|
907
|
-
if (err.code === 'ENOENT') {
|
|
908
|
-
return { entries: [] };
|
|
909
|
-
}
|
|
910
|
-
throw err;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
#### Step 4.4: Hash files during installation
|
|
916
|
-
|
|
917
|
-
**File:** `src/commands/add.ts`
|
|
918
|
-
|
|
919
|
-
```typescript
|
|
920
|
-
// After copying files
|
|
921
|
-
|
|
922
|
-
// Hash all copied files
|
|
923
|
-
log.info('Computing file hashes...');
|
|
924
|
-
const fileHashes = await hashFiles(copiedFiles);
|
|
925
|
-
|
|
926
|
-
// Update journal with hashes
|
|
927
|
-
if (!options.dryRun) {
|
|
928
|
-
await addEntry(paths.journalFile, {
|
|
929
|
-
feature: manifest.name,
|
|
930
|
-
target: manifest.target,
|
|
931
|
-
files: Array.from(fileHashes.entries()).map(([path, hash]) => ({
|
|
932
|
-
path,
|
|
933
|
-
hash,
|
|
934
|
-
})),
|
|
935
|
-
insertedNav: navInserted,
|
|
936
|
-
navHref,
|
|
937
|
-
navLabel,
|
|
938
|
-
ts: Date.now(),
|
|
939
|
-
manifest: {
|
|
940
|
-
version: manifest.version,
|
|
941
|
-
manualSteps: manifest.manualSteps,
|
|
942
|
-
env: manifest.env,
|
|
943
|
-
postInstall: manifest.postInstall,
|
|
944
|
-
},
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
#### Step 4.5: Check for modifications during removal
|
|
950
|
-
|
|
951
|
-
**File:** `src/commands/remove.ts`
|
|
952
|
-
|
|
953
|
-
```typescript
|
|
954
|
-
// After getting journal entry
|
|
955
|
-
|
|
956
|
-
// Check for modifications
|
|
957
|
-
log.info('Checking for file modifications...');
|
|
958
|
-
|
|
959
|
-
const modifiedFiles: Array<{ path: string; status: 'modified' | 'deleted' }> = [];
|
|
960
|
-
|
|
961
|
-
for (const fileEntry of entry.files) {
|
|
962
|
-
const currentHash = await hashFile(fileEntry.path);
|
|
963
|
-
|
|
964
|
-
if (currentHash === '') {
|
|
965
|
-
// File was deleted by user
|
|
966
|
-
modifiedFiles.push({ path: fileEntry.path, status: 'deleted' });
|
|
967
|
-
} else if (currentHash !== fileEntry.hash) {
|
|
968
|
-
// File was modified by user
|
|
969
|
-
modifiedFiles.push({ path: fileEntry.path, status: 'modified' });
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Warn about modifications
|
|
974
|
-
if (modifiedFiles.length > 0) {
|
|
975
|
-
log.plain('');
|
|
976
|
-
log.warn('⚠ WARNING: The following files were changed:');
|
|
977
|
-
log.plain('');
|
|
978
|
-
|
|
979
|
-
const modified = modifiedFiles.filter(f => f.status === 'modified');
|
|
980
|
-
const deleted = modifiedFiles.filter(f => f.status === 'deleted');
|
|
981
|
-
|
|
982
|
-
if (modified.length > 0) {
|
|
983
|
-
log.plain(' Modified by you:');
|
|
984
|
-
modified.slice(0, 10).forEach(f => {
|
|
985
|
-
const relativePath = f.path.replace(paths.cwd + '/', '');
|
|
986
|
-
log.plain(` • ${relativePath}`);
|
|
987
|
-
});
|
|
988
|
-
if (modified.length > 10) {
|
|
989
|
-
log.plain(` ... and ${modified.length - 10} more`);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
if (deleted.length > 0) {
|
|
994
|
-
log.plain('');
|
|
995
|
-
log.plain(' Already deleted:');
|
|
996
|
-
deleted.slice(0, 5).forEach(f => {
|
|
997
|
-
const relativePath = f.path.replace(paths.cwd + '/', '');
|
|
998
|
-
log.plain(` • ${relativePath}`);
|
|
999
|
-
});
|
|
1000
|
-
if (deleted.length > 5) {
|
|
1001
|
-
log.plain(` ... and ${deleted.length - 5} more`);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
log.plain('');
|
|
1006
|
-
log.warn('⚠ Your changes will be LOST if you continue!');
|
|
1007
|
-
log.info('💡 Make sure you have committed to Git.');
|
|
1008
|
-
log.plain('');
|
|
1009
|
-
|
|
1010
|
-
if (!options.force && !options.yes) {
|
|
1011
|
-
const shouldContinue = promptYesNo(
|
|
1012
|
-
'Continue with removal? (y/N): ',
|
|
1013
|
-
false
|
|
1014
|
-
);
|
|
1015
|
-
|
|
1016
|
-
if (!shouldContinue) {
|
|
1017
|
-
log.info('Removal cancelled');
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
// Continue with deletion...
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
**Performance Gotcha:** Hashing large files can be slow. Need to show progress.
|
|
1027
|
-
|
|
1028
|
-
#### Step 4.6: Add progress indicator
|
|
1029
|
-
|
|
1030
|
-
**File:** `src/core/hash.ts`
|
|
1031
|
-
|
|
1032
|
-
```typescript
|
|
1033
|
-
export async function hashFiles(
|
|
1034
|
-
filePaths: string[],
|
|
1035
|
-
options?: { showProgress?: boolean }
|
|
1036
|
-
): Promise<Map<string, string>> {
|
|
1037
|
-
const hashes = new Map<string, string>();
|
|
1038
|
-
|
|
1039
|
-
if (options?.showProgress && filePaths.length > 10) {
|
|
1040
|
-
const { log } = await import('./log.js');
|
|
1041
|
-
log.info(`Hashing ${filePaths.length} files...`);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// Process in batches to avoid overwhelming the system
|
|
1045
|
-
const batchSize = 10;
|
|
1046
|
-
for (let i = 0; i < filePaths.length; i += batchSize) {
|
|
1047
|
-
const batch = filePaths.slice(i, i + batchSize);
|
|
1048
|
-
|
|
1049
|
-
await Promise.all(
|
|
1050
|
-
batch.map(async (path) => {
|
|
1051
|
-
const hash = await hashFile(path);
|
|
1052
|
-
hashes.set(path, hash);
|
|
1053
|
-
})
|
|
1054
|
-
);
|
|
1055
|
-
|
|
1056
|
-
if (options?.showProgress && filePaths.length > 10) {
|
|
1057
|
-
const progress = Math.min(i + batchSize, filePaths.length);
|
|
1058
|
-
process.stdout.write(`\r Progress: ${progress}/${filePaths.length}`);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
if (options?.showProgress && filePaths.length > 10) {
|
|
1063
|
-
process.stdout.write('\n');
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
return hashes;
|
|
1067
|
-
}
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
---
|
|
1071
|
-
|
|
1072
|
-
## Phase 5: Additional Commands (Priority 5) 🟢
|
|
1073
|
-
|
|
1074
|
-
### vf status command
|
|
1075
|
-
|
|
1076
|
-
**File:** `src/commands/status.ts` (NEW)
|
|
1077
|
-
|
|
1078
|
-
```typescript
|
|
1079
|
-
import { Command } from 'commander';
|
|
1080
|
-
import { log } from '../core/log.js';
|
|
1081
|
-
import { getPaths } from '../core/paths.js';
|
|
1082
|
-
import { readJournal } from '../core/journal.js';
|
|
1083
|
-
|
|
1084
|
-
export const statusCommand = new Command('status')
|
|
1085
|
-
.description('Show installed features and their status')
|
|
1086
|
-
.action(async () => {
|
|
1087
|
-
try {
|
|
1088
|
-
const paths = getPaths();
|
|
1089
|
-
const journal = await readJournal(paths.journalFile);
|
|
1090
|
-
|
|
1091
|
-
if (journal.entries.length === 0) {
|
|
1092
|
-
log.info('No features installed yet');
|
|
1093
|
-
log.info('Run "vf list" to see available features');
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
log.info('Installed features:');
|
|
1098
|
-
log.plain('');
|
|
1099
|
-
|
|
1100
|
-
for (const entry of journal.entries) {
|
|
1101
|
-
const version = entry.manifest?.version || 'unknown';
|
|
1102
|
-
log.plain(` ✓ ${entry.feature} (v${version}) - ${entry.target}`);
|
|
1103
|
-
log.plain(` Files: ${entry.files.length}`);
|
|
1104
|
-
log.plain(` Installed: ${new Date(entry.ts).toLocaleDateString()}`);
|
|
1105
|
-
|
|
1106
|
-
if (entry.manifest?.manualSteps) {
|
|
1107
|
-
log.plain(` ⚠ Has manual setup steps (run: vf checklist ${entry.feature})`);
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
log.plain('');
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
} catch (error: any) {
|
|
1114
|
-
log.error(`Failed to show status: ${error.message}`);
|
|
1115
|
-
process.exit(1);
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
```
|
|
1119
|
-
|
|
1120
|
-
---
|
|
1121
|
-
|
|
1122
|
-
## Testing Strategy
|
|
1123
|
-
|
|
1124
|
-
### Unit Tests
|
|
1125
|
-
|
|
1126
|
-
**File:** `src/core/__tests__/hash.test.ts` (NEW)
|
|
1127
|
-
|
|
1128
|
-
```typescript
|
|
1129
|
-
import { describe, it, expect } from 'vitest';
|
|
1130
|
-
import { hashFile, hashFiles } from '../hash.js';
|
|
1131
|
-
import { writeFile, unlink } from 'fs/promises';
|
|
1132
|
-
import { join } from 'path';
|
|
1133
|
-
import { tmpdir } from 'os';
|
|
1134
|
-
|
|
1135
|
-
describe('hash', () => {
|
|
1136
|
-
it('should hash file content', async () => {
|
|
1137
|
-
const testFile = join(tmpdir(), 'test-hash.txt');
|
|
1138
|
-
await writeFile(testFile, 'hello world');
|
|
1139
|
-
|
|
1140
|
-
const hash = await hashFile(testFile);
|
|
1141
|
-
|
|
1142
|
-
// SHA-256 of "hello world"
|
|
1143
|
-
expect(hash).toBe('b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9');
|
|
1144
|
-
|
|
1145
|
-
await unlink(testFile);
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
it('should detect file modifications', async () => {
|
|
1149
|
-
const testFile = join(tmpdir(), 'test-modify.txt');
|
|
1150
|
-
await writeFile(testFile, 'original');
|
|
1151
|
-
|
|
1152
|
-
const hash1 = await hashFile(testFile);
|
|
1153
|
-
|
|
1154
|
-
await writeFile(testFile, 'modified');
|
|
1155
|
-
|
|
1156
|
-
const hash2 = await hashFile(testFile);
|
|
1157
|
-
|
|
1158
|
-
expect(hash1).not.toBe(hash2);
|
|
1159
|
-
|
|
1160
|
-
await unlink(testFile);
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
### Integration Tests
|
|
1166
|
-
|
|
1167
|
-
**File:** `src/__tests__/add-with-deps.test.ts` (NEW)
|
|
1168
|
-
|
|
1169
|
-
```typescript
|
|
1170
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
1171
|
-
import { executeBash } from '../core/shell.js';
|
|
1172
|
-
import { join } from 'path';
|
|
1173
|
-
import { mkdtemp, rm } from 'fs/promises';
|
|
1174
|
-
import { tmpdir } from 'os';
|
|
1175
|
-
|
|
1176
|
-
describe('vf add with dependencies', () => {
|
|
1177
|
-
let testDir: string;
|
|
1178
|
-
|
|
1179
|
-
beforeEach(async () => {
|
|
1180
|
-
testDir = await mkdtemp(join(tmpdir(), 'vf-test-'));
|
|
1181
|
-
// Setup test repo
|
|
1182
|
-
});
|
|
1183
|
-
|
|
1184
|
-
afterEach(async () => {
|
|
1185
|
-
await rm(testDir, { recursive: true, force: true });
|
|
1186
|
-
});
|
|
1187
|
-
|
|
1188
|
-
it('should show required packages', async () => {
|
|
1189
|
-
const result = await executeBash(
|
|
1190
|
-
'vf add charts --dry-run',
|
|
1191
|
-
{ cwd: testDir }
|
|
1192
|
-
);
|
|
1193
|
-
|
|
1194
|
-
expect(result.stdout).toContain('react-native-chart-kit');
|
|
1195
|
-
expect(result.stdout).toContain('Install with:');
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
|
-
it('should install packages with --yes', async () => {
|
|
1199
|
-
const result = await executeBash(
|
|
1200
|
-
'vf add charts --yes',
|
|
1201
|
-
{ cwd: testDir }
|
|
1202
|
-
);
|
|
1203
|
-
|
|
1204
|
-
expect(result.stdout).toContain('Installing packages');
|
|
1205
|
-
expect(result.stdout).toContain('Packages installed successfully');
|
|
1206
|
-
});
|
|
1207
|
-
});
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
---
|
|
1211
|
-
|
|
1212
|
-
## Rollout Plan
|
|
1213
|
-
|
|
1214
|
-
### Week 1: Core Improvements
|
|
1215
|
-
- Day 1-2: Interactive confirmation (Phase 1)
|
|
1216
|
-
- Day 3-4: Package management (Phase 2)
|
|
1217
|
-
- Day 5: Testing and bug fixes
|
|
1218
|
-
|
|
1219
|
-
### Week 2: Advanced Features
|
|
1220
|
-
- Day 1-2: Manual steps support (Phase 3)
|
|
1221
|
-
- Day 3-4: Modification detection (Phase 4)
|
|
1222
|
-
- Day 5: Additional commands (Phase 5)
|
|
1223
|
-
|
|
1224
|
-
### Week 3: Polish & Release
|
|
1225
|
-
- Day 1-2: Integration testing
|
|
1226
|
-
- Day 3: Documentation
|
|
1227
|
-
- Day 4: Beta testing with real features
|
|
1228
|
-
- Day 5: Production release
|
|
1229
|
-
|
|
1230
|
-
---
|
|
1231
|
-
|
|
1232
|
-
## Potential Issues & Solutions
|
|
1233
|
-
|
|
1234
|
-
### Issue 1: Monorepo Complexity
|
|
1235
|
-
**Problem:** Different workspaces might need different packages
|
|
1236
|
-
**Solution:** Detect workspace structure, install in correct location
|
|
1237
|
-
|
|
1238
|
-
### Issue 2: Package Installation Failures
|
|
1239
|
-
**Problem:** npm install might fail (network, permissions, etc.)
|
|
1240
|
-
**Solution:** Catch errors, show manual install command, don't fail the whole operation
|
|
1241
|
-
|
|
1242
|
-
### Issue 3: Large Files Hashing
|
|
1243
|
-
**Problem:** Hashing 100+ files can be slow
|
|
1244
|
-
**Solution:** Batch processing, progress indicator, skip large binary files
|
|
1245
|
-
|
|
1246
|
-
### Issue 4: Journal Migration
|
|
1247
|
-
**Problem:** Existing journals don't have hashes
|
|
1248
|
-
**Solution:** Auto-migrate on read, compute hashes for existing files
|
|
1249
|
-
|
|
1250
|
-
### Issue 5: Non-Interactive Environments
|
|
1251
|
-
**Problem:** CI/CD doesn't have TTY for prompts
|
|
1252
|
-
**Solution:** Check process.stdin.isTTY, default to safe behavior
|
|
1253
|
-
|
|
1254
|
-
### Issue 6: Recipe Versioning
|
|
1255
|
-
**Problem:** Recipe format changes over time
|
|
1256
|
-
**Solution:** Add schema version to recipe.json, handle migrations
|
|
1257
|
-
|
|
1258
|
-
---
|
|
1259
|
-
|
|
1260
|
-
## Recipe Creation Guidelines
|
|
1261
|
-
|
|
1262
|
-
### For Recipe Creators
|
|
1263
|
-
|
|
1264
|
-
When creating a new recipe, you MUST:
|
|
1265
|
-
|
|
1266
|
-
1. **List all dependencies explicitly**
|
|
1267
|
-
```json
|
|
1268
|
-
{
|
|
1269
|
-
"dependencies": {
|
|
1270
|
-
"npm": {
|
|
1271
|
-
"package-name": "^1.0.0"
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
```
|
|
1276
|
-
|
|
1277
|
-
2. **Add manual steps for services**
|
|
1278
|
-
```json
|
|
1279
|
-
{
|
|
1280
|
-
"manualSteps": [
|
|
1281
|
-
{
|
|
1282
|
-
"step": 1,
|
|
1283
|
-
"title": "Get API Key",
|
|
1284
|
-
"description": "Sign up and get your API key",
|
|
1285
|
-
"link": "https://service.com/signup"
|
|
1286
|
-
}
|
|
1287
|
-
]
|
|
1288
|
-
}
|
|
1289
|
-
```
|
|
1290
|
-
|
|
1291
|
-
3. **Document environment variables**
|
|
1292
|
-
```json
|
|
1293
|
-
{
|
|
1294
|
-
"env": {
|
|
1295
|
-
"required": [
|
|
1296
|
-
{
|
|
1297
|
-
"key": "API_KEY",
|
|
1298
|
-
"description": "Your API key from service.com",
|
|
1299
|
-
"example": "sk_test_123456"
|
|
1300
|
-
}
|
|
1301
|
-
]
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
```
|
|
1305
|
-
|
|
1306
|
-
4. **Test the recipe**
|
|
1307
|
-
```bash
|
|
1308
|
-
# Create test project
|
|
1309
|
-
$ git clone vibefast-starter test-project
|
|
1310
|
-
$ cd test-project
|
|
1311
|
-
|
|
1312
|
-
# Test installation
|
|
1313
|
-
$ vf add your-feature --dry-run
|
|
1314
|
-
$ vf add your-feature
|
|
1315
|
-
|
|
1316
|
-
# Verify it works
|
|
1317
|
-
$ npm run dev
|
|
1318
|
-
|
|
1319
|
-
# Test removal
|
|
1320
|
-
$ vf remove your-feature
|
|
1321
|
-
|
|
1322
|
-
# Verify clean removal
|
|
1323
|
-
$ git status
|
|
1324
|
-
```
|
|
1325
|
-
|
|
1326
|
-
---
|
|
1327
|
-
|
|
1328
|
-
## Success Metrics
|
|
1329
|
-
|
|
1330
|
-
### Before Launch
|
|
1331
|
-
- [ ] All 5 phases implemented
|
|
1332
|
-
- [ ] 90%+ test coverage
|
|
1333
|
-
- [ ] Tested with 5+ real features
|
|
1334
|
-
- [ ] Documentation complete
|
|
1335
|
-
- [ ] No critical bugs
|
|
1336
|
-
|
|
1337
|
-
### After Launch
|
|
1338
|
-
- Monitor error rates
|
|
1339
|
-
- Track user feedback
|
|
1340
|
-
- Measure installation success rate
|
|
1341
|
-
- Monitor package installation failures
|
|
1342
|
-
|
|
1343
|
-
---
|
|
1344
|
-
|
|
1345
|
-
## Summary
|
|
1346
|
-
|
|
1347
|
-
This is a **2-3 week project** with clear phases:
|
|
1348
|
-
|
|
1349
|
-
1. **Interactive confirmation** - Prevent accidental overwrites
|
|
1350
|
-
2. **Package management** - Auto-detect and install dependencies
|
|
1351
|
-
3. **Manual steps** - Guide users through service setup
|
|
1352
|
-
4. **Modification detection** - Warn before deleting user changes
|
|
1353
|
-
5. **Additional commands** - Better UX with status, checklist, etc.
|
|
1354
|
-
|
|
1355
|
-
The hardest parts are:
|
|
1356
|
-
- Monorepo package installation (need to detect workspace structure)
|
|
1357
|
-
- Journal migration (breaking change to add hashes)
|
|
1358
|
-
- Non-interactive environments (CI/CD compatibility)
|
|
1359
|
-
|
|
1360
|
-
But all are solvable with the approaches outlined above.
|