kitcn 0.13.2 → 0.13.3
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/dist/{backend-core-B4DHUUfg.mjs → backend-core-BV61sNRx.mjs} +1019 -21
- package/dist/cli.mjs +4 -4
- package/dist/watcher.mjs +1 -1
- package/package.json +1 -1
- package/skills/convex/references/setup/expo.md +73 -0
- package/skills/convex/references/setup/index.md +9 -0
- package/skills/convex/references/setup/server.md +1 -1
|
@@ -5967,7 +5967,7 @@ const syncEnv = pushEnv;
|
|
|
5967
5967
|
//#endregion
|
|
5968
5968
|
//#region src/cli/project-context.ts
|
|
5969
5969
|
function isReactScaffoldFramework(framework) {
|
|
5970
|
-
return framework !== "next-app";
|
|
5970
|
+
return framework !== "next-app" && framework !== "expo";
|
|
5971
5971
|
}
|
|
5972
5972
|
const NEXT_CONFIG_FILES = [
|
|
5973
5973
|
"next.config.ts",
|
|
@@ -5999,6 +5999,13 @@ const GATSBY_CONFIG_FILES = [
|
|
|
5999
5999
|
"gatsby-config.mjs",
|
|
6000
6000
|
"gatsby-config.cjs"
|
|
6001
6001
|
];
|
|
6002
|
+
const EXPO_CONFIG_FILES = [
|
|
6003
|
+
"app.json",
|
|
6004
|
+
"app.config.ts",
|
|
6005
|
+
"app.config.js",
|
|
6006
|
+
"app.config.mjs",
|
|
6007
|
+
"app.config.cjs"
|
|
6008
|
+
];
|
|
6002
6009
|
const LEADING_SLASH_RE = /^\/+/;
|
|
6003
6010
|
const TS_ALIAS_RE = /"@\/\*"\s*:\s*\[\s*"([^"]+)"\s*\]/m;
|
|
6004
6011
|
function hasAnyFile(cwd, relativePaths) {
|
|
@@ -6054,7 +6061,7 @@ function inferUsesSrcFromAppDirs(cwd) {
|
|
|
6054
6061
|
function inferUsesSrc(cwd, mode) {
|
|
6055
6062
|
const signals = [
|
|
6056
6063
|
inferUsesSrcFromComponentsJson(cwd),
|
|
6057
|
-
mode === "next-app" ? inferUsesSrcFromAppDirs(cwd) : null,
|
|
6064
|
+
mode === "next-app" || mode === "expo" ? inferUsesSrcFromAppDirs(cwd) : null,
|
|
6058
6065
|
inferUsesSrcFromComponentRoots(cwd),
|
|
6059
6066
|
inferUsesSrcFromTsconfig(cwd),
|
|
6060
6067
|
fs.existsSync(resolve(cwd, "src")) ? true : null
|
|
@@ -6085,6 +6092,7 @@ function detectProjectFramework(cwd = process.cwd()) {
|
|
|
6085
6092
|
if (fs.existsSync(resolve(cwd, "composer.json"))) return "laravel";
|
|
6086
6093
|
if (hasDependency$1(packageJson, (dependency) => dependency.startsWith("@remix-run/"))) return "remix";
|
|
6087
6094
|
if (hasDependency$1(packageJson, (dependency) => dependency.startsWith("@tanstack/react-start"))) return "tanstack-start";
|
|
6095
|
+
if (hasAnyFile(cwd, EXPO_CONFIG_FILES) && hasDependency$1(packageJson, (dependency) => dependency === "expo" || dependency === "expo-router")) return "expo";
|
|
6088
6096
|
if (hasAnyFile(cwd, REACT_ROUTER_CONFIG_FILES)) return "react-router";
|
|
6089
6097
|
if (hasAnyFile(cwd, VITE_CONFIG_FILES)) return "vite";
|
|
6090
6098
|
if (hasDependency$1(packageJson, (dependency) => dependency === "react" || dependency === "react-dom")) return "manual";
|
|
@@ -6092,15 +6100,16 @@ function detectProjectFramework(cwd = process.cwd()) {
|
|
|
6092
6100
|
}
|
|
6093
6101
|
function mapFrameworkToScaffoldMode(framework) {
|
|
6094
6102
|
if (framework === "next-app") return "next-app";
|
|
6103
|
+
if (framework === "expo") return "expo";
|
|
6095
6104
|
if (framework === "next-pages" || framework === "vite" || framework === "react-router" || framework === "tanstack-start" || framework === "manual") return "react";
|
|
6096
|
-
throw new Error(`Unsupported framework "${framework}" for kitcn init. Supported frameworks map to: next-app, next-pages, vite, react-router, tanstack-start, manual.`);
|
|
6105
|
+
throw new Error(`Unsupported framework "${framework}" for kitcn init. Supported frameworks map to: next-app, expo, next-pages, vite, react-router, tanstack-start, manual.`);
|
|
6097
6106
|
}
|
|
6098
6107
|
function resolveProjectScaffoldContext(params = {}) {
|
|
6099
6108
|
const cwd = params.cwd ?? process.cwd();
|
|
6100
|
-
const detectedFramework = (params.template === "next" ? "next-app" : params.template === "start" ? "tanstack-start" : params.template === "vite" ? "vite" : null) ?? detectProjectFramework(cwd);
|
|
6109
|
+
const detectedFramework = (params.template === "next" ? "next-app" : params.template === "expo" ? "expo" : params.template === "start" ? "tanstack-start" : params.template === "vite" ? "vite" : null) ?? detectProjectFramework(cwd);
|
|
6101
6110
|
if (!detectedFramework) {
|
|
6102
6111
|
if (params.allowMissing) return null;
|
|
6103
|
-
throw new Error("Could not detect a supported app scaffold. Supported modes currently start from `next`, `start`, or `vite`.");
|
|
6112
|
+
throw new Error("Could not detect a supported app scaffold. Supported modes currently start from `next`, `expo`, `start`, or `vite`.");
|
|
6104
6113
|
}
|
|
6105
6114
|
let mode;
|
|
6106
6115
|
try {
|
|
@@ -6132,6 +6141,23 @@ function resolveProjectScaffoldContext(params = {}) {
|
|
|
6132
6141
|
convexSiteUrlEnvKey: "NEXT_PUBLIC_CONVEX_SITE_URL"
|
|
6133
6142
|
};
|
|
6134
6143
|
}
|
|
6144
|
+
if (mode === "expo") {
|
|
6145
|
+
const appDir = posix.join(rootPrefix, "app").replace(LEADING_SLASH_RE, "");
|
|
6146
|
+
return {
|
|
6147
|
+
framework: "expo",
|
|
6148
|
+
mode,
|
|
6149
|
+
usesSrc,
|
|
6150
|
+
appDir,
|
|
6151
|
+
componentsDir,
|
|
6152
|
+
libDir,
|
|
6153
|
+
convexClientDir,
|
|
6154
|
+
tailwindCssPath: null,
|
|
6155
|
+
tsconfigAliasPath,
|
|
6156
|
+
clientSiteUrlEnvKey: "EXPO_PUBLIC_SITE_URL",
|
|
6157
|
+
convexUrlEnvKey: "EXPO_PUBLIC_CONVEX_URL",
|
|
6158
|
+
convexSiteUrlEnvKey: "EXPO_PUBLIC_CONVEX_SITE_URL"
|
|
6159
|
+
};
|
|
6160
|
+
}
|
|
6135
6161
|
const clientEntryFile = resolveReactClientEntryFile(cwd, usesSrc);
|
|
6136
6162
|
const viteConfigFile = VITE_CONFIG_FILES.find((relativePath) => fs.existsSync(resolve(cwd, relativePath)));
|
|
6137
6163
|
const tsconfigAppFile = ["tsconfig.app.json", "tsconfig.json"].find((relativePath) => fs.existsSync(resolve(cwd, relativePath)));
|
|
@@ -6467,6 +6493,51 @@ const createRegistryFile = (params) => {
|
|
|
6467
6493
|
};
|
|
6468
6494
|
};
|
|
6469
6495
|
|
|
6496
|
+
//#endregion
|
|
6497
|
+
//#region src/cli/registry/init/expo/init-expo-convex-provider.template.ts
|
|
6498
|
+
const INIT_EXPO_CONVEX_PROVIDER_TEMPLATE = `import { QueryClientProvider as TanstackQueryClientProvider } from '@tanstack/react-query';
|
|
6499
|
+
import {
|
|
6500
|
+
ConvexProvider,
|
|
6501
|
+
ConvexReactClient,
|
|
6502
|
+
getConvexQueryClientSingleton,
|
|
6503
|
+
getQueryClientSingleton,
|
|
6504
|
+
} from 'kitcn/react';
|
|
6505
|
+
import type { ReactNode } from 'react';
|
|
6506
|
+
|
|
6507
|
+
import { CRPCProvider } from '@/lib/convex/crpc';
|
|
6508
|
+
import { createQueryClient } from '@/lib/convex/query-client';
|
|
6509
|
+
|
|
6510
|
+
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!);
|
|
6511
|
+
|
|
6512
|
+
export function AppConvexProvider({
|
|
6513
|
+
children,
|
|
6514
|
+
}: {
|
|
6515
|
+
children: ReactNode;
|
|
6516
|
+
}) {
|
|
6517
|
+
return (
|
|
6518
|
+
<ConvexProvider client={convex}>
|
|
6519
|
+
<QueryProvider>{children}</QueryProvider>
|
|
6520
|
+
</ConvexProvider>
|
|
6521
|
+
);
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6524
|
+
function QueryProvider({ children }: { children: ReactNode }) {
|
|
6525
|
+
const queryClient = getQueryClientSingleton(createQueryClient);
|
|
6526
|
+
const convexQueryClient = getConvexQueryClientSingleton({
|
|
6527
|
+
convex,
|
|
6528
|
+
queryClient,
|
|
6529
|
+
});
|
|
6530
|
+
|
|
6531
|
+
return (
|
|
6532
|
+
<TanstackQueryClientProvider client={queryClient}>
|
|
6533
|
+
<CRPCProvider convexClient={convex} convexQueryClient={convexQueryClient}>
|
|
6534
|
+
{children}
|
|
6535
|
+
</CRPCProvider>
|
|
6536
|
+
</TanstackQueryClientProvider>
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
`;
|
|
6540
|
+
|
|
6470
6541
|
//#endregion
|
|
6471
6542
|
//#region src/cli/scaffold-placeholders.ts
|
|
6472
6543
|
const FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5 = "__KITCN_FUNCTIONS_DIR__";
|
|
@@ -7135,10 +7206,10 @@ const resolvePluginScaffoldFiles = (templates, roots, functionsDir, existingTemp
|
|
|
7135
7206
|
if (template.target === "lib") rootDir = roots.libRootDir;
|
|
7136
7207
|
else if (template.target === "functions") rootDir = roots.functionsRootDir;
|
|
7137
7208
|
else if (template.target === "app") {
|
|
7138
|
-
if (!roots.appRootDir) throw new Error(`${descriptor.label} scaffolding requires a supported app baseline. Run \`kitcn init --yes\` in a supported app, or bootstrap one with \`kitcn init -t <next|start|vite>\` first.`);
|
|
7209
|
+
if (!roots.appRootDir) throw new Error(`${descriptor.label} scaffolding requires a supported app baseline. Run \`kitcn init --yes\` in a supported app, or bootstrap one with \`kitcn init -t <next|expo|start|vite>\` first.`);
|
|
7139
7210
|
rootDir = roots.appRootDir;
|
|
7140
7211
|
} else {
|
|
7141
|
-
if (!roots.clientLibRootDir) throw new Error(`${descriptor.label} scaffolding requires a supported app baseline. Run \`kitcn init --yes\` in a supported app, or bootstrap one with \`kitcn init -t <next|start|vite>\` first.`);
|
|
7212
|
+
if (!roots.clientLibRootDir) throw new Error(`${descriptor.label} scaffolding requires a supported app baseline. Run \`kitcn init --yes\` in a supported app, or bootstrap one with \`kitcn init -t <next|expo|start|vite>\` first.`);
|
|
7142
7213
|
rootDir = roots.clientLibRootDir;
|
|
7143
7214
|
}
|
|
7144
7215
|
const mappedLockfilePath = existingTemplatePathMap?.[template.id];
|
|
@@ -8049,6 +8120,32 @@ export default defineAuth(() => ({
|
|
|
8049
8120
|
trustedOrigins: [getEnv().SITE_URL],
|
|
8050
8121
|
}));
|
|
8051
8122
|
`;
|
|
8123
|
+
const AUTH_EXPO_TEMPLATE = `import { expo } from '@better-auth/expo';
|
|
8124
|
+
import { convex } from 'kitcn/auth';
|
|
8125
|
+
import { getEnv } from '../lib/get-env';
|
|
8126
|
+
import authConfig from './auth.config';
|
|
8127
|
+
import { defineAuth } from './generated/auth';
|
|
8128
|
+
|
|
8129
|
+
export default defineAuth(() => ({
|
|
8130
|
+
emailAndPassword: {
|
|
8131
|
+
enabled: true,
|
|
8132
|
+
},
|
|
8133
|
+
baseURL: getEnv().CONVEX_SITE_URL ?? getEnv().SITE_URL,
|
|
8134
|
+
plugins: [
|
|
8135
|
+
expo(),
|
|
8136
|
+
convex({
|
|
8137
|
+
authConfig,
|
|
8138
|
+
jwks: getEnv().JWKS,
|
|
8139
|
+
}),
|
|
8140
|
+
],
|
|
8141
|
+
session: {
|
|
8142
|
+
expiresIn: 60 * 60 * 24 * 30,
|
|
8143
|
+
updateAge: 60 * 60 * 24 * 15,
|
|
8144
|
+
},
|
|
8145
|
+
telemetry: { enabled: false },
|
|
8146
|
+
trustedOrigins: [getEnv().SITE_URL],
|
|
8147
|
+
}));
|
|
8148
|
+
`;
|
|
8052
8149
|
const AUTH_CONVEX_TEMPLATE = `import { convex } from 'kitcn/auth';
|
|
8053
8150
|
import authConfig from './auth.config';
|
|
8054
8151
|
import { defineAuth } from './generated/auth';
|
|
@@ -8117,6 +8214,38 @@ export const authClient = createAuthClient({
|
|
|
8117
8214
|
plugins: [convexClient()],
|
|
8118
8215
|
});
|
|
8119
8216
|
|
|
8217
|
+
export const {
|
|
8218
|
+
useSignInMutationOptions,
|
|
8219
|
+
useSignOutMutationOptions,
|
|
8220
|
+
useSignUpMutationOptions,
|
|
8221
|
+
} = createAuthMutations(authClient);
|
|
8222
|
+
`;
|
|
8223
|
+
const AUTH_EXPO_CLIENT_TEMPLATE = `import { expoClient } from '@better-auth/expo/client';
|
|
8224
|
+
import Constants from 'expo-constants';
|
|
8225
|
+
import * as SecureStore from 'expo-secure-store';
|
|
8226
|
+
import { createAuthClient } from 'better-auth/react';
|
|
8227
|
+
import { convexClient } from 'kitcn/auth/client';
|
|
8228
|
+
import { createAuthMutations } from 'kitcn/react';
|
|
8229
|
+
import { Platform } from 'react-native';
|
|
8230
|
+
|
|
8231
|
+
const scheme = Constants.expoConfig?.scheme as string;
|
|
8232
|
+
|
|
8233
|
+
export const authClient = createAuthClient({
|
|
8234
|
+
baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL!,
|
|
8235
|
+
plugins: [
|
|
8236
|
+
convexClient(),
|
|
8237
|
+
...(Platform.OS === 'web'
|
|
8238
|
+
? []
|
|
8239
|
+
: [
|
|
8240
|
+
expoClient({
|
|
8241
|
+
scheme,
|
|
8242
|
+
storagePrefix: scheme,
|
|
8243
|
+
storage: SecureStore,
|
|
8244
|
+
}),
|
|
8245
|
+
]),
|
|
8246
|
+
],
|
|
8247
|
+
});
|
|
8248
|
+
|
|
8120
8249
|
export const {
|
|
8121
8250
|
useSignInMutationOptions,
|
|
8122
8251
|
useSignOutMutationOptions,
|
|
@@ -8372,6 +8501,293 @@ export const router = c.router;
|
|
|
8372
8501
|
`;
|
|
8373
8502
|
}
|
|
8374
8503
|
|
|
8504
|
+
//#endregion
|
|
8505
|
+
//#region src/cli/registry/items/auth/auth-expo-convex-provider.template.ts
|
|
8506
|
+
const AUTH_EXPO_CONVEX_PROVIDER_TEMPLATE = `import { QueryClientProvider as TanstackQueryClientProvider } from '@tanstack/react-query';
|
|
8507
|
+
import { useRouter } from 'expo-router';
|
|
8508
|
+
import { ConvexAuthProvider } from 'kitcn/auth/client';
|
|
8509
|
+
import {
|
|
8510
|
+
ConvexReactClient,
|
|
8511
|
+
getConvexQueryClientSingleton,
|
|
8512
|
+
getQueryClientSingleton,
|
|
8513
|
+
useAuthStore,
|
|
8514
|
+
} from 'kitcn/react';
|
|
8515
|
+
import type { ReactNode } from 'react';
|
|
8516
|
+
|
|
8517
|
+
import { authClient } from '@/lib/convex/auth-client';
|
|
8518
|
+
import { CRPCProvider } from '@/lib/convex/crpc';
|
|
8519
|
+
import { createQueryClient } from '@/lib/convex/query-client';
|
|
8520
|
+
|
|
8521
|
+
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!);
|
|
8522
|
+
|
|
8523
|
+
export function AppConvexProvider({
|
|
8524
|
+
children,
|
|
8525
|
+
}: {
|
|
8526
|
+
children: ReactNode;
|
|
8527
|
+
}) {
|
|
8528
|
+
const router = useRouter();
|
|
8529
|
+
|
|
8530
|
+
return (
|
|
8531
|
+
<ConvexAuthProvider
|
|
8532
|
+
authClient={authClient}
|
|
8533
|
+
client={convex}
|
|
8534
|
+
onMutationUnauthorized={() => {
|
|
8535
|
+
router.push('/auth');
|
|
8536
|
+
}}
|
|
8537
|
+
onQueryUnauthorized={() => {
|
|
8538
|
+
router.push('/auth');
|
|
8539
|
+
}}
|
|
8540
|
+
>
|
|
8541
|
+
<QueryProvider>{children}</QueryProvider>
|
|
8542
|
+
</ConvexAuthProvider>
|
|
8543
|
+
);
|
|
8544
|
+
}
|
|
8545
|
+
|
|
8546
|
+
function QueryProvider({ children }: { children: ReactNode }) {
|
|
8547
|
+
const authStore = useAuthStore();
|
|
8548
|
+
const queryClient = getQueryClientSingleton(createQueryClient);
|
|
8549
|
+
const convexQueryClient = getConvexQueryClientSingleton({
|
|
8550
|
+
authStore,
|
|
8551
|
+
convex,
|
|
8552
|
+
queryClient,
|
|
8553
|
+
});
|
|
8554
|
+
|
|
8555
|
+
return (
|
|
8556
|
+
<TanstackQueryClientProvider client={queryClient}>
|
|
8557
|
+
<CRPCProvider convexClient={convex} convexQueryClient={convexQueryClient}>
|
|
8558
|
+
{children}
|
|
8559
|
+
</CRPCProvider>
|
|
8560
|
+
</TanstackQueryClientProvider>
|
|
8561
|
+
);
|
|
8562
|
+
}
|
|
8563
|
+
`;
|
|
8564
|
+
|
|
8565
|
+
//#endregion
|
|
8566
|
+
//#region src/cli/registry/items/auth/auth-expo-page.template.ts
|
|
8567
|
+
const AUTH_EXPO_PAGE_TEMPLATE = `import { useMutation } from '@tanstack/react-query';
|
|
8568
|
+
import { useAuth } from 'kitcn/react';
|
|
8569
|
+
import { useState } from 'react';
|
|
8570
|
+
import {
|
|
8571
|
+
Pressable,
|
|
8572
|
+
SafeAreaView,
|
|
8573
|
+
StyleSheet,
|
|
8574
|
+
Text,
|
|
8575
|
+
TextInput,
|
|
8576
|
+
View,
|
|
8577
|
+
} from 'react-native';
|
|
8578
|
+
|
|
8579
|
+
import {
|
|
8580
|
+
authClient,
|
|
8581
|
+
useSignInMutationOptions,
|
|
8582
|
+
useSignOutMutationOptions,
|
|
8583
|
+
useSignUpMutationOptions,
|
|
8584
|
+
} from '@/lib/convex/auth-client';
|
|
8585
|
+
|
|
8586
|
+
export default function AuthPage() {
|
|
8587
|
+
const { hasSession } = useAuth();
|
|
8588
|
+
const authSession = authClient.useSession();
|
|
8589
|
+
const session = authSession.data;
|
|
8590
|
+
const user = session?.user ?? null;
|
|
8591
|
+
const hasSignedInUser = hasSession || Boolean(user);
|
|
8592
|
+
const [mode, setMode] = useState<'signin' | 'signup'>('signin');
|
|
8593
|
+
const [name, setName] = useState('');
|
|
8594
|
+
const [email, setEmail] = useState('');
|
|
8595
|
+
const [password, setPassword] = useState('');
|
|
8596
|
+
|
|
8597
|
+
const signIn = useMutation(useSignInMutationOptions());
|
|
8598
|
+
const signUp = useMutation(useSignUpMutationOptions());
|
|
8599
|
+
const signOut = useMutation(useSignOutMutationOptions());
|
|
8600
|
+
|
|
8601
|
+
const errorMessage =
|
|
8602
|
+
signIn.error?.message ??
|
|
8603
|
+
signUp.error?.message ??
|
|
8604
|
+
signOut.error?.message ??
|
|
8605
|
+
null;
|
|
8606
|
+
const isPending =
|
|
8607
|
+
signIn.isPending || signUp.isPending || signOut.isPending;
|
|
8608
|
+
|
|
8609
|
+
function handleSubmit() {
|
|
8610
|
+
if (mode === 'signup') {
|
|
8611
|
+
signUp.mutate({
|
|
8612
|
+
email,
|
|
8613
|
+
name,
|
|
8614
|
+
password,
|
|
8615
|
+
});
|
|
8616
|
+
return;
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8619
|
+
signIn.mutate({
|
|
8620
|
+
email,
|
|
8621
|
+
password,
|
|
8622
|
+
});
|
|
8623
|
+
}
|
|
8624
|
+
|
|
8625
|
+
if (hasSignedInUser) {
|
|
8626
|
+
return (
|
|
8627
|
+
<SafeAreaView style={styles.safeArea}>
|
|
8628
|
+
<View style={styles.screen}>
|
|
8629
|
+
<View style={styles.header}>
|
|
8630
|
+
<Text style={styles.kicker}>Signed in</Text>
|
|
8631
|
+
<Text style={styles.title}>
|
|
8632
|
+
{user?.name || user?.email || email}
|
|
8633
|
+
</Text>
|
|
8634
|
+
<Text style={styles.copy}>{user?.email || email}</Text>
|
|
8635
|
+
</View>
|
|
8636
|
+
<Pressable
|
|
8637
|
+
disabled={isPending}
|
|
8638
|
+
onPress={() => signOut.mutate()}
|
|
8639
|
+
style={[styles.button, isPending && styles.buttonDisabled]}
|
|
8640
|
+
>
|
|
8641
|
+
<Text style={styles.buttonText}>
|
|
8642
|
+
{signOut.isPending ? 'Signing out…' : 'Sign out'}
|
|
8643
|
+
</Text>
|
|
8644
|
+
</Pressable>
|
|
8645
|
+
</View>
|
|
8646
|
+
</SafeAreaView>
|
|
8647
|
+
);
|
|
8648
|
+
}
|
|
8649
|
+
|
|
8650
|
+
return (
|
|
8651
|
+
<SafeAreaView style={styles.safeArea}>
|
|
8652
|
+
<View style={styles.screen}>
|
|
8653
|
+
<View style={styles.header}>
|
|
8654
|
+
<Text style={styles.kicker}>Auth demo</Text>
|
|
8655
|
+
<Text style={styles.title}>
|
|
8656
|
+
{mode === 'signup' ? 'Create an account' : 'Sign in'}
|
|
8657
|
+
</Text>
|
|
8658
|
+
<Text style={styles.copy}>
|
|
8659
|
+
Minimal Better Auth wiring on top of the Expo baseline.
|
|
8660
|
+
</Text>
|
|
8661
|
+
</View>
|
|
8662
|
+
|
|
8663
|
+
<View style={styles.form}>
|
|
8664
|
+
{mode === 'signup' ? (
|
|
8665
|
+
<TextInput
|
|
8666
|
+
autoCapitalize="words"
|
|
8667
|
+
onChangeText={setName}
|
|
8668
|
+
placeholder="Name"
|
|
8669
|
+
style={styles.input}
|
|
8670
|
+
value={name}
|
|
8671
|
+
/>
|
|
8672
|
+
) : null}
|
|
8673
|
+
<TextInput
|
|
8674
|
+
autoCapitalize="none"
|
|
8675
|
+
keyboardType="email-address"
|
|
8676
|
+
onChangeText={setEmail}
|
|
8677
|
+
placeholder="Email"
|
|
8678
|
+
style={styles.input}
|
|
8679
|
+
value={email}
|
|
8680
|
+
/>
|
|
8681
|
+
<TextInput
|
|
8682
|
+
onChangeText={setPassword}
|
|
8683
|
+
placeholder="Password"
|
|
8684
|
+
secureTextEntry
|
|
8685
|
+
style={styles.input}
|
|
8686
|
+
value={password}
|
|
8687
|
+
/>
|
|
8688
|
+
<Pressable
|
|
8689
|
+
disabled={isPending}
|
|
8690
|
+
onPress={handleSubmit}
|
|
8691
|
+
style={[styles.button, isPending && styles.buttonDisabled]}
|
|
8692
|
+
>
|
|
8693
|
+
<Text style={styles.buttonText}>
|
|
8694
|
+
{isPending
|
|
8695
|
+
? 'Working…'
|
|
8696
|
+
: mode === 'signup'
|
|
8697
|
+
? 'Create account'
|
|
8698
|
+
: 'Sign in'}
|
|
8699
|
+
</Text>
|
|
8700
|
+
</Pressable>
|
|
8701
|
+
</View>
|
|
8702
|
+
|
|
8703
|
+
<Pressable
|
|
8704
|
+
onPress={() => setMode(mode === 'signin' ? 'signup' : 'signin')}
|
|
8705
|
+
>
|
|
8706
|
+
<Text style={styles.link}>
|
|
8707
|
+
{mode === 'signin'
|
|
8708
|
+
? "Don't have an account? Sign up"
|
|
8709
|
+
: 'Already have an account? Sign in'}
|
|
8710
|
+
</Text>
|
|
8711
|
+
</Pressable>
|
|
8712
|
+
|
|
8713
|
+
{errorMessage ? <Text style={styles.error}>{errorMessage}</Text> : null}
|
|
8714
|
+
</View>
|
|
8715
|
+
</SafeAreaView>
|
|
8716
|
+
);
|
|
8717
|
+
}
|
|
8718
|
+
|
|
8719
|
+
const styles = StyleSheet.create({
|
|
8720
|
+
safeArea: {
|
|
8721
|
+
flex: 1,
|
|
8722
|
+
backgroundColor: '#ffffff',
|
|
8723
|
+
},
|
|
8724
|
+
screen: {
|
|
8725
|
+
flex: 1,
|
|
8726
|
+
gap: 20,
|
|
8727
|
+
justifyContent: 'center',
|
|
8728
|
+
paddingHorizontal: 24,
|
|
8729
|
+
paddingVertical: 32,
|
|
8730
|
+
},
|
|
8731
|
+
header: {
|
|
8732
|
+
gap: 8,
|
|
8733
|
+
},
|
|
8734
|
+
kicker: {
|
|
8735
|
+
color: '#6b7280',
|
|
8736
|
+
fontSize: 12,
|
|
8737
|
+
fontWeight: '600',
|
|
8738
|
+
letterSpacing: 1.5,
|
|
8739
|
+
textTransform: 'uppercase',
|
|
8740
|
+
},
|
|
8741
|
+
title: {
|
|
8742
|
+
color: '#111827',
|
|
8743
|
+
fontSize: 28,
|
|
8744
|
+
fontWeight: '700',
|
|
8745
|
+
},
|
|
8746
|
+
copy: {
|
|
8747
|
+
color: '#4b5563',
|
|
8748
|
+
fontSize: 15,
|
|
8749
|
+
lineHeight: 22,
|
|
8750
|
+
},
|
|
8751
|
+
form: {
|
|
8752
|
+
gap: 12,
|
|
8753
|
+
},
|
|
8754
|
+
input: {
|
|
8755
|
+
borderColor: '#d1d5db',
|
|
8756
|
+
borderRadius: 12,
|
|
8757
|
+
borderWidth: 1,
|
|
8758
|
+
fontSize: 16,
|
|
8759
|
+
minHeight: 48,
|
|
8760
|
+
paddingHorizontal: 14,
|
|
8761
|
+
paddingVertical: 12,
|
|
8762
|
+
},
|
|
8763
|
+
button: {
|
|
8764
|
+
alignItems: 'center',
|
|
8765
|
+
backgroundColor: '#111827',
|
|
8766
|
+
borderRadius: 12,
|
|
8767
|
+
minHeight: 48,
|
|
8768
|
+
justifyContent: 'center',
|
|
8769
|
+
paddingHorizontal: 16,
|
|
8770
|
+
},
|
|
8771
|
+
buttonDisabled: {
|
|
8772
|
+
opacity: 0.5,
|
|
8773
|
+
},
|
|
8774
|
+
buttonText: {
|
|
8775
|
+
color: '#ffffff',
|
|
8776
|
+
fontSize: 15,
|
|
8777
|
+
fontWeight: '600',
|
|
8778
|
+
},
|
|
8779
|
+
link: {
|
|
8780
|
+
color: '#4b5563',
|
|
8781
|
+
fontSize: 14,
|
|
8782
|
+
textDecorationLine: 'underline',
|
|
8783
|
+
},
|
|
8784
|
+
error: {
|
|
8785
|
+
color: '#dc2626',
|
|
8786
|
+
fontSize: 14,
|
|
8787
|
+
},
|
|
8788
|
+
});
|
|
8789
|
+
`;
|
|
8790
|
+
|
|
8375
8791
|
//#endregion
|
|
8376
8792
|
//#region src/cli/registry/items/auth/auth-next-route.template.ts
|
|
8377
8793
|
const AUTH_NEXT_ROUTE_TEMPLATE = `import { handler } from '@/lib/convex/server';
|
|
@@ -9679,14 +10095,24 @@ const AUTH_PROVIDER_REACT_NODE_IMPORT_RE = /import\s+type\s+\{\s*ReactNode\s*\}\
|
|
|
9679
10095
|
const AUTH_CONVEX_NEXT_PROVIDER_RETURN_RE = /<ConvexProvider client=\{convex\}>[\s\S]*?<\/ConvexProvider>/;
|
|
9680
10096
|
const AUTH_CONVEX_REACT_PROVIDER_OPEN_RE = /<ConvexProvider client=\{convex\}>/;
|
|
9681
10097
|
const AUTH_CONVEX_REACT_PROVIDER_CLOSE_RE = /<\/ConvexProvider>/;
|
|
9682
|
-
const AUTH_ENV_FIELDS = [
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
10098
|
+
const AUTH_ENV_FIELDS = [
|
|
10099
|
+
{
|
|
10100
|
+
bootstrap: { kind: "generated-secret" },
|
|
10101
|
+
key: "BETTER_AUTH_SECRET",
|
|
10102
|
+
schema: "z.string().optional()"
|
|
10103
|
+
},
|
|
10104
|
+
{
|
|
10105
|
+
key: "JWKS",
|
|
10106
|
+
schema: "z.string().optional()"
|
|
10107
|
+
},
|
|
10108
|
+
{
|
|
10109
|
+
key: "CONVEX_SITE_URL",
|
|
10110
|
+
schema: "z.string().optional()"
|
|
10111
|
+
}
|
|
10112
|
+
];
|
|
10113
|
+
const BETTER_AUTH_EXPO_INSTALL_SPEC = "@better-auth/expo@1.6.5";
|
|
10114
|
+
const EXPO_SECURE_STORE_INSTALL_SPEC = "expo-secure-store@~55.0.8";
|
|
10115
|
+
const EXPO_NETWORK_INSTALL_SPEC = "expo-network@~55.0.8";
|
|
9690
10116
|
const AUTH_FILES = [
|
|
9691
10117
|
createRegistryFile({
|
|
9692
10118
|
id: "auth-config",
|
|
@@ -9844,7 +10270,16 @@ app.use(
|
|
|
9844
10270
|
}
|
|
9845
10271
|
function buildAuthProviderPlanFile(params) {
|
|
9846
10272
|
const projectContext = params.roots.projectContext;
|
|
9847
|
-
if (!projectContext) throw new Error("Auth scaffolding requires a supported app baseline. Run `kitcn init --yes` in a supported app, or bootstrap one with `kitcn init -t <next|start|vite>` first.");
|
|
10273
|
+
if (!projectContext) throw new Error("Auth scaffolding requires a supported app baseline. Run `kitcn init --yes` in a supported app, or bootstrap one with `kitcn init -t <next|expo|start|vite>` first.");
|
|
10274
|
+
if (projectContext.framework === "expo") return createPlanFile({
|
|
10275
|
+
kind: "scaffold",
|
|
10276
|
+
filePath: resolve(process.cwd(), projectContext.convexClientDir, "convex-provider.tsx"),
|
|
10277
|
+
content: AUTH_EXPO_CONVEX_PROVIDER_TEMPLATE,
|
|
10278
|
+
managedBaselineContent: INIT_EXPO_CONVEX_PROVIDER_TEMPLATE,
|
|
10279
|
+
createReason: "Create auth-aware kitcn provider for the Expo scaffold.",
|
|
10280
|
+
updateReason: "Update kitcn provider with auth-aware client wiring.",
|
|
10281
|
+
skipReason: "kitcn provider already matches the auth scaffold."
|
|
10282
|
+
});
|
|
9848
10283
|
if (projectContext.framework === "tanstack-start") return createPlanFile({
|
|
9849
10284
|
kind: "scaffold",
|
|
9850
10285
|
filePath: resolve(process.cwd(), projectContext.convexClientDir, "convex-provider.tsx"),
|
|
@@ -9939,6 +10374,18 @@ function buildAuthStartPagePlanFile(params) {
|
|
|
9939
10374
|
skipReason: "The Start auth demo route already exists."
|
|
9940
10375
|
});
|
|
9941
10376
|
}
|
|
10377
|
+
function buildAuthExpoPagePlanFile(params) {
|
|
10378
|
+
const projectContext = params.roots.projectContext;
|
|
10379
|
+
if (!projectContext || projectContext.framework !== "expo") throw new Error("Auth scaffolding requires a supported Expo shell.");
|
|
10380
|
+
return createPlanFile({
|
|
10381
|
+
kind: "scaffold",
|
|
10382
|
+
filePath: resolve(process.cwd(), projectContext.appDir, "auth.tsx"),
|
|
10383
|
+
content: AUTH_EXPO_PAGE_TEMPLATE,
|
|
10384
|
+
createReason: "Create the Expo auth demo route.",
|
|
10385
|
+
updateReason: "Update the Expo auth demo route.",
|
|
10386
|
+
skipReason: "The Expo auth demo route already exists."
|
|
10387
|
+
});
|
|
10388
|
+
}
|
|
9942
10389
|
function buildAuthConvexLocalEnvPlanFile(params) {
|
|
9943
10390
|
const envPath = resolve(params.functionsDir, ".env");
|
|
9944
10391
|
return createPlanFile({
|
|
@@ -10097,6 +10544,25 @@ const authRegistryItem = defineInternalRegistryItem({
|
|
|
10097
10544
|
})),
|
|
10098
10545
|
resolveTemplates: ({ roots, templates }) => {
|
|
10099
10546
|
if (!roots.projectContext || roots.projectContext.mode === "next-app") return templates;
|
|
10547
|
+
if (roots.projectContext.framework === "expo") return templates.filter((template) => template.id !== "auth-page").map((template) => {
|
|
10548
|
+
if (template.id === "auth-runtime") return {
|
|
10549
|
+
...template,
|
|
10550
|
+
content: AUTH_EXPO_TEMPLATE,
|
|
10551
|
+
dependencyHintMessage: "Expo auth runtime needs the Better Auth Expo plugin.",
|
|
10552
|
+
dependencyHints: [OPENTELEMETRY_API_INSTALL_SPEC, BETTER_AUTH_EXPO_INSTALL_SPEC]
|
|
10553
|
+
};
|
|
10554
|
+
if (template.id === "auth-client") return {
|
|
10555
|
+
...template,
|
|
10556
|
+
content: AUTH_EXPO_CLIENT_TEMPLATE,
|
|
10557
|
+
dependencyHintMessage: "Expo auth client needs native Better Auth and Expo storage dependencies.",
|
|
10558
|
+
dependencyHints: [
|
|
10559
|
+
BETTER_AUTH_EXPO_INSTALL_SPEC,
|
|
10560
|
+
EXPO_SECURE_STORE_INSTALL_SPEC,
|
|
10561
|
+
EXPO_NETWORK_INSTALL_SPEC
|
|
10562
|
+
]
|
|
10563
|
+
};
|
|
10564
|
+
return template;
|
|
10565
|
+
});
|
|
10100
10566
|
if (roots.projectContext.framework === "tanstack-start") return templates.filter((template) => template.id !== "auth-page" && template.id !== "auth-page-convex").map((template) => {
|
|
10101
10567
|
if (template.id === "auth-client") return {
|
|
10102
10568
|
...template,
|
|
@@ -10133,6 +10599,7 @@ const authRegistryItem = defineInternalRegistryItem({
|
|
|
10133
10599
|
buildAuthProviderPlanFile(params)
|
|
10134
10600
|
];
|
|
10135
10601
|
if (roots.projectContext?.mode === "next-app") files.push(buildAuthNextServerPlanFile(params), buildAuthNextRoutePlanFile(params));
|
|
10602
|
+
else if (roots.projectContext?.framework === "expo") files.push(buildAuthExpoPagePlanFile(params));
|
|
10136
10603
|
else if (roots.projectContext?.framework === "tanstack-start") files.push(buildAuthStartServerPlanFile(params), buildAuthStartRoutePlanFile(params), buildAuthStartServerCallPlanFile(params), buildAuthStartPagePlanFile(params));
|
|
10137
10604
|
return files;
|
|
10138
10605
|
},
|
|
@@ -11784,6 +12251,348 @@ const resolvePluginDocTopic = (topic) => {
|
|
|
11784
12251
|
};
|
|
11785
12252
|
};
|
|
11786
12253
|
|
|
12254
|
+
//#endregion
|
|
12255
|
+
//#region src/cli/registry/init/expo/init-expo-crpc.template.ts
|
|
12256
|
+
const INIT_EXPO_CRPC_TEMPLATE = `import { api } from '@convex/api';
|
|
12257
|
+
import { createCRPCContext } from 'kitcn/react';
|
|
12258
|
+
|
|
12259
|
+
export const { CRPCProvider, useCRPC, useCRPCClient } = createCRPCContext({
|
|
12260
|
+
api,
|
|
12261
|
+
convexSiteUrl: process.env.EXPO_PUBLIC_CONVEX_SITE_URL!,
|
|
12262
|
+
});
|
|
12263
|
+
`;
|
|
12264
|
+
|
|
12265
|
+
//#endregion
|
|
12266
|
+
//#region src/cli/registry/init/expo/init-expo-env.template.ts
|
|
12267
|
+
const INIT_EXPO_ENV_DEFAULTS = {
|
|
12268
|
+
EXPO_PUBLIC_CONVEX_URL: "http://127.0.0.1:3210",
|
|
12269
|
+
EXPO_PUBLIC_CONVEX_SITE_URL: "http://127.0.0.1:3211",
|
|
12270
|
+
EXPO_PUBLIC_SITE_URL: "http://localhost:3000"
|
|
12271
|
+
};
|
|
12272
|
+
function renderInitExpoEnvTemplate(source) {
|
|
12273
|
+
const existing = source ? parse(source) : {};
|
|
12274
|
+
const lines = Object.entries(INIT_EXPO_ENV_DEFAULTS).map(([key, value]) => `${key}=${existing[key] ?? value}`);
|
|
12275
|
+
for (const [key, value] of Object.entries(existing)) if (!(key in INIT_EXPO_ENV_DEFAULTS)) lines.push(`${key}=${value}`);
|
|
12276
|
+
return `${lines.join("\n")}\n`;
|
|
12277
|
+
}
|
|
12278
|
+
|
|
12279
|
+
//#endregion
|
|
12280
|
+
//#region src/cli/registry/init/expo/init-expo-env-types.template.ts
|
|
12281
|
+
const INIT_EXPO_ENV_TYPES_TEMPLATE = `/// <reference types="expo/types" />
|
|
12282
|
+
|
|
12283
|
+
declare module '*.module.css' {
|
|
12284
|
+
const classes: Record<string, string>;
|
|
12285
|
+
export default classes;
|
|
12286
|
+
}
|
|
12287
|
+
`;
|
|
12288
|
+
|
|
12289
|
+
//#endregion
|
|
12290
|
+
//#region src/cli/registry/init/expo/init-expo-explore.template.ts
|
|
12291
|
+
const INIT_EXPO_EXPLORE_TEMPLATE = `import { Redirect } from 'expo-router';
|
|
12292
|
+
|
|
12293
|
+
export default function ExploreScreen() {
|
|
12294
|
+
return <Redirect href="/" />;
|
|
12295
|
+
}
|
|
12296
|
+
`;
|
|
12297
|
+
|
|
12298
|
+
//#endregion
|
|
12299
|
+
//#region src/cli/registry/init/expo/init-expo-gitignore.template.ts
|
|
12300
|
+
const EXPO_ENV_TYPES_IGNORE_LINE = "expo-env.d.ts";
|
|
12301
|
+
const LINE_SPLIT_RE = /\r?\n/;
|
|
12302
|
+
function renderInitExpoGitignoreTemplate(source = "") {
|
|
12303
|
+
return `${source.split(LINE_SPLIT_RE).map((line) => line.trimEnd()).filter((line) => line.length > 0).filter((line) => line !== EXPO_ENV_TYPES_IGNORE_LINE).join("\n")}\n`;
|
|
12304
|
+
}
|
|
12305
|
+
|
|
12306
|
+
//#endregion
|
|
12307
|
+
//#region src/cli/registry/init/expo/init-expo-layout.template.ts
|
|
12308
|
+
const INIT_EXPO_LAYOUT_TEMPLATE = `import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
|
12309
|
+
import { Stack } from 'expo-router';
|
|
12310
|
+
import { StatusBar } from 'expo-status-bar';
|
|
12311
|
+
import { useColorScheme } from 'react-native';
|
|
12312
|
+
|
|
12313
|
+
import { Providers } from '@/components/providers';
|
|
12314
|
+
|
|
12315
|
+
export default function RootLayout() {
|
|
12316
|
+
const colorScheme = useColorScheme();
|
|
12317
|
+
|
|
12318
|
+
return (
|
|
12319
|
+
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
|
12320
|
+
<Providers>
|
|
12321
|
+
<Stack>
|
|
12322
|
+
<Stack.Screen name="index" options={{ title: 'Messages' }} />
|
|
12323
|
+
</Stack>
|
|
12324
|
+
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
|
|
12325
|
+
</Providers>
|
|
12326
|
+
</ThemeProvider>
|
|
12327
|
+
);
|
|
12328
|
+
}
|
|
12329
|
+
`;
|
|
12330
|
+
|
|
12331
|
+
//#endregion
|
|
12332
|
+
//#region src/cli/registry/init/expo/init-expo-messages-screen.template.ts
|
|
12333
|
+
const INIT_EXPO_MESSAGES_SCREEN_TEMPLATE = `import { useMutation, useQuery } from '@tanstack/react-query';
|
|
12334
|
+
import { useState } from 'react';
|
|
12335
|
+
import {
|
|
12336
|
+
Pressable,
|
|
12337
|
+
SafeAreaView,
|
|
12338
|
+
ScrollView,
|
|
12339
|
+
StyleSheet,
|
|
12340
|
+
Text,
|
|
12341
|
+
TextInput,
|
|
12342
|
+
View,
|
|
12343
|
+
} from 'react-native';
|
|
12344
|
+
|
|
12345
|
+
import { useCRPC } from '@/lib/convex/crpc';
|
|
12346
|
+
|
|
12347
|
+
export default function MessagesScreen() {
|
|
12348
|
+
const crpc = useCRPC();
|
|
12349
|
+
const [draft, setDraft] = useState('');
|
|
12350
|
+
const messagesQuery = useQuery(crpc.messages.list.queryOptions());
|
|
12351
|
+
const createMessage = useMutation(crpc.messages.create.mutationOptions());
|
|
12352
|
+
|
|
12353
|
+
async function handleSubmit() {
|
|
12354
|
+
const body = draft.trim();
|
|
12355
|
+
if (!body || createMessage.isPending) return;
|
|
12356
|
+
|
|
12357
|
+
try {
|
|
12358
|
+
await createMessage.mutateAsync({ body });
|
|
12359
|
+
setDraft('');
|
|
12360
|
+
} catch {}
|
|
12361
|
+
}
|
|
12362
|
+
|
|
12363
|
+
return (
|
|
12364
|
+
<SafeAreaView style={styles.safeArea}>
|
|
12365
|
+
<ScrollView
|
|
12366
|
+
contentContainerStyle={styles.content}
|
|
12367
|
+
keyboardShouldPersistTaps="handled"
|
|
12368
|
+
>
|
|
12369
|
+
<View style={styles.header}>
|
|
12370
|
+
<Text style={styles.kicker}>kitcn</Text>
|
|
12371
|
+
<Text style={styles.title}>Messages</Text>
|
|
12372
|
+
<Text style={styles.copy}>
|
|
12373
|
+
This screen is a tiny live query and mutation over kitcn. Start the
|
|
12374
|
+
backend, add a message, and watch the list update.
|
|
12375
|
+
</Text>
|
|
12376
|
+
</View>
|
|
12377
|
+
|
|
12378
|
+
<View style={styles.form}>
|
|
12379
|
+
<TextInput
|
|
12380
|
+
autoCapitalize="sentences"
|
|
12381
|
+
maxLength={120}
|
|
12382
|
+
onChangeText={setDraft}
|
|
12383
|
+
placeholder="Write a message"
|
|
12384
|
+
style={styles.input}
|
|
12385
|
+
value={draft}
|
|
12386
|
+
/>
|
|
12387
|
+
<Pressable
|
|
12388
|
+
disabled={createMessage.isPending || draft.trim().length === 0}
|
|
12389
|
+
onPress={() => {
|
|
12390
|
+
void handleSubmit();
|
|
12391
|
+
}}
|
|
12392
|
+
style={[
|
|
12393
|
+
styles.button,
|
|
12394
|
+
(createMessage.isPending || draft.trim().length === 0) &&
|
|
12395
|
+
styles.buttonDisabled,
|
|
12396
|
+
]}
|
|
12397
|
+
>
|
|
12398
|
+
<Text style={styles.buttonText}>
|
|
12399
|
+
{createMessage.isPending ? 'Saving...' : 'Add message'}
|
|
12400
|
+
</Text>
|
|
12401
|
+
</Pressable>
|
|
12402
|
+
</View>
|
|
12403
|
+
|
|
12404
|
+
{messagesQuery.isPending ? (
|
|
12405
|
+
<Text style={styles.muted}>Loading messages...</Text>
|
|
12406
|
+
) : messagesQuery.isError ? (
|
|
12407
|
+
<View style={styles.notice}>
|
|
12408
|
+
<Text style={styles.noticeText}>
|
|
12409
|
+
Backend not ready. Start kitcn dev and reload the app.
|
|
12410
|
+
</Text>
|
|
12411
|
+
</View>
|
|
12412
|
+
) : messagesQuery.data.length === 0 ? (
|
|
12413
|
+
<View style={styles.notice}>
|
|
12414
|
+
<Text style={styles.noticeText}>No messages yet. Add the first one.</Text>
|
|
12415
|
+
</View>
|
|
12416
|
+
) : (
|
|
12417
|
+
<View style={styles.list}>
|
|
12418
|
+
{messagesQuery.data.map((message) => (
|
|
12419
|
+
<View key={message.id} style={styles.card}>
|
|
12420
|
+
<View style={styles.cardHeader}>
|
|
12421
|
+
<Text style={styles.cardBody}>{message.body}</Text>
|
|
12422
|
+
<Text style={styles.cardTime}>
|
|
12423
|
+
{message.createdAt.toLocaleTimeString([], {
|
|
12424
|
+
hour: '2-digit',
|
|
12425
|
+
minute: '2-digit',
|
|
12426
|
+
})}
|
|
12427
|
+
</Text>
|
|
12428
|
+
</View>
|
|
12429
|
+
</View>
|
|
12430
|
+
))}
|
|
12431
|
+
</View>
|
|
12432
|
+
)}
|
|
12433
|
+
</ScrollView>
|
|
12434
|
+
</SafeAreaView>
|
|
12435
|
+
);
|
|
12436
|
+
}
|
|
12437
|
+
|
|
12438
|
+
const styles = StyleSheet.create({
|
|
12439
|
+
safeArea: {
|
|
12440
|
+
flex: 1,
|
|
12441
|
+
backgroundColor: '#ffffff',
|
|
12442
|
+
},
|
|
12443
|
+
content: {
|
|
12444
|
+
gap: 20,
|
|
12445
|
+
paddingHorizontal: 20,
|
|
12446
|
+
paddingVertical: 24,
|
|
12447
|
+
},
|
|
12448
|
+
header: {
|
|
12449
|
+
gap: 8,
|
|
12450
|
+
},
|
|
12451
|
+
kicker: {
|
|
12452
|
+
color: '#6b7280',
|
|
12453
|
+
fontSize: 12,
|
|
12454
|
+
fontWeight: '600',
|
|
12455
|
+
letterSpacing: 2,
|
|
12456
|
+
textTransform: 'uppercase',
|
|
12457
|
+
},
|
|
12458
|
+
title: {
|
|
12459
|
+
color: '#111827',
|
|
12460
|
+
fontSize: 28,
|
|
12461
|
+
fontWeight: '700',
|
|
12462
|
+
},
|
|
12463
|
+
copy: {
|
|
12464
|
+
color: '#4b5563',
|
|
12465
|
+
fontSize: 15,
|
|
12466
|
+
lineHeight: 22,
|
|
12467
|
+
},
|
|
12468
|
+
form: {
|
|
12469
|
+
gap: 12,
|
|
12470
|
+
},
|
|
12471
|
+
input: {
|
|
12472
|
+
borderColor: '#d1d5db',
|
|
12473
|
+
borderRadius: 12,
|
|
12474
|
+
borderWidth: 1,
|
|
12475
|
+
fontSize: 16,
|
|
12476
|
+
minHeight: 48,
|
|
12477
|
+
paddingHorizontal: 14,
|
|
12478
|
+
paddingVertical: 12,
|
|
12479
|
+
},
|
|
12480
|
+
button: {
|
|
12481
|
+
alignItems: 'center',
|
|
12482
|
+
backgroundColor: '#111827',
|
|
12483
|
+
borderRadius: 12,
|
|
12484
|
+
minHeight: 48,
|
|
12485
|
+
justifyContent: 'center',
|
|
12486
|
+
paddingHorizontal: 16,
|
|
12487
|
+
},
|
|
12488
|
+
buttonDisabled: {
|
|
12489
|
+
opacity: 0.5,
|
|
12490
|
+
},
|
|
12491
|
+
buttonText: {
|
|
12492
|
+
color: '#ffffff',
|
|
12493
|
+
fontSize: 15,
|
|
12494
|
+
fontWeight: '600',
|
|
12495
|
+
},
|
|
12496
|
+
muted: {
|
|
12497
|
+
color: '#6b7280',
|
|
12498
|
+
fontSize: 14,
|
|
12499
|
+
},
|
|
12500
|
+
notice: {
|
|
12501
|
+
borderColor: '#d1d5db',
|
|
12502
|
+
borderRadius: 12,
|
|
12503
|
+
borderStyle: 'dashed',
|
|
12504
|
+
borderWidth: 1,
|
|
12505
|
+
paddingHorizontal: 16,
|
|
12506
|
+
paddingVertical: 18,
|
|
12507
|
+
},
|
|
12508
|
+
noticeText: {
|
|
12509
|
+
color: '#4b5563',
|
|
12510
|
+
fontSize: 14,
|
|
12511
|
+
lineHeight: 20,
|
|
12512
|
+
},
|
|
12513
|
+
list: {
|
|
12514
|
+
gap: 12,
|
|
12515
|
+
},
|
|
12516
|
+
card: {
|
|
12517
|
+
backgroundColor: '#f9fafb',
|
|
12518
|
+
borderColor: '#e5e7eb',
|
|
12519
|
+
borderRadius: 12,
|
|
12520
|
+
borderWidth: 1,
|
|
12521
|
+
paddingHorizontal: 16,
|
|
12522
|
+
paddingVertical: 14,
|
|
12523
|
+
},
|
|
12524
|
+
cardHeader: {
|
|
12525
|
+
flexDirection: 'row',
|
|
12526
|
+
gap: 12,
|
|
12527
|
+
justifyContent: 'space-between',
|
|
12528
|
+
},
|
|
12529
|
+
cardBody: {
|
|
12530
|
+
color: '#111827',
|
|
12531
|
+
flex: 1,
|
|
12532
|
+
fontSize: 15,
|
|
12533
|
+
lineHeight: 22,
|
|
12534
|
+
},
|
|
12535
|
+
cardTime: {
|
|
12536
|
+
color: '#6b7280',
|
|
12537
|
+
fontSize: 12,
|
|
12538
|
+
fontVariant: ['tabular-nums'],
|
|
12539
|
+
},
|
|
12540
|
+
});
|
|
12541
|
+
`;
|
|
12542
|
+
|
|
12543
|
+
//#endregion
|
|
12544
|
+
//#region src/cli/registry/init/expo/init-expo-package-json.template.ts
|
|
12545
|
+
const INIT_EXPO_CODEGEN_SCRIPT = "kitcn codegen";
|
|
12546
|
+
const INIT_EXPO_PRIMARY_CODEGEN_SCRIPT_NAME = "codegen";
|
|
12547
|
+
const INIT_EXPO_FALLBACK_CODEGEN_SCRIPT_NAME = "convex:codegen";
|
|
12548
|
+
const INIT_EXPO_CONVEX_DEV_SCRIPT_NAME = "convex:dev";
|
|
12549
|
+
const INIT_EXPO_CONVEX_DEV_SCRIPT = "kitcn dev";
|
|
12550
|
+
const INIT_EXPO_CONVEX_TYPECHECK_SCRIPT_NAME = "typecheck:convex";
|
|
12551
|
+
const getInitExpoConvexTypecheckScript = (functionsDirRelative = "convex/functions") => `tsc --noEmit --project ${functionsDirRelative}/tsconfig.json`;
|
|
12552
|
+
const INIT_EXPO_PACKAGE_JSON_DEPENDENCIES = {
|
|
12553
|
+
"@opentelemetry/api": SUPPORTED_DEPENDENCY_VERSIONS.opentelemetryApi.exact,
|
|
12554
|
+
superjson: "2.2.6"
|
|
12555
|
+
};
|
|
12556
|
+
const INIT_EXPO_PACKAGE_JSON_DEV_DEPENDENCIES = { "@types/bun": "latest" };
|
|
12557
|
+
const getInitExpoPackageJsonDevDependencies = (options) => ({
|
|
12558
|
+
...INIT_EXPO_PACKAGE_JSON_DEV_DEPENDENCIES,
|
|
12559
|
+
...options.backend === "concave" ? { "@concavejs/cli": "latest" } : {}
|
|
12560
|
+
});
|
|
12561
|
+
function renderInitExpoPackageJsonTemplate(source, options = {}) {
|
|
12562
|
+
const existing = source ? JSON.parse(source) : {};
|
|
12563
|
+
const nextScripts = {
|
|
12564
|
+
...existing.scripts,
|
|
12565
|
+
typecheck: "tsc --noEmit && bun run typecheck:convex"
|
|
12566
|
+
};
|
|
12567
|
+
if (!nextScripts[INIT_EXPO_PRIMARY_CODEGEN_SCRIPT_NAME]) nextScripts[INIT_EXPO_PRIMARY_CODEGEN_SCRIPT_NAME] = INIT_EXPO_CODEGEN_SCRIPT;
|
|
12568
|
+
else if (!nextScripts[INIT_EXPO_FALLBACK_CODEGEN_SCRIPT_NAME]) nextScripts[INIT_EXPO_FALLBACK_CODEGEN_SCRIPT_NAME] = INIT_EXPO_CODEGEN_SCRIPT;
|
|
12569
|
+
if (!nextScripts[INIT_EXPO_CONVEX_DEV_SCRIPT_NAME]) nextScripts[INIT_EXPO_CONVEX_DEV_SCRIPT_NAME] = INIT_EXPO_CONVEX_DEV_SCRIPT;
|
|
12570
|
+
if (!nextScripts[INIT_EXPO_CONVEX_TYPECHECK_SCRIPT_NAME]) nextScripts[INIT_EXPO_CONVEX_TYPECHECK_SCRIPT_NAME] = getInitExpoConvexTypecheckScript(options.functionsDirRelative);
|
|
12571
|
+
return `${JSON.stringify({
|
|
12572
|
+
...existing,
|
|
12573
|
+
scripts: nextScripts,
|
|
12574
|
+
dependencies: {
|
|
12575
|
+
...existing.dependencies,
|
|
12576
|
+
...INIT_EXPO_PACKAGE_JSON_DEPENDENCIES
|
|
12577
|
+
},
|
|
12578
|
+
devDependencies: {
|
|
12579
|
+
...existing.devDependencies,
|
|
12580
|
+
...getInitExpoPackageJsonDevDependencies(options)
|
|
12581
|
+
}
|
|
12582
|
+
}, null, 2)}\n`;
|
|
12583
|
+
}
|
|
12584
|
+
|
|
12585
|
+
//#endregion
|
|
12586
|
+
//#region src/cli/registry/init/expo/init-expo-providers.template.ts
|
|
12587
|
+
const INIT_EXPO_PROVIDERS_TEMPLATE = `import type { ReactNode } from 'react';
|
|
12588
|
+
|
|
12589
|
+
import { AppConvexProvider } from '@/lib/convex/convex-provider';
|
|
12590
|
+
|
|
12591
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
12592
|
+
return <AppConvexProvider>{children}</AppConvexProvider>;
|
|
12593
|
+
}
|
|
12594
|
+
`;
|
|
12595
|
+
|
|
11787
12596
|
//#endregion
|
|
11788
12597
|
//#region src/cli/registry/init/init-convex-config.template.ts
|
|
11789
12598
|
const INIT_CONVEX_CONFIG_TEMPLATE = `{
|
|
@@ -12667,6 +13476,8 @@ const LEADING_SLASHES_RE = /^\/+/;
|
|
|
12667
13476
|
const AGGREGATE_STATE_RELATIVE_PATH = join(".convex", "kitcn", "aggregate-backfill-state.json");
|
|
12668
13477
|
const AGGREGATE_STATE_VERSION = 1;
|
|
12669
13478
|
const INIT_SHADCN_PACKAGE_SPEC = "shadcn@4.3.0";
|
|
13479
|
+
const INIT_EXPO_PACKAGE_SPEC = "create-expo-app@latest";
|
|
13480
|
+
const INIT_EXPO_TEMPLATE_SPEC = "default@sdk-55";
|
|
12670
13481
|
const INIT_LOCAL_BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
12671
13482
|
const LOCAL_BACKEND_NOT_RUNNING_RE = /Local backend isn't running/i;
|
|
12672
13483
|
const INIT_GENERATED_SERVER_STUB_TEMPLATE = `// @ts-nocheck
|
|
@@ -12701,6 +13512,7 @@ const SUPPORTED_PLUGINS = new Set(getSupportedPluginKeys());
|
|
|
12701
13512
|
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12702
13513
|
const SUPPORTED_INIT_TEMPLATES = [
|
|
12703
13514
|
"next",
|
|
13515
|
+
"expo",
|
|
12704
13516
|
"start",
|
|
12705
13517
|
"vite"
|
|
12706
13518
|
];
|
|
@@ -13096,6 +13908,38 @@ async function createProjectWithShadcn(params) {
|
|
|
13096
13908
|
});
|
|
13097
13909
|
}
|
|
13098
13910
|
}
|
|
13911
|
+
async function createProjectWithExpo(params) {
|
|
13912
|
+
const stagingRoot = fs.existsSync(params.projectDir) && fs.readdirSync(params.projectDir).length === 0 ? fs.mkdtempSync(join(dirname(params.projectDir), ".kitcn-expo-")) : null;
|
|
13913
|
+
const expoCwd = stagingRoot ?? dirname(params.projectDir);
|
|
13914
|
+
fs.mkdirSync(expoCwd, { recursive: true });
|
|
13915
|
+
const projectName = basename(params.projectDir);
|
|
13916
|
+
const command = detectPackageManager(expoCwd) === "bun" ? "bunx" : "npx";
|
|
13917
|
+
const args = [
|
|
13918
|
+
INIT_EXPO_PACKAGE_SPEC,
|
|
13919
|
+
projectName,
|
|
13920
|
+
"--template",
|
|
13921
|
+
INIT_EXPO_TEMPLATE_SPEC,
|
|
13922
|
+
"--no-install",
|
|
13923
|
+
...params.yes ? ["--yes"] : []
|
|
13924
|
+
];
|
|
13925
|
+
try {
|
|
13926
|
+
if (((await params.execaFn(command, args, {
|
|
13927
|
+
cwd: expoCwd,
|
|
13928
|
+
env: createCommandEnv(),
|
|
13929
|
+
reject: false,
|
|
13930
|
+
stdio: "inherit"
|
|
13931
|
+
})).exitCode ?? 0) !== 0) throw new Error(`Expo init failed: ${command} ${args.join(" ")}`);
|
|
13932
|
+
if (stagingRoot) moveStagedProjectIntoExistingDir({
|
|
13933
|
+
stagedProjectDir: join(stagingRoot, projectName),
|
|
13934
|
+
targetDir: params.projectDir
|
|
13935
|
+
});
|
|
13936
|
+
} finally {
|
|
13937
|
+
if (stagingRoot) fs.rmSync(stagingRoot, {
|
|
13938
|
+
recursive: true,
|
|
13939
|
+
force: true
|
|
13940
|
+
});
|
|
13941
|
+
}
|
|
13942
|
+
}
|
|
13099
13943
|
function buildMissingShadcnScaffoldMessage(projectDir) {
|
|
13100
13944
|
return [
|
|
13101
13945
|
"Shadcn exited without creating a supported local scaffold.",
|
|
@@ -13103,6 +13947,9 @@ function buildMissingShadcnScaffoldMessage(projectDir) {
|
|
|
13103
13947
|
`Run the generated shadcn command from ui.shadcn.com in ${normalizePath(relative(process.cwd(), projectDir) || ".")} then re-run \`kitcn init --yes\` to adopt it.`
|
|
13104
13948
|
].join(" ");
|
|
13105
13949
|
}
|
|
13950
|
+
function buildMissingExpoScaffoldMessage(projectDir) {
|
|
13951
|
+
return ["create-expo-app exited without creating a supported local scaffold.", `Re-run \`kitcn init -t expo --yes\` in ${normalizePath(relative(process.cwd(), projectDir) || ".")}, or try \`${INIT_EXPO_PACKAGE_SPEC} ${basename(projectDir)} --template ${INIT_EXPO_TEMPLATE_SPEC}\` directly.`].join(" ");
|
|
13952
|
+
}
|
|
13106
13953
|
function moveStagedProjectIntoExistingDir(params) {
|
|
13107
13954
|
if (!fs.existsSync(params.stagedProjectDir)) throw new Error(buildMissingShadcnScaffoldMessage(params.targetDir));
|
|
13108
13955
|
if (!fs.existsSync(params.targetDir) || fs.readdirSync(params.targetDir).length > 0) throw new Error(`Cannot move staged project into non-empty target ${params.targetDir}.`);
|
|
@@ -13254,6 +14101,137 @@ function buildInitNextOwnedScaffoldFiles(context, functionsDirRelative, backend,
|
|
|
13254
14101
|
});
|
|
13255
14102
|
return files;
|
|
13256
14103
|
}
|
|
14104
|
+
function buildInitExpoOwnedScaffoldFiles(context, functionsDirRelative, backend, includeDemoFiles) {
|
|
14105
|
+
const files = [
|
|
14106
|
+
{
|
|
14107
|
+
kind: "config",
|
|
14108
|
+
relativePath: "package.json",
|
|
14109
|
+
requiresExplicitOverwrite: false,
|
|
14110
|
+
content: ({ existingContent }) => renderInitExpoPackageJsonTemplate(existingContent, {
|
|
14111
|
+
backend,
|
|
14112
|
+
functionsDirRelative
|
|
14113
|
+
}),
|
|
14114
|
+
createReason: "Create baseline package.json scripts for the Expo scaffold.",
|
|
14115
|
+
updateReason: "Update package.json scripts for the Expo scaffold.",
|
|
14116
|
+
skipReason: "package.json scripts already match the Expo scaffold."
|
|
14117
|
+
},
|
|
14118
|
+
{
|
|
14119
|
+
kind: "env",
|
|
14120
|
+
relativePath: ".env.local",
|
|
14121
|
+
requiresExplicitOverwrite: false,
|
|
14122
|
+
content: ({ existingContent }) => renderInitExpoEnvTemplate(existingContent),
|
|
14123
|
+
createReason: "Create baseline .env.local for the Expo scaffold.",
|
|
14124
|
+
updateReason: "Update baseline .env.local for the Expo scaffold.",
|
|
14125
|
+
skipReason: ".env.local already matches the Expo scaffold."
|
|
14126
|
+
},
|
|
14127
|
+
{
|
|
14128
|
+
kind: "config",
|
|
14129
|
+
relativePath: "expo-env.d.ts",
|
|
14130
|
+
requiresExplicitOverwrite: true,
|
|
14131
|
+
content: INIT_EXPO_ENV_TYPES_TEMPLATE,
|
|
14132
|
+
createReason: "Create expo-env.d.ts for the Expo scaffold.",
|
|
14133
|
+
updateReason: "Update expo-env.d.ts for the Expo scaffold.",
|
|
14134
|
+
skipReason: "expo-env.d.ts already matches the Expo scaffold."
|
|
14135
|
+
},
|
|
14136
|
+
{
|
|
14137
|
+
kind: "config",
|
|
14138
|
+
relativePath: ".gitignore",
|
|
14139
|
+
requiresExplicitOverwrite: false,
|
|
14140
|
+
content: ({ existingContent }) => renderInitExpoGitignoreTemplate(existingContent ?? ""),
|
|
14141
|
+
createReason: "Create .gitignore for the Expo scaffold.",
|
|
14142
|
+
updateReason: "Update .gitignore so the Expo scaffold keeps expo-env.d.ts tracked.",
|
|
14143
|
+
skipReason: ".gitignore already matches the Expo scaffold."
|
|
14144
|
+
},
|
|
14145
|
+
{
|
|
14146
|
+
kind: "scaffold",
|
|
14147
|
+
relativePath: `${context.componentsDir}/providers.tsx`,
|
|
14148
|
+
requiresExplicitOverwrite: true,
|
|
14149
|
+
content: INIT_EXPO_PROVIDERS_TEMPLATE,
|
|
14150
|
+
createReason: `Create baseline ${context.componentsDir}/providers.tsx for the Expo scaffold.`,
|
|
14151
|
+
updateReason: `Update ${context.componentsDir}/providers.tsx for the Expo scaffold.`,
|
|
14152
|
+
skipReason: `${context.componentsDir}/providers.tsx already matches the Expo scaffold.`
|
|
14153
|
+
},
|
|
14154
|
+
{
|
|
14155
|
+
kind: "scaffold",
|
|
14156
|
+
relativePath: `${context.convexClientDir}/query-client.ts`,
|
|
14157
|
+
requiresExplicitOverwrite: true,
|
|
14158
|
+
content: INIT_NEXT_QUERY_CLIENT_TEMPLATE,
|
|
14159
|
+
createReason: `Create baseline ${context.convexClientDir}/query-client.ts for the Expo scaffold.`,
|
|
14160
|
+
updateReason: `Update ${context.convexClientDir}/query-client.ts for the Expo scaffold.`,
|
|
14161
|
+
skipReason: `${context.convexClientDir}/query-client.ts already matches the Expo scaffold.`
|
|
14162
|
+
},
|
|
14163
|
+
{
|
|
14164
|
+
kind: "scaffold",
|
|
14165
|
+
relativePath: `${context.convexClientDir}/crpc.tsx`,
|
|
14166
|
+
requiresExplicitOverwrite: true,
|
|
14167
|
+
content: INIT_EXPO_CRPC_TEMPLATE,
|
|
14168
|
+
createReason: `Create baseline ${context.convexClientDir}/crpc.tsx for the Expo scaffold.`,
|
|
14169
|
+
updateReason: `Update ${context.convexClientDir}/crpc.tsx for the Expo scaffold.`,
|
|
14170
|
+
skipReason: `${context.convexClientDir}/crpc.tsx already matches the Expo scaffold.`
|
|
14171
|
+
},
|
|
14172
|
+
{
|
|
14173
|
+
kind: "scaffold",
|
|
14174
|
+
relativePath: `${context.convexClientDir}/convex-provider.tsx`,
|
|
14175
|
+
requiresExplicitOverwrite: true,
|
|
14176
|
+
content: INIT_EXPO_CONVEX_PROVIDER_TEMPLATE,
|
|
14177
|
+
createReason: `Create baseline ${context.convexClientDir}/convex-provider.tsx for the Expo scaffold.`,
|
|
14178
|
+
updateReason: `Update ${context.convexClientDir}/convex-provider.tsx for the Expo scaffold.`,
|
|
14179
|
+
skipReason: `${context.convexClientDir}/convex-provider.tsx already matches the Expo scaffold.`
|
|
14180
|
+
},
|
|
14181
|
+
{
|
|
14182
|
+
kind: "config",
|
|
14183
|
+
relativePath: join(functionsDirRelative, "tsconfig.json"),
|
|
14184
|
+
managedBaselineContent: getManagedConvexTsconfigBaselines(functionsDirRelative),
|
|
14185
|
+
requiresExplicitOverwrite: true,
|
|
14186
|
+
content: ({ existingContent }) => typeof existingContent === "string" ? patchInitConvexTsconfigContent(existingContent, functionsDirRelative) : renderInitConvexTsconfigTemplate(functionsDirRelative),
|
|
14187
|
+
createReason: `Create ${join(functionsDirRelative, "tsconfig.json")} for kitcn functions.`,
|
|
14188
|
+
updateReason: `Patch ${join(functionsDirRelative, "tsconfig.json")} for kitcn functions.`,
|
|
14189
|
+
skipReason: `${join(functionsDirRelative, "tsconfig.json")} already matches the kitcn functions project.`
|
|
14190
|
+
}
|
|
14191
|
+
];
|
|
14192
|
+
if (includeDemoFiles) files.push({
|
|
14193
|
+
kind: "scaffold",
|
|
14194
|
+
relativePath: `${context.appDir}/_layout.tsx`,
|
|
14195
|
+
requiresExplicitOverwrite: true,
|
|
14196
|
+
content: INIT_EXPO_LAYOUT_TEMPLATE,
|
|
14197
|
+
createReason: `Create ${context.appDir}/_layout.tsx for the Expo scaffold.`,
|
|
14198
|
+
updateReason: `Update ${context.appDir}/_layout.tsx for the Expo scaffold.`,
|
|
14199
|
+
skipReason: `${context.appDir}/_layout.tsx already matches the Expo scaffold.`
|
|
14200
|
+
}, {
|
|
14201
|
+
kind: "scaffold",
|
|
14202
|
+
relativePath: `${context.appDir}/index.tsx`,
|
|
14203
|
+
requiresExplicitOverwrite: true,
|
|
14204
|
+
content: INIT_EXPO_MESSAGES_SCREEN_TEMPLATE,
|
|
14205
|
+
createReason: `Create ${context.appDir}/index.tsx as the minimal kitcn demo route.`,
|
|
14206
|
+
updateReason: `Update ${context.appDir}/index.tsx for the kitcn demo route.`,
|
|
14207
|
+
skipReason: `${context.appDir}/index.tsx already matches the kitcn demo route.`
|
|
14208
|
+
}, {
|
|
14209
|
+
kind: "scaffold",
|
|
14210
|
+
relativePath: `${context.appDir}/explore.tsx`,
|
|
14211
|
+
requiresExplicitOverwrite: true,
|
|
14212
|
+
content: INIT_EXPO_EXPLORE_TEMPLATE,
|
|
14213
|
+
createReason: `Create ${context.appDir}/explore.tsx as a redirect back to the kitcn demo route.`,
|
|
14214
|
+
updateReason: `Update ${context.appDir}/explore.tsx as a redirect back to the kitcn demo route.`,
|
|
14215
|
+
skipReason: `${context.appDir}/explore.tsx already redirects to the kitcn demo route.`
|
|
14216
|
+
}, {
|
|
14217
|
+
kind: "schema",
|
|
14218
|
+
relativePath: `${functionsDirRelative}/schema.ts`,
|
|
14219
|
+
requiresExplicitOverwrite: true,
|
|
14220
|
+
content: INIT_NEXT_SCHEMA_TEMPLATE,
|
|
14221
|
+
createReason: `Create ${functionsDirRelative}/schema.ts with the minimal kitcn demo schema.`,
|
|
14222
|
+
updateReason: `Update ${functionsDirRelative}/schema.ts with the minimal kitcn demo schema.`,
|
|
14223
|
+
skipReason: `${functionsDirRelative}/schema.ts already matches the kitcn demo schema.`
|
|
14224
|
+
}, {
|
|
14225
|
+
kind: "scaffold",
|
|
14226
|
+
relativePath: `${functionsDirRelative}/messages.ts`,
|
|
14227
|
+
requiresExplicitOverwrite: true,
|
|
14228
|
+
content: renderInitNextMessagesTemplate(functionsDirRelative),
|
|
14229
|
+
createReason: `Create ${functionsDirRelative}/messages.ts for the kitcn demo route.`,
|
|
14230
|
+
updateReason: `Update ${functionsDirRelative}/messages.ts for the kitcn demo route.`,
|
|
14231
|
+
skipReason: `${functionsDirRelative}/messages.ts already matches the kitcn demo route.`
|
|
14232
|
+
});
|
|
14233
|
+
return files;
|
|
14234
|
+
}
|
|
13257
14235
|
function buildInitReactOwnedScaffoldFiles(context, functionsDirRelative, backend) {
|
|
13258
14236
|
return [
|
|
13259
14237
|
{
|
|
@@ -13661,6 +14639,19 @@ function buildInitNextLayoutPlanFile(context) {
|
|
|
13661
14639
|
skipReason: `${context.appDir}/layout.tsx already mounts Providers.`
|
|
13662
14640
|
});
|
|
13663
14641
|
}
|
|
14642
|
+
function buildInitExpoTsconfigPlanFile(context) {
|
|
14643
|
+
const filePath = resolve(process.cwd(), "tsconfig.json");
|
|
14644
|
+
if (!fs.existsSync(filePath)) throw new Error("Could not patch tsconfig.json: the Expo scaffold did not create a tsconfig file.");
|
|
14645
|
+
return createPlanFile({
|
|
14646
|
+
kind: "config",
|
|
14647
|
+
filePath,
|
|
14648
|
+
requiresExplicitOverwrite: false,
|
|
14649
|
+
content: patchInitTsconfigContent(fs.readFileSync(filePath, "utf8"), context),
|
|
14650
|
+
updateReason: "Patch tsconfig.json to keep the Expo alias and add @convex/*.",
|
|
14651
|
+
createReason: "Patch tsconfig.json to keep the Expo alias and add @convex/*.",
|
|
14652
|
+
skipReason: "tsconfig.json already includes the kitcn alias."
|
|
14653
|
+
});
|
|
14654
|
+
}
|
|
13664
14655
|
function buildInitNextTsconfigPlanFile(context) {
|
|
13665
14656
|
const filePath = resolve(process.cwd(), "tsconfig.json");
|
|
13666
14657
|
if (!fs.existsSync(filePath)) throw new Error("Could not patch tsconfig.json: shadcn did not create a tsconfig file.");
|
|
@@ -13773,7 +14764,7 @@ function buildTemplateInitializationPlanFiles(params) {
|
|
|
13773
14764
|
allowUnsupported: true
|
|
13774
14765
|
});
|
|
13775
14766
|
if (!projectContext) return [];
|
|
13776
|
-
const plannedOwnedFiles = (projectContext.mode === "next-app" ? buildInitNextOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : projectContext.framework === "tanstack-start" ? buildInitStartOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : buildInitReactOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend)).map((file) => {
|
|
14767
|
+
const plannedOwnedFiles = (projectContext.mode === "next-app" ? buildInitNextOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : projectContext.framework === "expo" ? buildInitExpoOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : projectContext.framework === "tanstack-start" ? buildInitStartOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : buildInitReactOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend)).map((file) => {
|
|
13777
14768
|
const filePath = resolve(process.cwd(), file.relativePath);
|
|
13778
14769
|
const existingContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : void 0;
|
|
13779
14770
|
const nextContent = typeof file.content === "function" ? file.content({ existingContent }) : file.content;
|
|
@@ -13803,6 +14794,7 @@ function buildTemplateInitializationPlanFiles(params) {
|
|
|
13803
14794
|
buildInitNextLayoutPlanFile(projectContext)
|
|
13804
14795
|
];
|
|
13805
14796
|
}
|
|
14797
|
+
if (projectContext.framework === "expo") return [...plannedOwnedFiles, buildInitExpoTsconfigPlanFile(projectContext)];
|
|
13806
14798
|
if (projectContext.framework === "tanstack-start") return [
|
|
13807
14799
|
...plannedOwnedFiles,
|
|
13808
14800
|
buildInitReactRootTsconfigPlanFile(projectContext),
|
|
@@ -14031,7 +15023,12 @@ async function withWorkingDirectory(cwd, fn) {
|
|
|
14031
15023
|
}
|
|
14032
15024
|
}
|
|
14033
15025
|
async function runScaffoldCommandFlow(params) {
|
|
14034
|
-
if (params.template) await
|
|
15026
|
+
if (params.template) if (params.template === "expo") await createProjectWithExpo({
|
|
15027
|
+
projectDir: params.projectDir,
|
|
15028
|
+
yes: params.yes,
|
|
15029
|
+
execaFn: params.execaFn
|
|
15030
|
+
});
|
|
15031
|
+
else await createProjectWithShadcn({
|
|
14035
15032
|
projectDir: params.projectDir,
|
|
14036
15033
|
template: params.template,
|
|
14037
15034
|
yes: params.yes,
|
|
@@ -14043,7 +15040,7 @@ async function runScaffoldCommandFlow(params) {
|
|
|
14043
15040
|
cwd: scaffoldProjectDir,
|
|
14044
15041
|
allowMissing: true,
|
|
14045
15042
|
allowUnsupported: true
|
|
14046
|
-
})) throw new Error(buildMissingShadcnScaffoldMessage(scaffoldProjectDir));
|
|
15043
|
+
})) throw new Error(params.template === "expo" ? buildMissingExpoScaffoldMessage(scaffoldProjectDir) : buildMissingShadcnScaffoldMessage(scaffoldProjectDir));
|
|
14047
15044
|
return withWorkingDirectory(scaffoldProjectDir, async () => {
|
|
14048
15045
|
const config = params.loadCliConfigFn(params.configPath);
|
|
14049
15046
|
const backend = resolveConfiguredBackend({
|
|
@@ -14109,7 +15106,7 @@ async function runInitCommandFlow(params) {
|
|
|
14109
15106
|
allowUnsupported: true
|
|
14110
15107
|
});
|
|
14111
15108
|
if (template !== void 0 || params.initArgs.defaults || params.initArgs.name !== void 0) {
|
|
14112
|
-
if (!template) throw new Error("Fresh app scaffolding requires `kitcn init -t <next|start|vite>`.");
|
|
15109
|
+
if (!template) throw new Error("Fresh app scaffolding requires `kitcn init -t <next|expo|start|vite>`.");
|
|
14113
15110
|
if (existingProjectContext) throw new Error(`Existing supported app scaffold detected. Run \`kitcn init --yes\` in ${normalizePath(relative(process.cwd(), projectDir) || ".")} to adopt the current project.`);
|
|
14114
15111
|
return runScaffoldCommandFlow({
|
|
14115
15112
|
allowCodegenBootstrapFallback: !params.initArgs.json,
|
|
@@ -14131,7 +15128,8 @@ async function runInitCommandFlow(params) {
|
|
|
14131
15128
|
realConcavePath: params.realConcavePath
|
|
14132
15129
|
});
|
|
14133
15130
|
}
|
|
14134
|
-
if (
|
|
15131
|
+
if (existingProjectContext?.framework === "expo") throw new Error("Expo adoption is not supported yet. Start with `kitcn init -t expo --yes`.");
|
|
15132
|
+
if (!existingProjectContext) throw new Error("Could not detect a supported app scaffold. Use `kitcn init -t <next|expo|start|vite>` for a fresh app.");
|
|
14135
15133
|
return runScaffoldCommandFlow({
|
|
14136
15134
|
allowCodegenBootstrapFallback: !params.initArgs.json,
|
|
14137
15135
|
projectDir,
|