@tanstack/cli 0.60.1 → 0.62.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/dist/cli.js +266 -11
- package/dist/command-line.js +103 -8
- package/dist/discovery.js +144 -0
- package/dist/options.js +35 -2
- package/dist/types/command-line.d.ts +7 -0
- package/dist/types/{mcp/types.d.ts → discovery.d.ts} +23 -75
- package/dist/types/types.d.ts +1 -2
- package/dist/types/ui-prompts.d.ts +5 -0
- package/dist/ui-prompts.js +26 -0
- package/package.json +6 -5
- package/skills/CHANGELOG.md +18 -0
- package/skills/add-addons-existing-app/SKILL.md +113 -0
- package/skills/choose-ecosystem-integrations/SKILL.md +140 -0
- package/skills/choose-ecosystem-integrations/references/authentication-providers.md +19 -0
- package/skills/choose-ecosystem-integrations/references/data-layer-providers.md +20 -0
- package/skills/choose-ecosystem-integrations/references/deployment-targets.md +19 -0
- package/skills/create-app-scaffold/SKILL.md +132 -0
- package/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md +34 -0
- package/skills/create-app-scaffold/references/deployment-providers.md +19 -0
- package/skills/create-app-scaffold/references/framework-adapters.md +17 -0
- package/skills/create-app-scaffold/references/toolchains.md +17 -0
- package/skills/maintain-custom-addons-dev-watch/SKILL.md +118 -0
- package/skills/query-docs-library-metadata/SKILL.md +85 -0
- package/skills/query-docs-library-metadata/references/discovery-command-output-schemas.md +70 -0
- package/CHANGELOG.md +0 -787
- package/dist/mcp/api.js +0 -31
- package/dist/mcp/tools.js +0 -250
- package/dist/mcp/types.js +0 -37
- package/dist/mcp.js +0 -181
- package/dist/types/mcp/api.d.ts +0 -4
- package/dist/types/mcp/tools.d.ts +0 -2
- package/dist/types/mcp.d.ts +0 -5
- package/playwright-report/index.html +0 -85
- package/playwright.config.ts +0 -21
- package/src/bin.ts +0 -15
- package/src/cli.ts +0 -767
- package/src/command-line.ts +0 -473
- package/src/dev-watch.ts +0 -564
- package/src/file-syncer.ts +0 -263
- package/src/index.ts +0 -21
- package/src/mcp/api.ts +0 -42
- package/src/mcp/tools.ts +0 -323
- package/src/mcp/types.ts +0 -46
- package/src/mcp.ts +0 -263
- package/src/options.ts +0 -234
- package/src/types.ts +0 -28
- package/src/ui-environment.ts +0 -74
- package/src/ui-prompts.ts +0 -355
- package/src/utils.ts +0 -30
- package/test-results/.last-run.json +0 -4
- package/tests/command-line.test.ts +0 -622
- package/tests/index.test.ts +0 -9
- package/tests/mcp.test.ts +0 -225
- package/tests/options.test.ts +0 -216
- package/tests/setupVitest.ts +0 -6
- package/tests/ui-environment.test.ts +0 -97
- package/tests/ui-prompts.test.ts +0 -205
- package/tests-e2e/addons-smoke.spec.ts +0 -31
- package/tests-e2e/create-smoke.spec.ts +0 -39
- package/tests-e2e/helpers.ts +0 -526
- package/tests-e2e/matrix-opportunistic.spec.ts +0 -142
- package/tests-e2e/router-only-smoke.spec.ts +0 -68
- package/tests-e2e/solid-smoke.spec.ts +0 -25
- package/tests-e2e/templates-smoke.spec.ts +0 -52
- package/tsconfig.json +0 -17
- package/vitest.config.js +0 -8
|
@@ -1,622 +0,0 @@
|
|
|
1
|
-
import { basename, resolve } from 'node:path'
|
|
2
|
-
import { beforeEach, describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
normalizeOptions,
|
|
6
|
-
validateLegacyCreateFlags,
|
|
7
|
-
} from '../src/command-line.js'
|
|
8
|
-
import {
|
|
9
|
-
sanitizePackageName,
|
|
10
|
-
getCurrentDirectoryName,
|
|
11
|
-
} from '../src/utils.js'
|
|
12
|
-
import {
|
|
13
|
-
__testRegisterFramework,
|
|
14
|
-
__testClearFrameworks,
|
|
15
|
-
} from '@tanstack/create'
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
__testClearFrameworks()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('sanitizePackageName', () => {
|
|
22
|
-
it('should convert to lowercase', () => {
|
|
23
|
-
expect(sanitizePackageName('MyProject')).toBe('myproject')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('should replace spaces with hyphens', () => {
|
|
27
|
-
expect(sanitizePackageName('my project')).toBe('my-project')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should replace underscores with hyphens', () => {
|
|
31
|
-
expect(sanitizePackageName('my_project')).toBe('my-project')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should remove invalid characters', () => {
|
|
35
|
-
expect(sanitizePackageName('my@project!')).toBe('myproject')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('should ensure it starts with a letter', () => {
|
|
39
|
-
expect(sanitizePackageName('123project')).toBe('project')
|
|
40
|
-
expect(sanitizePackageName('_myproject')).toBe('myproject')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should collapse multiple hyphens', () => {
|
|
44
|
-
expect(sanitizePackageName('my--project')).toBe('my-project')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should remove trailing hyphen', () => {
|
|
48
|
-
expect(sanitizePackageName('myproject-')).toBe('myproject')
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe('getCurrentDirectoryName', () => {
|
|
53
|
-
it('should return the basename of the current working directory', () => {
|
|
54
|
-
expect(getCurrentDirectoryName()).toBe(basename(process.cwd()))
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('normalizeOptions', () => {
|
|
59
|
-
it('should return undefined if project name is not provided', async () => {
|
|
60
|
-
const options = await normalizeOptions({})
|
|
61
|
-
expect(options).toBeUndefined()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should handle "." as project name by using sanitized current directory name', async () => {
|
|
65
|
-
const options = await normalizeOptions({
|
|
66
|
-
projectName: '.',
|
|
67
|
-
})
|
|
68
|
-
const expectedName = sanitizePackageName(getCurrentDirectoryName())
|
|
69
|
-
expect(options?.projectName).toBe(expectedName)
|
|
70
|
-
expect(options?.targetDir).toBe(resolve(process.cwd()))
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('should always enable typescript (file-router/TanStack Start requires it)', async () => {
|
|
74
|
-
const options = await normalizeOptions({
|
|
75
|
-
projectName: 'test',
|
|
76
|
-
})
|
|
77
|
-
expect(options?.typescript).toBe(true)
|
|
78
|
-
expect(options?.mode).toBe('file-router')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('tailwind is always enabled', async () => {
|
|
82
|
-
const options = await normalizeOptions({
|
|
83
|
-
projectName: 'test',
|
|
84
|
-
})
|
|
85
|
-
expect(options?.tailwind).toBe(true)
|
|
86
|
-
|
|
87
|
-
const solidOptions = await normalizeOptions({
|
|
88
|
-
projectName: 'test',
|
|
89
|
-
framework: 'solid',
|
|
90
|
-
})
|
|
91
|
-
expect(solidOptions?.tailwind).toBe(true)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('defaults git initialization to enabled', async () => {
|
|
95
|
-
const options = await normalizeOptions({
|
|
96
|
-
projectName: 'test',
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
expect(options?.git).toBe(true)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('respects explicit --no-git option', async () => {
|
|
103
|
-
const options = await normalizeOptions({
|
|
104
|
-
projectName: 'test',
|
|
105
|
-
git: false,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
expect(options?.git).toBe(false)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('should handle a starter url', async () => {
|
|
112
|
-
__testRegisterFramework({
|
|
113
|
-
id: 'solid',
|
|
114
|
-
name: 'Solid',
|
|
115
|
-
getAddOns: () => [
|
|
116
|
-
{
|
|
117
|
-
id: 'nitro',
|
|
118
|
-
name: 'nitro',
|
|
119
|
-
modes: ['file-router'],
|
|
120
|
-
default: true,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
supportedModes: {
|
|
124
|
-
'code-router': {
|
|
125
|
-
displayName: 'Code Router',
|
|
126
|
-
description: 'TanStack Router using code to define the routes',
|
|
127
|
-
forceTypescript: false,
|
|
128
|
-
},
|
|
129
|
-
'file-router': {
|
|
130
|
-
displayName: 'File Router',
|
|
131
|
-
description: 'TanStack Router using files to define the routes',
|
|
132
|
-
forceTypescript: true,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
})
|
|
136
|
-
fetch.mockResponseOnce(
|
|
137
|
-
JSON.stringify({
|
|
138
|
-
id: 'https://github.com/cta-dev/cta-starter-solid',
|
|
139
|
-
typescript: false,
|
|
140
|
-
framework: 'solid',
|
|
141
|
-
mode: 'file-router',
|
|
142
|
-
type: 'starter',
|
|
143
|
-
description: 'A starter for Solid',
|
|
144
|
-
name: 'My Solid Starter',
|
|
145
|
-
dependsOn: [],
|
|
146
|
-
files: {},
|
|
147
|
-
deletedFiles: [],
|
|
148
|
-
}),
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
const options = await normalizeOptions({
|
|
152
|
-
projectName: 'test',
|
|
153
|
-
starter: 'https://github.com/cta-dev/cta-starter-solid',
|
|
154
|
-
deployment: 'nitro',
|
|
155
|
-
})
|
|
156
|
-
expect(options?.mode).toBe('file-router')
|
|
157
|
-
expect(options?.tailwind).toBe(true)
|
|
158
|
-
expect(options?.typescript).toBe(true)
|
|
159
|
-
expect(options?.framework?.id).toBe('solid')
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should resolve built-in starter id from registry', async () => {
|
|
163
|
-
__testRegisterFramework({
|
|
164
|
-
id: 'react',
|
|
165
|
-
name: 'React',
|
|
166
|
-
getAddOns: () => [],
|
|
167
|
-
supportedModes: {
|
|
168
|
-
'file-router': {
|
|
169
|
-
displayName: 'File Router',
|
|
170
|
-
description: 'TanStack Router using files to define the routes',
|
|
171
|
-
forceTypescript: true,
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
const originalRegistry = process.env.CTA_REGISTRY
|
|
177
|
-
process.env.CTA_REGISTRY = 'https://registry.example/registry.json'
|
|
178
|
-
|
|
179
|
-
fetch
|
|
180
|
-
.mockResponseOnce(
|
|
181
|
-
JSON.stringify({
|
|
182
|
-
starters: [
|
|
183
|
-
{
|
|
184
|
-
name: 'Ecommerce',
|
|
185
|
-
description: 'Ecommerce base',
|
|
186
|
-
url: './ecommerce/template.json',
|
|
187
|
-
mode: 'file-router',
|
|
188
|
-
framework: 'react',
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
}),
|
|
192
|
-
)
|
|
193
|
-
.mockResponseOnce(
|
|
194
|
-
JSON.stringify({
|
|
195
|
-
id: 'ecommerce',
|
|
196
|
-
typescript: true,
|
|
197
|
-
framework: 'react',
|
|
198
|
-
mode: 'file-router',
|
|
199
|
-
type: 'starter',
|
|
200
|
-
description: 'Ecommerce base',
|
|
201
|
-
name: 'Ecommerce',
|
|
202
|
-
dependsOn: [],
|
|
203
|
-
files: {},
|
|
204
|
-
deletedFiles: [],
|
|
205
|
-
}),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const options = await normalizeOptions({
|
|
210
|
-
projectName: 'test',
|
|
211
|
-
starter: 'ecommerce',
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
expect(options?.framework?.id).toBe('react')
|
|
215
|
-
expect(options?.starter?.id).toBe(
|
|
216
|
-
'https://registry.example/ecommerce/template.json',
|
|
217
|
-
)
|
|
218
|
-
} finally {
|
|
219
|
-
process.env.CTA_REGISTRY = originalRegistry
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should map --template-id to starter resolution', async () => {
|
|
224
|
-
__testRegisterFramework({
|
|
225
|
-
id: 'react',
|
|
226
|
-
name: 'React',
|
|
227
|
-
getAddOns: () => [],
|
|
228
|
-
supportedModes: {
|
|
229
|
-
'file-router': {
|
|
230
|
-
displayName: 'File Router',
|
|
231
|
-
description: 'TanStack Router using files to define the routes',
|
|
232
|
-
forceTypescript: true,
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
const originalRegistry = process.env.CTA_REGISTRY
|
|
238
|
-
process.env.CTA_REGISTRY = 'https://registry.example/registry.json'
|
|
239
|
-
|
|
240
|
-
fetch
|
|
241
|
-
.mockResponseOnce(
|
|
242
|
-
JSON.stringify({
|
|
243
|
-
templates: [
|
|
244
|
-
{
|
|
245
|
-
name: 'Resume',
|
|
246
|
-
description: 'Resume template',
|
|
247
|
-
url: './resume/template.json',
|
|
248
|
-
mode: 'file-router',
|
|
249
|
-
framework: 'react',
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
}),
|
|
253
|
-
)
|
|
254
|
-
.mockResponseOnce(
|
|
255
|
-
JSON.stringify({
|
|
256
|
-
id: 'resume',
|
|
257
|
-
typescript: true,
|
|
258
|
-
framework: 'react',
|
|
259
|
-
mode: 'file-router',
|
|
260
|
-
type: 'starter',
|
|
261
|
-
description: 'Resume template',
|
|
262
|
-
name: 'Resume',
|
|
263
|
-
dependsOn: [],
|
|
264
|
-
files: {},
|
|
265
|
-
deletedFiles: [],
|
|
266
|
-
}),
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const options = await normalizeOptions({
|
|
271
|
-
projectName: 'test',
|
|
272
|
-
templateId: 'resume',
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
expect(options?.starter?.id).toBe(
|
|
276
|
-
'https://registry.example/resume/template.json',
|
|
277
|
-
)
|
|
278
|
-
} finally {
|
|
279
|
-
process.env.CTA_REGISTRY = originalRegistry
|
|
280
|
-
}
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('should resolve --template as a template id from registry', async () => {
|
|
284
|
-
__testRegisterFramework({
|
|
285
|
-
id: 'react',
|
|
286
|
-
name: 'React',
|
|
287
|
-
getAddOns: () => [],
|
|
288
|
-
supportedModes: {
|
|
289
|
-
'file-router': {
|
|
290
|
-
displayName: 'File Router',
|
|
291
|
-
description: 'TanStack Router using files to define the routes',
|
|
292
|
-
forceTypescript: true,
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
const originalRegistry = process.env.CTA_REGISTRY
|
|
298
|
-
process.env.CTA_REGISTRY = 'https://registry.example/registry.json'
|
|
299
|
-
|
|
300
|
-
fetch
|
|
301
|
-
.mockResponseOnce(
|
|
302
|
-
JSON.stringify({
|
|
303
|
-
templates: [
|
|
304
|
-
{
|
|
305
|
-
name: 'Ecommerce',
|
|
306
|
-
description: 'Ecommerce template',
|
|
307
|
-
url: './ecommerce/template.json',
|
|
308
|
-
mode: 'file-router',
|
|
309
|
-
framework: 'react',
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
}),
|
|
313
|
-
)
|
|
314
|
-
.mockResponseOnce(
|
|
315
|
-
JSON.stringify({
|
|
316
|
-
id: 'ecommerce',
|
|
317
|
-
typescript: true,
|
|
318
|
-
framework: 'react',
|
|
319
|
-
mode: 'file-router',
|
|
320
|
-
type: 'starter',
|
|
321
|
-
description: 'Ecommerce template',
|
|
322
|
-
name: 'Ecommerce',
|
|
323
|
-
dependsOn: [],
|
|
324
|
-
files: {},
|
|
325
|
-
deletedFiles: [],
|
|
326
|
-
}),
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
const options = await normalizeOptions({
|
|
331
|
-
projectName: 'test',
|
|
332
|
-
template: 'ecommerce',
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
expect(options?.starter?.id).toBe(
|
|
336
|
-
'https://registry.example/ecommerce/template.json',
|
|
337
|
-
)
|
|
338
|
-
} finally {
|
|
339
|
-
process.env.CTA_REGISTRY = originalRegistry
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
it('should default to react if no framework is provided', async () => {
|
|
344
|
-
__testRegisterFramework({
|
|
345
|
-
id: 'react',
|
|
346
|
-
name: 'react',
|
|
347
|
-
})
|
|
348
|
-
const options = await normalizeOptions({
|
|
349
|
-
projectName: 'test',
|
|
350
|
-
})
|
|
351
|
-
expect(options?.framework?.id).toBe('react')
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
it('should handle forced addons', async () => {
|
|
355
|
-
__testRegisterFramework({
|
|
356
|
-
id: 'react',
|
|
357
|
-
name: 'react',
|
|
358
|
-
getAddOns: () => [
|
|
359
|
-
{
|
|
360
|
-
id: 'foo',
|
|
361
|
-
name: 'foobar',
|
|
362
|
-
modes: ['file-router'],
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
id: 'nitro',
|
|
366
|
-
name: 'nitro',
|
|
367
|
-
modes: ['file-router'],
|
|
368
|
-
default: true,
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
})
|
|
372
|
-
const options = await normalizeOptions(
|
|
373
|
-
{
|
|
374
|
-
projectName: 'test',
|
|
375
|
-
framework: 'react',
|
|
376
|
-
},
|
|
377
|
-
['foo'],
|
|
378
|
-
)
|
|
379
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
it('should handle additional addons from the CLI', async () => {
|
|
383
|
-
__testRegisterFramework({
|
|
384
|
-
id: 'react',
|
|
385
|
-
name: 'react',
|
|
386
|
-
getAddOns: () => [
|
|
387
|
-
{
|
|
388
|
-
id: 'foo',
|
|
389
|
-
name: 'foobar',
|
|
390
|
-
modes: ['file-router'],
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
id: 'baz',
|
|
394
|
-
name: 'baz',
|
|
395
|
-
modes: ['file-router'],
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
id: 'nitro',
|
|
399
|
-
name: 'nitro',
|
|
400
|
-
modes: ['file-router'],
|
|
401
|
-
default: true,
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
})
|
|
405
|
-
const options = await normalizeOptions(
|
|
406
|
-
{
|
|
407
|
-
projectName: 'test',
|
|
408
|
-
addOns: ['baz'],
|
|
409
|
-
framework: 'react',
|
|
410
|
-
},
|
|
411
|
-
['foo'],
|
|
412
|
-
)
|
|
413
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
|
|
414
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('baz')).toBe(true)
|
|
415
|
-
expect(options?.tailwind).toBe(true)
|
|
416
|
-
expect(options?.typescript).toBe(true)
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
it('should ignore legacy start add-on id from exported commands', async () => {
|
|
420
|
-
__testRegisterFramework({
|
|
421
|
-
id: 'react',
|
|
422
|
-
name: 'react',
|
|
423
|
-
getAddOns: () => [
|
|
424
|
-
{
|
|
425
|
-
id: 'tanstack-query',
|
|
426
|
-
name: 'TanStack Query',
|
|
427
|
-
modes: ['file-router'],
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
id: 'nitro',
|
|
431
|
-
name: 'nitro',
|
|
432
|
-
modes: ['file-router'],
|
|
433
|
-
default: true,
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
const options = await normalizeOptions({
|
|
439
|
-
projectName: 'test',
|
|
440
|
-
addOns: ['start', 'tanstack-query'],
|
|
441
|
-
framework: 'react',
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
expect(options?.chosenAddOns.map((a) => a.id)).toContain('tanstack-query')
|
|
445
|
-
expect(options?.chosenAddOns.map((a) => a.id)).not.toContain('start')
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
it('should handle toolchain as an addon', async () => {
|
|
449
|
-
__testRegisterFramework({
|
|
450
|
-
id: 'react',
|
|
451
|
-
name: 'react',
|
|
452
|
-
getAddOns: () => [
|
|
453
|
-
{
|
|
454
|
-
id: 'biome',
|
|
455
|
-
name: 'Biome',
|
|
456
|
-
modes: ['file-router', 'code-router'],
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
id: 'nitro',
|
|
460
|
-
name: 'nitro',
|
|
461
|
-
modes: ['file-router', 'code-router'],
|
|
462
|
-
default: true,
|
|
463
|
-
},
|
|
464
|
-
],
|
|
465
|
-
})
|
|
466
|
-
const options = await normalizeOptions({
|
|
467
|
-
projectName: 'test',
|
|
468
|
-
toolchain: 'biome',
|
|
469
|
-
})
|
|
470
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('biome')).toBe(true)
|
|
471
|
-
expect(options?.tailwind).toBe(true)
|
|
472
|
-
expect(options?.typescript).toBe(true)
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
it('should keep file-router mode in router-only compatibility mode', async () => {
|
|
476
|
-
const options = await normalizeOptions({
|
|
477
|
-
projectName: 'test',
|
|
478
|
-
routerOnly: true,
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
expect(options?.mode).toBe('file-router')
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
it('includes examples by default in non-router-only mode', async () => {
|
|
485
|
-
const options = await normalizeOptions({
|
|
486
|
-
projectName: 'test',
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
expect((options as any)?.includeExamples).toBe(true)
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
it('supports disabling examples from the CLI', async () => {
|
|
493
|
-
const options = await normalizeOptions({
|
|
494
|
-
projectName: 'test',
|
|
495
|
-
examples: false,
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
expect((options as any)?.includeExamples).toBe(false)
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
it('should ignore add-ons and deployment in router-only mode but keep toolchain', async () => {
|
|
502
|
-
__testRegisterFramework({
|
|
503
|
-
id: 'react',
|
|
504
|
-
name: 'react',
|
|
505
|
-
getAddOns: () => [
|
|
506
|
-
{
|
|
507
|
-
id: 'form',
|
|
508
|
-
name: 'Form',
|
|
509
|
-
modes: ['file-router'],
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
id: 'nitro',
|
|
513
|
-
name: 'nitro',
|
|
514
|
-
modes: ['file-router'],
|
|
515
|
-
type: 'deployment',
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
id: 'biome',
|
|
519
|
-
name: 'Biome',
|
|
520
|
-
modes: ['file-router'],
|
|
521
|
-
type: 'toolchain',
|
|
522
|
-
},
|
|
523
|
-
],
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
const options = await normalizeOptions(
|
|
527
|
-
{
|
|
528
|
-
projectName: 'test',
|
|
529
|
-
framework: 'react',
|
|
530
|
-
routerOnly: true,
|
|
531
|
-
addOns: ['form'],
|
|
532
|
-
deployment: 'nitro',
|
|
533
|
-
toolchain: 'biome',
|
|
534
|
-
},
|
|
535
|
-
['form'],
|
|
536
|
-
{ forcedDeployment: 'nitro' },
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
expect(options?.chosenAddOns.map((a) => a.id)).toEqual(['biome'])
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
it('should handle the funky Windows edge case with CLI parsing', async () => {
|
|
543
|
-
__testRegisterFramework({
|
|
544
|
-
id: 'react',
|
|
545
|
-
name: 'react',
|
|
546
|
-
getAddOns: () => [
|
|
547
|
-
{
|
|
548
|
-
id: 'foo',
|
|
549
|
-
name: 'foobar',
|
|
550
|
-
modes: ['file-router', 'code-router'],
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
id: 'baz',
|
|
554
|
-
name: 'baz',
|
|
555
|
-
modes: ['file-router', 'code-router'],
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
id: 'nitro',
|
|
559
|
-
name: 'nitro',
|
|
560
|
-
modes: ['file-router', 'code-router'],
|
|
561
|
-
default: true,
|
|
562
|
-
},
|
|
563
|
-
],
|
|
564
|
-
})
|
|
565
|
-
const options = await normalizeOptions({
|
|
566
|
-
projectName: 'test',
|
|
567
|
-
addOns: ['baz foo'],
|
|
568
|
-
})
|
|
569
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('foo')).toBe(true)
|
|
570
|
-
expect(options?.chosenAddOns.map((a) => a.id).includes('baz')).toBe(true)
|
|
571
|
-
})
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
describe('validateLegacyCreateFlags', () => {
|
|
575
|
-
it('returns no warnings or errors without legacy flags', () => {
|
|
576
|
-
const result = validateLegacyCreateFlags({})
|
|
577
|
-
expect(result.warnings).toEqual([])
|
|
578
|
-
expect(result.error).toBeUndefined()
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
it('warns when --router-only is used', () => {
|
|
582
|
-
const result = validateLegacyCreateFlags({ routerOnly: true })
|
|
583
|
-
expect(result.error).toBeUndefined()
|
|
584
|
-
expect(result.warnings[0]).toContain('--router-only')
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
it('warns when --tailwind is used', () => {
|
|
588
|
-
const result = validateLegacyCreateFlags({ tailwind: true })
|
|
589
|
-
expect(result.error).toBeUndefined()
|
|
590
|
-
expect(result.warnings[0]).toContain('--tailwind')
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
it('warns heavily when --no-tailwind is used', () => {
|
|
594
|
-
const result = validateLegacyCreateFlags({ tailwind: false })
|
|
595
|
-
expect(result.error).toBeUndefined()
|
|
596
|
-
expect(result.warnings[0]).toContain('--no-tailwind')
|
|
597
|
-
expect(result.warnings[0]).toContain('intentionally unsupported')
|
|
598
|
-
})
|
|
599
|
-
|
|
600
|
-
it('errors for JavaScript templates', () => {
|
|
601
|
-
const result = validateLegacyCreateFlags({ template: 'javascript' })
|
|
602
|
-
expect(result.error).toContain('JavaScript/JSX templates are not supported')
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
it('does not error for non-legacy template values', () => {
|
|
606
|
-
const result = validateLegacyCreateFlags({ template: 'foo' })
|
|
607
|
-
expect(result.error).toBeUndefined()
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
it('warns for supported deprecated template values', () => {
|
|
611
|
-
const result = validateLegacyCreateFlags({ template: 'tsx' })
|
|
612
|
-
expect(result.error).toBeUndefined()
|
|
613
|
-
expect(result.warnings[0]).toContain('--template')
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
it('warns when --starter is used', () => {
|
|
617
|
-
const result = validateLegacyCreateFlags({ starter: 'ecommerce' })
|
|
618
|
-
expect(result.error).toBeUndefined()
|
|
619
|
-
expect(result.warnings[0]).toContain('--starter')
|
|
620
|
-
expect(result.warnings[0]).toContain('deprecated')
|
|
621
|
-
})
|
|
622
|
-
})
|