Skip to content

Commit aa0b507

Browse files
fix(cli): improve nuxt templates and upgrade to ai sdk v6 (#775)
* fix(cli): update nuxt templates, upgrade to ai sdk v6, update uniwind template * update deps * fix * fix tests
1 parent 8f16a69 commit aa0b507

File tree

40 files changed

+958
-936
lines changed

40 files changed

+958
-936
lines changed

apps/cli/src/constants.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,14 @@ export const dependencyVersionMap = {
116116

117117
turbo: "^2.6.3",
118118

119-
ai: "^5.0.49",
120-
"@ai-sdk/google": "^2.0.51",
121-
"@ai-sdk/vue": "^2.0.49",
122-
"@ai-sdk/svelte": "^3.0.39",
123-
"@ai-sdk/react": "^2.0.39",
119+
ai: "^6.0.3",
120+
"@ai-sdk/google": "^3.0.1",
121+
"@ai-sdk/vue": "^3.0.3",
122+
"@ai-sdk/svelte": "^4.0.3",
123+
"@ai-sdk/react": "^3.0.3",
124+
"@ai-sdk/devtools": "^0.0.2",
124125
streamdown: "^1.6.10",
125-
shiki: "^3.12.2",
126+
shiki: "^3.20.0",
126127

127128
"@orpc/server": "^1.12.2",
128129
"@orpc/client": "^1.12.2",

apps/cli/src/helpers/addons/examples-setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ async function setupAIDependencies(config: ProjectConfig) {
8787
});
8888
} else if (backend === "self" && webClientDirExists) {
8989
await addPackageDependency({
90-
dependencies: ["ai", "@ai-sdk/google"],
90+
dependencies: ["ai", "@ai-sdk/google", "@ai-sdk/devtools"],
9191
projectDir: webClientDir,
9292
});
9393
} else if (serverDirExists && backend !== "none") {
9494
await addPackageDependency({
95-
dependencies: ["ai", "@ai-sdk/google"],
95+
dependencies: ["ai", "@ai-sdk/google", "@ai-sdk/devtools"],
9696
projectDir: serverDir,
9797
});
9898
}

apps/cli/src/prompts/auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export async function getAuthChoice(
5656
});
5757
}
5858

59+
if (options.length === 0) {
60+
return "none" as Auth;
61+
}
62+
5963
options.push({ value: "none", label: "None", hint: "No auth" });
6064

6165
const response = await select({

apps/cli/src/prompts/examples.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,10 @@ export async function getExamplesChoice(
1919
return [];
2020
}
2121

22-
if (backend !== "convex") {
23-
if (api === "none") {
24-
return [];
25-
}
26-
if (database === "none") {
27-
return [];
28-
}
29-
}
30-
3122
let response: Examples[] | symbol = [];
3223
const options: { value: Examples; label: string; hint: string }[] = [];
3324

34-
if (isExampleTodoAllowed(backend, database)) {
25+
if (isExampleTodoAllowed(backend, database, api)) {
3526
options.push({
3627
value: "todo" as const,
3728
label: "Todo App",

apps/cli/src/utils/compatibility-rules.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,13 @@ export function allowedApisForFrontends(frontends: Frontend[] = []) {
183183
export function isExampleTodoAllowed(
184184
backend?: ProjectConfig["backend"],
185185
database?: ProjectConfig["database"],
186+
api?: API,
186187
) {
187-
return !(backend !== "convex" && backend !== "none" && database === "none");
188+
// Convex handles its own data layer, no need for database or API
189+
if (backend === "convex") return true;
190+
// Todo requires both database and API to communicate
191+
if (database === "none" || api === "none") return false;
192+
return true;
188193
}
189194

190195
export function isExampleAIAllowed(backend?: ProjectConfig["backend"], frontends: Frontend[] = []) {
@@ -306,18 +311,21 @@ export function validateExamplesCompatibility(
306311
backend: ProjectConfig["backend"] | undefined,
307312
database: ProjectConfig["database"] | undefined,
308313
frontend?: Frontend[],
314+
api?: API,
309315
) {
310316
const examplesArr = examples ?? [];
311317
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
312-
if (
313-
examplesArr.includes("todo") &&
314-
backend !== "convex" &&
315-
backend !== "none" &&
316-
database === "none"
317-
) {
318-
exitWithError(
319-
"The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.",
320-
);
318+
if (examplesArr.includes("todo") && backend !== "convex") {
319+
if (database === "none") {
320+
exitWithError(
321+
"The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.",
322+
);
323+
}
324+
if (api === "none") {
325+
exitWithError(
326+
"The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.",
327+
);
328+
}
321329
}
322330

323331
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) {

apps/cli/src/utils/config-validation.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,13 +370,12 @@ export function validateFrontendConstraints(
370370
export function validateApiConstraints(config: Partial<ProjectConfig>, options: CLIInput) {
371371
if (config.api === "none") {
372372
if (
373-
options.examples &&
374-
!(options.examples.length === 1 && options.examples[0] === "none") &&
373+
options.examples?.includes("todo") &&
375374
options.backend !== "convex" &&
376375
options.backend !== "none"
377376
) {
378377
exitWithError(
379-
"Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.",
378+
"Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.",
380379
);
381380
}
382381
}
@@ -430,6 +429,7 @@ export function validateFullConfig(
430429
config.backend,
431430
config.database,
432431
config.frontend ?? [],
432+
config.api,
433433
);
434434

435435
validatePaymentsCompatibility(
@@ -461,6 +461,7 @@ export function validateConfigForProgrammaticUse(config: Partial<ProjectConfig>)
461461
config.backend,
462462
config.database,
463463
config.frontend ?? [],
464+
config.api,
464465
);
465466
} catch (error) {
466467
if (error instanceof Error) {
Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
import { authClient } from "@/lib/auth-client";
22
import { useState } from "react";
3-
import {
4-
ActivityIndicator,
5-
Text,
6-
TextInput,
7-
Pressable,
8-
View,
9-
} from "react-native";
10-
import { Card, useThemeColor } from "heroui-native";
3+
import { Text, View } from "react-native";
4+
import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
115

126
export function SignIn() {
137
const [email, setEmail] = useState("");
148
const [password, setPassword] = useState("");
159
const [isLoading, setIsLoading] = useState(false);
1610
const [error, setError] = useState<string | null>(null);
1711

18-
const mutedColor = useThemeColor("muted");
19-
const accentColor = useThemeColor("accent");
20-
const foregroundColor = useThemeColor("foreground");
21-
const dangerColor = useThemeColor("danger");
22-
2312
const handleLogin = async () => {
2413
setIsLoading(true);
2514
setError(null);
@@ -46,46 +35,39 @@ export function SignIn() {
4635
};
4736

4837
return (
49-
<Card variant="secondary" className="mt-6 p-4">
50-
<Card.Title className="mb-4">Sign In</Card.Title>
38+
<Surface variant="secondary" className="p-4 rounded-lg">
39+
<Text className="text-foreground font-medium mb-4">Sign In</Text>
5140

52-
{error && (
53-
<View className="mb-4 p-3 bg-danger/10 rounded-lg">
54-
<Text className="text-danger text-sm">{error}</Text>
55-
</View>
56-
)}
41+
<ErrorView isInvalid={!!error} className="mb-3">
42+
{error}
43+
</ErrorView>
5744

58-
<TextInput
59-
className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
60-
placeholder="Email"
61-
value={email}
62-
onChangeText={setEmail}
63-
placeholderTextColor={mutedColor}
64-
keyboardType="email-address"
65-
autoCapitalize="none"
66-
/>
45+
<View className="gap-3">
46+
<TextField>
47+
<TextField.Label>Email</TextField.Label>
48+
<TextField.Input
49+
value={email}
50+
onChangeText={setEmail}
51+
placeholder="[email protected]"
52+
keyboardType="email-address"
53+
autoCapitalize="none"
54+
/>
55+
</TextField>
6756

68-
<TextInput
69-
className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
70-
placeholder="Password"
71-
value={password}
72-
onChangeText={setPassword}
73-
placeholderTextColor={mutedColor}
74-
secureTextEntry
75-
/>
57+
<TextField>
58+
<TextField.Label>Password</TextField.Label>
59+
<TextField.Input
60+
value={password}
61+
onChangeText={setPassword}
62+
placeholder="••••••••"
63+
secureTextEntry
64+
/>
65+
</TextField>
7666

77-
<Pressable
78-
onPress={handleLogin}
79-
disabled={isLoading}
80-
className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70"
81-
>
82-
{isLoading ? (
83-
<ActivityIndicator size="small" color={foregroundColor} />
84-
) : (
85-
<Text className="text-foreground font-medium">Sign In</Text>
86-
)}
87-
</Pressable>
88-
</Card>
67+
<Button onPress={handleLogin} isDisabled={isLoading} className="mt-1">
68+
{isLoading ? <Spinner size="sm" color="default" /> : <Button.Label>Sign In</Button.Label>}
69+
</Button>
70+
</View>
71+
</Surface>
8972
);
9073
}
91-
Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { authClient } from "@/lib/auth-client";
22
import { useState } from "react";
3-
import {
4-
ActivityIndicator,
5-
Text,
6-
TextInput,
7-
Pressable,
8-
View,
9-
} from "react-native";
10-
import { Card, useThemeColor } from "heroui-native";
3+
import { Text, View } from "react-native";
4+
import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
115

126
export function SignUp() {
137
const [name, setName] = useState("");
@@ -16,11 +10,6 @@ export function SignUp() {
1610
const [isLoading, setIsLoading] = useState(false);
1711
const [error, setError] = useState<string | null>(null);
1812

19-
const mutedColor = useThemeColor("muted");
20-
const accentColor = useThemeColor("accent");
21-
const foregroundColor = useThemeColor("foreground");
22-
const dangerColor = useThemeColor("danger");
23-
2413
const handleSignUp = async () => {
2514
setIsLoading(true);
2615
setError(null);
@@ -49,54 +38,48 @@ export function SignUp() {
4938
};
5039

5140
return (
52-
<Card variant="secondary" className="mt-6 p-4">
53-
<Card.Title className="mb-4">Create Account</Card.Title>
41+
<Surface variant="secondary" className="p-4 rounded-lg">
42+
<Text className="text-foreground font-medium mb-4">Create Account</Text>
5443

55-
{error && (
56-
<View className="mb-4 p-3 bg-danger/10 rounded-lg">
57-
<Text className="text-danger text-sm">{error}</Text>
58-
</View>
59-
)}
44+
<ErrorView isInvalid={!!error} className="mb-3">
45+
{error}
46+
</ErrorView>
6047

61-
<TextInput
62-
className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
63-
placeholder="Name"
64-
value={name}
65-
onChangeText={setName}
66-
placeholderTextColor={mutedColor}
67-
/>
48+
<View className="gap-3">
49+
<TextField>
50+
<TextField.Label>Name</TextField.Label>
51+
<TextField.Input value={name} onChangeText={setName} placeholder="John Doe" />
52+
</TextField>
6853

69-
<TextInput
70-
className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
71-
placeholder="Email"
72-
value={email}
73-
onChangeText={setEmail}
74-
placeholderTextColor={mutedColor}
75-
keyboardType="email-address"
76-
autoCapitalize="none"
77-
/>
54+
<TextField>
55+
<TextField.Label>Email</TextField.Label>
56+
<TextField.Input
57+
value={email}
58+
onChangeText={setEmail}
59+
placeholder="[email protected]"
60+
keyboardType="email-address"
61+
autoCapitalize="none"
62+
/>
63+
</TextField>
7864

79-
<TextInput
80-
className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
81-
placeholder="Password"
82-
value={password}
83-
onChangeText={setPassword}
84-
placeholderTextColor={mutedColor}
85-
secureTextEntry
86-
/>
65+
<TextField>
66+
<TextField.Label>Password</TextField.Label>
67+
<TextField.Input
68+
value={password}
69+
onChangeText={setPassword}
70+
placeholder="••••••••"
71+
secureTextEntry
72+
/>
73+
</TextField>
8774

88-
<Pressable
89-
onPress={handleSignUp}
90-
disabled={isLoading}
91-
className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70"
92-
>
93-
{isLoading ? (
94-
<ActivityIndicator size="small" color={foregroundColor} />
95-
) : (
96-
<Text className="text-foreground font-medium">Sign Up</Text>
97-
)}
98-
</Pressable>
99-
</Card>
75+
<Button onPress={handleSignUp} isDisabled={isLoading} className="mt-1">
76+
{isLoading ? (
77+
<Spinner size="sm" color="default" />
78+
) : (
79+
<Button.Label>Create Account</Button.Label>
80+
)}
81+
</Button>
82+
</View>
83+
</Surface>
10084
);
10185
}
102-

0 commit comments

Comments
 (0)