@epxing/cli 0.3.1 → 0.4.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 CHANGED
@@ -3,40 +3,41 @@
3
3
  独立的工程开发工具,包含交互菜单、开发服务、工程与产品库构建、创建、Vue3 迁移、静态检查和升级技能安装。CLI 使用自己的版本号,目标平台由 `--epx-frame` 指定;发布包没有 npm 运行时依赖。启动、构建调用当前工程的脚本及本地依赖;迁移代码与框架仓库的 `tools/` 共用,打包时编译为独立 Node 程序。
4
4
 
5
5
  ```powershell
6
- npm install -g @epxing/cli@0.3.1 --registry https://registry.npmjs.org/
7
- epx # ↑↓ + Enter 选择;Esc / Ctrl+C 取消
8
- epx dev --port 3000
9
- epx build --mode prd
10
- epx lib # 产品默认懒加载;平台目录构建完整聚合组件库
11
- epx lib --full # 产品完整打包
12
- epx lib --lazy # 显式选择懒加载,兼容已有命令
13
- epx create my-product --epx-frame <目标平台版本>
14
- epx migrate E:/products/old-product --epx-frame <目标平台版本>
15
- epx migrate E:/products/old-product --epx-frame <目标平台版本> --apply
16
- epx skill install --dir E:/products/my-product
6
+ npm install -g @epxing/cli@0.4.0 --registry https://registry.npmjs.org/
7
+ epxing # ↑↓ + Enter 选择;Esc / Ctrl+C 取消
8
+ epxing dev --port 3000
9
+ epxing build --mode prd
10
+ epxing lib # 产品默认懒加载;平台目录构建完整聚合组件库
11
+ epxing lib --full # 产品完整打包
12
+ epxing lib --lazy # 显式选择懒加载,兼容已有命令
13
+ epxing create my-product --epx-frame <目标平台版本>
14
+ epxing migrate E:/products/old-product --epx-frame <目标平台版本>
15
+ epxing migrate E:/products/old-product --epx-frame <目标平台版本> --apply
16
+ epxing skill install --dir E:/products/my-product
17
17
  ```
18
18
 
19
- 如已全局安装旧的 `epx-frame` CLI,先用 `npm uninstall -g epx-frame` 释放 `epx` 命令。产品工程里的 `epx-frame` 依赖保留。工具不在安装阶段运行迁移、下载平台或改产品文件;产品仍需安装目标框架依赖才能开发和构建。
19
+ 从 0.4.0 起终端命令改为 `epxing`,不再注册 `epx`,因此可与旧 `epx-frame` CLI 的 `epx` 共存。包名仍是 `@epxing/cli`,产品依赖仍是 `epx-frame`。更新同一个全局 CLI 包时 npm 会替换其旧命令;不要使用 `--force` 覆盖其他包的命令。工具不在安装阶段运行迁移、下载平台或改产品文件;产品仍需安装目标框架依赖才能开发和构建。
20
20
 
21
- 使用 fnm/nvm 时,不同 Node 版本的全局包分别安装。切换到日常使用的 Node 版本后更新 CLI,再用 `epx help` 确认开头为 `@epxing/cli`、`epx --version` 确认版本。旧仓库的 `pnpm epx` 仍是该仓库自己的脚本。
21
+ 使用 fnm/nvm 时,不同 Node 版本的全局包分别安装。切换到日常使用的 Node 版本后更新 CLI,再用 `epxing help` 确认开头为 `@epxing/cli`、`epxing --version` 确认版本。旧仓库的 `pnpm epx` 仍是该仓库自己的脚本。
22
22
 
23
23
  ## 命令
24
24
 
25
25
  | 命令 | 行为 |
26
26
  | --- | --- |
27
- | `epx` / `epx menu` | 交互选择操作、工程目录、环境、端口和构建方式;管道支持数字输入 |
28
- | `epx dev` / `epx serve` | 使用工程 `dev`、`serve` 或 `start` 脚本启动服务 |
29
- | `epx build` | 使用工程的构建脚本;只有 `build:lib` 的产品自动构建产品库 |
30
- | `epx lib` / `epx build --lib` / `epx lib --lazy` | 产品默认懒加载,优先 `build:lib:lazy`,否则使用 `build:lib` / `lib`;平台聚合库仍完整构建 |
31
- | `epx lib --full` / `epx build --lib --full` | 产品完整打包,明确设置 `LAZY_LIB=false`;兼容旧 `--target lib` 脚本 |
32
- | `epx preview` | 使用 `preview` / `vite:preview`;没有脚本时使用工程本地 Vite |
33
- | `epx create` | 无参数进入创建引导,也可用参数直接创建 |
34
- | `epx migrate` / `epx upgrade` | 无参数引导:目标版本、迁移范围、预览、确认应用 |
35
- | `epx skill install` | 安装技能,支持自定义目录和明确覆盖 |
36
- | `epx scan` / `epx verify` | 迁移风险扫描 / 本地与线上组件清单校验 |
37
- | `epx lint [--fix]` / `epx circular` | 执行工程已有的代码检查、循环依赖检查脚本 |
38
- | `epx remote` / `epx pkg <包名\|all>` | 使用平台已有 `build:remote` / `build:pkg` / `build:pkgs` 脚本 |
39
- | `epx help` / `epx --version` | 帮助 / 工具版本 |
27
+ | `epxing` / `epxing menu` | 交互选择操作、工程目录、环境、端口和构建方式;管道支持数字输入 |
28
+ | `epxing dev` / `epxing serve` | 使用工程 `dev`、`serve` 或 `start` 脚本启动服务 |
29
+ | `epxing build` | 使用工程的构建脚本;只有 `build:lib` 的产品自动构建产品库 |
30
+ | `epxing lib` / `epxing build --lib` / `epxing lib --lazy` | 产品默认懒加载,优先 `build:lib:lazy`,否则使用 `build:lib` / `lib`;平台聚合库仍完整构建 |
31
+ | `epxing lib --full` / `epxing build --lib --full` | 产品完整打包,明确设置 `LAZY_LIB=false`;兼容旧 `--target lib` 脚本 |
32
+ | `epxing preview` | 使用 `preview` / `vite:preview`;没有脚本时使用工程本地 Vite |
33
+ | `epxing create` | 无参数进入创建引导,也可用参数直接创建 |
34
+ | `epxing migrate` / `epxing upgrade` | 无参数引导:目标版本、迁移范围、预览、确认应用 |
35
+ | `epxing skill install` | 安装技能,支持自定义目录和明确覆盖 |
36
+ | `epxing scan` / `epxing verify` | 迁移风险扫描 / 本地与线上组件清单校验 |
37
+ | `epxing theme [目录] [--apply]` | 补修产品/项目主题样式,默认只读预览;迁移时自动执行 |
38
+ | `epxing lint [--fix]` / `epxing circular` | 执行工程已有的代码检查、循环依赖检查脚本 |
39
+ | `epxing remote` / `epxing pkg <包名\|all>` | 使用平台已有 `build:remote` / `build:pkg` / `build:pkgs` 脚本 |
40
+ | `epxing help` / `epxing --version` | 帮助 / 工具版本 |
40
41
 
41
42
  工程命令支持 `--dir <目录>`,默认从当前目录向上找到最近的 `package.json`;不会改为 CLI 全局安装目录。没有项目脚本时,dev/build/preview 可使用项目已安装的 Vite,不会自动下载构建工具。工程仍需先安装自身依赖。
42
43
 
@@ -46,11 +47,13 @@ epx skill install --dir E:/products/my-product
46
47
 
47
48
  `--mode/-m`、`--port/-p` 支持旧的 `-mprd`、`-p3000` 写法。build 优先匹配 `build:<mode>`;`localize` 也匹配 `build:local`,其余情况向构建脚本传入 `--mode`。平台 `build:component` 包含多步构建,不追加 `--mode`,环境由工程脚本统一配置。
48
49
 
49
- 平台 `epx build` 保留 `--thin`、`--no-designer`、`--no-dashboard`;产品工程不接受平台裁剪选项。需向工程脚本透传其他参数时使用 `--`,如 `epx dev -- --host 0.0.0.0`。脚本原有的 pre/post 生命周期、自定义环境和多步构建会保留,失败退出码向上传递。
50
+ 平台 `epxing build` 保留 `--thin`、`--no-designer`、`--no-dashboard`;产品工程不接受平台裁剪选项。需向工程脚本透传其他参数时使用 `--`,如 `epxing dev -- --host 0.0.0.0`。脚本原有的 pre/post 生命周期、自定义环境和多步构建会保留,失败退出码向上传递。
50
51
 
51
52
  `create` 和 `migrate --apply` 自动写入 `runtime: "vue3"`。带参数直接调用时 `--epx-frame` 必填,交互引导也会要求输入目标平台。迁移默认预览;apply 要求目标 Git 工作树干净。实际执行 `.vue` 转换时才按需安装并缓存 gogocode。标识表示迁移目标,仍需完成转换、构建和业务复测。
52
53
 
53
- `epx scan <目录>` 扫描静态风险,`epx verify <目录> --online <地址|文件>` 对照组件清单。技能安装到 `.claude/skills/epxing-frontend-vue3-migrate/`,可用 `--skills-dir` 指定其他客户端要求的位置。已有技能默认不覆盖;确认替换使用 `--force`。平台基类源码分析仍需准备对应版本源码或文档。
54
+ 0.4.0 的迁移会同步适配平台主题:常见静态字号(12/14/16/20px)、圆角(2/4/8px,包括多角简写)和明确的语义颜色改为平台 CSS 变量,保留原值兜底。覆盖 Vue 样式块、template/JSX/render 内联样式和 src 中的 CSS/SCSS/Less;不改 scoped,不导入全局主题,不改圆形、胶囊、图标、打印及动画中的固定设计。动态样式、特殊字号/圆角、缩进式 Sass/Stylus 等列入复核清单,需按技能 E2 继续分析。返回码 2 表示有待复核项;不代表自动修复没有写入。已有 Vue3 工程可运行 `epxing theme <目录>` 预览,审查后加 `--apply`;无须重跑依赖迁移。最终产物须验证平台字号、圆角、明暗与密度切换、懒加载和样式隔离。
55
+
56
+ `epxing scan <目录>` 扫描静态风险,`epxing verify <目录> --online <地址|文件>` 对照组件清单。技能安装到 `.claude/skills/epxing-frontend-vue3-migrate/`,可用 `--skills-dir` 指定其他客户端要求的位置。已有技能默认不覆盖;确认替换使用 `--force`。平台基类源码分析仍需准备对应版本源码或文档。
54
57
 
55
58
  在框架源码仓库发布工具,不需要构建平台 `lib/`:
56
59
 
@@ -19,3 +19,28 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
22
+
23
+
24
+ @vue/compiler-dom
25
+
26
+ The MIT License (MIT)
27
+
28
+ Copyright (c) 2018-present, Yuxi (Evan) You
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining a copy
31
+ of this software and associated documentation files (the "Software"), to deal
32
+ in the Software without restriction, including without limitation the rights
33
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
+ copies of the Software, and to permit persons to whom the Software is
35
+ furnished to do so, subject to the following conditions:
36
+
37
+ The above copyright notice and this permission notice shall be included in
38
+ all copies or substantial portions of the Software.
39
+
40
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
+ THE SOFTWARE.
package/dist/cli.mjs CHANGED
@@ -112,7 +112,7 @@ async function interactive (cwd, action) {
112
112
  }
113
113
  if (action === 'check') {
114
114
  action = await prompts.select('检查类型', choices([
115
- ['源码迁移风险扫描', 'scan'], ['本地 / 线上组件清单核对', 'verify'], ['项目代码检查', 'lint'], ['项目循环依赖检查', 'circular']
115
+ ['源码迁移风险扫描', 'scan'], ['本地 / 线上组件清单核对', 'verify'], ['项目代码检查', 'lint'], ['项目循环依赖检查', 'circular'], ['产品主题适配(预览)', 'theme']
116
116
  ]))
117
117
  }
118
118
  if (action === 'advanced') {
@@ -121,7 +121,7 @@ async function interactive (cwd, action) {
121
121
  ]))
122
122
  }
123
123
  const dir = await prompts.required('工程目录', '.')
124
- if (action === 'scan' || action === 'verify') {
124
+ if (['scan', 'verify', 'theme'].includes(action)) {
125
125
  const online = action === 'verify' ? await prompts.text('线上 JS 地址或文件(留空仅列本地组件)') : ''
126
126
  return dispatch([action, dir, ...(online ? ['--online', online] : [])])
127
127
  }
@@ -167,22 +167,23 @@ export async function main (argv = process.argv.slice(2), cwd = process.env.INIT
167
167
  if (['help', '--help', '-h'].includes(command)) {
168
168
  console.log(`@epxing/cli · 工程开发与升级工具(独立于平台版本)
169
169
 
170
- epx / epx menu 交互菜单(↑↓ + Enter;管道支持数字输入)
171
- epx dev [-m <模式>] [-p <端口>] [--dir <工程目录>]
172
- epx build [-m <模式>] [--thin] [--no-designer] [--no-dashboard] [--dir <目录>]
173
- epx lib [--full | --lazy] [-m <模式>] [--dir <目录>] 产品默认懒加载,--full 完整打包;平台聚合库保持完整
174
- epx build --lib 同 epx lib
175
- epx preview [-p <端口>] [--dir <目录>]
176
- epx create <名称|路径> --epx-frame <目标版本> [--dir <目录>]
177
- epx migrate [项目目录] --epx-frame <目标版本> [--apply] [--transform-vue] [--to-vite]
178
- epx scan [项目目录]
179
- epx verify [项目目录] [--online <线上JS地址|文件>]
180
- epx skill install [--dir <项目目录>] [--skills-dir <技能目录>] [--force]
181
- epx lint [--fix] [--dir <目录>]
182
- epx circular [--dir <目录>]
183
- epx remote [--dir <平台目录>]
184
- epx pkg <包名|all> [--dir <平台目录>]
185
- epx --version
170
+ epxing / epxing menu 交互菜单(↑↓ + Enter;管道支持数字输入)
171
+ epxing dev [-m <模式>] [-p <端口>] [--dir <工程目录>]
172
+ epxing build [-m <模式>] [--thin] [--no-designer] [--no-dashboard] [--dir <目录>]
173
+ epxing lib [--full | --lazy] [-m <模式>] [--dir <目录>] 产品默认懒加载,--full 完整打包;平台聚合库保持完整
174
+ epxing build --lib 同 epxing lib
175
+ epxing preview [-p <端口>] [--dir <目录>]
176
+ epxing create <名称|路径> --epx-frame <目标版本> [--dir <目录>]
177
+ epxing migrate [项目目录] --epx-frame <目标版本> [--apply] [--transform-vue] [--to-vite]
178
+ epxing scan [项目目录]
179
+ epxing theme [项目目录] [--apply] 主题变量补修,默认只读;迁移时自动执行
180
+ epxing verify [项目目录] [--online <线上JS地址|文件>]
181
+ epxing skill install [--dir <项目目录>] [--skills-dir <技能目录>] [--force]
182
+ epxing lint [--fix] [--dir <目录>]
183
+ epxing circular [--dir <目录>]
184
+ epxing remote [--dir <平台目录>]
185
+ epxing pkg <包名|all> [--dir <平台目录>]
186
+ epxing --version
186
187
 
187
188
  dev / build / lib 等执行当前工程的 package.json 脚本,使用工程本地依赖。
188
189
  工程命令可用 -- 后的参数透传给脚本;serve / upgrade 分别是 dev / migrate 的别名。
@@ -196,14 +197,14 @@ create、migrate 不带参数时进入引导;直接带参数时 --epx-frame
196
197
  return 0
197
198
  }
198
199
  if (command === 'skill') {
199
- if (args.shift() !== 'install') throw new Error('用法:epx skill install [--dir <项目目录>]')
200
+ if (args.shift() !== 'install') throw new Error('用法:epxing skill install [--dir <项目目录>]')
200
201
  return installSkill(args, cwd)
201
202
  }
202
203
  if (command === 'create') {
203
204
  const target = requireTarget(args)
204
205
  const chosenDir = takeOption(args, '--dir')
205
206
  const nameOrPath = args.shift()
206
- if (!nameOrPath || args.length || nameOrPath.startsWith('-')) throw new Error('用法:epx create <名称|路径> --epx-frame <目标版本> [--dir <目录>]')
207
+ if (!nameOrPath || args.length || nameOrPath.startsWith('-')) throw new Error('用法:epxing create <名称|路径> --epx-frame <目标版本> [--dir <目录>]')
207
208
  const dir = path.resolve(cwd, chosenDir || nameOrPath)
208
209
  const name = /[\\/]/.test(nameOrPath) ? path.basename(path.resolve(cwd, nameOrPath)) : nameOrPath
209
210
  const { scaffold } = await import(pathToFileURL(path.join(payload, 'tools/epx-create/scaffold.mjs')).href)
@@ -217,13 +218,19 @@ create、migrate 不带参数时进入引导;直接带参数时 --epx-frame
217
218
  const dir = dirs[0] || '.'
218
219
  return runTool('epx-migrate/migrate.mjs', [path.resolve(cwd, dir), ...args.filter(arg => arg !== dir), '--epx-frame', target], cwd)
219
220
  }
221
+ if (command === 'theme') {
222
+ const apply = args.includes('--apply')
223
+ args = args.filter(arg => arg !== '--apply')
224
+ if (args.length > 1 || args.some(arg => arg.startsWith('-'))) throw new Error('用法:epxing theme [项目目录] [--apply]')
225
+ return runTool('epx-migrate/fix-theme-styles.mjs', [path.resolve(cwd, args[0] || '.'), ...(apply ? ['--apply'] : [])], cwd)
226
+ }
220
227
  if (command === 'scan' || command === 'verify') {
221
228
  const online = command === 'verify' ? takeOption(args, '--online') : null
222
- if (args.length > 1 || args.some(arg => arg.startsWith('-'))) throw new Error(`用法:epx ${command} [项目目录]${command === 'verify' ? ' [--online <地址|文件>]' : ''}`)
229
+ if (args.length > 1 || args.some(arg => arg.startsWith('-'))) throw new Error(`用法:epxing ${command} [项目目录]${command === 'verify' ? ' [--online <地址|文件>]' : ''}`)
223
230
  const script = command === 'scan' ? 'detect-vue3-risks.mjs' : 'verify-components.mjs'
224
231
  return runTool('epx-migrate/' + script, [path.resolve(cwd, args[0] || '.'), ...(online ? ['--online', online] : [])], cwd)
225
232
  }
226
- throw new Error(`未知命令:${command},运行 epx help 查看用法。`)
233
+ throw new Error(`未知命令:${command},运行 epxing help 查看用法。`)
227
234
  }
228
235
 
229
236
  let isMain = false
@@ -38,7 +38,7 @@ function configInScript (script) {
38
38
  function verifyLazy (outDir, previousStamp) {
39
39
  const file = path.join(outDir, 'index.manifest.json')
40
40
  if (!fs.existsSync(file) || manifestStamp(outDir) === previousStamp || !fs.existsSync(path.join(outDir, 'index.full.js'))) {
41
- throw new Error('没有生成新的按需库清单和完整兜底包,不能视为懒加载构建成功。请检查构建脚本或使用 epx lib --full。')
41
+ throw new Error('没有生成新的按需库清单和完整兜底包,不能视为懒加载构建成功。请检查构建脚本或使用 epxing lib --full。')
42
42
  }
43
43
  const manifest = JSON.parse(fs.readFileSync(file, 'utf8'))
44
44
  const entries = Object.entries(manifest.components || {})
@@ -37,7 +37,7 @@ export async function configureLibrary (env, options) {
37
37
  if (!file) throw new Error('无法确定产品库入口 build.lib.entry')
38
38
  const source = fs.readFileSync(path.resolve(root, file), 'utf8')
39
39
  const match = /import\.meta\.glob\(\s*(['"])([^'"]+)\1\s*,\s*\{\s*(?:import\s*:\s*['"]default['"]\s*,\s*)?eager\s*:\s*true\s*(?:,\s*import\s*:\s*['"]default['"]\s*)?,?\s*\}\s*\)/.exec(source)
40
- if (!match) throw new Error('按需库需要入口中的 eager import.meta.glob;请先迁移产品入口,或使用 epx lib --full。')
40
+ if (!match) throw new Error('按需库需要入口中的 eager import.meta.glob;请先迁移产品入口,或使用 epxing lib --full。')
41
41
  config.plugins.push(lazyLib({ enabled: true, glob: match[2], pkgName }))
42
42
  }
43
43
  }
package/dist/project.mjs CHANGED
@@ -34,7 +34,7 @@ function npmEntry () {
34
34
  } catch (error) { /* 继续查找 */ }
35
35
  }
36
36
  const found = candidates.find(file => file && fs.existsSync(file))
37
- if (!found) throw new Error('未找到 npm-cli.js。请使用带 npm 的 Node.js 环境运行 epx。')
37
+ if (!found) throw new Error('未找到 npm-cli.js。请使用带 npm 的 Node.js 环境运行 epxing。')
38
38
  return found
39
39
  }
40
40
 
@@ -114,7 +114,7 @@ export function projectPlan (command, options, project) {
114
114
  let libraryMode
115
115
  const platform = project.pkg.name === 'epx-frame' || !!scripts['build:component']
116
116
  if (options.lib) {
117
- if (command !== 'build') throw new Error('--lib 用于 epx build;也可直接使用 epx lib')
117
+ if (command !== 'build') throw new Error('--lib 用于 epxing build;也可直接使用 epxing lib')
118
118
  command = 'lib'
119
119
  }
120
120
  if (command === 'build' && !scripts.build && scripts['build:lib']) command = 'lib'
@@ -124,7 +124,7 @@ export function projectPlan (command, options, project) {
124
124
  if (options.port && !['dev', 'preview'].includes(command)) throw new Error('--port 仅用于 dev 或 preview')
125
125
  if (options.lazy && command !== 'lib') throw new Error('--lazy 仅用于 lib 或 build --lib')
126
126
  if (options.fix && command !== 'lint') throw new Error('--fix 仅用于 lint')
127
- if ((options.thin || options.noDesigner || options.noDashboard) && (command !== 'build' || !platform)) throw new Error('平台裁剪选项仅用于平台源码中的 epx build')
127
+ if ((options.thin || options.noDesigner || options.noDashboard) && (command !== 'build' || !platform)) throw new Error('平台裁剪选项仅用于平台源码中的 epxing build')
128
128
  if (options.mode && !['dev', 'build', 'lib', 'preview'].includes(command)) throw new Error('--mode 仅用于 dev、build、lib 或 preview')
129
129
  if (command === 'dev') {
130
130
  script = pick('dev', 'serve', 'start')
@@ -146,19 +146,19 @@ export function projectPlan (command, options, project) {
146
146
  env.LAZY_LIB = options.full ? 'false' : 'true'
147
147
  script = options.full ? pick('build:lib:full', 'build:lib', 'lib', 'build:lib:lazy') : pick('build:lib:lazy', 'build:lib', 'lib')
148
148
  if (!script && /--target(?:=|\s+)lib\b/.test(scripts.build || '')) {
149
- if (!options.full) throw new Error('旧 webpack 产品请先迁移到 Vite,或使用 epx lib --full 完整打包')
149
+ if (!options.full) throw new Error('旧 webpack 产品请先迁移到 Vite,或使用 epxing lib --full 完整打包')
150
150
  script = 'build'
151
151
  }
152
152
  }
153
153
  if (!script) throw new Error('当前工程缺少库构建脚本。产品请提供 build:lib(按需库可用 build:lib:lazy),平台使用 build:component。')
154
- if (script === 'build:component' && options.mode) throw new Error('平台聚合库包含多步构建,请在 build:component 中配置环境;epx lib 不向该聚合脚本追加 --mode。')
154
+ if (script === 'build:component' && options.mode) throw new Error('平台聚合库包含多步构建,请在 build:component 中配置环境;epxing lib 不向该聚合脚本追加 --mode。')
155
155
  } else if (command === 'preview') {
156
156
  script = pick('preview', 'vite:preview')
157
157
  if (!script) binary = 'vite'
158
158
  } else if (command === 'remote') script = pick('build:remote')
159
159
  else if (command === 'pkg') {
160
160
  const name = options.positional[0]
161
- if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) throw new Error('用法:epx pkg <包名|all> [--dir <平台目录>]')
161
+ if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) throw new Error('用法:epxing pkg <包名|all> [--dir <平台目录>]')
162
162
  script = name === 'all' ? pick('build:pkgs') : pick('build:pkg')
163
163
  if (name !== 'all') env.PKG = name
164
164
  } else if (command === 'lint') {
@@ -10,6 +10,8 @@ description: epx 产品/项目远程库 Vue2/webpack → Vue3/Vite 升级 + 按
10
10
  epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK` 配 url、运行时加载)
11
11
  从 Vue2/webpack 迁到 Vue3/Vite,并可选切到「清单 + 组件按需加载」形态的完整流程与踩坑清单。
12
12
 
13
+ 独立 CLI 从 `@epxing/cli@0.4.0` 起使用 `epxing` 命令(包名不变,平台依赖仍为 `epx-frame`)。例如 `epxing migrate <产品目录> --epx-frame <目标版本> --apply`;同时执行日期格式和主题样式迁移。已升级工程可用 `epxing theme <目录> --apply` 单独补修。仓库原有 `pnpm epx` 是本地脚本,不等同于全局命令。
14
+
13
15
  **核心判断:这条链上的失败几乎全是静默的。** 构建绿、控制台干净、组件就是不出来。
14
16
  所以每一步都要有**判据**,别靠"看着没报错"。
15
17
 
@@ -41,7 +43,7 @@ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK`
41
43
  | ① 依赖迁移 | `MIGRATION-REPORT.md` 的人工清单**逐条看过**——里面写着的坑会真的咬人 |
42
44
  | ② .vue 转换 | `npx eslint src --ext .js,.jsx,.vue` 能跑(迁移会删掉 eslint 生态,先修工具链) |
43
45
  | ③ 切 Vite | `npm run build:lib` exit 0 **且** `lib/` 里有 `index.js`/`index.css`/`index.deps.json` |
44
- | ④ dev 壳 | 浏览器里 `#app.__vue_app__._context.components` 能数到你的组件个数;对自定义页、继承组件和多分支 render,核对实际根节点的样式命名空间及关键元素的 `getComputedStyle` |
46
+ | ④ dev 壳 | 浏览器里 `#app.__vue_app__._context.components` 能数到你的组件个数;对自定义页、继承组件和多分支 render,核对实际 DOM 与 CSS 选择器是否匹配。样式隔离变更还须用最终产物通过 F15 的组件内、组件外及跨页面检查 |
45
47
  | ⑤ 按需 | `index.manifest.json` 组件数 == `.vue` 文件数,**且路径兜底名 0 个** |
46
48
 
47
49
  第⑤条那个"路径兜底名"是最容易漏的:注册名抽不到就退成 `hrcomponentsSearch` 这种,
@@ -55,6 +57,10 @@ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK`
55
57
  | `__epxInstalledLibs__` 有记录但 app 里 0 个组件 | 记账写在 install 之前,产品幂等守卫空转 |
56
58
  | 注册名变成 `xxxcomponentsYyy` | `extractName` 没抽到 `name`(按需形态特有) |
57
59
  | 组件出来了但完全没样式 | 先查样式表是否加载、实际 DOM 能否匹配选择器;常见原因是 CSS 与 chunk 不同批(`data-v` hash 对不上),或根节点漏了 `.epx-xxx` 样式命名空间(F19) |
60
+ | 隔离测试样式正常,实际页面中继承组件的图片/标题失去样式 | 检查平台包装组件及 Element Plus 插槽的完整渲染链;根有 `data-v-*` 不代表插槽内后代也有。按 F15 核对选择器与继承值,在有效专属容器内局部使用 `:deep()` |
61
+ | 升级后工具栏 `el-select` 把相邻按钮挤到下一行 | Element Plus 选择框默认宽度为 100%;Vue3 产品不再走旧 render 垫片的宽度兜底,按 F20 在产品工具栏显式设置宽度和布局 |
62
+ | 日期选择器显示错误日期,但绑定值/数据时点正确 | 控件还用 fecha 的 `yyyy-MM-dd`,Element Plus 使用 Day.js 的 `YYYY-MM-DD`;按 F21 自动修复产品/项目源码,原生 Vue3 包不依赖旧垫片 |
63
+ | 平台已切换字号、圆角或明暗,产品样式仍固定 | 按 E2 迁移硬编码及动态样式,消费平台主题变量;不在产品重新导入整套主题,不删除 scoped |
58
64
  | 图片 404,路径是 `/assets/xxx.png` | 嵌套构建没继承 `assetsInlineLimit` |
59
65
  | `Cannot read properties of undefined (reading 'i18n')` | recorder-core 执行序(模块图里没有那条边) |
60
66
  | `[vuex] duplicate getter key` | 库被装了两次(本地 + 远程) |
@@ -71,7 +77,13 @@ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK`
71
77
 
72
78
  > **本轮新增(postAppoint 实证)**:
73
79
  > - `fix-jsx-vue2` 除自动改 4 类 JSX 属性(`domProps*`/`nativeOn*`/`scopedSlots=`),现在还**报告** `<script>` 区的 `slot="xxx"` 具名插槽(第 5 类,需人工改 `v-slots`——因要重组 JSX 结构故不自动改;甄别见 F14);
74
- > - 新增 `detect-vue3-risks.mjs`(`node tools/epx-migrate/detect-vue3-risks.mjs <项目>`):发布前静态扫 scoped 整块失效等风险(R1 = extends+scoped+无 template+裸类选择器;已改 `:deep()` 的不误报;退出码 2 可 CI 拦),见 F15。
80
+ > - `detect-vue3-risks.mjs`(`node tools/epx-migrate/detect-vue3-risks.mjs <项目>`)仅提示待复核模式:R1 = extends+scoped+无 template+裸类选择器,退出码 2 可用于 CI 提醒复核。命中不证明样式失效,未命中也不证明隔离安全;不要为消除告警批量删 scoped 或替换成 `:deep()`,按 F15 用实际产物验证。
81
+
82
+ **样式隔离默认保留**:JSX/render、无 template 或继承组件均不能作为删除 scoped 的依据;隔离测试正常也不能排除实际页面中包装组件/插槽链造成的作用域失配。用最终产物检查实际页面的失效元素、祖先和选择器,确认后优先保留 scoped,在有对应作用域属性的专属容器内对必要后代使用 `:deep()`。若确须去掉 scoped,根命名空间、全部选择器隔离和组件外回归须同时完成,不可留下“先去 scoped、稍后补隔离”的中间结果。详见 F15。
83
+
84
+ **日期格式必须自动补修(产品和项目都适用,含已标识 `runtime: vue3` 的工程)**:迁移器会在不依赖 gogocode 的阶段修复日期控件 `format`、`value-format/valueFormat` 的静态旧 token,覆盖 template、JSX 和常见 render 写法,并将文件/行号写入报告。仅补日期问题时,在平台工程运行 `node tools/epx-migrate/fix-date-formats.mjs <目标工程> --apply`,不用重跑整个依赖迁移。默认不带 `--apply` 为只读预览。
85
+
86
+ 执行技能时,若自动修复器或发布前扫描发现动态格式变量、配置对象、函数/展开属性,继续追踪实际来源并修复能确认的旧格式;不能只记录“人工检查”便算完成。无法确认用途或缺少源配置的项才保留为未解决,并写明原因。不要全仓替换 `yyyy/dd`,也不改后端协议、其它日期库、分钟 `mm` 或星期 `ddd/dddd`。用最终产物核对初始回显、选择后的绑定值和请求日期一致;例如 `2026-10-20` 不能显示成 `2026-02-01`。详见 [F21](pitfalls.md#f21-element-plus-日期格式-token-迁移遗漏hr-历史组织机构图)。
75
87
 
76
88
  ## 迁移完怎么验:迁移包 vs 线上 三方对比(postAppoint / recruit / hr 实证)
77
89
 
@@ -90,6 +102,8 @@ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK`
90
102
  **运行时实证判据(别靠肉眼 / 文本 / 静态 grep)**:
91
103
  - 组件在不在:`#app.__vue_app__._context.components` 数量 / 组件实例的 `$slots` 键;
92
104
  - 样式生不生效:`getComputedStyle(el)` 比对(容器高度是否塌陷 0、色值 / 尺寸是否对齐线上);
105
+ - 样式是否泄漏:涉及 scoped、`:deep()` 或全局样式变更时,用最终懒加载产物记录外部同名元素(如 `.avatar`、`.container`)加载组件前后的计算样式;覆盖组件加载后、离开页面及进入其他页面的状态。组件内有效且组件外不受影响,才能验收(F15);
106
+ - 工具栏选择框与按钮:核对选择框宽度、父容器布局及 `getBoundingClientRect()` 的纵向中心位置;按 F20 检查升级后不再享有的旧垫片默认值,同时确认表单内整行选择框仍按设计占满可用宽度;
93
107
  - 差异定性:`el-table` 固定列会复制一份 DOM(Vue2 Element UI 行数翻倍、Vue3 Element Plus 不翻)——比对「共 N 条 / 首行文本」而非 DOM 元素数,否则把「翻倍」误当差异;
94
108
  - **静态 grep 到 Vue2 写法 ≠ 运行时一定失效**:失效与否取决于消费方 / 是否被平台全局样式兜住 / 组件运行时兼容(如 JSX `slot=` 在 el-popover reference 失效、在 el-dropdown 正常),必须运行时实证再判改不改。
95
109
 
@@ -118,6 +132,7 @@ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK`
118
132
  `FM_SYSTEM_WEBPACK` 里仍只配那两条 url,其余按相对路径取。
119
133
  3. **`chunks/` 的 `import()` 需要文件服务给 CORS 头**,跨域没配会整库降级。
120
134
  4. `index.deps.json` **必须与 index.js 同目录同名一并上传**,漏传不报错但精确 ensure 会静默退化。
135
+ 5. 样式隔离有改动时,逐个核对修改的 style 块和最终 CSS;完成 F15 的组件外同名元素及跨页面验收。构建和静态扫描通过不能替代这一步;未测项明确记录为待验证。
121
136
 
122
137
  ## 常见错误
123
138