create-start-app 0.1.2

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.
Files changed (44) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/FUNDING.yml +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. package/.github/workflows/auto.yml +46 -0
  6. package/.github/workflows/ci.yml +43 -0
  7. package/.nvmrc +1 -0
  8. package/.prettierignore +3 -0
  9. package/CONTRIBUTING.md +34 -0
  10. package/LICENSE +21 -0
  11. package/README.md +51 -0
  12. package/dist/index.js +199 -0
  13. package/dist/utils/getPackageManager.js +15 -0
  14. package/eslint.config.js +35 -0
  15. package/package.json +48 -0
  16. package/prettier.config.js +10 -0
  17. package/scripts/publish.js +33 -0
  18. package/src/index.ts +335 -0
  19. package/src/utils/getPackageManager.ts +22 -0
  20. package/templates/base/.vscode/settings.json +11 -0
  21. package/templates/base/README.md.ejs +530 -0
  22. package/templates/base/gitignore +5 -0
  23. package/templates/base/index.html.ejs +20 -0
  24. package/templates/base/package.json +28 -0
  25. package/templates/base/package.ts.json +7 -0
  26. package/templates/base/package.tw.json +6 -0
  27. package/templates/base/public/favicon.ico +0 -0
  28. package/templates/base/public/logo192.png +0 -0
  29. package/templates/base/public/logo512.png +0 -0
  30. package/templates/base/public/manifest.json +25 -0
  31. package/templates/base/public/robots.txt +3 -0
  32. package/templates/base/src/App.css +38 -0
  33. package/templates/base/src/App.test.tsx.ejs +10 -0
  34. package/templates/base/src/App.tsx.ejs +74 -0
  35. package/templates/base/src/logo.svg +43 -0
  36. package/templates/base/src/reportWebVitals.ts.ejs +28 -0
  37. package/templates/base/src/styles.css.ejs +15 -0
  38. package/templates/base/tsconfig.json +24 -0
  39. package/templates/base/vite.config.js.ejs +15 -0
  40. package/templates/code-router/src/main.tsx.ejs +61 -0
  41. package/templates/file-router/package.fr.json +5 -0
  42. package/templates/file-router/src/main.tsx.ejs +35 -0
  43. package/templates/file-router/src/routes/__root.tsx +11 -0
  44. package/tsconfig.json +15 -0
package/src/index.ts ADDED
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
4
+ import { existsSync } from 'node:fs'
5
+ import { resolve } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { Command, InvalidArgumentError } from 'commander'
8
+ import { intro, outro, spinner, log } from '@clack/prompts'
9
+ import { execa } from 'execa'
10
+ import { render } from 'ejs'
11
+
12
+ import {
13
+ SUPPORTED_PACKAGE_MANAGERS,
14
+ getPackageManager,
15
+ } from './utils/getPackageManager.js'
16
+
17
+ import type { PackageManager } from './utils/getPackageManager.js'
18
+
19
+ const program = new Command()
20
+
21
+ const CODE_ROUTER = 'code-router'
22
+ const FILE_ROUTER = 'file-router'
23
+
24
+ interface Options {
25
+ typescript: boolean
26
+ tailwind: boolean
27
+ packageManager: PackageManager
28
+ mode: typeof CODE_ROUTER | typeof FILE_ROUTER
29
+ }
30
+
31
+ function sortObject(obj: Record<string, string>): Record<string, string> {
32
+ return Object.keys(obj)
33
+ .sort()
34
+ .reduce<Record<string, string>>((acc, key) => {
35
+ acc[key] = obj[key]
36
+ return acc
37
+ }, {})
38
+ }
39
+
40
+ function createCopyFile(targetDir: string) {
41
+ return async function copyFiles(templateDir: string, files: Array<string>) {
42
+ for (const file of files) {
43
+ const targetFileName = file.replace('.tw', '')
44
+ await copyFile(
45
+ resolve(templateDir, file),
46
+ resolve(targetDir, targetFileName),
47
+ )
48
+ }
49
+ }
50
+ }
51
+
52
+ function createTemplateFile(
53
+ projectName: string,
54
+ options: Required<Options>,
55
+ targetDir: string,
56
+ ) {
57
+ return async function templateFile(
58
+ templateDir: string,
59
+ file: string,
60
+ targetFileName?: string,
61
+ ) {
62
+ const templateValues = {
63
+ packageManager: options.packageManager,
64
+ projectName: projectName,
65
+ typescript: options.typescript,
66
+ tailwind: options.tailwind,
67
+ js: options.typescript ? 'ts' : 'js',
68
+ jsx: options.typescript ? 'tsx' : 'jsx',
69
+ fileRouter: options.mode === FILE_ROUTER,
70
+ codeRouter: options.mode === CODE_ROUTER,
71
+ }
72
+
73
+ const template = await readFile(resolve(templateDir, file), 'utf-8')
74
+ const content = render(template, templateValues)
75
+ const target = targetFileName ?? file.replace('.ejs', '')
76
+ await writeFile(resolve(targetDir, target), content)
77
+ }
78
+ }
79
+
80
+ async function createPackageJSON(
81
+ projectName: string,
82
+ options: Required<Options>,
83
+ templateDir: string,
84
+ routerDir: string,
85
+ targetDir: string,
86
+ ) {
87
+ let packageJSON = JSON.parse(
88
+ await readFile(resolve(templateDir, 'package.json'), 'utf8'),
89
+ )
90
+ packageJSON.name = projectName
91
+ if (options.typescript) {
92
+ const tsPackageJSON = JSON.parse(
93
+ await readFile(resolve(templateDir, 'package.ts.json'), 'utf8'),
94
+ )
95
+ packageJSON = {
96
+ ...packageJSON,
97
+ devDependencies: {
98
+ ...packageJSON.devDependencies,
99
+ ...tsPackageJSON.devDependencies,
100
+ },
101
+ }
102
+ }
103
+ if (options.tailwind) {
104
+ const twPackageJSON = JSON.parse(
105
+ await readFile(resolve(templateDir, 'package.tw.json'), 'utf8'),
106
+ )
107
+ packageJSON = {
108
+ ...packageJSON,
109
+ dependencies: {
110
+ ...packageJSON.dependencies,
111
+ ...twPackageJSON.dependencies,
112
+ },
113
+ }
114
+ }
115
+ if (options.mode === FILE_ROUTER) {
116
+ const frPackageJSON = JSON.parse(
117
+ await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
118
+ )
119
+ packageJSON = {
120
+ ...packageJSON,
121
+ dependencies: {
122
+ ...packageJSON.dependencies,
123
+ ...frPackageJSON.dependencies,
124
+ },
125
+ }
126
+ }
127
+ packageJSON.dependencies = sortObject(
128
+ packageJSON.dependencies as Record<string, string>,
129
+ )
130
+ packageJSON.devDependencies = sortObject(
131
+ packageJSON.devDependencies as Record<string, string>,
132
+ )
133
+ await writeFile(
134
+ resolve(targetDir, 'package.json'),
135
+ JSON.stringify(packageJSON, null, 2),
136
+ )
137
+ }
138
+
139
+ async function createApp(projectName: string, options: Required<Options>) {
140
+ const templateDirBase = fileURLToPath(
141
+ new URL('../templates/base', import.meta.url),
142
+ )
143
+ const templateDirRouter = fileURLToPath(
144
+ new URL(`../templates/${options.mode}`, import.meta.url),
145
+ )
146
+ const targetDir = resolve(process.cwd(), projectName)
147
+
148
+ if (existsSync(targetDir)) {
149
+ log.error(`Directory "${projectName}" already exists`)
150
+ return
151
+ }
152
+
153
+ const copyFiles = createCopyFile(targetDir)
154
+ const templateFile = createTemplateFile(projectName, options, targetDir)
155
+
156
+ intro(`Creating a new TanStack app in ${targetDir}...`)
157
+
158
+ // Make the root directory
159
+ await mkdir(targetDir, { recursive: true })
160
+
161
+ // Setup the .vscode directory
162
+ await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
163
+ await copyFile(
164
+ resolve(templateDirBase, '.vscode/settings.json'),
165
+ resolve(targetDir, '.vscode/settings.json'),
166
+ )
167
+
168
+ // Fill the public directory
169
+ await mkdir(resolve(targetDir, 'public'), { recursive: true })
170
+ copyFiles(templateDirBase, [
171
+ './public/robots.txt',
172
+ './public/favicon.ico',
173
+ './public/manifest.json',
174
+ './public/logo192.png',
175
+ './public/logo512.png',
176
+ ])
177
+
178
+ // Make the src directory
179
+ await mkdir(resolve(targetDir, 'src'), { recursive: true })
180
+ if (options.mode === FILE_ROUTER) {
181
+ await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
182
+ }
183
+
184
+ // Copy in Vite and Tailwind config and CSS
185
+ if (!options.tailwind) {
186
+ await copyFiles(templateDirBase, ['./src/App.css'])
187
+ }
188
+ await templateFile(templateDirBase, './vite.config.js.ejs')
189
+ await templateFile(templateDirBase, './src/styles.css.ejs')
190
+
191
+ copyFiles(templateDirBase, ['./src/logo.svg'])
192
+
193
+ // Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
194
+ if (options.mode === FILE_ROUTER) {
195
+ copyFiles(templateDirRouter, ['./src/routes/__root.tsx'])
196
+ await templateFile(
197
+ templateDirBase,
198
+ './src/App.tsx.ejs',
199
+ './src/routes/index.tsx',
200
+ )
201
+ } else {
202
+ await templateFile(
203
+ templateDirBase,
204
+ './src/App.tsx.ejs',
205
+ options.typescript ? undefined : './src/App.jsx',
206
+ )
207
+ await templateFile(
208
+ templateDirBase,
209
+ './src/App.test.tsx.ejs',
210
+ options.typescript ? undefined : './src/App.test.jsx',
211
+ )
212
+ }
213
+
214
+ // Create the main entry point
215
+ if (options.typescript) {
216
+ await templateFile(templateDirRouter, './src/main.tsx.ejs')
217
+ } else {
218
+ await templateFile(
219
+ templateDirRouter,
220
+ './src/main.tsx.ejs',
221
+ './src/main.jsx',
222
+ )
223
+ }
224
+
225
+ // Setup the main, reportWebVitals and index.html files
226
+ if (options.typescript) {
227
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs')
228
+ } else {
229
+ await templateFile(
230
+ templateDirBase,
231
+ './src/reportWebVitals.ts.ejs',
232
+ './src/reportWebVitals.js',
233
+ )
234
+ }
235
+ await templateFile(templateDirBase, './index.html.ejs')
236
+
237
+ // Setup tsconfig
238
+ if (options.typescript) {
239
+ await copyFiles(templateDirBase, ['./tsconfig.json'])
240
+ }
241
+
242
+ // Setup the package.json file, optionally with typescript and tailwind
243
+ await createPackageJSON(
244
+ projectName,
245
+ options,
246
+ templateDirBase,
247
+ templateDirRouter,
248
+ targetDir,
249
+ )
250
+
251
+ // Add .gitignore
252
+ await copyFile(
253
+ resolve(templateDirBase, 'gitignore'),
254
+ resolve(targetDir, '.gitignore'),
255
+ )
256
+
257
+ // Create the README.md
258
+ await templateFile(templateDirBase, 'README.md.ejs')
259
+
260
+ // Install dependencies
261
+ const s = spinner()
262
+ s.start(`Installing dependencies via ${options.packageManager}...`)
263
+ await execa(options.packageManager, ['install'], { cwd: targetDir })
264
+ s.stop(`Installed dependencies`)
265
+
266
+ outro(`Created your new TanStack app in ${targetDir}.
267
+
268
+ Use the following commands to start your app:
269
+
270
+ % cd ${projectName}
271
+ % ${options.packageManager} start
272
+
273
+ Please read README.md for more information on testing, styling, adding routes, react-query, etc.
274
+ `)
275
+ }
276
+
277
+ program
278
+ .name('create-tsrouter-app')
279
+ .description('CLI to create a new TanStack application')
280
+ .argument('<project-name>', 'name of the project')
281
+ .option<'typescript' | 'javascript' | 'file-router'>(
282
+ '--template <type>',
283
+ 'project template (typescript, javascript, file-router)',
284
+ (value) => {
285
+ if (
286
+ value !== 'typescript' &&
287
+ value !== 'javascript' &&
288
+ value !== 'file-router'
289
+ ) {
290
+ throw new InvalidArgumentError(
291
+ `Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
292
+ )
293
+ }
294
+ return value
295
+ },
296
+ 'javascript',
297
+ )
298
+ .option<PackageManager>(
299
+ `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
300
+ `Explicitly tell the CLI to use this package manager`,
301
+ (value) => {
302
+ if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
303
+ throw new InvalidArgumentError(
304
+ `Invalid package manager: ${value}. Only the following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(
305
+ ', ',
306
+ )}`,
307
+ )
308
+ }
309
+ return value as PackageManager
310
+ },
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
+ )
334
+
335
+ program.parse()
@@ -0,0 +1,22 @@
1
+ export const SUPPORTED_PACKAGE_MANAGERS = [
2
+ 'npm',
3
+ 'yarn',
4
+ 'pnpm',
5
+ 'bun',
6
+ ] as const
7
+ export type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number]
8
+ export const DEFAULT_PACKAGE_MANAGER: PackageManager = 'npm'
9
+
10
+ export function getPackageManager(): PackageManager | undefined {
11
+ const userAgent = process.env.npm_config_user_agent
12
+
13
+ if (userAgent === undefined) {
14
+ return DEFAULT_PACKAGE_MANAGER
15
+ }
16
+
17
+ const packageManager = SUPPORTED_PACKAGE_MANAGERS.find((manager) =>
18
+ userAgent.startsWith(manager),
19
+ )
20
+
21
+ return packageManager || DEFAULT_PACKAGE_MANAGER
22
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "files.watcherExclude": {
3
+ "**/routeTree.gen.ts": true
4
+ },
5
+ "search.exclude": {
6
+ "**/routeTree.gen.ts": true
7
+ },
8
+ "files.readonlyInclude": {
9
+ "**/routeTree.gen.ts": true
10
+ }
11
+ }