@tanstack/cli 0.48.6 → 0.58.5

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.
@@ -1,7 +1,10 @@
1
1
  import { basename, resolve } from 'node:path'
2
2
  import { beforeEach, describe, expect, it } from 'vitest'
3
3
 
4
- import { normalizeOptions } from '../src/command-line.js'
4
+ import {
5
+ normalizeOptions,
6
+ validateLegacyCreateFlags,
7
+ } from '../src/command-line.js'
5
8
  import {
6
9
  sanitizePackageName,
7
10
  getCurrentDirectoryName,
@@ -67,46 +70,25 @@ describe('normalizeOptions', () => {
67
70
  expect(options?.targetDir).toBe(resolve(process.cwd()))
68
71
  })
69
72
 
70
- it('should return enable typescript based on the framework', async () => {
71
- const jsOptions = await normalizeOptions({
72
- projectName: 'test',
73
- template: 'javascript',
74
- })
75
- expect(jsOptions?.typescript).toBe(false)
76
- expect(jsOptions?.mode).toBe('code-router')
77
-
78
- const tsOptions = await normalizeOptions({
73
+ it('should always enable typescript (file-router/TanStack Start requires it)', async () => {
74
+ const options = await normalizeOptions({
79
75
  projectName: 'test',
80
- template: 'typescript',
81
76
  })
82
- expect(tsOptions?.typescript).toBe(true)
83
- expect(tsOptions?.mode).toBe('code-router')
77
+ expect(options?.typescript).toBe(true)
78
+ expect(options?.mode).toBe('file-router')
79
+ })
84
80
 
85
- const frOptions = await normalizeOptions({
81
+ it('tailwind is always enabled', async () => {
82
+ const options = await normalizeOptions({
86
83
  projectName: 'test',
87
- template: 'file-router',
88
84
  })
89
- expect(frOptions?.typescript).toBe(true)
90
- expect(frOptions?.mode).toBe('file-router')
91
- })
85
+ expect(options?.tailwind).toBe(true)
92
86
 
93
- it('should return enable tailwind if the framework is solid', async () => {
94
87
  const solidOptions = await normalizeOptions({
95
88
  projectName: 'test',
96
89
  framework: 'solid',
97
90
  })
98
91
  expect(solidOptions?.tailwind).toBe(true)
99
-
100
- const twOptions = await normalizeOptions({
101
- projectName: 'test',
102
- tailwind: true,
103
- })
104
- expect(twOptions?.tailwind).toBe(true)
105
-
106
- const noOptions = await normalizeOptions({
107
- projectName: 'test',
108
- })
109
- expect(noOptions?.tailwind).toBe(false)
110
92
  })
111
93
 
112
94
  it('should handle a starter url', async () => {
@@ -137,7 +119,6 @@ describe('normalizeOptions', () => {
137
119
  fetch.mockResponseOnce(
138
120
  JSON.stringify({
139
121
  id: 'https://github.com/cta-dev/cta-starter-solid',
140
- tailwind: true,
141
122
  typescript: false,
142
123
  framework: 'solid',
143
124
  mode: 'file-router',
@@ -195,7 +176,6 @@ describe('normalizeOptions', () => {
195
176
  projectName: 'test',
196
177
  framework: 'react-cra',
197
178
  },
198
- 'file-router',
199
179
  ['foo'],
200
180
  )
201
181
  expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
@@ -229,16 +209,12 @@ describe('normalizeOptions', () => {
229
209
  projectName: 'test',
230
210
  addOns: ['baz'],
231
211
  framework: 'react-cra',
232
- template: 'file-router',
233
212
  },
234
- 'file-router',
235
213
  ['foo'],
236
214
  )
237
215
  expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
238
216
  expect(options?.chosenAddOns.map((a) => a.id).includes('baz')).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)
217
+ expect(options?.tailwind).toBe(true)
242
218
  expect(options?.typescript).toBe(true)
243
219
  })
244
220
 
@@ -265,9 +241,7 @@ describe('normalizeOptions', () => {
265
241
  toolchain: 'biome',
266
242
  })
267
243
  expect(options?.chosenAddOns.map((a) => a.id).includes('biome')).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)
244
+ expect(options?.tailwind).toBe(true)
271
245
  expect(options?.typescript).toBe(true)
272
246
  })
273
247
 
@@ -302,3 +276,33 @@ describe('normalizeOptions', () => {
302
276
  expect(options?.chosenAddOns.map((a) => a.id).includes('baz')).toBe(true)
303
277
  })
304
278
  })
279
+
280
+ describe('validateLegacyCreateFlags', () => {
281
+ it('returns no warnings or errors without legacy flags', () => {
282
+ const result = validateLegacyCreateFlags({})
283
+ expect(result.warnings).toEqual([])
284
+ expect(result.error).toBeUndefined()
285
+ })
286
+
287
+ it('warns when --router-only is used', () => {
288
+ const result = validateLegacyCreateFlags({ routerOnly: true })
289
+ expect(result.error).toBeUndefined()
290
+ expect(result.warnings[0]).toContain('--router-only')
291
+ })
292
+
293
+ it('errors for JavaScript templates', () => {
294
+ const result = validateLegacyCreateFlags({ template: 'javascript' })
295
+ expect(result.error).toContain('JavaScript/JSX templates are not supported')
296
+ })
297
+
298
+ it('errors for unknown template values', () => {
299
+ const result = validateLegacyCreateFlags({ template: 'foo' })
300
+ expect(result.error).toContain('Invalid --template value')
301
+ })
302
+
303
+ it('warns for supported deprecated template values', () => {
304
+ const result = validateLegacyCreateFlags({ template: 'tsx' })
305
+ expect(result.error).toBeUndefined()
306
+ expect(result.warnings[0]).toContain('--template')
307
+ })
308
+ })
@@ -23,28 +23,23 @@ beforeEach(() => {
23
23
  {
24
24
  id: 'react-query',
25
25
  type: 'add-on',
26
- modes: ['file-router', 'code-router'],
26
+ modes: ['file-router'],
27
27
  },
28
28
  {
29
29
  id: 'tanstack-chat',
30
30
  type: 'add-on',
31
- modes: ['file-router', 'code-router'],
31
+ modes: ['file-router'],
32
32
  },
33
33
  {
34
34
  id: 'biome',
35
35
  type: 'toolchain',
36
- modes: ['file-router', 'code-router'],
36
+ modes: ['file-router'],
37
37
  },
38
38
  ],
39
39
  supportedModes: {
40
- 'code-router': {
41
- displayName: 'Code Router',
42
- description: 'TanStack Router using code to define the routes',
43
- forceTypescript: false,
44
- },
45
40
  'file-router': {
46
41
  displayName: 'File Router',
47
- description: 'TanStack Router using files to define the routes',
42
+ description: 'TanStack Start with file-based routing',
48
43
  forceTypescript: true,
49
44
  },
50
45
  },
@@ -67,11 +62,6 @@ const baseCliOptions: CliOptions = {
67
62
 
68
63
  function setBasicSpies() {
69
64
  vi.spyOn(prompts, 'getProjectName').mockImplementation(async () => 'hello')
70
- vi.spyOn(prompts, 'selectRouterType').mockImplementation(
71
- async () => 'file-router',
72
- )
73
- vi.spyOn(prompts, 'selectTypescript').mockImplementation(async () => true)
74
- vi.spyOn(prompts, 'selectTailwind').mockImplementation(async () => true)
75
65
  vi.spyOn(prompts, 'selectPackageManager').mockImplementation(
76
66
  async () => 'npm',
77
67
  )
@@ -101,95 +91,22 @@ describe('promptForCreateOptions', () => {
101
91
  expect(options?.projectName).toBe('override')
102
92
  })
103
93
 
104
- //// Mode (router type)
94
+ //// Mode is always file-router (TanStack Start)
105
95
 
106
- it('forceMode should override template', async () => {
96
+ it('mode should always be file-router', async () => {
107
97
  setBasicSpies()
108
98
 
109
- const options = await promptForCreateOptions(
110
- { ...baseCliOptions, template: 'javascript' },
111
- { forcedMode: 'file-router' },
112
- )
99
+ const options = await promptForCreateOptions(baseCliOptions, {})
113
100
 
114
101
  expect(options?.mode).toBe('file-router')
115
102
  expect(options?.typescript).toBe(true)
116
103
  })
117
104
 
118
- it('takes template from cli options - code-router', async () => {
119
- setBasicSpies()
120
-
121
- vi.spyOn(prompts, 'selectRouterType').mockImplementation(
122
- async () => 'code-router',
123
- )
124
-
125
- const options = await promptForCreateOptions(
126
- { ...baseCliOptions, template: 'javascript' },
127
- {},
128
- )
129
-
130
- expect(options?.mode).toBe('code-router')
131
- })
132
-
133
- it('takes template from cli options - file-router', async () => {
134
- setBasicSpies()
135
-
136
- vi.spyOn(prompts, 'selectRouterType').mockImplementation(
137
- async () => 'code-router',
138
- )
139
-
140
- const options = await promptForCreateOptions(
141
- { ...baseCliOptions, template: 'file-router' },
142
- {},
143
- )
144
-
145
- expect(options?.mode).toBe('file-router')
146
- })
147
-
148
- it('prompt for router type when unspecified', async () => {
149
- setBasicSpies()
150
-
151
- vi.spyOn(prompts, 'selectRouterType').mockImplementation(
152
- async () => 'code-router',
153
- )
154
-
155
- const options = await promptForCreateOptions(
156
- { ...baseCliOptions, tailwind: false, framework: undefined },
157
- {},
158
- )
159
-
160
- expect(options?.mode).toBe('code-router')
161
- })
162
-
163
- //// Tailwind
164
-
165
- it('prompt for tailwind when unspecified in react-cra', async () => {
166
- setBasicSpies()
167
- vi.spyOn(prompts, 'selectTailwind').mockImplementation(async () => false)
168
- const options = await promptForCreateOptions(
169
- { ...baseCliOptions, tailwind: undefined },
170
- {},
171
- )
172
-
173
- expect(options?.tailwind).toBe(false)
174
- })
175
-
176
- it('prompt for tailwind when unspecified in react-cra - true', async () => {
177
- setBasicSpies()
178
- vi.spyOn(prompts, 'selectTailwind').mockImplementation(async () => true)
179
- const options = await promptForCreateOptions(
180
- { ...baseCliOptions, tailwind: undefined },
181
- {},
182
- )
183
-
184
- expect(options?.tailwind).toBe(true)
185
- })
105
+ //// Tailwind is always enabled
186
106
 
187
- it('set tailwind when solid', async () => {
107
+ it('tailwind is always enabled', async () => {
188
108
  setBasicSpies()
189
- const options = await promptForCreateOptions(
190
- { ...baseCliOptions, tailwind: undefined, framework: 'solid' },
191
- {},
192
- )
109
+ const options = await promptForCreateOptions(baseCliOptions, {})
193
110
 
194
111
  expect(options?.tailwind).toBe(true)
195
112
  })
@@ -207,7 +124,7 @@ describe('promptForCreateOptions', () => {
207
124
  expect(options?.packageManager).toBe('bun')
208
125
  })
209
126
 
210
- it('uses the package manager from the cli options', async () => {
127
+ it('detects package manager from environment', async () => {
211
128
  setBasicSpies()
212
129
 
213
130
  process.env.npm_config_userconfig = 'blarg'
@@ -257,7 +174,6 @@ describe('promptForCreateOptions', () => {
257
174
  expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual([
258
175
  'react-query',
259
176
  ])
260
- // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
261
177
  expect(options?.tailwind).toBe(true)
262
178
  expect(options?.typescript).toBe(true)
263
179
  })
@@ -274,9 +190,6 @@ describe('promptForCreateOptions', () => {
274
190
  'biome',
275
191
  'react-query',
276
192
  ])
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
280
193
  expect(options?.tailwind).toBe(true)
281
194
  expect(options?.typescript).toBe(true)
282
195
  })
@@ -297,7 +210,6 @@ describe('promptForCreateOptions', () => {
297
210
  'biome',
298
211
  'react-query',
299
212
  ])
300
- // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
301
213
  expect(options?.tailwind).toBe(true)
302
214
  expect(options?.typescript).toBe(true)
303
215
  })
@@ -7,8 +7,6 @@ import {
7
7
  selectAddOns,
8
8
  selectGit,
9
9
  selectPackageManager,
10
- selectRouterType,
11
- selectTailwind,
12
10
  selectToolchain,
13
11
  } from '../src/ui-prompts'
14
12
 
@@ -37,42 +35,6 @@ describe('getProjectName', () => {
37
35
  })
38
36
  })
39
37
 
40
- describe('selectRouterType', () => {
41
- it('should select the file router', async () => {
42
- vi.spyOn(clack, 'select').mockImplementation(async () => 'file-router')
43
- vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
44
-
45
- const routerType = await selectRouterType()
46
- expect(routerType).toBe('file-router')
47
- })
48
-
49
- it('should exit on cancel', async () => {
50
- vi.spyOn(clack, 'select').mockImplementation(async () => 'Cancelled')
51
- vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
52
-
53
- await expect(() => selectRouterType()).rejects.toThrowError(/exit/)
54
- })
55
- })
56
-
57
- describe('selectTailwind', () => {
58
- it('should select tailwind', async () => {
59
- vi.spyOn(clack, 'confirm').mockImplementation(async () => true)
60
- vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
61
-
62
- const tailwind = await selectTailwind()
63
- expect(tailwind).toBe(true)
64
- })
65
-
66
- it('should exit on cancel', async () => {
67
- vi.spyOn(clack, 'confirm').mockImplementation(async () =>
68
- Symbol.for('cancel'),
69
- )
70
- vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
71
-
72
- await expect(selectTailwind()).rejects.toThrowError(/exit/)
73
- })
74
- })
75
-
76
38
  describe('selectPackageManager', () => {
77
39
  it('should select pnpm', async () => {
78
40
  vi.spyOn(clack, 'select').mockImplementation(async () => 'pnpm')