vibefast-cli 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/FEATURE-DEPENDENCY-SPEC.md +338 -0
  2. package/dist/__tests__/integration.test.d.ts +2 -0
  3. package/dist/__tests__/integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/integration.test.js +219 -0
  5. package/dist/__tests__/integration.test.js.map +1 -0
  6. package/dist/__tests__/recipes.test.d.ts +2 -0
  7. package/dist/__tests__/recipes.test.d.ts.map +1 -0
  8. package/dist/__tests__/recipes.test.js +143 -0
  9. package/dist/__tests__/recipes.test.js.map +1 -0
  10. package/dist/commands/__tests__/init.test.d.ts +2 -0
  11. package/dist/commands/__tests__/init.test.d.ts.map +1 -0
  12. package/dist/commands/__tests__/init.test.js +95 -0
  13. package/dist/commands/__tests__/init.test.js.map +1 -0
  14. package/dist/commands/__tests__/platform.test.d.ts +2 -0
  15. package/dist/commands/__tests__/platform.test.d.ts.map +1 -0
  16. package/dist/commands/__tests__/platform.test.js +123 -0
  17. package/dist/commands/__tests__/platform.test.js.map +1 -0
  18. package/dist/commands/add.d.ts.map +1 -1
  19. package/dist/commands/add.js +8 -5
  20. package/dist/commands/add.js.map +1 -1
  21. package/dist/core/journal.d.ts.map +1 -1
  22. package/dist/core/journal.js +36 -19
  23. package/dist/core/journal.js.map +1 -1
  24. package/dist/core/recipes.d.ts +1 -1
  25. package/dist/core/recipes.d.ts.map +1 -1
  26. package/dist/core/recipes.js +12 -41
  27. package/dist/core/recipes.js.map +1 -1
  28. package/package.json +1 -1
  29. package/recipes/ios-widget/recipe.json +78 -0
  30. package/recipes/ios-widget/targets/widget/AppIntent.swift +46 -0
  31. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
  32. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
  33. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
  34. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
  35. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
  36. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
  37. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
  38. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
  39. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
  40. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
  41. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
  42. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
  43. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
  44. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
  45. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  46. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
  47. package/recipes/ios-widget/targets/widget/CalorieTrackerWidget.swift +424 -0
  48. package/recipes/ios-widget/targets/widget/HabitTrackerWidget.swift +305 -0
  49. package/recipes/ios-widget/targets/widget/Info.plist +11 -0
  50. package/recipes/ios-widget/targets/widget/WidgetLiveActivity.swift +75 -0
  51. package/recipes/ios-widget/targets/widget/expo-target.config.js +10 -0
  52. package/recipes/ios-widget/targets/widget/generated.entitlements +5 -0
  53. package/recipes/ios-widget/targets/widget/index.swift +18 -0
  54. package/recipes/ios-widget/targets/widget/widgets.swift +96 -0
  55. package/recipes/ios-widget@latest.zip +0 -0
  56. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +74 -0
  57. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +25 -0
  58. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +23 -0
  59. package/recipes/payments/apps/native/src/features/payments/README.md +200 -0
  60. package/recipes/payments/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  61. package/recipes/payments/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  62. package/recipes/payments/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  63. package/recipes/payments/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  64. package/recipes/payments/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  65. package/recipes/payments/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  66. package/recipes/payments/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  67. package/recipes/payments/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  68. package/recipes/payments/apps/native/src/features/payments/index.ts +8 -0
  69. package/recipes/payments/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  70. package/recipes/payments/recipe.json +58 -0
  71. package/recipes/payments@latest.zip +0 -0
  72. package/src/__tests__/integration.test.ts +249 -0
  73. package/src/__tests__/recipes.test.ts +168 -0
  74. package/src/commands/__tests__/init.test.ts +112 -0
  75. package/src/commands/__tests__/platform.test.ts +141 -0
  76. package/src/commands/add.ts +9 -5
  77. package/src/core/journal.ts +42 -25
  78. package/src/core/recipes.ts +12 -42
@@ -0,0 +1,116 @@
1
+ import type React from 'react';
2
+ import { ActivityIndicator, ScrollView, Text, View } from 'react-native';
3
+
4
+ import type { ProductOffering } from '@/core/payments/payment-service';
5
+ import { translate } from '@/lib';
6
+ import { useThemeConfig } from '@/lib/use-theme-config';
7
+
8
+ import { PaywallErrorState } from './paywall-error-state';
9
+ import { PaywallProductCard } from './paywall-product-card';
10
+
11
+ type PaywallLocalModeProps = {
12
+ isLoading: boolean;
13
+ error: string | null;
14
+ offerings: ProductOffering[];
15
+ onPurchase: (productId: string) => void;
16
+ onRetry: () => void;
17
+ onRestorePurchases: () => void;
18
+ isPurchasing: boolean;
19
+ primaryColor: string;
20
+ };
21
+
22
+ export const PaywallLocalMode: React.FC<PaywallLocalModeProps> = ({
23
+ isLoading,
24
+ error,
25
+ offerings,
26
+ onPurchase,
27
+ onRetry,
28
+ onRestorePurchases,
29
+ isPurchasing,
30
+ primaryColor,
31
+ }) => {
32
+ const theme = useThemeConfig();
33
+
34
+ return (
35
+ <View className="flex-1">
36
+ <View className="mb-8 items-center px-4">
37
+ <View
38
+ className="mb-6 size-32 items-center justify-center rounded-full"
39
+ style={{
40
+ backgroundColor: `${theme.colors.primary}10`,
41
+ borderWidth: 3,
42
+ borderColor: theme.colors.primary,
43
+ }}
44
+ >
45
+ <Text className="text-6xl">✨</Text>
46
+ </View>
47
+ <Text
48
+ className="mb-4 text-center text-3xl font-bold"
49
+ style={{ color: theme.colors.foreground }}
50
+ >
51
+ {translate('paywall.title')}
52
+ </Text>
53
+ <Text
54
+ className="text-center text-lg leading-6"
55
+ style={{ color: theme.colors.mutedForeground }}
56
+ >
57
+ {translate('paywall.subtitle')}
58
+ </Text>
59
+ </View>
60
+
61
+ {isLoading && (
62
+ <View className="flex-1 items-center justify-center py-20">
63
+ <ActivityIndicator size="large" color={theme.colors.primary} />
64
+ <Text
65
+ className="mt-4 text-lg"
66
+ style={{ color: theme.colors.mutedForeground }}
67
+ >
68
+ {translate('paywall.loading')}
69
+ </Text>
70
+ </View>
71
+ )}
72
+
73
+ {error && !isLoading && (
74
+ <PaywallErrorState
75
+ error={error}
76
+ onRetry={onRetry}
77
+ onRestorePurchases={onRestorePurchases}
78
+ isRestoringPurchases={isPurchasing}
79
+ />
80
+ )}
81
+
82
+ {!isLoading && !error && offerings.length === 0 && (
83
+ <View className="mx-4 mb-6">
84
+ <View
85
+ className="rounded-2xl p-6"
86
+ style={{ backgroundColor: `${theme.colors.warning}10` }}
87
+ >
88
+ <Text
89
+ className="text-center"
90
+ style={{ color: theme.colors.warning }}
91
+ >
92
+ {translate('paywall.no_offerings')}
93
+ </Text>
94
+ </View>
95
+ </View>
96
+ )}
97
+
98
+ {!isLoading && !error && offerings.length > 0 && (
99
+ <ScrollView
100
+ showsVerticalScrollIndicator={false}
101
+ contentContainerStyle={{ paddingBottom: 20 }}
102
+ >
103
+ {offerings.map((offering) => (
104
+ <PaywallProductCard
105
+ key={offering.id}
106
+ offering={offering}
107
+ onPurchase={onPurchase}
108
+ isLoading={isPurchasing}
109
+ primaryColor={primaryColor}
110
+ />
111
+ ))}
112
+ </ScrollView>
113
+ )}
114
+ </View>
115
+ );
116
+ };
@@ -0,0 +1,133 @@
1
+ import type React from 'react';
2
+ import { ActivityIndicator, Pressable, Text, View } from 'react-native';
3
+
4
+ import type { ProductOffering } from '@/core/payments/payment-service';
5
+ import { translate } from '@/lib';
6
+ import { useThemeConfig } from '@/lib/use-theme-config';
7
+
8
+ type PaywallProductCardProps = {
9
+ offering: ProductOffering;
10
+ onPurchase: (productId: string) => void;
11
+ isLoading: boolean;
12
+ primaryColor: string;
13
+ };
14
+
15
+ export const PaywallProductCard: React.FC<PaywallProductCardProps> = ({
16
+ offering,
17
+ onPurchase,
18
+ isLoading,
19
+ primaryColor,
20
+ }) => {
21
+ const theme = useThemeConfig();
22
+
23
+ return (
24
+ <View
25
+ key={offering.id}
26
+ className="mx-4 mb-6 overflow-hidden rounded-3xl"
27
+ style={{
28
+ backgroundColor: theme.colors.card,
29
+ shadowColor: theme.colors.primary,
30
+ shadowOffset: { width: 0, height: 20 },
31
+ shadowOpacity: 0.15,
32
+ shadowRadius: 40,
33
+ elevation: 8,
34
+ }}
35
+ >
36
+ {/* Popular Badge */}
37
+ <View
38
+ className="absolute right-6 top-0 z-10 rounded-b-2xl px-4 py-2"
39
+ style={{ backgroundColor: theme.colors.primary }}
40
+ >
41
+ <Text
42
+ className="text-xs font-bold"
43
+ style={{ color: theme.colors.primaryForeground }}
44
+ >
45
+ {translate('paywall.product_card.badge')}
46
+ </Text>
47
+ </View>
48
+
49
+ <View className="p-6 pt-8">
50
+ {/* Header */}
51
+ <View className="mb-6 items-center">
52
+ <View
53
+ className="mb-4 size-20 items-center justify-center rounded-full"
54
+ style={{
55
+ backgroundColor: `${primaryColor}20`,
56
+ borderWidth: 3,
57
+ borderColor: `${primaryColor}30`,
58
+ }}
59
+ >
60
+ <Text className="text-3xl">💎</Text>
61
+ </View>
62
+
63
+ <Text
64
+ className="mb-2 text-xl font-bold"
65
+ style={{ color: theme.colors.foreground }}
66
+ >
67
+ {translate('paywall.product_card.title')}
68
+ </Text>
69
+
70
+ <Text className="text-3xl font-black" style={{ color: primaryColor }}>
71
+ {offering.priceString}
72
+ </Text>
73
+ </View>
74
+
75
+ {/* Features */}
76
+ <View className="mb-6 space-y-3">
77
+ <View className="flex-row items-center">
78
+ <Text className="mr-2 text-base">✨</Text>
79
+ <Text
80
+ className="text-base"
81
+ style={{ color: theme.colors.mutedForeground }}
82
+ >
83
+ {translate('paywall.product_card.features.ai_generations')}
84
+ </Text>
85
+ </View>
86
+ <View className="flex-row items-center">
87
+ <Text className="mr-2 text-base">🚀</Text>
88
+ <Text
89
+ className="text-base"
90
+ style={{ color: theme.colors.mutedForeground }}
91
+ >
92
+ {translate('paywall.product_card.features.priority_processing')}
93
+ </Text>
94
+ </View>
95
+ <View className="flex-row items-center">
96
+ <Text className="mr-2 text-base">💫</Text>
97
+ <Text
98
+ className="text-base"
99
+ style={{ color: theme.colors.mutedForeground }}
100
+ >
101
+ {translate('paywall.product_card.features.advanced_models')}
102
+ </Text>
103
+ </View>
104
+ </View>
105
+
106
+ {/* Purchase Button */}
107
+ <Pressable
108
+ onPress={() => onPurchase(offering.id)}
109
+ disabled={isLoading}
110
+ className="items-center justify-center rounded-2xl py-4"
111
+ style={{
112
+ backgroundColor: primaryColor,
113
+ opacity: isLoading ? 0.7 : 1,
114
+ }}
115
+ >
116
+ {isLoading ? (
117
+ <ActivityIndicator
118
+ size="small"
119
+ color={theme.colors.primaryForeground}
120
+ />
121
+ ) : (
122
+ <Text
123
+ className="text-lg font-bold"
124
+ style={{ color: theme.colors.primaryForeground }}
125
+ >
126
+ {translate('paywall.product_card.cta')}
127
+ </Text>
128
+ )}
129
+ </Pressable>
130
+ </View>
131
+ </View>
132
+ );
133
+ };
@@ -0,0 +1,146 @@
1
+ import type React from 'react';
2
+ import { ActivityIndicator, Pressable, Text, View } from 'react-native';
3
+
4
+ import { translate } from '@/lib';
5
+ import { useThemeConfig } from '@/lib/use-theme-config';
6
+
7
+ type PaywallRemoteModeProps = {
8
+ onPresentPaywall: () => void;
9
+ isPurchasing: boolean;
10
+ };
11
+
12
+ export const PaywallRemoteMode: React.FC<PaywallRemoteModeProps> = ({
13
+ onPresentPaywall,
14
+ isPurchasing,
15
+ }) => {
16
+ const theme = useThemeConfig();
17
+
18
+ return (
19
+ <View className="flex-1 px-4">
20
+ <View className="mb-8 items-center">
21
+ <View
22
+ className="mb-6 size-32 items-center justify-center rounded-full"
23
+ style={{
24
+ backgroundColor: `${theme.colors.primary}10`,
25
+ borderWidth: 3,
26
+ borderColor: theme.colors.primary,
27
+ }}
28
+ >
29
+ <Text className="text-6xl">🎨</Text>
30
+ </View>
31
+ <Text
32
+ className="mb-4 text-center text-3xl font-bold"
33
+ style={{ color: theme.colors.foreground }}
34
+ >
35
+ {translate('paywall.remote.hero_title')}
36
+ </Text>
37
+ <Text
38
+ className="mb-8 text-center text-lg leading-6"
39
+ style={{ color: theme.colors.mutedForeground }}
40
+ >
41
+ {translate('paywall.remote.hero_subtitle')}
42
+ </Text>
43
+ </View>
44
+
45
+ <View
46
+ className="mb-6 rounded-3xl p-8"
47
+ style={{
48
+ backgroundColor: theme.colors.card,
49
+ shadowColor: theme.colors.primary,
50
+ shadowOffset: { width: 0, height: 20 },
51
+ shadowOpacity: 0.1,
52
+ shadowRadius: 30,
53
+ elevation: 8,
54
+ }}
55
+ >
56
+ <View className="mb-6 items-center">
57
+ <Text
58
+ className="mb-2 text-xl font-semibold"
59
+ style={{ color: theme.colors.foreground }}
60
+ >
61
+ {translate('paywall.remote.features_title')}
62
+ </Text>
63
+ <View
64
+ className="h-1 w-16 rounded-full"
65
+ style={{ backgroundColor: theme.colors.primary }}
66
+ />
67
+ </View>
68
+
69
+ <View className="gap-y-4">
70
+ <View className="flex-row items-center">
71
+ <View
72
+ className="mr-3 size-8 items-center justify-center rounded-full"
73
+ style={{ backgroundColor: `${theme.colors.success}20` }}
74
+ >
75
+ <Text className="text-sm">🎨</Text>
76
+ </View>
77
+ <Text style={{ color: theme.colors.mutedForeground }}>
78
+ {translate('paywall.remote.features.native_ui')}
79
+ </Text>
80
+ </View>
81
+ <View className="flex-row items-center">
82
+ <View
83
+ className="mr-3 size-8 items-center justify-center rounded-full"
84
+ style={{ backgroundColor: `${theme.colors.success}20` }}
85
+ >
86
+ <Text className="text-sm">🔄</Text>
87
+ </View>
88
+ <Text style={{ color: theme.colors.mutedForeground }}>
89
+ {translate('paywall.remote.features.realtime_updates')}
90
+ </Text>
91
+ </View>
92
+ <View className="flex-row items-center">
93
+ <View
94
+ className="mr-3 size-8 items-center justify-center rounded-full"
95
+ style={{ backgroundColor: `${theme.colors.success}20` }}
96
+ >
97
+ <Text className="text-sm">📱</Text>
98
+ </View>
99
+ <Text style={{ color: theme.colors.mutedForeground }}>
100
+ {translate('paywall.remote.features.mobile_optimized')}
101
+ </Text>
102
+ </View>
103
+ </View>
104
+ </View>
105
+
106
+ <Pressable
107
+ onPress={onPresentPaywall}
108
+ disabled={isPurchasing}
109
+ className="mb-4 items-center justify-center rounded-3xl py-6"
110
+ style={{
111
+ backgroundColor: isPurchasing
112
+ ? theme.colors.muted
113
+ : theme.colors.primary,
114
+ opacity: isPurchasing ? 0.6 : 1,
115
+ shadowColor: theme.colors.primary,
116
+ shadowOffset: { width: 0, height: 8 },
117
+ shadowOpacity: 0.3,
118
+ shadowRadius: 16,
119
+ elevation: 8,
120
+ }}
121
+ >
122
+ {isPurchasing ? (
123
+ <View className="flex-row items-center">
124
+ <ActivityIndicator
125
+ size="small"
126
+ color={theme.colors.primaryForeground}
127
+ />
128
+ <Text
129
+ className="ml-3 text-lg font-bold"
130
+ style={{ color: theme.colors.primaryForeground }}
131
+ >
132
+ {translate('common.loading')}
133
+ </Text>
134
+ </View>
135
+ ) : (
136
+ <Text
137
+ className="text-xl font-bold"
138
+ style={{ color: theme.colors.primaryForeground }}
139
+ >
140
+ {translate('paywall.remote.cta')}
141
+ </Text>
142
+ )}
143
+ </Pressable>
144
+ </View>
145
+ );
146
+ };
@@ -0,0 +1,63 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { RevenueCatAdapter } from '@/features/payments/services/revenuecat-adapter';
4
+
5
+ /**
6
+ * Hook to check if a user has a specific entitlement
7
+ * @param entitlementId - The entitlement identifier to check
8
+ * @returns Object containing entitlement status, loading state, and any error
9
+ */
10
+ export const useEntitlement = (entitlementId: string) => {
11
+ const [isEntitled, setIsEntitled] = useState(false);
12
+ const [isLoading, setIsLoading] = useState(true);
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ let isMounted = true;
17
+
18
+ const checkEntitlement = async () => {
19
+ if (!entitlementId) {
20
+ if (!isMounted) return;
21
+ setIsEntitled(false);
22
+ setIsLoading(false);
23
+ return;
24
+ }
25
+
26
+ try {
27
+ if (!isMounted) return;
28
+ setIsLoading(true);
29
+ setError(null);
30
+
31
+ if (!RevenueCatAdapter.getIsInitialized()) {
32
+ await RevenueCatAdapter.initialize();
33
+ }
34
+
35
+ const paymentService = new RevenueCatAdapter();
36
+ const { isActive } =
37
+ await paymentService.getUserSubscriptionStatus(entitlementId);
38
+ if (!isMounted) return;
39
+ setIsEntitled(isActive);
40
+ } catch (e) {
41
+ if (!isMounted) return;
42
+ setIsEntitled(false); // Default to not entitled on error
43
+ setError(
44
+ e instanceof Error ? e.message : 'Failed to check entitlement',
45
+ );
46
+ } finally {
47
+ if (!isMounted) return;
48
+ setIsLoading(false);
49
+ }
50
+ };
51
+
52
+ checkEntitlement();
53
+
54
+ // Optional: Add listener for customer info updates from RevenueCat
55
+ // This would require implementing a listener mechanism in RevenueCatAdapter
56
+ // For now, we'll rely on component re-mounting or manual refresh
57
+ return () => {
58
+ isMounted = false;
59
+ };
60
+ }, [entitlementId]);
61
+
62
+ return { isEntitled, isLoading, error };
63
+ };
@@ -0,0 +1,8 @@
1
+ // Components
2
+ export { PaymentInitializer } from './components/payment-initializer';
3
+
4
+ // Hooks
5
+ export { useEntitlement } from './hooks/use-entitlement';
6
+
7
+ // Services
8
+ export { RevenueCatAdapter } from './services/revenuecat-adapter';