Skip to content

Commit 61bcc38

Browse files
committed
feat: 重构模板并添加新功能
- 移除旧的 JavaScript 和 TypeScript 模板 - 新增 minimal 和 preact 模板 - 添加 gradient-string 依赖用于标题渲染 - 改进 CLI 交互流程,增加 git 初始化和镜像源选项 - 更新 README 文档和项目结构
1 parent 2ab8f71 commit 61bcc38

File tree

35 files changed

+678
-159
lines changed

35 files changed

+678
-159
lines changed

packages/create-tona/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
},
1010
"files": [
1111
"index.js",
12-
"template-js",
13-
"template-ts",
12+
"template-minimal",
13+
"template-preact",
1414
"dist"
1515
],
1616
"scripts": {
@@ -35,10 +35,14 @@
3535
"devDependencies": {
3636
"@clack/prompts": "catalog:",
3737
"@types/cross-spawn": "catalog:",
38+
"@types/gradient-string": "^1.1.6",
3839
"@types/node": "catalog:",
3940
"cross-spawn": "catalog:",
4041
"mri": "catalog:",
4142
"picocolors": "catalog:",
4243
"tsdown": "catalog:"
44+
},
45+
"dependencies": {
46+
"gradient-string": "^3.0.0"
4347
}
4448
}

packages/create-tona/src/consts.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import path from 'node:path'
2+
import { fileURLToPath } from 'node:url'
3+
4+
const __filename = fileURLToPath(import.meta.url)
5+
const distPath = path.dirname(__filename)
6+
export const PKG_ROOT = path.join(distPath, '../')
7+
8+
export const TITLE_TEXT = `
9+
_____ ___ _ _ _
10+
|_ _/ _ \\| \\ | | / \\
11+
| || | | | \\| | / _ \\
12+
| || |_| | |\\ |/ ___ \\
13+
|_| \\___/|_| \\_/_/ \\_\\
14+
`
15+
16+
export const DEFAULT_APP_NAME = 'tona-theme'
17+
export const CREATE_TONA_APP = 'create-tona'

packages/create-tona/src/index.ts

Lines changed: 149 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@ import spawn from 'cross-spawn'
77
import mri from 'mri'
88
import colors from 'picocolors'
99

10-
const { blue, yellow } = colors
10+
import { DEFAULT_APP_NAME } from './consts.js'
11+
import { renderTitle } from './utils/renderTitle.js'
12+
13+
const { blue, yellow, red, cyan } = colors
1114

1215
const argv = mri<{
1316
template?: string
1417
help?: boolean
1518
overwrite?: boolean
1619
immediate?: boolean
1720
interactive?: boolean
21+
git?: boolean
22+
install?: boolean
1823
}>(process.argv.slice(2), {
19-
boolean: ['help', 'overwrite', 'immediate', 'interactive'],
20-
alias: { h: 'help', t: 'template', i: 'immediate' },
24+
boolean: ['help', 'overwrite', 'immediate', 'interactive', 'git', 'install'],
25+
alias: { h: 'help', t: 'template', i: 'immediate', g: 'git' },
2126
string: ['template'],
2227
})
2328
const cwd = process.cwd()
@@ -26,26 +31,24 @@ const cwd = process.cwd()
2631
const helpMessage = `\
2732
Usage: create-tona [OPTION]... [DIRECTORY]
2833
29-
Create a new Tona theme project in JavaScript or TypeScript.
34+
Create a new Tona theme project with TypeScript.
3035
When running in TTY, the CLI will start in interactive mode.
3136
3237
Options:
33-
-t, --template NAME use a specific template (js or ts)
38+
-t, --template NAME use a specific template (minimal or preact)
3439
-i, --immediate install dependencies and start dev
40+
-g, --git initialize git repository and stage changes
41+
--install install dependencies only
3542
--interactive / --no-interactive force interactive / non-interactive mode
3643
3744
Available templates:
38-
${blue('ts TypeScript')}
39-
${yellow('js JavaScript')}`
40-
41-
const TEMPLATES = ['js', 'ts']
45+
${cyan('preact Preact + TypeScript (recommended)')}
46+
${blue('minimal Minimal TypeScript')}`
4247

4348
const renameFiles: Record<string, string | undefined> = {
4449
_gitignore: '.gitignore',
4550
}
4651

47-
const defaultTargetDir = 'tona-theme'
48-
4952
function run([command, ...args]: string[], options?: SpawnOptions) {
5053
const { status, error } = spawn.sync(command, args, options)
5154
if (status != null && status > 0) {
@@ -59,15 +62,16 @@ function run([command, ...args]: string[], options?: SpawnOptions) {
5962
}
6063
}
6164

62-
function install(root: string, agent: string) {
65+
function install(root: string, agent: string, registry?: string) {
6366
if (process.env._VITE_TEST_CLI) {
6467
prompts.log.step(
6568
`Installing dependencies with ${agent}... (skipped in test)`,
6669
)
6770
return
6871
}
69-
prompts.log.step(`Installing dependencies with ${agent}...`)
70-
run(getInstallCommand(agent), {
72+
const registryInfo = registry ? ` (${registry})` : ''
73+
prompts.log.step(`Installing dependencies with ${agent}${registryInfo}...`)
74+
run(getInstallCommand(agent, registry), {
7175
stdio: 'inherit',
7276
cwd: root,
7377
})
@@ -86,13 +90,17 @@ function start(root: string, agent: string) {
8690
}
8791

8892
async function init() {
93+
renderTitle()
94+
8995
const argTargetDir = argv._[0]
9096
? formatTargetDir(String(argv._[0]))
9197
: undefined
9298
const argTemplate = argv.template
9399
const argOverwrite = argv.overwrite
94100
const argImmediate = argv.immediate
95101
const argInteractive = argv.interactive
102+
const argGit = argv.git
103+
const argInstall = argv.install
96104

97105
const help = argv.help
98106
if (help) {
@@ -111,9 +119,10 @@ async function init() {
111119
if (interactive) {
112120
const projectName = await prompts.text({
113121
message: 'Project name:',
114-
defaultValue: defaultTargetDir,
115-
placeholder: defaultTargetDir,
122+
defaultValue: DEFAULT_APP_NAME,
123+
placeholder: DEFAULT_APP_NAME,
116124
validate: (value) => {
125+
if (!value) return 'Invalid project name'
117126
return value.length === 0 || formatTargetDir(value).length > 0
118127
? undefined
119128
: 'Invalid project name'
@@ -122,7 +131,7 @@ async function init() {
122131
if (prompts.isCancel(projectName)) return cancel()
123132
targetDir = formatTargetDir(projectName)
124133
} else {
125-
targetDir = defaultTargetDir
134+
targetDir = DEFAULT_APP_NAME
126135
}
127136
}
128137

@@ -178,7 +187,7 @@ async function init() {
178187
defaultValue: toValidPackageName(packageName),
179188
placeholder: toValidPackageName(packageName),
180189
validate(dir) {
181-
if (!isValidPackageName(dir)) {
190+
if (!dir || !isValidPackageName(dir)) {
182191
return 'Invalid package.json name'
183192
}
184193
},
@@ -190,10 +199,32 @@ async function init() {
190199
}
191200
}
192201

193-
// 4. Choose template
202+
// 4. Choose language (typescript/javascript) - javascript auto-switches to typescript
203+
if (interactive) {
204+
const languageResult = await prompts.select({
205+
message: 'Select language:',
206+
options: [
207+
{
208+
label: blue('TypeScript (recommended)'),
209+
value: 'ts',
210+
},
211+
{
212+
label: yellow('JavaScript'),
213+
value: 'js',
214+
},
215+
],
216+
})
217+
if (prompts.isCancel(languageResult)) return cancel()
218+
219+
if (languageResult === 'js') {
220+
prompts.log.warn(red('Wrong answer, using TypeScript instead'))
221+
}
222+
}
223+
224+
// 5. Choose template
194225
let template = argTemplate
195226
let hasInvalidArgTemplate = false
196-
if (argTemplate && !TEMPLATES.includes(argTemplate)) {
227+
if (argTemplate && !['minimal', 'preact'].includes(argTemplate)) {
197228
template = undefined
198229
hasInvalidArgTemplate = true
199230
}
@@ -205,35 +236,89 @@ async function init() {
205236
: 'Select a template:',
206237
options: [
207238
{
208-
label: blue('TypeScript'),
209-
value: 'ts',
239+
label: cyan('preact (recommended)'),
240+
value: 'preact',
210241
},
211242
{
212-
label: yellow('JavaScript'),
213-
value: 'js',
243+
label: blue('minimal'),
244+
value: 'minimal',
214245
},
215246
],
216247
})
217248
if (prompts.isCancel(templateResult)) return cancel()
218249
template = templateResult
219250
} else {
220-
template = 'ts'
251+
template = 'minimal'
221252
}
222253
}
223254

224-
const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
255+
let pkgManager = pkgInfo ? pkgInfo.name : 'npm'
256+
257+
const hasPnpm =
258+
spawn.sync('pnpm', ['--version'], { stdio: 'ignore' }).status === 0
259+
if (hasPnpm) {
260+
pkgManager = 'pnpm'
261+
}
225262

226-
// 5. Ask about immediate install and package manager
227-
let immediate = argImmediate
228-
if (immediate === undefined) {
263+
// 6. Ask about git initialization
264+
let initGit = argGit
265+
if (initGit === undefined) {
229266
if (interactive) {
230-
const immediateResult = await prompts.confirm({
231-
message: `Install with ${pkgManager} and start now?`,
267+
const gitResult = await prompts.confirm({
268+
message: 'Initialize a Git repository and stage the changes?',
232269
})
233-
if (prompts.isCancel(immediateResult)) return cancel()
234-
immediate = immediateResult
270+
if (prompts.isCancel(gitResult)) return cancel()
271+
initGit = gitResult
235272
} else {
236-
immediate = false
273+
initGit = false
274+
}
275+
}
276+
277+
// 7. Ask about installing dependencies
278+
let registry: string | undefined
279+
let shouldInstall = argInstall || argImmediate
280+
if (!shouldInstall) {
281+
if (interactive) {
282+
const installResult = await prompts.select({
283+
message: 'Install dependencies?',
284+
options: [
285+
{
286+
label: cyan('Yes, via Taobao mirror (recommended)'),
287+
value: 'taobao',
288+
},
289+
{
290+
label: blue('Yes, via official registry'),
291+
value: 'official',
292+
},
293+
{
294+
label: 'No',
295+
value: 'no',
296+
},
297+
],
298+
})
299+
if (prompts.isCancel(installResult)) return cancel()
300+
if (installResult === 'no') {
301+
shouldInstall = false
302+
} else {
303+
shouldInstall = true
304+
registry = installResult === 'taobao' ? 'taobao' : undefined
305+
}
306+
} else {
307+
shouldInstall = false
308+
}
309+
}
310+
311+
// 8. Ask about starting dev server
312+
let shouldStart = argImmediate
313+
if (shouldStart === undefined && shouldInstall) {
314+
if (interactive) {
315+
const startResult = await prompts.confirm({
316+
message: 'Start dev server?',
317+
})
318+
if (prompts.isCancel(startResult)) return cancel()
319+
shouldStart = startResult
320+
} else {
321+
shouldStart = false
237322
}
238323
}
239324

@@ -278,9 +363,25 @@ async function init() {
278363

279364
write('package.json', JSON.stringify(pkg, null, 2) + '\n')
280365

281-
if (immediate) {
282-
install(root, pkgManager)
283-
start(root, pkgManager)
366+
if (initGit) {
367+
prompts.log.step('Initializing Git repository...')
368+
run(['git', 'init', '-b', 'main'], { stdio: 'inherit', cwd: root })
369+
run(['git', 'add', '.'], { stdio: 'inherit', cwd: root })
370+
}
371+
372+
if (shouldInstall) {
373+
install(root, pkgManager, registry)
374+
if (shouldStart) {
375+
start(root, pkgManager)
376+
} else {
377+
const cdProjectName = path.relative(cwd, root)
378+
let doneMessage = `Done. Now run:\n`
379+
if (root !== cwd) {
380+
doneMessage += `\n cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`
381+
}
382+
doneMessage += `\n ${getRunCommand(pkgManager, 'dev').join(' ')}`
383+
prompts.outro(doneMessage)
384+
}
284385
} else {
285386
let doneMessage = ''
286387
const cdProjectName = path.relative(cwd, root)
@@ -365,10 +466,20 @@ function pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined {
365466
}
366467
}
367468

368-
function getInstallCommand(agent: string) {
469+
function getInstallCommand(agent: string, registry?: string) {
470+
const registryUrl =
471+
registry === 'taobao' ? 'https://registry.npmmirror.com' : undefined
472+
369473
if (agent === 'yarn') {
474+
if (registryUrl) {
475+
return [agent, '--registry', registryUrl]
476+
}
370477
return [agent]
371478
}
479+
480+
if (registryUrl) {
481+
return [agent, 'install', '--registry', registryUrl]
482+
}
372483
return [agent, 'install']
373484
}
374485

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import gradient from 'gradient-string'
2+
3+
import { TITLE_TEXT } from '../consts.js'
4+
5+
const tonaTheme = {
6+
blue: '#add7ff',
7+
cyan: '#89ddff',
8+
green: '#5de4c7',
9+
magenta: '#fae4fc',
10+
red: '#d0679d',
11+
yellow: '#fffac2',
12+
}
13+
14+
export function renderTitle() {
15+
const tonaGradient = gradient(Object.values(tonaTheme))
16+
console.log(tonaGradient.multiline(TITLE_TEXT))
17+
}

0 commit comments

Comments
 (0)