UNPKG

create-base

Version:

快速构建项目基础框架,支持vue/uni-app/后台管理模板等

258 lines (226 loc) 6.9 kB
#!/usr/bin/env node const fs = require('fs') const path = require('path') // Avoids autoconversion to number of the project name by defining that the args // non associated with an option ( _ ) needs to be parsed as a string. const argv = require('minimist')(process.argv.slice(2), { string: ['_'] }) const prompts = require('prompts') const { green, yellow, blue, gray, cyan, magenta, red, reset } = require('kolorist') const cwd = process.cwd() const FRAMEWORKS = [ { name: 'vue', color: green, variants: [ // { name: 'vue2', color: gray }, // { name: 'vue', color: cyan }, { name: 'vue-ts', color: magenta } ] }, { name: 'uniapp', color: yellow, variants: [ // { name: 'uniapp-vue2', color: gray }, // { name: 'uniapp', color: cyan }, { name: 'uniapp-ts', color: magenta } ] } // { // name: 'admin', // color: blue, // variants: [{ name: 'admin-element', color: gray }] // } ] const TEMPLATES = FRAMEWORKS.map(f => (f.variants && f.variants.map(v => v.name)) || [f.name]).reduce( (a, b) => a.concat(b), [] ) const renameFiles = { _gitignore: '.gitignore' } // 需要过滤的目录/文件名 const filterFiles = ['package.json', 'node_modules'] async function init() { let targetDir = argv._[0] let template = argv.template || argv.t const defaultProjectName = !targetDir ? 'base-project' : targetDir let result = {} try { result = await prompts( [ { type: targetDir ? null : 'text', name: 'projectName', message: reset('项目名称'), initial: defaultProjectName, onState: state => (targetDir = state.value.trim() || defaultProjectName) }, { type: () => (!fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm'), name: 'overwrite', message: () => (targetDir === '.' ? '当前目录' : `目标目录 "${targetDir}"`) + ' 不是空的。是否删除现有文件并继续?' }, { type: (_, { overwrite } = {}) => { if (overwrite === false) { throw new Error(red('✖') + ' 操作取消') } return null }, name: 'overwriteChecker' }, { type: () => (isValidPackageName(targetDir) ? null : 'text'), name: 'packageName', message: reset('包名'), initial: () => toValidPackageName(targetDir), validate: dir => isValidPackageName(dir) || '无效的 package.json 名称' }, { type: template && TEMPLATES.includes(template) ? null : 'select', name: 'framework', message: typeof template === 'string' && !TEMPLATES.includes(template) ? reset(`"${template}" 不是有效的模板。请从下面选择`) : reset('选择一个框架'), initial: 0, choices: FRAMEWORKS.map(framework => { const frameworkColor = framework.color return { title: frameworkColor(framework.name), value: framework } }) }, { type: framework => (framework && framework.variants ? 'select' : null), name: 'variant', message: reset('选择一个版本'), choices: framework => framework.variants.map(variant => { const variantColor = variant.color return { title: variantColor(variant.name), value: variant.name } }) } ], { onCancel: () => { throw new Error(red('✖') + ' 操作取消') } } ) } catch (cancelled) { console.log(cancelled.message) return } // user choice associated with prompts const { framework, overwrite, packageName, variant } = result const root = path.join(cwd, targetDir) if (overwrite) { emptyDir(root) } else if (!fs.existsSync(root)) { fs.mkdirSync(root) } // determine template template = variant || framework || template console.log(`\n正在拉取项目文件到 ${root}...`) const templateDir = path.join(__dirname, `template-${template}`) const write = (file, content) => { const targetPath = renameFiles[file] ? path.join(root, renameFiles[file]) : path.join(root, file) if (content) { fs.writeFileSync(targetPath, content) } else { copy(path.join(templateDir, file), targetPath) } } const files = fs.readdirSync(templateDir) for (const file of files.filter(f => !filterFiles.includes(f))) { write(file) } const pkg = require(path.join(templateDir, `package.json`)) pkg.name = packageName || targetDir write('package.json', JSON.stringify(pkg, null, 2)) const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent) const pkgManager = pkgInfo ? pkgInfo.name : 'npm' console.log(`\n完成!现在运行下面命令\n`) if (root !== cwd) { console.log(` cd ${path.relative(cwd, root)}`) } switch (pkgManager) { case 'yarn': console.log(' yarn') console.log(' yarn dev') break default: console.log(` ${pkgManager} install`) console.log(` ${pkgManager} run dev`) break } console.log() } function copy(src, dest) { const stat = fs.statSync(src) if (stat.isDirectory()) { copyDir(src, dest) } else { fs.copyFileSync(src, dest) } } function isValidPackageName(projectName) { return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName) } function toValidPackageName(projectName) { return projectName .trim() .toLowerCase() .replace(/\s+/g, '-') .replace(/^[._]/, '') .replace(/[^a-z0-9-~]+/g, '-') } function copyDir(srcDir, destDir) { fs.mkdirSync(destDir, { recursive: true }) for (const file of fs.readdirSync(srcDir)) { const srcFile = path.resolve(srcDir, file) const destFile = path.resolve(destDir, file) copy(srcFile, destFile) } } function isEmpty(path) { return fs.readdirSync(path).length === 0 } function emptyDir(dir) { if (!fs.existsSync(dir)) { return } for (const file of fs.readdirSync(dir)) { const abs = path.resolve(dir, file) // baseline is Node 12 so can't use rmSync :( if (fs.lstatSync(abs).isDirectory()) { emptyDir(abs) fs.rmdirSync(abs) } else { fs.unlinkSync(abs) } } } /** * @param {string | undefined} userAgent process.env.npm_config_user_agent * @returns object | undefined */ function pkgFromUserAgent(userAgent) { if (!userAgent) return undefined const pkgSpec = userAgent.split(' ')[0] const pkgSpecArr = pkgSpec.split('/') return { name: pkgSpecArr[0], version: pkgSpecArr[1] } } init().catch(e => { console.error(e) })