Skip to content

Commit 905f219

Browse files
jherrIainMcHugh
andauthored
feat: Adding option for toolchain (#37)
Co-authored-by: Iain McHugh <[email protected]>
1 parent 9d4e6a6 commit 905f219

File tree

19 files changed

+308
-17
lines changed

19 files changed

+308
-17
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This will start an interactive CLI that guides you through the setup process, al
3030
- TypeScript support
3131
- Tailwind CSS integration
3232
- Package manager
33+
- Toolchain
3334
- Git initialization
3435

3536
## Command Line Options
@@ -45,6 +46,7 @@ Available options:
4546
- `--template <type>`: Choose between `file-router`, `typescript`, or `javascript`
4647
- `--tailwind`: Enable Tailwind CSS
4748
- `--package-manager`: Specify your preferred package manager (`npm`, `yarn`, `pnpm`, `bun`, or `deno`)
49+
- `--toolchain`: Specify your toolchain solution for formatting/linting (`biome`)
4850
- `--no-git`: Do not initialize a git repository
4951
- `--add-ons`: Enable add-on selection or specify add-ons to install
5052

@@ -94,6 +96,12 @@ Choose your preferred package manager (`npm`, `bun`, `yarn`, `pnpm`, or `deno`)
9496

9597
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.
9698

99+
### Toolchain
100+
101+
Choose your preferred solution for formatting and linting either through the interactive CLI or using the `--toolchain` flag.
102+
103+
Setting this flag to `biome` will configure it as your toolchain of choice, adding a `biome.json` to the root of the project. Consult the [biome documentation](https://biomejs.dev/guides/getting-started/) for further customization.
104+
97105
## Add-ons (experimental)
98106

99107
You can enable add-on selection:

src/cli.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { intro, log } from '@clack/prompts'
44
import { createApp } from './create-app.js'
55
import { normalizeOptions, promptForOptions } from './options.js'
66
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js'
7+
import { SUPPORTED_TOOLCHAINS } from './toolchain.js'
78

89
import runServer from './mcp.js'
910
import { listAddOns } from './add-ons.js'
1011
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js'
1112

1213
import type { PackageManager } from './package-manager.js'
14+
import type { ToolChain } from './toolchain.js'
1315
import type { CliOptions, Framework } from './types.js'
1416

1517
export function cli() {
@@ -65,6 +67,20 @@ export function cli() {
6567
return value as PackageManager
6668
},
6769
)
70+
.option<ToolChain>(
71+
`--toolchain <${SUPPORTED_TOOLCHAINS.join('|')}>`,
72+
`Explicitly tell the CLI to use this toolchain`,
73+
(value) => {
74+
if (!SUPPORTED_TOOLCHAINS.includes(value as ToolChain)) {
75+
throw new InvalidArgumentError(
76+
`Invalid toolchain: ${value}. The following are allowed: ${SUPPORTED_TOOLCHAINS.join(
77+
', ',
78+
)}`,
79+
)
80+
}
81+
return value as ToolChain
82+
},
83+
)
6884
.option('--tailwind', 'add Tailwind CSS', false)
6985
.option<Array<string> | boolean>(
7086
'--add-ons [...add-ons]',

src/create-app.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,18 @@ function sortObject(obj: Record<string, string>): Record<string, string> {
3030
}
3131

3232
function createCopyFiles(targetDir: string) {
33-
return async function copyFiles(templateDir: string, files: Array<string>) {
33+
return async function copyFiles(
34+
templateDir: string,
35+
files: Array<string>,
36+
// optionally copy files from a folder to the root
37+
toRoot?: boolean,
38+
) {
3439
for (const file of files) {
35-
const targetFileName = file.replace('.tw', '')
40+
let targetFileName = file.replace('.tw', '')
41+
if (toRoot) {
42+
const fileNoPath = targetFileName.split('/').pop()
43+
targetFileName = fileNoPath ? `./${fileNoPath}` : targetFileName
44+
}
3645
await copyFile(
3746
resolve(templateDir, file),
3847
resolve(targetDir, targetFileName),
@@ -64,6 +73,7 @@ function createTemplateFile(
6473
projectName: projectName,
6574
typescript: options.typescript,
6675
tailwind: options.tailwind,
76+
toolchain: options.toolchain,
6777
js: options.typescript ? 'ts' : 'js',
6878
jsx: options.typescript ? 'tsx' : 'jsx',
6979
fileRouter: options.mode === FILE_ROUTER,
@@ -147,6 +157,22 @@ async function createPackageJSON(
147157
},
148158
}
149159
}
160+
if (options.toolchain === 'biome') {
161+
const biomePackageJSON = JSON.parse(
162+
await readFile(resolve(templateDir, 'package.biome.json'), 'utf8'),
163+
)
164+
packageJSON = {
165+
...packageJSON,
166+
scripts: {
167+
...packageJSON.scripts,
168+
...biomePackageJSON.scripts,
169+
},
170+
devDependencies: {
171+
...packageJSON.devDependencies,
172+
...biomePackageJSON.devDependencies,
173+
},
174+
}
175+
}
150176
if (options.mode === FILE_ROUTER) {
151177
const frPackageJSON = JSON.parse(
152178
await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
@@ -282,10 +308,20 @@ export async function createApp(
282308

283309
// Setup the .vscode directory
284310
await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
285-
await copyFile(
286-
resolve(templateDirBase, '_dot_vscode/settings.json'),
287-
resolve(targetDir, '.vscode/settings.json'),
288-
)
311+
switch (options.toolchain) {
312+
case 'biome':
313+
await copyFile(
314+
resolve(templateDirBase, '_dot_vscode/settings.biome.json'),
315+
resolve(targetDir, '.vscode/settings.json'),
316+
)
317+
break
318+
case 'none':
319+
default:
320+
await copyFile(
321+
resolve(templateDirBase, '_dot_vscode/settings.json'),
322+
resolve(targetDir, '.vscode/settings.json'),
323+
)
324+
}
289325

290326
// Fill the public directory
291327
await mkdir(resolve(targetDir, 'public'), { recursive: true })
@@ -321,6 +357,10 @@ export async function createApp(
321357

322358
copyFiles(templateDirBase, ['./src/logo.svg'])
323359

360+
if (options.toolchain === 'biome') {
361+
copyFiles(templateDirBase, ['./toolchain/biome.json'], true)
362+
}
363+
324364
// Setup the main, reportWebVitals and index.html files
325365
if (!isAddOnEnabled('start') && options.framework === 'react') {
326366
if (options.typescript) {
@@ -346,7 +386,7 @@ export async function createApp(
346386
)
347387
}
348388

349-
// Setup the package.json file, optionally with typescript and tailwind
389+
// Setup the package.json file, optionally with typescript, tailwind and biome
350390
await createPackageJSON(
351391
options.projectName,
352392
options,
@@ -567,6 +607,24 @@ export async function createApp(
567607
}
568608
}
569609

610+
if (options.toolchain === 'biome') {
611+
s?.start(`Applying toolchain ${options.toolchain}...`)
612+
switch (options.packageManager) {
613+
case 'pnpm':
614+
// pnpm automatically forwards extra arguments
615+
await execa(options.packageManager, ['run', 'check', '--fix'], {
616+
cwd: targetDir,
617+
})
618+
break
619+
default:
620+
await execa(options.packageManager, ['run', 'check', '--', '--fix'], {
621+
cwd: targetDir,
622+
})
623+
break
624+
}
625+
s?.stop(`Applied toolchain ${options.toolchain}...`)
626+
}
627+
570628
if (options.git) {
571629
s?.start(`Initializing git repository...`)
572630
await execa('git', ['init'], { cwd: targetDir })

src/mcp.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ const tanStackReactAddOns = [
5050
id: 'store',
5151
description: 'Enable the TanStack Store state management library',
5252
},
53+
{
54+
id: 'tanchat',
55+
description: 'Add an AI chatbot example to the application',
56+
},
5357
]
5458

5559
server.tool('listTanStackReactAddOns', {}, () => {
@@ -98,6 +102,7 @@ server.tool(
98102
typescript: true,
99103
tailwind: true,
100104
packageManager: 'pnpm',
105+
toolchain: 'none',
101106
mode: 'file-router',
102107
addOns: true,
103108
chosenAddOns,
@@ -142,6 +147,10 @@ const tanStackSolidAddOns = [
142147
id: 'tanstack-query',
143148
description: 'Enable TanStack Query for data fetching',
144149
},
150+
{
151+
id: 'tanchat',
152+
description: 'Add an AI chatbot example to the application',
153+
},
145154
]
146155

147156
server.tool('listTanStackSolidAddOns', {}, () => {
@@ -178,6 +187,7 @@ server.tool(
178187
typescript: true,
179188
tailwind: true,
180189
packageManager: 'pnpm',
190+
toolchain: 'none',
181191
mode: 'file-router',
182192
addOns: true,
183193
chosenAddOns,

src/options.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SUPPORTED_PACKAGE_MANAGERS,
1313
getPackageManager,
1414
} from './package-manager.js'
15+
import { DEFAULT_TOOLCHAIN, SUPPORTED_TOOLCHAINS } from './toolchain.js'
1516
import { CODE_ROUTER, DEFAULT_FRAMEWORK, FILE_ROUTER } from './constants.js'
1617
import { finalizeAddOns, getAllAddOns } from './add-ons.js'
1718
import type { AddOn, Variable } from './add-ons.js'
@@ -52,6 +53,7 @@ export async function normalizeOptions(
5253
typescript,
5354
tailwind,
5455
packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
56+
toolchain: cliOptions.toolchain || DEFAULT_TOOLCHAIN,
5557
mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
5658
git: !!cliOptions.git,
5759
addOns,
@@ -181,7 +183,7 @@ export async function promptForOptions(
181183
}
182184

183185
// Tailwind selection
184-
if (cliOptions.tailwind === undefined && options.framework === 'react') {
186+
if (!cliOptions.tailwind && options.framework === 'react') {
185187
const tailwind = await confirm({
186188
message: 'Would you like to use Tailwind CSS?',
187189
initialValue: true,
@@ -219,6 +221,25 @@ export async function promptForOptions(
219221
options.packageManager = cliOptions.packageManager
220222
}
221223

224+
// Toolchain selection
225+
if (cliOptions.toolchain === undefined) {
226+
const tc = await select({
227+
message: 'Select toolchain',
228+
options: SUPPORTED_TOOLCHAINS.map((tc) => ({
229+
value: tc,
230+
label: tc,
231+
})),
232+
initialValue: DEFAULT_TOOLCHAIN,
233+
})
234+
if (isCancel(tc)) {
235+
cancel('Operation cancelled.')
236+
process.exit(0)
237+
}
238+
options.toolchain = tc
239+
} else {
240+
options.toolchain = cliOptions.toolchain
241+
}
242+
222243
options.chosenAddOns = []
223244
if (Array.isArray(cliOptions.addOns)) {
224245
options.chosenAddOns = await finalizeAddOns(

src/toolchain.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const SUPPORTED_TOOLCHAINS = ['none', 'biome'] as const
2+
export type ToolChain = (typeof SUPPORTED_TOOLCHAINS)[number]
3+
export const DEFAULT_TOOLCHAIN: ToolChain = 'none'

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AddOn } from './add-ons.js'
22
import type { CODE_ROUTER, FILE_ROUTER } from './constants.js'
33
import type { PackageManager } from './package-manager.js'
4+
import type { ToolChain } from './toolchain.js'
45

56
export type Framework = 'solid' | 'react'
67

@@ -10,6 +11,7 @@ export interface Options {
1011
typescript: boolean
1112
tailwind: boolean
1213
packageManager: PackageManager
14+
toolchain: ToolChain
1315
mode: typeof CODE_ROUTER | typeof FILE_ROUTER
1416
addOns: boolean
1517
chosenAddOns: Array<AddOn>
@@ -22,6 +24,7 @@ export interface CliOptions {
2224
framework?: Framework
2325
tailwind?: boolean
2426
packageManager?: PackageManager
27+
toolchain?: ToolChain
2528
projectName?: string
2629
git?: boolean
2730
addOns?: Array<string> | boolean

templates/react/base/README.md.ejs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
3131
<% } else { %>
3232
This project uses CSS for styling.
3333
<% } %>
34+
<% if (toolchain && toolchain === 'biome') { %>
35+
## Linting & Formatting
36+
This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available:
37+
38+
```bash
39+
<%= packageManager %> run lint
40+
<%= packageManager %> run format
41+
<%= packageManager %> run check # runs both lint and format together
42+
```
43+
<% } %>
3444
3545
<% for(const addon of addOns.filter(addon => addon.readme)) { %>
3646
<%- addon.readme %>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
"[javascript]": {
12+
"editor.defaultFormatter": "biomejs.biome"
13+
},
14+
"[javascriptreact]": {
15+
"editor.defaultFormatter": "biomejs.biome"
16+
},
17+
"[typescript]": {
18+
"editor.defaultFormatter": "biomejs.biome"
19+
},
20+
"[typescriptreact]": {
21+
"editor.defaultFormatter": "biomejs.biome"
22+
},
23+
"[json]": {
24+
"editor.defaultFormatter": "biomejs.biome"
25+
},
26+
"[jsonc]": {
27+
"editor.defaultFormatter": "biomejs.biome"
28+
},
29+
"[html]": {
30+
"editor.defaultFormatter": "biomejs.biome"
31+
},
32+
"[css]": {
33+
"editor.defaultFormatter": "biomejs.biome"
34+
},
35+
"editor.codeActionsOnSave": {
36+
"source.organizeImports.biome": "explicit"
37+
}
38+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"scripts": {
3+
"format": "biome format",
4+
"lint": "biome lint",
5+
"check": "biome check"
6+
},
7+
"devDependencies": {
8+
"@biomejs/biome": "1.9.4"
9+
}
10+
}

0 commit comments

Comments
 (0)