@tanstack/cli 0.0.8 → 0.48.3

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.js +7 -0
  3. package/dist/cli.js +481 -0
  4. package/dist/command-line.js +174 -0
  5. package/dist/dev-watch.js +290 -0
  6. package/dist/file-syncer.js +148 -0
  7. package/dist/index.js +1 -0
  8. package/dist/mcp/api.js +31 -0
  9. package/dist/mcp/tools.js +250 -0
  10. package/dist/mcp/types.js +37 -0
  11. package/dist/mcp.js +121 -0
  12. package/dist/options.js +162 -0
  13. package/dist/types/bin.d.ts +2 -0
  14. package/dist/types/cli.d.ts +16 -0
  15. package/dist/types/command-line.d.ts +10 -0
  16. package/dist/types/dev-watch.d.ts +27 -0
  17. package/dist/types/file-syncer.d.ts +18 -0
  18. package/dist/types/index.d.ts +1 -0
  19. package/dist/types/mcp/api.d.ts +4 -0
  20. package/dist/types/mcp/tools.d.ts +2 -0
  21. package/dist/types/mcp/types.d.ts +217 -0
  22. package/dist/types/mcp.d.ts +6 -0
  23. package/dist/types/options.d.ts +8 -0
  24. package/dist/types/types.d.ts +25 -0
  25. package/dist/types/ui-environment.d.ts +2 -0
  26. package/dist/types/ui-prompts.d.ts +12 -0
  27. package/dist/types/utils.d.ts +8 -0
  28. package/dist/types.js +1 -0
  29. package/dist/ui-environment.js +52 -0
  30. package/dist/ui-prompts.js +244 -0
  31. package/dist/utils.js +30 -0
  32. package/package.json +46 -47
  33. package/src/bin.ts +6 -93
  34. package/src/cli.ts +692 -0
  35. package/src/command-line.ts +236 -0
  36. package/src/dev-watch.ts +430 -0
  37. package/src/file-syncer.ts +205 -0
  38. package/src/index.ts +1 -85
  39. package/src/mcp.ts +190 -0
  40. package/src/options.ts +260 -0
  41. package/src/types.ts +27 -0
  42. package/src/ui-environment.ts +74 -0
  43. package/src/ui-prompts.ts +322 -0
  44. package/src/utils.ts +38 -0
  45. package/tests/command-line.test.ts +304 -0
  46. package/tests/index.test.ts +9 -0
  47. package/tests/mcp.test.ts +225 -0
  48. package/tests/options.test.ts +304 -0
  49. package/tests/setupVitest.ts +6 -0
  50. package/tests/ui-environment.test.ts +97 -0
  51. package/tests/ui-prompts.test.ts +238 -0
  52. package/tsconfig.json +17 -0
  53. package/vitest.config.js +7 -0
  54. package/dist/bin.cjs +0 -769
  55. package/dist/bin.d.cts +0 -1
  56. package/dist/bin.d.mts +0 -1
  57. package/dist/bin.mjs +0 -768
  58. package/dist/fetch-CbFFGJEw.cjs +0 -3
  59. package/dist/fetch-DG5dLrsb.cjs +0 -522
  60. package/dist/fetch-DhlVXS6S.mjs +0 -390
  61. package/dist/fetch-I_OVg8JX.mjs +0 -3
  62. package/dist/index.cjs +0 -37
  63. package/dist/index.d.cts +0 -1172
  64. package/dist/index.d.mts +0 -1172
  65. package/dist/index.mjs +0 -4
  66. package/dist/template-Szi7-AZJ.mjs +0 -2202
  67. package/dist/template-lWrIZhCQ.cjs +0 -2314
  68. package/src/api/fetch.test.ts +0 -114
  69. package/src/api/fetch.ts +0 -278
  70. package/src/cache/index.ts +0 -89
  71. package/src/commands/create.ts +0 -470
  72. package/src/commands/mcp.test.ts +0 -152
  73. package/src/commands/mcp.ts +0 -211
  74. package/src/engine/compile-with-addons.test.ts +0 -302
  75. package/src/engine/compile.test.ts +0 -404
  76. package/src/engine/compile.ts +0 -569
  77. package/src/engine/config-file.test.ts +0 -118
  78. package/src/engine/config-file.ts +0 -61
  79. package/src/engine/custom-addons/integration.ts +0 -323
  80. package/src/engine/custom-addons/shared.test.ts +0 -98
  81. package/src/engine/custom-addons/shared.ts +0 -281
  82. package/src/engine/custom-addons/template.test.ts +0 -288
  83. package/src/engine/custom-addons/template.ts +0 -124
  84. package/src/engine/template.test.ts +0 -256
  85. package/src/engine/template.ts +0 -269
  86. package/src/engine/types.ts +0 -336
  87. package/src/parse-gitignore.d.ts +0 -5
  88. package/src/templates/base.ts +0 -883
@@ -0,0 +1,304 @@
1
+ import { beforeEach, describe, it, expect, vi } from 'vitest'
2
+
3
+ import { promptForCreateOptions } from '../src/options'
4
+ import {
5
+ __testClearFrameworks,
6
+ __testRegisterFramework,
7
+ } from '@tanstack/create'
8
+
9
+ import * as prompts from '../src/ui-prompts'
10
+
11
+ import type { Framework } from '@tanstack/create'
12
+
13
+ import type { CliOptions } from '../src/types'
14
+
15
+ vi.mock('../src/ui-prompts')
16
+
17
+ beforeEach(() => {
18
+ __testClearFrameworks()
19
+ __testRegisterFramework({
20
+ id: 'react-cra',
21
+ name: 'react',
22
+ getAddOns: () => [
23
+ {
24
+ id: 'react-query',
25
+ type: 'add-on',
26
+ modes: ['file-router', 'code-router'],
27
+ },
28
+ {
29
+ id: 'tanstack-chat',
30
+ type: 'add-on',
31
+ modes: ['file-router', 'code-router'],
32
+ },
33
+ {
34
+ id: 'biome',
35
+ type: 'toolchain',
36
+ modes: ['file-router', 'code-router'],
37
+ },
38
+ ],
39
+ supportedModes: {
40
+ 'code-router': {
41
+ displayName: 'Code Router',
42
+ description: 'TanStack Router using code to define the routes',
43
+ forceTypescript: false,
44
+ },
45
+ 'file-router': {
46
+ displayName: 'File Router',
47
+ description: 'TanStack Router using files to define the routes',
48
+ forceTypescript: true,
49
+ },
50
+ },
51
+ } as unknown as Framework)
52
+
53
+ __testRegisterFramework({
54
+ id: 'solid',
55
+ name: 'solid',
56
+ getAddOns: () => [],
57
+ } as unknown as Framework)
58
+ })
59
+
60
+ const baseCliOptions: CliOptions = {
61
+ framework: 'react-cra',
62
+ addOns: [],
63
+ toolchain: undefined,
64
+ projectName: undefined,
65
+ git: undefined,
66
+ }
67
+
68
+ function setBasicSpies() {
69
+ 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
+ vi.spyOn(prompts, 'selectPackageManager').mockImplementation(
76
+ async () => 'npm',
77
+ )
78
+ vi.spyOn(prompts, 'selectToolchain').mockImplementation(async () => undefined)
79
+ vi.spyOn(prompts, 'selectAddOns').mockImplementation(async () => [])
80
+ }
81
+
82
+ describe('promptForCreateOptions', () => {
83
+ //// Project name
84
+
85
+ it('prompt for a project name', async () => {
86
+ setBasicSpies()
87
+
88
+ const options = await promptForCreateOptions(baseCliOptions, {})
89
+
90
+ expect(options?.projectName).toBe('hello')
91
+ })
92
+
93
+ it('accept incoming project name', async () => {
94
+ setBasicSpies()
95
+
96
+ const options = await promptForCreateOptions(
97
+ { ...baseCliOptions, projectName: 'override' },
98
+ {},
99
+ )
100
+
101
+ expect(options?.projectName).toBe('override')
102
+ })
103
+
104
+ //// Mode (router type)
105
+
106
+ it('forceMode should override template', async () => {
107
+ setBasicSpies()
108
+
109
+ const options = await promptForCreateOptions(
110
+ { ...baseCliOptions, template: 'javascript' },
111
+ { forcedMode: 'file-router' },
112
+ )
113
+
114
+ expect(options?.mode).toBe('file-router')
115
+ expect(options?.typescript).toBe(true)
116
+ })
117
+
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
+ })
186
+
187
+ it('set tailwind when solid', async () => {
188
+ setBasicSpies()
189
+ const options = await promptForCreateOptions(
190
+ { ...baseCliOptions, tailwind: undefined, framework: 'solid' },
191
+ {},
192
+ )
193
+
194
+ expect(options?.tailwind).toBe(true)
195
+ })
196
+
197
+ //// Package manager
198
+
199
+ it('uses the package manager from the cli options', async () => {
200
+ setBasicSpies()
201
+
202
+ const options = await promptForCreateOptions(
203
+ { ...baseCliOptions, packageManager: 'bun' },
204
+ {},
205
+ )
206
+
207
+ expect(options?.packageManager).toBe('bun')
208
+ })
209
+
210
+ it('uses the package manager from the cli options', async () => {
211
+ setBasicSpies()
212
+
213
+ process.env.npm_config_userconfig = 'blarg'
214
+
215
+ const options = await promptForCreateOptions(
216
+ { ...baseCliOptions, packageManager: undefined },
217
+ {},
218
+ )
219
+
220
+ expect(options?.packageManager).toBe('pnpm')
221
+ })
222
+
223
+ //// Add-ons
224
+ it('should be clean when no add-ons are selected', async () => {
225
+ setBasicSpies()
226
+
227
+ const options = await promptForCreateOptions({ ...baseCliOptions }, {})
228
+
229
+ expect(options?.chosenAddOns).toEqual([])
230
+ })
231
+
232
+ it('should select biome when toolchain is specified', async () => {
233
+ setBasicSpies()
234
+
235
+ vi.spyOn(prompts, 'selectToolchain').mockImplementation(async () => 'biome')
236
+
237
+ const options = await promptForCreateOptions(
238
+ { ...baseCliOptions, toolchain: 'biome' },
239
+ {},
240
+ )
241
+
242
+ expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual(['biome'])
243
+ })
244
+
245
+ it('should handle forced add-ons', async () => {
246
+ setBasicSpies()
247
+
248
+ vi.spyOn(prompts, 'selectToolchain').mockImplementation(
249
+ async () => undefined,
250
+ )
251
+
252
+ const options = await promptForCreateOptions(
253
+ { ...baseCliOptions },
254
+ { forcedAddOns: ['react-query'] },
255
+ )
256
+
257
+ expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual([
258
+ 'react-query',
259
+ ])
260
+ // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
261
+ expect(options?.tailwind).toBe(true)
262
+ expect(options?.typescript).toBe(true)
263
+ })
264
+
265
+ it('should handle add-ons from the CLI', async () => {
266
+ setBasicSpies()
267
+
268
+ const options = await promptForCreateOptions(
269
+ { ...baseCliOptions, addOns: ['biome', 'react-query'] },
270
+ {},
271
+ )
272
+
273
+ expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual([
274
+ 'biome',
275
+ 'react-query',
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
280
+ expect(options?.tailwind).toBe(true)
281
+ expect(options?.typescript).toBe(true)
282
+ })
283
+
284
+ it('should handle user-selected add-ons', async () => {
285
+ setBasicSpies()
286
+
287
+ vi.spyOn(prompts, 'selectAddOns').mockImplementation(async () =>
288
+ Promise.resolve(['biome', 'react-query']),
289
+ )
290
+
291
+ const options = await promptForCreateOptions(
292
+ { ...baseCliOptions, addOns: undefined },
293
+ {},
294
+ )
295
+
296
+ expect(options?.chosenAddOns.map((a) => a.id).sort()).toEqual([
297
+ 'biome',
298
+ 'react-query',
299
+ ])
300
+ // Tailwind should be prompted (and mock returns true) since no add-on explicitly requires it
301
+ expect(options?.tailwind).toBe(true)
302
+ expect(options?.typescript).toBe(true)
303
+ })
304
+ })
@@ -0,0 +1,6 @@
1
+ import createFetchMock from 'vitest-fetch-mock'
2
+ import { vi } from 'vitest'
3
+
4
+ const fetchMocker = createFetchMock(vi)
5
+
6
+ fetchMocker.enableMocks()
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+
3
+ import * as clack from '@clack/prompts'
4
+
5
+ import { createUIEnvironment } from '../src/ui-environment.js'
6
+
7
+ vi.mock('@clack/prompts')
8
+
9
+ // @ts-expect-error
10
+ vi.spyOn(process, 'exit').mockImplementation(() => {})
11
+
12
+ describe('createUIEnvironment', () => {
13
+ it('should create a silent UI environment', () => {
14
+ const environment = createUIEnvironment('test', true)
15
+ expect(environment.appName).toBe('test')
16
+ })
17
+
18
+ it('should handle intro', () => {
19
+ const environment = createUIEnvironment('test', false)
20
+ const spy = vi
21
+ .spyOn(clack, 'intro')
22
+ .mockImplementation(async () => undefined)
23
+ environment.intro('test')
24
+ expect(spy).toHaveBeenCalledWith('test')
25
+ })
26
+
27
+ it('should handle outro', () => {
28
+ const environment = createUIEnvironment('test', false)
29
+ const spy = vi
30
+ .spyOn(clack, 'outro')
31
+ .mockImplementation(async () => undefined)
32
+ environment.outro('test')
33
+ expect(spy).toHaveBeenCalledWith('test')
34
+ })
35
+
36
+ it('should handle info', () => {
37
+ const environment = createUIEnvironment('test', false)
38
+ const spy = vi
39
+ .spyOn(clack.log, 'info')
40
+ .mockImplementation(async () => undefined)
41
+ environment.info('test')
42
+ expect(spy).toHaveBeenCalled()
43
+ })
44
+
45
+ it('should handle error', () => {
46
+ const environment = createUIEnvironment('test', false)
47
+ const spy = vi
48
+ .spyOn(clack.log, 'error')
49
+ .mockImplementation(async () => undefined)
50
+ environment.error('test')
51
+ expect(spy).toHaveBeenCalled()
52
+ })
53
+
54
+ it('should handle warn', () => {
55
+ const environment = createUIEnvironment('test', false)
56
+ const spy = vi
57
+ .spyOn(clack.log, 'warn')
58
+ .mockImplementation(async () => undefined)
59
+ environment.warn('test')
60
+ expect(spy).toHaveBeenCalled()
61
+ })
62
+
63
+ it('should handle confirm', async () => {
64
+ const environment = createUIEnvironment('test', false)
65
+ const spy = vi.spyOn(clack, 'confirm').mockImplementation(async () => true)
66
+ const isCancelSpy = vi
67
+ .spyOn(clack, 'isCancel')
68
+ .mockImplementation(() => false)
69
+ const result = await environment.confirm('test')
70
+ expect(spy).toHaveBeenCalled()
71
+ expect(isCancelSpy).toHaveBeenCalled()
72
+ expect(result).toBe(true)
73
+ })
74
+
75
+ it('should handle confirm', async () => {
76
+ const environment = createUIEnvironment('test', false)
77
+ const spy = vi.spyOn(clack, 'confirm').mockImplementation(async () => true)
78
+ const isCancelSpy = vi
79
+ .spyOn(clack, 'isCancel')
80
+ .mockImplementation(() => true)
81
+ await environment.confirm('test')
82
+ expect(spy).toHaveBeenCalled()
83
+ expect(isCancelSpy).toHaveBeenCalled()
84
+ })
85
+
86
+ it('should handle spinner', async () => {
87
+ const environment = createUIEnvironment('test', false)
88
+ // @ts-expect-error
89
+ const spy = vi.spyOn(clack, 'spinner').mockImplementation(async () => ({
90
+ start: (_msg?: string) => {},
91
+ stop: (_msg?: string) => {},
92
+ message: (_msg?: string) => {},
93
+ }))
94
+ const result = await environment.spinner()
95
+ expect(spy).toHaveBeenCalled()
96
+ })
97
+ })
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+
3
+ import * as clack from '@clack/prompts'
4
+
5
+ import {
6
+ getProjectName,
7
+ selectAddOns,
8
+ selectGit,
9
+ selectPackageManager,
10
+ selectRouterType,
11
+ selectTailwind,
12
+ selectToolchain,
13
+ } from '../src/ui-prompts'
14
+
15
+ import type { AddOn, Framework } from '@tanstack/create'
16
+
17
+ vi.mock('@clack/prompts')
18
+
19
+ vi.spyOn(process, 'exit').mockImplementation((number) => {
20
+ throw new Error(`process.exit: ${number}`)
21
+ })
22
+
23
+ describe('getProjectName', () => {
24
+ it('should return the project name', async () => {
25
+ vi.spyOn(clack, 'text').mockImplementation(async () => 'my-app')
26
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
27
+
28
+ const projectName = await getProjectName()
29
+ expect(projectName).toBe('my-app')
30
+ })
31
+
32
+ it('should exit on cancel', async () => {
33
+ vi.spyOn(clack, 'text').mockImplementation(async () => 'Cancelled')
34
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
35
+
36
+ await expect(getProjectName()).rejects.toThrowError(/exit/)
37
+ })
38
+ })
39
+
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
+ describe('selectPackageManager', () => {
77
+ it('should select pnpm', async () => {
78
+ vi.spyOn(clack, 'select').mockImplementation(async () => 'pnpm')
79
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
80
+
81
+ const packageManager = await selectPackageManager()
82
+ expect(packageManager).toBe('pnpm')
83
+ })
84
+
85
+ it('should exit on cancel', async () => {
86
+ vi.spyOn(clack, 'select').mockImplementation(async () =>
87
+ Symbol.for('cancel'),
88
+ )
89
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
90
+
91
+ await expect(selectPackageManager()).rejects.toThrowError(/exit/)
92
+ })
93
+ })
94
+
95
+ describe('selectAddOns', () => {
96
+ it('should show keyboard shortcuts help and select add-ons', async () => {
97
+ const noteSpy = vi.spyOn(clack, 'note').mockImplementation(() => {})
98
+ vi.spyOn(clack, 'multiselect').mockImplementation(async () => ['add-on-1'])
99
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
100
+
101
+ const packageManager = await selectAddOns(
102
+ {
103
+ getAddOns: () =>
104
+ [
105
+ {
106
+ id: 'add-on-1',
107
+ name: 'Add-on 1',
108
+ description: 'Add-on 1 description',
109
+ type: 'add-on',
110
+ modes: ['file-router'],
111
+ },
112
+ ] as Array<AddOn>,
113
+ } as Framework,
114
+ 'file-router',
115
+ 'add-on',
116
+ 'Select add-ons',
117
+ )
118
+
119
+ expect(packageManager).toEqual(['add-on-1'])
120
+ expect(noteSpy).toHaveBeenCalledWith(
121
+ 'Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm',
122
+ 'Keyboard Shortcuts',
123
+ )
124
+ })
125
+
126
+ it('should exit on cancel', async () => {
127
+ vi.spyOn(clack, 'select').mockImplementation(async () =>
128
+ Symbol.for('cancel'),
129
+ )
130
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
131
+
132
+ await expect(
133
+ selectAddOns(
134
+ {
135
+ getAddOns: () =>
136
+ [
137
+ {
138
+ id: 'add-on-1',
139
+ name: 'Add-on 1',
140
+ description: 'Add-on 1 description',
141
+ type: 'add-on',
142
+ modes: ['file-router'],
143
+ },
144
+ ] as Array<AddOn>,
145
+ } as Framework,
146
+ 'file-router',
147
+ 'add-on',
148
+ 'Select add-ons',
149
+ ),
150
+ ).rejects.toThrowError(/exit/)
151
+ })
152
+ })
153
+
154
+ describe('selectGit', () => {
155
+ it('should select git', async () => {
156
+ vi.spyOn(clack, 'confirm').mockImplementation(async () => true)
157
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
158
+
159
+ const git = await selectGit()
160
+ expect(git).toBe(true)
161
+ })
162
+
163
+ it('should exit on cancel', async () => {
164
+ vi.spyOn(clack, 'confirm').mockImplementation(async () =>
165
+ Symbol.for('cancel'),
166
+ )
167
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
168
+
169
+ await expect(selectGit()).rejects.toThrowError(/exit/)
170
+ })
171
+ })
172
+
173
+ describe('selectToolchain', () => {
174
+ it('should select a toolchain', async () => {
175
+ vi.spyOn(clack, 'select').mockImplementation(async () => 'biome')
176
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
177
+
178
+ const packageManager = await selectToolchain({
179
+ getAddOns: () =>
180
+ [
181
+ {
182
+ id: 'biome',
183
+ name: 'Biome',
184
+ description: 'Biome description',
185
+ type: 'toolchain',
186
+ modes: ['file-router'],
187
+ },
188
+ ] as Array<AddOn>,
189
+ } as Framework)
190
+ expect(packageManager).toEqual('biome')
191
+ })
192
+ it('should select a toolchain', async () => {
193
+ const selectSpy = vi
194
+ .spyOn(clack, 'select')
195
+ .mockImplementation(async () => 'biome')
196
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
197
+
198
+ const packageManager = await selectToolchain(
199
+ {
200
+ getAddOns: () =>
201
+ [
202
+ {
203
+ id: 'biome',
204
+ name: 'Biome',
205
+ description: 'Biome description',
206
+ type: 'toolchain',
207
+ modes: ['file-router'],
208
+ },
209
+ ] as Array<AddOn>,
210
+ } as Framework,
211
+ 'biome',
212
+ )
213
+ expect(packageManager).toEqual('biome')
214
+ expect(selectSpy).not.toHaveBeenCalled()
215
+ })
216
+
217
+ it('should exit on cancel', async () => {
218
+ vi.spyOn(clack, 'select').mockImplementation(async () =>
219
+ Symbol.for('cancel'),
220
+ )
221
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
222
+
223
+ await expect(
224
+ selectToolchain({
225
+ getAddOns: () =>
226
+ [
227
+ {
228
+ id: 'biome',
229
+ name: 'Biome',
230
+ description: 'Biome description',
231
+ type: 'toolchain',
232
+ modes: ['file-router'],
233
+ },
234
+ ] as Array<AddOn>,
235
+ } as Framework),
236
+ ).rejects.toThrowError(/exit/)
237
+ })
238
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "moduleResolution": "node",
12
+ "declaration": true,
13
+ "declarationDir": "./dist/types"
14
+ },
15
+ "include": ["./src/**/*.ts"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ setupFiles: ['./tests/setupVitest.js'],
6
+ },
7
+ })