Skip to content

Commit e3de582

Browse files
tannerlinsleyWaryaWaynewyMinLwin
authored
fix(create): strip all demo/example files when users opt out (#434)
* fix(create): exclude demo files in lib, hooks, data, and components when demo is disabled Previously only demo route files were filtered out when the user chose no demo files. Demo files in components/, lib/, hooks/, and data/ directories would still be created. Rename isDemoRoutePath to isDemoFilePath to reflect the broader scope and extend pattern matching to cover all non-route demo file paths. * fix(create): exclude better-auth demo integration files when demo is disabled The better-auth header-user components reference the /demo/better-auth route, which doesn't exist when users opt out of demo files. Convert these to EJS templates with conditional rendering so they return null instead of linking to a non-existent route. Also extend isDemoFilePath filtering to cover add-on integration files (previously only routes, lib, hooks, data, and components were filtered in a037c4d). * fix(create): generate minimal scaffolding when declining demo/example pages * fix(create): move HeadContent to head block in Solid root template * fix(create): broaden demo file matching to catch all demo-prefixed files The directory-specific filter missed demo support files in /store/, /public/, at src root (demo.index.css), and example assets (example-guitar-*.jpg). Replace directory-specific checks with a filename-prefix match so any file named demo.*, demo-*, example.*, or example-* gets stripped when users opt out of demo pages, regardless of which directory it lives in. Add a regression test that exercises demo files across lib, hooks, data, components, store, public, and routes. --------- Co-authored-by: Abdullahi Mohamed <126521894+WaryaWayne@users.noreply.github.com> Co-authored-by: wyMinLwin <waiyanminlwin421@gmail.com>
1 parent 523c999 commit e3de582

19 files changed

Lines changed: 400 additions & 9 deletions

File tree

.changeset/fix-demo-file-leaks.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@tanstack/create': patch
3+
---
4+
5+
Fix demo/example files leaking into projects when users opt out of demo pages.
6+
7+
- Strip add-on demo support files in `src/lib/`, `src/hooks/`, `src/data/`, `src/components/`, `src/store/`, and any `demo.*` / `demo-*` / `example.*` / `example-*` files.
8+
- Strip example image assets under `public/`.
9+
- Generate a minimal base starter (no Header, Footer, ThemeToggle, about page, or styled index page) when declining demo/example pages.
10+
- Render Better Auth header-user component as `null` when its demo route is excluded, instead of linking to a non-existent route.
11+
12+
Closes #422, #409.

packages/create/src/create-app.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,23 @@ import { runSpecialSteps } from './special-steps/index.js'
1717

1818
import type { Environment, FileBundleHandler, Options } from './types.js'
1919

20-
function isDemoRoutePath(path?: string) {
20+
function isDemoFilePath(path?: string) {
2121
if (!path) return false
2222
const normalized = path.replace(/\\/g, '/')
23-
return (
23+
24+
if (
2425
normalized.includes('/routes/demo/') ||
25-
normalized.includes('/routes/demo.') ||
26-
normalized.includes('/routes/example/') ||
27-
normalized.includes('/routes/example.')
26+
normalized.includes('/routes/example/')
27+
) {
28+
return true
29+
}
30+
31+
const filename = normalized.split('/').pop() || ''
32+
return (
33+
filename.startsWith('demo.') ||
34+
filename.startsWith('demo-') ||
35+
filename.startsWith('example.') ||
36+
filename.startsWith('example-')
2837
)
2938
}
3039

@@ -38,20 +47,25 @@ function stripExamplesFromOptions(options: Options): Options {
3847
.map((addOn) => {
3948
const filteredRoutes = (addOn.routes || []).filter(
4049
(route) =>
41-
!isDemoRoutePath(route.path) &&
50+
!isDemoFilePath(route.path) &&
4251
!(route.url && route.url.startsWith('/demo')),
4352
)
53+
54+
const filteredIntegrations = (addOn.integrations || []).filter(
55+
(integration) => !isDemoFilePath(integration.path)
56+
)
4457

4558
return {
4659
...addOn,
4760
routes: filteredRoutes,
61+
integrations: filteredIntegrations,
4862
getFiles: async () => {
4963
const files = await addOn.getFiles()
50-
return files.filter((file) => !isDemoRoutePath(file))
64+
return files.filter((file) => !isDemoFilePath(file))
5165
},
5266
getDeletedFiles: async () => {
5367
const deletedFiles = await addOn.getDeletedFiles()
54-
return deletedFiles.filter((file) => !isDemoRoutePath(file))
68+
return deletedFiles.filter((file) => !isDemoFilePath(file))
5569
},
5670
}
5771
})

packages/create/src/frameworks/react/add-ons/better-auth/assets/src/integrations/better-auth/header-user.tsx renamed to packages/create/src/frameworks/react/add-ons/better-auth/assets/src/integrations/better-auth/header-user.tsx.ejs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { authClient } from "#/lib/auth-client";
2+
<%_ if (routes.some(r => r.url === '/demo/better-auth')) { _%>
23
import { Link } from "@tanstack/react-router";
4+
<%_ } _%>
35

46
export default function BetterAuthHeader() {
57
const { data: session, isPending } = authClient.useSession();
@@ -34,6 +36,7 @@ export default function BetterAuthHeader() {
3436
);
3537
}
3638

39+
<%_ if (routes.some(r => r.url === '/demo/better-auth')) { _%>
3740
return (
3841
<Link
3942
to="/demo/better-auth"
@@ -42,4 +45,7 @@ export default function BetterAuthHeader() {
4245
Sign in
4346
</Link>
4447
);
48+
<%_ } else { _%>
49+
return null;
50+
<%_ } _%>
4551
}

packages/create/src/frameworks/react/project/base/src/components/Footer.tsx.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<% if (!includeExamples) { ignoreFile(); return; } %>
12
export default function Footer() {
23
const year = new Date().getFullYear()
34

packages/create/src/frameworks/react/project/base/src/components/Header.tsx.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<% if (!includeExamples) { ignoreFile(); return; } %>
12
import { Link } from '@tanstack/react-router'
23
<% for (const integration of integrations.filter((i) => i.type === 'header-user')) { %>import <%= integration.jsName %> from '<%= relativePath(integration.path) %>'
34
<% } %>import ThemeToggle from './ThemeToggle'

packages/create/src/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<% if (!includeExamples) { ignoreFile(); return; } %>
12
import { useEffect, useState } from 'react'
23

34
type ThemeMode = 'light' | 'dark' | 'auto'

packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,99 @@ function RootComponent() {
2727
</>
2828
)
2929
}
30+
<% } else if (!includeExamples) { %>
31+
<% let hasContext = addOnEnabled["apollo-client"] || addOnEnabled["tanstack-query"]; %>
32+
import {
33+
HeadContent, Scripts, <% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router'
34+
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
35+
import { TanStackDevtools } from '@tanstack/react-devtools'
36+
<% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
37+
import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>'
38+
<% } %><% if (addOnEnabled.paraglide) { %>
39+
import { getLocale } from '#/paraglide/runtime'
40+
<% } %>
41+
import appCss from '../styles.css?url'
42+
<% if (addOnEnabled["apollo-client"]) { %>
43+
import type { ApolloClientIntegration } from "@apollo/client-integration-tanstack-start";
44+
<% } %>
45+
<% if (addOnEnabled["tanstack-query"]) { %>
46+
import type { QueryClient } from '@tanstack/react-query'
47+
<% if (addOnEnabled.tRPC) { %>
48+
import type { TRPCRouter } from '#/integrations/trpc/router'
49+
import type { TRPCOptionsProxy } from '@trpc/tanstack-react-query'
50+
<% } %>
51+
<% } %>
52+
<% if (hasContext) { %>
53+
interface MyRouterContext <% if (addOnEnabled["apollo-client"]) {%> extends ApolloClientIntegration.RouterContext <%} %>{
54+
<% if (addOnEnabled["tanstack-query"]) { %>
55+
queryClient: QueryClient
56+
<% if (addOnEnabled.tRPC) { %>
57+
trpc: TRPCOptionsProxy<TRPCRouter>
58+
<% } %>
59+
<% } %>
60+
}<% } %>
61+
62+
export const Route = <% if (hasContext) { %>createRootRouteWithContext<MyRouterContext>()<% } else { %>createRootRoute<% } %>({
63+
<% if (addOnEnabled.paraglide) { %>
64+
beforeLoad: async () => {
65+
// Other redirect strategies are possible; see
66+
// https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#offline-redirect
67+
if (typeof document !== 'undefined') {
68+
document.documentElement.setAttribute('lang', getLocale())
69+
}
70+
},
71+
<% } %>
72+
head: () => ({
73+
meta: [
74+
{
75+
charSet: 'utf-8',
76+
},
77+
{
78+
name: 'viewport',
79+
content: 'width=device-width, initial-scale=1',
80+
},
81+
{
82+
title: 'TanStack Start Starter',
83+
},
84+
],
85+
links: [
86+
{
87+
rel: 'stylesheet',
88+
href: appCss,
89+
},
90+
],
91+
}),
92+
shellComponent: RootDocument
93+
})
94+
95+
function RootDocument({ children }: { children: React.ReactNode }) {
96+
return (
97+
<% if (addOnEnabled.paraglide) { %><html lang={getLocale()}><% } else { %><html lang="en"><% } %>
98+
<head>
99+
<HeadContent />
100+
</head>
101+
<body>
102+
<% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>>
103+
<% } %>{children}
104+
<TanStackDevtools
105+
config={{
106+
position: 'bottom-right',
107+
}}
108+
plugins={[
109+
{
110+
name: 'Tanstack Router',
111+
render: <TanStackRouterDevtoolsPanel />,
112+
},
113+
<% for(const integration of integrations.filter(i => i.type === 'devtools')) { %><%= integration.jsName %>,<% } %>
114+
]}
115+
/>
116+
<% for(const integration of integrations.filter(i => i.type === 'layout')) { %><<%= integration.jsName %> />
117+
<% } %><% for(const integration of integrations.filter(i => i.type === 'provider').reverse()) { %></<%= integration.jsName %>>
118+
<% } %><Scripts />
119+
</body>
120+
</html>
121+
)
122+
}
30123
<% } else { %>
31124
<% let hasContext = addOnEnabled["apollo-client"] || addOnEnabled["tanstack-query"]; %>
32125
import {

packages/create/src/frameworks/react/project/base/src/routes/about.tsx.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<% if (!includeExamples) { ignoreFile(); return; } %>
12
import { createFileRoute } from '@tanstack/react-router'
23

34
export const Route = createFileRoute('/about')({

packages/create/src/frameworks/react/project/base/src/routes/index.tsx.ejs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
<% if (!includeExamples) { %>
2+
import { createFileRoute } from "@tanstack/react-router";
3+
4+
export const Route = createFileRoute("/")({ component: Home });
5+
6+
function Home() {
7+
return (
8+
<div className="p-8">
9+
<h1 className="text-4xl font-bold">Welcome to TanStack Start</h1>
10+
<p className="mt-4 text-lg">
11+
Edit <code>src/routes/index.tsx</code> to get started.
12+
</p>
13+
</div>
14+
);
15+
}
16+
<% } else { %>
117
import { createFileRoute } from "@tanstack/react-router";
218
319
export const Route = createFileRoute("/")({ component: App });
@@ -70,3 +86,4 @@ function App() {
7086
</main>
7187
);
7288
}
89+
<% } %>

packages/create/src/frameworks/react/project/base/src/styles.css.ejs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
<% if (!includeExamples) { %>
2+
@import "tailwindcss";
3+
4+
* {
5+
box-sizing: border-box;
6+
}
7+
8+
html,
9+
body,
10+
#app {
11+
min-height: 100%;
12+
}
13+
14+
body {
15+
margin: 0;
16+
}
17+
<% } else { %>
118
@import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,700&family=Manrope:wght@400;500;600;700;800&display=swap");
219
@import "tailwindcss";
320
@plugin "@tailwindcss/typography";
@@ -257,3 +274,4 @@ a {
257274
transform: translateY(0);
258275
}
259276
}
277+
<% } %>

0 commit comments

Comments
 (0)