@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.
- package/.eslintignore +1 -0
- package/.eslintrc +73 -0
- package/README.md +1 -0
- package/bin/mvc.js +14 -0
- package/dist/mvc.js +135771 -0
- package/ejs-demo.js +29 -0
- package/lib/ConfigTransform.js +40 -0
- package/lib/Creator.js +29 -0
- package/lib/Generator.js +307 -0
- package/lib/PromptModuleAPI.js +13 -0
- package/lib/create.js +328 -0
- package/lib/generator/babel/index.js +15 -0
- package/lib/generator/linter/index.js +55 -0
- package/lib/generator/linter/template/.eslintrc.js +30 -0
- package/lib/generator/router/index.js +17 -0
- package/lib/generator/router/template/src/App.vue +33 -0
- package/lib/generator/router/template/src/router/index.js +30 -0
- package/lib/generator/router/template/src/views/About.vue +5 -0
- package/lib/generator/router/template/src/views/Home.vue +18 -0
- package/lib/generator/vue/index.js +20 -0
- package/lib/generator/vue/template/public/favicon.ico +0 -0
- package/lib/generator/vue/template/public/index.html +19 -0
- package/lib/generator/vue/template/src/App.vue +29 -0
- package/lib/generator/vue/template/src/assets/logo.png +0 -0
- package/lib/generator/vue/template/src/components/HelloWorld.vue +110 -0
- package/lib/generator/vue/template/src/main.js +8 -0
- package/lib/generator/vuex/index.js +13 -0
- package/lib/generator/vuex/template/src/store/index.js +15 -0
- package/lib/generator/webpack/index.js +27 -0
- package/lib/generator/webpack/template/build/base.config.js +68 -0
- package/lib/generator/webpack/template/build/dev.config.js +19 -0
- package/lib/generator/webpack/template/build/pro.config.js +16 -0
- package/lib/promptModules/babel.js +10 -0
- package/lib/promptModules/linter.js +48 -0
- package/lib/promptModules/router.js +19 -0
- package/lib/promptModules/vuex.js +8 -0
- package/lib/utils/clearConsole.js +13 -0
- package/lib/utils/codemods/injectImports.js +31 -0
- package/lib/utils/codemods/injectOptions.js +27 -0
- package/lib/utils/configTransforms.js +49 -0
- package/lib/utils/executeCommand.js +33 -0
- package/lib/utils/normalizeFilePaths.js +11 -0
- package/lib/utils/sortObject.js +32 -0
- package/lib/utils/stringifyJS.js +12 -0
- package/lib/utils/writeFileTree.js +12 -0
- package/package.json +53 -0
- package/rollup.config.cjs +43 -0
- package/scripts/verify-commit.js +24 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="hello">
|
|
3
|
+
<h1>{{ msg }}</h1>
|
|
4
|
+
<p>
|
|
5
|
+
For a guide and recipes on how to configure / customize this
|
|
6
|
+
project,<br />
|
|
7
|
+
check out the
|
|
8
|
+
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
|
9
|
+
>vue-cli documentation</a
|
|
10
|
+
>.
|
|
11
|
+
</p>
|
|
12
|
+
<h3>Essential Links</h3>
|
|
13
|
+
<ul>
|
|
14
|
+
<li>
|
|
15
|
+
<a href="https://vuejs.org" target="_blank" rel="noopener"
|
|
16
|
+
>Core Docs</a
|
|
17
|
+
>
|
|
18
|
+
</li>
|
|
19
|
+
<li>
|
|
20
|
+
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
|
21
|
+
>Forum</a
|
|
22
|
+
>
|
|
23
|
+
</li>
|
|
24
|
+
<li>
|
|
25
|
+
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
|
26
|
+
>Community Chat</a
|
|
27
|
+
>
|
|
28
|
+
</li>
|
|
29
|
+
<li>
|
|
30
|
+
<a
|
|
31
|
+
href="https://twitter.com/vuejs"
|
|
32
|
+
target="_blank"
|
|
33
|
+
rel="noopener"
|
|
34
|
+
>Twitter</a
|
|
35
|
+
>
|
|
36
|
+
</li>
|
|
37
|
+
<li>
|
|
38
|
+
<a href="https://news.vuejs.org" target="_blank" rel="noopener"
|
|
39
|
+
>News</a
|
|
40
|
+
>
|
|
41
|
+
</li>
|
|
42
|
+
</ul>
|
|
43
|
+
<h3>Ecosystem</h3>
|
|
44
|
+
<ul>
|
|
45
|
+
<li>
|
|
46
|
+
<a
|
|
47
|
+
href="https://router.vuejs.org"
|
|
48
|
+
target="_blank"
|
|
49
|
+
rel="noopener"
|
|
50
|
+
>vue-router</a
|
|
51
|
+
>
|
|
52
|
+
</li>
|
|
53
|
+
<li>
|
|
54
|
+
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener"
|
|
55
|
+
>vuex</a
|
|
56
|
+
>
|
|
57
|
+
</li>
|
|
58
|
+
<li>
|
|
59
|
+
<a
|
|
60
|
+
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
|
61
|
+
target="_blank"
|
|
62
|
+
rel="noopener"
|
|
63
|
+
>vue-devtools</a
|
|
64
|
+
>
|
|
65
|
+
</li>
|
|
66
|
+
<li>
|
|
67
|
+
<a
|
|
68
|
+
href="https://vue-loader.vuejs.org"
|
|
69
|
+
target="_blank"
|
|
70
|
+
rel="noopener"
|
|
71
|
+
>vue-loader</a
|
|
72
|
+
>
|
|
73
|
+
</li>
|
|
74
|
+
<li>
|
|
75
|
+
<a
|
|
76
|
+
href="https://github.com/vuejs/awesome-vue"
|
|
77
|
+
target="_blank"
|
|
78
|
+
rel="noopener"
|
|
79
|
+
>awesome-vue</a
|
|
80
|
+
>
|
|
81
|
+
</li>
|
|
82
|
+
</ul>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<script>
|
|
87
|
+
export default {
|
|
88
|
+
name: 'HelloWorld',
|
|
89
|
+
props: {
|
|
90
|
+
msg: String
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
h3 {
|
|
97
|
+
margin: 40px 0 0;
|
|
98
|
+
}
|
|
99
|
+
ul {
|
|
100
|
+
list-style-type: none;
|
|
101
|
+
padding: 0;
|
|
102
|
+
}
|
|
103
|
+
li {
|
|
104
|
+
display: inline-block;
|
|
105
|
+
margin: 0 10px;
|
|
106
|
+
}
|
|
107
|
+
a {
|
|
108
|
+
color: #42b983;
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = (generator) => {
|
|
2
|
+
generator.injectImports(generator.entryFile, `import store from './store'`)
|
|
3
|
+
|
|
4
|
+
generator.injectRootOptions(generator.entryFile, `store`)
|
|
5
|
+
|
|
6
|
+
generator.extendPackage({
|
|
7
|
+
dependencies: {
|
|
8
|
+
vuex: '^3.6.2',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
generator.render('./template', {})
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = (generator, options = {}) => {
|
|
2
|
+
generator.extendPackage({
|
|
3
|
+
scripts: {
|
|
4
|
+
dev: 'webpack-dev-server --config ./build/dev.config.js',
|
|
5
|
+
build: 'webpack --config ./build/pro.config.js',
|
|
6
|
+
},
|
|
7
|
+
devDependencies: {
|
|
8
|
+
'clean-webpack-plugin': '^3.0.0',
|
|
9
|
+
'css-loader': '^5.0.2',
|
|
10
|
+
'file-loader': '^6.2.0',
|
|
11
|
+
'url-loader': '^4.1.1',
|
|
12
|
+
'html-webpack-plugin': '^4.5.1',
|
|
13
|
+
'style-loader': '^2.0.0',
|
|
14
|
+
'vue-loader': '^15.9.6',
|
|
15
|
+
webpack: '^4.32.2',
|
|
16
|
+
'webpack-cli': '^3.3.11',
|
|
17
|
+
'webpack-dev-server': '^3.11.2',
|
|
18
|
+
'webpack-merge': '^4.2.1',
|
|
19
|
+
webpackbar: '^4.0.0',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
generator.render('./template', {
|
|
24
|
+
hasBabel: options.features.includes('babel'),
|
|
25
|
+
lintOnSave: options.lintOn.includes('save'),
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { VueLoaderPlugin } = require('vue-loader')
|
|
3
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
4
|
+
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
|
|
5
|
+
const WebpackBar = require('webpackbar')
|
|
6
|
+
|
|
7
|
+
const resolve = (filePath) => path.resolve(__dirname, filePath)
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
entry: resolve('../src/main.js'),
|
|
11
|
+
resolve: {
|
|
12
|
+
extensions: ['.js', '.vue', '.json', '.css'],
|
|
13
|
+
alias: {
|
|
14
|
+
'@': resolve('../src'),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
module: {
|
|
18
|
+
rules: [
|
|
19
|
+
<%_ if (lintOnSave) { _%>
|
|
20
|
+
{
|
|
21
|
+
enforce: 'pre',
|
|
22
|
+
test: /\.(js|vue)$/,
|
|
23
|
+
loader: 'eslint-loader',
|
|
24
|
+
exclude: /node_modules/
|
|
25
|
+
},
|
|
26
|
+
<%_ } _%>
|
|
27
|
+
{
|
|
28
|
+
test: /\.vue$/,
|
|
29
|
+
loader: 'vue-loader',
|
|
30
|
+
exclude: /node_modules/,
|
|
31
|
+
},
|
|
32
|
+
<%_ if (hasBabel) { _%>
|
|
33
|
+
{
|
|
34
|
+
test: /\.js$/,
|
|
35
|
+
loader: 'babel-loader',
|
|
36
|
+
exclude: /node_modules/,
|
|
37
|
+
},
|
|
38
|
+
<%_ } _%>
|
|
39
|
+
{
|
|
40
|
+
test: /\.(png|svg|jpg|gif|ico)$/,
|
|
41
|
+
use: [{
|
|
42
|
+
loader: 'file-loader',
|
|
43
|
+
options: {
|
|
44
|
+
esModule: false,
|
|
45
|
+
},
|
|
46
|
+
}],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
test: /\.(woff|eot|ttf)\??.*$/,
|
|
50
|
+
loader: 'url-loader?name=fonts/[name].[md5:hash:hex:7].[ext]',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
test: /\.css$/,
|
|
54
|
+
use: ['style-loader', 'css-loader'],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
plugins: [
|
|
59
|
+
new WebpackBar(),
|
|
60
|
+
new VueLoaderPlugin(),
|
|
61
|
+
new CleanWebpackPlugin(),
|
|
62
|
+
new HtmlWebpackPlugin({
|
|
63
|
+
title: 'My App',
|
|
64
|
+
template: resolve('../public/index.html'),
|
|
65
|
+
favicon: resolve('../public/favicon.ico'),
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const merge = require('webpack-merge')
|
|
3
|
+
const base = require('./base.config')
|
|
4
|
+
|
|
5
|
+
const resolve = (filePath) => path.resolve(__dirname, filePath)
|
|
6
|
+
|
|
7
|
+
module.exports = merge(base, {
|
|
8
|
+
mode: 'development',
|
|
9
|
+
devtool: 'inline-source-map',
|
|
10
|
+
devServer: {
|
|
11
|
+
contentBase: resolve('../dist'),
|
|
12
|
+
hot: true,
|
|
13
|
+
port: 8080,
|
|
14
|
+
},
|
|
15
|
+
output: {
|
|
16
|
+
filename: '[name].bundle.js',
|
|
17
|
+
path: resolve('../dist'),
|
|
18
|
+
},
|
|
19
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const merge = require('webpack-merge')
|
|
3
|
+
const base = require('./base.config')
|
|
4
|
+
|
|
5
|
+
const resolve = (filePath) => path.resolve(__dirname, filePath)
|
|
6
|
+
|
|
7
|
+
module.exports = merge(base, {
|
|
8
|
+
mode: 'production',
|
|
9
|
+
devtool: 'source-map',
|
|
10
|
+
output: {
|
|
11
|
+
path: resolve('../dist'),
|
|
12
|
+
publicPath: './',
|
|
13
|
+
filename: '[name].[contenthash].js',
|
|
14
|
+
chunkFilename: '[name].[contenthash].js',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module.exports = (api) => {
|
|
2
|
+
api.injectFeature({
|
|
3
|
+
name: 'Linter / Formatter',
|
|
4
|
+
value: 'linter',
|
|
5
|
+
short: 'Linter',
|
|
6
|
+
description: 'Check and enforce code quality with ESLint or Prettier',
|
|
7
|
+
link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint',
|
|
8
|
+
checked: true,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
api.injectPrompt({
|
|
12
|
+
name: 'eslintConfig',
|
|
13
|
+
when: answers => answers.features.includes('linter'),
|
|
14
|
+
type: 'list',
|
|
15
|
+
message: 'Pick a linter / formatter config:',
|
|
16
|
+
description: 'Checking code errors and enforcing an homogeoneous code style is recommended.',
|
|
17
|
+
choices: () => [
|
|
18
|
+
{
|
|
19
|
+
name: 'ESLint + Airbnb config',
|
|
20
|
+
value: 'airbnb',
|
|
21
|
+
short: 'Airbnb',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'ESLint + Standard config',
|
|
25
|
+
value: 'standard',
|
|
26
|
+
short: 'Standard',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
api.injectPrompt({
|
|
32
|
+
name: 'lintOn',
|
|
33
|
+
message: 'Pick additional lint features:',
|
|
34
|
+
when: answers => answers.features.includes('linter'),
|
|
35
|
+
type: 'checkbox',
|
|
36
|
+
choices: [
|
|
37
|
+
{
|
|
38
|
+
name: 'Lint on save',
|
|
39
|
+
value: 'save',
|
|
40
|
+
checked: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Lint and fix on commit',
|
|
44
|
+
value: 'commit',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
|
|
3
|
+
module.exports = (api) => {
|
|
4
|
+
api.injectFeature({
|
|
5
|
+
name: 'Router',
|
|
6
|
+
value: 'router',
|
|
7
|
+
description: 'Structure the app with dynamic pages',
|
|
8
|
+
link: 'https://router.vuejs.org/',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
api.injectPrompt({
|
|
12
|
+
name: 'historyMode',
|
|
13
|
+
when: answers => answers.features.includes('router'),
|
|
14
|
+
type: 'confirm',
|
|
15
|
+
message: `Use history mode for router? ${chalk.yellow(`(Requires proper server setup for index fallback in production)`)}`,
|
|
16
|
+
description: `By using the HTML5 History API, the URLs don't need the '#' character anymore.`,
|
|
17
|
+
link: 'https://router.vuejs.org/guide/essentials/history-mode.html',
|
|
18
|
+
})
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const readline = require('readline')
|
|
2
|
+
// 清空控制台
|
|
3
|
+
module.exports = function clearConsole(title) {
|
|
4
|
+
if (process.stdout.isTTY) {
|
|
5
|
+
const blank = '\n'.repeat(process.stdout.rows)
|
|
6
|
+
console.log(blank)
|
|
7
|
+
readline.cursorTo(process.stdout, 0, 0)
|
|
8
|
+
readline.clearScreenDown(process.stdout)
|
|
9
|
+
if (title) {
|
|
10
|
+
console.log(title)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// https://github.com/facebook/jscodeshift
|
|
2
|
+
// 对代码进行解析得到 AST,再将参数 imports 中的语句插入
|
|
3
|
+
module.exports = function injectImports(fileInfo, api, { imports }) {
|
|
4
|
+
const j = api.jscodeshift
|
|
5
|
+
const root = j(fileInfo.source)
|
|
6
|
+
|
|
7
|
+
const toImportAST = i => j(`${i}\n`).nodes()[0].program.body[0]
|
|
8
|
+
const toImportHash = node => JSON.stringify({
|
|
9
|
+
specifiers: node.specifiers.map(s => s.local.name),
|
|
10
|
+
source: node.source.raw,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const declarations = root.find(j.ImportDeclaration)
|
|
14
|
+
const importSet = new Set(declarations.nodes().map(toImportHash))
|
|
15
|
+
const nonDuplicates = node => !importSet.has(toImportHash(node))
|
|
16
|
+
|
|
17
|
+
const importASTNodes = imports.map(toImportAST).filter(nonDuplicates)
|
|
18
|
+
|
|
19
|
+
if (declarations.length) {
|
|
20
|
+
declarations
|
|
21
|
+
.at(-1)
|
|
22
|
+
// a tricky way to avoid blank line after the previous import
|
|
23
|
+
.forEach(({ node }) => delete node.loc)
|
|
24
|
+
.insertAfter(importASTNodes)
|
|
25
|
+
} else {
|
|
26
|
+
// no pre-existing import declarations
|
|
27
|
+
root.get().node.program.body.unshift(...importASTNodes)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return root.toSource()
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// https://github.com/facebook/jscodeshift
|
|
2
|
+
// 对代码进行解析得到 AST,再将参数 injections 中的语句插入
|
|
3
|
+
module.exports = function injectOptions(fileInfo, api, { injections }) {
|
|
4
|
+
const j = api.jscodeshift
|
|
5
|
+
const root = j(fileInfo.source)
|
|
6
|
+
|
|
7
|
+
const toPropertyAST = i => j(`({${i}})`).nodes()[0].program.body[0].expression.properties[0]
|
|
8
|
+
|
|
9
|
+
const properties = root
|
|
10
|
+
.find(j.NewExpression, {
|
|
11
|
+
callee: { name: 'Vue' },
|
|
12
|
+
arguments: [{ type: 'ObjectExpression' }],
|
|
13
|
+
})
|
|
14
|
+
.map(path => path.get('arguments', 0))
|
|
15
|
+
.get()
|
|
16
|
+
.node
|
|
17
|
+
.properties
|
|
18
|
+
|
|
19
|
+
const toPropertyHash = p => `${p.key.name}: ${j(p.value).toSource()}`
|
|
20
|
+
const propertySet = new Set(properties.map(toPropertyHash))
|
|
21
|
+
const nonDuplicates = p => !propertySet.has(toPropertyHash(p))
|
|
22
|
+
|
|
23
|
+
// inject at index length - 1 as it's usually the render fn
|
|
24
|
+
properties.splice(-1, 0, ...injections.map(toPropertyAST).filter(nonDuplicates))
|
|
25
|
+
|
|
26
|
+
return root.toSource()
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const stringifyJS = require('./stringifyJS')
|
|
2
|
+
const merge = require('deepmerge')
|
|
3
|
+
|
|
4
|
+
const mergeArrayWithDedupe = (a, b) => Array.from(new Set([...a, ...b]))
|
|
5
|
+
const mergeOptions = {
|
|
6
|
+
arrayMerge: mergeArrayWithDedupe,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const transformJS = {
|
|
10
|
+
read: ({ filename, context }) => {
|
|
11
|
+
try {
|
|
12
|
+
return require(`./${filename}`, context, true)
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
write: ({ value }) => `module.exports = ${stringifyJS(value, null, 4)}`,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const transformJSON = {
|
|
21
|
+
read: ({ source }) => JSON.parse(source),
|
|
22
|
+
write: ({ value, existing }) => JSON.stringify(merge(existing, value, mergeOptions), null, 4),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const transformYAML = {
|
|
26
|
+
read: ({ source }) => require('js-yaml').safeLoad(source),
|
|
27
|
+
write: ({ value, existing }) => require('js-yaml').safeDump(merge(existing, value, mergeOptions), {
|
|
28
|
+
skipInvalid: true,
|
|
29
|
+
}),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const transformLines = {
|
|
33
|
+
read: ({ source }) => source.split('\n'),
|
|
34
|
+
write: ({ value, existing }) => {
|
|
35
|
+
if (existing) {
|
|
36
|
+
value = existing.concat(value)
|
|
37
|
+
// Dedupe
|
|
38
|
+
value = value.filter((item, index) => value.indexOf(item) === index)
|
|
39
|
+
}
|
|
40
|
+
return value.join('\n')
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
js: transformJS,
|
|
46
|
+
json: transformJSON,
|
|
47
|
+
yaml: transformYAML,
|
|
48
|
+
lines: transformLines,
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const execa = require('execa')
|
|
2
|
+
module.exports = function executeCommand(command, cwd) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const child = execa.command(command, {
|
|
5
|
+
cwd,
|
|
6
|
+
/*
|
|
7
|
+
三个选项分别对应:
|
|
8
|
+
stdin = 'inherit': 子进程直接用用户的输入
|
|
9
|
+
stdout = 'pipe':把子进程的正常输出“接到一根管子里”给 Node 程序
|
|
10
|
+
stderr = 'inherit':子进程的错误输出直接显示到当前终端
|
|
11
|
+
不用你自己监听 child.stderr,报错会直接出现在屏幕上
|
|
12
|
+
*/
|
|
13
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
child.stdout.on('data', buffer => {
|
|
17
|
+
process.stdout.write(buffer)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
child.on('close', code => {
|
|
21
|
+
/*
|
|
22
|
+
子进程结束时触发
|
|
23
|
+
code是退出码:一般0 表示成功,非0表示失败
|
|
24
|
+
*/
|
|
25
|
+
if (code !== 0) {
|
|
26
|
+
reject(new Error(`command failed: ${command}`))
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
resolve()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对对象 key 排序并返回新对象。
|
|
3
|
+
* - 先按 keyOrder 指定的顺序放置(若存在)
|
|
4
|
+
* - 剩余 key 默认按 Unicode 字典序排序(可关闭)
|
|
5
|
+
*
|
|
6
|
+
* @param {Record<string, any>} obj 要排序的对象(会被 delete 已提取的 key)
|
|
7
|
+
* @param {string[]} [keyOrder] 优先排序的 key 列表
|
|
8
|
+
* @param {boolean} [dontSortByUnicode] 为 true 时不对剩余 key 做 keys.sort()
|
|
9
|
+
* @returns {Record<string, any> | undefined} 排序后的新对象;obj 为空时返回 undefined
|
|
10
|
+
*/
|
|
11
|
+
module.exports = function sortObject(obj, keyOrder, dontSortByUnicode) {
|
|
12
|
+
if (!obj) return
|
|
13
|
+
const res = {}
|
|
14
|
+
|
|
15
|
+
if (keyOrder) {
|
|
16
|
+
keyOrder.forEach(key => {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
18
|
+
res[key] = obj[key]
|
|
19
|
+
delete obj[key]
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const keys = Object.keys(obj)
|
|
25
|
+
|
|
26
|
+
!dontSortByUnicode && keys.sort()
|
|
27
|
+
keys.forEach(key => {
|
|
28
|
+
res[key] = obj[key]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return res
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// 递归的序列化代码
|
|
2
|
+
module.exports = function stringifyJS(value) {
|
|
3
|
+
const { stringify } = require('javascript-stringify')
|
|
4
|
+
// eslint-disable-next-line no-shadow
|
|
5
|
+
return stringify(value, (val, indent, stringify) => {
|
|
6
|
+
if (val && val.__expression) {
|
|
7
|
+
return val.__expression
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return stringify(val)
|
|
11
|
+
}, 4)
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const fs = require('fs-extra')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
module.exports = async function writeFileTree(dir, files, options = {}) {
|
|
5
|
+
const { overwrite = true } = options
|
|
6
|
+
Object.keys(files).forEach((name) => {
|
|
7
|
+
const filePath = path.join(dir, name)
|
|
8
|
+
fs.ensureDirSync(path.dirname(filePath))
|
|
9
|
+
if (!overwrite && name !== 'package.json' && fs.existsSync(filePath)) return
|
|
10
|
+
fs.writeFileSync(filePath, files[name])
|
|
11
|
+
})
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@weig_3078/cli-demo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "脚手架 demo",
|
|
5
|
+
"private": false,
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mvc": "./dist/mvc.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rollup -c rollup.config.cjs",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/weige3078/cli-demo.git"
|
|
17
|
+
},
|
|
18
|
+
"husky": {
|
|
19
|
+
"hooks": {
|
|
20
|
+
"commit-msg": "node scripts/verify-commit.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"keywords": [],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"commander": "^7.0.0",
|
|
29
|
+
"download-git-repo": "^3.0.2",
|
|
30
|
+
"ejs": "^3.1.6",
|
|
31
|
+
"execa": "^5.0.0",
|
|
32
|
+
"fs-extra": "^9.1.0",
|
|
33
|
+
"globby": "^11.0.2",
|
|
34
|
+
"inquirer": "^7.3.3",
|
|
35
|
+
"isbinaryfile": "^4.0.6",
|
|
36
|
+
"javascript-stringify": "^2.0.1",
|
|
37
|
+
"js-yaml": "^4.1.0",
|
|
38
|
+
"readline": "^1.3.0",
|
|
39
|
+
"recast": "^0.20.4",
|
|
40
|
+
"vue-codemod": "0.0.5"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rollup/plugin-commonjs": "^29.0.2",
|
|
44
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
45
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
46
|
+
"deepmerge": "^4.3.1",
|
|
47
|
+
"eslint": "^7.20.0",
|
|
48
|
+
"eslint-config-airbnb-base": "^14.2.1",
|
|
49
|
+
"eslint-plugin-import": "^2.22.1",
|
|
50
|
+
"husky": "^4.3.0",
|
|
51
|
+
"rollup": "^4.60.0"
|
|
52
|
+
}
|
|
53
|
+
}
|