Skip to content

Commit 7606ba4

Browse files
committed
feat(cli): add include-examples toggle for demo content
1 parent ebbc3fb commit 7606ba4

9 files changed

Lines changed: 119 additions & 17 deletions

File tree

docs/cli-reference.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ tanstack create [project-name] [options]
1717
| `--starter <url>` | Starter URL or local path |
1818
| `--package-manager <pm>` | `npm`, `pnpm`, `yarn`, `bun`, `deno` |
1919
| `--framework <name>` | `React`, `Solid` |
20-
| `--router-only` | Create Router-only SPA without TanStack Start (no SSR) |
20+
| `--router-only` | Create file-based Router-only app without TanStack Start (add-ons/deployment/starter disabled) |
2121
| `--toolchain <id>` | Toolchain add-on (use `--list-add-ons` to see options) |
2222
| `--deployment <id>` | Deployment add-on (use `--list-add-ons` to see options) |
23+
| `--examples` / `--no-examples` | Include or exclude demo/example pages |
2324
| `--tailwind` / `--no-tailwind` | Deprecated compatibility flags; accepted but ignored (Tailwind is always enabled) |
2425
| `--no-git` | Skip git init |
2526
| `--no-install` | Skip dependency install |
@@ -36,7 +37,7 @@ tanstack create [project-name] [options]
3637
# Examples
3738
tanstack create my-app -y
3839
tanstack create my-app --add-ons clerk,drizzle,tanstack-query
39-
tanstack create my-app --router-only --add-ons tanstack-query
40+
tanstack create my-app --router-only --toolchain eslint --no-examples
4041
tanstack create my-app --starter https://example.com/starter.json
4142
```
4243

packages/cli/src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ export function cli({
466466
'--no-tailwind',
467467
'Deprecated: compatibility flag; Tailwind opt-out is ignored',
468468
)
469+
.option('--examples', 'include demo/example pages')
470+
.option('--no-examples', 'exclude demo/example pages')
469471

470472
if (deployments.size > 0) {
471473
cmd.option<string>(

packages/cli/src/command-line.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ export async function normalizeOptions(
191191
return []
192192
}
193193

194-
const chosenAddOns = await selectAddOns()
194+
const includeExamples = cliOptions.examples ?? !routerOnly
195+
const chosenAddOnsRaw = await selectAddOns()
196+
const chosenAddOns = includeExamples
197+
? chosenAddOnsRaw
198+
: chosenAddOnsRaw.filter((addOn) => addOn.type !== 'example')
195199

196200
// Handle add-on configuration option
197201
let addOnOptionsFromCLI = {}
@@ -204,7 +208,7 @@ export async function normalizeOptions(
204208
}
205209
}
206210

207-
return {
211+
const normalized = {
208212
projectName: projectName,
209213
targetDir,
210214
framework,
@@ -224,6 +228,11 @@ export async function normalizeOptions(
224228
},
225229
starter: starter,
226230
}
231+
232+
;(normalized as Options & { includeExamples?: boolean }).includeExamples =
233+
includeExamples
234+
235+
return normalized
227236
}
228237

229238
export function validateDevWatchOptions(cliOptions: CliOptions): {

packages/cli/src/options.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
promptForAddOnOptions,
1414
selectAddOns,
1515
selectDeployment,
16+
selectExamples,
1617
selectGit,
1718
selectPackageManager,
1819
selectToolchain,
@@ -92,6 +93,12 @@ export async function promptForCreateOptions(
9293
// Add-ons selection
9394
const addOns: Set<string> = new Set()
9495

96+
// Examples/demo pages are enabled by default
97+
const includeExamples =
98+
cliOptions.examples ?? (routerOnly ? false : await selectExamples())
99+
;(options as Required<Options> & { includeExamples?: boolean }).includeExamples =
100+
includeExamples
101+
95102
if (toolchain) {
96103
addOns.add(toolchain)
97104
}
@@ -123,21 +130,26 @@ export async function promptForCreateOptions(
123130
addOns.add(addOn)
124131
}
125132

126-
for (const addOn of await selectAddOns(
127-
options.framework,
128-
options.mode,
129-
'example',
130-
'Would you like an example?',
131-
forcedAddOns,
132-
false,
133-
)) {
134-
addOns.add(addOn)
133+
if (includeExamples) {
134+
for (const addOn of await selectAddOns(
135+
options.framework,
136+
options.mode,
137+
'example',
138+
'Would you like an example?',
139+
forcedAddOns,
140+
false,
141+
)) {
142+
addOns.add(addOn)
143+
}
135144
}
136145
}
137146

138-
options.chosenAddOns = Array.from(
147+
const chosenAddOns = Array.from(
139148
await finalizeAddOns(options.framework, options.mode, Array.from(addOns)),
140149
)
150+
options.chosenAddOns = includeExamples
151+
? chosenAddOns
152+
: chosenAddOns.filter((addOn) => addOn.type !== 'example')
141153

142154
// Tailwind is always enabled
143155
options.tailwind = true

packages/cli/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export interface CliOptions {
2323
routerOnly?: boolean
2424
template?: string
2525
tailwind?: boolean
26+
examples?: boolean
2627
}

packages/cli/src/ui-prompts.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ export async function selectGit(): Promise<boolean> {
151151
return git
152152
}
153153

154+
export async function selectExamples(): Promise<boolean> {
155+
const includeExamples = await confirm({
156+
message: 'Would you like to include demo/example pages?',
157+
initialValue: true,
158+
})
159+
if (isCancel(includeExamples)) {
160+
cancel('Operation cancelled.')
161+
process.exit(0)
162+
}
163+
return includeExamples
164+
}
165+
154166
export async function selectToolchain(
155167
framework: Framework,
156168
toolchain?: string | false,

packages/cli/tests/command-line.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,23 @@ describe('normalizeOptions', () => {
300300
expect(options?.mode).toBe('file-router')
301301
})
302302

303+
it('includes examples by default in non-router-only mode', async () => {
304+
const options = await normalizeOptions({
305+
projectName: 'test',
306+
})
307+
308+
expect((options as any)?.includeExamples).toBe(true)
309+
})
310+
311+
it('supports disabling examples from the CLI', async () => {
312+
const options = await normalizeOptions({
313+
projectName: 'test',
314+
examples: false,
315+
})
316+
317+
expect((options as any)?.includeExamples).toBe(false)
318+
})
319+
303320
it('should ignore add-ons and deployment in router-only mode but keep toolchain', async () => {
304321
__testRegisterFramework({
305322
id: 'react-cra',

packages/create/src/create-app.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,51 @@ import { runSpecialSteps } from './special-steps/index.js'
1616

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

19+
function isDemoRoutePath(path?: string) {
20+
if (!path) return false
21+
const normalized = path.replace(/\\/g, '/')
22+
return (
23+
normalized.includes('/routes/demo/') ||
24+
normalized.includes('/routes/demo.') ||
25+
normalized.includes('/routes/example/') ||
26+
normalized.includes('/routes/example.')
27+
)
28+
}
29+
30+
function stripExamplesFromOptions(options: Options): Options {
31+
if (options.includeExamples !== false) {
32+
return options
33+
}
34+
35+
const chosenAddOns = options.chosenAddOns
36+
.filter((addOn) => addOn.type !== 'example')
37+
.map((addOn) => {
38+
const filteredRoutes = (addOn.routes || []).filter(
39+
(route) =>
40+
!isDemoRoutePath(route.path) &&
41+
!(route.url && route.url.startsWith('/demo')),
42+
)
43+
44+
return {
45+
...addOn,
46+
routes: filteredRoutes,
47+
getFiles: async () => {
48+
const files = await addOn.getFiles()
49+
return files.filter((file) => !isDemoRoutePath(file))
50+
},
51+
getDeletedFiles: async () => {
52+
const deletedFiles = await addOn.getDeletedFiles()
53+
return deletedFiles.filter((file) => !isDemoRoutePath(file))
54+
},
55+
}
56+
})
57+
58+
return {
59+
...options,
60+
chosenAddOns,
61+
}
62+
}
63+
1964
async function writeFiles(environment: Environment, options: Options) {
2065
const templateFileFromContent = createTemplateFile(environment, options)
2166

@@ -282,10 +327,12 @@ Please read the README.md file for information on testing, styling, adding route
282327
}
283328

284329
export async function createApp(environment: Environment, options: Options) {
330+
const effectiveOptions = stripExamplesFromOptions(options)
331+
285332
environment.startRun()
286-
await writeFiles(environment, options)
287-
await runCommandsAndInstallDependencies(environment, options)
333+
await writeFiles(environment, effectiveOptions)
334+
await runCommandsAndInstallDependencies(environment, effectiveOptions)
288335
environment.finishRun()
289336

290-
report(environment, options)
337+
report(environment, effectiveOptions)
291338
}

packages/create/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ export interface Options {
207207
addOnOptions: Record<string, Record<string, any>>
208208
starter?: Starter | undefined
209209
routerOnly?: boolean
210+
includeExamples?: boolean
210211
}
211212

212213
export type SerializedOptions = Omit<

0 commit comments

Comments
 (0)