返回 slidev
index.mjs
根目录 / packages / create-theme / index.mjs
1 #!/usr/bin/env node
2 /* eslint-disable no-console */
3
4 import fs from 'node:fs'
5 import { createRequire } from 'node:module'
6 // @ts-check
7 import process from 'node:process'
8 import { fileURLToPath } from 'node:url'
9 import { blue, bold, cyan, dim, green, yellow } from 'ansis'
10 import minimist from 'minimist'
11 import path from 'pathe'
12 import prompts from 'prompts'
13
14 const argv = minimist(process.argv.slice(2))
15 const cwd = process.cwd()
16 const require = createRequire(import.meta.url)
17 const __dirname = fileURLToPath(new URL('.', import.meta.url))
18 const { version } = require('./package.json')
19
20 const RE_PNPM = /pnpm/
21 const RE_YARN = /yarn/
22 const RE_SLIDEV_THEME_PREFIX = /^slidev-theme-/
23
24 const renameFiles = {
25 _gitignore: '.gitignore',
26 _npmrc: '.npmrc',
27 }
28
29 async function init() {
30 console.log()
31 console.log(` ${cyan('●') + blue('■') + yellow('▲')}`)
32 console.log(`${bold(' Slidev') + dim(' Theme Creator')} ${blue(`v${version}`)}`)
33 console.log()
34
35 let targetDir = argv._[0]
36 if (!targetDir) {
37 /**
38 * @type {{ name: string }}
39 */
40 const { name } = await prompts({
41 type: 'text',
42 name: 'name',
43 message: 'Theme name:',
44 initial: 'slidev-theme-starter',
45 })
46 targetDir = name
47 }
48 const packageName = await getValidPackageName(targetDir)
49 const root = path.join(cwd, targetDir)
50
51 if (!fs.existsSync(root)) {
52 fs.mkdirSync(root, { recursive: true })
53 }
54 else {
55 const existing = fs.readdirSync(root)
56 if (existing.length) {
57 console.log(yellow(` Target directory "${targetDir}" is not empty.`))
58 /**
59 * @type {{ yes: boolean }}
60 */
61 const { yes } = await prompts({
62 type: 'confirm',
63 name: 'yes',
64 initial: 'Y',
65 message: 'Remove existing files and continue?',
66 })
67 if (yes)
68 emptyDir(root)
69
70 else
71 return
72 }
73 }
74
75 console.log(dim(' Scaffolding Slidev theme in ') + targetDir + dim(' ...'))
76
77 prepareTemplate(root, path.join(__dirname, 'template'), packageName)
78
79 const pkgManager = (RE_PNPM.test(process.env.npm_execpath) || RE_PNPM.test(process.env.npm_config_user_agent))
80 ? 'pnpm'
81 : RE_YARN.test(process.env.npm_execpath) ? 'yarn' : 'npm'
82
83 const related = path.relative(cwd, root)
84
85 console.log(green(' Done.\n'))
86
87 console.log(dim('\n start it by:\n'))
88 if (root !== cwd)
89 console.log(blue(` cd ${bold(related)}`))
90
91 console.log(blue(` ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`))
92 console.log(blue(` ${pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`}`))
93 console.log()
94 console.log(` ${cyan('●')} ${blue('■')} ${yellow('▲')}`)
95 console.log()
96 }
97
98 function prepareTemplate(root, templateDir, packageName) {
99 const write = (file, content) => {
100 const targetPath = renameFiles[file]
101 ? path.join(root, renameFiles[file])
102 : path.join(root, file)
103 if (content)
104 fs.writeFileSync(targetPath, content)
105
106 else
107 copy(path.join(templateDir, file), targetPath)
108 }
109
110 const files = fs.readdirSync(templateDir)
111 for (const file of files.filter(f => !['package.json', 'README.md'].includes(f)))
112 write(file)
113
114 write(
115 'package.json',
116 JSON.stringify({
117 name: packageName,
118 version: '0.0.0',
119 ...require(path.join(templateDir, 'package.json')),
120 }, null, 2),
121 )
122
123 write(
124 'README.md',
125 fs
126 .readFileSync(path.join(templateDir, 'README.md'), 'utf-8')
127 .replace(/\{\{package-name\}\}/g, packageName)
128 .replace(/\{\{name\}\}/g, getThemeName(packageName)),
129 )
130 }
131
132 function copy(src, dest) {
133 const stat = fs.statSync(src)
134 if (stat.isDirectory())
135 copyDir(src, dest)
136
137 else
138 fs.copyFileSync(src, dest)
139 }
140
141 function getValidPackageName(projectName) {
142 projectName = path.basename(projectName)
143 if (!projectName.startsWith('slidev-theme-'))
144 return `slidev-theme-${projectName}`
145 return projectName
146 }
147
148 function getThemeName(pkgName) {
149 return pkgName.replace(RE_SLIDEV_THEME_PREFIX, '')
150 }
151
152 function copyDir(srcDir, destDir) {
153 fs.mkdirSync(destDir, { recursive: true })
154 for (const file of fs.readdirSync(srcDir)) {
155 const srcFile = path.resolve(srcDir, file)
156 const destFile = path.resolve(destDir, file)
157 copy(srcFile, destFile)
158 }
159 }
160
161 function emptyDir(dir) {
162 if (!fs.existsSync(dir))
163 return
164
165 for (const file of fs.readdirSync(dir)) {
166 const abs = path.resolve(dir, file)
167 // baseline is Node 12 so can't use rmSync :(
168 if (fs.lstatSync(abs).isDirectory()) {
169 emptyDir(abs)
170 fs.rmdirSync(abs)
171 }
172 else {
173 fs.unlinkSync(abs)
174 }
175 }
176 }
177
178 init().catch((e) => {
179 console.error(e)
180 })
181
181 lines Plain Text