@snack-kit/scripts 0.7.0 → 0.9.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/README.md +46 -14
- package/bin/lib.js +91 -0
- package/bin/snack-cli.js +280 -0
- package/bin/snack-scripts.js +163 -0
- package/config/webpack.dev.config.js +1 -1
- package/config/webpack.shared.js +20 -10
- package/package.json +5 -5
- package/utils/package.js +15 -0
- package/bin/main.js +0 -478
package/README.md
CHANGED
|
@@ -10,15 +10,25 @@ npm install @snack-kit/scripts --save-dev
|
|
|
10
10
|
|
|
11
11
|
## 命令
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|------------------------|---------------|
|
|
15
|
-
| `snack-cli init [dir]` | 初始化新 snack 工程 |
|
|
16
|
-
| `snack-cli create` | 在当前工程创建新模块模版 |
|
|
17
|
-
| `snack-cli start` | 启动开发调试服务 |
|
|
18
|
-
| `snack-cli build` | 生产模式打包 |
|
|
19
|
-
| `snack-cli entry` | 打包独立入口页面 |
|
|
13
|
+
### snack-cli — 全局脚手架
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
全局安装后使用,用于初始化工程和创建模块。
|
|
16
|
+
|
|
17
|
+
| 命令 | 说明 |
|
|
18
|
+
|------------------------|-----------------|
|
|
19
|
+
| `snack-cli init [dir]` | 初始化新 snack 工程 |
|
|
20
|
+
| `snack-cli create` | 在当前工程创建新模块模版 |
|
|
21
|
+
| `snack-cli -v` | 查看当前版本号 |
|
|
22
|
+
|
|
23
|
+
### snack-scripts — 项目构建
|
|
24
|
+
|
|
25
|
+
安装到项目 `devDependencies` 后通过 `npm run` 调用,用于开发调试和生产打包。
|
|
26
|
+
|
|
27
|
+
| 命令 | 说明 |
|
|
28
|
+
|-----------------------|------------|
|
|
29
|
+
| `snack-scripts start` | 启动开发调试服务 |
|
|
30
|
+
| `snack-scripts build` | 生产模式打包 |
|
|
31
|
+
| `snack-scripts entry` | 打包独立入口页面 |
|
|
22
32
|
|
|
23
33
|
### init — 初始化工程
|
|
24
34
|
|
|
@@ -75,9 +85,12 @@ src/package/UserManager/
|
|
|
75
85
|
```json
|
|
76
86
|
{
|
|
77
87
|
"scripts": {
|
|
78
|
-
"start": "snack-
|
|
79
|
-
"build": "snack-
|
|
80
|
-
"entry": "snack-
|
|
88
|
+
"start": "snack-scripts start",
|
|
89
|
+
"build": "snack-scripts build",
|
|
90
|
+
"entry": "snack-scripts entry"
|
|
91
|
+
},
|
|
92
|
+
"devDependencies": {
|
|
93
|
+
"@snack-kit/scripts": "^0.4.0"
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
```
|
|
@@ -143,11 +156,17 @@ src/package/UserManager/
|
|
|
143
156
|
|
|
144
157
|
## config-overrides.js
|
|
145
158
|
|
|
146
|
-
在项目根目录创建 `config-overrides.js` 可自定义 webpack
|
|
159
|
+
在项目根目录创建 `config-overrides.js` 可自定义 webpack 配置。
|
|
160
|
+
|
|
161
|
+
> **注意:** `snack-scripts` 会将已解析的 webpack 实例作为**第二参数**传入。若需使用 webpack 内置插件(如 `ProvidePlugin`),必须使用该参数而非 `require('webpack')`,否则会因 tapable 多实例冲突导致构建报错。
|
|
147
162
|
|
|
148
163
|
```js
|
|
149
|
-
module.exports = (config) => {
|
|
150
|
-
//
|
|
164
|
+
module.exports = (config, webpack) => {
|
|
165
|
+
// 使用传入的 webpack 实例,而非 require('webpack')
|
|
166
|
+
config.plugins = [
|
|
167
|
+
...config.plugins,
|
|
168
|
+
new webpack.ProvidePlugin({ /* ... */ }),
|
|
169
|
+
];
|
|
151
170
|
return config;
|
|
152
171
|
};
|
|
153
172
|
```
|
|
@@ -170,6 +189,19 @@ module.exports = (config) => {
|
|
|
170
189
|
|
|
171
190
|
## Changelog
|
|
172
191
|
|
|
192
|
+
### 0.9.0
|
|
193
|
+
|
|
194
|
+
- 修复:sass-loader 改回 legacy API,解决旧项目 `@import '~xxx'` 路径无法解析的问题(`api: 'modern'` 的 webpack importer 在 sass-loader 13.x 中未实现)
|
|
195
|
+
- 优化:添加 `silenceDeprecations` 配置,屏蔽 Dart Sass 1.80+ 对 `@import`、legacy JS API 等语法的弃用警告,迁移期间无需修改 SCSS 源文件
|
|
196
|
+
|
|
197
|
+
### 0.8.0
|
|
198
|
+
|
|
199
|
+
- 重构:`bin/main.js` 拆分为 `snack-cli`(init / create)和 `snack-scripts`(start / build / entry)两个独立命令
|
|
200
|
+
- 新增:`snack-cli -v` 查看当前版本号
|
|
201
|
+
- 新增:共享工具模块 `bin/lib.js`(语言检测、i18n 字符串、交互式提问工具)
|
|
202
|
+
- 修复:`init` 生成项目的 `devDependencies` 新增 `@snack-kit/scripts`,本地安装后不再依赖全局版本
|
|
203
|
+
- 修复:`init` 生成的 `scripts` 命令修正为 `snack-scripts`,与实际 bin 名一致
|
|
204
|
+
|
|
173
205
|
### 0.7.0
|
|
174
206
|
|
|
175
207
|
- 修复: 修复 loader.js 打包丢失的问题
|
package/bin/lib.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
// ─── 语言检测 ─────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function detectLang() {
|
|
8
|
+
const env = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || process.env.LC_MESSAGES || '';
|
|
9
|
+
return env.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const lang = detectLang();
|
|
13
|
+
|
|
14
|
+
// ─── i18n 字符串表 ────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const t = {
|
|
17
|
+
// init
|
|
18
|
+
initTitle: { zh: ' Snack 项目初始化', en: ' Snack Project Init' },
|
|
19
|
+
projectName: { zh: '项目名称', en: 'Project name' },
|
|
20
|
+
description: { zh: '项目描述', en: 'Description' },
|
|
21
|
+
author: { zh: '作者', en: 'Author' },
|
|
22
|
+
debugServers: { zh: '调试服务器(多个用逗号分隔)', en: 'Debug servers (comma separated)' },
|
|
23
|
+
entryPageId: { zh: '入口页面 ID', en: 'Entry page id' },
|
|
24
|
+
entryPageType: { zh: '入口页面类型', en: 'Entry page type' },
|
|
25
|
+
reactVersion: { zh: 'React 版本', en: 'React version' },
|
|
26
|
+
projectCreated: { zh: ' 项目已创建:', en: ' Project created at:' },
|
|
27
|
+
nextSteps: { zh: ' 后续步骤:', en: ' Next steps:' },
|
|
28
|
+
// create
|
|
29
|
+
createTitle: { zh: ' Snack 模块创建', en: ' Snack Module Create' },
|
|
30
|
+
moduleName: { zh: '模块名称', en: 'Module name' },
|
|
31
|
+
moduleDirName: { zh: '模块目录名(PascalCase)', en: 'Module directory name (PascalCase)' },
|
|
32
|
+
withSetting: { zh: '生成配置模块?(y/n)', en: 'Generate setting module? (y/n)' },
|
|
33
|
+
moduleCreated: { zh: ' 模块已创建:', en: ' Module created:' },
|
|
34
|
+
filesGenerated: { zh: ' 已生成文件:', en: ' Files generated:' },
|
|
35
|
+
// errors
|
|
36
|
+
errNoPkg: { zh: '错误:当前目录没有 package.json,请在 snack 工程根目录下执行该命令', en: 'Error: No package.json found. Run this command in a snack project root.' },
|
|
37
|
+
errDirRequired: { zh: '错误:模块目录名不能为空', en: 'Error: Module directory name is required' },
|
|
38
|
+
errDirExists: { zh: '错误:模块目录已存在:', en: 'Error: Module directory already exists:' },
|
|
39
|
+
errNameConflict: { zh: '请修改 snack-cli package.json "name" 的默认名称,该名称将在部署时作为分类目录', en: 'Please change the default "name" in package.json. It will be used as a category directory during deployment.' },
|
|
40
|
+
errNoEntry: { zh: 'package.json 未找到 snack.entry 配置项', en: 'snack.entry config not found in package.json' },
|
|
41
|
+
errTempDir: { zh: '临时目录创建失败', en: 'Failed to create temp directory' },
|
|
42
|
+
errIntegrity: { zh: '完整性校验失败', en: 'Integrity check failed' },
|
|
43
|
+
// build logs
|
|
44
|
+
clearOutputDir: { zh: '清理输出目录', en: 'clear output dir' },
|
|
45
|
+
clearOutputDirEnd: { zh: '清理完成', en: 'clear output dir end' },
|
|
46
|
+
devServerRunning: { zh: 'Snack 开发服务器运行中...', en: 'Snack DevServer Runing...' },
|
|
47
|
+
createLoaderAndHTML: { zh: '生成 loader.js 和入口 HTML(并行)...', en: 'create loader.js & entry HTML (parallel) ...' },
|
|
48
|
+
createLoaderAndHTMLEnd: { zh: '生成完成', en: 'create loader.js & entry HTML end' },
|
|
49
|
+
createSnack: { zh: '生成 snack 模块...', en: 'create snack ...' },
|
|
50
|
+
createSnackEnd: { zh: '生成完成', en: 'create snack end' },
|
|
51
|
+
createEntry: { zh: '生成入口页...', en: 'create entry ...' },
|
|
52
|
+
integrityPass: { zh: '完整性校验通过', en: 'Integrity check passed' },
|
|
53
|
+
integrityFileMissing: { zh: '完整性校验:文件缺失', en: 'snack check: file is missing' },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function i(key) {
|
|
57
|
+
return t[key][lang];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── 交互式提问工具 ───────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
function prompt(rl, question, defaultValue) {
|
|
63
|
+
return new Promise(resolve => {
|
|
64
|
+
const hint = defaultValue !== undefined ? ` (${defaultValue})` : '';
|
|
65
|
+
rl.question(`${question}${hint}: `, answer => {
|
|
66
|
+
const val = answer.trim();
|
|
67
|
+
resolve(val === '' && defaultValue !== undefined ? defaultValue : val);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function select(rl, question, options, defaultValue) {
|
|
73
|
+
return new Promise(resolve => {
|
|
74
|
+
const hint = options.map((o, idx) => `${idx + 1}) ${o}`).join(' ');
|
|
75
|
+
const defaultHint = options.includes(defaultValue) ? defaultValue : options[0];
|
|
76
|
+
rl.question(`${question} [${hint}] (${defaultHint}): `, answer => {
|
|
77
|
+
const val = answer.trim();
|
|
78
|
+
if (val === '') return resolve(defaultHint);
|
|
79
|
+
const num = parseInt(val, 10);
|
|
80
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return resolve(options[num - 1]);
|
|
81
|
+
if (options.includes(val)) return resolve(val);
|
|
82
|
+
resolve(defaultHint);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createRl() {
|
|
88
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { lang, i, prompt, select, createRl };
|
package/bin/snack-cli.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* snack-cli — 全局脚手架命令
|
|
6
|
+
*
|
|
7
|
+
* 命令:
|
|
8
|
+
* snack-cli init [dir] 初始化新 snack 工程
|
|
9
|
+
* snack-cli create 在当前工程创建新模块模版
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const minimist = require('minimist');
|
|
14
|
+
const fs = require('fs-extra');
|
|
15
|
+
const { version } = require('../package.json');
|
|
16
|
+
const { lang, i, prompt, select, createRl } = require('./lib');
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const argv = minimist(args);
|
|
20
|
+
const script = args[0];
|
|
21
|
+
|
|
22
|
+
// ─── init 命令:初始化新 snack 工程 ──────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
async function runInit() {
|
|
25
|
+
const targetDir = argv._[1] ? path.resolve(process.cwd(), argv._[1]) : process.cwd();
|
|
26
|
+
const dirName = path.basename(targetDir);
|
|
27
|
+
|
|
28
|
+
console.log(`\n${i('initTitle')}\n`);
|
|
29
|
+
|
|
30
|
+
const rl = createRl();
|
|
31
|
+
|
|
32
|
+
const name = await prompt(rl, i('projectName'), dirName);
|
|
33
|
+
const description = await prompt(rl, i('description'), '');
|
|
34
|
+
const author = await prompt(rl, i('author'), '');
|
|
35
|
+
const debugInput = await prompt(rl, i('debugServers'), 'http://127.0.0.1:3000');
|
|
36
|
+
const entryId = await prompt(rl, i('entryPageId'), name);
|
|
37
|
+
const entryType = await prompt(rl, i('entryPageType'), 'portal');
|
|
38
|
+
const reactVersion = await select(rl, i('reactVersion'), ['17', '18', '19'], '19');
|
|
39
|
+
|
|
40
|
+
rl.close();
|
|
41
|
+
|
|
42
|
+
const debugServers = debugInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
43
|
+
|
|
44
|
+
const reactVersionMap = {
|
|
45
|
+
'17': { react: '^17.0.0', reactDom: '^17.0.0', typesReact: '^17.0.0', typesReactDom: '^17.0.0' },
|
|
46
|
+
'18': { react: '^18.0.0', reactDom: '^18.0.0', typesReact: '^18.0.0', typesReactDom: '^18.0.0' },
|
|
47
|
+
'19': { react: '^19.0.0', reactDom: '^19.0.0', typesReact: '^19.0.0', typesReactDom: '^19.0.0' },
|
|
48
|
+
};
|
|
49
|
+
const reactDeps = reactVersionMap[reactVersion];
|
|
50
|
+
|
|
51
|
+
// ── package.json ─────────────────────────────────────────────────────────
|
|
52
|
+
const pkg = {
|
|
53
|
+
name,
|
|
54
|
+
version: '1.0.0',
|
|
55
|
+
description,
|
|
56
|
+
scripts: {
|
|
57
|
+
start: 'snack-scripts start',
|
|
58
|
+
build: 'snack-scripts build',
|
|
59
|
+
entry: 'snack-scripts entry'
|
|
60
|
+
},
|
|
61
|
+
input: './src/package',
|
|
62
|
+
output: './dist',
|
|
63
|
+
snack: {
|
|
64
|
+
externals: {},
|
|
65
|
+
buildIgnore: [],
|
|
66
|
+
devPackage: [],
|
|
67
|
+
entry: {
|
|
68
|
+
name: 'entry',
|
|
69
|
+
id: entryId,
|
|
70
|
+
type: entryType,
|
|
71
|
+
title: '',
|
|
72
|
+
favicon: '',
|
|
73
|
+
mobile: { id: `${entryId}_mobile`, type: entryType }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
author,
|
|
77
|
+
license: 'ISC',
|
|
78
|
+
dependencies: {
|
|
79
|
+
'@snack-kit/core': '^0.3.0',
|
|
80
|
+
'react': reactDeps.react,
|
|
81
|
+
'react-dom': reactDeps.reactDom
|
|
82
|
+
},
|
|
83
|
+
devDependencies: {
|
|
84
|
+
'@snack-kit/scripts': `^${version}`,
|
|
85
|
+
'@types/react': reactDeps.typesReact,
|
|
86
|
+
'@types/react-dom': reactDeps.typesReactDom
|
|
87
|
+
},
|
|
88
|
+
dev: { debug: debugServers }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ── tsconfig.json ─────────────────────────────────────────────────────────
|
|
92
|
+
const tsconfig = {
|
|
93
|
+
compilerOptions: {
|
|
94
|
+
target: 'es5',
|
|
95
|
+
module: 'es6',
|
|
96
|
+
outDir: './dist/',
|
|
97
|
+
baseUrl: '.',
|
|
98
|
+
experimentalDecorators: true,
|
|
99
|
+
allowJs: true,
|
|
100
|
+
skipLibCheck: true,
|
|
101
|
+
esModuleInterop: true,
|
|
102
|
+
allowSyntheticDefaultImports: true,
|
|
103
|
+
strict: true,
|
|
104
|
+
forceConsistentCasingInFileNames: true,
|
|
105
|
+
moduleResolution: 'node',
|
|
106
|
+
resolveJsonModule: true,
|
|
107
|
+
isolatedModules: true,
|
|
108
|
+
jsx: 'react-jsx'
|
|
109
|
+
},
|
|
110
|
+
include: ['src'],
|
|
111
|
+
exclude: ['node_modules', 'dist']
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// ── .gitignore ────────────────────────────────────────────────────────────
|
|
115
|
+
const gitignore = `node_modules
|
|
116
|
+
dist
|
|
117
|
+
.snack
|
|
118
|
+
*.local
|
|
119
|
+
.DS_Store
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// ── src/env.d.ts ──────────────────────────────────────────────────────────
|
|
123
|
+
const envDts = `declare module '*.svg'
|
|
124
|
+
declare module '*.png'
|
|
125
|
+
declare module '*.jpg'
|
|
126
|
+
declare module '*.jpeg'
|
|
127
|
+
declare module '*.gif'
|
|
128
|
+
declare module '*.bmp'
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
// ── 写入文件 ───────────────────────────────────────────────────────────────
|
|
132
|
+
await fs.ensureDir(targetDir);
|
|
133
|
+
await fs.outputJSON(path.join(targetDir, 'package.json'), pkg, { spaces: 4 });
|
|
134
|
+
await fs.outputJSON(path.join(targetDir, 'tsconfig.json'), tsconfig, { spaces: 4 });
|
|
135
|
+
await fs.outputFile(path.join(targetDir, '.gitignore'), gitignore);
|
|
136
|
+
await fs.ensureDir(path.join(targetDir, 'src/package'));
|
|
137
|
+
await fs.outputFile(path.join(targetDir, 'src/env.d.ts'), envDts);
|
|
138
|
+
|
|
139
|
+
console.log(`
|
|
140
|
+
${i('projectCreated')} ${targetDir}
|
|
141
|
+
|
|
142
|
+
${i('nextSteps')}
|
|
143
|
+
${targetDir !== process.cwd() ? `cd ${argv._[1]}\n ` : ''}npm install
|
|
144
|
+
npm run start
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── create 命令:在当前工程创建新模块模版 ────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async function runCreate() {
|
|
151
|
+
const projectPath = argv.project || process.cwd();
|
|
152
|
+
const pkgPath = path.resolve(projectPath, 'package.json');
|
|
153
|
+
if (!fs.existsSync(pkgPath)) {
|
|
154
|
+
console.error(i('errNoPkg'));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const projectPkg = fs.readJSONSync(pkgPath);
|
|
158
|
+
const packageDir = path.resolve(projectPath, projectPkg.input || 'src/package');
|
|
159
|
+
|
|
160
|
+
console.log(`\n${i('createTitle')}\n`);
|
|
161
|
+
|
|
162
|
+
const rl = createRl();
|
|
163
|
+
|
|
164
|
+
const moduleName = await prompt(rl, i('moduleName'), lang === 'zh' ? '新模块' : 'New Module');
|
|
165
|
+
let dirName = await prompt(rl, i('moduleDirName'), '');
|
|
166
|
+
const description = await prompt(rl, i('description'), '');
|
|
167
|
+
const author = await prompt(rl, i('author'), projectPkg.author || '');
|
|
168
|
+
const withSetting = (await prompt(rl, i('withSetting'), 'y')).toLowerCase() === 'y';
|
|
169
|
+
|
|
170
|
+
rl.close();
|
|
171
|
+
|
|
172
|
+
if (!dirName) {
|
|
173
|
+
console.error(i('errDirRequired'));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
dirName = dirName.charAt(0).toUpperCase() + dirName.slice(1);
|
|
178
|
+
const moduleDir = path.join(packageDir, dirName);
|
|
179
|
+
const className = dirName;
|
|
180
|
+
|
|
181
|
+
if (fs.existsSync(moduleDir)) {
|
|
182
|
+
console.error(`${i('errDirExists')} ${moduleDir}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── snack.json ────────────────────────────────────────────────────────────
|
|
187
|
+
const snackJson = {
|
|
188
|
+
name: moduleName,
|
|
189
|
+
version: '0.0.0.1',
|
|
190
|
+
author,
|
|
191
|
+
description
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ── index.ts ──────────────────────────────────────────────────────────────
|
|
195
|
+
const indexTs = `export * from './src/index';
|
|
196
|
+
${withSetting ? "export * from './src/setting';\n" : ''}`;
|
|
197
|
+
|
|
198
|
+
// ── src/index.tsx ─────────────────────────────────────────────────────────
|
|
199
|
+
const indexTsx = `import React from 'react';
|
|
200
|
+
import { Snack, SnackData } from '@snack-kit/core';
|
|
201
|
+
import './style/index.scss';
|
|
202
|
+
|
|
203
|
+
interface Props extends SnackData {}
|
|
204
|
+
|
|
205
|
+
export class ${className} extends Snack {
|
|
206
|
+
constructor(data: Props = {}) {
|
|
207
|
+
super(data);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public $component(props: Props): React.ReactNode {
|
|
211
|
+
return (
|
|
212
|
+
<div className={'${className.toLowerCase()}'}>
|
|
213
|
+
{/* TODO */}
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default ${className};
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
// ── src/setting.tsx ───────────────────────────────────────────────────────
|
|
223
|
+
const settingTsx = `import React from 'react';
|
|
224
|
+
import { SnackSetting } from '@snack-kit/core';
|
|
225
|
+
import Main from './index';
|
|
226
|
+
import './style/setting.scss';
|
|
227
|
+
|
|
228
|
+
interface Props {}
|
|
229
|
+
|
|
230
|
+
export class ${className}Setting extends SnackSetting {
|
|
231
|
+
constructor(public main: Main, public data: Props) {
|
|
232
|
+
super(data);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public $component(): React.ReactNode {
|
|
236
|
+
return <></>;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
// ── 样式文件 ───────────────────────────────────────────────────────────────
|
|
242
|
+
const indexScss = `.${className.toLowerCase()} {\n}\n`;
|
|
243
|
+
const settingScss = `.${className.toLowerCase()}-setting {\n}\n`;
|
|
244
|
+
|
|
245
|
+
// ── 写入文件 ───────────────────────────────────────────────────────────────
|
|
246
|
+
await fs.outputJSON(path.join(moduleDir, 'snack.json'), snackJson, { spaces: 4 });
|
|
247
|
+
await fs.outputFile(path.join(moduleDir, 'index.ts'), indexTs);
|
|
248
|
+
await fs.outputFile(path.join(moduleDir, 'src/index.tsx'), indexTsx);
|
|
249
|
+
await fs.outputFile(path.join(moduleDir, 'src/style/index.scss'), indexScss);
|
|
250
|
+
|
|
251
|
+
if (withSetting) {
|
|
252
|
+
await fs.outputFile(path.join(moduleDir, 'src/setting.tsx'), settingTsx);
|
|
253
|
+
await fs.outputFile(path.join(moduleDir, 'src/style/setting.scss'), settingScss);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(`
|
|
257
|
+
${i('moduleCreated')} ${moduleDir}
|
|
258
|
+
|
|
259
|
+
${i('filesGenerated')}
|
|
260
|
+
${dirName}/snack.json
|
|
261
|
+
${dirName}/index.ts
|
|
262
|
+
${dirName}/src/index.tsx
|
|
263
|
+
${dirName}/src/style/index.scss${withSetting ? `
|
|
264
|
+
${dirName}/src/setting.tsx
|
|
265
|
+
${dirName}/src/style/setting.scss` : ''}
|
|
266
|
+
`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── 入口路由 ─────────────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
if (script === 'init') {
|
|
272
|
+
runInit().catch(err => { console.error(err); process.exit(1); });
|
|
273
|
+
} else if (script === 'create') {
|
|
274
|
+
runCreate().catch(err => { console.error(err); process.exit(1); });
|
|
275
|
+
} else if (script === '-v' || script === '--version') {
|
|
276
|
+
console.log(version);
|
|
277
|
+
} else {
|
|
278
|
+
console.error(`snack-cli: unknown command "${script || ''}"\nUsage: snack-cli <init|create|-v>`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* snack-scripts — 项目构建 / 调试命令
|
|
6
|
+
*
|
|
7
|
+
* 命令:
|
|
8
|
+
* snack-scripts start 启动开发调试服务
|
|
9
|
+
* snack-scripts build 生产模式打包
|
|
10
|
+
* snack-scripts entry 打包独立入口页面
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const minimist = require('minimist');
|
|
16
|
+
const fs = require('fs-extra');
|
|
17
|
+
const { version } = require('../package.json');
|
|
18
|
+
const { GetPackageList } = require('../utils/package');
|
|
19
|
+
const { i } = require('./lib');
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const argv = minimist(args, { default: { project: null } });
|
|
23
|
+
const scriptIndex = args.findIndex(x => x === 'build' || x === 'start' || x === 'entry');
|
|
24
|
+
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
|
25
|
+
|
|
26
|
+
let projectPath = argv.project || process.cwd();
|
|
27
|
+
let templatePath = argv.templatePath
|
|
28
|
+
? path.resolve(projectPath, argv.templatePath)
|
|
29
|
+
: path.resolve(__dirname, '../template');
|
|
30
|
+
|
|
31
|
+
const snackPath = path.resolve(__dirname, '../config/webpack.snack.config.js');
|
|
32
|
+
const indexHTMLPath = path.resolve(__dirname, '../config/webpack.index.config.js');
|
|
33
|
+
const loaderConfigPath = path.resolve(__dirname, '../config/webpack.loader.config.js');
|
|
34
|
+
const devConfigPath = path.resolve(__dirname, '../config/webpack.dev.config.js');
|
|
35
|
+
const entryConfigPath = path.resolve(__dirname, '../config/webpack.entry.config.js');
|
|
36
|
+
|
|
37
|
+
const platform = process.platform;
|
|
38
|
+
const webpackFile = platform === 'win32' ? 'webpack.cmd' : 'webpack';
|
|
39
|
+
|
|
40
|
+
// 始终使用 snack-scripts 自身内置的 webpack CLI,保证 CLI 进程与配置文件加载同一 webpack 实例,
|
|
41
|
+
// 避免与宿主项目 node_modules 中的 webpack 产生多实例 tapable 冲突。
|
|
42
|
+
const webpackBin = path.resolve(__dirname, '../node_modules/.bin/', webpackFile);
|
|
43
|
+
|
|
44
|
+
// ─── webpack 构建命令 ─────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function execWebpack(wpArgs) {
|
|
47
|
+
return new Promise(resolve => {
|
|
48
|
+
const ps = spawn(webpackBin, wpArgs, { stdio: 'inherit' });
|
|
49
|
+
ps.on('error', e => { console.log(e); resolve(false); });
|
|
50
|
+
ps.on('exit', () => resolve(true));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function clearOutput(output) {
|
|
55
|
+
fs.removeSync(output);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function check(output) {
|
|
59
|
+
try {
|
|
60
|
+
const list = await fs.readdir(output);
|
|
61
|
+
for (const dirname of list) {
|
|
62
|
+
if (dirname === '__common__') continue;
|
|
63
|
+
const snackjson = path.join(output, dirname, 'snack.json');
|
|
64
|
+
if (!await fs.exists(snackjson)) continue;
|
|
65
|
+
const index = path.join(output, dirname, 'index.js');
|
|
66
|
+
if (!await fs.exists(index)) {
|
|
67
|
+
console.error(`${i('integrityFileMissing')}: ${index}`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('snack check: ', err);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
console.log(i('integrityPass'));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function run() {
|
|
80
|
+
const projectPackageJSON = fs.readJSONSync(path.resolve(projectPath, 'package.json'));
|
|
81
|
+
if (projectPackageJSON.name === 'snack-cli') {
|
|
82
|
+
throw i('errNameConflict');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
process.env.SNACK_CLI_VERSION = version;
|
|
86
|
+
process.env.RUN_SCRIPT = script;
|
|
87
|
+
process.env.SNACK_SCRIPTS_PATH = __dirname;
|
|
88
|
+
process.env.PROJECT_COMMON_NAME = '__common__';
|
|
89
|
+
process.env.PROJECT_PACKAGE_JSON = JSON.stringify(projectPackageJSON);
|
|
90
|
+
process.env.PROJECT_TYPE = projectPackageJSON.name;
|
|
91
|
+
process.env.PROJECT_PATH = projectPath;
|
|
92
|
+
process.env.PROJECT_PACKAGE_PATH = path.resolve(projectPath, projectPackageJSON.input || 'src/package');
|
|
93
|
+
process.env.PROJECT_TEMP_PATH = path.resolve(projectPath, '.snack');
|
|
94
|
+
process.env.PROJECT_TEMPLATE_PATH = path.join(process.env.PROJECT_TEMP_PATH, 'template');
|
|
95
|
+
|
|
96
|
+
await fs.remove(process.env.PROJECT_TEMPLATE_PATH);
|
|
97
|
+
const err = await fs.copy(templatePath, process.env.PROJECT_TEMPLATE_PATH);
|
|
98
|
+
if (err) {
|
|
99
|
+
console.error(i('errTempDir'));
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
process.env.PROJECT_SNACK_CONFIG = JSON.stringify(projectPackageJSON.snack || {});
|
|
104
|
+
|
|
105
|
+
if (script === 'entry') {
|
|
106
|
+
if (!projectPackageJSON.snack?.entry) throw i('errNoEntry');
|
|
107
|
+
process.env.RUN_MODE = 'production';
|
|
108
|
+
process.env.PROJECT_OUT_PATH = path.resolve(projectPath, projectPackageJSON.snack.entry.name || 'entry');
|
|
109
|
+
console.log(i('createEntry'));
|
|
110
|
+
await execWebpack(['--mode=production', `--config=${entryConfigPath}`]);
|
|
111
|
+
} else {
|
|
112
|
+
if (script === 'start') {
|
|
113
|
+
process.env.RUN_MODE = 'development';
|
|
114
|
+
process.env.PROJECT_OUT_PATH = path.resolve(process.env.PROJECT_TEMP_PATH, 'temp');
|
|
115
|
+
} else {
|
|
116
|
+
process.env.RUN_MODE = 'production';
|
|
117
|
+
process.env.PROJECT_OUT_PATH = path.resolve(projectPath, projectPackageJSON.output || 'dist', projectPackageJSON.name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
process.env.PROJECT_PACKAGE_LIST = JSON.stringify(GetPackageList(process.env.PROJECT_PACKAGE_PATH));
|
|
121
|
+
|
|
122
|
+
console.log(`${i('clearOutputDir')} "${process.env.PROJECT_OUT_PATH}" ...`);
|
|
123
|
+
clearOutput(process.env.PROJECT_OUT_PATH);
|
|
124
|
+
console.log(i('clearOutputDirEnd'));
|
|
125
|
+
|
|
126
|
+
if (script === 'start') {
|
|
127
|
+
console.log(i('devServerRunning'));
|
|
128
|
+
await execWebpack(['serve', '--mode=development', `--config=${devConfigPath}`]);
|
|
129
|
+
} else {
|
|
130
|
+
console.log(i('createLoaderAndHTML'));
|
|
131
|
+
await Promise.all([
|
|
132
|
+
execWebpack(['--mode=production', `--config=${loaderConfigPath}`]),
|
|
133
|
+
execWebpack(['--mode=production', `--config=${indexHTMLPath}`])
|
|
134
|
+
]);
|
|
135
|
+
console.log(i('createLoaderAndHTMLEnd'));
|
|
136
|
+
console.log(i('createSnack'));
|
|
137
|
+
await execWebpack(['--mode=production', `--config=${snackPath}`]);
|
|
138
|
+
console.log(i('createSnackEnd'));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (process.env.RUN_MODE !== 'development' && !await check(process.env.PROJECT_OUT_PATH)) {
|
|
142
|
+
fs.removeSync(process.env.PROJECT_OUT_PATH);
|
|
143
|
+
throw i('errIntegrity');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`
|
|
148
|
+
_____ __ ________ ____
|
|
149
|
+
/ ___/____ ____ ______/ /__ / ____/ / / _/
|
|
150
|
+
\\__ \\/ __ \\/ __ \`/ ___/ //_/ / / / / / /
|
|
151
|
+
___/ / / / / /_/ / /__/ ,< / /___/ /____/ /
|
|
152
|
+
/____/_/ /_/\\__,_/\\___/_/|_| \\____/_____/___/ v.${version}
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── 入口路由 ─────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
if (script === 'start' || script === 'build' || script === 'entry') {
|
|
159
|
+
run().catch(err => { console.error(err); process.exit(1); });
|
|
160
|
+
} else {
|
|
161
|
+
console.error(`snack-scripts: unknown command "${script || ''}"\nUsage: snack-scripts <start|build|entry>`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
@@ -100,7 +100,7 @@ const configOverridesPath = path.resolve(process.env.PROJECT_PATH, 'config-overr
|
|
|
100
100
|
if (fs.existsSync(configOverridesPath)) {
|
|
101
101
|
try {
|
|
102
102
|
const overrides = require(configOverridesPath);
|
|
103
|
-
conf = overrides(conf);
|
|
103
|
+
conf = overrides(conf, webpack);
|
|
104
104
|
console.log('snack config overrides');
|
|
105
105
|
} catch (err) {
|
|
106
106
|
console.error('SnackCLI Error:', err);
|
package/config/webpack.shared.js
CHANGED
|
@@ -151,7 +151,22 @@ function createSwcRule(withRefresh = false) {
|
|
|
151
151
|
*/
|
|
152
152
|
const styleRule = {
|
|
153
153
|
test: /\.(css|sass|scss)$/,
|
|
154
|
-
use: [
|
|
154
|
+
use: [
|
|
155
|
+
'style-loader',
|
|
156
|
+
'css-loader',
|
|
157
|
+
{
|
|
158
|
+
loader: 'sass-loader',
|
|
159
|
+
options: {
|
|
160
|
+
// 使用 legacy API:getModernWebpackImporter 在 sass-loader 13.x 中未实现,
|
|
161
|
+
// 导致 @import '~xxx' 的 ~ 前缀无法解析;legacy API 有完整的 webpack resolver 支持
|
|
162
|
+
webpackImporter: true,
|
|
163
|
+
sassOptions: {
|
|
164
|
+
// 屏蔽 Dart Sass 1.80+ 对 @import 语法的弃用警告,旧项目迁移期间无需修改 SCSS
|
|
165
|
+
silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'legacy-js-api']
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
155
170
|
};
|
|
156
171
|
|
|
157
172
|
/**
|
|
@@ -219,17 +234,12 @@ const filesystemCache = {
|
|
|
219
234
|
};
|
|
220
235
|
|
|
221
236
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
237
|
+
* 获取 webpack 实例。
|
|
238
|
+
* 始终使用 snack-scripts 自身内置的 webpack,保证 CLI 进程与 config 文件加载同一个实例,
|
|
239
|
+
* 避免与宿主项目 node_modules 中的 webpack 产生多实例 tapable 冲突。
|
|
225
240
|
*/
|
|
226
241
|
function resolveProjectWebpack() {
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
return require(require.resolve('webpack', { paths: searchPaths }));
|
|
230
|
-
} catch (_) {
|
|
231
|
-
return require('webpack');
|
|
232
|
-
}
|
|
242
|
+
return require('webpack');
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
/**
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snack-kit/scripts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "snack-cli package scripts Powered by Para FED",
|
|
5
5
|
"bin": {
|
|
6
|
-
"snack-
|
|
7
|
-
"snack-
|
|
6
|
+
"snack-cli": "./bin/snack-cli.js",
|
|
7
|
+
"snack-scripts": "./bin/snack-scripts.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"tsconfig.json"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"dev:start": "node bin/
|
|
20
|
-
"dev:build": "node bin/
|
|
19
|
+
"dev:start": "node bin/snack-scripts.js start --project=/Users/liujia/工作/snack/pam-snack",
|
|
20
|
+
"dev:build": "node bin/snack-scripts.js build --project=/Users/liujia/工作/snack3-test",
|
|
21
21
|
"release": "npm publish --access public",
|
|
22
22
|
"release:beta": "npm publish --tag=beta --access public",
|
|
23
23
|
"reinstall": "rm -rf node_modules && npm i"
|
package/utils/package.js
CHANGED
|
@@ -62,6 +62,21 @@ module.exports.GetPackageList = (projectPath) => {
|
|
|
62
62
|
const runtimeEntry = path.resolve(snackPath, 'src', 'index.tsx');
|
|
63
63
|
if (!fs.existsSync(runtimeEntry)) throw new Error(`Package(${dirName}) entry "${runtimeEntry}" is missing`);
|
|
64
64
|
|
|
65
|
+
// 检测 dirName 是否作为具名导出存在于入口文件链中
|
|
66
|
+
const exportPattern = new RegExp(
|
|
67
|
+
`export\\s+class\\s+${dirName}\\b` + // export class Foo
|
|
68
|
+
`|export\\s*\\{[^}]*\\bas\\s+${dirName}\\b[^}]*\\}` + // export { X as Foo }
|
|
69
|
+
`|export\\s*\\{[^}]*\\b${dirName}\\b[^}]*\\}` // export { Foo }
|
|
70
|
+
);
|
|
71
|
+
const filesToCheck = [entry, runtimeEntry];
|
|
72
|
+
const hasExport = filesToCheck.some(f => exportPattern.test(fs.readFileSync(f, 'utf-8')));
|
|
73
|
+
if (!hasExport) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Package(${dirName}) missing named export "${dirName}".\n` +
|
|
76
|
+
` Expected: "export class ${dirName}" or "export { ... as ${dirName} }" in index.ts or src/index.tsx`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
acc.push({
|
|
66
81
|
name: dirName.toLocaleLowerCase(),
|
|
67
82
|
dirName,
|
package/bin/main.js
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { spawn } = require('child_process');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const readline = require('readline');
|
|
7
|
-
const minimist = require('minimist');
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const { version } = require('../package.json');
|
|
10
|
-
const { GetPackageList } = require(path.resolve(__dirname, '../utils/package'));
|
|
11
|
-
|
|
12
|
-
const args = process.argv.slice(2);
|
|
13
|
-
const argv = minimist(args, { default: { project: null } });
|
|
14
|
-
const scriptIndex = args.findIndex(x => x === 'build' || x === 'start' || x === 'entry' || x === 'init' || x === 'create');
|
|
15
|
-
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
|
16
|
-
|
|
17
|
-
let projectPath = argv.project || process.cwd();
|
|
18
|
-
let templatePath = argv.templatePath
|
|
19
|
-
? path.resolve(projectPath, argv.templatePath)
|
|
20
|
-
: path.resolve(__dirname, '../template');
|
|
21
|
-
|
|
22
|
-
const snackPath = path.resolve(__dirname, '../config/webpack.snack.config.js');
|
|
23
|
-
const indexHTMLPath = path.resolve(__dirname, '../config/webpack.index.config.js');
|
|
24
|
-
const loaderConfigPath = path.resolve(__dirname, '../config/webpack.loader.config.js');
|
|
25
|
-
const devConfigPath = path.resolve(__dirname, '../config/webpack.dev.config.js');
|
|
26
|
-
const entryConfigPath = path.resolve(__dirname, '../config/webpack.entry.config.js');
|
|
27
|
-
|
|
28
|
-
const platform = process.platform;
|
|
29
|
-
const webpackFile = platform === 'win32' ? 'webpack.cmd' : 'webpack';
|
|
30
|
-
const isNpm = fs.existsSync(path.resolve(process.cwd(), './node_modules/.bin/', webpackFile));
|
|
31
|
-
|
|
32
|
-
// ─── 语言检测 ─────────────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
function detectLang() {
|
|
35
|
-
const env = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || process.env.LC_MESSAGES || '';
|
|
36
|
-
return env.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const lang = detectLang();
|
|
40
|
-
const t = {
|
|
41
|
-
// init
|
|
42
|
-
initTitle: { zh: ' Snack 项目初始化', en: ' Snack Project Init' },
|
|
43
|
-
projectName: { zh: '项目名称', en: 'Project name' },
|
|
44
|
-
description: { zh: '项目描述', en: 'Description' },
|
|
45
|
-
author: { zh: '作者', en: 'Author' },
|
|
46
|
-
debugServers: { zh: '调试服务器(多个用逗号分隔)', en: 'Debug servers (comma separated)' },
|
|
47
|
-
entryPageId: { zh: '入口页面 ID', en: 'Entry page id' },
|
|
48
|
-
entryPageType: { zh: '入口页面类型', en: 'Entry page type' },
|
|
49
|
-
reactVersion: { zh: 'React 版本', en: 'React version' },
|
|
50
|
-
projectCreated: { zh: ' 项目已创建:', en: ' Project created at:' },
|
|
51
|
-
nextSteps: { zh: ' 后续步骤:', en: ' Next steps:' },
|
|
52
|
-
// create
|
|
53
|
-
createTitle: { zh: ' Snack 模块创建', en: ' Snack Module Create' },
|
|
54
|
-
moduleName: { zh: '模块名称', en: 'Module name' },
|
|
55
|
-
moduleDirName: { zh: '模块目录名(PascalCase)', en: 'Module directory name (PascalCase)' },
|
|
56
|
-
withSetting: { zh: '生成配置模块?(y/n)', en: 'Generate setting module? (y/n)' },
|
|
57
|
-
moduleCreated: { zh: ' 模块已创建:', en: ' Module created:' },
|
|
58
|
-
filesGenerated: { zh: ' 已生成文件:', en: ' Files generated:' },
|
|
59
|
-
// errors
|
|
60
|
-
errNoPkg: { zh: '错误:当前目录没有 package.json,请在 snack 工程根目录下执行该命令', en: 'Error: No package.json found. Run this command in a snack project root.' },
|
|
61
|
-
errDirRequired: { zh: '错误:模块目录名不能为空', en: 'Error: Module directory name is required' },
|
|
62
|
-
errDirExists: { zh: '错误:模块目录已存在:', en: 'Error: Module directory already exists:' },
|
|
63
|
-
errNameConflict: { zh: '请修改 snack-cli package.json "name" 的默认名称,该名称将在部署时作为分类目录', en: 'Please change the default "name" in package.json. It will be used as a category directory during deployment.' },
|
|
64
|
-
errNoEntry: { zh: 'package.json 未找到 snack.entry 配置项', en: 'snack.entry config not found in package.json' },
|
|
65
|
-
errTempDir: { zh: '临时目录创建失败', en: 'Failed to create temp directory' },
|
|
66
|
-
errIntegrity: { zh: '完整性校验失败', en: 'Integrity check failed' },
|
|
67
|
-
// build logs
|
|
68
|
-
clearOutputDir: { zh: '清理输出目录', en: 'clear output dir' },
|
|
69
|
-
clearOutputDirEnd: { zh: '清理完成', en: 'clear output dir end' },
|
|
70
|
-
devServerRunning: { zh: 'Snack 开发服务器运行中...', en: 'Snack DevServer Runing...' },
|
|
71
|
-
createLoaderAndHTML: { zh: '生成 loader.js 和入口 HTML(并行)...', en: 'create loader.js & entry HTML (parallel) ...' },
|
|
72
|
-
createLoaderAndHTMLEnd: { zh: '生成完成', en: 'create loader.js & entry HTML end' },
|
|
73
|
-
createSnack: { zh: '生成 snack 模块...', en: 'create snack ...' },
|
|
74
|
-
createSnackEnd: { zh: '生成完成', en: 'create snack end' },
|
|
75
|
-
createEntry: { zh: '生成入口页...', en: 'create entry ...' },
|
|
76
|
-
integrityPass: { zh: '完整性校验通过', en: 'Integrity check passed' },
|
|
77
|
-
integrityFileMissing: { zh: '完整性校验:文件缺失', en: 'snack check: file is missing' },
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
function i(key) {
|
|
81
|
-
return t[key][lang];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── 交互式提问工具 ───────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
function prompt(rl, question, defaultValue) {
|
|
87
|
-
return new Promise(resolve => {
|
|
88
|
-
const hint = defaultValue !== undefined ? ` (${defaultValue})` : '';
|
|
89
|
-
rl.question(`${question}${hint}: `, answer => {
|
|
90
|
-
const val = answer.trim();
|
|
91
|
-
resolve(val === '' && defaultValue !== undefined ? defaultValue : val);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function select(rl, question, options, defaultValue) {
|
|
97
|
-
return new Promise(resolve => {
|
|
98
|
-
const hint = options.map((o, i) => `${i + 1}) ${o}`).join(' ');
|
|
99
|
-
const defaultHint = options.includes(defaultValue) ? defaultValue : options[0];
|
|
100
|
-
rl.question(`${question} [${hint}] (${defaultHint}): `, answer => {
|
|
101
|
-
const val = answer.trim();
|
|
102
|
-
if (val === '') return resolve(defaultHint);
|
|
103
|
-
const num = parseInt(val, 10);
|
|
104
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return resolve(options[num - 1]);
|
|
105
|
-
if (options.includes(val)) return resolve(val);
|
|
106
|
-
resolve(defaultHint);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ─── init 命令:初始化新 snack 工程 ──────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
async function runInit() {
|
|
114
|
-
const targetDir = argv._[1] ? path.resolve(process.cwd(), argv._[1]) : process.cwd();
|
|
115
|
-
const dirName = path.basename(targetDir);
|
|
116
|
-
|
|
117
|
-
console.log(`\n${i('initTitle')}\n`);
|
|
118
|
-
|
|
119
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
120
|
-
|
|
121
|
-
const name = await prompt(rl, i('projectName'), dirName);
|
|
122
|
-
const description = await prompt(rl, i('description'), '');
|
|
123
|
-
const author = await prompt(rl, i('author'), '');
|
|
124
|
-
const debugInput = await prompt(rl, i('debugServers'), 'http://127.0.0.1:3000');
|
|
125
|
-
const entryId = await prompt(rl, i('entryPageId'), name);
|
|
126
|
-
const entryType = await prompt(rl, i('entryPageType'), 'portal');
|
|
127
|
-
const reactVersion = await select(rl, i('reactVersion'), ['17', '18', '19'], '19');
|
|
128
|
-
|
|
129
|
-
rl.close();
|
|
130
|
-
|
|
131
|
-
const debugServers = debugInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
132
|
-
|
|
133
|
-
const reactVersionMap = {
|
|
134
|
-
'17': { react: '^17.0.0', reactDom: '^17.0.0', typesReact: '^17.0.0', typesReactDom: '^17.0.0' },
|
|
135
|
-
'18': { react: '^18.0.0', reactDom: '^18.0.0', typesReact: '^18.0.0', typesReactDom: '^18.0.0' },
|
|
136
|
-
'19': { react: '^19.0.0', reactDom: '^19.0.0', typesReact: '^19.0.0', typesReactDom: '^19.0.0' },
|
|
137
|
-
};
|
|
138
|
-
const reactDeps = reactVersionMap[reactVersion];
|
|
139
|
-
|
|
140
|
-
// ── package.json ─────────────────────────────────────────────────────────
|
|
141
|
-
const pkg = {
|
|
142
|
-
name,
|
|
143
|
-
version: '1.0.0',
|
|
144
|
-
description,
|
|
145
|
-
scripts: {
|
|
146
|
-
start: 'snack-cli start',
|
|
147
|
-
build: 'snack-cli build',
|
|
148
|
-
entry: 'snack-cli entry'
|
|
149
|
-
},
|
|
150
|
-
input: './src/package',
|
|
151
|
-
output: './dist',
|
|
152
|
-
snack: {
|
|
153
|
-
externals: {},
|
|
154
|
-
buildIgnore: [],
|
|
155
|
-
devPackage: [],
|
|
156
|
-
entry: {
|
|
157
|
-
name: 'entry',
|
|
158
|
-
id: entryId,
|
|
159
|
-
type: entryType,
|
|
160
|
-
title: '',
|
|
161
|
-
favicon: '',
|
|
162
|
-
mobile: { id: `${entryId}_mobile`, type: entryType }
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
author,
|
|
166
|
-
license: 'ISC',
|
|
167
|
-
dependencies: {
|
|
168
|
-
'@snack-kit/core': '^0.3.0',
|
|
169
|
-
'react': reactDeps.react,
|
|
170
|
-
'react-dom': reactDeps.reactDom
|
|
171
|
-
},
|
|
172
|
-
devDependencies: {
|
|
173
|
-
'@types/react': reactDeps.typesReact,
|
|
174
|
-
'@types/react-dom': reactDeps.typesReactDom
|
|
175
|
-
},
|
|
176
|
-
dev: { debug: debugServers }
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// ── tsconfig.json ─────────────────────────────────────────────────────────
|
|
180
|
-
const tsconfig = {
|
|
181
|
-
compilerOptions: {
|
|
182
|
-
target: 'es5',
|
|
183
|
-
module: 'es6',
|
|
184
|
-
outDir: './dist/',
|
|
185
|
-
baseUrl: '.',
|
|
186
|
-
experimentalDecorators: true,
|
|
187
|
-
allowJs: true,
|
|
188
|
-
skipLibCheck: true,
|
|
189
|
-
esModuleInterop: true,
|
|
190
|
-
allowSyntheticDefaultImports: true,
|
|
191
|
-
strict: true,
|
|
192
|
-
forceConsistentCasingInFileNames: true,
|
|
193
|
-
moduleResolution: 'node',
|
|
194
|
-
resolveJsonModule: true,
|
|
195
|
-
isolatedModules: true,
|
|
196
|
-
jsx: 'react-jsx'
|
|
197
|
-
},
|
|
198
|
-
include: ['src'],
|
|
199
|
-
exclude: ['node_modules', 'dist']
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// ── .gitignore ────────────────────────────────────────────────────────────
|
|
203
|
-
const gitignore = `node_modules
|
|
204
|
-
dist
|
|
205
|
-
.snack
|
|
206
|
-
*.local
|
|
207
|
-
.DS_Store
|
|
208
|
-
`;
|
|
209
|
-
|
|
210
|
-
// ── src/env.d.ts ──────────────────────────────────────────────────────────
|
|
211
|
-
const envDts = `declare module '*.svg'
|
|
212
|
-
declare module '*.png'
|
|
213
|
-
declare module '*.jpg'
|
|
214
|
-
declare module '*.jpeg'
|
|
215
|
-
declare module '*.gif'
|
|
216
|
-
declare module '*.bmp'
|
|
217
|
-
`;
|
|
218
|
-
|
|
219
|
-
// ── 写入文件 ───────────────────────────────────────────────────────────────
|
|
220
|
-
await fs.ensureDir(targetDir);
|
|
221
|
-
await fs.outputJSON(path.join(targetDir, 'package.json'), pkg, { spaces: 4 });
|
|
222
|
-
await fs.outputJSON(path.join(targetDir, 'tsconfig.json'), tsconfig, { spaces: 4 });
|
|
223
|
-
await fs.outputFile(path.join(targetDir, '.gitignore'), gitignore);
|
|
224
|
-
await fs.ensureDir(path.join(targetDir, 'src/package'));
|
|
225
|
-
await fs.outputFile(path.join(targetDir, 'src/env.d.ts'), envDts);
|
|
226
|
-
|
|
227
|
-
console.log(`
|
|
228
|
-
${i('projectCreated')} ${targetDir}
|
|
229
|
-
|
|
230
|
-
${i('nextSteps')}
|
|
231
|
-
${targetDir !== process.cwd() ? `cd ${argv._[1]}\n ` : ''}npm install
|
|
232
|
-
npm run start
|
|
233
|
-
`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ─── create 命令:在当前工程创建新模块模版 ────────────────────────────────────
|
|
237
|
-
|
|
238
|
-
async function runCreate() {
|
|
239
|
-
const pkgPath = path.resolve(projectPath, 'package.json');
|
|
240
|
-
if (!fs.existsSync(pkgPath)) {
|
|
241
|
-
console.error(i('errNoPkg'));
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
const projectPkg = fs.readJSONSync(pkgPath);
|
|
245
|
-
const packageDir = path.resolve(projectPath, projectPkg.input || 'src/package');
|
|
246
|
-
|
|
247
|
-
console.log(`\n${i('createTitle')}\n`);
|
|
248
|
-
|
|
249
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
250
|
-
|
|
251
|
-
const moduleName = await prompt(rl, i('moduleName'), lang === 'zh' ? '新模块' : 'New Module');
|
|
252
|
-
let dirName = await prompt(rl, i('moduleDirName'), '');
|
|
253
|
-
const description = await prompt(rl, i('description'), '');
|
|
254
|
-
const author = await prompt(rl, i('author'), projectPkg.author || '');
|
|
255
|
-
const withSetting = (await prompt(rl, i('withSetting'), 'y')).toLowerCase() === 'y';
|
|
256
|
-
|
|
257
|
-
rl.close();
|
|
258
|
-
|
|
259
|
-
if (!dirName) {
|
|
260
|
-
console.error(i('errDirRequired'));
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
dirName = dirName.charAt(0).toUpperCase() + dirName.slice(1);
|
|
265
|
-
const moduleDir = path.join(packageDir, dirName);
|
|
266
|
-
const className = dirName;
|
|
267
|
-
|
|
268
|
-
if (fs.existsSync(moduleDir)) {
|
|
269
|
-
console.error(`${i('errDirExists')} ${moduleDir}`);
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ── snack.json ────────────────────────────────────────────────────────────
|
|
274
|
-
const snackJson = {
|
|
275
|
-
name: moduleName,
|
|
276
|
-
version: '0.0.0.1',
|
|
277
|
-
author,
|
|
278
|
-
description
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// ── index.ts ──────────────────────────────────────────────────────────────
|
|
282
|
-
const indexTs = `export * from './src/index';
|
|
283
|
-
${withSetting ? "export * from './src/setting';\n" : ''}`;
|
|
284
|
-
|
|
285
|
-
// ── src/index.tsx ─────────────────────────────────────────────────────────
|
|
286
|
-
const indexTsx = `import React from 'react';
|
|
287
|
-
import { Snack, SnackData } from '@snack-kit/core';
|
|
288
|
-
import './style/index.scss';
|
|
289
|
-
|
|
290
|
-
interface Props extends SnackData {}
|
|
291
|
-
|
|
292
|
-
export class ${className} extends Snack {
|
|
293
|
-
constructor(data: Props = {}) {
|
|
294
|
-
super(data);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
public $component(props: Props): React.ReactNode {
|
|
298
|
-
return (
|
|
299
|
-
<div className={'${className.toLowerCase()}'}>
|
|
300
|
-
{/* TODO */}
|
|
301
|
-
</div>
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export default ${className};
|
|
307
|
-
`;
|
|
308
|
-
|
|
309
|
-
// ── src/setting.tsx ───────────────────────────────────────────────────────
|
|
310
|
-
const settingTsx = `import React from 'react';
|
|
311
|
-
import { SnackSetting } from '@snack-kit/core';
|
|
312
|
-
import Main from './index';
|
|
313
|
-
import './style/setting.scss';
|
|
314
|
-
|
|
315
|
-
interface Props {}
|
|
316
|
-
|
|
317
|
-
export class ${className}Setting extends SnackSetting {
|
|
318
|
-
constructor(public main: Main, public data: Props) {
|
|
319
|
-
super(data);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
public $component(): React.ReactNode {
|
|
323
|
-
return <></>;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
`;
|
|
327
|
-
|
|
328
|
-
// ── 样式文件 ───────────────────────────────────────────────────────────────
|
|
329
|
-
const indexScss = `.${className.toLowerCase()} {\n}\n`;
|
|
330
|
-
const settingScss = `.${className.toLowerCase()}-setting {\n}\n`;
|
|
331
|
-
|
|
332
|
-
// ── 写入文件 ───────────────────────────────────────────────────────────────
|
|
333
|
-
await fs.outputJSON(path.join(moduleDir, 'snack.json'), snackJson, { spaces: 4 });
|
|
334
|
-
await fs.outputFile(path.join(moduleDir, 'index.ts'), indexTs);
|
|
335
|
-
await fs.outputFile(path.join(moduleDir, 'src/index.tsx'), indexTsx);
|
|
336
|
-
await fs.outputFile(path.join(moduleDir, 'src/style/index.scss'), indexScss);
|
|
337
|
-
|
|
338
|
-
if (withSetting) {
|
|
339
|
-
await fs.outputFile(path.join(moduleDir, 'src/setting.tsx'), settingTsx);
|
|
340
|
-
await fs.outputFile(path.join(moduleDir, 'src/style/setting.scss'), settingScss);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
console.log(`
|
|
344
|
-
${i('moduleCreated')} ${moduleDir}
|
|
345
|
-
|
|
346
|
-
${i('filesGenerated')}
|
|
347
|
-
${dirName}/snack.json
|
|
348
|
-
${dirName}/index.ts
|
|
349
|
-
${dirName}/src/index.tsx
|
|
350
|
-
${dirName}/src/style/index.scss${withSetting ? `
|
|
351
|
-
${dirName}/src/setting.tsx
|
|
352
|
-
${dirName}/src/style/setting.scss` : ''}
|
|
353
|
-
`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ─── webpack 构建命令 ─────────────────────────────────────────────────────────
|
|
357
|
-
|
|
358
|
-
function execWebpack(args) {
|
|
359
|
-
return new Promise(resolve => {
|
|
360
|
-
const ps = isNpm
|
|
361
|
-
? spawn('webpack', args, { stdio: 'inherit', shell: true })
|
|
362
|
-
: spawn(path.resolve(__dirname, '../node_modules/.bin/', webpackFile), args, { stdio: 'inherit' });
|
|
363
|
-
ps.on('error', e => { console.log(e); resolve(false); });
|
|
364
|
-
ps.on('exit', () => resolve(true));
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function clearOutput(output) {
|
|
369
|
-
fs.removeSync(output);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function check(output) {
|
|
373
|
-
try {
|
|
374
|
-
const list = await fs.readdir(output);
|
|
375
|
-
for (const dirname of list) {
|
|
376
|
-
if (dirname === '__common__') continue;
|
|
377
|
-
const snackjson = path.join(output, dirname, 'snack.json');
|
|
378
|
-
if (!await fs.exists(snackjson)) continue;
|
|
379
|
-
const index = path.join(output, dirname, 'index.js');
|
|
380
|
-
if (!await fs.exists(index)) {
|
|
381
|
-
console.error(`${i('integrityFileMissing')}: ${index}`);
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
} catch (err) {
|
|
386
|
-
console.error('snack check: ', err);
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
console.log(i('integrityPass'));
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async function run(script) {
|
|
394
|
-
const projectPackageJSON = fs.readJSONSync(path.resolve(projectPath, 'package.json'));
|
|
395
|
-
if (projectPackageJSON.name === 'snack-cli') {
|
|
396
|
-
throw i('errNameConflict');
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
process.env.SNACK_CLI_VERSION = version;
|
|
400
|
-
process.env.RUN_SCRIPT = script;
|
|
401
|
-
process.env.SNACK_SCRIPTS_PATH = __dirname;
|
|
402
|
-
process.env.PROJECT_COMMON_NAME = '__common__';
|
|
403
|
-
process.env.PROJECT_PACKAGE_JSON = JSON.stringify(projectPackageJSON);
|
|
404
|
-
process.env.PROJECT_TYPE = projectPackageJSON.name;
|
|
405
|
-
process.env.PROJECT_PATH = projectPath;
|
|
406
|
-
process.env.PROJECT_PACKAGE_PATH = path.resolve(projectPath, projectPackageJSON.input || 'src/package');
|
|
407
|
-
process.env.PROJECT_TEMP_PATH = path.resolve(projectPath, '.snack');
|
|
408
|
-
process.env.PROJECT_TEMPLATE_PATH = path.join(process.env.PROJECT_TEMP_PATH, 'template');
|
|
409
|
-
|
|
410
|
-
await fs.remove(process.env.PROJECT_TEMPLATE_PATH);
|
|
411
|
-
const err = await fs.copy(templatePath, process.env.PROJECT_TEMPLATE_PATH);
|
|
412
|
-
if (err) {
|
|
413
|
-
console.error(i('errTempDir'));
|
|
414
|
-
throw err;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
process.env.PROJECT_SNACK_CONFIG = JSON.stringify(projectPackageJSON.snack || {});
|
|
418
|
-
|
|
419
|
-
if (script === 'entry') {
|
|
420
|
-
if (!projectPackageJSON.snack?.entry) throw i('errNoEntry');
|
|
421
|
-
process.env.RUN_MODE = 'production';
|
|
422
|
-
process.env.PROJECT_OUT_PATH = path.resolve(projectPath, projectPackageJSON.snack.entry.name || 'entry');
|
|
423
|
-
console.log(i('createEntry'));
|
|
424
|
-
await execWebpack(['--mode=production', `--config=${entryConfigPath}`]);
|
|
425
|
-
} else {
|
|
426
|
-
if (script === 'start') {
|
|
427
|
-
process.env.RUN_MODE = 'development';
|
|
428
|
-
process.env.PROJECT_OUT_PATH = path.resolve(process.env.PROJECT_TEMP_PATH, 'temp');
|
|
429
|
-
} else {
|
|
430
|
-
process.env.RUN_MODE = 'production';
|
|
431
|
-
process.env.PROJECT_OUT_PATH = path.resolve(projectPath, projectPackageJSON.output || 'dist', projectPackageJSON.name);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
process.env.PROJECT_PACKAGE_LIST = JSON.stringify(GetPackageList(process.env.PROJECT_PACKAGE_PATH));
|
|
435
|
-
|
|
436
|
-
console.log(`${i('clearOutputDir')} "${process.env.PROJECT_OUT_PATH}" ...`);
|
|
437
|
-
clearOutput(process.env.PROJECT_OUT_PATH);
|
|
438
|
-
console.log(i('clearOutputDirEnd'));
|
|
439
|
-
|
|
440
|
-
if (script === 'start') {
|
|
441
|
-
console.log(i('devServerRunning'));
|
|
442
|
-
await execWebpack(['serve', '--mode=development', `--config=${devConfigPath}`]);
|
|
443
|
-
} else {
|
|
444
|
-
console.log(i('createLoaderAndHTML'));
|
|
445
|
-
await Promise.all([
|
|
446
|
-
execWebpack(['--mode=production', `--config=${loaderConfigPath}`]),
|
|
447
|
-
execWebpack(['--mode=production', `--config=${indexHTMLPath}`])
|
|
448
|
-
]);
|
|
449
|
-
console.log(i('createLoaderAndHTMLEnd'));
|
|
450
|
-
console.log(i('createSnack'));
|
|
451
|
-
await execWebpack(['--mode=production', `--config=${snackPath}`]);
|
|
452
|
-
console.log(i('createSnackEnd'));
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (process.env.RUN_MODE !== 'development' && !await check(process.env.PROJECT_OUT_PATH)) {
|
|
456
|
-
fs.removeSync(process.env.PROJECT_OUT_PATH);
|
|
457
|
-
throw i('errIntegrity');
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
console.log(`
|
|
462
|
-
_____ __ ________ ____
|
|
463
|
-
/ ___/____ ____ ______/ /__ / ____/ / / _/
|
|
464
|
-
\\__ \\/ __ \\/ __ \`/ ___/ //_/ / / / / / /
|
|
465
|
-
___/ / / / / /_/ / /__/ ,< / /___/ /____/ /
|
|
466
|
-
/____/_/ /_/\\__,_/\\___/_/|_| \\____/_____/___/ v.${version}
|
|
467
|
-
`);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// ─── 入口路由 ─────────────────────────────────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
if (script === 'init') {
|
|
473
|
-
runInit().catch(err => { console.error(err); process.exit(1); });
|
|
474
|
-
} else if (script === 'create') {
|
|
475
|
-
runCreate().catch(err => { console.error(err); process.exit(1); });
|
|
476
|
-
} else {
|
|
477
|
-
run(script).catch(err => { console.error(err); process.exit(1); });
|
|
478
|
-
}
|