@tanstack/cta-cli 0.46.2 → 0.48.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/src/options.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { intro } from '@clack/prompts'
1
+ import fs from 'node:fs'
2
+ import { cancel, confirm, intro, isCancel } from '@clack/prompts'
2
3
 
3
4
  import {
4
5
  finalizeAddOns,
@@ -12,8 +13,8 @@ import {
12
13
  getProjectName,
13
14
  promptForAddOnOptions,
14
15
  selectAddOns,
15
- selectGit,
16
16
  selectDeployment,
17
+ selectGit,
17
18
  selectPackageManager,
18
19
  selectRouterType,
19
20
  selectTailwind,
@@ -46,6 +47,7 @@ export async function promptForCreateOptions(
46
47
 
47
48
  options.framework = getFrameworkById(cliOptions.framework || 'react-cra')!
48
49
 
50
+ // Validate project name
49
51
  if (cliOptions.projectName) {
50
52
  // Handle "." as project name - use sanitized current directory name
51
53
  if (cliOptions.projectName === '.') {
@@ -62,6 +64,23 @@ export async function promptForCreateOptions(
62
64
  options.projectName = await getProjectName()
63
65
  }
64
66
 
67
+ // Check if target directory is empty
68
+ if (
69
+ !cliOptions.force &&
70
+ fs.existsSync(options.projectName) &&
71
+ fs.readdirSync(options.projectName).length > 0
72
+ ) {
73
+ const shouldContinue = await confirm({
74
+ message: `Target directory ${options.projectName} is not empty. Do you want to continue?`,
75
+ initialValue: true,
76
+ })
77
+
78
+ if (isCancel(shouldContinue) || !shouldContinue) {
79
+ cancel('Operation cancelled.')
80
+ process.exit(0)
81
+ }
82
+ }
83
+
65
84
  // Router type selection
66
85
  if (forcedMode) {
67
86
  options.mode = forcedMode
@@ -86,13 +105,6 @@ export async function promptForCreateOptions(
86
105
  options.typescript = await selectTypescript()
87
106
  }
88
107
 
89
- // Tailwind selection
90
- if (!cliOptions.tailwind && options.framework.id === 'react-cra') {
91
- options.tailwind = await selectTailwind()
92
- } else {
93
- options.tailwind = true
94
- }
95
-
96
108
  // Package manager selection
97
109
  if (cliOptions.packageManager) {
98
110
  options.packageManager = cliOptions.packageManager
@@ -159,10 +171,29 @@ export async function promptForCreateOptions(
159
171
  )
160
172
 
161
173
  if (options.chosenAddOns.length) {
162
- options.tailwind = true
163
174
  options.typescript = true
164
175
  }
165
176
 
177
+ // Tailwind selection
178
+ // Only treat add-ons as requiring tailwind if they explicitly have "tailwind": true
179
+ const addOnsRequireTailwind = options.chosenAddOns.some(
180
+ (addOn) => addOn.tailwind === true,
181
+ )
182
+
183
+ if (addOnsRequireTailwind) {
184
+ // If any add-on explicitly requires tailwind, enable it automatically
185
+ options.tailwind = true
186
+ } else if (cliOptions.tailwind !== undefined) {
187
+ // User explicitly provided a CLI flag, respect it
188
+ options.tailwind = !!cliOptions.tailwind
189
+ } else if (options.framework.id === 'react-cra') {
190
+ // Only show prompt for react-cra when no CLI flag and no add-ons require it
191
+ options.tailwind = await selectTailwind()
192
+ } else {
193
+ // For other frameworks (like solid), default to true
194
+ options.tailwind = true
195
+ }
196
+
166
197
  // Prompt for add-on options in interactive mode
167
198
  if (Array.isArray(cliOptions.addOns)) {
168
199
  // Non-interactive mode: use defaults
@@ -177,7 +208,11 @@ export async function promptForCreateOptions(
177
208
  // Merge user options with defaults
178
209
  options.addOnOptions = { ...defaultOptions, ...userOptions }
179
210
  }
211
+
180
212
  options.git = cliOptions.git || (await selectGit())
213
+ if (cliOptions.install === false) {
214
+ options.install = false
215
+ }
181
216
 
182
217
  return options
183
218
  }
package/src/types.ts CHANGED
@@ -7,7 +7,7 @@ export interface CliOptions {
7
7
  framework?: string
8
8
  tailwind?: boolean
9
9
  packageManager?: PackageManager
10
- toolchain?: string
10
+ toolchain?: string | false
11
11
  deployment?: string
12
12
  projectName?: string
13
13
  git?: boolean
@@ -20,5 +20,8 @@ export interface CliOptions {
20
20
  targetDir?: string
21
21
  interactive?: boolean
22
22
  ui?: boolean
23
+ devWatch?: string
24
+ install?: boolean
23
25
  addOnConfig?: string
26
+ force?: boolean
24
27
  }
package/src/ui-prompts.ts CHANGED
@@ -18,7 +18,6 @@ import { validateProjectName } from './utils.js'
18
18
  import type { AddOn, PackageManager } from '@tanstack/cta-engine'
19
19
 
20
20
  import type { Framework } from '@tanstack/cta-engine/dist/types/types.js'
21
- import { InitialData } from '../../cta-ui/src/types'
22
21
 
23
22
  export async function getProjectName(): Promise<string> {
24
23
  const value = await text({
@@ -197,8 +196,12 @@ export async function selectGit(): Promise<boolean> {
197
196
 
198
197
  export async function selectToolchain(
199
198
  framework: Framework,
200
- toolchain?: string,
199
+ toolchain?: string | false,
201
200
  ): Promise<string | undefined> {
201
+ if (toolchain === false) {
202
+ return undefined
203
+ }
204
+
202
205
  const toolchains = new Set<AddOn>()
203
206
  for (const addOn of framework.getAddOns()) {
204
207
  if (addOn.type === 'toolchain') {
@@ -236,7 +236,9 @@ describe('normalizeOptions', () => {
236
236
  )
237
237
  expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
238
238
  expect(options?.chosenAddOns.map((a) => a.id).includes('baz')).toBe(true)
239
- expect(options?.tailwind).toBe(true)
239
+ // Tailwind is not automatically set to true unless an add-on explicitly requires it
240
+ // Since mock add-ons don't have tailwind: true, tailwind should be false
241
+ expect(options?.tailwind).toBe(false)
240
242
  expect(options?.typescript).toBe(true)
241
243
  })
242
244
 
@@ -263,7 +265,9 @@ describe('normalizeOptions', () => {
263
265
  toolchain: 'biome',
264
266
  })
265
267
  expect(options?.chosenAddOns.map((a) => a.id).includes('biome')).toBe(true)
266
- expect(options?.tailwind).toBe(true)
268
+ // Tailwind is not automatically set to true unless an add-on explicitly requires it
269
+ // Since mock add-ons don't have tailwind: true, tailwind should be false
270
+ expect(options?.tailwind).toBe(false)
267
271
  expect(options?.typescript).toBe(true)
268
272
  })
269
273
 
@@ -257,6 +257,7 @@ describe('promptForCreateOptions', () => {
257
257
  expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual([
258
258
  'react-query',
259
259
  ])
260
+ // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
260
261
  expect(options?.tailwind).toBe(true)
261
262
  expect(options?.typescript).toBe(true)
262
263
  })
@@ -273,6 +274,9 @@ describe('promptForCreateOptions', () => {
273
274
  'biome',
274
275
  'react-query',
275
276
  ])
277
+ // In non-interactive mode with add-ons, tailwind defaults to false unless explicitly required
278
+ // But since we're in interactive mode (addOns is an array but we still prompt), tailwind is prompted
279
+ // The mock returns true, so tailwind should be true
276
280
  expect(options?.tailwind).toBe(true)
277
281
  expect(options?.typescript).toBe(true)
278
282
  })
@@ -293,6 +297,7 @@ describe('promptForCreateOptions', () => {
293
297
  'biome',
294
298
  'react-query',
295
299
  ])
300
+ // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
296
301
  expect(options?.tailwind).toBe(true)
297
302
  expect(options?.typescript).toBe(true)
298
303
  })