vibefast-cli 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FEATURE-DEPENDENCY-SPEC.md +338 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +219 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/recipes.test.d.ts +2 -0
- package/dist/__tests__/recipes.test.d.ts.map +1 -0
- package/dist/__tests__/recipes.test.js +143 -0
- package/dist/__tests__/recipes.test.js.map +1 -0
- package/dist/commands/__tests__/init.test.d.ts +2 -0
- package/dist/commands/__tests__/init.test.d.ts.map +1 -0
- package/dist/commands/__tests__/init.test.js +95 -0
- package/dist/commands/__tests__/init.test.js.map +1 -0
- package/dist/commands/__tests__/platform.test.d.ts +2 -0
- package/dist/commands/__tests__/platform.test.d.ts.map +1 -0
- package/dist/commands/__tests__/platform.test.js +123 -0
- package/dist/commands/__tests__/platform.test.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +4 -5
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -12
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/platform.d.ts +3 -0
- package/dist/commands/platform.d.ts.map +1 -0
- package/dist/commands/platform.js +245 -0
- package/dist/commands/platform.js.map +1 -0
- package/dist/core/journal.d.ts.map +1 -1
- package/dist/core/journal.js +36 -19
- package/dist/core/journal.js.map +1 -1
- package/dist/core/recipes.d.ts.map +1 -1
- package/dist/core/recipes.js +8 -39
- package/dist/core/recipes.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/recipes/ios-widget/recipe.json +78 -0
- package/recipes/ios-widget/targets/widget/AppIntent.swift +46 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
- package/recipes/ios-widget/targets/widget/CalorieTrackerWidget.swift +424 -0
- package/recipes/ios-widget/targets/widget/HabitTrackerWidget.swift +305 -0
- package/recipes/ios-widget/targets/widget/Info.plist +11 -0
- package/recipes/ios-widget/targets/widget/WidgetLiveActivity.swift +75 -0
- package/recipes/ios-widget/targets/widget/expo-target.config.js +10 -0
- package/recipes/ios-widget/targets/widget/generated.entitlements +5 -0
- package/recipes/ios-widget/targets/widget/index.swift +18 -0
- package/recipes/ios-widget/targets/widget/widgets.swift +96 -0
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +74 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +25 -0
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +23 -0
- package/recipes/payments/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments/recipe.json +58 -0
- package/recipes/payments@latest.zip +0 -0
- package/src/__tests__/integration.test.ts +249 -0
- package/src/__tests__/recipes.test.ts +168 -0
- package/src/commands/__tests__/init.test.ts +112 -0
- package/src/commands/__tests__/platform.test.ts +141 -0
- package/src/commands/add.ts +4 -5
- package/src/commands/init.ts +14 -15
- package/src/commands/platform.ts +309 -0
- package/src/core/journal.ts +42 -25
- package/src/core/recipes.ts +8 -40
- package/src/index.ts +2 -0
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { log } from '../core/log.js';
|
|
3
3
|
import { withSpinner } from '../core/spinner.js';
|
|
4
|
-
import {
|
|
4
|
+
import { promptSelectAsync, promptMultiSelectAsync, promptUser, promptYesNo } from '../core/prompt.js';
|
|
5
5
|
import { detectPackageManager } from '../core/detect.js';
|
|
6
6
|
import simpleGit from 'simple-git';
|
|
7
7
|
import { existsSync } from 'fs';
|
|
@@ -42,17 +42,17 @@ export const initCommand = new Command('init')
|
|
|
42
42
|
let selectedFeatures: string[] = [];
|
|
43
43
|
|
|
44
44
|
if (!options.yes) {
|
|
45
|
-
starterType = await
|
|
45
|
+
starterType = await promptSelectAsync(
|
|
46
46
|
'How would you like to start?',
|
|
47
47
|
[
|
|
48
48
|
{
|
|
49
|
-
name: 'Clean',
|
|
50
49
|
value: 'clean',
|
|
50
|
+
label: 'Clean',
|
|
51
51
|
description: 'Minimal setup - add features later with vf add',
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
|
-
name: 'Custom',
|
|
55
54
|
value: 'custom',
|
|
55
|
+
label: 'Custom',
|
|
56
56
|
description: 'Choose features now - one-shot setup',
|
|
57
57
|
},
|
|
58
58
|
]
|
|
@@ -100,22 +100,22 @@ export const initCommand = new Command('init')
|
|
|
100
100
|
let platforms: string[] = ['native', 'web'];
|
|
101
101
|
|
|
102
102
|
if (!options.yes) {
|
|
103
|
-
const platformChoice = await
|
|
103
|
+
const platformChoice = await promptSelectAsync(
|
|
104
104
|
'Which platforms do you want to include?',
|
|
105
105
|
[
|
|
106
106
|
{
|
|
107
|
-
name: 'Both (Native + Web)',
|
|
108
107
|
value: 'both',
|
|
108
|
+
label: 'Both (Native + Web)',
|
|
109
109
|
description: 'Full monorepo with mobile and web app',
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
|
-
name: 'Native Only (Expo)',
|
|
113
112
|
value: 'native',
|
|
113
|
+
label: 'Native Only (Expo)',
|
|
114
114
|
description: 'Mobile app only (iOS & Android)',
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
name: 'Web Only (Next.js)',
|
|
118
117
|
value: 'web',
|
|
118
|
+
label: 'Web Only (Next.js)',
|
|
119
119
|
description: 'Web app only',
|
|
120
120
|
},
|
|
121
121
|
]
|
|
@@ -142,10 +142,9 @@ export const initCommand = new Command('init')
|
|
|
142
142
|
let projectName = defaultName;
|
|
143
143
|
|
|
144
144
|
if (!options.yes) {
|
|
145
|
-
projectName = await
|
|
146
|
-
'Project name:'
|
|
147
|
-
|
|
148
|
-
);
|
|
145
|
+
projectName = await promptUser(
|
|
146
|
+
'Project name: '
|
|
147
|
+
) || defaultName;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
// Validate project name
|
|
@@ -300,7 +299,7 @@ export const initCommand = new Command('init')
|
|
|
300
299
|
log.warn('pnpm is not installed');
|
|
301
300
|
log.plain('');
|
|
302
301
|
|
|
303
|
-
const shouldInstallPnpm = options.yes || await
|
|
302
|
+
const shouldInstallPnpm = options.yes || await promptYesNo(
|
|
304
303
|
'Would you like to install pnpm globally?',
|
|
305
304
|
true
|
|
306
305
|
);
|
|
@@ -383,7 +382,7 @@ export const initCommand = new Command('init')
|
|
|
383
382
|
log.plain(' • Configure Convex Auth');
|
|
384
383
|
log.plain('');
|
|
385
384
|
|
|
386
|
-
const runSetup = options.yes || await
|
|
385
|
+
const runSetup = options.yes || await promptYesNo(
|
|
387
386
|
'Would you like to run the starter setup now?',
|
|
388
387
|
true
|
|
389
388
|
);
|
|
@@ -567,7 +566,7 @@ export const initCommand = new Command('init')
|
|
|
567
566
|
process.exit(1);
|
|
568
567
|
}
|
|
569
568
|
|
|
570
|
-
licenseKey = await
|
|
569
|
+
licenseKey = await promptUser('Enter your license key: ');
|
|
571
570
|
|
|
572
571
|
if (!licenseKey || licenseKey.trim() === '') {
|
|
573
572
|
log.error('License key cannot be empty');
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { log } from '../core/log.js';
|
|
3
|
+
import { withSpinner } from '../core/spinner.js';
|
|
4
|
+
import { promptSelectAsync } from '../core/prompt.js';
|
|
5
|
+
import { existsSync, rmSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import simpleGit from 'simple-git';
|
|
8
|
+
|
|
9
|
+
interface PlatformOptions {
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const platformCommand = new Command('platform')
|
|
14
|
+
.description('Manage platforms in your VibeFast project')
|
|
15
|
+
.addCommand(
|
|
16
|
+
new Command('add')
|
|
17
|
+
.description('Add a missing platform to your project')
|
|
18
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
19
|
+
.action(async (options: PlatformOptions) => {
|
|
20
|
+
try {
|
|
21
|
+
const paths = await import('../core/paths.js').then(m => m.getPaths());
|
|
22
|
+
|
|
23
|
+
// Check if we're in a VibeFast project
|
|
24
|
+
if (!existsSync(join(paths.cwd, '.vibefast'))) {
|
|
25
|
+
log.error('Not a VibeFast project. Run this from your VibeFast project root.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check what platforms currently exist
|
|
30
|
+
const hasNative = existsSync(join(paths.cwd, 'apps/native'));
|
|
31
|
+
const hasWeb = existsSync(join(paths.cwd, 'apps/web'));
|
|
32
|
+
|
|
33
|
+
if (hasNative && hasWeb) {
|
|
34
|
+
log.info('✓ Both platforms already exist');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Determine what can be added
|
|
39
|
+
const availablePlatforms = [];
|
|
40
|
+
if (!hasNative) availablePlatforms.push({ value: 'native', label: '📱 Native (Expo)' });
|
|
41
|
+
if (!hasWeb) availablePlatforms.push({ value: 'web', label: '🌐 Web (Next.js)' });
|
|
42
|
+
|
|
43
|
+
if (availablePlatforms.length === 0) {
|
|
44
|
+
log.info('✓ All platforms already exist');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log.plain('');
|
|
49
|
+
log.info('Available platforms to add:');
|
|
50
|
+
log.plain('');
|
|
51
|
+
|
|
52
|
+
const platformToAdd = await promptSelectAsync(
|
|
53
|
+
'Which platform would you like to add?',
|
|
54
|
+
availablePlatforms
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!platformToAdd) {
|
|
58
|
+
log.info('Cancelled');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
log.plain('');
|
|
63
|
+
log.info(`Adding ${platformToAdd === 'native' ? 'Native (Expo)' : 'Web (Next.js)'} platform...`);
|
|
64
|
+
log.plain('');
|
|
65
|
+
|
|
66
|
+
// Clone the platform from the clean branch
|
|
67
|
+
const repoUrl = 'https://github.com/mzafarr/vibefast-pro.git';
|
|
68
|
+
const tempDir = join(paths.cwd, '.temp-platform-clone');
|
|
69
|
+
|
|
70
|
+
await withSpinner(
|
|
71
|
+
`Cloning ${platformToAdd} platform...`,
|
|
72
|
+
async () => {
|
|
73
|
+
const git = simpleGit();
|
|
74
|
+
const cloneOptions = ['--depth', '1', '--branch', 'clean', '--single-branch', '--sparse'];
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await git.clone(repoUrl, tempDir, cloneOptions);
|
|
78
|
+
|
|
79
|
+
// Sparse checkout only the platform we need
|
|
80
|
+
const sparseGit = simpleGit(tempDir);
|
|
81
|
+
await sparseGit.raw(['sparse-checkout', 'set', `apps/${platformToAdd}`]);
|
|
82
|
+
} catch (error: any) {
|
|
83
|
+
throw new Error(`Failed to clone platform: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
successText: `✓ Platform cloned`,
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Copy the platform to the project
|
|
92
|
+
await withSpinner(
|
|
93
|
+
`Setting up ${platformToAdd} platform...`,
|
|
94
|
+
async () => {
|
|
95
|
+
const { copyFileSync } = await import('fs');
|
|
96
|
+
const { cp } = await import('fs/promises');
|
|
97
|
+
|
|
98
|
+
const sourcePath = join(tempDir, 'apps', platformToAdd);
|
|
99
|
+
const destPath = join(paths.cwd, 'apps', platformToAdd);
|
|
100
|
+
|
|
101
|
+
if (existsSync(sourcePath)) {
|
|
102
|
+
await cp(sourcePath, destPath, { recursive: true });
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(`Platform directory not found in clone`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Update package.json workspaces
|
|
108
|
+
const packageJsonPath = join(paths.cwd, 'package.json');
|
|
109
|
+
if (existsSync(packageJsonPath)) {
|
|
110
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
111
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
112
|
+
|
|
113
|
+
if (packageJson.workspaces && !packageJson.workspaces.includes(`apps/${platformToAdd}`)) {
|
|
114
|
+
packageJson.workspaces.push(`apps/${platformToAdd}`);
|
|
115
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update turbo.json
|
|
120
|
+
const turboJsonPath = join(paths.cwd, 'turbo.json');
|
|
121
|
+
if (existsSync(turboJsonPath)) {
|
|
122
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
123
|
+
const turboJson = JSON.parse(readFileSync(turboJsonPath, 'utf-8'));
|
|
124
|
+
|
|
125
|
+
if (turboJson.pipeline) {
|
|
126
|
+
// Add platform-specific tasks if they don't exist
|
|
127
|
+
if (platformToAdd === 'native') {
|
|
128
|
+
if (!turboJson.pipeline['dev:native']) {
|
|
129
|
+
turboJson.pipeline['dev:native'] = { cache: false };
|
|
130
|
+
}
|
|
131
|
+
if (!turboJson.pipeline['native:*']) {
|
|
132
|
+
turboJson.pipeline['native:*'] = { cache: false };
|
|
133
|
+
}
|
|
134
|
+
} else if (platformToAdd === 'web') {
|
|
135
|
+
if (!turboJson.pipeline['dev:web']) {
|
|
136
|
+
turboJson.pipeline['dev:web'] = { cache: false };
|
|
137
|
+
}
|
|
138
|
+
if (!turboJson.pipeline['web:*']) {
|
|
139
|
+
turboJson.pipeline['web:*'] = { cache: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
writeFileSync(turboJsonPath, JSON.stringify(turboJson, null, 2));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clean up temp directory
|
|
148
|
+
if (existsSync(tempDir)) {
|
|
149
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
successText: `✓ Platform setup complete`,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
log.plain('');
|
|
158
|
+
log.success(`✓ ${platformToAdd === 'native' ? 'Native' : 'Web'} platform added successfully!`);
|
|
159
|
+
log.plain('');
|
|
160
|
+
log.info('Next steps:');
|
|
161
|
+
log.plain(' 1. Install dependencies:');
|
|
162
|
+
log.plain(' pnpm install');
|
|
163
|
+
log.plain('');
|
|
164
|
+
if (platformToAdd === 'native') {
|
|
165
|
+
log.plain(' 2. Start developing:');
|
|
166
|
+
log.plain(' pnpm dev:native');
|
|
167
|
+
log.plain(' pnpm native:ios');
|
|
168
|
+
log.plain(' pnpm native:android');
|
|
169
|
+
} else {
|
|
170
|
+
log.plain(' 2. Start developing:');
|
|
171
|
+
log.plain(' pnpm dev:web');
|
|
172
|
+
}
|
|
173
|
+
log.plain('');
|
|
174
|
+
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
log.plain('');
|
|
177
|
+
log.error(`Failed to add platform: ${error.message}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
.addCommand(
|
|
183
|
+
new Command('remove')
|
|
184
|
+
.description('Remove a platform from your project')
|
|
185
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
186
|
+
.action(async (options: PlatformOptions) => {
|
|
187
|
+
try {
|
|
188
|
+
const paths = await import('../core/paths.js').then(m => m.getPaths());
|
|
189
|
+
|
|
190
|
+
// Check if we're in a VibeFast project
|
|
191
|
+
if (!existsSync(join(paths.cwd, '.vibefast'))) {
|
|
192
|
+
log.error('Not a VibeFast project. Run this from your VibeFast project root.');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check what platforms currently exist
|
|
197
|
+
const hasNative = existsSync(join(paths.cwd, 'apps/native'));
|
|
198
|
+
const hasWeb = existsSync(join(paths.cwd, 'apps/web'));
|
|
199
|
+
|
|
200
|
+
if (!hasNative && !hasWeb) {
|
|
201
|
+
log.error('No platforms found to remove');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Determine what can be removed
|
|
206
|
+
const removablePlatforms = [];
|
|
207
|
+
if (hasNative) removablePlatforms.push({ value: 'native', label: '📱 Native (Expo)' });
|
|
208
|
+
if (hasWeb) removablePlatforms.push({ value: 'web', label: '🌐 Web (Next.js)' });
|
|
209
|
+
|
|
210
|
+
if (removablePlatforms.length === 1) {
|
|
211
|
+
log.error('Cannot remove the only platform. You must have at least one platform.');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
log.plain('');
|
|
216
|
+
log.info('Platforms you can remove:');
|
|
217
|
+
log.plain('');
|
|
218
|
+
|
|
219
|
+
const platformToRemove = await promptSelectAsync(
|
|
220
|
+
'Which platform would you like to remove?',
|
|
221
|
+
removablePlatforms
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (!platformToRemove) {
|
|
225
|
+
log.info('Cancelled');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
log.plain('');
|
|
230
|
+
log.warn(`⚠ This will remove the ${platformToRemove === 'native' ? 'Native' : 'Web'} platform and all its files.`);
|
|
231
|
+
|
|
232
|
+
const { promptYesNo } = await import('../core/prompt.js');
|
|
233
|
+
const confirmed = options.yes || await promptYesNo('Are you sure?', false);
|
|
234
|
+
|
|
235
|
+
if (!confirmed) {
|
|
236
|
+
log.info('Cancelled');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
log.plain('');
|
|
241
|
+
|
|
242
|
+
await withSpinner(
|
|
243
|
+
`Removing ${platformToRemove} platform...`,
|
|
244
|
+
async () => {
|
|
245
|
+
const platformPath = join(paths.cwd, 'apps', platformToRemove);
|
|
246
|
+
|
|
247
|
+
if (existsSync(platformPath)) {
|
|
248
|
+
rmSync(platformPath, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Update package.json workspaces
|
|
252
|
+
const packageJsonPath = join(paths.cwd, 'package.json');
|
|
253
|
+
if (existsSync(packageJsonPath)) {
|
|
254
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
255
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
256
|
+
|
|
257
|
+
if (packageJson.workspaces) {
|
|
258
|
+
packageJson.workspaces = packageJson.workspaces.filter(
|
|
259
|
+
(ws: string) => !ws.includes(`apps/${platformToRemove}`)
|
|
260
|
+
);
|
|
261
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Update turbo.json
|
|
266
|
+
const turboJsonPath = join(paths.cwd, 'turbo.json');
|
|
267
|
+
if (existsSync(turboJsonPath)) {
|
|
268
|
+
const { readFileSync, writeFileSync } = await import('fs');
|
|
269
|
+
const turboJson = JSON.parse(readFileSync(turboJsonPath, 'utf-8'));
|
|
270
|
+
|
|
271
|
+
if (turboJson.pipeline) {
|
|
272
|
+
const tasksToRemove: string[] = [];
|
|
273
|
+
|
|
274
|
+
if (platformToRemove === 'native') {
|
|
275
|
+
tasksToRemove.push('native:*', 'dev:native', 'build:native');
|
|
276
|
+
} else {
|
|
277
|
+
tasksToRemove.push('web:*', 'dev:web', 'build:web');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
tasksToRemove.forEach(task => {
|
|
281
|
+
if (turboJson.pipeline[task]) {
|
|
282
|
+
delete turboJson.pipeline[task];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
writeFileSync(turboJsonPath, JSON.stringify(turboJson, null, 2));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
successText: `✓ Platform removed`,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
log.plain('');
|
|
296
|
+
log.success(`✓ ${platformToRemove === 'native' ? 'Native' : 'Web'} platform removed successfully!`);
|
|
297
|
+
log.plain('');
|
|
298
|
+
log.info('Next steps:');
|
|
299
|
+
log.plain(' 1. Clean up dependencies:');
|
|
300
|
+
log.plain(' pnpm install');
|
|
301
|
+
log.plain('');
|
|
302
|
+
|
|
303
|
+
} catch (error: any) {
|
|
304
|
+
log.plain('');
|
|
305
|
+
log.error(`Failed to remove platform: ${error.message}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
);
|
package/src/core/journal.ts
CHANGED
|
@@ -30,35 +30,52 @@ export async function readJournal(journalPath: string): Promise<Journal> {
|
|
|
30
30
|
if (!(await exists(journalPath))) {
|
|
31
31
|
return { entries: [] };
|
|
32
32
|
}
|
|
33
|
-
const content = await readFileContent(journalPath);
|
|
34
|
-
const journal = JSON.parse(content) as Journal;
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFileContent(journalPath);
|
|
36
|
+
const journal = JSON.parse(content) as Journal;
|
|
37
|
+
|
|
38
|
+
// Ensure entries is an array
|
|
39
|
+
if (!journal.entries || !Array.isArray(journal.entries)) {
|
|
40
|
+
console.warn('Invalid journal format, resetting to empty journal');
|
|
41
|
+
return { entries: [] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Migrate old format to new format
|
|
45
|
+
let needsMigration = false;
|
|
46
|
+
|
|
47
|
+
for (const entry of journal.entries) {
|
|
48
|
+
if (entry.files && entry.files.length > 0 && typeof entry.files[0] === 'string') {
|
|
49
|
+
needsMigration = true;
|
|
50
|
+
// Old format: array of strings
|
|
51
|
+
const oldFiles = entry.files as string[];
|
|
52
|
+
|
|
53
|
+
// Convert to new format with hashes
|
|
54
|
+
const newFiles: FileEntry[] = [];
|
|
55
|
+
for (const filePath of oldFiles) {
|
|
56
|
+
try {
|
|
57
|
+
const hash = await hashFile(filePath);
|
|
58
|
+
newFiles.push({ path: filePath, hash });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// File might not exist anymore, skip it
|
|
61
|
+
console.warn(`Could not hash file ${filePath}, skipping`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
entry.files = newFiles;
|
|
50
66
|
}
|
|
51
|
-
|
|
52
|
-
entry.files = newFiles;
|
|
53
67
|
}
|
|
68
|
+
|
|
69
|
+
// Save migrated journal
|
|
70
|
+
if (needsMigration) {
|
|
71
|
+
await writeJournal(journalPath, journal);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return journal;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn('Failed to read journal, resetting to empty journal:', err);
|
|
77
|
+
return { entries: [] };
|
|
54
78
|
}
|
|
55
|
-
|
|
56
|
-
// Save migrated journal
|
|
57
|
-
if (needsMigration) {
|
|
58
|
-
await writeJournal(journalPath, journal);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return journal;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
export async function writeJournal(journalPath: string, journal: Journal): Promise<void> {
|
package/src/core/recipes.ts
CHANGED
|
@@ -70,49 +70,17 @@ export const RECIPES: RecipeInfo[] = [
|
|
|
70
70
|
description: 'Wake word detection with Vosk',
|
|
71
71
|
icon: '🎙️',
|
|
72
72
|
},
|
|
73
|
-
|
|
74
|
-
// Advanced UI Components
|
|
75
73
|
{
|
|
76
|
-
name: '
|
|
77
|
-
category: '
|
|
78
|
-
description: '
|
|
79
|
-
icon: '
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'animated-switch',
|
|
83
|
-
category: 'advanced-ui',
|
|
84
|
-
description: 'Smooth animated toggle switch',
|
|
85
|
-
icon: '🎚️',
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: 'animated-chip',
|
|
89
|
-
category: 'advanced-ui',
|
|
90
|
-
description: 'Animated chip/tag component with press effects',
|
|
91
|
-
icon: '🏷️',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'number-stepper',
|
|
95
|
-
category: 'advanced-ui',
|
|
96
|
-
description: 'Number input with +/- buttons',
|
|
97
|
-
icon: '🔢',
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'progress-circle',
|
|
101
|
-
category: 'advanced-ui',
|
|
102
|
-
description: 'Circular progress indicator with animation',
|
|
103
|
-
icon: '⭕',
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'swipe-slider',
|
|
107
|
-
category: 'advanced-ui',
|
|
108
|
-
description: 'Interactive swipe-to-confirm slider',
|
|
109
|
-
icon: '👆',
|
|
74
|
+
name: 'ios-widget',
|
|
75
|
+
category: 'feature',
|
|
76
|
+
description: 'iOS Home Screen widgets with Live Activities',
|
|
77
|
+
icon: '📱',
|
|
110
78
|
},
|
|
111
79
|
{
|
|
112
|
-
name: '
|
|
113
|
-
category: '
|
|
114
|
-
description: '
|
|
115
|
-
icon: '
|
|
80
|
+
name: 'payments',
|
|
81
|
+
category: 'feature',
|
|
82
|
+
description: 'In-app purchases and subscriptions with RevenueCat',
|
|
83
|
+
icon: '💳',
|
|
116
84
|
},
|
|
117
85
|
];
|
|
118
86
|
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { statusCommand } from './commands/status.js';
|
|
|
14
14
|
import { checklistCommand } from './commands/checklist.js';
|
|
15
15
|
import { envCommand } from './commands/env.js';
|
|
16
16
|
import { healthCommand } from './commands/health.js';
|
|
17
|
+
import { platformCommand } from './commands/platform.js';
|
|
17
18
|
import { log } from './core/log.js';
|
|
18
19
|
import { showCommandHint } from './core/errors.js';
|
|
19
20
|
|
|
@@ -32,6 +33,7 @@ program.addCommand(doctorCommand);
|
|
|
32
33
|
program.addCommand(listCommand);
|
|
33
34
|
program.addCommand(addCommand);
|
|
34
35
|
program.addCommand(removeCommand);
|
|
36
|
+
program.addCommand(platformCommand);
|
|
35
37
|
program.addCommand(statusCommand);
|
|
36
38
|
program.addCommand(checklistCommand);
|
|
37
39
|
program.addCommand(envCommand);
|