create-start-app 0.3.0 → 0.4.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/README.md +41 -9
- package/dist/add-ons.js +69 -0
- package/dist/cli.js +78 -0
- package/dist/constants.js +4 -0
- package/dist/create-app.js +371 -0
- package/dist/index.js +2 -347
- package/dist/mcp.js +169 -0
- package/dist/options.js +260 -0
- package/dist/{utils/getPackageManager.js → package-manager.js} +1 -0
- package/dist/types.js +1 -0
- package/package.json +8 -4
- package/src/add-ons.ts +156 -0
- package/src/cli.ts +114 -0
- package/src/constants.ts +7 -0
- package/src/create-app.ts +582 -0
- package/src/index.ts +2 -507
- package/src/mcp.ts +205 -0
- package/src/options.ts +308 -0
- package/src/{utils/getPackageManager.ts → package-manager.ts} +1 -0
- package/src/types.ts +30 -0
- package/templates/react/add-on/clerk/README.md +3 -0
- package/templates/react/add-on/clerk/assets/_dot_env.local.append +2 -0
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
- package/templates/react/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
- package/templates/react/add-on/clerk/info.json +13 -0
- package/templates/react/add-on/clerk/package.json +5 -0
- package/templates/react/add-on/convex/README.md +4 -0
- package/templates/react/add-on/convex/assets/_dot_cursorrules.append +93 -0
- package/templates/react/add-on/convex/assets/_dot_env.local.append +3 -0
- package/templates/react/add-on/convex/assets/convex/products.ts +8 -0
- package/templates/react/add-on/convex/assets/convex/schema.ts +10 -0
- package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
- package/templates/react/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
- package/templates/react/add-on/convex/info.json +13 -0
- package/templates/react/add-on/convex/package.json +6 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +62 -0
- package/templates/react/add-on/form/info.json +13 -0
- package/templates/react/add-on/form/package.json +5 -0
- package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
- package/templates/react/add-on/module-federation/info.json +7 -0
- package/templates/react/add-on/module-federation/package.json +5 -0
- package/templates/react/add-on/netlify/README.md +11 -0
- package/templates/react/add-on/netlify/info.json +7 -0
- package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +22 -0
- package/templates/react/add-on/sentry/assets/_dot_env.local.append +2 -0
- package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +25 -0
- package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +480 -0
- package/templates/react/add-on/sentry/info.json +14 -0
- package/templates/react/add-on/sentry/package.json +7 -0
- package/templates/react/add-on/shadcn/README.md +7 -0
- package/templates/react/add-on/shadcn/assets/_dot_cursorrules.append +7 -0
- package/templates/react/add-on/shadcn/info.json +11 -0
- package/templates/react/add-on/start/assets/app.config.ts +16 -0
- package/templates/react/add-on/start/assets/postcss.config.ts +5 -0
- package/templates/react/add-on/start/assets/src/api.ts +6 -0
- package/templates/react/add-on/start/assets/src/client.tsx +10 -0
- package/templates/react/add-on/start/assets/src/router.tsx.ejs +51 -0
- package/templates/react/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
- package/templates/react/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
- package/templates/react/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
- package/templates/react/add-on/start/assets/src/ssr.tsx +12 -0
- package/templates/react/add-on/start/info.json +19 -0
- package/templates/react/add-on/start/package.json +14 -0
- package/templates/react/add-on/store/assets/src/lib/demo-store.ts +13 -0
- package/templates/react/add-on/store/assets/src/routes/demo.store.tsx.ejs +75 -0
- package/templates/react/add-on/store/info.json +13 -0
- package/templates/react/add-on/store/package.json +6 -0
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +9 -0
- package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +38 -0
- package/templates/react/add-on/tanstack-query/info.json +13 -0
- package/templates/react/add-on/tanstack-query/package.json +6 -0
- package/templates/{base → react/base}/README.md.ejs +9 -0
- package/templates/react/base/_dot_vscode/settings.json +11 -0
- package/templates/react/base/src/components/Header.tsx.ejs +25 -0
- package/templates/{base/tsconfig.json → react/base/tsconfig.json.ejs} +5 -1
- package/templates/react/base/vite.config.js.ejs +24 -0
- package/templates/{code-router → react/code-router}/src/main.tsx.ejs +17 -1
- package/templates/react/example/tanchat/README.md +37 -0
- package/templates/react/example/tanchat/assets/_dot_env.local.append +2 -0
- package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
- package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
- package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
- package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
- package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
- package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
- package/templates/react/example/tanchat/info.json +15 -0
- package/templates/react/example/tanchat/package.json +10 -0
- package/templates/react/file-router/src/routes/__root.tsx.ejs +71 -0
- package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +148 -0
- package/templates/solid/add-on/form/info.json +13 -0
- package/templates/solid/add-on/form/package.json +5 -0
- package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
- package/templates/solid/add-on/module-federation/info.json +7 -0
- package/templates/solid/add-on/module-federation/package.json +5 -0
- package/templates/solid/add-on/sentry/assets/_dot_cursorrules.append +22 -0
- package/templates/solid/add-on/sentry/assets/_dot_env.local.append +2 -0
- package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
- package/templates/solid/add-on/sentry/info.json +13 -0
- package/templates/solid/add-on/sentry/package.json +5 -0
- package/templates/solid/add-on/solid-ui/README.md +9 -0
- package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
- package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
- package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
- package/templates/solid/add-on/solid-ui/info.json +11 -0
- package/templates/solid/add-on/solid-ui/package.json +9 -0
- package/templates/solid/add-on/store/assets/src/lib/demo-store.ts +13 -0
- package/templates/solid/add-on/store/assets/src/routes/demo.store.tsx.ejs +77 -0
- package/templates/solid/add-on/store/info.json +13 -0
- package/templates/solid/add-on/store/package.json +6 -0
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
- package/templates/solid/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +30 -0
- package/templates/solid/add-on/tanstack-query/info.json +13 -0
- package/templates/solid/add-on/tanstack-query/package.json +6 -0
- package/templates/solid/base/README.md.ejs +200 -0
- package/templates/solid/base/_dot_cursorrules.append +35 -0
- package/templates/solid/base/_dot_gitignore +5 -0
- package/templates/solid/base/_dot_vscode/settings.json +11 -0
- package/templates/solid/base/index.html.ejs +20 -0
- package/templates/solid/base/package.json +22 -0
- package/templates/solid/base/package.ts.json +5 -0
- package/templates/solid/base/package.tw.json +6 -0
- package/templates/solid/base/public/favicon.ico +0 -0
- package/templates/solid/base/public/logo192.png +0 -0
- package/templates/solid/base/public/logo512.png +0 -0
- package/templates/solid/base/public/manifest.json +25 -0
- package/templates/solid/base/public/robots.txt +3 -0
- package/templates/solid/base/src/App.css +0 -0
- package/templates/solid/base/src/App.tsx.ejs +47 -0
- package/templates/solid/base/src/components/Header.tsx.ejs +26 -0
- package/templates/solid/base/src/logo.svg +120 -0
- package/templates/solid/base/src/styles.css.ejs +15 -0
- package/templates/solid/base/tsconfig.json.ejs +30 -0
- package/templates/solid/base/vite.config.js.ejs +22 -0
- package/templates/solid/code-router/src/main.tsx.ejs +69 -0
- package/templates/solid/file-router/package.fr.json +5 -0
- package/templates/solid/file-router/src/main.tsx.ejs +44 -0
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +41 -0
- package/templates/solid/file-router/src/routes/index.tsx +43 -0
- package/templates/base/vite.config.js.ejs +0 -15
- package/templates/file-router/src/routes/__root.tsx +0 -11
- /package/templates/{base/gitignore → react/base/_dot_gitignore} +0 -0
- /package/templates/{base → react/base}/index.html.ejs +0 -0
- /package/templates/{base → react/base}/package.json +0 -0
- /package/templates/{base → react/base}/package.ts.json +0 -0
- /package/templates/{base → react/base}/package.tw.json +0 -0
- /package/templates/{base → react/base}/public/favicon.ico +0 -0
- /package/templates/{base → react/base}/public/logo192.png +0 -0
- /package/templates/{base → react/base}/public/logo512.png +0 -0
- /package/templates/{base → react/base}/public/manifest.json +0 -0
- /package/templates/{base → react/base}/public/robots.txt +0 -0
- /package/templates/{base → react/base}/src/App.css +0 -0
- /package/templates/{base → react/base}/src/App.test.tsx.ejs +0 -0
- /package/templates/{base → react/base}/src/App.tsx.ejs +0 -0
- /package/templates/{base → react/base}/src/logo.svg +0 -0
- /package/templates/{base → react/base}/src/reportWebVitals.ts.ejs +0 -0
- /package/templates/{base → react/base}/src/styles.css.ejs +0 -0
- /package/templates/{file-router → react/file-router}/package.fr.json +0 -0
- /package/templates/{file-router → react/file-router}/src/main.tsx.ejs +0 -0
package/README.md
CHANGED
|
@@ -8,13 +8,14 @@ To help accelerate the migration away from `create-react-app` we created the `cr
|
|
|
8
8
|
|
|
9
9
|
To maintain compatability with `create-react-app` you can build a new application by running:
|
|
10
10
|
|
|
11
|
-
| Command
|
|
12
|
-
|
|
|
13
|
-
| `npx create-tsrouter-app@latest my-app`
|
|
14
|
-
| `npx create-tsrouter-app@latest my-app --template file-router`
|
|
15
|
-
| `npx create-tsrouter-app@latest my-app --template typescript`
|
|
16
|
-
| `npx create-tsrouter-app@latest my-app --
|
|
17
|
-
| `npx create-tsrouter-app@latest my-app --
|
|
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 using the Code Router |
|
|
16
|
+
| `npx create-tsrouter-app@latest my-app --tailwind` | Add Tailwind CSS support |
|
|
17
|
+
| `npx create-tsrouter-app@latest my-app --framework solid` | Create a Solid app |
|
|
18
|
+
| `npx create-tsrouter-app@latest my-app --framework solid --template file-router` | Create a Solid app with file-router |
|
|
18
19
|
|
|
19
20
|
If you don't specify a project name, the CLI will walk you through an interactive setup process:
|
|
20
21
|
|
|
@@ -43,8 +44,9 @@ Available options:
|
|
|
43
44
|
|
|
44
45
|
- `--template <type>`: Choose between `file-router`, `typescript`, or `javascript`
|
|
45
46
|
- `--tailwind`: Enable Tailwind CSS
|
|
46
|
-
- `--package-manager`: Specify your preferred package manager (`npm`, `yarn`, `pnpm`, or `
|
|
47
|
+
- `--package-manager`: Specify your preferred package manager (`npm`, `yarn`, `pnpm`, `bun`, or `deno`)
|
|
47
48
|
- `--no-git`: Do not initialize a git repository
|
|
49
|
+
- `--add-ons`: Enable add-on selection or specify add-ons to install
|
|
48
50
|
|
|
49
51
|
When using flags, the CLI will display which options were provided and only prompt for the remaining choices.
|
|
50
52
|
|
|
@@ -88,10 +90,40 @@ Enable Tailwind CSS either through the interactive CLI or by adding the `--tailw
|
|
|
88
90
|
|
|
89
91
|
### Package Manager
|
|
90
92
|
|
|
91
|
-
Choose your preferred package manager (`npm`, `bun`, `yarn`, or `
|
|
93
|
+
Choose your preferred package manager (`npm`, `bun`, `yarn`, `pnpm`, or `deno`) either through the interactive CLI or using the `--package-manager` flag.
|
|
92
94
|
|
|
93
95
|
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.
|
|
94
96
|
|
|
97
|
+
## Add-ons (experimental)
|
|
98
|
+
|
|
99
|
+
You can enable add-on selection:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx create-tsrouter-app@latest --add-ons
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This will prompt you to select the add-ons you want to enable during application creation.
|
|
106
|
+
|
|
107
|
+
You can enable specific add-ons directly by adding a comma separated list of add-on names to the `--add-ons` flag. For example:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx create-tsrouter-app@latest my-app --add-ons shadcn,tanstack-query
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
You can get a list of all available add-ons by running:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npx create-tsrouter-app@latest --list-add-ons
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This will display a list of all available add-ons for React that are compatible with the Code Router.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx create-tsrouter-app@latest --list-add-ons --framework solid --template file-router
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Will get you a list of all available add-ons for Solid that are compatible with the File Router.
|
|
126
|
+
|
|
95
127
|
# Contributing
|
|
96
128
|
|
|
97
129
|
Check out the [Contributing](CONTRIBUTING.md) guide.
|
package/dist/add-ons.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { DEFAULT_FRAMEWORK } from './constants.js';
|
|
7
|
+
function isDirectory(path) {
|
|
8
|
+
return statSync(path).isDirectory();
|
|
9
|
+
}
|
|
10
|
+
export async function getAllAddOns(framework, template) {
|
|
11
|
+
const addOns = [];
|
|
12
|
+
for (const type of ['add-on', 'example']) {
|
|
13
|
+
const addOnsBase = fileURLToPath(new URL(`../templates/${framework}/${type}`, import.meta.url));
|
|
14
|
+
if (!existsSync(addOnsBase)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
for (const dir of await readdirSync(addOnsBase).filter((file) => isDirectory(resolve(addOnsBase, file)))) {
|
|
18
|
+
const filePath = resolve(addOnsBase, dir, 'info.json');
|
|
19
|
+
const fileContent = await readFile(filePath, 'utf-8');
|
|
20
|
+
const info = JSON.parse(fileContent);
|
|
21
|
+
if (!info.templates.includes(template)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
let packageAdditions = {};
|
|
25
|
+
if (existsSync(resolve(addOnsBase, dir, 'package.json'))) {
|
|
26
|
+
packageAdditions = JSON.parse(await readFile(resolve(addOnsBase, dir, 'package.json'), 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
let readme;
|
|
29
|
+
if (existsSync(resolve(addOnsBase, dir, 'README.md'))) {
|
|
30
|
+
readme = await readFile(resolve(addOnsBase, dir, 'README.md'), 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
addOns.push({
|
|
33
|
+
...info,
|
|
34
|
+
id: dir,
|
|
35
|
+
type,
|
|
36
|
+
directory: resolve(addOnsBase, dir),
|
|
37
|
+
packageAdditions,
|
|
38
|
+
readme,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return addOns;
|
|
43
|
+
}
|
|
44
|
+
// Turn the list of chosen add-on IDs into a final list of add-ons by resolving dependencies
|
|
45
|
+
export async function finalizeAddOns(framework, template, chosenAddOnIDs) {
|
|
46
|
+
const finalAddOnIDs = new Set(chosenAddOnIDs);
|
|
47
|
+
const addOns = await getAllAddOns(framework, template);
|
|
48
|
+
for (const addOnID of finalAddOnIDs) {
|
|
49
|
+
const addOn = addOns.find((a) => a.id === addOnID);
|
|
50
|
+
if (!addOn) {
|
|
51
|
+
throw new Error(`Add-on ${addOnID} not found`);
|
|
52
|
+
}
|
|
53
|
+
for (const dependsOn of addOn.dependsOn || []) {
|
|
54
|
+
const dep = addOns.find((a) => a.id === dependsOn);
|
|
55
|
+
if (!dep) {
|
|
56
|
+
throw new Error(`Dependency ${dependsOn} not found`);
|
|
57
|
+
}
|
|
58
|
+
finalAddOnIDs.add(dep.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [...finalAddOnIDs].map((id) => addOns.find((a) => a.id === id));
|
|
62
|
+
}
|
|
63
|
+
export async function listAddOns(options) {
|
|
64
|
+
const mode = options.template === 'file-router' ? 'file-router' : 'code-router';
|
|
65
|
+
const addOns = await getAllAddOns(options.framework || DEFAULT_FRAMEWORK, mode);
|
|
66
|
+
for (const addOn of addOns) {
|
|
67
|
+
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
2
|
+
import { intro, log } from '@clack/prompts';
|
|
3
|
+
import { createApp } from './create-app.js';
|
|
4
|
+
import { normalizeOptions, promptForOptions } from './options.js';
|
|
5
|
+
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
|
|
6
|
+
import runServer from './mcp.js';
|
|
7
|
+
import { listAddOns } from './add-ons.js';
|
|
8
|
+
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js';
|
|
9
|
+
export function cli() {
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program
|
|
12
|
+
.name('create-tsrouter-app')
|
|
13
|
+
.description('CLI to create a new TanStack application')
|
|
14
|
+
.argument('[project-name]', 'name of the project')
|
|
15
|
+
.option('--no-git', 'do not create a git repository')
|
|
16
|
+
.option('--framework <type>', 'project framework (solid, react)', (value) => {
|
|
17
|
+
if (!SUPPORTED_FRAMEWORKS.includes(value)) {
|
|
18
|
+
throw new InvalidArgumentError(`Invalid framework: ${value}. Only the following are allowed: ${SUPPORTED_FRAMEWORKS.join(', ')}`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}, DEFAULT_FRAMEWORK)
|
|
22
|
+
.option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
|
|
23
|
+
if (value !== 'typescript' &&
|
|
24
|
+
value !== 'javascript' &&
|
|
25
|
+
value !== 'file-router') {
|
|
26
|
+
throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
})
|
|
30
|
+
.option(`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`, `Explicitly tell the CLI to use this package manager`, (value) => {
|
|
31
|
+
if (!SUPPORTED_PACKAGE_MANAGERS.includes(value)) {
|
|
32
|
+
throw new InvalidArgumentError(`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
})
|
|
36
|
+
.option('--tailwind', 'add Tailwind CSS', false)
|
|
37
|
+
.option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
|
|
38
|
+
let addOns = !!value;
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
addOns = value.split(',').map((addon) => addon.trim());
|
|
41
|
+
}
|
|
42
|
+
return addOns;
|
|
43
|
+
})
|
|
44
|
+
.option('--list-add-ons', 'list all available add-ons', false)
|
|
45
|
+
.option('--mcp', 'run the MCP server', false)
|
|
46
|
+
.action(async (projectName, options) => {
|
|
47
|
+
if (options.listAddOns) {
|
|
48
|
+
await listAddOns(options);
|
|
49
|
+
}
|
|
50
|
+
else if (options.mcp) {
|
|
51
|
+
await runServer();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
try {
|
|
55
|
+
const cliOptions = {
|
|
56
|
+
projectName,
|
|
57
|
+
...options,
|
|
58
|
+
};
|
|
59
|
+
let finalOptions = await normalizeOptions(cliOptions);
|
|
60
|
+
if (finalOptions) {
|
|
61
|
+
intro(`Creating a new TanStack app in ${projectName}...`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
intro("Let's configure your TanStack application");
|
|
65
|
+
finalOptions = await promptForOptions(cliOptions);
|
|
66
|
+
}
|
|
67
|
+
await createApp(finalOptions);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
log.error(error instanceof Error
|
|
71
|
+
? error.message
|
|
72
|
+
: 'An unknown error occurred');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
program.parse();
|
|
78
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { appendFile, copyFile, mkdir, readFile, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { log, outro, spinner } from '@clack/prompts';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import { render } from 'ejs';
|
|
9
|
+
import { format } from 'prettier';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { CODE_ROUTER, FILE_ROUTER } from './constants.js';
|
|
12
|
+
function sortObject(obj) {
|
|
13
|
+
return Object.keys(obj)
|
|
14
|
+
.sort()
|
|
15
|
+
.reduce((acc, key) => {
|
|
16
|
+
acc[key] = obj[key];
|
|
17
|
+
return acc;
|
|
18
|
+
}, {});
|
|
19
|
+
}
|
|
20
|
+
function createCopyFiles(targetDir) {
|
|
21
|
+
return async function copyFiles(templateDir, files) {
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const targetFileName = file.replace('.tw', '');
|
|
24
|
+
await copyFile(resolve(templateDir, file), resolve(targetDir, targetFileName));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function jsSafeName(name) {
|
|
29
|
+
return name
|
|
30
|
+
.split(/[^a-zA-Z0-9]/)
|
|
31
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
32
|
+
.join('');
|
|
33
|
+
}
|
|
34
|
+
function createTemplateFile(projectName, options, targetDir) {
|
|
35
|
+
return async function templateFile(templateDir, file, targetFileName, extraTemplateValues) {
|
|
36
|
+
const templateValues = {
|
|
37
|
+
packageManager: options.packageManager,
|
|
38
|
+
projectName: projectName,
|
|
39
|
+
typescript: options.typescript,
|
|
40
|
+
tailwind: options.tailwind,
|
|
41
|
+
js: options.typescript ? 'ts' : 'js',
|
|
42
|
+
jsx: options.typescript ? 'tsx' : 'jsx',
|
|
43
|
+
fileRouter: options.mode === FILE_ROUTER,
|
|
44
|
+
codeRouter: options.mode === CODE_ROUTER,
|
|
45
|
+
addOnEnabled: options.chosenAddOns.reduce((acc, addOn) => {
|
|
46
|
+
acc[addOn.id] = true;
|
|
47
|
+
return acc;
|
|
48
|
+
}, {}),
|
|
49
|
+
addOns: options.chosenAddOns,
|
|
50
|
+
...extraTemplateValues,
|
|
51
|
+
};
|
|
52
|
+
const template = await readFile(resolve(templateDir, file), 'utf-8');
|
|
53
|
+
let content = '';
|
|
54
|
+
try {
|
|
55
|
+
content = render(template, templateValues);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(chalk.red(`EJS error in file ${file}`));
|
|
59
|
+
console.error(error);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const target = targetFileName ?? file.replace('.ejs', '');
|
|
63
|
+
if (target.endsWith('.ts') || target.endsWith('.tsx')) {
|
|
64
|
+
content = await format(content, {
|
|
65
|
+
semi: false,
|
|
66
|
+
singleQuote: true,
|
|
67
|
+
trailingComma: 'all',
|
|
68
|
+
parser: 'typescript',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
await mkdir(dirname(resolve(targetDir, target)), {
|
|
72
|
+
recursive: true,
|
|
73
|
+
});
|
|
74
|
+
await writeFile(resolve(targetDir, target), content);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function createPackageJSON(projectName, options, templateDir, routerDir, targetDir, addOns) {
|
|
78
|
+
let packageJSON = JSON.parse(await readFile(resolve(templateDir, 'package.json'), 'utf8'));
|
|
79
|
+
packageJSON.name = projectName;
|
|
80
|
+
if (options.typescript) {
|
|
81
|
+
const tsPackageJSON = JSON.parse(await readFile(resolve(templateDir, 'package.ts.json'), 'utf8'));
|
|
82
|
+
packageJSON = {
|
|
83
|
+
...packageJSON,
|
|
84
|
+
devDependencies: {
|
|
85
|
+
...packageJSON.devDependencies,
|
|
86
|
+
...tsPackageJSON.devDependencies,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (options.tailwind) {
|
|
91
|
+
const twPackageJSON = JSON.parse(await readFile(resolve(templateDir, 'package.tw.json'), 'utf8'));
|
|
92
|
+
packageJSON = {
|
|
93
|
+
...packageJSON,
|
|
94
|
+
dependencies: {
|
|
95
|
+
...packageJSON.dependencies,
|
|
96
|
+
...twPackageJSON.dependencies,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (options.mode === FILE_ROUTER) {
|
|
101
|
+
const frPackageJSON = JSON.parse(await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'));
|
|
102
|
+
packageJSON = {
|
|
103
|
+
...packageJSON,
|
|
104
|
+
dependencies: {
|
|
105
|
+
...packageJSON.dependencies,
|
|
106
|
+
...frPackageJSON.dependencies,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
for (const addOn of addOns) {
|
|
111
|
+
packageJSON = {
|
|
112
|
+
...packageJSON,
|
|
113
|
+
dependencies: {
|
|
114
|
+
...packageJSON.dependencies,
|
|
115
|
+
...addOn.dependencies,
|
|
116
|
+
},
|
|
117
|
+
devDependencies: {
|
|
118
|
+
...packageJSON.devDependencies,
|
|
119
|
+
...addOn.devDependencies,
|
|
120
|
+
},
|
|
121
|
+
scripts: {
|
|
122
|
+
...packageJSON.scripts,
|
|
123
|
+
...addOn.scripts,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
packageJSON.dependencies = sortObject(packageJSON.dependencies);
|
|
128
|
+
packageJSON.devDependencies = sortObject(packageJSON.devDependencies);
|
|
129
|
+
await writeFile(resolve(targetDir, 'package.json'), JSON.stringify(packageJSON, null, 2));
|
|
130
|
+
}
|
|
131
|
+
async function copyFilesRecursively(source, target, copyFile, templateFile) {
|
|
132
|
+
const sourceStat = statSync(source);
|
|
133
|
+
if (sourceStat.isDirectory()) {
|
|
134
|
+
const files = readdirSync(source);
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const sourceChild = resolve(source, file);
|
|
137
|
+
const targetChild = resolve(target, file);
|
|
138
|
+
await copyFilesRecursively(sourceChild, targetChild, copyFile, templateFile);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
let targetFile = basename(target).replace(/_dot_/, '.');
|
|
143
|
+
let isTemplate = false;
|
|
144
|
+
if (targetFile.endsWith('.ejs')) {
|
|
145
|
+
targetFile = targetFile.replace('.ejs', '');
|
|
146
|
+
isTemplate = true;
|
|
147
|
+
}
|
|
148
|
+
let isAppend = false;
|
|
149
|
+
if (targetFile.endsWith('.append')) {
|
|
150
|
+
targetFile = targetFile.replace('.append', '');
|
|
151
|
+
isAppend = true;
|
|
152
|
+
}
|
|
153
|
+
const targetPath = resolve(dirname(target), targetFile);
|
|
154
|
+
await mkdir(dirname(targetPath), {
|
|
155
|
+
recursive: true,
|
|
156
|
+
});
|
|
157
|
+
if (isTemplate) {
|
|
158
|
+
await templateFile(source, targetPath);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (isAppend) {
|
|
162
|
+
await appendFile(targetPath, (await readFile(source)).toString());
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await copyFile(source, targetPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export async function createApp(options, { silent = false, } = {}) {
|
|
171
|
+
const templateDirBase = fileURLToPath(new URL(`../templates/${options.framework}/base`, import.meta.url));
|
|
172
|
+
const templateDirRouter = fileURLToPath(new URL(`../templates/${options.framework}/${options.mode}`, import.meta.url));
|
|
173
|
+
const targetDir = resolve(process.cwd(), options.projectName);
|
|
174
|
+
if (existsSync(targetDir)) {
|
|
175
|
+
if (!silent) {
|
|
176
|
+
log.error(`Directory "${options.projectName}" already exists`);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const copyFiles = createCopyFiles(targetDir);
|
|
181
|
+
const templateFile = createTemplateFile(options.projectName, options, targetDir);
|
|
182
|
+
const isAddOnEnabled = (id) => options.chosenAddOns.find((a) => a.id === id);
|
|
183
|
+
// Make the root directory
|
|
184
|
+
await mkdir(targetDir, { recursive: true });
|
|
185
|
+
// Setup the .vscode directory
|
|
186
|
+
await mkdir(resolve(targetDir, '.vscode'), { recursive: true });
|
|
187
|
+
await copyFile(resolve(templateDirBase, '_dot_vscode/settings.json'), resolve(targetDir, '.vscode/settings.json'));
|
|
188
|
+
// Fill the public directory
|
|
189
|
+
await mkdir(resolve(targetDir, 'public'), { recursive: true });
|
|
190
|
+
copyFiles(templateDirBase, [
|
|
191
|
+
'./public/robots.txt',
|
|
192
|
+
'./public/favicon.ico',
|
|
193
|
+
'./public/manifest.json',
|
|
194
|
+
'./public/logo192.png',
|
|
195
|
+
'./public/logo512.png',
|
|
196
|
+
]);
|
|
197
|
+
// Make the src directory
|
|
198
|
+
await mkdir(resolve(targetDir, 'src'), { recursive: true });
|
|
199
|
+
if (options.mode === FILE_ROUTER) {
|
|
200
|
+
await mkdir(resolve(targetDir, 'src/routes'), { recursive: true });
|
|
201
|
+
await mkdir(resolve(targetDir, 'src/components'), { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
// Check for a .cursorrules file
|
|
204
|
+
if (existsSync(resolve(templateDirBase, '.cursorrules'))) {
|
|
205
|
+
await copyFile(resolve(templateDirBase, '.cursorrules'), resolve(targetDir, '.cursorrules'));
|
|
206
|
+
}
|
|
207
|
+
// Copy in Vite and Tailwind config and CSS
|
|
208
|
+
if (!options.tailwind) {
|
|
209
|
+
await copyFiles(templateDirBase, ['./src/App.css']);
|
|
210
|
+
}
|
|
211
|
+
await templateFile(templateDirBase, './vite.config.js.ejs');
|
|
212
|
+
await templateFile(templateDirBase, './src/styles.css.ejs');
|
|
213
|
+
copyFiles(templateDirBase, ['./src/logo.svg']);
|
|
214
|
+
// Setup the main, reportWebVitals and index.html files
|
|
215
|
+
if (!isAddOnEnabled('start') && options.framework === 'react') {
|
|
216
|
+
if (options.typescript) {
|
|
217
|
+
await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs');
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs', './src/reportWebVitals.js');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!isAddOnEnabled('start')) {
|
|
224
|
+
await templateFile(templateDirBase, './index.html.ejs');
|
|
225
|
+
}
|
|
226
|
+
// Setup tsconfig
|
|
227
|
+
if (options.typescript) {
|
|
228
|
+
await templateFile(templateDirBase, './tsconfig.json.ejs', './tsconfig.json');
|
|
229
|
+
}
|
|
230
|
+
// Setup the package.json file, optionally with typescript and tailwind
|
|
231
|
+
await createPackageJSON(options.projectName, options, templateDirBase, templateDirRouter, targetDir, options.chosenAddOns.map((addOn) => addOn.packageAdditions));
|
|
232
|
+
// Copy all the asset files from the addons
|
|
233
|
+
const s = silent ? null : spinner();
|
|
234
|
+
for (const phase of ['setup', 'add-on', 'example']) {
|
|
235
|
+
for (const addOn of options.chosenAddOns.filter((addOn) => addOn.phase === phase)) {
|
|
236
|
+
s?.start(`Setting up ${addOn.name}...`);
|
|
237
|
+
const addOnDir = resolve(addOn.directory, 'assets');
|
|
238
|
+
if (existsSync(addOnDir)) {
|
|
239
|
+
await copyFilesRecursively(addOnDir, targetDir, copyFile, async (file, targetFileName) => templateFile(addOnDir, file, targetFileName));
|
|
240
|
+
}
|
|
241
|
+
if (addOn.command) {
|
|
242
|
+
await execa(addOn.command.command, addOn.command.args || [], {
|
|
243
|
+
cwd: targetDir,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
s?.stop(`${addOn.name} setup complete`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (isAddOnEnabled('shadcn')) {
|
|
250
|
+
const shadcnComponents = new Set();
|
|
251
|
+
for (const addOn of options.chosenAddOns) {
|
|
252
|
+
if (addOn.shadcnComponents) {
|
|
253
|
+
for (const component of addOn.shadcnComponents) {
|
|
254
|
+
shadcnComponents.add(component);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (shadcnComponents.size > 0) {
|
|
259
|
+
s?.start(`Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`);
|
|
260
|
+
await execa('npx', ['shadcn@canary', 'add', ...shadcnComponents], {
|
|
261
|
+
cwd: targetDir,
|
|
262
|
+
});
|
|
263
|
+
s?.stop(`Installed shadcn components`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const integrations = [];
|
|
267
|
+
if (existsSync(resolve(targetDir, 'src/integrations'))) {
|
|
268
|
+
for (const integration of readdirSync(resolve(targetDir, 'src/integrations'))) {
|
|
269
|
+
const integrationName = jsSafeName(integration);
|
|
270
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'layout.tsx'))) {
|
|
271
|
+
integrations.push({
|
|
272
|
+
type: 'layout',
|
|
273
|
+
name: `${integrationName}Layout`,
|
|
274
|
+
path: `integrations/${integration}/layout`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'provider.tsx'))) {
|
|
278
|
+
integrations.push({
|
|
279
|
+
type: 'provider',
|
|
280
|
+
name: `${integrationName}Provider`,
|
|
281
|
+
path: `integrations/${integration}/provider`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'header-user.tsx'))) {
|
|
285
|
+
integrations.push({
|
|
286
|
+
type: 'header-user',
|
|
287
|
+
name: `${integrationName}Header`,
|
|
288
|
+
path: `integrations/${integration}/header-user`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const routes = [];
|
|
294
|
+
if (existsSync(resolve(targetDir, 'src/routes'))) {
|
|
295
|
+
for (const file of readdirSync(resolve(targetDir, 'src/routes'))) {
|
|
296
|
+
const name = file.replace(/\.tsx?|\.jsx?/, '');
|
|
297
|
+
const safeRouteName = jsSafeName(name);
|
|
298
|
+
routes.push({
|
|
299
|
+
path: `./routes/${name}`,
|
|
300
|
+
name: safeRouteName,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Create the main entry point
|
|
305
|
+
if (!isAddOnEnabled('start')) {
|
|
306
|
+
if (options.typescript) {
|
|
307
|
+
await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.tsx', {
|
|
308
|
+
routes,
|
|
309
|
+
integrations,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.jsx', {
|
|
314
|
+
routes,
|
|
315
|
+
integrations,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
|
|
320
|
+
if (options.mode === FILE_ROUTER) {
|
|
321
|
+
await templateFile(templateDirRouter, './src/routes/__root.tsx.ejs', './src/routes/__root.tsx', {
|
|
322
|
+
integrations,
|
|
323
|
+
});
|
|
324
|
+
await templateFile(templateDirBase, './src/App.tsx.ejs', './src/routes/index.tsx');
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
await templateFile(templateDirBase, './src/App.tsx.ejs', options.typescript ? undefined : './src/App.jsx');
|
|
328
|
+
if (options.framework === 'react') {
|
|
329
|
+
await templateFile(templateDirBase, './src/App.test.tsx.ejs', options.typescript ? undefined : './src/App.test.jsx');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (routes.length > 0) {
|
|
333
|
+
await templateFile(templateDirBase, './src/components/Header.tsx.ejs', './src/components/Header.tsx', {
|
|
334
|
+
integrations,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
const warnings = [];
|
|
338
|
+
for (const addOn of options.chosenAddOns) {
|
|
339
|
+
if (addOn.warning) {
|
|
340
|
+
warnings.push(addOn.warning);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Add .gitignore
|
|
344
|
+
await copyFile(resolve(templateDirBase, '_dot_gitignore'), resolve(targetDir, '.gitignore'));
|
|
345
|
+
// Create the README.md
|
|
346
|
+
await templateFile(templateDirBase, 'README.md.ejs');
|
|
347
|
+
// Install dependencies
|
|
348
|
+
s?.start(`Installing dependencies via ${options.packageManager}...`);
|
|
349
|
+
await execa(options.packageManager, ['install'], { cwd: targetDir });
|
|
350
|
+
s?.stop(`Installed dependencies`);
|
|
351
|
+
if (warnings.length > 0) {
|
|
352
|
+
if (!silent) {
|
|
353
|
+
log.warn(chalk.red(warnings.join('\n')));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (options.git) {
|
|
357
|
+
s?.start(`Initializing git repository...`);
|
|
358
|
+
await execa('git', ['init'], { cwd: targetDir });
|
|
359
|
+
s?.stop(`Initialized git repository`);
|
|
360
|
+
}
|
|
361
|
+
if (!silent) {
|
|
362
|
+
outro(`Created your new TanStack app in '${basename(targetDir)}'.
|
|
363
|
+
|
|
364
|
+
Use the following commands to start your app:
|
|
365
|
+
% cd ${options.projectName}
|
|
366
|
+
% ${options.packageManager === 'deno' ? 'deno start' : options.packageManager} ${isAddOnEnabled('start') ? 'dev' : 'start'}
|
|
367
|
+
|
|
368
|
+
Please read README.md for more information on testing, styling, adding routes, react-query, etc.
|
|
369
|
+
`);
|
|
370
|
+
}
|
|
371
|
+
}
|