Skip to content

Commit e719c2a

Browse files
committed
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.
1 parent e054017 commit e719c2a

3 files changed

Lines changed: 102 additions & 12 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: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@ import type { Environment, FileBundleHandler, Options } from './types.js'
2020
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.') ||
28-
normalized.includes('/lib/demo-') ||
29-
normalized.includes('/lib/demo.') ||
30-
normalized.includes('/hooks/demo-') ||
31-
normalized.includes('/hooks/demo.') ||
32-
normalized.includes('/data/demo-') ||
33-
normalized.includes('/data/demo.') ||
34-
normalized.includes('/components/demo-') ||
35-
normalized.includes('/components/demo.')
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-')
3637
)
3738
}
3839

packages/create/tests/create-app.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,81 @@ describe('createApp', () => {
117117
expect(output.files['/src/test2.txt']).toEqual('base64::aGVsbG8=')
118118
expect(output.commands.some(({ command }) => command === 'echo')).toBe(true)
119119
})
120+
121+
it('should strip demo files from add-ons when includeExamples is false', async () => {
122+
const { environment, output } = createMemoryEnvironment()
123+
124+
const demoFiles = [
125+
'src/routes/demo/form.simple.tsx',
126+
'src/routes/demo.form.tsx',
127+
'src/routes/example.chat.tsx',
128+
'src/components/demo-AIAssistant.tsx',
129+
'src/components/demo.FormComponents.tsx',
130+
'src/hooks/demo-useAudioRecorder.ts',
131+
'src/hooks/demo.form.ts',
132+
'src/lib/demo-store.ts',
133+
'src/lib/demo.ai-hook.ts',
134+
'src/data/demo-guitars.ts',
135+
'src/store/demo.hooks.ts',
136+
'src/store/demo.store.ts',
137+
'src/demo.index.css',
138+
'public/demo-neon.svg',
139+
'public/example-guitar-flowers.jpg',
140+
]
141+
const keepFiles = [
142+
'src/routes/index.tsx',
143+
'src/components/Header.tsx',
144+
'src/lib/utils.ts',
145+
]
146+
const allFiles = [...demoFiles, ...keepFiles]
147+
148+
await createApp(environment, {
149+
...simpleOptions,
150+
includeExamples: false,
151+
chosenAddOns: [
152+
{
153+
id: 'test-addon',
154+
type: 'add-on',
155+
phase: 'add-on',
156+
packageAdditions: { dependencies: {}, devDependencies: {} },
157+
routes: [],
158+
integrations: [],
159+
getFiles: () => allFiles,
160+
getFileContents: () => 'content',
161+
getDeletedFiles: () => [],
162+
} as unknown as AddOn,
163+
],
164+
} as Options)
165+
166+
for (const file of demoFiles) {
167+
expect(output.files[`/${file}`]).toBeUndefined()
168+
}
169+
for (const file of keepFiles) {
170+
expect(output.files[`/${file}`]).toBeDefined()
171+
}
172+
})
173+
174+
it('should keep demo files from add-ons when includeExamples is true', async () => {
175+
const { environment, output } = createMemoryEnvironment()
176+
177+
await createApp(environment, {
178+
...simpleOptions,
179+
includeExamples: true,
180+
chosenAddOns: [
181+
{
182+
id: 'test-addon',
183+
type: 'add-on',
184+
phase: 'add-on',
185+
packageAdditions: { dependencies: {}, devDependencies: {} },
186+
routes: [],
187+
integrations: [],
188+
getFiles: () => ['src/components/demo-AIAssistant.tsx'],
189+
getFileContents: () => 'content',
190+
getDeletedFiles: () => [],
191+
} as unknown as AddOn,
192+
],
193+
} as Options)
194+
195+
expect(output.files['/src/components/demo-AIAssistant.tsx']).toBeDefined()
196+
})
120197
})

0 commit comments

Comments
 (0)