@htyf-mp/cli 0.1.3 → 0.2.0-alpha.3
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/README.md +1 -1
- package/package.json +45 -13
- package/src/HtyfImportsPlugin.mjs +190 -0
- package/src/ListImportsPlugin.mjs +105 -0
- package/src/babel.config.js +10 -0
- package/src/build.mjs +146 -0
- package/src/clean.mjs +83 -0
- package/src/command-executor.mjs +107 -0
- package/src/config.mjs +55 -0
- package/src/constants.mjs +55 -0
- package/src/debug.mjs +111 -0
- package/src/file-system.mjs +96 -0
- package/src/index.mjs +255 -0
- package/src/logger.mjs +72 -0
- package/src/project-initializer.mjs +207 -0
- package/src/shared-output.json +81 -0
- package/src/template-processor.mjs +137 -0
- package/src/utils-functions.mjs +76 -0
- package/src/utils.mjs +14 -0
- package/src/validators.mjs +82 -0
- package/src/webpack.config.mjs +168 -0
- package/cli.js +0 -288
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// 导入Node.js和第三方依赖
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import fse from 'fs-extra';
|
|
5
|
+
import webpack from 'webpack';
|
|
6
|
+
import TerserPlugin from 'terser-webpack-plugin';
|
|
7
|
+
import * as Repack from '@callstack/repack';
|
|
8
|
+
import { ExpoModulesPlugin } from '@callstack/repack-plugin-expo-modules';
|
|
9
|
+
import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated';
|
|
10
|
+
import { HtyfModulesPlugin } from './HtyfImportsPlugin.mjs';
|
|
11
|
+
import { getProjectRoot } from './utils.mjs';
|
|
12
|
+
|
|
13
|
+
// 获取当前文件和目录路径
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
// 微前端暴露配置,通常为JSON字符串
|
|
18
|
+
const appExposesOptions = process.env.APP_EXPOSES_OPTIONS;
|
|
19
|
+
const appRootIndexPath = process.env.APP_ROOT_INDEX_PATH;
|
|
20
|
+
|
|
21
|
+
// 读取依赖共享配置
|
|
22
|
+
const dependencies = fse.readJsonSync(path.join(__dirname, './shared-output.json'));
|
|
23
|
+
|
|
24
|
+
// 记录构建时间戳
|
|
25
|
+
const time = Date.now();
|
|
26
|
+
|
|
27
|
+
const version = process.env.APP_VERSION;
|
|
28
|
+
const appid = process.env.APP_ID;
|
|
29
|
+
|
|
30
|
+
// 构建Module Federation共享依赖对象
|
|
31
|
+
const sharedObj = {};
|
|
32
|
+
for (const key in dependencies) {
|
|
33
|
+
const p = dependencies[key];
|
|
34
|
+
sharedObj[key] = {
|
|
35
|
+
/** 是否运行为单例 */
|
|
36
|
+
singleton: true,
|
|
37
|
+
/** host模式下必须为true,微应用为false */
|
|
38
|
+
eager: true,
|
|
39
|
+
version: p.version,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const projectRoot = getProjectRoot();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Webpack配置导出函数
|
|
47
|
+
* @param {object} env - 构建环境变量
|
|
48
|
+
* @returns {object} - Webpack配置对象
|
|
49
|
+
*/
|
|
50
|
+
export default (env = {}) => {
|
|
51
|
+
// 设置构建上下文
|
|
52
|
+
env.context = projectRoot || __dirname;
|
|
53
|
+
const { entry = appRootIndexPath, platform = 'ios' } = env;
|
|
54
|
+
const context = env.context || __dirname;
|
|
55
|
+
// 获取Repack的resolve配置
|
|
56
|
+
const _options = Repack.getResolveOptions();
|
|
57
|
+
|
|
58
|
+
// 解析微前端相关配置
|
|
59
|
+
let mpOptions = {};
|
|
60
|
+
if (appExposesOptions) {
|
|
61
|
+
mpOptions = JSON.parse(decodeURI(appExposesOptions));
|
|
62
|
+
}
|
|
63
|
+
console.log('\n');
|
|
64
|
+
console.log('=====mpOptions=====');
|
|
65
|
+
console.log('\n');
|
|
66
|
+
console.log('context:', projectRoot);
|
|
67
|
+
console.log('\n');
|
|
68
|
+
console.log('entry:', entry);
|
|
69
|
+
console.log('\n');
|
|
70
|
+
console.log(JSON.stringify(mpOptions, null, 2));
|
|
71
|
+
console.log('\n');
|
|
72
|
+
console.log('=====mpOptions=====');
|
|
73
|
+
console.log('\n');
|
|
74
|
+
// 基础Webpack配置
|
|
75
|
+
const mpConfig = {
|
|
76
|
+
context,
|
|
77
|
+
entry,
|
|
78
|
+
resolve: {
|
|
79
|
+
..._options,
|
|
80
|
+
},
|
|
81
|
+
output: {
|
|
82
|
+
// 默认输出路径,host模式下会被覆盖
|
|
83
|
+
path: mpOptions.outputPath,
|
|
84
|
+
publicPath: mpOptions.outputPath,
|
|
85
|
+
// uniqueName: `htyf_mp_for_${mpOptions.name}`,
|
|
86
|
+
},
|
|
87
|
+
module: {
|
|
88
|
+
rules: [
|
|
89
|
+
// 处理js/ts/tsx文件,使用babel-loader
|
|
90
|
+
{
|
|
91
|
+
test: /\.[cm]?[jt]sx?$/,
|
|
92
|
+
use: {
|
|
93
|
+
loader: 'babel-loader',
|
|
94
|
+
// options: {
|
|
95
|
+
// configFile: path.resolve(__dirname, './babel.config.js'),
|
|
96
|
+
// }
|
|
97
|
+
},
|
|
98
|
+
type: 'javascript/auto',
|
|
99
|
+
},
|
|
100
|
+
// 处理静态资源(图片等),支持inline模式
|
|
101
|
+
...Repack.getAssetTransformRules({
|
|
102
|
+
inline: !!mpOptions.extraChunksPath,
|
|
103
|
+
svg: 'xml',
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
optimization: {
|
|
108
|
+
// 压缩配置,去除注释
|
|
109
|
+
minimizer: [
|
|
110
|
+
new TerserPlugin({
|
|
111
|
+
test: /\.(js)?bundle(\?.*)?$/i,
|
|
112
|
+
extractComments: false,
|
|
113
|
+
terserOptions: {
|
|
114
|
+
format: {
|
|
115
|
+
comments: false,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
chunkIds: 'named', // 便于调试
|
|
121
|
+
},
|
|
122
|
+
plugins: [
|
|
123
|
+
// 注入全局常量(appid、版本、构建时间)
|
|
124
|
+
new webpack.DefinePlugin({
|
|
125
|
+
__APP_DEFINE_APPID__: `"${appid}"`,
|
|
126
|
+
__APP_DEFINE_VERSION__: `"${version}"`,
|
|
127
|
+
__APP_DEFINE_BUILD_TIME__: `"${time}"`,
|
|
128
|
+
}),
|
|
129
|
+
// Repack主插件,支持extraChunks配置
|
|
130
|
+
new Repack.RepackPlugin({
|
|
131
|
+
platform,
|
|
132
|
+
extraChunks: [
|
|
133
|
+
{
|
|
134
|
+
include: /.*/,
|
|
135
|
+
type: 'remote',
|
|
136
|
+
outputPath: `${mpOptions.extraChunksPath}`,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
}),
|
|
140
|
+
// Expo模块支持
|
|
141
|
+
new ExpoModulesPlugin(),
|
|
142
|
+
// Reanimated支持
|
|
143
|
+
new ReanimatedPlugin(),
|
|
144
|
+
// Module Federation V2插件,支持host和remote两种模式
|
|
145
|
+
new Repack.plugins.ModuleFederationPluginV2({
|
|
146
|
+
dts: false,
|
|
147
|
+
// 微应用模式
|
|
148
|
+
name: mpOptions.name,
|
|
149
|
+
filename: mpOptions.filename,
|
|
150
|
+
exposes: mpOptions.exposes,
|
|
151
|
+
shared: {
|
|
152
|
+
...sharedObj,
|
|
153
|
+
react: { singleton: true, eager: true },
|
|
154
|
+
'react-native': { singleton: true, eager: true },
|
|
155
|
+
},
|
|
156
|
+
shareStrategy: 'loaded-first',
|
|
157
|
+
}),
|
|
158
|
+
new HtyfModulesPlugin({
|
|
159
|
+
codeSigning: true
|
|
160
|
+
}),
|
|
161
|
+
new webpack.optimize.LimitChunkCountPlugin({
|
|
162
|
+
maxChunks: 2, // 限制最大chunk数,便于远程加载
|
|
163
|
+
})
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return mpConfig;
|
|
168
|
+
};
|
package/cli.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import fs from 'fs'
|
|
6
|
-
import fse from 'fs-extra'
|
|
7
|
-
import { Command } from 'commander'
|
|
8
|
-
import select from '@inquirer/select'
|
|
9
|
-
import { input } from '@inquirer/prompts';
|
|
10
|
-
import {execa, execaCommand} from 'execa'
|
|
11
|
-
import ora from 'ora'
|
|
12
|
-
import childProcess from 'child_process'
|
|
13
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
14
|
-
import md5 from 'md5'
|
|
15
|
-
import os from 'os'
|
|
16
|
-
|
|
17
|
-
const v4options = {
|
|
18
|
-
random: [
|
|
19
|
-
0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0xc1, 0xea, 0x71, 0xb4, 0xef, 0xe1, 0x67, 0x1c, 0x58, 0x36,
|
|
20
|
-
],
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const log = console.log
|
|
24
|
-
const program = new Command()
|
|
25
|
-
const green = chalk.green
|
|
26
|
-
|
|
27
|
-
const repoUrl = 'https://github.com/htyf-mp-community/htyf-mp.git'
|
|
28
|
-
const codingRepoUrl = 'https://e.coding.net/dagouzhi/hongtangyun_mobile/htyf-mp.git'
|
|
29
|
-
|
|
30
|
-
const taroTempPath = 'mini-apps-template';
|
|
31
|
-
const expoTempPath = 'mini-apps-template';
|
|
32
|
-
const gameTempPath = 'mini-game-template';
|
|
33
|
-
|
|
34
|
-
const isYarnInstalled = () => {
|
|
35
|
-
try {
|
|
36
|
-
childProcess.execSync('yarn --version');
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const isBunInstalled = () => {
|
|
44
|
-
try {
|
|
45
|
-
childProcess.execSync('bun --version')
|
|
46
|
-
return true;
|
|
47
|
-
} catch(err) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function main() {
|
|
53
|
-
const spinner = ora({
|
|
54
|
-
text: '创建代码库'
|
|
55
|
-
})
|
|
56
|
-
try {
|
|
57
|
-
const kebabRegez = /^([a-z]+)(-[a-z0-9]+)*$/
|
|
58
|
-
|
|
59
|
-
program
|
|
60
|
-
.name('红糖云服APP-小程序')
|
|
61
|
-
.description('快速创建红糖云服APP小程序')
|
|
62
|
-
|
|
63
|
-
program.parse(process.argv)
|
|
64
|
-
|
|
65
|
-
const args = program.args
|
|
66
|
-
let appName = args[0]
|
|
67
|
-
|
|
68
|
-
if (!appName || !kebabRegez.test(args[0])) {
|
|
69
|
-
appName = await input({
|
|
70
|
-
message: '输入应用程序目录名称',
|
|
71
|
-
default: 'my-htyf-mp',
|
|
72
|
-
validate: d => {
|
|
73
|
-
if(!kebabRegez.test(d)) {
|
|
74
|
-
return '请以my-app-name格式输入您的应用程序英文目录名称'
|
|
75
|
-
}
|
|
76
|
-
return true
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const displayNameRegez = /^[\u4e00-\u9fa5a-zA-Z0-9]{2,4}$/
|
|
82
|
-
|
|
83
|
-
let displayName = ''
|
|
84
|
-
if (!displayName) {
|
|
85
|
-
displayName = await input({
|
|
86
|
-
message: '输入应用程序名称',
|
|
87
|
-
default: '小程序',
|
|
88
|
-
validate: d => {
|
|
89
|
-
if(!displayNameRegez.test(d)) {
|
|
90
|
-
return '只能中文、字母和数字, 不能包含特殊字符, 限2-4个字符'
|
|
91
|
-
}
|
|
92
|
-
return true
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const tempType = await select({
|
|
98
|
-
message: '请选择模板类型?',
|
|
99
|
-
choices: [
|
|
100
|
-
{
|
|
101
|
-
name: 'Expo (https://docs.expo.dev/)',
|
|
102
|
-
value: 'expo',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: 'Taro (https://docs.taro.zone/)',
|
|
106
|
-
value: 'taro',
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'Game (https://docs.cocos.com/)',
|
|
110
|
-
value: 'game-cocos',
|
|
111
|
-
}
|
|
112
|
-
]
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const repoType = await select({
|
|
116
|
-
message: '请选择模板镜像?',
|
|
117
|
-
choices: [
|
|
118
|
-
{
|
|
119
|
-
name: 'github(最新)',
|
|
120
|
-
value: repoUrl,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: 'coding(最快)',
|
|
124
|
-
value: codingRepoUrl,
|
|
125
|
-
},
|
|
126
|
-
]
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const appid = md5(uuidv4(v4options));
|
|
130
|
-
|
|
131
|
-
let config = {
|
|
132
|
-
"type": "app",
|
|
133
|
-
"name": appName,
|
|
134
|
-
"projectname": displayName,
|
|
135
|
-
"appid": appid,
|
|
136
|
-
"appUrlConfig": "https://xxx.cos.ap-chengdu.myqcloud.com/assets/testMiniApps/htyanimation/app.json",
|
|
137
|
-
"zipUrl": "https://xxxxx.myqcloud.com/assets/testMiniApps/xxxxx/outputs/dist.dgz"
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
log(`\n初始化项目. \n`)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const root_path = path.join(process.cwd(), `${appName}`);
|
|
144
|
-
const isExists = fs.existsSync(root_path);
|
|
145
|
-
if (isExists) {
|
|
146
|
-
console.error('目录已经存在。')
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (/[\u4e00-\u9fa5]/gi.test(root_path)) {
|
|
150
|
-
console.error('禁止目录中包含中文字符')
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
spinner.start()
|
|
154
|
-
const tmpdir = path.join(os.tmpdir(), md5(`__HTYF__`), appName);
|
|
155
|
-
fse.emptyDirSync(tmpdir)
|
|
156
|
-
await execa('git', ['clone', repoType, tmpdir])
|
|
157
|
-
let appRootPath = '';
|
|
158
|
-
const taro_path = path.join(tmpdir, `${taroTempPath}`)
|
|
159
|
-
const expo_path = path.join(tmpdir, `${expoTempPath}`)
|
|
160
|
-
const game_path = path.join(tmpdir, `${gameTempPath}`)
|
|
161
|
-
const cli_path = path.join(tmpdir, `cli`)
|
|
162
|
-
const git_path = path.join(tmpdir, `.git`)
|
|
163
|
-
const docs_path = path.join(tmpdir, `docs`)
|
|
164
|
-
const README_path = path.join(tmpdir, `README.md`)
|
|
165
|
-
try {
|
|
166
|
-
if (tempType === 'taro') {
|
|
167
|
-
appRootPath = taro_path
|
|
168
|
-
fse.removeSync(game_path)
|
|
169
|
-
}
|
|
170
|
-
if (tempType === 'expo') {
|
|
171
|
-
appRootPath = expo_path
|
|
172
|
-
fse.removeSync(game_path)
|
|
173
|
-
}
|
|
174
|
-
if (tempType === 'game-cocos') {
|
|
175
|
-
appRootPath = game_path
|
|
176
|
-
fse.removeSync(taro_path)
|
|
177
|
-
fse.removeSync(expo_path)
|
|
178
|
-
}
|
|
179
|
-
fse.removeSync(cli_path)
|
|
180
|
-
fse.removeSync(git_path)
|
|
181
|
-
fse.removeSync(docs_path)
|
|
182
|
-
fse.removeSync(README_path)
|
|
183
|
-
} catch (err) {}
|
|
184
|
-
const __TEMP_PATH__ = path.join(appRootPath, '__TEMP__');
|
|
185
|
-
const pkg_path = path.join(appRootPath, './package.json');
|
|
186
|
-
const dgzJosn = path.join(`${appRootPath}`, 'project.dgz.json')
|
|
187
|
-
console.log(' ')
|
|
188
|
-
console.log(tempType)
|
|
189
|
-
console.log(__TEMP_PATH__)
|
|
190
|
-
console.log(' ')
|
|
191
|
-
if (tempType === 'taro') {
|
|
192
|
-
fse.copySync(
|
|
193
|
-
path.join(__TEMP_PATH__, 'taro_src'),
|
|
194
|
-
path.join(appRootPath, './src')
|
|
195
|
-
)
|
|
196
|
-
fse.copySync(
|
|
197
|
-
path.join(__TEMP_PATH__, 'taro.App.tsx'),
|
|
198
|
-
path.join(appRootPath, './App.tsx')
|
|
199
|
-
)
|
|
200
|
-
fse.copySync(
|
|
201
|
-
path.join(__TEMP_PATH__, 'taro.index.js'),
|
|
202
|
-
path.join(appRootPath, './index.js')
|
|
203
|
-
)
|
|
204
|
-
fse.copySync(
|
|
205
|
-
path.join(__TEMP_PATH__, 'taro.babel.config.js'),
|
|
206
|
-
path.join(appRootPath, './babel.config.js')
|
|
207
|
-
)
|
|
208
|
-
fse.copySync(
|
|
209
|
-
path.join(__TEMP_PATH__, 'taro.project.dgz.json'),
|
|
210
|
-
path.join(appRootPath, './project.dgz.json')
|
|
211
|
-
)
|
|
212
|
-
const pkg_info = fse.readJSONSync(pkg_path)
|
|
213
|
-
pkg_info['scripts'] = {
|
|
214
|
-
...(pkg_info['scripts'] || {}),
|
|
215
|
-
"build:weapp": "taro build --type weapp",
|
|
216
|
-
"build:swan": "taro build --type swan",
|
|
217
|
-
"build:alipay": "taro build --type alipay",
|
|
218
|
-
"build:tt": "taro build --type tt",
|
|
219
|
-
"build:h5": "taro build --type h5",
|
|
220
|
-
"build:rn": "taro build --type rn",
|
|
221
|
-
"build:qq": "taro build --type qq",
|
|
222
|
-
"build:jd": "taro build --type jd",
|
|
223
|
-
"build:quickapp": "taro build --type quickapp",
|
|
224
|
-
"dev:weapp": "npm run build:weapp -- --watch",
|
|
225
|
-
"dev:swan": "npm run build:swan -- --watch",
|
|
226
|
-
"dev:alipay": "npm run build:alipay -- --watch",
|
|
227
|
-
"dev:tt": "npm run build:tt -- --watch",
|
|
228
|
-
"dev:h5": "npm run build:h5 -- --watch",
|
|
229
|
-
"dev:rn": "npm run build:rn -- --watch",
|
|
230
|
-
"dev:qq": "npm run build:qq -- --watch",
|
|
231
|
-
"dev:jd": "npm run build:jd -- --watch",
|
|
232
|
-
"dev:quickapp": "npm run build:quickapp -- --watch",
|
|
233
|
-
}
|
|
234
|
-
delete pkg_info['scripts']['web']
|
|
235
|
-
delete pkg_info['scripts']['web:dev']
|
|
236
|
-
fse.writeJSONSync(pkg_path, pkg_info, {
|
|
237
|
-
spaces: 2
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
if (tempType === 'expo') {
|
|
241
|
-
fse.copySync(
|
|
242
|
-
path.join(__TEMP_PATH__, 'expo_src'),
|
|
243
|
-
path.join(appRootPath, './src')
|
|
244
|
-
)
|
|
245
|
-
fse.copySync(
|
|
246
|
-
path.join(__TEMP_PATH__, 'expo.App.tsx'),
|
|
247
|
-
path.join(appRootPath, './App.tsx')
|
|
248
|
-
)
|
|
249
|
-
fse.copySync(
|
|
250
|
-
path.join(__TEMP_PATH__, 'expo.index.js'),
|
|
251
|
-
path.join(appRootPath, './index.js')
|
|
252
|
-
)
|
|
253
|
-
fse.copySync(
|
|
254
|
-
path.join(__TEMP_PATH__, 'expo.babel.config.js'),
|
|
255
|
-
path.join(appRootPath, './babel.config.js')
|
|
256
|
-
)
|
|
257
|
-
fse.copySync(
|
|
258
|
-
path.join(__TEMP_PATH__, 'expo.project.dgz.json'),
|
|
259
|
-
path.join(appRootPath, './project.dgz.json')
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
|
-
if (tempType === 'game-cocos') {
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
fs.writeFileSync(dgzJosn, JSON.stringify(config, undefined, 2))
|
|
267
|
-
console.log(dgzJosn)
|
|
268
|
-
|
|
269
|
-
fse.removeSync(__TEMP_PATH__)
|
|
270
|
-
|
|
271
|
-
spinner.text = ''
|
|
272
|
-
|
|
273
|
-
fse.moveSync(appRootPath, root_path)
|
|
274
|
-
|
|
275
|
-
spinner.stop()
|
|
276
|
-
log(`${green.bold('Success!')} 创建项目 ${appName} 在 ${root_path} \n`)
|
|
277
|
-
log(`切换到【 ${root_path} 】目录并运行开发`)
|
|
278
|
-
log('\n')
|
|
279
|
-
} catch (err) {
|
|
280
|
-
console.log(err)
|
|
281
|
-
log('\n')
|
|
282
|
-
if (err.exitCode == 128) {
|
|
283
|
-
log('Error: 目录已经存在。')
|
|
284
|
-
}
|
|
285
|
-
spinner.stop()
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
main()
|