UNPKG

7.21 kBJavaScriptView Raw
1'use strict'
2
3// Makes the script crash on unhandled rejections instead of silently
4// ignoring them. In the future, promise rejections that are not handled will
5// terminate the Node.js process with a non-zero exit code.
6process.on('unhandledRejection', err => {
7 throw err
8})
9
10const fs = require('fs-extra')
11const path = require('path')
12const chalk = require('chalk')
13const execSync = require('child_process').execSync
14const { execa } = require('@mara/devkit')
15const os = require('os')
16const { sortObject } = require('../lib/utils')
17// const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup')
18
19const TMPL_VUE = 'project-vue'
20const TMPL_REACT = 'project-react'
21const TMPL_GENERAL = 'project-general'
22const BASE_DIR = 'base'
23const JS_DIR = 'js'
24const TS_DIR = 'ts'
25
26function isInGitRepository() {
27 try {
28 execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
29 return true
30 } catch (e) {
31 return false
32 }
33}
34
35function tryGitInit(appPath) {
36 let didInit = false
37
38 try {
39 execSync('git --version', { stdio: 'ignore' })
40
41 if (isInGitRepository()) return false
42
43 execSync('git init', { stdio: 'ignore' })
44 didInit = true
45
46 execSync('git add -A', { stdio: 'ignore' })
47 execSync('git commit -m "Initial commit from Marauder"', {
48 stdio: 'ignore'
49 })
50
51 return true
52 } catch (e) {
53 if (didInit) {
54 // If we successfully initialized but couldn't commit,
55 // maybe the commit author config is not set.
56 // In the future, we might supply our own committer
57 // like Ember CLI does, but for now, let's just
58 // remove the Git files to avoid a half-done state.
59 try {
60 // unlinkSync() doesn't work on directories.
61 fs.removeSync(path.join(appPath, '.git'))
62 } catch (removeErr) {
63 // Ignore.
64 }
65 }
66
67 return false
68 }
69}
70
71// Display the most elegant way to cd.
72function getCdPath(appName, appPath, originalDirectory) {
73 return path.join(originalDirectory, appName) === appPath ? appName : appPath
74}
75
76function resetGitignore(appPath) {
77 // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
78 // See: https://github.com/npm/npm/issues/1862
79 try {
80 fs.moveSync(
81 path.join(appPath, 'gitignore'),
82 path.join(appPath, '.gitignore'),
83 []
84 )
85 } catch (err) {
86 // Append if there's already a `.gitignore` file there
87 if (err.code === 'EEXIST') {
88 const data = fs.readFileSync(path.join(appPath, 'gitignore'))
89
90 fs.appendFileSync(path.join(appPath, '.gitignore'), data)
91 fs.unlinkSync(path.join(appPath, 'gitignore'))
92 } else {
93 throw err
94 }
95 }
96}
97
98function copyRootFiles(tmplPath, appPath, filterFn = () => true) {
99 fs.copySync(tmplPath, appPath, { filter: filterFn })
100 resetGitignore(appPath)
101}
102
103function sortPackageJsonField(pkg) {
104 // ensure package.json keys has readable order
105 pkg.dependencies = sortObject(pkg.dependencies)
106 pkg.devDependencies = sortObject(pkg.devDependencies)
107 pkg.scripts = sortObject(pkg.scripts, [
108 'serve',
109 'build',
110 'test',
111 'e2e',
112 'lint',
113 'deploy'
114 ])
115 pkg = sortObject(pkg, [
116 'name',
117 'version',
118 'private',
119 'description',
120 'author',
121 'scripts',
122 'dependencies',
123 'devDependencies',
124 'vue',
125 'babel',
126 'eslintConfig',
127 'prettier',
128 'postcss',
129 'browserslist',
130 'jest'
131 ])
132
133 return pkg
134}
135
136function getTmplName(type, depName) {
137 const isComp = type === 'component'
138
139 switch (depName) {
140 case 'react':
141 return TMPL_REACT
142 case 'vue':
143 return TMPL_VUE
144 default:
145 return isComp ? 'component' : TMPL_GENERAL
146 }
147}
148
149function copyFiles(src, dest, pathname = '') {
150 fs.copySync(path.join(src, pathname), dest)
151}
152
153// merge root-files <- project-<dep>/base <- project-<dep>/<js|ts>
154function fillProjectContent({ tmplType, preset, useTs, ownPath, appPath }) {
155 const templateName = getTmplName(tmplType, preset)
156 const templatePath = path.join(ownPath, 'templates', templateName)
157 const rootConfigPath = path.join(ownPath, 'templates/root-files')
158 const copyProjectFiles = copyFiles.bind(null, templatePath, appPath)
159
160 // @TODO project or component
161
162 if (!fs.existsSync(templatePath)) {
163 throw new Error(
164 `Could not locate supplied template: ${chalk.green(templatePath)}`
165 )
166 }
167
168 // 通用共享资源
169 copyRootFiles(rootConfigPath, appPath, src =>
170 useTs ? true : !src.includes('tsconfig.json')
171 )
172
173 // 项目共享资源
174 copyProjectFiles(BASE_DIR)
175
176 // 项目特定资源
177 copyProjectFiles(useTs ? TS_DIR : JS_DIR)
178}
179
180function getGitUserInfo() {
181 try {
182 const { stdout: name } = execa.sync('git', ['config', 'user.name'])
183 const { stdout: email } = execa.sync('git', ['config', 'user.email'])
184
185 return `${name} <${email}>`
186 } catch (e) {
187 return ''
188 }
189}
190
191module.exports = function({
192 appPath,
193 appName,
194 preset,
195 useTs,
196 originalDirectory,
197 tmplType
198}) {
199 const ownPath = path.dirname(
200 require.resolve(path.join(__dirname, '..', 'package.json'))
201 )
202 let appPackage = require(path.join(appPath, 'package.json'))
203 const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'))
204
205 // Copy over some of the devDependencies
206 appPackage.dependencies = appPackage.dependencies || {}
207
208 // Setup the script rules
209 appPackage.scripts = {
210 dev: 'marax dev',
211 build: 'marax build',
212 test: 'marax test'
213 }
214
215 // Setup the eslint config
216 appPackage.eslintConfig = {
217 extends: 'eslint-config-sinamfe'
218 }
219
220 appPackage.author = getGitUserInfo()
221
222 appPackage = sortPackageJsonField(appPackage)
223
224 // 补充 package.json 杂项
225 fs.writeFileSync(
226 path.join(appPath, 'package.json'),
227 JSON.stringify(appPackage, null, 2) + os.EOL
228 )
229
230 const readmeExists = fs.existsSync(path.join(appPath, 'README.md'))
231
232 if (readmeExists) {
233 fs.renameSync(
234 path.join(appPath, 'README.md'),
235 path.join(appPath, 'README.old.md')
236 )
237 }
238
239 fillProjectContent({
240 tmplType: 'project',
241 useTs,
242 preset,
243 ownPath,
244 appPath
245 })
246
247 // if (useTs) {
248 // verifyTypeScriptSetup()
249 // }
250
251 if (tryGitInit(appPath)) {
252 console.log()
253 console.log('Initialized a git repository.')
254 }
255
256 // Change displayed command to yarn instead of yarnpkg
257 const runScript = useYarn ? 'yarn' : 'npm run'
258
259 console.log()
260 console.log(`🎉 Successfully created project ${chalk.yellow(appName)}.`)
261 console.log('👉 Inside that directory, you can run several commands:\n')
262
263 console.log(chalk.cyan(` ${runScript} dev`))
264 console.log(' Starts the development server.\n')
265
266 console.log(chalk.cyan(` ${runScript} build`))
267 console.log(' Bundles the app into static files for production.\n')
268
269 console.log(chalk.cyan(` ${runScript} test`))
270 console.log(' Starts the test runner.\n')
271
272 console.log('👉 Get started with the following commands:\n')
273
274 console.log(
275 chalk.cyan(` cd ${getCdPath(appName, appPath, originalDirectory)}`)
276 )
277 console.log(` ${chalk.cyan(`${runScript} dev`)}\n`)
278
279 if (readmeExists) {
280 console.log(
281 chalk.yellow(
282 'You had a `README.md` file, we renamed it to `README.old.md`\n'
283 )
284 )
285 }
286
287 console.log('😎 Happy hacking!')
288}