kitcn 0.12.4 → 0.12.6

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/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as isColorEnabled, c as EnableRLS, d as TableName, f as createSystemFields, i as highlighter, l as OrmSchemaExtensions, n as getConvexConfig, o as getSchemaRelations, r as logger, s as Columns, t as generateMeta, u as RlsPolicies } from "./codegen-lF80HSWu.mjs";
2
+ import { a as isColorEnabled, c as EnableRLS, d as TableName, f as createSystemFields, i as highlighter, l as OrmSchemaExtensions, n as getConvexConfig, o as getSchemaRelations, r as logger, s as Columns, t as generateMeta, u as RlsPolicies } from "./codegen-B60fOYhd.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import fs, { existsSync, readFileSync } from "node:fs";
5
5
  import path, { basename, delimiter, dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
@@ -2330,10 +2330,10 @@ function mapFrameworkToScaffoldMode(framework) {
2330
2330
  }
2331
2331
  function resolveProjectScaffoldContext(params = {}) {
2332
2332
  const cwd = params.cwd ?? process.cwd();
2333
- const detectedFramework = detectProjectFramework(cwd) ?? (params.template === "next" ? "next-app" : params.template === "vite" ? "vite" : null);
2333
+ const detectedFramework = (params.template === "next" ? "next-app" : params.template === "start" ? "tanstack-start" : params.template === "vite" ? "vite" : null) ?? detectProjectFramework(cwd);
2334
2334
  if (!detectedFramework) {
2335
2335
  if (params.allowMissing) return null;
2336
- throw new Error("Could not detect a supported app scaffold. Supported modes currently start from `next` or `vite`.");
2336
+ throw new Error("Could not detect a supported app scaffold. Supported modes currently start from `next`, `start`, or `vite`.");
2337
2337
  }
2338
2338
  let mode;
2339
2339
  try {
@@ -2451,6 +2451,8 @@ const INIT_TEMPLATE_DEPENDENCY_INSTALL_SPECS = ["superjson"];
2451
2451
 
2452
2452
  //#endregion
2453
2453
  //#region src/cli/registry/dependencies.ts
2454
+ const BUN_LOCK_PATH = "bun.lock";
2455
+ const BETTER_AUTH_CORE_LOCK_MARKER = "@better-auth/core";
2454
2456
  const findNearestPackageJsonPath$1 = (startDir) => {
2455
2457
  let current = resolve(startDir);
2456
2458
  while (true) {
@@ -2479,6 +2481,26 @@ const resolvePackageJsonInstallTarget = () => {
2479
2481
  packageJson: packageJsonPath ? JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) : null
2480
2482
  };
2481
2483
  };
2484
+ const resolveBunPeerWarningPreinstallSpecs = () => {
2485
+ const { packageJsonPath, packageJson } = resolvePackageJsonInstallTarget();
2486
+ if (!packageJsonPath || !packageJson) return [];
2487
+ if (!(hasDependency(packageJson, "kitcn") || hasDependency(packageJson, "better-auth") || hasDependency(packageJson, "@convex-dev/better-auth"))) return [];
2488
+ if (hasDependency(packageJson, getPackageNameFromInstallSpec(OPENTELEMETRY_API_INSTALL_SPEC))) return [];
2489
+ const bunLockPath = join(dirname(packageJsonPath), BUN_LOCK_PATH);
2490
+ if (!fs.existsSync(bunLockPath)) return [];
2491
+ if (!fs.readFileSync(bunLockPath, "utf8").includes(BETTER_AUTH_CORE_LOCK_MARKER)) return [];
2492
+ return [OPENTELEMETRY_API_INSTALL_SPEC];
2493
+ };
2494
+ const applyBunPeerWarningPreinstall = async (execaFn) => {
2495
+ const dependencySpecs = resolveBunPeerWarningPreinstallSpecs();
2496
+ if (dependencySpecs.length === 0) return [];
2497
+ const { packageJsonPath } = resolvePackageJsonInstallTarget();
2498
+ await execaFn("bun", ["add", ...dependencySpecs], {
2499
+ cwd: dirname(packageJsonPath),
2500
+ stdio: "inherit"
2501
+ });
2502
+ return dependencySpecs;
2503
+ };
2482
2504
  const inspectPluginDependencyInstall = async (params) => {
2483
2505
  const packageName = params.descriptor.packageName;
2484
2506
  const packageSpec = resolveSupportedDependencyInstallSpec(params.descriptor.packageInstallSpec ?? params.descriptor.packageName);
@@ -2512,27 +2534,30 @@ const resolveMissingDependencyHints = (dependencyHints) => {
2512
2534
  return dependencyHints.filter((dependencyHint) => !hasDependency(packageJson, getPackageNameFromInstallSpec(dependencyHint)));
2513
2535
  };
2514
2536
  const applyDependencyHintsInstall = async (dependencyHints, execaFn) => {
2515
- const missingDependencyHints = resolveMissingDependencyHints(dependencyHints);
2516
- if (missingDependencyHints.length === 0) return [];
2537
+ const preinstalledSpecs = await applyBunPeerWarningPreinstall(execaFn);
2538
+ const missingDependencyHints = resolveMissingDependencyHints(dependencyHints).filter((dependencyHint) => !preinstalledSpecs.includes(dependencyHint));
2539
+ if (missingDependencyHints.length === 0) return preinstalledSpecs;
2517
2540
  const { packageJsonPath } = resolvePackageJsonInstallTarget();
2518
2541
  await execaFn("bun", ["add", ...missingDependencyHints], {
2519
2542
  cwd: dirname(packageJsonPath),
2520
2543
  stdio: "inherit"
2521
2544
  });
2522
- return missingDependencyHints;
2545
+ return [...preinstalledSpecs, ...missingDependencyHints];
2523
2546
  };
2524
2547
  const applyPlanningDependencyInstall = async (dependencySpecs, execaFn) => {
2525
- const missingDependencySpecs = resolveMissingDependencyHints(dependencySpecs);
2526
- if (missingDependencySpecs.length === 0) return [];
2548
+ const preinstalledSpecs = await applyBunPeerWarningPreinstall(execaFn);
2549
+ const missingDependencySpecs = resolveMissingDependencyHints(dependencySpecs).filter((dependencySpec) => !preinstalledSpecs.includes(dependencySpec));
2550
+ if (missingDependencySpecs.length === 0) return preinstalledSpecs;
2527
2551
  const { packageJsonPath } = resolvePackageJsonInstallTarget();
2528
2552
  await execaFn("bun", ["add", ...missingDependencySpecs], {
2529
2553
  cwd: dirname(packageJsonPath),
2530
2554
  stdio: "inherit"
2531
2555
  });
2532
- return missingDependencySpecs;
2556
+ return [...preinstalledSpecs, ...missingDependencySpecs];
2533
2557
  };
2534
2558
  const applyPluginDependencyInstall = async (install, execaFn) => {
2535
2559
  if (install.skipped || !install.packageName || !install.packageJsonPath) return install;
2560
+ await applyBunPeerWarningPreinstall(execaFn);
2536
2561
  const packageSpec = install.packageSpec ?? install.packageName;
2537
2562
  await execaFn("bun", ["add", packageSpec], {
2538
2563
  cwd: dirname(install.packageJsonPath),
@@ -2723,6 +2748,53 @@ function QueryProvider({ children }: { children: ReactNode }) {
2723
2748
  }
2724
2749
  `;
2725
2750
 
2751
+ //#endregion
2752
+ //#region src/cli/registry/init/start/init-start-convex-provider.template.ts
2753
+ const INIT_START_CONVEX_PROVIDER_TEMPLATE = `'use client';
2754
+
2755
+ import { QueryClientProvider as TanstackQueryClientProvider } from '@tanstack/react-query';
2756
+ import {
2757
+ ConvexProvider,
2758
+ ConvexReactClient,
2759
+ getConvexQueryClientSingleton,
2760
+ getQueryClientSingleton,
2761
+ } from 'kitcn/react';
2762
+ import type { ReactNode } from 'react';
2763
+
2764
+ import { CRPCProvider } from '@/lib/convex/crpc';
2765
+ import { createQueryClient } from '@/lib/convex/query-client';
2766
+
2767
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL!);
2768
+
2769
+ export function AppConvexProvider({
2770
+ children,
2771
+ }: {
2772
+ children: ReactNode;
2773
+ }) {
2774
+ return (
2775
+ <ConvexProvider client={convex}>
2776
+ <QueryProvider>{children}</QueryProvider>
2777
+ </ConvexProvider>
2778
+ );
2779
+ }
2780
+
2781
+ function QueryProvider({ children }: { children: ReactNode }) {
2782
+ const queryClient = getQueryClientSingleton(createQueryClient);
2783
+ const convexQueryClient = getConvexQueryClientSingleton({
2784
+ convex,
2785
+ queryClient,
2786
+ });
2787
+
2788
+ return (
2789
+ <TanstackQueryClientProvider client={queryClient}>
2790
+ <CRPCProvider convexClient={convex} convexQueryClient={convexQueryClient}>
2791
+ {children}
2792
+ </CRPCProvider>
2793
+ </TanstackQueryClientProvider>
2794
+ );
2795
+ }
2796
+ `;
2797
+
2726
2798
  //#endregion
2727
2799
  //#region src/cli/utils/content-compare.ts
2728
2800
  const AST_COMPARABLE_EXTENSIONS = new Set([
@@ -3195,10 +3267,10 @@ const resolvePluginScaffoldFiles = (templates, roots, functionsDir, existingTemp
3195
3267
  if (template.target === "lib") rootDir = roots.libRootDir;
3196
3268
  else if (template.target === "functions") rootDir = roots.functionsRootDir;
3197
3269
  else if (template.target === "app") {
3198
- 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|vite>\` first.`);
3270
+ 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.`);
3199
3271
  rootDir = roots.appRootDir;
3200
3272
  } else {
3201
- 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|vite>\` first.`);
3273
+ 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.`);
3202
3274
  rootDir = roots.clientLibRootDir;
3203
3275
  }
3204
3276
  const mappedLockfilePath = existingTemplatePathMap?.[template.id];
@@ -4159,6 +4231,24 @@ export const authClient = createAuthClient({
4159
4231
  plugins: [convexClient()],
4160
4232
  });
4161
4233
 
4234
+ export const {
4235
+ useSignInMutationOptions,
4236
+ useSignOutMutationOptions,
4237
+ useSignUpMutationOptions,
4238
+ } = createAuthMutations(authClient);
4239
+ `;
4240
+ const AUTH_START_CLIENT_TEMPLATE = `import { createAuthClient } from 'better-auth/react';
4241
+ import { convexClient } from 'kitcn/auth/client';
4242
+ import { createAuthMutations } from 'kitcn/react';
4243
+
4244
+ export const authClient = createAuthClient({
4245
+ baseURL:
4246
+ typeof window === 'undefined'
4247
+ ? (import.meta.env.VITE_SITE_URL as string | undefined)
4248
+ : window.location.origin,
4249
+ plugins: [convexClient()],
4250
+ });
4251
+
4162
4252
  export const {
4163
4253
  useSignInMutationOptions,
4164
4254
  useSignOutMutationOptions,
@@ -4712,6 +4802,295 @@ export const authSchema = {
4712
4802
  };
4713
4803
  `;
4714
4804
 
4805
+ //#endregion
4806
+ //#region src/cli/registry/items/auth/auth-start-convex-provider.template.ts
4807
+ const AUTH_START_CONVEX_PROVIDER_TEMPLATE = `'use client';
4808
+
4809
+ import { QueryClientProvider as TanstackQueryClientProvider } from '@tanstack/react-query';
4810
+ import { ConvexAuthProvider } from 'kitcn/auth/client';
4811
+ import {
4812
+ ConvexReactClient,
4813
+ getConvexQueryClientSingleton,
4814
+ getQueryClientSingleton,
4815
+ useAuthStore,
4816
+ } from 'kitcn/react';
4817
+ import type { ReactNode } from 'react';
4818
+
4819
+ import { authClient } from '@/lib/convex/auth-client';
4820
+ import { CRPCProvider } from '@/lib/convex/crpc';
4821
+ import { createQueryClient } from '@/lib/convex/query-client';
4822
+
4823
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL!);
4824
+
4825
+ export function AppConvexProvider({
4826
+ children,
4827
+ }: {
4828
+ children: ReactNode;
4829
+ }) {
4830
+ return (
4831
+ <ConvexAuthProvider authClient={authClient} client={convex}>
4832
+ <QueryProvider>{children}</QueryProvider>
4833
+ </ConvexAuthProvider>
4834
+ );
4835
+ }
4836
+
4837
+ function QueryProvider({ children }: { children: ReactNode }) {
4838
+ const authStore = useAuthStore();
4839
+ const queryClient = getQueryClientSingleton(createQueryClient);
4840
+ const convexQueryClient = getConvexQueryClientSingleton({
4841
+ authStore,
4842
+ convex,
4843
+ queryClient,
4844
+ });
4845
+
4846
+ return (
4847
+ <TanstackQueryClientProvider client={queryClient}>
4848
+ <CRPCProvider convexClient={convex} convexQueryClient={convexQueryClient}>
4849
+ {children}
4850
+ </CRPCProvider>
4851
+ </TanstackQueryClientProvider>
4852
+ );
4853
+ }
4854
+ `;
4855
+
4856
+ //#endregion
4857
+ //#region src/cli/registry/items/auth/auth-start-page.template.ts
4858
+ const AUTH_START_PAGE_TEMPLATE = `'use client';
4859
+
4860
+ import { useMutation } from '@tanstack/react-query';
4861
+ import { createFileRoute } from '@tanstack/react-router';
4862
+ import { useAuth } from 'kitcn/react';
4863
+ import { useState } from 'react';
4864
+
4865
+ import {
4866
+ authClient,
4867
+ useSignInMutationOptions,
4868
+ useSignOutMutationOptions,
4869
+ useSignUpMutationOptions,
4870
+ } from '@/lib/convex/auth-client';
4871
+
4872
+ export const Route = createFileRoute('/auth' as never)({
4873
+ component: AuthPage,
4874
+ });
4875
+
4876
+ function AuthPage() {
4877
+ const { hasSession, isLoading } = useAuth();
4878
+ const authSession = authClient.useSession();
4879
+ const session = authSession.data;
4880
+ const user = session?.user ?? null;
4881
+ const hasSignedInUser = hasSession || Boolean(user);
4882
+ const [mode, setMode] = useState<'signin' | 'signup'>('signin');
4883
+ const [name, setName] = useState('');
4884
+ const [email, setEmail] = useState('');
4885
+ const [password, setPassword] = useState('');
4886
+
4887
+ const signIn = useMutation(useSignInMutationOptions());
4888
+ const signUp = useMutation(useSignUpMutationOptions());
4889
+ const signOut = useMutation(useSignOutMutationOptions());
4890
+
4891
+ const errorMessage =
4892
+ signIn.error?.message ??
4893
+ signUp.error?.message ??
4894
+ signOut.error?.message ??
4895
+ null;
4896
+ const isPending =
4897
+ signIn.isPending || signUp.isPending || signOut.isPending;
4898
+
4899
+ function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
4900
+ event.preventDefault();
4901
+
4902
+ if (mode === 'signup') {
4903
+ signUp.mutate({
4904
+ email,
4905
+ name,
4906
+ password,
4907
+ });
4908
+ return;
4909
+ }
4910
+
4911
+ signIn.mutate({
4912
+ email,
4913
+ password,
4914
+ });
4915
+ }
4916
+
4917
+ if (isLoading && !hasSignedInUser) {
4918
+ return (
4919
+ <main className="mx-auto flex min-h-[60vh] max-w-md items-center px-6 py-16">
4920
+ <p className="text-sm text-muted-foreground">Loading auth…</p>
4921
+ </main>
4922
+ );
4923
+ }
4924
+
4925
+ if (hasSignedInUser) {
4926
+ return (
4927
+ <main className="mx-auto flex min-h-[60vh] max-w-md flex-col justify-center gap-6 px-6 py-16">
4928
+ <div className="space-y-2">
4929
+ <p className="text-sm font-medium text-muted-foreground">Signed in</p>
4930
+ <h1 className="text-3xl font-semibold tracking-tight">
4931
+ {user?.name || user?.email || email}
4932
+ </h1>
4933
+ <p className="text-sm text-muted-foreground">
4934
+ {user?.email || email}
4935
+ </p>
4936
+ </div>
4937
+ <button
4938
+ className="rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition hover:opacity-90 disabled:opacity-60"
4939
+ disabled={isPending}
4940
+ onClick={() => signOut.mutate()}
4941
+ type="button"
4942
+ >
4943
+ {signOut.isPending ? 'Signing out…' : 'Sign out'}
4944
+ </button>
4945
+ </main>
4946
+ );
4947
+ }
4948
+
4949
+ return (
4950
+ <main className="mx-auto flex min-h-[60vh] max-w-md flex-col justify-center gap-6 px-6 py-16">
4951
+ <div className="space-y-2">
4952
+ <p className="text-sm font-medium text-muted-foreground">Auth demo</p>
4953
+ <h1 className="text-3xl font-semibold tracking-tight">
4954
+ {mode === 'signup' ? 'Create an account' : 'Sign in'}
4955
+ </h1>
4956
+ <p className="text-sm text-muted-foreground">
4957
+ Minimal Better Auth wiring on top of the init baseline.
4958
+ </p>
4959
+ </div>
4960
+
4961
+ <form className="space-y-3" onSubmit={handleSubmit}>
4962
+ {mode === 'signup' ? (
4963
+ <input
4964
+ autoComplete="name"
4965
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
4966
+ onChange={(event) => setName(event.target.value)}
4967
+ placeholder="Name"
4968
+ required
4969
+ type="text"
4970
+ value={name}
4971
+ />
4972
+ ) : null}
4973
+ <input
4974
+ autoComplete="email"
4975
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
4976
+ onChange={(event) => setEmail(event.target.value)}
4977
+ placeholder="Email"
4978
+ required
4979
+ type="email"
4980
+ value={email}
4981
+ />
4982
+ <input
4983
+ autoComplete={mode === 'signup' ? 'new-password' : 'current-password'}
4984
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
4985
+ minLength={8}
4986
+ onChange={(event) => setPassword(event.target.value)}
4987
+ placeholder="Password"
4988
+ required
4989
+ type="password"
4990
+ value={password}
4991
+ />
4992
+ <button
4993
+ className="w-full rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition hover:opacity-90 disabled:opacity-60"
4994
+ disabled={isPending}
4995
+ type="submit"
4996
+ >
4997
+ {isPending
4998
+ ? 'Working…'
4999
+ : mode === 'signup'
5000
+ ? 'Create account'
5001
+ : 'Sign in'}
5002
+ </button>
5003
+ </form>
5004
+
5005
+ <button
5006
+ className="text-left text-sm text-muted-foreground underline-offset-4 hover:underline"
5007
+ onClick={() => setMode(mode === 'signin' ? 'signup' : 'signin')}
5008
+ type="button"
5009
+ >
5010
+ {mode === 'signin'
5011
+ ? "Don't have an account? Sign up"
5012
+ : 'Already have an account? Sign in'}
5013
+ </button>
5014
+
5015
+ {errorMessage ? (
5016
+ <p className="text-sm text-destructive">{errorMessage}</p>
5017
+ ) : null}
5018
+ </main>
5019
+ );
5020
+ }
5021
+ `;
5022
+
5023
+ //#endregion
5024
+ //#region src/cli/registry/items/auth/auth-start-route.template.ts
5025
+ const AUTH_START_ROUTE_TEMPLATE = `import { createFileRoute } from '@tanstack/react-router';
5026
+
5027
+ import { handler } from '@/lib/convex/auth-server';
5028
+
5029
+ export const Route = createFileRoute('/api/auth/$' as never)({
5030
+ server: {
5031
+ handlers: {
5032
+ GET: ({ request }) => handler(request),
5033
+ POST: ({ request }) => handler(request),
5034
+ },
5035
+ },
5036
+ });
5037
+ `;
5038
+
5039
+ //#endregion
5040
+ //#region src/cli/registry/items/auth/auth-start-server.template.ts
5041
+ const AUTH_START_SERVER_TEMPLATE = `import { convexBetterAuthReactStart } from 'kitcn/auth/start';
5042
+
5043
+ export const {
5044
+ handler,
5045
+ getToken,
5046
+ fetchAuthQuery,
5047
+ fetchAuthMutation,
5048
+ fetchAuthAction,
5049
+ } = convexBetterAuthReactStart({
5050
+ convexUrl: import.meta.env.VITE_CONVEX_URL!,
5051
+ convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
5052
+ });
5053
+ `;
5054
+
5055
+ //#endregion
5056
+ //#region src/cli/registry/items/auth/auth-start-server-call.template.ts
5057
+ const AUTH_START_SERVER_CALL_TEMPLATE = `import { api } from '@convex/api';
5058
+ import { getRequestHeaders } from '@tanstack/react-start/server';
5059
+ import { createCallerFactory } from 'kitcn/server';
5060
+
5061
+ import { getToken } from '@/lib/convex/auth-server';
5062
+
5063
+ const { createContext, createCaller } = createCallerFactory({
5064
+ api,
5065
+ convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
5066
+ auth: {
5067
+ getToken: async () => {
5068
+ return {
5069
+ token: await getToken(),
5070
+ };
5071
+ },
5072
+ },
5073
+ });
5074
+
5075
+ type ServerCaller = ReturnType<typeof createCaller>;
5076
+
5077
+ async function makeContext() {
5078
+ const headers = await getRequestHeaders();
5079
+ return createContext({ headers });
5080
+ }
5081
+
5082
+ function createServerCaller(): ServerCaller {
5083
+ return createCaller(async () => {
5084
+ return await makeContext();
5085
+ });
5086
+ }
5087
+
5088
+ export function runServerCall<T>(fn: (caller: ServerCaller) => Promise<T> | T) {
5089
+ const caller = createServerCaller();
5090
+ return fn(caller);
5091
+ }
5092
+ `;
5093
+
4715
5094
  //#endregion
4716
5095
  //#region src/auth/auth-config.ts
4717
5096
  const createPublicJwks = (jwks, options) => {
@@ -5493,11 +5872,21 @@ app.use(
5493
5872
  updateReason: "Register auth middleware in http.ts.",
5494
5873
  skipReason: "Auth middleware is already registered in http.ts."
5495
5874
  });
5496
- }
5497
- function buildAuthProviderPlanFile(params) {
5498
- if (!params.roots.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|vite>` first.");
5499
- const providerPath = resolve(process.cwd(), params.roots.projectContext.convexClientDir, "convex-provider.tsx");
5500
- const isNextApp = params.roots.projectContext.mode === "next-app";
5875
+ }
5876
+ function buildAuthProviderPlanFile(params) {
5877
+ const projectContext = params.roots.projectContext;
5878
+ 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.");
5879
+ if (projectContext.framework === "tanstack-start") return createPlanFile({
5880
+ kind: "scaffold",
5881
+ filePath: resolve(process.cwd(), projectContext.convexClientDir, "convex-provider.tsx"),
5882
+ content: AUTH_START_CONVEX_PROVIDER_TEMPLATE,
5883
+ managedBaselineContent: INIT_START_CONVEX_PROVIDER_TEMPLATE,
5884
+ createReason: "Create auth-aware kitcn provider for the app scaffold.",
5885
+ updateReason: "Update kitcn provider with auth-aware client wiring.",
5886
+ skipReason: "kitcn provider already matches the auth scaffold."
5887
+ });
5888
+ const providerPath = resolve(process.cwd(), projectContext.convexClientDir, "convex-provider.tsx");
5889
+ const isNextApp = projectContext.mode === "next-app";
5501
5890
  return createPlanFile({
5502
5891
  kind: "scaffold",
5503
5892
  filePath: providerPath,
@@ -5533,6 +5922,54 @@ function buildAuthNextRoutePlanFile(params) {
5533
5922
  skipReason: "The Next auth proxy route already exists."
5534
5923
  });
5535
5924
  }
5925
+ function buildAuthStartServerPlanFile(params) {
5926
+ const projectContext = params.roots.projectContext;
5927
+ if (!projectContext || projectContext.framework !== "tanstack-start") throw new Error("Auth scaffolding requires a supported TanStack Start shell.");
5928
+ return createPlanFile({
5929
+ kind: "scaffold",
5930
+ filePath: resolve(process.cwd(), projectContext.convexClientDir, "auth-server.ts"),
5931
+ content: AUTH_START_SERVER_TEMPLATE,
5932
+ createReason: "Create auth-aware Start server helpers.",
5933
+ updateReason: "Update Start server helpers with auth route support.",
5934
+ skipReason: "Start server helpers already include auth route support."
5935
+ });
5936
+ }
5937
+ function buildAuthStartRoutePlanFile(params) {
5938
+ const projectContext = params.roots.projectContext;
5939
+ if (!projectContext || projectContext.framework !== "tanstack-start") throw new Error("Auth scaffolding requires a supported TanStack Start shell.");
5940
+ return createPlanFile({
5941
+ kind: "scaffold",
5942
+ filePath: resolve(process.cwd(), projectContext.usesSrc ? "src" : "", "routes", "api", "auth", "$.ts"),
5943
+ content: AUTH_START_ROUTE_TEMPLATE,
5944
+ createReason: "Create the Start auth proxy route.",
5945
+ updateReason: "Update the Start auth proxy route.",
5946
+ skipReason: "The Start auth proxy route already exists."
5947
+ });
5948
+ }
5949
+ function buildAuthStartServerCallPlanFile(params) {
5950
+ const projectContext = params.roots.projectContext;
5951
+ if (!projectContext || projectContext.framework !== "tanstack-start") throw new Error("Auth scaffolding requires a supported TanStack Start shell.");
5952
+ return createPlanFile({
5953
+ kind: "scaffold",
5954
+ filePath: resolve(process.cwd(), projectContext.convexClientDir, "server.ts"),
5955
+ content: AUTH_START_SERVER_CALL_TEMPLATE,
5956
+ createReason: "Create auth-aware Start server caller helpers.",
5957
+ updateReason: "Update Start server caller helpers with auth token wiring.",
5958
+ skipReason: "Start server caller helpers already include auth token wiring."
5959
+ });
5960
+ }
5961
+ function buildAuthStartPagePlanFile(params) {
5962
+ const projectContext = params.roots.projectContext;
5963
+ if (!projectContext || projectContext.framework !== "tanstack-start") throw new Error("Auth scaffolding requires a supported TanStack Start shell.");
5964
+ return createPlanFile({
5965
+ kind: "scaffold",
5966
+ filePath: resolve(process.cwd(), projectContext.usesSrc ? "src" : "", "routes", "auth.tsx"),
5967
+ content: AUTH_START_PAGE_TEMPLATE,
5968
+ createReason: "Create the Start auth demo route.",
5969
+ updateReason: "Update the Start auth demo route.",
5970
+ skipReason: "The Start auth demo route already exists."
5971
+ });
5972
+ }
5536
5973
  function buildAuthConvexLocalEnvPlanFile(params) {
5537
5974
  const envPath = resolve(params.functionsDir, ".env");
5538
5975
  return createPlanFile({
@@ -5669,6 +6106,13 @@ const authRegistryItem = defineInternalRegistryItem({
5669
6106
  })),
5670
6107
  resolveTemplates: ({ roots, templates }) => {
5671
6108
  if (!roots.projectContext || roots.projectContext.mode === "next-app") return templates;
6109
+ if (roots.projectContext.framework === "tanstack-start") return templates.filter((template) => template.id !== "auth-page" && template.id !== "auth-page-convex").map((template) => {
6110
+ if (template.id === "auth-client") return {
6111
+ ...template,
6112
+ content: AUTH_START_CLIENT_TEMPLATE
6113
+ };
6114
+ return template;
6115
+ });
5672
6116
  return templates.filter((template) => template.id !== "auth-page" && template.id !== "auth-page-convex").map((template) => {
5673
6117
  if (template.id === "auth-client") return {
5674
6118
  ...template,
@@ -5694,6 +6138,7 @@ const authRegistryItem = defineInternalRegistryItem({
5694
6138
  buildAuthProviderPlanFile(params)
5695
6139
  ];
5696
6140
  if (roots.projectContext?.mode === "next-app") files.push(buildAuthNextServerPlanFile(params), buildAuthNextRoutePlanFile(params));
6141
+ else if (roots.projectContext?.framework === "tanstack-start") files.push(buildAuthStartServerPlanFile(params), buildAuthStartRoutePlanFile(params), buildAuthStartServerCallPlanFile(params), buildAuthStartPagePlanFile(params));
5697
6142
  return files;
5698
6143
  },
5699
6144
  buildSchemaRegistrationPlanFile: ({ applyScope, config, functionsDir, lockfile, overwrite, preset, preview, promptAdapter, roots, yes }) => buildAuthSchemaRegistrationPlanFile({
@@ -7379,6 +7824,8 @@ function renderInitConvexTsconfigTemplate(functionsDirRelative = "convex/functio
7379
7824
  forceConsistentCasingInFileNames: true,
7380
7825
  isolatedModules: true,
7381
7826
  skipLibCheck: true,
7827
+ noUnusedLocals: false,
7828
+ noUnusedParameters: false,
7382
7829
  noEmit: true,
7383
7830
  jsx: "react-jsx",
7384
7831
  lib: ["esnext", "dom"],
@@ -7577,7 +8024,10 @@ const INIT_NEXT_CONVEX_DEV_SCRIPT_NAME = "convex:dev";
7577
8024
  const INIT_NEXT_CONVEX_DEV_SCRIPT = "kitcn dev";
7578
8025
  const INIT_NEXT_CONVEX_TYPECHECK_SCRIPT_NAME = "typecheck:convex";
7579
8026
  const getInitNextConvexTypecheckScript = (functionsDirRelative = "convex/functions") => `tsc --noEmit --project ${functionsDirRelative}/tsconfig.json`;
7580
- const INIT_NEXT_PACKAGE_JSON_DEPENDENCIES = { superjson: "2.2.6" };
8027
+ const INIT_NEXT_PACKAGE_JSON_DEPENDENCIES = {
8028
+ "@opentelemetry/api": SUPPORTED_DEPENDENCY_VERSIONS.opentelemetryApi.exact,
8029
+ superjson: "2.2.6"
8030
+ };
7581
8031
  const INIT_NEXT_PACKAGE_JSON_DEV_DEPENDENCIES = { "@types/bun": "latest" };
7582
8032
  const getInitNextPackageJsonDevDependencies = (options) => ({
7583
8033
  ...INIT_NEXT_PACKAGE_JSON_DEV_DEPENDENCIES,
@@ -7786,7 +8236,10 @@ const INIT_REACT_CONVEX_DEV_SCRIPT_NAME = "convex:dev";
7786
8236
  const INIT_REACT_CONVEX_DEV_SCRIPT = "kitcn dev";
7787
8237
  const INIT_REACT_CONVEX_TYPECHECK_SCRIPT_NAME = "typecheck:convex";
7788
8238
  const getInitReactConvexTypecheckScript = (functionsDirRelative = "convex/functions") => `tsc --noEmit --project ${functionsDirRelative}/tsconfig.json`;
7789
- const INIT_REACT_PACKAGE_JSON_DEPENDENCIES = { superjson: "2.2.6" };
8239
+ const INIT_REACT_PACKAGE_JSON_DEPENDENCIES = {
8240
+ "@opentelemetry/api": SUPPORTED_DEPENDENCY_VERSIONS.opentelemetryApi.exact,
8241
+ superjson: "2.2.6"
8242
+ };
7790
8243
  const INIT_REACT_PACKAGE_JSON_DEV_DEPENDENCIES = { "@types/bun": "latest" };
7791
8244
  const getInitReactPackageJsonDevDependencies = (options) => ({
7792
8245
  ...INIT_REACT_PACKAGE_JSON_DEV_DEPENDENCIES,
@@ -7827,6 +8280,203 @@ export function Providers({ children }: { children: ReactNode }) {
7827
8280
  }
7828
8281
  `;
7829
8282
 
8283
+ //#endregion
8284
+ //#region src/cli/registry/init/start/init-start-crpc.template.ts
8285
+ const INIT_START_CRPC_TEMPLATE = `import { api } from '@convex/api';
8286
+ import { createCRPCContext } from 'kitcn/react';
8287
+
8288
+ export const { CRPCProvider, useCRPC, useCRPCClient } = createCRPCContext({
8289
+ api,
8290
+ convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL!,
8291
+ });
8292
+ `;
8293
+
8294
+ //#endregion
8295
+ //#region src/cli/registry/init/start/init-start-messages-page.template.ts
8296
+ const INIT_START_MESSAGES_PAGE_TEMPLATE = `'use client';
8297
+
8298
+ import { useMutation, useQuery } from '@tanstack/react-query';
8299
+ import { createFileRoute } from '@tanstack/react-router';
8300
+ import { type FormEvent, useState } from 'react';
8301
+
8302
+ import { Button } from '@/components/ui/button';
8303
+ import { useCRPC } from '@/lib/convex/crpc';
8304
+
8305
+ export const Route = createFileRoute('/')({
8306
+ component: ConvexMessagesPage,
8307
+ });
8308
+
8309
+ function ConvexMessagesPage() {
8310
+ const crpc = useCRPC();
8311
+ const [draft, setDraft] = useState('');
8312
+ const messagesQuery = useQuery(crpc.messages.list.queryOptions());
8313
+ const createMessage = useMutation(crpc.messages.create.mutationOptions());
8314
+
8315
+ async function handleSubmit(event: FormEvent<HTMLFormElement>) {
8316
+ event.preventDefault();
8317
+ const body = draft.trim();
8318
+ if (!body) return;
8319
+
8320
+ try {
8321
+ await createMessage.mutateAsync({ body });
8322
+ setDraft('');
8323
+ } catch {}
8324
+ }
8325
+
8326
+ return (
8327
+ <main className="mx-auto flex min-h-svh w-full max-w-2xl flex-col gap-6 px-6 py-10 text-sm">
8328
+ <header className="space-y-2">
8329
+ <p className="font-mono text-xs uppercase tracking-[0.2em] text-muted-foreground">
8330
+ kitcn
8331
+ </p>
8332
+ <h1 className="font-medium text-2xl tracking-tight">Messages</h1>
8333
+ <p className="max-w-xl text-muted-foreground leading-6">
8334
+ This page is a tiny live query and mutation over kitcn. Start the
8335
+ backend, send a message, and watch the list update.
8336
+ </p>
8337
+ </header>
8338
+
8339
+ <form className="flex flex-col gap-3 sm:flex-row" onSubmit={handleSubmit}>
8340
+ <input
8341
+ className="min-h-10 flex-1 rounded-md border border-border bg-background px-3 py-2 outline-none transition-colors focus:border-primary"
8342
+ maxLength={120}
8343
+ onChange={(event) => setDraft(event.target.value)}
8344
+ placeholder="Write a message"
8345
+ value={draft}
8346
+ />
8347
+ <Button disabled={createMessage.isPending || draft.trim().length === 0} type="submit">
8348
+ {createMessage.isPending ? 'Saving...' : 'Add message'}
8349
+ </Button>
8350
+ </form>
8351
+
8352
+ {messagesQuery.isPending ? (
8353
+ <p className="text-muted-foreground">Loading messages...</p>
8354
+ ) : messagesQuery.isError ? (
8355
+ <div className="rounded-md border border-dashed border-border px-4 py-3 text-muted-foreground leading-6">
8356
+ Backend not ready. Start <code>kitcn dev</code> and refresh.
8357
+ </div>
8358
+ ) : messagesQuery.data.length === 0 ? (
8359
+ <div className="rounded-md border border-dashed border-border px-4 py-6 text-muted-foreground">
8360
+ No messages yet. Add the first one.
8361
+ </div>
8362
+ ) : (
8363
+ <ul className="space-y-3">
8364
+ {messagesQuery.data.map((message) => (
8365
+ <li
8366
+ className="rounded-md border border-border bg-background px-4 py-3"
8367
+ key={message.id}
8368
+ >
8369
+ <div className="flex items-start justify-between gap-3">
8370
+ <p className="leading-6">{message.body}</p>
8371
+ <time className="shrink-0 font-mono text-xs text-muted-foreground">
8372
+ {message.createdAt.toLocaleTimeString()}
8373
+ </time>
8374
+ </div>
8375
+ </li>
8376
+ ))}
8377
+ </ul>
8378
+ )}
8379
+ </main>
8380
+ );
8381
+ }
8382
+ `;
8383
+
8384
+ //#endregion
8385
+ //#region src/cli/registry/init/start/init-start-root.template.ts
8386
+ const INIT_START_ROOT_TEMPLATE = `import {
8387
+ HeadContent,
8388
+ Outlet,
8389
+ Scripts,
8390
+ createRootRoute,
8391
+ } from '@tanstack/react-router';
8392
+ import { TanStackDevtools } from '@tanstack/react-devtools';
8393
+ import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
8394
+
8395
+ import { Providers } from '@/components/providers';
8396
+ import appCss from '../styles.css?url';
8397
+
8398
+ export const Route = createRootRoute({
8399
+ head: () => ({
8400
+ meta: [
8401
+ {
8402
+ charSet: 'utf-8',
8403
+ },
8404
+ {
8405
+ name: 'viewport',
8406
+ content: 'width=device-width, initial-scale=1',
8407
+ },
8408
+ {
8409
+ title: 'TanStack Start Starter',
8410
+ },
8411
+ ],
8412
+ links: [
8413
+ {
8414
+ rel: 'stylesheet',
8415
+ href: appCss,
8416
+ },
8417
+ ],
8418
+ }),
8419
+ component: RootComponent,
8420
+ shellComponent: RootDocument,
8421
+ });
8422
+
8423
+ function RootDocument({ children }: { children: React.ReactNode }) {
8424
+ return (
8425
+ <html lang="en">
8426
+ <head>
8427
+ <HeadContent />
8428
+ </head>
8429
+ <body>
8430
+ {children}
8431
+ <TanStackDevtools
8432
+ config={{
8433
+ position: 'bottom-right',
8434
+ }}
8435
+ plugins={[
8436
+ {
8437
+ name: 'Tanstack Router',
8438
+ render: <TanStackRouterDevtoolsPanel />,
8439
+ },
8440
+ ]}
8441
+ />
8442
+ <Scripts />
8443
+ </body>
8444
+ </html>
8445
+ );
8446
+ }
8447
+
8448
+ function RootComponent() {
8449
+ return (
8450
+ <Providers>
8451
+ <Outlet />
8452
+ </Providers>
8453
+ );
8454
+ }
8455
+ `;
8456
+
8457
+ //#endregion
8458
+ //#region src/cli/registry/init/start/init-start-router.template.ts
8459
+ const INIT_START_ROUTER_TEMPLATE = `import { createRouter } from '@tanstack/react-router';
8460
+ import { routeTree } from './routeTree.gen';
8461
+
8462
+ export function getRouter() {
8463
+ const router = createRouter({
8464
+ routeTree,
8465
+ scrollRestoration: true,
8466
+ defaultPreload: 'intent',
8467
+ defaultPreloadStaleTime: 0,
8468
+ });
8469
+
8470
+ return router;
8471
+ }
8472
+
8473
+ declare module '@tanstack/react-router' {
8474
+ interface Register {
8475
+ router: ReturnType<typeof getRouter>;
8476
+ }
8477
+ }
8478
+ `;
8479
+
7830
8480
  //#endregion
7831
8481
  //#region src/cli/registry/selection.ts
7832
8482
  const getPluginDisplayHint = (descriptor) => descriptor.presets[0]?.description;
@@ -8288,6 +8938,7 @@ const realConvex = join(dirname(require.resolve("convex/package.json")), "bin/ma
8288
8938
  const MISSING_BACKFILL_FUNCTION_RE = /could not find function|function .* was not found|unknown function/i;
8289
8939
  const GITIGNORE_RUNTIME_ENTRIES = [".convex/", ".concave/"];
8290
8940
  const TS_EXTENSION_RE = /\.ts$/;
8941
+ const LEADING_SLASHES_RE = /^\/+/;
8291
8942
  const AGGREGATE_STATE_RELATIVE_PATH = join(".convex", "kitcn", "aggregate-backfill-state.json");
8292
8943
  const AGGREGATE_STATE_VERSION = 1;
8293
8944
  const INIT_SHADCN_PACKAGE_SPEC = "shadcn@4.0.1";
@@ -8323,7 +8974,11 @@ const VALID_SCOPES = new Set([
8323
8974
  const VALID_BACKENDS = new Set(["convex", "concave"]);
8324
8975
  const SUPPORTED_PLUGINS = new Set(getSupportedPluginKeys());
8325
8976
  const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8326
- const SUPPORTED_INIT_TEMPLATES = ["next", "vite"];
8977
+ const SUPPORTED_INIT_TEMPLATES = [
8978
+ "next",
8979
+ "start",
8980
+ "vite"
8981
+ ];
8327
8982
  const REACT_APP_MOUNT_RE = /<App\s*\/>/;
8328
8983
  const DOCS_BASE_URL = "https://kitcn.vercel.app/docs";
8329
8984
  const CORE_DOC_TOPICS = {
@@ -8918,9 +9573,129 @@ function buildInitReactOwnedScaffoldFiles(context, functionsDirRelative, backend
8918
9573
  }
8919
9574
  ];
8920
9575
  }
9576
+ function buildInitStartOwnedScaffoldFiles(context, functionsDirRelative, backend, includeDemoFiles) {
9577
+ const rootPrefix = context.usesSrc ? "src" : "";
9578
+ const files = [
9579
+ {
9580
+ kind: "config",
9581
+ relativePath: "package.json",
9582
+ requiresExplicitOverwrite: false,
9583
+ content: ({ existingContent }) => renderInitReactPackageJsonTemplate(existingContent, {
9584
+ backend,
9585
+ functionsDirRelative
9586
+ }),
9587
+ createReason: "Create baseline package.json scripts for the Start scaffold.",
9588
+ updateReason: "Update package.json scripts for the Start scaffold.",
9589
+ skipReason: "package.json scripts already match the Start scaffold."
9590
+ },
9591
+ {
9592
+ kind: "env",
9593
+ relativePath: ".env.local",
9594
+ requiresExplicitOverwrite: false,
9595
+ content: ({ existingContent }) => renderInitReactEnvLocalTemplate(existingContent),
9596
+ createReason: "Create baseline .env.local for the Start scaffold.",
9597
+ updateReason: "Update baseline .env.local for the Start scaffold.",
9598
+ skipReason: ".env.local already matches the Start scaffold."
9599
+ },
9600
+ {
9601
+ kind: "scaffold",
9602
+ relativePath: `${context.componentsDir}/providers.tsx`,
9603
+ requiresExplicitOverwrite: true,
9604
+ content: INIT_REACT_PROVIDERS_TEMPLATE,
9605
+ createReason: `Create baseline ${context.componentsDir}/providers.tsx for the Start scaffold.`,
9606
+ updateReason: `Update ${context.componentsDir}/providers.tsx for the Start scaffold.`,
9607
+ skipReason: `${context.componentsDir}/providers.tsx already matches the Start scaffold.`
9608
+ },
9609
+ {
9610
+ kind: "scaffold",
9611
+ relativePath: `${context.convexClientDir}/query-client.ts`,
9612
+ requiresExplicitOverwrite: true,
9613
+ content: INIT_NEXT_QUERY_CLIENT_TEMPLATE,
9614
+ createReason: `Create baseline ${context.convexClientDir}/query-client.ts for the Start scaffold.`,
9615
+ updateReason: `Update ${context.convexClientDir}/query-client.ts for the Start scaffold.`,
9616
+ skipReason: `${context.convexClientDir}/query-client.ts already matches the Start scaffold.`
9617
+ },
9618
+ {
9619
+ kind: "scaffold",
9620
+ relativePath: `${context.convexClientDir}/crpc.tsx`,
9621
+ requiresExplicitOverwrite: true,
9622
+ content: INIT_START_CRPC_TEMPLATE,
9623
+ createReason: `Create baseline ${context.convexClientDir}/crpc.tsx for the Start scaffold.`,
9624
+ updateReason: `Update ${context.convexClientDir}/crpc.tsx for the Start scaffold.`,
9625
+ skipReason: `${context.convexClientDir}/crpc.tsx already matches the Start scaffold.`
9626
+ },
9627
+ {
9628
+ kind: "scaffold",
9629
+ relativePath: `${context.convexClientDir}/convex-provider.tsx`,
9630
+ requiresExplicitOverwrite: true,
9631
+ content: INIT_START_CONVEX_PROVIDER_TEMPLATE,
9632
+ preserveManagedContent: [AUTH_START_CONVEX_PROVIDER_TEMPLATE],
9633
+ createReason: `Create baseline ${context.convexClientDir}/convex-provider.tsx for the Start scaffold.`,
9634
+ updateReason: `Update ${context.convexClientDir}/convex-provider.tsx for the Start scaffold.`,
9635
+ skipReason: `${context.convexClientDir}/convex-provider.tsx already matches the Start scaffold.`
9636
+ },
9637
+ {
9638
+ kind: "scaffold",
9639
+ relativePath: trimLeadingSlashes(posix.join(rootPrefix, "router.tsx")),
9640
+ requiresExplicitOverwrite: true,
9641
+ content: INIT_START_ROUTER_TEMPLATE,
9642
+ createReason: `Create baseline ${trimLeadingSlashes(posix.join(rootPrefix, "router.tsx"))} for the Start scaffold.`,
9643
+ updateReason: `Update ${trimLeadingSlashes(posix.join(rootPrefix, "router.tsx"))} for the Start scaffold.`,
9644
+ skipReason: `${trimLeadingSlashes(posix.join(rootPrefix, "router.tsx"))} already matches the Start scaffold.`
9645
+ },
9646
+ {
9647
+ kind: "scaffold",
9648
+ relativePath: trimLeadingSlashes(posix.join(rootPrefix, "routes", "__root.tsx")),
9649
+ requiresExplicitOverwrite: true,
9650
+ content: INIT_START_ROOT_TEMPLATE,
9651
+ createReason: `Create baseline ${trimLeadingSlashes(posix.join(rootPrefix, "routes", "__root.tsx"))} for the Start scaffold.`,
9652
+ updateReason: `Update ${trimLeadingSlashes(posix.join(rootPrefix, "routes", "__root.tsx"))} for the Start scaffold.`,
9653
+ skipReason: `${trimLeadingSlashes(posix.join(rootPrefix, "routes", "__root.tsx"))} already matches the Start scaffold.`
9654
+ },
9655
+ {
9656
+ kind: "config",
9657
+ relativePath: join(functionsDirRelative, "tsconfig.json"),
9658
+ managedBaselineContent: getManagedConvexTsconfigBaselines(functionsDirRelative),
9659
+ requiresExplicitOverwrite: true,
9660
+ content: ({ existingContent }) => typeof existingContent === "string" ? patchInitConvexTsconfigContent(existingContent, functionsDirRelative) : renderInitConvexTsconfigTemplate(functionsDirRelative),
9661
+ createReason: `Create ${join(functionsDirRelative, "tsconfig.json")} for kitcn functions.`,
9662
+ updateReason: `Patch ${join(functionsDirRelative, "tsconfig.json")} for kitcn functions.`,
9663
+ skipReason: `${join(functionsDirRelative, "tsconfig.json")} already matches the kitcn functions project.`
9664
+ }
9665
+ ];
9666
+ if (includeDemoFiles) files.push({
9667
+ kind: "scaffold",
9668
+ relativePath: trimLeadingSlashes(posix.join(rootPrefix, "routes", "index.tsx")),
9669
+ requiresExplicitOverwrite: false,
9670
+ content: INIT_START_MESSAGES_PAGE_TEMPLATE,
9671
+ createReason: `Create ${trimLeadingSlashes(posix.join(rootPrefix, "routes", "index.tsx"))} as the minimal kitcn demo route.`,
9672
+ updateReason: `Update ${trimLeadingSlashes(posix.join(rootPrefix, "routes", "index.tsx"))} for the kitcn demo route.`,
9673
+ skipReason: `${trimLeadingSlashes(posix.join(rootPrefix, "routes", "index.tsx"))} already matches the kitcn demo route.`
9674
+ }, {
9675
+ kind: "schema",
9676
+ relativePath: `${functionsDirRelative}/schema.ts`,
9677
+ requiresExplicitOverwrite: true,
9678
+ content: INIT_NEXT_SCHEMA_TEMPLATE,
9679
+ createReason: `Create ${functionsDirRelative}/schema.ts with the minimal kitcn demo schema.`,
9680
+ updateReason: `Update ${functionsDirRelative}/schema.ts with the minimal kitcn demo schema.`,
9681
+ skipReason: `${functionsDirRelative}/schema.ts already matches the kitcn demo schema.`
9682
+ }, {
9683
+ kind: "scaffold",
9684
+ relativePath: `${functionsDirRelative}/messages.ts`,
9685
+ requiresExplicitOverwrite: true,
9686
+ content: renderInitNextMessagesTemplate(functionsDirRelative),
9687
+ createReason: `Create ${functionsDirRelative}/messages.ts for the kitcn demo route.`,
9688
+ updateReason: `Update ${functionsDirRelative}/messages.ts for the kitcn demo route.`,
9689
+ skipReason: `${functionsDirRelative}/messages.ts already matches the kitcn demo route.`
9690
+ });
9691
+ return files;
9692
+ }
8921
9693
  function detectImportQuote(source) {
8922
9694
  return source.match(INIT_NEXT_IMPORT_QUOTE_RE)?.[1] === "'" ? "'" : "\"";
8923
9695
  }
9696
+ function trimLeadingSlashes(value) {
9697
+ return value.replace(LEADING_SLASHES_RE, "");
9698
+ }
8924
9699
  function detectStatementTerminator(source) {
8925
9700
  return INIT_NEXT_IMPORT_SEMICOLON_RE.test(source) ? ";" : "";
8926
9701
  }
@@ -8963,6 +9738,25 @@ function patchInitTsconfigContent(source, context) {
8963
9738
  }
8964
9739
  }, null, 2)}\n`;
8965
9740
  }
9741
+ function patchInitStartTsconfigContent(source, context) {
9742
+ const parsedResult = ts.parseConfigFileTextToJson("tsconfig.json", source);
9743
+ if (parsedResult.error || parsedResult.config === void 0) throw new Error("Could not patch tsconfig.json: expected valid JSON or JSONC scaffold output.");
9744
+ const parsed = parsedResult.config;
9745
+ if (!isPlainObject(parsed)) throw new Error("Could not patch tsconfig.json: expected a top-level JSON object.");
9746
+ const compilerOptions = isPlainObject(parsed.compilerOptions) ? { ...parsed.compilerOptions } : {};
9747
+ const paths = isPlainObject(compilerOptions.paths) ? { ...compilerOptions.paths } : {};
9748
+ if (!("@/*" in paths)) paths["@/*"] = [context.tsconfigAliasPath];
9749
+ paths["@convex/*"] = ["./convex/shared/*"];
9750
+ return `${JSON.stringify({
9751
+ ...parsed,
9752
+ compilerOptions: {
9753
+ ...compilerOptions,
9754
+ strictFunctionTypes: false,
9755
+ paths
9756
+ },
9757
+ ...context.usesSrc ? { include: ["src"] } : {}
9758
+ }, null, 2)}\n`;
9759
+ }
8966
9760
  function patchInitConvexTsconfigContent(source, functionsDirRelative) {
8967
9761
  const parsedResult = ts.parseConfigFileTextToJson("tsconfig.json", source);
8968
9762
  if (parsedResult.error || parsedResult.config === void 0) throw new Error("Could not patch Convex tsconfig.json: expected valid JSON or JSONC scaffold output.");
@@ -9074,6 +9868,7 @@ function patchInitReactMainContent(source) {
9074
9868
  }
9075
9869
  function patchInitReactViteConfigContent(source) {
9076
9870
  if (source.includes("'@convex'") || source.includes("\"@convex\"")) return source.endsWith("\n") ? source : `${source}\n`;
9871
+ if (source.includes("viteTsConfigPaths(") || source.includes("tsConfigPaths(")) return source.endsWith("\n") ? source : `${source}\n`;
9077
9872
  if (!source.includes("alias: {")) throw new Error("Could not patch vite.config.ts: expected a resolve.alias block.");
9078
9873
  const nextSource = source.replace("alias: {", `alias: {\n '@convex': path.resolve(__dirname, './convex/shared'),`);
9079
9874
  return nextSource.endsWith("\n") ? nextSource : `${nextSource}\n`;
@@ -9133,7 +9928,7 @@ function buildInitReactRootTsconfigPlanFile(context) {
9133
9928
  kind: "config",
9134
9929
  filePath,
9135
9930
  requiresExplicitOverwrite: false,
9136
- content: patchInitTsconfigContent(fs.readFileSync(filePath, "utf8"), context),
9931
+ content: context.framework === "tanstack-start" ? patchInitStartTsconfigContent(fs.readFileSync(filePath, "utf8"), context) : patchInitTsconfigContent(fs.readFileSync(filePath, "utf8"), context),
9137
9932
  updateReason: "Patch tsconfig.json to keep the app alias and add @convex/*.",
9138
9933
  createReason: "Patch tsconfig.json to keep the app alias and add @convex/*.",
9139
9934
  skipReason: "tsconfig.json already includes the kitcn alias."
@@ -9225,7 +10020,7 @@ function buildTemplateInitializationPlanFiles(params) {
9225
10020
  allowUnsupported: true
9226
10021
  });
9227
10022
  if (!projectContext) return [];
9228
- const plannedOwnedFiles = (projectContext.mode === "next-app" ? buildInitNextOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend, params.includeDemoFiles) : buildInitReactOwnedScaffoldFiles(projectContext, params.functionsDirRelative, params.backend)).map((file) => {
10023
+ 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) => {
9229
10024
  const filePath = resolve(process.cwd(), file.relativePath);
9230
10025
  const existingContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : void 0;
9231
10026
  const nextContent = typeof file.content === "function" ? file.content({ existingContent }) : file.content;
@@ -9255,6 +10050,11 @@ function buildTemplateInitializationPlanFiles(params) {
9255
10050
  buildInitNextLayoutPlanFile(projectContext)
9256
10051
  ];
9257
10052
  }
10053
+ if (projectContext.framework === "tanstack-start") return [
10054
+ ...plannedOwnedFiles,
10055
+ buildInitReactRootTsconfigPlanFile(projectContext),
10056
+ ...buildInitReactViteConfigPlanFile(projectContext)
10057
+ ];
9258
10058
  return [
9259
10059
  ...plannedOwnedFiles,
9260
10060
  buildInitReactRootTsconfigPlanFile(projectContext),
@@ -9483,7 +10283,7 @@ async function runScaffoldCommandFlow(params) {
9483
10283
  template: params.template
9484
10284
  });
9485
10285
  const applyResult = await applyPluginInstallPlanFiles(initPlan.files, {
9486
- overwrite: Boolean(params.overwrite),
10286
+ overwrite: Boolean(params.overwrite) || params.template !== void 0,
9487
10287
  yes: params.yes || Boolean(params.defaults),
9488
10288
  promptAdapter: params.promptAdapter
9489
10289
  });
@@ -9533,7 +10333,7 @@ async function runInitCommandFlow(params) {
9533
10333
  allowUnsupported: true
9534
10334
  });
9535
10335
  if (template !== void 0 || params.initArgs.defaults || params.initArgs.name !== void 0) {
9536
- if (!template) throw new Error("Fresh app scaffolding requires `kitcn init -t <next|vite>`.");
10336
+ if (!template) throw new Error("Fresh app scaffolding requires `kitcn init -t <next|start|vite>`.");
9537
10337
  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.`);
9538
10338
  return runScaffoldCommandFlow({
9539
10339
  allowCodegenBootstrapFallback: !params.initArgs.json,
@@ -9555,7 +10355,7 @@ async function runInitCommandFlow(params) {
9555
10355
  realConcavePath: params.realConcavePath
9556
10356
  });
9557
10357
  }
9558
- if (!existingProjectContext) throw new Error("Could not detect a supported app scaffold. Use `kitcn init -t <next|vite>` for a fresh app.");
10358
+ if (!existingProjectContext) throw new Error("Could not detect a supported app scaffold. Use `kitcn init -t <next|start|vite>` for a fresh app.");
9559
10359
  return runScaffoldCommandFlow({
9560
10360
  allowCodegenBootstrapFallback: !params.initArgs.json,
9561
10361
  projectDir,
@@ -10008,7 +10808,7 @@ async function runConvexInitIfNeeded(params) {
10008
10808
  stdout: "",
10009
10809
  stderr: ""
10010
10810
  };
10011
- const shouldUseAnonymousAgentMode = params.yes && !hasRemoteConvexInitTargetArgs(params.targetArgs);
10811
+ const agentModeOverride = params.yes && !hasRemoteConvexInitTargetArgs(params.targetArgs) ? "anonymous" : params.env?.CONVEX_AGENT_MODE;
10012
10812
  const result = normalizeConvexCommandResult(await params.execaFn(params.backendAdapter.command, [
10013
10813
  ...params.backendAdapter.argsPrefix,
10014
10814
  "init",
@@ -10017,7 +10817,7 @@ async function runConvexInitIfNeeded(params) {
10017
10817
  cwd: process.cwd(),
10018
10818
  env: createBackendCommandEnv({
10019
10819
  ...params.env,
10020
- CONVEX_AGENT_MODE: shouldUseAnonymousAgentMode ? "anonymous" : params.env?.CONVEX_AGENT_MODE
10820
+ ...agentModeOverride ? { CONVEX_AGENT_MODE: agentModeOverride } : {}
10021
10821
  }),
10022
10822
  reject: false,
10023
10823
  stdio: "pipe"
@@ -11953,6 +12753,7 @@ const handleAddCommand = async (argv, deps = {}) => {
11953
12753
  templates: selectedTemplates
11954
12754
  })) throw new Error(AUTH_SCHEMA_ONLY_MISSING_ERROR);
11955
12755
  }
12756
+ if (rawConvexAuthPreset && !addArgs.dryRun) assertRawConvexAuthDeploymentReady();
11956
12757
  if (!addArgs.dryRun) await applyPlanningDependencyInstall(pluginDescriptor.planningDependencies ?? [], execaFn);
11957
12758
  const plan = filterPluginInstallPlanForAddScope({
11958
12759
  plan: await buildPluginInstallPlan({
@@ -11995,7 +12796,6 @@ const handleAddCommand = async (argv, deps = {}) => {
11995
12796
  else logger.write(formatPlanSummary(plan));
11996
12797
  return 0;
11997
12798
  }
11998
- if (rawConvexAuthPreset) assertRawConvexAuthDeploymentReady();
11999
12799
  const applyResult = await applyPluginInstallPlanFiles(plan.files, {
12000
12800
  overwrite: addArgs.overwrite,
12001
12801
  yes: addArgs.yes,
@@ -12618,7 +13418,7 @@ const INIT_LOCAL_BOOTSTRAP_PROMPT = "Run one-shot local Convex bootstrap after i
12618
13418
  const INIT_HELP_TEXT = `Usage: kitcn init [options]
12619
13419
 
12620
13420
  Options:
12621
- --template, -t App template ("next" or "vite") for fresh app scaffolding
13421
+ --template, -t App template ("next", "start", or "vite") for fresh app scaffolding
12622
13422
  --cwd Target directory (or parent when used with --name)
12623
13423
  --name Project name when scaffolding a fresh app
12624
13424
  --prod Forward to \`convex init\`
@@ -13088,7 +13888,7 @@ const __filename = fileURLToPath(import.meta.url);
13088
13888
  const HELP_FLAGS = new Set(["--help", "-h"]);
13089
13889
  const VERSION_FLAGS = new Set(["--version", "-v"]);
13090
13890
  const packageJson = readOwnPackageJson(import.meta.url);
13091
- const REMOVED_CREATE_MESSAGE = "Removed `kitcn create`. Use `kitcn init -t <next|vite>` for fresh app scaffolding.";
13891
+ const REMOVED_CREATE_MESSAGE = "Removed `kitcn create`. Use `kitcn init -t <next|start|vite>` for fresh app scaffolding.";
13092
13892
  const LOCAL_NODE_REEXEC_ENV = "KITCN_NODE_REEXEC";
13093
13893
  const LOCAL_NODE_REEXEC_COMMANDS = new Set([
13094
13894
  "add",