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.
- package/.github/workflows/auto.yml +4 -4
- package/README.md +65 -15
- package/dist/index.js +169 -20
- package/package.json +2 -2
- package/src/index.ts +205 -31
- package/templates/base/package.json +1 -0
- package/templates/base/src/logo.svg +3 -2
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
22
|
+
npx create-tsrouter-app@latest
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
|
|
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
|
-
|
|
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-
|
|
66
|
+
npx create-tsrouter-app@latest my-app --template file-router
|
|
23
67
|
```
|
|
24
68
|
|
|
25
|
-
|
|
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
|
-
|
|
77
|
+
## Additional Configuration
|
|
32
78
|
|
|
33
|
-
|
|
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
|
-
|
|
85
|
+
### Tailwind CSS
|
|
36
86
|
|
|
37
|
-
|
|
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
|
-
|
|
89
|
+
### Package Manager
|
|
40
90
|
|
|
41
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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('
|
|
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
|
-
}
|
|
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
|
-
}
|
|
189
|
-
.option('--tailwind', 'add Tailwind CSS'
|
|
190
|
-
.action((projectName, options) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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.
|
|
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": "
|
|
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 {
|
|
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(
|
|
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(
|
|
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('
|
|
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()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg id="Layer_1"
|
|
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.
|
|
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>
|