@tanstack/cli 0.0.8 → 0.48.2
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/bin.js +7 -0
- package/dist/cli.js +481 -0
- package/dist/command-line.js +174 -0
- package/dist/dev-watch.js +290 -0
- package/dist/file-syncer.js +148 -0
- package/dist/index.js +1 -0
- package/dist/mcp/api.js +31 -0
- package/dist/mcp/tools.js +250 -0
- package/dist/mcp/types.js +37 -0
- package/dist/mcp.js +121 -0
- package/dist/options.js +162 -0
- package/dist/types/bin.d.ts +2 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/command-line.d.ts +10 -0
- package/dist/types/dev-watch.d.ts +27 -0
- package/dist/types/file-syncer.d.ts +18 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcp/api.d.ts +4 -0
- package/dist/types/mcp/tools.d.ts +2 -0
- package/dist/types/mcp/types.d.ts +217 -0
- package/dist/types/mcp.d.ts +6 -0
- package/dist/types/options.d.ts +8 -0
- package/dist/types/types.d.ts +25 -0
- package/dist/types/ui-environment.d.ts +2 -0
- package/dist/types/ui-prompts.d.ts +12 -0
- package/dist/types/utils.d.ts +8 -0
- package/dist/types.js +1 -0
- package/dist/ui-environment.js +52 -0
- package/dist/ui-prompts.js +244 -0
- package/dist/utils.js +30 -0
- package/package.json +46 -46
- package/src/bin.ts +6 -93
- package/src/cli.ts +692 -0
- package/src/command-line.ts +236 -0
- package/src/dev-watch.ts +430 -0
- package/src/file-syncer.ts +205 -0
- package/src/index.ts +1 -85
- package/src/mcp.ts +190 -0
- package/src/options.ts +260 -0
- package/src/types.ts +27 -0
- package/src/ui-environment.ts +74 -0
- package/src/ui-prompts.ts +322 -0
- package/src/utils.ts +38 -0
- package/tests/command-line.test.ts +304 -0
- package/tests/index.test.ts +9 -0
- package/tests/mcp.test.ts +225 -0
- package/tests/options.test.ts +304 -0
- package/tests/setupVitest.ts +6 -0
- package/tests/ui-environment.test.ts +97 -0
- package/tests/ui-prompts.test.ts +238 -0
- package/tsconfig.json +17 -0
- package/vitest.config.js +7 -0
- package/dist/bin.cjs +0 -769
- package/dist/bin.d.cts +0 -1
- package/dist/bin.d.mts +0 -1
- package/dist/bin.mjs +0 -768
- package/dist/fetch-CbFFGJEw.cjs +0 -3
- package/dist/fetch-DG5dLrsb.cjs +0 -522
- package/dist/fetch-DhlVXS6S.mjs +0 -390
- package/dist/fetch-I_OVg8JX.mjs +0 -3
- package/dist/index.cjs +0 -37
- package/dist/index.d.cts +0 -1172
- package/dist/index.d.mts +0 -1172
- package/dist/index.mjs +0 -4
- package/dist/template-Szi7-AZJ.mjs +0 -2202
- package/dist/template-lWrIZhCQ.cjs +0 -2314
- package/src/api/fetch.test.ts +0 -114
- package/src/api/fetch.ts +0 -278
- package/src/cache/index.ts +0 -89
- package/src/commands/create.ts +0 -470
- package/src/commands/mcp.test.ts +0 -152
- package/src/commands/mcp.ts +0 -211
- package/src/engine/compile-with-addons.test.ts +0 -302
- package/src/engine/compile.test.ts +0 -404
- package/src/engine/compile.ts +0 -569
- package/src/engine/config-file.test.ts +0 -118
- package/src/engine/config-file.ts +0 -61
- package/src/engine/custom-addons/integration.ts +0 -323
- package/src/engine/custom-addons/shared.test.ts +0 -98
- package/src/engine/custom-addons/shared.ts +0 -281
- package/src/engine/custom-addons/template.test.ts +0 -288
- package/src/engine/custom-addons/template.ts +0 -124
- package/src/engine/template.test.ts +0 -256
- package/src/engine/template.ts +0 -269
- package/src/engine/types.ts +0 -336
- package/src/parse-gitignore.d.ts +0 -5
- package/src/templates/base.ts +0 -883
package/src/options.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { cancel, confirm, intro, isCancel } from '@clack/prompts'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
finalizeAddOns,
|
|
6
|
+
getFrameworkById,
|
|
7
|
+
getPackageManager,
|
|
8
|
+
populateAddOnOptionsDefaults,
|
|
9
|
+
readConfigFile,
|
|
10
|
+
} from '@tanstack/create'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getProjectName,
|
|
14
|
+
promptForAddOnOptions,
|
|
15
|
+
selectAddOns,
|
|
16
|
+
selectDeployment,
|
|
17
|
+
selectGit,
|
|
18
|
+
selectPackageManager,
|
|
19
|
+
selectRouterType,
|
|
20
|
+
selectTailwind,
|
|
21
|
+
selectToolchain,
|
|
22
|
+
selectTypescript,
|
|
23
|
+
} from './ui-prompts.js'
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
getCurrentDirectoryName,
|
|
27
|
+
sanitizePackageName,
|
|
28
|
+
validateProjectName,
|
|
29
|
+
} from './utils.js'
|
|
30
|
+
import type { Options } from '@tanstack/create'
|
|
31
|
+
|
|
32
|
+
import type { CliOptions } from './types.js'
|
|
33
|
+
|
|
34
|
+
export async function promptForCreateOptions(
|
|
35
|
+
cliOptions: CliOptions,
|
|
36
|
+
{
|
|
37
|
+
forcedAddOns = [],
|
|
38
|
+
forcedMode,
|
|
39
|
+
showDeploymentOptions = false,
|
|
40
|
+
}: {
|
|
41
|
+
forcedAddOns?: Array<string>
|
|
42
|
+
forcedMode?: string
|
|
43
|
+
showDeploymentOptions?: boolean
|
|
44
|
+
},
|
|
45
|
+
): Promise<Required<Options> | undefined> {
|
|
46
|
+
const options = {} as Required<Options>
|
|
47
|
+
|
|
48
|
+
options.framework = getFrameworkById(cliOptions.framework || 'react-cra')!
|
|
49
|
+
|
|
50
|
+
// Validate project name
|
|
51
|
+
if (cliOptions.projectName) {
|
|
52
|
+
// Handle "." as project name - use sanitized current directory name
|
|
53
|
+
if (cliOptions.projectName === '.') {
|
|
54
|
+
options.projectName = sanitizePackageName(getCurrentDirectoryName())
|
|
55
|
+
} else {
|
|
56
|
+
options.projectName = cliOptions.projectName
|
|
57
|
+
}
|
|
58
|
+
const { valid, error } = validateProjectName(options.projectName)
|
|
59
|
+
if (!valid) {
|
|
60
|
+
console.error(error)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
options.projectName = await getProjectName()
|
|
65
|
+
}
|
|
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
|
+
|
|
84
|
+
// Router type selection
|
|
85
|
+
if (forcedMode) {
|
|
86
|
+
options.mode = forcedMode
|
|
87
|
+
} else if (cliOptions.template) {
|
|
88
|
+
options.mode =
|
|
89
|
+
cliOptions.template === 'file-router' ? 'file-router' : 'code-router'
|
|
90
|
+
} else {
|
|
91
|
+
options.mode = await selectRouterType()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TypeScript selection (if using Code Router)
|
|
95
|
+
// TODO: Make this declarative
|
|
96
|
+
options.typescript =
|
|
97
|
+
options.mode === 'file-router' || options.framework.id === 'solid'
|
|
98
|
+
if (
|
|
99
|
+
forcedMode &&
|
|
100
|
+
options.framework.supportedModes[forcedMode].forceTypescript
|
|
101
|
+
) {
|
|
102
|
+
options.typescript = true
|
|
103
|
+
}
|
|
104
|
+
if (!options.typescript && options.mode === 'code-router') {
|
|
105
|
+
options.typescript = await selectTypescript()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Package manager selection
|
|
109
|
+
if (cliOptions.packageManager) {
|
|
110
|
+
options.packageManager = cliOptions.packageManager
|
|
111
|
+
} else {
|
|
112
|
+
const detectedPackageManager = await getPackageManager()
|
|
113
|
+
options.packageManager =
|
|
114
|
+
detectedPackageManager || (await selectPackageManager())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Toolchain selection
|
|
118
|
+
const toolchain = await selectToolchain(
|
|
119
|
+
options.framework,
|
|
120
|
+
cliOptions.toolchain,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Deployment selection
|
|
124
|
+
const deployment = showDeploymentOptions
|
|
125
|
+
? await selectDeployment(options.framework, cliOptions.deployment)
|
|
126
|
+
: undefined
|
|
127
|
+
|
|
128
|
+
// Add-ons selection
|
|
129
|
+
const addOns: Set<string> = new Set()
|
|
130
|
+
|
|
131
|
+
if (toolchain) {
|
|
132
|
+
addOns.add(toolchain)
|
|
133
|
+
}
|
|
134
|
+
if (deployment) {
|
|
135
|
+
addOns.add(deployment)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const addOn of forcedAddOns) {
|
|
139
|
+
addOns.add(addOn)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
143
|
+
for (const addOn of cliOptions.addOns) {
|
|
144
|
+
addOns.add(addOn)
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
for (const addOn of await selectAddOns(
|
|
148
|
+
options.framework,
|
|
149
|
+
options.mode,
|
|
150
|
+
'add-on',
|
|
151
|
+
'What add-ons would you like for your project?',
|
|
152
|
+
forcedAddOns,
|
|
153
|
+
)) {
|
|
154
|
+
addOns.add(addOn)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const addOn of await selectAddOns(
|
|
158
|
+
options.framework,
|
|
159
|
+
options.mode,
|
|
160
|
+
'example',
|
|
161
|
+
'Would you like an example?',
|
|
162
|
+
forcedAddOns,
|
|
163
|
+
false,
|
|
164
|
+
)) {
|
|
165
|
+
addOns.add(addOn)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
options.chosenAddOns = Array.from(
|
|
170
|
+
await finalizeAddOns(options.framework, options.mode, Array.from(addOns)),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if (options.chosenAddOns.length) {
|
|
174
|
+
options.typescript = true
|
|
175
|
+
}
|
|
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
|
+
|
|
197
|
+
// Prompt for add-on options in interactive mode
|
|
198
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
199
|
+
// Non-interactive mode: use defaults
|
|
200
|
+
options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
|
|
201
|
+
} else {
|
|
202
|
+
// Interactive mode: prompt for options
|
|
203
|
+
const userOptions = await promptForAddOnOptions(
|
|
204
|
+
options.chosenAddOns.map((a) => a.id),
|
|
205
|
+
options.framework,
|
|
206
|
+
)
|
|
207
|
+
const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
|
|
208
|
+
// Merge user options with defaults
|
|
209
|
+
options.addOnOptions = { ...defaultOptions, ...userOptions }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
options.git = cliOptions.git || (await selectGit())
|
|
213
|
+
if (cliOptions.install === false) {
|
|
214
|
+
options.install = false
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return options
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function promptForAddOns(): Promise<Array<string>> {
|
|
221
|
+
const config = await readConfigFile(process.cwd())
|
|
222
|
+
|
|
223
|
+
if (!config) {
|
|
224
|
+
console.error('No config file found')
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const framework = getFrameworkById(config.framework)
|
|
229
|
+
|
|
230
|
+
if (!framework) {
|
|
231
|
+
console.error(`Unknown framework: ${config.framework}`)
|
|
232
|
+
process.exit(1)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
intro(`Adding new add-ons to '${config.projectName}'`)
|
|
236
|
+
|
|
237
|
+
const addOns: Set<string> = new Set()
|
|
238
|
+
|
|
239
|
+
for (const addOn of await selectAddOns(
|
|
240
|
+
framework,
|
|
241
|
+
config.mode!,
|
|
242
|
+
'add-on',
|
|
243
|
+
'What add-ons would you like for your project?',
|
|
244
|
+
config.chosenAddOns,
|
|
245
|
+
)) {
|
|
246
|
+
addOns.add(addOn)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const addOn of await selectAddOns(
|
|
250
|
+
framework,
|
|
251
|
+
config.mode!,
|
|
252
|
+
'example',
|
|
253
|
+
'Would you like any examples?',
|
|
254
|
+
config.chosenAddOns,
|
|
255
|
+
)) {
|
|
256
|
+
addOns.add(addOn)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return Array.from(addOns)
|
|
260
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PackageManager } from '@tanstack/create'
|
|
2
|
+
|
|
3
|
+
export type TemplateOptions = 'typescript' | 'javascript' | 'file-router'
|
|
4
|
+
|
|
5
|
+
export interface CliOptions {
|
|
6
|
+
template?: TemplateOptions
|
|
7
|
+
framework?: string
|
|
8
|
+
tailwind?: boolean
|
|
9
|
+
packageManager?: PackageManager
|
|
10
|
+
toolchain?: string | false
|
|
11
|
+
deployment?: string
|
|
12
|
+
projectName?: string
|
|
13
|
+
git?: boolean
|
|
14
|
+
addOns?: Array<string> | boolean
|
|
15
|
+
listAddOns?: boolean
|
|
16
|
+
addonDetails?: string
|
|
17
|
+
mcp?: boolean
|
|
18
|
+
mcpSse?: boolean
|
|
19
|
+
starter?: string
|
|
20
|
+
targetDir?: string
|
|
21
|
+
interactive?: boolean
|
|
22
|
+
ui?: boolean
|
|
23
|
+
devWatch?: string
|
|
24
|
+
install?: boolean
|
|
25
|
+
addOnConfig?: string
|
|
26
|
+
force?: boolean
|
|
27
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cancel,
|
|
3
|
+
confirm,
|
|
4
|
+
intro,
|
|
5
|
+
isCancel,
|
|
6
|
+
log,
|
|
7
|
+
outro,
|
|
8
|
+
spinner,
|
|
9
|
+
} from '@clack/prompts'
|
|
10
|
+
import chalk from 'chalk'
|
|
11
|
+
|
|
12
|
+
import { createDefaultEnvironment } from '@tanstack/create'
|
|
13
|
+
|
|
14
|
+
import type { Environment } from '@tanstack/create'
|
|
15
|
+
|
|
16
|
+
export function createUIEnvironment(
|
|
17
|
+
appName: string,
|
|
18
|
+
silent: boolean,
|
|
19
|
+
): Environment {
|
|
20
|
+
const defaultEnvironment = createDefaultEnvironment()
|
|
21
|
+
|
|
22
|
+
let newEnvironment = {
|
|
23
|
+
...defaultEnvironment,
|
|
24
|
+
appName,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!silent) {
|
|
28
|
+
newEnvironment = {
|
|
29
|
+
...newEnvironment,
|
|
30
|
+
intro: (message: string) => {
|
|
31
|
+
intro(message)
|
|
32
|
+
},
|
|
33
|
+
outro: (message: string) => {
|
|
34
|
+
outro(message)
|
|
35
|
+
},
|
|
36
|
+
info: (title?: string, message?: string) => {
|
|
37
|
+
log.info(
|
|
38
|
+
`${title ? chalk.red(title) : ''}${message ? '\n' + chalk.green(message) : ''}`,
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
error: (title?: string, message?: string) => {
|
|
42
|
+
log.error(
|
|
43
|
+
`${title ? `${title}: ` : ''}${message ? '\n' + message : ''}`,
|
|
44
|
+
)
|
|
45
|
+
},
|
|
46
|
+
warn: (title?: string, message?: string) => {
|
|
47
|
+
log.warn(`${title ? `${title}: ` : ''}${message ? '\n' + message : ''}`)
|
|
48
|
+
},
|
|
49
|
+
confirm: async (message: string) => {
|
|
50
|
+
const shouldContinue = await confirm({
|
|
51
|
+
message,
|
|
52
|
+
})
|
|
53
|
+
if (isCancel(shouldContinue)) {
|
|
54
|
+
cancel('Operation cancelled.')
|
|
55
|
+
process.exit(0)
|
|
56
|
+
}
|
|
57
|
+
return shouldContinue
|
|
58
|
+
},
|
|
59
|
+
spinner: () => {
|
|
60
|
+
const s = spinner()
|
|
61
|
+
return {
|
|
62
|
+
start: (message: string) => {
|
|
63
|
+
s.start(message)
|
|
64
|
+
},
|
|
65
|
+
stop: (message: string) => {
|
|
66
|
+
s.stop(message)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return newEnvironment
|
|
74
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cancel,
|
|
3
|
+
confirm,
|
|
4
|
+
isCancel,
|
|
5
|
+
multiselect,
|
|
6
|
+
note,
|
|
7
|
+
select,
|
|
8
|
+
text,
|
|
9
|
+
} from '@clack/prompts'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_PACKAGE_MANAGER,
|
|
13
|
+
SUPPORTED_PACKAGE_MANAGERS,
|
|
14
|
+
getAllAddOns,
|
|
15
|
+
} from '@tanstack/create'
|
|
16
|
+
|
|
17
|
+
import { validateProjectName } from './utils.js'
|
|
18
|
+
import type { AddOn, PackageManager } from '@tanstack/create'
|
|
19
|
+
|
|
20
|
+
import type { Framework } from '@tanstack/create/dist/types/types.js'
|
|
21
|
+
|
|
22
|
+
export async function getProjectName(): Promise<string> {
|
|
23
|
+
const value = await text({
|
|
24
|
+
message: 'What would you like to name your project?',
|
|
25
|
+
defaultValue: 'my-app',
|
|
26
|
+
validate(value) {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return 'Please enter a name'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { valid, error } = validateProjectName(value)
|
|
32
|
+
if (!valid) {
|
|
33
|
+
return error
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (isCancel(value)) {
|
|
39
|
+
cancel('Operation cancelled.')
|
|
40
|
+
process.exit(0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function selectRouterType(): Promise<string> {
|
|
47
|
+
const routerType = await select({
|
|
48
|
+
message: 'Select the router type:',
|
|
49
|
+
options: [
|
|
50
|
+
{
|
|
51
|
+
value: 'file-router',
|
|
52
|
+
label: 'File Router - File-based routing structure',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: 'code-router',
|
|
56
|
+
label: 'Code Router - Traditional code-based routing',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
initialValue: 'file-router',
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (isCancel(routerType)) {
|
|
63
|
+
cancel('Operation cancelled.')
|
|
64
|
+
process.exit(0)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return routerType
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function selectTypescript(): Promise<boolean> {
|
|
71
|
+
const typescriptEnable = await confirm({
|
|
72
|
+
message: 'Would you like to use TypeScript?',
|
|
73
|
+
initialValue: true,
|
|
74
|
+
})
|
|
75
|
+
if (isCancel(typescriptEnable)) {
|
|
76
|
+
cancel('Operation cancelled.')
|
|
77
|
+
process.exit(0)
|
|
78
|
+
}
|
|
79
|
+
return typescriptEnable
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function selectTailwind(): Promise<boolean> {
|
|
83
|
+
const tailwind = await confirm({
|
|
84
|
+
message: 'Would you like to use Tailwind CSS?',
|
|
85
|
+
initialValue: true,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (isCancel(tailwind)) {
|
|
89
|
+
cancel('Operation cancelled.')
|
|
90
|
+
process.exit(0)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return tailwind
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function selectPackageManager(): Promise<PackageManager> {
|
|
97
|
+
const packageManager = await select({
|
|
98
|
+
message: 'Select package manager:',
|
|
99
|
+
options: SUPPORTED_PACKAGE_MANAGERS.map((pm) => ({
|
|
100
|
+
value: pm,
|
|
101
|
+
label: pm,
|
|
102
|
+
})),
|
|
103
|
+
initialValue: DEFAULT_PACKAGE_MANAGER,
|
|
104
|
+
})
|
|
105
|
+
if (isCancel(packageManager)) {
|
|
106
|
+
cancel('Operation cancelled.')
|
|
107
|
+
process.exit(0)
|
|
108
|
+
}
|
|
109
|
+
return packageManager
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Track if we've shown the multiselect help text
|
|
113
|
+
let hasShownMultiselectHelp = false
|
|
114
|
+
|
|
115
|
+
export async function selectAddOns(
|
|
116
|
+
framework: Framework,
|
|
117
|
+
mode: string,
|
|
118
|
+
type: string,
|
|
119
|
+
message: string,
|
|
120
|
+
forcedAddOns: Array<string> = [],
|
|
121
|
+
allowMultiple: boolean = true,
|
|
122
|
+
): Promise<Array<string>> {
|
|
123
|
+
const allAddOns = await getAllAddOns(framework, mode)
|
|
124
|
+
const addOns = allAddOns.filter((addOn) => addOn.type === type)
|
|
125
|
+
if (addOns.length === 0) {
|
|
126
|
+
return []
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Show help text only once
|
|
130
|
+
if (!hasShownMultiselectHelp) {
|
|
131
|
+
note(
|
|
132
|
+
'Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm',
|
|
133
|
+
'Keyboard Shortcuts',
|
|
134
|
+
)
|
|
135
|
+
hasShownMultiselectHelp = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (allowMultiple) {
|
|
139
|
+
const value = await multiselect({
|
|
140
|
+
message,
|
|
141
|
+
options: addOns
|
|
142
|
+
.filter((addOn) => !forcedAddOns.includes(addOn.id))
|
|
143
|
+
.map((addOn) => ({
|
|
144
|
+
value: addOn.id,
|
|
145
|
+
label: addOn.name,
|
|
146
|
+
hint: addOn.description,
|
|
147
|
+
})),
|
|
148
|
+
required: false,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (isCancel(value)) {
|
|
152
|
+
cancel('Operation cancelled.')
|
|
153
|
+
process.exit(0)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return value
|
|
157
|
+
} else {
|
|
158
|
+
const value = await select({
|
|
159
|
+
message,
|
|
160
|
+
options: [
|
|
161
|
+
{
|
|
162
|
+
value: 'none',
|
|
163
|
+
label: 'None',
|
|
164
|
+
},
|
|
165
|
+
...addOns
|
|
166
|
+
.filter((addOn) => !forcedAddOns.includes(addOn.id))
|
|
167
|
+
.map((addOn) => ({
|
|
168
|
+
value: addOn.id,
|
|
169
|
+
label: addOn.name,
|
|
170
|
+
hint: addOn.description,
|
|
171
|
+
})),
|
|
172
|
+
],
|
|
173
|
+
initialValue: 'none',
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
if (isCancel(value)) {
|
|
177
|
+
cancel('Operation cancelled.')
|
|
178
|
+
process.exit(0)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return value === 'none' ? [] : [value]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function selectGit(): Promise<boolean> {
|
|
186
|
+
const git = await confirm({
|
|
187
|
+
message: 'Would you like to initialize a new git repository?',
|
|
188
|
+
initialValue: true,
|
|
189
|
+
})
|
|
190
|
+
if (isCancel(git)) {
|
|
191
|
+
cancel('Operation cancelled.')
|
|
192
|
+
process.exit(0)
|
|
193
|
+
}
|
|
194
|
+
return git
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function selectToolchain(
|
|
198
|
+
framework: Framework,
|
|
199
|
+
toolchain?: string | false,
|
|
200
|
+
): Promise<string | undefined> {
|
|
201
|
+
if (toolchain === false) {
|
|
202
|
+
return undefined
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const toolchains = new Set<AddOn>()
|
|
206
|
+
for (const addOn of framework.getAddOns()) {
|
|
207
|
+
if (addOn.type === 'toolchain') {
|
|
208
|
+
toolchains.add(addOn)
|
|
209
|
+
if (toolchain && addOn.id === toolchain) {
|
|
210
|
+
return toolchain
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const tc = await select({
|
|
216
|
+
message: 'Select toolchain',
|
|
217
|
+
options: [
|
|
218
|
+
{
|
|
219
|
+
value: undefined,
|
|
220
|
+
label: 'None',
|
|
221
|
+
},
|
|
222
|
+
...Array.from(toolchains).map((tc) => ({
|
|
223
|
+
value: tc.id,
|
|
224
|
+
label: tc.name,
|
|
225
|
+
})),
|
|
226
|
+
],
|
|
227
|
+
initialValue: undefined,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (isCancel(tc)) {
|
|
231
|
+
cancel('Operation cancelled.')
|
|
232
|
+
process.exit(0)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return tc
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function promptForAddOnOptions(
|
|
239
|
+
addOnIds: Array<string>,
|
|
240
|
+
framework: Framework,
|
|
241
|
+
): Promise<Record<string, Record<string, any>>> {
|
|
242
|
+
const addOnOptions: Record<string, Record<string, any>> = {}
|
|
243
|
+
|
|
244
|
+
for (const addOnId of addOnIds) {
|
|
245
|
+
const addOn = framework.getAddOns().find((a) => a.id === addOnId)
|
|
246
|
+
if (!addOn || !addOn.options) continue
|
|
247
|
+
|
|
248
|
+
addOnOptions[addOnId] = {}
|
|
249
|
+
|
|
250
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
251
|
+
if (option && typeof option === 'object' && 'type' in option) {
|
|
252
|
+
if (option.type === 'select') {
|
|
253
|
+
const selectOption = option as {
|
|
254
|
+
type: 'select'
|
|
255
|
+
label: string
|
|
256
|
+
description?: string
|
|
257
|
+
default: string
|
|
258
|
+
options: Array<{ value: string; label: string }>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const value = await select({
|
|
262
|
+
message: `${addOn.name}: ${selectOption.label}`,
|
|
263
|
+
options: selectOption.options.map((opt) => ({
|
|
264
|
+
value: opt.value,
|
|
265
|
+
label: opt.label,
|
|
266
|
+
})),
|
|
267
|
+
initialValue: selectOption.default,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
if (isCancel(value)) {
|
|
271
|
+
cancel('Operation cancelled.')
|
|
272
|
+
process.exit(0)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
addOnOptions[addOnId][optionName] = value
|
|
276
|
+
}
|
|
277
|
+
// Future option types can be added here
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return addOnOptions
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function selectDeployment(
|
|
286
|
+
framework: Framework,
|
|
287
|
+
deployment?: string,
|
|
288
|
+
): Promise<string | undefined> {
|
|
289
|
+
const deployments = new Set<AddOn>()
|
|
290
|
+
let initialValue: string | undefined = undefined
|
|
291
|
+
for (const addOn of framework
|
|
292
|
+
.getAddOns()
|
|
293
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
294
|
+
if (addOn.type === 'deployment') {
|
|
295
|
+
deployments.add(addOn)
|
|
296
|
+
if (deployment && addOn.id === deployment) {
|
|
297
|
+
return deployment
|
|
298
|
+
}
|
|
299
|
+
if (addOn.default) {
|
|
300
|
+
initialValue = addOn.id
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const dp = await select({
|
|
306
|
+
message: 'Select deployment adapter',
|
|
307
|
+
options: [
|
|
308
|
+
...Array.from(deployments).map((d) => ({
|
|
309
|
+
value: d.id,
|
|
310
|
+
label: d.name,
|
|
311
|
+
})),
|
|
312
|
+
],
|
|
313
|
+
initialValue: initialValue,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
if (isCancel(dp)) {
|
|
317
|
+
cancel('Operation cancelled.')
|
|
318
|
+
process.exit(0)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return dp
|
|
322
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { basename } from 'node:path'
|
|
2
|
+
import validatePackageName from 'validate-npm-package-name'
|
|
3
|
+
import type { TemplateOptions } from './types.js'
|
|
4
|
+
|
|
5
|
+
export function convertTemplateToMode(template: TemplateOptions): string {
|
|
6
|
+
if (template === 'typescript' || template === 'javascript') {
|
|
7
|
+
return 'code-router'
|
|
8
|
+
}
|
|
9
|
+
return 'file-router'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function sanitizePackageName(name: string): string {
|
|
13
|
+
return name
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
16
|
+
.replace(/_/g, '-') // Replace underscores with hyphens
|
|
17
|
+
.replace(/[^a-z0-9-]/g, '') // Remove invalid characters
|
|
18
|
+
.replace(/^[^a-z]+/, '') // Ensure it starts with a letter
|
|
19
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
20
|
+
.replace(/-$/, '') // Remove trailing hyphen
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getCurrentDirectoryName(): string {
|
|
24
|
+
return basename(process.cwd())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validateProjectName(name: string) {
|
|
28
|
+
const { validForNewPackages, validForOldPackages, errors, warnings } =
|
|
29
|
+
validatePackageName(name)
|
|
30
|
+
const error = errors?.[0] || warnings?.[0]
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
valid: validForNewPackages && validForOldPackages,
|
|
34
|
+
error:
|
|
35
|
+
error?.replace(/name/g, 'Project name') ||
|
|
36
|
+
'Project name does not meet npm package naming requirements',
|
|
37
|
+
}
|
|
38
|
+
}
|