@infly/libs 2.0.43 → 2.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +104 -67
  2. package/{module → adapters/vue2/module}/Permission.js +2 -2
  3. package/{module → adapters/vue2/module}/REST.js +32 -20
  4. package/{module → adapters/vue2/module}/TokenService.js +10 -1
  5. package/adapters/vue2/module/file-export.js +18 -0
  6. package/adapters/vue2/postinstall.js +46 -0
  7. package/adapters/vue2/project-preview.js +152 -0
  8. package/adapters/vue2/script-config.js +26 -0
  9. package/adapters/vue2/store/index.js +1 -0
  10. package/{store → adapters/vue2/store}/modules/user.js +1 -1
  11. package/adapters/vue2/vite/index.js +95 -0
  12. package/adapters/vue2/vite/jest.config.js +11 -0
  13. package/{build → adapters/vue2}/webpack5/webpack.base.js +26 -79
  14. package/bin/cli.js +71 -71
  15. package/build/build-dist/index.js +863 -863
  16. package/build/build-dist/script-management.js +5 -5
  17. package/{script/build → build/build-utils}/command.js +13 -4
  18. package/{script/build → build/build-utils}/deps.js +1 -1
  19. package/{script/build → build/build-utils}/git.js +6 -4
  20. package/{script/build → build/build-utils}/preview.js +4 -1
  21. package/{script/webhook/webhook.js → build/build-utils/webhook-single.js} +7 -2
  22. package/{script/build → build/build-utils}/webhook.js +3 -2
  23. package/build/docker/compose-command.js +74 -74
  24. package/build/docker/compose-release.js +91 -91
  25. package/compat/init.js +9 -0
  26. package/index.js +8 -20
  27. package/module/Uts.js +77 -7265
  28. package/module/cjs/{request-url-rules.cjs → request-url-rules.js} +6 -0
  29. package/module/cjs/rest-error-rules.js +33 -0
  30. package/package.json +124 -92
  31. package/script/git-automation/git-clone.js +3 -4
  32. package/script/git-automation/git-configure.js +29 -0
  33. package/script/git-automation/index.js +0 -14
  34. package/script/git-automation/submodule-status.js +113 -0
  35. package/script/git-automation/submodule-utils.js +18 -0
  36. package/script/index.js +1 -26
  37. package/tools/auto-export.js +1 -2
  38. package/tools/concurrency.js +30 -0
  39. package/tools/progress.js +63 -0
  40. package/tools/project-command.js +198 -194
  41. package/tools/project-preview.js +1 -148
  42. package/tools/select-option.js +60 -60
  43. package/tools/workspace-config.js +145 -0
  44. package/.prettierrc +0 -5
  45. package/build/build-dist/script-management.test.js +0 -16
  46. package/build/docker/compose-command.test.js +0 -148
  47. package/build/docker/compose-release.test.js +0 -101
  48. package/build/docker/docker-build-push.test.js +0 -124
  49. package/build/webpack5/remove-legacy-assets-plugin.js +0 -84
  50. package/build/webpack5/webpack.base.test.js +0 -59
  51. package/lib/index.js +0 -6
  52. package/lib/server/connect.js +0 -3011
  53. package/lib/server/serve-static.js +0 -4848
  54. package/module/cjs/deep-merge.cjs +0 -38
  55. package/script/pts/cloud-scenes.mjs +0 -65
  56. package/script/pts/cloud.js +0 -151
  57. package/script/pts/generate-cloud-params.mjs +0 -210
  58. package/script/pts/generate-cloud-params.test.mjs +0 -67
  59. package/store/index.js +0 -0
  60. package/tools/file-export.js +0 -165
  61. package/tools/project-command.test.js +0 -267
  62. package/tools/select-option.test.js +0 -52
  63. package/webpack.config.js +0 -38
  64. /package/{module → adapters/vue2/module}/Router.js +0 -0
  65. /package/{build → adapters/vue2}/webpack5/inline-runtime-plugin.js +0 -0
  66. /package/{script/build → build/build-utils}/env.js +0 -0
  67. /package/module/cjs/{page-config.cjs → page-config.js} +0 -0
package/README.md CHANGED
@@ -1,35 +1,60 @@
1
- # npm-package
1
+ # @infly/libs
2
2
 
3
3
  ## 介绍
4
4
 
5
- 基础库
5
+ `@infly/libs` 是不受 Vue、React 等前端框架限制的独立 JavaScript 工具库。包根入口无副作用;HTTP、Token、文件处理、Docker/Compose、通用项目命令等能力可被不同技术栈使用。
6
+
7
+ Vue 2 专属的 Permission、Vuex store、实例 mixin、Vue CLI 预览和 webpack 配置集中在 `adapters/vue2/`,避免核心入口依赖 Vue 项目约定。
6
8
 
7
9
  ## 注意事项
8
10
 
9
- 如需使用第三方库,推荐使用 webpack 进行打包,放入 lib/中使用
11
+ 新代码应显式导入 Vue 2 适配路径。Webpack 适配器仅服务脱离
12
+ admin-monorepo 的子模块独立构建;Monorepo 根构建不会调用该实现。
10
13
 
11
14
  ## 软件架构
12
15
 
13
- @infly/libs
14
- ├── bin - 脚本文件暴露为可直接调用的命令
15
- └── cli.js - 命令入口调用方法判断
16
- ├── build - 构建相关工具库
17
- └── build-dist - 项目构建
18
- └───── zip.js - 压缩构建文件
19
- └───── index.js - 构建入口
20
- └── webpack5 - 构建配置封装
21
- ├── dataInit - 公共枚举
22
- ├── lib - 第三方库
23
- ├── module - 模块
24
- ├── script - 脚本模块
25
- └── git-automation - git自动化
26
- └── webhook - 机器人推送
27
- ├── store - 公共 vuex 模块
28
- ├── tools - 小工具类型方法
29
- ├── types - ts 类型说明
30
- ├── index.js - npm 包初始文件
31
- ├── package.json - npm 包管理配置, 正常无需 npm install 当前项目的依赖
32
- ├── webpack.config.js - webpack 配置实现打包第三方库减小体积
16
+ ```text
17
+ @infly/libs
18
+ ├── index.js # 框架无关、无副作用的包根入口
19
+ ├── module/ # HTTP、Token、格式化等通用模块
20
+ ├── tools/ # 文件与项目命令等通用工具
21
+ ├── build/
22
+ ├── docker/ # Docker/Compose 工具
23
+ └── build-dist/ # 构建产物处理
24
+ ├── adapters/vue2/
25
+ ├── module/file-export.js # 注入请求与 Element UI 能力的 Vue2 导出 mixin
26
+ ├── module/Permission.js # Vue Router/Vuex 权限适配
27
+ ├── store/ # Vuex 模块
28
+ ├── project-preview.js # Vue CLI 预览
29
+ ├── script-config.js # Vue CLI 脚本模板
30
+ ├── vite/ # Monorepo Vite 定位与兼容入口
31
+ ├── webpack5/ # 独立子仓 Webpack 兼容适配器
32
+ │ └── postinstall.js # Vue 2 项目执行初始化
33
+ ├── bin/cli.js # 命令行入口
34
+ └── dataInit/ # 公共枚举
35
+ ```
36
+
37
+ Vue2 文件导出适配:
38
+
39
+ ```javascript
40
+ import { fileExportMixin } from "@infly/libs/adapters/vue2/module/file-export.js";
41
+
42
+ Vue.mixin(fileExportMixin);
43
+ ```
44
+
45
+ 适配器只负责从 Vue 实例注入 `$axios`、`$message`、`$msgbox` 和 `$loading`;确认、请求与下载流程由 `@infly/ts-libs` 的 `exportFile` 实现。`@infly/ui` 不公开该 mixin。
46
+
47
+ 根入口加载示例:
48
+
49
+ ```javascript
50
+ const inflyLibs = require('@infly/libs');
51
+ ```
52
+
53
+ 根入口继续保留历史 `init()` API,但只在显式调用时懒加载 Vue 2 适配器,不会在 `require('@infly/libs')` 时自动执行:
54
+
55
+ ```javascript
56
+ inflyLibs.init();
57
+ ```
33
58
 
34
59
  ## 安装教程
35
60
 
@@ -49,57 +74,69 @@ infly-libs build:docker --local --env prod
49
74
  可通过可选参数 `--cicd-url` 在构建和镜像推送全部成功后输出蓝色发布系统链接。支持等号和空格两种传参形式:
50
75
 
51
76
  ```bash
52
- infly-libs build:docker --local --cicd-url="https://cicd.sutpay.com/view/积分商城/"
77
+ infly-libs build:docker --local --cicd-url="https://deploy.example.com/projects/admin/"
53
78
  ```
54
79
 
55
80
  ```bash
56
- infly-libs build:docker --local --cicd-url "https://cicd.sutpay.com/view/积分商城/"
81
+ infly-libs build:docker --local --cicd-url "https://deploy.example.com/projects/admin/"
57
82
  ```
58
83
 
59
84
  未传 `--cicd-url` 时保持原有输出。参数值必须是合法的 HTTP/HTTPS URL,且不能包含用户名、密码或控制字符。
60
85
 
61
- ## 配置驱动的项目命令
62
-
63
- `project` 命令将多平台项目的开发和构建目标从 package scripts 移到项目配置:
64
-
65
- ```bash
66
- infly-libs project
67
- infly-libs project dev
68
- infly-libs project build
69
- ```
70
-
71
- 命令从当前目录 `package.json` 的 `infly.targets`、`infly.dev` 和 `infly.build` 读取配置。交互选择会追加“退出”选项;选择退出或按取消键时会正常结束,不执行后续命令。项目命令不支持 `--config` 文件入口。
72
-
73
- 未指定 action、target 或构建环境时,会在 TTY 中显示单选菜单。CI/CD 必须显式传参:
74
-
75
- ```bash
76
- infly-libs project dev --target QDXY
77
- infly-libs project build --env stage --target SQ223
78
- ```
79
-
80
- `--dry-run` 只打印带 `VUE_APP_PLATFORM` 的命令计划。`infly.targets` 推荐使用平台代码数组,例如 `["DEFAULT", "MER", "JXEE"]`;同时兼容对象形式,对象值中的 `label` 只用于交互显示。普通 target 就是注入 `VUE_APP_PLATFORM` 的平台代码,批量构建通过 `build.presets` 组合多个 target。
81
-
82
- `dev.command` 以及 `build.before/command/after` 同时支持字符串和参数数组。字符串按空白拆分为可执行文件和参数,不经过 shell;参数本身包含空格时应继续使用数组形式。
83
-
86
+ ## HTTP 环境保护配置
87
+
88
+ `module/REST.js` 不内置任何项目域名。应用如需在测试环境误连正式接口时告警,应由应用显式注入完整的正式 API 基地址:
89
+
90
+ ```javascript
91
+ const service = createService({
92
+ productionBaseURLs: ["https://api.example.com"]
93
+ });
94
+ ```
95
+
96
+ 未传 `productionBaseURLs` 时不启用该项目级域名判断。
97
+
98
+ ## 配置驱动的项目命令
99
+
100
+ `project` 命令将多平台项目的开发和构建目标从 package scripts 移到项目配置:
101
+
102
+ ```bash
103
+ infly-libs project
104
+ infly-libs project dev
105
+ infly-libs project build
106
+ ```
107
+
108
+ 命令从当前目录 `package.json` 的 `infly.targets`、`infly.dev` 和 `infly.build` 读取配置。交互选择会追加“退出”选项;选择退出或按取消键时会正常结束,不执行后续命令。项目命令不支持 `--config` 文件入口。
109
+
110
+ 未指定 action、target 或构建环境时,会在 TTY 中显示单选菜单。CI/CD 必须显式传参:
111
+
112
+ ```bash
113
+ infly-libs project dev --target QDXY
114
+ infly-libs project build --env stage --target SQ223
115
+ ```
116
+
117
+ `--dry-run` 只打印带 `VUE_APP_PLATFORM` 的命令计划。`infly.targets` 推荐使用平台代码数组,例如 `["DEFAULT", "MER", "JXEE"]`;同时兼容对象形式,对象值中的 `label` 只用于交互显示。普通 target 就是注入 `VUE_APP_PLATFORM` 的平台代码,批量构建通过 `build.presets` 组合多个 target。
118
+
119
+ `dev.command` 以及 `build.before/command/after` 同时支持字符串和参数数组。字符串按空白拆分为可执行文件和参数,不经过 shell;参数本身包含空格时应继续使用数组形式。
120
+
84
121
  `build.environments` 推荐使用 `选择键:实际 mode` 数组,例如 `["prod:production", "stage:staging"]`;CLI 使用冒号前的值进行选择,并把冒号后的值作为 `--mode` 参数。没有冒号时选择键和 mode 同名。对象形式继续兼容,用于自定义 `label`、`mode` 或 `args`。项目如果自行管理构建入口,应设置 `infly.buildConfigs.manageScripts=false`,避免安装阶段重新写入传统 `build:prod/build:stage`。
85
122
 
86
- ### Docker Compose 版本发布
87
-
88
- `docker:compose` 面向已有 Compose 文件的镜像版本递增、配置校验和构建,与上面的 Dockerfile/commit-tag `build:docker` 流程互不替代:
89
-
90
- ```bash
91
- infly-libs docker:compose --target yhqy --bump patch
92
- ```
93
-
94
- 默认从当前目录 `package.json` 的 `infly.docker.targets` 读取目标,并以该 package 所在目录解析 Compose 相对路径;省略 `--target` 时在 TTY 中交互选择。`--config` 外部 JS 配置和原有 `--project-dir` + `--file` 直传方式继续兼容。
95
-
96
- 参数约束:
97
-
98
- - `--target`:选择 `infly.docker.targets` 或外部配置中的目标;非交互环境必须显式传入。
99
- - `--config`:可选的外部 JS 配置文件,使用 `targets.<name>.file` 结构。
100
- - `--project-dir` + `--file`:不使用 target 配置时的兼容直传方式,Compose 文件不能越出 `project-dir`。
101
- - `--bump patch`:目前只支持 patch 递增,且 Compose 中必须恰好有一个 `x.y.z` 镜像 tag。
102
- - `--push`:显式推送;未传时只执行 `docker compose config` 和 `build`。
103
- - `--dry-run`:只打印版本变化和计划命令,不写文件、不调用 Docker。
104
-
123
+ ### Docker Compose 版本发布
124
+
125
+ `docker:compose` 面向已有 Compose 文件的镜像版本递增、配置校验和构建,与上面的 Dockerfile/commit-tag `build:docker` 流程互不替代:
126
+
127
+ ```bash
128
+ infly-libs docker:compose --target yhqy --bump patch
129
+ ```
130
+
131
+ 默认从当前目录 `package.json` 的 `infly.docker.targets` 读取目标,并以该 package 所在目录解析 Compose 相对路径;省略 `--target` 时在 TTY 中交互选择。`--config` 外部 JS 配置和原有 `--project-dir` + `--file` 直传方式继续兼容。
132
+
133
+ 参数约束:
134
+
135
+ - `--target`:选择 `infly.docker.targets` 或外部配置中的目标;非交互环境必须显式传入。
136
+ - `--config`:可选的外部 JS 配置文件,使用 `targets.<name>.file` 结构。
137
+ - `--project-dir` + `--file`:不使用 target 配置时的兼容直传方式,Compose 文件不能越出 `project-dir`。
138
+ - `--bump patch`:目前只支持 patch 递增,且 Compose 中必须恰好有一个 `x.y.z` 镜像 tag。
139
+ - `--push`:显式推送;未传时只执行 `docker compose config` 和 `build`。
140
+ - `--dry-run`:只打印版本变化和计划命令,不写文件、不调用 Docker。
141
+
105
142
  构建、校验或推送失败时,命令会恢复原 Compose 内容。公共包不内置 yhqy、qdxy 等业务目标映射;目标配置和单一发布 script 应归 Compose 所在项目所有。
@@ -1,5 +1,5 @@
1
- import Uts from "./Uts";
2
- import TokenService from "./TokenService";
1
+ import Uts from "../../../module/Uts";
2
+ import TokenService from "./TokenService.js";
3
3
 
4
4
  /**
5
5
  * 路由拦截器
@@ -1,7 +1,23 @@
1
+ /**
2
+ * REST HTTP 客户端(Vue 2 / axios 生态)
3
+ *
4
+ * ⚠️ 本模块是 Vue 2 生态专用 axios 封装(依赖 process.env.VUE_APP_BASE_API、
5
+ * window.location、TokenService),与 @infly/ts-libs 的 createHttpClient
6
+ * (框架无关 fetch 客户端)是并行实现,非代码重复。
7
+ *
8
+ * 新项目(React/非 Vue 生态)请使用 @infly/ts-libs createHttpClient。
9
+ */
1
10
  import axios from "axios";
2
- import Uts from "./Uts.js";
11
+ import Uts from "../../../module/Uts.js";
3
12
  import TokenService from "./TokenService.js";
4
- import { resolveInternalServiceURL } from "./cjs/request-url-rules.cjs";
13
+ import {
14
+ matchesConfiguredBaseURL,
15
+ resolveInternalServiceURL
16
+ } from "../../../module/cjs/request-url-rules.js";
17
+ import {
18
+ normalizeErrorPayload,
19
+ resolveStatusErrorMessage
20
+ } from "../../../module/cjs/rest-error-rules.js";
5
21
 
6
22
  const baseURL = process.env.VUE_APP_BASE_API;
7
23
  const { env } = Uts.getEnv() || {};
@@ -28,7 +44,7 @@ function remapLocalhostToCurrentHost(url) {
28
44
 
29
45
  parsed.hostname = window.location.hostname;
30
46
  return parsed.toString();
31
- } catch (e) {
47
+ } catch {
32
48
  return normalized;
33
49
  }
34
50
  }
@@ -66,6 +82,7 @@ export function createNotifyDedup(fn, ttl = 500) {
66
82
  * @param {Function} config.onLogout 登出回调
67
83
  * @param {Object} config.notify Message-like 对象(如 ElementUI Message),传入后自动应用去重并构建 onNotify
68
84
  * @param {Function} config.onNotify 自定义通知回调;若传入 Message-like 对象也可自动识别
85
+ * @param {String[]} config.productionBaseURLs 由应用注入的正式 API 基地址;测试环境命中时告警
69
86
  * @returns {AxiosInstance}
70
87
  */
71
88
  export function createService(config) {
@@ -78,11 +95,12 @@ export function createService(config) {
78
95
  notify,
79
96
  onNotify: rawOnNotify,
80
97
  getTokenFunc = "getToken",
81
- internalServiceURLRules = []
98
+ internalServiceURLRules = [],
99
+ productionBaseURLs = []
82
100
  } = config || {};
83
101
  // 统一在 REST 内部处理 baseURL,避免业务项目重复实现主机替换逻辑。
84
102
  const resolvedBaseURL = resolveServiceBaseURL(customBaseURL);
85
- const isProdAPI = Uts.contain(["https://api.sutpay.com"], resolvedBaseURL);
103
+ const isProdAPI = matchesConfiguredBaseURL(resolvedBaseURL, productionBaseURLs);
86
104
  const service = axios.create({ baseURL: resolvedBaseURL });
87
105
 
88
106
  // 统一处理 notify 与 onNotify:
@@ -165,27 +183,21 @@ export function createService(config) {
165
183
  (error) => {
166
184
  const { response, request } = error || {};
167
185
  const { status, data } = response || {};
168
- const { error: bufferError } = Uts.transArrayBufferToObj(data) ?? {};
169
- const { error: dataError, detail, message } = data || {};
170
- const errorMsg = bufferError || dataError || detail || message || `请求失败(${status})`;
186
+ const normalizedData = Uts.transArrayBufferToObj(data);
187
+ const backendErrorMsg = normalizeErrorPayload(normalizedData);
188
+ const errorMsg = backendErrorMsg || `请求失败(${status})`;
189
+
190
+ // 让仍按 data.error 读取错误的旧调用方也能兼容 detail/message/msg 返回。
191
+ if (response && normalizedData !== undefined) {
192
+ response.data = normalizedData;
193
+ }
171
194
 
172
195
  // 添加标识,表示错误已被拦截器处理
173
196
  error.handledByInterceptor = true;
174
197
 
175
- // HTTP 状态码 → 固定提示语映射(400 直接用后端返回的 errorMsg)
176
- const STATUS_MESSAGES = {
177
- 400: errorMsg,
178
- 401: "登录信息已过期,请重新登录",
179
- 403: "您没有权限访问,请联系管理员",
180
- 500: errorMsg || "服务器内部错误,请联系开发人员",
181
- 502: "网关错误,请稍后重试或联系开发人员",
182
- 503: "服务暂时不可用,请稍后重试",
183
- 504: "网关超时,请稍后重试或联系开发人员"
184
- };
185
-
186
198
  // 按优先级确定最终提示语,合并为一次 handleNotify 调用
187
199
  const notifyMsg =
188
- STATUS_MESSAGES[status] ??
200
+ resolveStatusErrorMessage(status, normalizedData) ??
189
201
  (response ? errorMsg : null) ??
190
202
  (request ? "网络请求失败,请检查网络连接" : null) ??
191
203
  (error.message || "请求配置错误");
@@ -1,5 +1,14 @@
1
+ /**
2
+ * Token 服务(Vue 2 / localStorage 生态)
3
+ *
4
+ * ⚠️ 本模块是 Vue 2 生态专用(localStorage + cookie + 多 key 支持),
5
+ * 与 @infly/ts-libs 的 createStorageTokenStore(框架无关 Token 抽象)
6
+ * 是并行实现,非代码重复。
7
+ *
8
+ * 新项目(React/非 Vue 生态)请使用 @infly/ts-libs createStorageTokenStore。
9
+ */
1
10
  // TokenService.js
2
- import Uts from "./Uts"; // 假设Uts工具类在同级目录或可访问路径
11
+ import Uts from "../../../module/Uts.js"; // 假设Uts工具类在同级目录或可访问路径
3
12
 
4
13
  let TOKEN_KEY = "accessToken"; // 默认的token键名
5
14
  let CHAT_TOKEN_KEY = "chatAccessToken"; // 默认的聊天token键名
@@ -0,0 +1,18 @@
1
+ import { exportFile } from "@infly/ts-libs";
2
+
3
+ const fileExportMixin = {
4
+ methods: {
5
+ async $exportFile(config = {}) {
6
+ return exportFile({
7
+ ...config,
8
+ message: this.$message || config.message || config.Message || {},
9
+ messageBox:
10
+ this.$msgbox || config.messageBox || config.MessageBox || {},
11
+ loading: this.$loading || config.loading || config.Loading || {},
12
+ request: config.request || this.$axios,
13
+ });
14
+ },
15
+ },
16
+ };
17
+
18
+ export { fileExportMixin };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ function resolveProjectRoot({ cwd = process.cwd(), initCwd = process.env.INIT_CWD } = {}) {
7
+ return path.resolve(initCwd || cwd);
8
+ }
9
+
10
+ function isVue2Project(projectRoot = resolveProjectRoot()) {
11
+ return fs.existsSync(path.join(projectRoot, "vue.config.js"));
12
+ }
13
+
14
+ function initVue2Project(projectRoot = resolveProjectRoot(), dependencies = {}) {
15
+ if (!isVue2Project(projectRoot)) {
16
+ return false;
17
+ }
18
+
19
+ const previousCwd = process.cwd();
20
+ const resolvedProjectRoot = path.resolve(projectRoot);
21
+
22
+ try {
23
+ if (path.resolve(previousCwd) !== resolvedProjectRoot) {
24
+ process.chdir(resolvedProjectRoot);
25
+ }
26
+ const projectPreview = dependencies.projectPreview || require("./project-preview");
27
+ const buildDist = dependencies.buildDist || require("../../build/build-dist");
28
+ projectPreview.scriptInit();
29
+ buildDist.scriptInit();
30
+ return true;
31
+ } finally {
32
+ if (process.cwd() !== previousCwd) {
33
+ process.chdir(previousCwd);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (require.main === module) {
39
+ initVue2Project();
40
+ }
41
+
42
+ module.exports = {
43
+ initVue2Project,
44
+ isVue2Project,
45
+ resolveProjectRoot,
46
+ };
@@ -0,0 +1,152 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const net = require("net");
4
+ const { execSync } = require("child_process");
5
+
6
+ const { scriptWrite } = require("../../tools/file-process.js");
7
+ const { previewList } = require("./script-config.js");
8
+
9
+ const projectRoot = process.cwd().replace(/\\node_modules.*$/g, "");
10
+ const projectPkgPath = path.resolve(projectRoot, "package.json");
11
+ const projectVueConfigPath = path.resolve(projectRoot, "vue.config.js");
12
+ const projectConfig = fs.existsSync(projectVueConfigPath) ? require(projectVueConfigPath) : {};
13
+ const projectPackage = fs.existsSync(projectPkgPath) ? require(projectPkgPath) : {};
14
+ const rawArgv = process.argv.slice(2);
15
+ const args = rawArgv.join(" ");
16
+
17
+ function getBuildContext() {
18
+ try {
19
+ return JSON.parse(process.env.INFLY_BUILD_CONTEXT || "{}");
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ const buildContext = getBuildContext();
26
+
27
+ global.logColor = global.logColor || {
28
+ success: "\n\x1b[32m%s\x1b[0m",
29
+ error: "\n\x1b[31m%s\x1b[0m",
30
+ warning: "\n\x1b[33m%s\x1b[0m",
31
+ link: "\x1b[34m%s\x1b[0m",
32
+ info: "\n\x1b[36m%s\x1b[0m"
33
+ };
34
+
35
+ const DEFAULT_CONFIG = {
36
+ port: 9001,
37
+ publicPath: "/",
38
+ outputDir: "dist"
39
+ };
40
+
41
+ function scriptInit() {
42
+ if (fs.existsSync(projectVueConfigPath)) {
43
+ return;
44
+ }
45
+ scriptWrite(projectPkgPath, previewList);
46
+ }
47
+
48
+ function isPortAvailable(port) {
49
+ return new Promise((resolve) => {
50
+ const server = net.createServer();
51
+
52
+ server.listen(port, () => {
53
+ server.once("close", () => {
54
+ resolve(true);
55
+ });
56
+ server.close();
57
+ });
58
+
59
+ server.on("error", () => {
60
+ resolve(false);
61
+ });
62
+ });
63
+ }
64
+
65
+ async function findAvailablePort(startPort, maxTries = 10) {
66
+ for (let i = 0; i < maxTries; i++) {
67
+ const port = startPort + i;
68
+ const available = await isPortAvailable(port);
69
+
70
+ if (available) {
71
+ if (i > 0) {
72
+ console.log(global.logColor.warning, `⚠️ 端口 ${startPort} 被占用,使用端口 ${port}`);
73
+ }
74
+ return port;
75
+ }
76
+ }
77
+
78
+ throw new Error(`无法找到可用端口,已尝试从 ${startPort} 到 ${startPort + maxTries - 1}`);
79
+ }
80
+
81
+ async function init(skipBuild = false) {
82
+ try {
83
+ const connect = require("connect");
84
+ const serveStatic = require("serve-static");
85
+ const app = connect();
86
+ const noBuild = skipBuild || rawArgv.includes("--no-build");
87
+ // Vite 构建:使用 workspace runner 传入的 target 输出目录
88
+ const useViteOutput = buildContext.outputDir && fs.existsSync(buildContext.outputDir);
89
+ const { devServer } = projectConfig || {};
90
+ const { port: devServerPort } = devServer || {};
91
+ projectConfig.port = devServerPort || DEFAULT_CONFIG.port;
92
+ const { publicPath, outputDir, port } = {
93
+ ...DEFAULT_CONFIG,
94
+ ...projectConfig
95
+ };
96
+ const serveDir = useViteOutput ? buildContext.outputDir : outputDir;
97
+ const baseListenPort = port - 1000;
98
+
99
+ if (!noBuild) {
100
+ buildProject();
101
+ }
102
+
103
+ const listenPort = await findAvailablePort(baseListenPort);
104
+
105
+ app.use(
106
+ publicPath,
107
+ serveStatic(serveDir, {
108
+ index: ["index.html", "/"]
109
+ })
110
+ );
111
+
112
+ const server = app.listen(listenPort, function () {
113
+ const previewUrl = `http://localhost:${listenPort}${publicPath}`;
114
+ const targetSuffix = buildContext.targetLabel ? ` (${buildContext.targetLabel})` : '';
115
+
116
+ if (buildContext.batchPreview === true || process.env.INFLY_BATCH_PREVIEW === "1") {
117
+ console.log(global.logColor.link, `${projectPackage.name || path.basename(projectRoot)}${targetSuffix}: ${previewUrl}`);
118
+ return;
119
+ }
120
+
121
+ console.log(global.logColor.success, `\x1b[42m\x1b[30m DONE \x1b[0m\x1b[32m 项目构建预览,CTRL + C结束:`);
122
+ console.log(global.logColor.link, previewUrl);
123
+ });
124
+
125
+ server.on("error", (error) => {
126
+ if (error.code === "EADDRINUSE") {
127
+ console.log(global.logColor.error, `❌ 端口 ${listenPort} 仍然被占用`);
128
+ } else {
129
+ console.log(global.logColor.error, `❌ 服务器启动失败:${error.message}`);
130
+ }
131
+ });
132
+
133
+ return server;
134
+ } catch (error) {
135
+ console.log(global.logColor.error, `❌ 预览项目失败:${error.message}`);
136
+ throw error;
137
+ }
138
+ }
139
+
140
+ function buildProject() {
141
+ try {
142
+ execSync(`vue-cli-service build ${args}`, { stdio: "inherit" });
143
+ } catch (err) {
144
+ console.error(global.logColor.error, "❌ 构建失败:", err.message);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ module.exports = {
150
+ init,
151
+ scriptInit
152
+ };
@@ -0,0 +1,26 @@
1
+ module.exports = {
2
+ previewList: [
3
+ {
4
+ key: "preview",
5
+ value: "infly-libs --preview"
6
+ },
7
+ {
8
+ key: "preview:no-build",
9
+ value: "infly-libs --preview --no-build"
10
+ }
11
+ ],
12
+ buildList: [
13
+ {
14
+ key: "build:prod",
15
+ value: "infly-libs beforeBuild && vue-cli-service build && infly-libs afterBuild"
16
+ },
17
+ {
18
+ key: "build:stage",
19
+ value: "infly-libs beforeBuild && vue-cli-service build --mode staging --no-module && infly-libs afterBuild"
20
+ },
21
+ {
22
+ key: "build:test",
23
+ value: "infly-libs beforeBuild && infly-libs afterBuild"
24
+ }
25
+ ]
26
+ };
@@ -0,0 +1 @@
1
+ export { default as user } from "./modules/user";
@@ -1,4 +1,4 @@
1
- import Uts from "../../module/Uts.js";
1
+ import Uts from "../../../../module/Uts.js";
2
2
  import TokenService from "../../module/TokenService.js";
3
3
 
4
4
  const CONFIG = {
@@ -0,0 +1,95 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ function findAdminMonorepoRoot(startDir = process.cwd()) {
5
+ let directory = path.resolve(startDir);
6
+ for (;;) {
7
+ if (
8
+ fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))
9
+ && fs.existsSync(path.join(directory, "configs/vite-configs/registry.js"))
10
+ ) return directory;
11
+ const parent = path.dirname(directory);
12
+ if (parent === directory) return null;
13
+ directory = parent;
14
+ }
15
+ }
16
+
17
+ function resolveAppScssImport(importPath, importer, appRoot) {
18
+ const cleanImporter = importer.replace(/[?#].*$/, "");
19
+ if (importPath.startsWith("@/")) {
20
+ return path.resolve(appRoot, "src", importPath.slice(2));
21
+ }
22
+ if (importPath.startsWith("./") || importPath.startsWith("../")) {
23
+ return path.resolve(path.dirname(cleanImporter), importPath);
24
+ }
25
+ if (path.isAbsolute(importPath)) return path.resolve(importPath);
26
+ return null;
27
+ }
28
+
29
+ function transformScssExportImports(code, importer, options) {
30
+ const { appRoot, readFile = (file) => fs.readFileSync(file, "utf8") } = options;
31
+ return code.replace(
32
+ /import\s+([\w$]+)\s+from\s+(["'])([^"'?]+\.scss)\2\s*;?/g,
33
+ (statement, binding, quote, importPath) => {
34
+ const resolved = resolveAppScssImport(importPath, importer, appRoot);
35
+ if (!resolved) return statement;
36
+ let scss;
37
+ try {
38
+ scss = readFile(resolved);
39
+ } catch (_) {
40
+ return statement;
41
+ }
42
+ if (typeof scss !== "string") return statement;
43
+ const exportBlock = scss.match(/:export\s*\{([\s\S]*?)\}/);
44
+ if (!exportBlock) return statement;
45
+
46
+ const variables = new Map();
47
+ for (const match of scss.matchAll(/^\s*\$([\w-]+)\s*:\s*([^;]+);/gm)) {
48
+ variables.set(match[1], match[2].trim());
49
+ }
50
+ const exported = {};
51
+ for (const match of exportBlock[1].matchAll(/([\w-]+)\s*:\s*([^;]+);/g)) {
52
+ const rawValue = match[2].trim();
53
+ const variableName = rawValue.match(/^\$([\w-]+)$/);
54
+ exported[match[1]] = variableName && variables.has(variableName[1])
55
+ ? variables.get(variableName[1])
56
+ : rawValue;
57
+ }
58
+ return `import ${quote}${importPath}${quote};\nconst ${binding} = ${JSON.stringify(exported)};`;
59
+ },
60
+ );
61
+ }
62
+
63
+ function createScssExportCompatPlugin(appRoot) {
64
+ return {
65
+ name: "infly-vue2:scss-export-compat",
66
+ enforce: "pre",
67
+ transform(code, id) {
68
+ if (
69
+ id.includes("node_modules")
70
+ || (!id.includes(".vue") && !/\.[cm]?[jt]sx?(?:[?#].*)?$/.test(id))
71
+ || !/import\s+[\w$]+\s+from\s+["'][^"']+\.scss["']/.test(code)
72
+ ) return null;
73
+ const transformed = transformScssExportImports(code, id, { appRoot });
74
+ return transformed === code ? null : { code: transformed, map: null };
75
+ },
76
+ };
77
+ }
78
+
79
+ function addAppRootSassLoadPath(css, appRoot) {
80
+ if (!css.preprocessorOptions) css.preprocessorOptions = {};
81
+ if (!css.preprocessorOptions.scss) css.preprocessorOptions.scss = {};
82
+ const loadPaths = css.preprocessorOptions.scss.loadPaths || [];
83
+ const resolvedAppRoot = path.resolve(appRoot);
84
+ css.preprocessorOptions.scss.loadPaths = loadPaths.includes(resolvedAppRoot)
85
+ ? loadPaths
86
+ : [...loadPaths, resolvedAppRoot];
87
+ return css;
88
+ }
89
+
90
+ module.exports = {
91
+ addAppRootSassLoadPath,
92
+ findAdminMonorepoRoot,
93
+ transformScssExportImports,
94
+ createScssExportCompatPlugin,
95
+ };