create-start-app 0.1.2 → 0.2.0

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.
@@ -33,13 +33,13 @@ jobs:
33
33
 
34
34
  - name: Publish Packages
35
35
  run: |
36
- bunx gitpick@latest https://github.com/TanStack/create-tsrouter-app . --overwrite
37
- bun i
38
- bun run build
39
- rm -rf bun.lock
40
36
  npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
37
+ # ~ alias create-tsrouter-app
38
+ bunx gitpick@latest https://github.com/TanStack/create-tsrouter-app . --overwrite
39
+ bun i && bun run build && rm -rf bun.lock
41
40
  bunx json -I -f package.json -e 'this.name="create-router-app"'
42
41
  npm publish || true
42
+ # ~ alias create-tsrouter-app
43
43
  bunx json -I -f package.json -e 'this.name="create-start-app"'
44
44
  npm publish || true
45
45
  env:
package/README.md CHANGED
@@ -1,46 +1,96 @@
1
1
  # Create React App for TanStack Router
2
2
 
3
- This CLI applications builds Tanstack Start applications that are the functional equivalent of [Create React App](https://create-react-app.dev/).
3
+ This CLI applications builds Tanstack Router applications that are the functional equivalent of [Create React App](https://create-react-app.dev/).
4
4
 
5
5
  To help accelerate the migration away from `create-react-app` we created the `create-tsrouter-app` CLI which is a plug-n-play replacement for CRA.
6
6
 
7
- Instead of:
7
+ ## Quick Start
8
+
9
+ To maintain compatability with `create-react-app` you can build a new application by running:
10
+
11
+ | Command | Description |
12
+ | -------------------------------------------------------------- | --------------------------- |
13
+ | `npx create-tsrouter-app@latest my-app` | Create a new app |
14
+ | `npx create-tsrouter-app@latest my-app --template file-router` | Create a new file based app |
15
+ | `npx create-tsrouter-app@latest my-app --template typescript` | Create a new TypeScript app |
16
+ | `npx create-tsrouter-app@latest my-app --template javascript` | Create a new JavaScript app |
17
+ | `npx create-tsrouter-app@latest my-app --tailwind` | Add Tailwind CSS support |
18
+
19
+ If you don't specify a project name, the CLI will walk you through an interactive setup process:
8
20
 
9
21
  ```bash
10
- npx create-react-app my-app
22
+ npx create-tsrouter-app@latest
11
23
  ```
12
24
 
13
- You can now run:
25
+ This will start an interactive CLI that guides you through the setup process, allowing you to choose:
26
+
27
+ - Project Name
28
+ - Router Type (File-based or Code-based routing)
29
+ - TypeScript support
30
+ - Tailwind CSS integration
31
+ - Package manager
32
+ - Git initialization
33
+
34
+ ## Command Line Options
35
+
36
+ You can also use command line flags to specify your preferences directly:
14
37
 
15
38
  ```bash
16
- npx create-tsrouter-app@latest my-app
39
+ npx create-tsrouter-app@latest my-app --template file-router --tailwind --package-manager pnpm
17
40
  ```
18
41
 
19
- Instead of using:
42
+ Available options:
43
+
44
+ - `--template <type>`: Choose between `file-router`, `typescript`, or `javascript`
45
+ - `--tailwind`: Enable Tailwind CSS
46
+ - `--package-manager`: Specify your preferred package manager (`npm`, `yarn`, `pnpm`, or `bun`)
47
+ - `--no-git`: Do not initialize a git repository
48
+
49
+ When using flags, the CLI will display which options were provided and only prompt for the remaining choices.
50
+
51
+ ## Features
52
+
53
+ What you'll get is a Vite application that uses TanStack Router. All the files will still be in the same place as in CRA, but you'll get a fully functional Router setup under in `app/main.tsx`.
54
+
55
+ `create-tsrouter-app` is everything you loved about CRA but implemented with modern tools and best practices, on top of the popular TanStack set of libraries. Which includes [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/react-router](https://tanstack.com/router/latest).
56
+
57
+ ## Routing Options
58
+
59
+ ### File Based Routing (Recommended)
60
+
61
+ File Based Routing is the default option when using the interactive CLI. The location of the home page will be `app/routes/index.tsx`. This approach provides a more intuitive and maintainable way to structure your routes.
62
+
63
+ To explicitly choose File Based Routing, use:
20
64
 
21
65
  ```bash
22
- npx create-react-app my-app --template typescript
66
+ npx create-tsrouter-app@latest my-app --template file-router
23
67
  ```
24
68
 
25
- To create a SPA application using TypeScript. You can now run:
69
+ ### Code Based Routing
70
+
71
+ If you prefer traditional code-based routing, you can select it in the interactive CLI or specify it by using either the `typescript` or `javascript` template:
26
72
 
27
73
  ```bash
28
74
  npx create-tsrouter-app@latest my-app --template typescript
29
75
  ```
30
76
 
31
- What you'll get is a Vite application that uses TanStack Router. All the files will still be in the same place as in CRA, but you'll get a fully functional Router setup under in `app/main.tsx`.
77
+ ## Additional Configuration
32
78
 
33
- `create-tsrouter-app` is everything you loved about CRA but implemented with modern tools and best practices, on top of the popular TanStack set of libraries. Which includes [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/react-router](https://tanstack.com/router/latest).
79
+ ### TypeScript
80
+
81
+ - File Based Routing always uses TypeScript
82
+ - For Code Based Routing, you can choose between TypeScript and JavaScript
83
+ - Enable TypeScript explicitly with `--template typescript`
34
84
 
35
- If you want Tailwind then just add `--tailwind` and that will automatically configure [Tailwind V4](https://tailwindcss.com/).
85
+ ### Tailwind CSS
36
86
 
37
- You can also specify your preferred package manager with `--package-manager` such as `npm`, `bun`, `yarn`, or `pnpm`.
87
+ Enable Tailwind CSS either through the interactive CLI or by adding the `--tailwind` flag. This will automatically configure [Tailwind V4](https://tailwindcss.com/).
38
88
 
39
- Extensive documentation on using the TanStack Router, migrating to a File Base Routing approach, as well as integrating [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/store](https://tanstack.com/store/latest) be found in the generated `README.md` for your project.
89
+ ### Package Manager
40
90
 
41
- ## File Based Routing
91
+ Choose your preferred package manager (`npm`, `bun`, `yarn`, or `pnpm`) either through the interactive CLI or using the `--package-manager` flag.
42
92
 
43
- By default `create-tsrouter-app` will create a Code Based Routing application. If you want to use File Based Routing then you can specify `--template file-router`. The location of the home page will be `app/routes/index.tsx`.
93
+ Extensive documentation on using the TanStack Router, migrating to a File Base Routing approach, as well as integrating [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/store](https://tanstack.com/store/latest) can be found in the generated `README.md` for your project.
44
94
 
45
95
  # Contributing
46
96
 
package/dist/index.js CHANGED
@@ -4,10 +4,10 @@ import { existsSync } from 'node:fs';
4
4
  import { resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { Command, InvalidArgumentError } from 'commander';
7
- import { intro, outro, spinner, log } from '@clack/prompts';
7
+ import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text, } from '@clack/prompts';
8
8
  import { execa } from 'execa';
9
9
  import { render } from 'ejs';
10
- import { SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './utils/getPackageManager.js';
10
+ import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './utils/getPackageManager.js';
11
11
  const program = new Command();
12
12
  const CODE_ROUTER = 'code-router';
13
13
  const FILE_ROUTER = 'file-router';
@@ -82,16 +82,16 @@ async function createPackageJSON(projectName, options, templateDir, routerDir, t
82
82
  packageJSON.devDependencies = sortObject(packageJSON.devDependencies);
83
83
  await writeFile(resolve(targetDir, 'package.json'), JSON.stringify(packageJSON, null, 2));
84
84
  }
85
- async function createApp(projectName, options) {
85
+ async function createApp(options) {
86
86
  const templateDirBase = fileURLToPath(new URL('../templates/base', import.meta.url));
87
87
  const templateDirRouter = fileURLToPath(new URL(`../templates/${options.mode}`, import.meta.url));
88
- const targetDir = resolve(process.cwd(), projectName);
88
+ const targetDir = resolve(process.cwd(), options.projectName);
89
89
  if (existsSync(targetDir)) {
90
- log.error(`Directory "${projectName}" already exists`);
90
+ log.error(`Directory "${options.projectName}" already exists`);
91
91
  return;
92
92
  }
93
93
  const copyFiles = createCopyFile(targetDir);
94
- const templateFile = createTemplateFile(projectName, options, targetDir);
94
+ const templateFile = createTemplateFile(options.projectName, options, targetDir);
95
95
  intro(`Creating a new TanStack app in ${targetDir}...`);
96
96
  // Make the root directory
97
97
  await mkdir(targetDir, { recursive: true });
@@ -148,7 +148,7 @@ async function createApp(projectName, options) {
148
148
  await copyFiles(templateDirBase, ['./tsconfig.json']);
149
149
  }
150
150
  // Setup the package.json file, optionally with typescript and tailwind
151
- await createPackageJSON(projectName, options, templateDirBase, templateDirRouter, targetDir);
151
+ await createPackageJSON(options.projectName, options, templateDirBase, templateDirRouter, targetDir);
152
152
  // Add .gitignore
153
153
  await copyFile(resolve(templateDirBase, 'gitignore'), resolve(targetDir, '.gitignore'));
154
154
  // Create the README.md
@@ -158,20 +158,161 @@ async function createApp(projectName, options) {
158
158
  s.start(`Installing dependencies via ${options.packageManager}...`);
159
159
  await execa(options.packageManager, ['install'], { cwd: targetDir });
160
160
  s.stop(`Installed dependencies`);
161
+ if (options.git) {
162
+ s.start(`Initializing git repository...`);
163
+ await execa('git', ['init'], { cwd: targetDir });
164
+ s.stop(`Initialized git repository`);
165
+ }
161
166
  outro(`Created your new TanStack app in ${targetDir}.
162
167
 
163
168
  Use the following commands to start your app:
164
169
 
165
- % cd ${projectName}
170
+ % cd ${options.projectName}
166
171
  % ${options.packageManager} start
167
172
 
168
173
  Please read README.md for more information on testing, styling, adding routes, react-query, etc.
169
174
  `);
170
175
  }
176
+ // If all CLI options are provided, use them directly
177
+ function normalizeOptions(cliOptions) {
178
+ if (cliOptions.projectName) {
179
+ const typescript = cliOptions.template === 'typescript' ||
180
+ cliOptions.template === 'file-router';
181
+ return {
182
+ projectName: cliOptions.projectName,
183
+ typescript,
184
+ tailwind: !!cliOptions.tailwind,
185
+ packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
186
+ mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
187
+ git: !!cliOptions.git,
188
+ };
189
+ }
190
+ }
191
+ async function promptForOptions(cliOptions) {
192
+ const options = {};
193
+ if (!cliOptions.projectName) {
194
+ const value = await text({
195
+ message: 'What would you like to name your project?',
196
+ defaultValue: 'my-app',
197
+ validate(value) {
198
+ if (!value) {
199
+ return 'Please enter a name';
200
+ }
201
+ },
202
+ });
203
+ if (isCancel(value)) {
204
+ cancel('Operation cancelled.');
205
+ process.exit(0);
206
+ }
207
+ options.projectName = value;
208
+ }
209
+ // Router type selection
210
+ if (!cliOptions.template) {
211
+ const routerType = await select({
212
+ message: 'Select the router type:',
213
+ options: [
214
+ {
215
+ value: FILE_ROUTER,
216
+ label: 'File Router - File-based routing structure',
217
+ },
218
+ {
219
+ value: CODE_ROUTER,
220
+ label: 'Code Router - Traditional code-based routing',
221
+ },
222
+ ],
223
+ initialValue: FILE_ROUTER,
224
+ });
225
+ if (isCancel(routerType)) {
226
+ cancel('Operation cancelled.');
227
+ process.exit(0);
228
+ }
229
+ options.mode = routerType;
230
+ }
231
+ else {
232
+ options.mode = cliOptions.template;
233
+ if (options.mode === FILE_ROUTER) {
234
+ options.typescript = true;
235
+ }
236
+ }
237
+ // TypeScript selection (if using Code Router)
238
+ if (!options.typescript) {
239
+ if (options.mode === CODE_ROUTER) {
240
+ const typescriptEnable = await confirm({
241
+ message: 'Would you like to use TypeScript?',
242
+ initialValue: true,
243
+ });
244
+ if (isCancel(typescriptEnable)) {
245
+ cancel('Operation cancelled.');
246
+ process.exit(0);
247
+ }
248
+ options.typescript = typescriptEnable;
249
+ }
250
+ else {
251
+ options.typescript = true;
252
+ }
253
+ }
254
+ // Tailwind selection
255
+ if (cliOptions.tailwind === undefined) {
256
+ const tailwind = await confirm({
257
+ message: 'Would you like to use Tailwind CSS?',
258
+ initialValue: true,
259
+ });
260
+ if (isCancel(tailwind)) {
261
+ cancel('Operation cancelled.');
262
+ process.exit(0);
263
+ }
264
+ options.tailwind = tailwind;
265
+ }
266
+ else {
267
+ options.tailwind = cliOptions.tailwind;
268
+ }
269
+ // Package manager selection
270
+ if (cliOptions.packageManager === undefined) {
271
+ const detectedPackageManager = getPackageManager();
272
+ if (!detectedPackageManager) {
273
+ const pm = await select({
274
+ message: 'Select package manager:',
275
+ options: SUPPORTED_PACKAGE_MANAGERS.map((pm) => ({
276
+ value: pm,
277
+ label: pm,
278
+ })),
279
+ initialValue: DEFAULT_PACKAGE_MANAGER,
280
+ });
281
+ if (isCancel(pm)) {
282
+ cancel('Operation cancelled.');
283
+ process.exit(0);
284
+ }
285
+ options.packageManager = pm;
286
+ }
287
+ else {
288
+ options.packageManager = detectedPackageManager;
289
+ }
290
+ }
291
+ else {
292
+ options.packageManager = cliOptions.packageManager;
293
+ }
294
+ // Git selection
295
+ if (cliOptions.git === undefined) {
296
+ const git = await confirm({
297
+ message: 'Would you like to initialize a new git repository?',
298
+ initialValue: true,
299
+ });
300
+ if (isCancel(git)) {
301
+ cancel('Operation cancelled.');
302
+ process.exit(0);
303
+ }
304
+ options.git = git;
305
+ }
306
+ else {
307
+ options.git = !!cliOptions.git;
308
+ }
309
+ return options;
310
+ }
171
311
  program
172
312
  .name('create-tsrouter-app')
173
313
  .description('CLI to create a new TanStack application')
174
- .argument('<project-name>', 'name of the project')
314
+ .argument('[project-name]', 'name of the project')
315
+ .option('--no-git', 'do not create a git repository')
175
316
  .option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
176
317
  if (value !== 'typescript' &&
177
318
  value !== 'javascript' &&
@@ -179,21 +320,29 @@ program
179
320
  throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`);
180
321
  }
181
322
  return value;
182
- }, 'javascript')
323
+ })
183
324
  .option(`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`, `Explicitly tell the CLI to use this package manager`, (value) => {
184
325
  if (!SUPPORTED_PACKAGE_MANAGERS.includes(value)) {
185
326
  throw new InvalidArgumentError(`Invalid package manager: ${value}. Only the following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
186
327
  }
187
328
  return value;
188
- }, getPackageManager())
189
- .option('--tailwind', 'add Tailwind CSS', false)
190
- .action((projectName, options) => {
191
- const typescript = options.template === 'typescript' || options.template === 'file-router';
192
- createApp(projectName, {
193
- typescript,
194
- tailwind: options.tailwind,
195
- packageManager: options.packageManager,
196
- mode: options.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
197
- });
329
+ })
330
+ .option('--tailwind', 'add Tailwind CSS')
331
+ .action(async (projectName, options) => {
332
+ try {
333
+ const cliOptions = {
334
+ projectName,
335
+ ...options,
336
+ };
337
+ let finalOptions = normalizeOptions(cliOptions);
338
+ if (!finalOptions) {
339
+ finalOptions = await promptForOptions(cliOptions);
340
+ }
341
+ await createApp(finalOptions);
342
+ }
343
+ catch (error) {
344
+ log.error(error instanceof Error ? error.message : 'An unknown error occurred');
345
+ process.exit(1);
346
+ }
198
347
  });
199
348
  program.parse();
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "create-start-app",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Tanstack Application Builder",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "start": "tsc && node dist/index.js",
10
- "test": "echo \"Error: no test specified\" && exit 0",
10
+ "test": "npm run test:lint",
11
11
  "cipublish": "node scripts/publish.js",
12
12
  "test:lint": "eslint ./src"
13
13
  },
package/src/index.ts CHANGED
@@ -5,11 +5,22 @@ import { existsSync } from 'node:fs'
5
5
  import { resolve } from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { Command, InvalidArgumentError } from 'commander'
8
- import { intro, outro, spinner, log } from '@clack/prompts'
8
+ import {
9
+ cancel,
10
+ confirm,
11
+ intro,
12
+ isCancel,
13
+ log,
14
+ outro,
15
+ select,
16
+ spinner,
17
+ text,
18
+ } from '@clack/prompts'
9
19
  import { execa } from 'execa'
10
20
  import { render } from 'ejs'
11
21
 
12
22
  import {
23
+ DEFAULT_PACKAGE_MANAGER,
13
24
  SUPPORTED_PACKAGE_MANAGERS,
14
25
  getPackageManager,
15
26
  } from './utils/getPackageManager.js'
@@ -22,10 +33,20 @@ const CODE_ROUTER = 'code-router'
22
33
  const FILE_ROUTER = 'file-router'
23
34
 
24
35
  interface Options {
36
+ projectName: string
25
37
  typescript: boolean
26
38
  tailwind: boolean
27
39
  packageManager: PackageManager
28
40
  mode: typeof CODE_ROUTER | typeof FILE_ROUTER
41
+ git: boolean
42
+ }
43
+
44
+ interface CliOptions {
45
+ template?: 'typescript' | 'javascript' | 'file-router'
46
+ tailwind?: boolean
47
+ packageManager?: PackageManager
48
+ projectName?: string
49
+ git?: boolean
29
50
  }
30
51
 
31
52
  function sortObject(obj: Record<string, string>): Record<string, string> {
@@ -136,22 +157,26 @@ async function createPackageJSON(
136
157
  )
137
158
  }
138
159
 
139
- async function createApp(projectName: string, options: Required<Options>) {
160
+ async function createApp(options: Required<Options>) {
140
161
  const templateDirBase = fileURLToPath(
141
162
  new URL('../templates/base', import.meta.url),
142
163
  )
143
164
  const templateDirRouter = fileURLToPath(
144
165
  new URL(`../templates/${options.mode}`, import.meta.url),
145
166
  )
146
- const targetDir = resolve(process.cwd(), projectName)
167
+ const targetDir = resolve(process.cwd(), options.projectName)
147
168
 
148
169
  if (existsSync(targetDir)) {
149
- log.error(`Directory "${projectName}" already exists`)
170
+ log.error(`Directory "${options.projectName}" already exists`)
150
171
  return
151
172
  }
152
173
 
153
174
  const copyFiles = createCopyFile(targetDir)
154
- const templateFile = createTemplateFile(projectName, options, targetDir)
175
+ const templateFile = createTemplateFile(
176
+ options.projectName,
177
+ options,
178
+ targetDir,
179
+ )
155
180
 
156
181
  intro(`Creating a new TanStack app in ${targetDir}...`)
157
182
 
@@ -241,7 +266,7 @@ async function createApp(projectName: string, options: Required<Options>) {
241
266
 
242
267
  // Setup the package.json file, optionally with typescript and tailwind
243
268
  await createPackageJSON(
244
- projectName,
269
+ options.projectName,
245
270
  options,
246
271
  templateDirBase,
247
272
  templateDirRouter,
@@ -263,21 +288,174 @@ async function createApp(projectName: string, options: Required<Options>) {
263
288
  await execa(options.packageManager, ['install'], { cwd: targetDir })
264
289
  s.stop(`Installed dependencies`)
265
290
 
291
+ if (options.git) {
292
+ s.start(`Initializing git repository...`)
293
+ await execa('git', ['init'], { cwd: targetDir })
294
+ s.stop(`Initialized git repository`)
295
+ }
296
+
266
297
  outro(`Created your new TanStack app in ${targetDir}.
267
298
 
268
299
  Use the following commands to start your app:
269
300
 
270
- % cd ${projectName}
301
+ % cd ${options.projectName}
271
302
  % ${options.packageManager} start
272
303
 
273
304
  Please read README.md for more information on testing, styling, adding routes, react-query, etc.
274
305
  `)
275
306
  }
276
307
 
308
+ // If all CLI options are provided, use them directly
309
+ function normalizeOptions(
310
+ cliOptions: CliOptions,
311
+ ): Required<Options> | undefined {
312
+ if (cliOptions.projectName) {
313
+ const typescript =
314
+ cliOptions.template === 'typescript' ||
315
+ cliOptions.template === 'file-router'
316
+
317
+ return {
318
+ projectName: cliOptions.projectName,
319
+ typescript,
320
+ tailwind: !!cliOptions.tailwind,
321
+ packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
322
+ mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
323
+ git: !!cliOptions.git,
324
+ }
325
+ }
326
+ }
327
+
328
+ async function promptForOptions(
329
+ cliOptions: CliOptions,
330
+ ): Promise<Required<Options>> {
331
+ const options = {} as Required<Options>
332
+
333
+ if (!cliOptions.projectName) {
334
+ const value = await text({
335
+ message: 'What would you like to name your project?',
336
+ defaultValue: 'my-app',
337
+ validate(value) {
338
+ if (!value) {
339
+ return 'Please enter a name'
340
+ }
341
+ },
342
+ })
343
+ if (isCancel(value)) {
344
+ cancel('Operation cancelled.')
345
+ process.exit(0)
346
+ }
347
+ options.projectName = value
348
+ }
349
+
350
+ // Router type selection
351
+ if (!cliOptions.template) {
352
+ const routerType = await select({
353
+ message: 'Select the router type:',
354
+ options: [
355
+ {
356
+ value: FILE_ROUTER,
357
+ label: 'File Router - File-based routing structure',
358
+ },
359
+ {
360
+ value: CODE_ROUTER,
361
+ label: 'Code Router - Traditional code-based routing',
362
+ },
363
+ ],
364
+ initialValue: FILE_ROUTER,
365
+ })
366
+ if (isCancel(routerType)) {
367
+ cancel('Operation cancelled.')
368
+ process.exit(0)
369
+ }
370
+ options.mode = routerType as typeof CODE_ROUTER | typeof FILE_ROUTER
371
+ } else {
372
+ options.mode = cliOptions.template as
373
+ | typeof CODE_ROUTER
374
+ | typeof FILE_ROUTER
375
+ if (options.mode === FILE_ROUTER) {
376
+ options.typescript = true
377
+ }
378
+ }
379
+
380
+ // TypeScript selection (if using Code Router)
381
+ if (!options.typescript) {
382
+ if (options.mode === CODE_ROUTER) {
383
+ const typescriptEnable = await confirm({
384
+ message: 'Would you like to use TypeScript?',
385
+ initialValue: true,
386
+ })
387
+ if (isCancel(typescriptEnable)) {
388
+ cancel('Operation cancelled.')
389
+ process.exit(0)
390
+ }
391
+ options.typescript = typescriptEnable
392
+ } else {
393
+ options.typescript = true
394
+ }
395
+ }
396
+
397
+ // Tailwind selection
398
+ if (cliOptions.tailwind === undefined) {
399
+ const tailwind = await confirm({
400
+ message: 'Would you like to use Tailwind CSS?',
401
+ initialValue: true,
402
+ })
403
+ if (isCancel(tailwind)) {
404
+ cancel('Operation cancelled.')
405
+ process.exit(0)
406
+ }
407
+ options.tailwind = tailwind
408
+ } else {
409
+ options.tailwind = cliOptions.tailwind
410
+ }
411
+
412
+ // Package manager selection
413
+ if (cliOptions.packageManager === undefined) {
414
+ const detectedPackageManager = getPackageManager()
415
+ if (!detectedPackageManager) {
416
+ const pm = await select({
417
+ message: 'Select package manager:',
418
+ options: SUPPORTED_PACKAGE_MANAGERS.map((pm) => ({
419
+ value: pm,
420
+ label: pm,
421
+ })),
422
+ initialValue: DEFAULT_PACKAGE_MANAGER,
423
+ })
424
+ if (isCancel(pm)) {
425
+ cancel('Operation cancelled.')
426
+ process.exit(0)
427
+ }
428
+ options.packageManager = pm
429
+ } else {
430
+ options.packageManager = detectedPackageManager
431
+ }
432
+ } else {
433
+ options.packageManager = cliOptions.packageManager
434
+ }
435
+
436
+ // Git selection
437
+ if (cliOptions.git === undefined) {
438
+ const git = await confirm({
439
+ message: 'Would you like to initialize a new git repository?',
440
+ initialValue: true,
441
+ })
442
+ if (isCancel(git)) {
443
+ cancel('Operation cancelled.')
444
+ process.exit(0)
445
+ }
446
+ options.git = git
447
+ } else {
448
+ options.git = !!cliOptions.git
449
+ }
450
+
451
+ return options
452
+ }
453
+
277
454
  program
278
455
  .name('create-tsrouter-app')
279
456
  .description('CLI to create a new TanStack application')
280
- .argument('<project-name>', 'name of the project')
457
+ .argument('[project-name]', 'name of the project')
458
+ .option('--no-git', 'do not create a git repository')
281
459
  .option<'typescript' | 'javascript' | 'file-router'>(
282
460
  '--template <type>',
283
461
  'project template (typescript, javascript, file-router)',
@@ -293,7 +471,6 @@ program
293
471
  }
294
472
  return value
295
473
  },
296
- 'javascript',
297
474
  )
298
475
  .option<PackageManager>(
299
476
  `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
@@ -308,28 +485,25 @@ program
308
485
  }
309
486
  return value as PackageManager
310
487
  },
311
- getPackageManager(),
312
- )
313
- .option('--tailwind', 'add Tailwind CSS', false)
314
- .action(
315
- (
316
- projectName: string,
317
- options: {
318
- template: 'typescript' | 'javascript' | 'file-router'
319
- tailwind: boolean
320
- packageManager: PackageManager
321
- },
322
- ) => {
323
- const typescript =
324
- options.template === 'typescript' || options.template === 'file-router'
325
-
326
- createApp(projectName, {
327
- typescript,
328
- tailwind: options.tailwind,
329
- packageManager: options.packageManager,
330
- mode: options.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
331
- })
332
- },
333
488
  )
489
+ .option('--tailwind', 'add Tailwind CSS')
490
+ .action(async (projectName: string, options: CliOptions) => {
491
+ try {
492
+ const cliOptions = {
493
+ projectName,
494
+ ...options,
495
+ } as CliOptions
496
+ let finalOptions = normalizeOptions(cliOptions)
497
+ if (!finalOptions) {
498
+ finalOptions = await promptForOptions(cliOptions)
499
+ }
500
+ await createApp(finalOptions)
501
+ } catch (error) {
502
+ log.error(
503
+ error instanceof Error ? error.message : 'An unknown error occurred',
504
+ )
505
+ process.exit(1)
506
+ }
507
+ })
334
508
 
335
509
  program.parse()
@@ -15,6 +15,7 @@
15
15
  "react-dom": "^19.0.0"
16
16
  },
17
17
  "devDependencies": {
18
+ "@testing-library/dom": "^10.4.0",
18
19
  "@testing-library/react": "^16.2.0",
19
20
  "@types/react": "^19.0.8",
20
21
  "@types/react-dom": "^19.0.3",
@@ -1,5 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
2
+ <svg id="Layer_1"
3
+ xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
3
4
  <!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
4
5
  <defs>
5
6
  <style>
@@ -13,7 +14,7 @@
13
14
  </style>
14
15
  </defs>
15
16
  <g>
16
- <path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2s16-1.8,22.6-5.6c28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3h0ZM536.1,229.8c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9h0ZM490.3,336.3c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9h0ZM522.6,323.3c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1h0ZM421.2,430c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32ZM346.8,371.1c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4ZM420.7,163c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32ZM346.7,221.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9h0ZM256.2,347.1c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2h0ZM310,490c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7h0ZM547.2,413.8c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1ZM585.7,347.1c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6h0Z"/>
17
+ <path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
17
18
  <circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
18
19
  <path class="st1" d="M520.5,78.1"/>
19
20
  </g>