@weig_3078/cli-demo 0.1.0

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.
Files changed (48) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc +73 -0
  3. package/README.md +1 -0
  4. package/bin/mvc.js +14 -0
  5. package/dist/mvc.js +135771 -0
  6. package/ejs-demo.js +29 -0
  7. package/lib/ConfigTransform.js +40 -0
  8. package/lib/Creator.js +29 -0
  9. package/lib/Generator.js +307 -0
  10. package/lib/PromptModuleAPI.js +13 -0
  11. package/lib/create.js +328 -0
  12. package/lib/generator/babel/index.js +15 -0
  13. package/lib/generator/linter/index.js +55 -0
  14. package/lib/generator/linter/template/.eslintrc.js +30 -0
  15. package/lib/generator/router/index.js +17 -0
  16. package/lib/generator/router/template/src/App.vue +33 -0
  17. package/lib/generator/router/template/src/router/index.js +30 -0
  18. package/lib/generator/router/template/src/views/About.vue +5 -0
  19. package/lib/generator/router/template/src/views/Home.vue +18 -0
  20. package/lib/generator/vue/index.js +20 -0
  21. package/lib/generator/vue/template/public/favicon.ico +0 -0
  22. package/lib/generator/vue/template/public/index.html +19 -0
  23. package/lib/generator/vue/template/src/App.vue +29 -0
  24. package/lib/generator/vue/template/src/assets/logo.png +0 -0
  25. package/lib/generator/vue/template/src/components/HelloWorld.vue +110 -0
  26. package/lib/generator/vue/template/src/main.js +8 -0
  27. package/lib/generator/vuex/index.js +13 -0
  28. package/lib/generator/vuex/template/src/store/index.js +15 -0
  29. package/lib/generator/webpack/index.js +27 -0
  30. package/lib/generator/webpack/template/build/base.config.js +68 -0
  31. package/lib/generator/webpack/template/build/dev.config.js +19 -0
  32. package/lib/generator/webpack/template/build/pro.config.js +16 -0
  33. package/lib/promptModules/babel.js +10 -0
  34. package/lib/promptModules/linter.js +48 -0
  35. package/lib/promptModules/router.js +19 -0
  36. package/lib/promptModules/vuex.js +8 -0
  37. package/lib/utils/clearConsole.js +13 -0
  38. package/lib/utils/codemods/injectImports.js +31 -0
  39. package/lib/utils/codemods/injectOptions.js +27 -0
  40. package/lib/utils/configTransforms.js +49 -0
  41. package/lib/utils/executeCommand.js +33 -0
  42. package/lib/utils/normalizeFilePaths.js +11 -0
  43. package/lib/utils/sortObject.js +32 -0
  44. package/lib/utils/stringifyJS.js +12 -0
  45. package/lib/utils/writeFileTree.js +12 -0
  46. package/package.json +53 -0
  47. package/rollup.config.cjs +43 -0
  48. package/scripts/verify-commit.js +24 -0
package/ejs-demo.js ADDED
@@ -0,0 +1,29 @@
1
+ let ejs
2
+ try {
3
+ ejs = require('ejs')
4
+ } catch (e) {
5
+ console.error('缺少依赖 ejs,请先安装:npm i ejs')
6
+ process.exit(1)
7
+ }
8
+
9
+ const template = `
10
+ <h1><%= title %></h1>
11
+ <%_ if (isProd) { _%>
12
+ <script>console.log('prod')</script>
13
+ <%_ } else { _%>
14
+ <script>console.log('dev')</script>
15
+ <%_ } _%>
16
+ <ul>
17
+ <%_ items.forEach(i => { _%>
18
+ <li><%= i %></li>
19
+ <%_ }) _%>
20
+ </ul>
21
+ `.trim()
22
+
23
+ const html = ejs.render(template, {
24
+ title: 'EJS Demo',
25
+ isProd: process.env.NODE_ENV === 'dev',
26
+ items: ['a', 'b', 'c'],
27
+ })
28
+
29
+ console.log(html)
@@ -0,0 +1,40 @@
1
+ const transforms = require('./utils/configTransforms')
2
+
3
+ class ConfigTransform {
4
+ constructor(options) {
5
+ this.fileDescriptor = options.file
6
+ }
7
+
8
+ transform(value, context) {
9
+ let file
10
+
11
+ if (!file) {
12
+ file = this.getDefaultFile()
13
+ }
14
+ const { type, filename } = file
15
+
16
+ const transform = transforms[type]
17
+
18
+ let source
19
+
20
+ const content = transform.write({
21
+ source,
22
+ filename,
23
+ context,
24
+ value,
25
+ })
26
+
27
+ return {
28
+ filename,
29
+ content,
30
+ }
31
+ }
32
+
33
+ getDefaultFile() {
34
+ const [type] = Object.keys(this.fileDescriptor)
35
+ const [filename] = this.fileDescriptor[type]
36
+ return { type, filename }
37
+ }
38
+ }
39
+
40
+ module.exports = ConfigTransform
package/lib/Creator.js ADDED
@@ -0,0 +1,29 @@
1
+ class Creator {
2
+ constructor() {
3
+ this.featurePrompt = {
4
+ name: 'features',
5
+ message: 'Check the features needed for your project:',
6
+ pageSize: 10,
7
+ type: 'checkbox',
8
+ choices: [],
9
+ }
10
+
11
+ this.injectedPrompts = []
12
+ }
13
+
14
+ getFinalPrompts() {
15
+ this.injectedPrompts.forEach(prompt => {
16
+ const originalWhen = prompt.when || (() => true)
17
+ prompt.when = answers => originalWhen(answers)
18
+ })
19
+
20
+ const prompts = [
21
+ this.featurePrompt,
22
+ ...this.injectedPrompts,
23
+ ]
24
+
25
+ return prompts
26
+ }
27
+ }
28
+
29
+ module.exports = Creator
@@ -0,0 +1,307 @@
1
+ const fs = require('fs-extra')
2
+ const path = require('path')
3
+ const ejs = require('ejs')
4
+ const sortObject = require('./utils/sortObject')
5
+ const normalizeFilePaths = require('./utils/normalizeFilePaths')
6
+ const { runTransformation } = require('vue-codemod')
7
+ const writeFileTree = require('./utils/writeFileTree')
8
+ const { isBinaryFileSync } = require('isbinaryfile')
9
+ const isObject = (val) => val && typeof val === 'object'
10
+ const ConfigTransform = require('./ConfigTransform')
11
+
12
+ const defaultConfigTransforms = {
13
+ babel: new ConfigTransform({
14
+ file: {
15
+ js: ['babel.config.js'],
16
+ },
17
+ }),
18
+ postcss: new ConfigTransform({
19
+ file: {
20
+ js: ['postcss.config.js'],
21
+ json: ['.postcssrc.json', '.postcssrc'],
22
+ yaml: ['.postcssrc.yaml', '.postcssrc.yml'],
23
+ },
24
+ }),
25
+ eslintConfig: new ConfigTransform({
26
+ file: {
27
+ js: ['.eslintrc.js'],
28
+ json: ['.eslintrc', '.eslintrc.json'],
29
+ yaml: ['.eslintrc.yaml', '.eslintrc.yml'],
30
+ },
31
+ }),
32
+ jest: new ConfigTransform({
33
+ file: {
34
+ js: ['jest.config.js'],
35
+ },
36
+ }),
37
+ browserslist: new ConfigTransform({
38
+ file: {
39
+ lines: ['.browserslistrc'],
40
+ },
41
+ }),
42
+ }
43
+
44
+ const reservedConfigTransforms = {
45
+ vue: new ConfigTransform({
46
+ file: {
47
+ js: ['vue.config.js'],
48
+ },
49
+ }),
50
+ }
51
+
52
+ const ensureEOL = str => {
53
+ if (str.charAt(str.length - 1) !== '\n') {
54
+ return str + '\n'
55
+ }
56
+
57
+ return str
58
+ }
59
+
60
+ class Generator {
61
+ constructor(pkg, context) {
62
+ this.pkg = pkg
63
+ this.rootOptions = {}
64
+ this.imports = {}
65
+ this.files = {}
66
+ this.entryFile = `src/main.js`
67
+ this.fileMiddlewares = []
68
+ this.context = context
69
+ this.configTransforms = {}
70
+ }
71
+
72
+ extendPackage(fields) {
73
+ const pkg = this.pkg
74
+ for (const key in fields) {
75
+ const value = fields[key]
76
+ const existing = pkg[key]
77
+ if (isObject(value) && (key === 'dependencies' || key === 'devDependencies' || key === 'scripts')) {
78
+ pkg[key] = Object.assign(existing || {}, value)
79
+ } else {
80
+ pkg[key] = value
81
+ }
82
+ }
83
+ }
84
+
85
+ async generate(options = {}) {
86
+ // 从 package.json 中提取文件
87
+ this.extractConfigFiles()
88
+ // 解析文件内容
89
+ await this.resolveFiles()
90
+ // 将 package.json 中的字段排序
91
+ this.sortPkg()
92
+ this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
93
+ // 将所有文件写入到用户要创建的目录
94
+ await writeFileTree(this.context, this.files, options)
95
+ }
96
+
97
+ // 按照下面的顺序对 package.json 中的 key 进行排序
98
+ sortPkg() {
99
+ // ensure package.json keys has readable order
100
+ this.pkg.dependencies = sortObject(this.pkg.dependencies)
101
+ this.pkg.devDependencies = sortObject(this.pkg.devDependencies)
102
+ this.pkg.scripts = sortObject(this.pkg.scripts, [
103
+ 'dev',
104
+ 'build',
105
+ 'test:unit',
106
+ 'test:e2e',
107
+ 'lint',
108
+ 'deploy',
109
+ ])
110
+
111
+ this.pkg = sortObject(this.pkg, [
112
+ 'name',
113
+ 'version',
114
+ 'private',
115
+ 'description',
116
+ 'author',
117
+ 'scripts',
118
+ 'husky',
119
+ 'lint-staged',
120
+ 'main',
121
+ 'module',
122
+ 'browser',
123
+ 'jsDelivr',
124
+ 'unpkg',
125
+ 'files',
126
+ 'dependencies',
127
+ 'devDependencies',
128
+ 'peerDependencies',
129
+ 'vue',
130
+ 'babel',
131
+ 'eslintConfig',
132
+ 'prettier',
133
+ 'postcss',
134
+ 'browserslist',
135
+ 'jest',
136
+ ])
137
+ }
138
+
139
+ // 使用 ejs 解析 lib\generator\xx\template 中的文件
140
+ async resolveFiles() {
141
+ const files = this.files
142
+ for (const middleware of this.fileMiddlewares) {
143
+ await middleware(files, ejs.render)
144
+ }
145
+
146
+ // normalize file paths on windows
147
+ // all paths are converted to use / instead of \
148
+ // 将反斜杠 \ 转换为正斜杠 /
149
+ normalizeFilePaths(files)
150
+
151
+ // 处理 import 语句的导入和 new Vue() 选项的注入
152
+ // vue-codemod 库,对代码进行解析得到 AST,再将 import 语句和根选项注入
153
+ Object.keys(files).forEach(file => {
154
+ let imports = this.imports[file]
155
+ imports = imports instanceof Set ? Array.from(imports) : imports
156
+ if (imports && imports.length > 0) {
157
+ files[file] = runTransformation(
158
+ { path: file, source: files[file] },
159
+ require('./utils/codemods/injectImports'),
160
+ { imports },
161
+ )
162
+ }
163
+
164
+ let injections = this.rootOptions[file]
165
+ injections = injections instanceof Set ? Array.from(injections) : injections
166
+ if (injections && injections.length > 0) {
167
+ files[file] = runTransformation(
168
+ { path: file, source: files[file] },
169
+ require('./utils/codemods/injectOptions'),
170
+ { injections },
171
+ )
172
+ }
173
+ })
174
+ }
175
+
176
+ // 将 package.json 中的配置提取出来,生成单独的文件
177
+ // 例如将 package.json 中的
178
+ // babel: {
179
+ // presets: ['@babel/preset-env']
180
+ // },
181
+ // 提取出来变成 babel.config.js 文件
182
+ extractConfigFiles() {
183
+ const configTransforms = {
184
+ ...defaultConfigTransforms,
185
+ ...this.configTransforms,
186
+ ...reservedConfigTransforms,
187
+ }
188
+
189
+ const extract = key => {
190
+ if (configTransforms[key] && this.pkg[key]) {
191
+ const value = this.pkg[key]
192
+ const configTransform = configTransforms[key]
193
+ const res = configTransform.transform(
194
+ value,
195
+ this.files,
196
+ this.context,
197
+ )
198
+
199
+ const { content, filename } = res
200
+ // 如果文件不是以 \n 结尾,则补上 \n
201
+ this.files[filename] = ensureEOL(content)
202
+ delete this.pkg[key]
203
+ }
204
+ }
205
+
206
+ extract('vue')
207
+ extract('babel')
208
+ }
209
+
210
+ render(source, additionalData = {}, ejsOptions = {}) {
211
+ // 获取调用 generator.render() 函数的文件的父目录路径
212
+ const baseDir = extractCallDir()
213
+ source = path.resolve(baseDir, source)
214
+ this._injectFileMiddleware(async (files) => {
215
+ const data = this._resolveData(additionalData)
216
+ // https://github.com/sindresorhus/globby
217
+ const globby = require('globby')
218
+ // 读取目录中所有的文件
219
+ const _files = await globby(['**/*'], { cwd: source, dot: true })
220
+ for (const rawPath of _files) {
221
+ const sourcePath = path.resolve(source, rawPath)
222
+ // 解析文件内容
223
+ const content = this.renderFile(sourcePath, data, ejsOptions)
224
+ // only set file if it's not all whitespace, or is a Buffer (binary files)
225
+ if (Buffer.isBuffer(content) || /[^\s]/.test(content)) {
226
+ files[rawPath] = content
227
+ }
228
+ }
229
+ })
230
+ }
231
+
232
+ _injectFileMiddleware(middleware) {
233
+ this.fileMiddlewares.push(middleware)
234
+ }
235
+
236
+ // 合并选项
237
+ _resolveData(additionalData) {
238
+ return {
239
+ options: this.options,
240
+ rootOptions: this.rootOptions,
241
+ ...additionalData,
242
+ }
243
+ }
244
+
245
+ renderFile(name, data, ejsOptions) {
246
+ // 如果是二进制文件,直接将读取结果返回
247
+ if (isBinaryFileSync(name)) {
248
+ return fs.readFileSync(name) // return buffer
249
+ }
250
+
251
+ // 返回文件内容
252
+ const template = fs.readFileSync(name, 'utf-8')
253
+ return ejs.render(template, data, ejsOptions)
254
+ }
255
+
256
+ /**
257
+ * Add import statements to a file.
258
+ */
259
+ injectImports(file, imports) {
260
+ const _imports = (
261
+ this.imports[file]
262
+ || (this.imports[file] = new Set())
263
+ );
264
+ (Array.isArray(imports) ? imports : [imports]).forEach(imp => {
265
+ _imports.add(imp)
266
+ })
267
+ }
268
+
269
+ /**
270
+ * Add options to the root Vue instance (detected by `new Vue`).
271
+ */
272
+ injectRootOptions(file, options) {
273
+ const _options = (
274
+ this.rootOptions[file]
275
+ || (this.rootOptions[file] = new Set())
276
+ );
277
+ (Array.isArray(options) ? options : [options]).forEach(opt => {
278
+ _options.add(opt)
279
+ })
280
+ }
281
+ }
282
+
283
+ // http://blog.shaochuancs.com/about-error-capturestacktrace/
284
+ // 获取调用栈信息
285
+ function extractCallDir() {
286
+ const obj = {}
287
+ Error.captureStackTrace(obj)
288
+ // 在 lib\generator\xx 等各个模块中 调用 generator.render()
289
+ // 将会排在调用栈中的第四个,也就是 obj.stack.split('\n')[3]
290
+ const callSite = obj.stack.split('\n')[3]
291
+
292
+ // the regexp for the stack when called inside a named function
293
+ const namedStackRegExp = /\s\((.*):\d+:\d+\)$/
294
+ // the regexp for the stack when called inside an anonymous
295
+ const anonymousStackRegExp = /at (.*):\d+:\d+$/
296
+
297
+ let matchResult = callSite.match(namedStackRegExp)
298
+ if (!matchResult) {
299
+ matchResult = callSite.match(anonymousStackRegExp)
300
+ }
301
+
302
+ const fileName = matchResult[1]
303
+ // 获取对应文件的目录
304
+ return path.dirname(fileName)
305
+ }
306
+
307
+ module.exports = Generator
@@ -0,0 +1,13 @@
1
+ module.exports = class PromptModuleAPI {
2
+ constructor(creator) {
3
+ this.creator = creator
4
+ }
5
+
6
+ injectFeature(feature) {
7
+ this.creator.featurePrompt.choices.push(feature)
8
+ }
9
+
10
+ injectPrompt(prompt) {
11
+ this.creator.injectedPrompts.push(prompt)
12
+ }
13
+ }