@longzai-intelligence-vite/plugins 0.0.0-stage → 0.0.1

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
@@ -1,3 +1,9 @@
1
- # Temporary Holding Version
1
+ # @longzai-intelligence-vite/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 开发工具类项目的核心包模板,承载项目核心类型定义与配置校验能力,为 CLI 与 Dashboard 应用提供共享的类型与校验函数。
4
+
5
+ 定位为 dev-tool 项目模板 monorepo 的核心 packages 包,归属于 `packages/*` workspace,是 apps/cli 与 apps/dashboard 共同依赖的底层包。上游被同项目内的 cli 与 dashboard 通过 `workspace:*` 引用,自身依赖 zod 提供 Schema 校验能力,不反向依赖任何应用层包。
6
+
7
+ 职能上,该包通过 src/index.ts 对外导出 CoreConfig 类型与 validateConfig 校验函数:CoreConfig 定义项目核心配置的数据结构,validateConfig 基于 zod 对配置进行运行时校验。构建产物经 tsgo --build 与 resolve-aliases 处理别名解析,再由 tsdown 产出生产构建,导出路径通过 exports 字段限定为 ./dist/index.js 与对应的类型声明。
8
+
9
+ 实现情况为脚手架模板初始状态,package.json 的脚本(build、build:prod、typecheck、lint、test、clean 等)与 exports 声明均已就绪,zod 依赖已声明。src/index.ts 已定义导出契约,但其引用的 ./types 与 ./validate 模块文件尚未在模板中提供,需在使用模板后由开发者按导出契约补齐具体实现。
@@ -0,0 +1,171 @@
1
+ import { PortRegistryOptions } from "@longzai-intelligence-port/registry";
2
+ import { Plugin } from "vite";
3
+ //#region src/dist-manifest.plugin.d.ts
4
+ /**
5
+ * 产物版本清单形态,字段与升级域解析服务的 zod 校验同构
6
+ */
7
+ type DistManifest = {
8
+ /**
9
+ * 包名
10
+ */
11
+ name: string;
12
+ /**
13
+ * 版本号
14
+ */
15
+ version: string;
16
+ /**
17
+ * 构建时刻的 ISO 时间戳
18
+ */
19
+ builtAt: string;
20
+ /**
21
+ * 构建时所在提交的短哈希,非 git 环境省略该键
22
+ */
23
+ gitCommit?: string;
24
+ };
25
+ /**
26
+ * 清单组装入参形态
27
+ */
28
+ type ComposeDistManifestInput = {
29
+ /**
30
+ * 包定义文件路径,缺省取进程工作目录下 `package.json`
31
+ *
32
+ * @default `path.join(process.cwd(), 'package.json')`
33
+ */
34
+ pkgPath?: string;
35
+ /**
36
+ * 提交短哈希推导函数,测试可注入桩替身
37
+ *
38
+ * @default `resolveGitCommit`
39
+ */
40
+ resolveCommit?: () => string;
41
+ };
42
+ /**
43
+ * 插件选项形态
44
+ */
45
+ type DistManifestPluginOptions = {
46
+ /**
47
+ * 包定义文件路径,缺省取插件工厂调用时进程工作目录下 `package.json`
48
+ *
49
+ * @default `path.join(process.cwd(), 'package.json')`
50
+ */
51
+ pkgPath?: string;
52
+ /**
53
+ * 写入产物目录的清单文件名
54
+ *
55
+ * @default `manifest.json`
56
+ */
57
+ manifestFileName?: string;
58
+ };
59
+ /**
60
+ * 推导当前提交短哈希
61
+ *
62
+ * @param cwd - git 仓库工作目录,缺省取进程工作目录
63
+ * @returns 七位短哈希,非 git 环境或执行失败时为空文本
64
+ */
65
+ declare function resolveGitCommit(cwd?: string): string;
66
+ /**
67
+ * 组装产物版本清单
68
+ *
69
+ * @remarks
70
+ * 字段与写盘形态对齐发布脚本的清单约定,桌面宿主的升级域解析服务按产物目录内清单文件展示当前版本与构建时间,
71
+ * 治理沿革自 git-reviewer 仓 issue 0048,统一方案正本见本仓 `docs/ledgers/lzi-vite-adoption/`
72
+ * @param input - 清单组装入参,路径与哈希推导均可注入
73
+ * @returns 清单对象
74
+ */
75
+ declare function composeDistManifest(input?: ComposeDistManifestInput): DistManifest;
76
+ /**
77
+ * 写盘入参形态
78
+ */
79
+ type WriteDistManifestInput = {
80
+ /**
81
+ * 产物目录绝对路径
82
+ */
83
+ outDir: string;
84
+ /**
85
+ * 包定义文件路径
86
+ *
87
+ * @default `path.join(process.cwd(), 'package.json')`
88
+ */
89
+ pkgPath?: string;
90
+ /**
91
+ * 清单文件名
92
+ *
93
+ * @default `manifest.json`
94
+ */
95
+ manifestFileName?: string;
96
+ };
97
+ /**
98
+ * 向产物目录写入版本清单
99
+ *
100
+ * @remarks
101
+ * 插件 `closeBundle` 钩子的写盘正体,独立导出以便消费方与测试直接复用
102
+ * @param input - 写盘入参
103
+ */
104
+ declare function writeDistManifest(input: WriteDistManifestInput): void;
105
+ /**
106
+ * 产物版本清单写入插件工厂
107
+ *
108
+ * @remarks
109
+ * 构建收尾把清单写入产物目录,开发邻接解析层与打包基线均从该清单取版本与构建时间,
110
+ * 目录缺清单时界面将显示版本未知,产物目录经 `configResolved` 取解析后 `outDir` 以尊重命令行覆写
111
+ * @param options - 插件选项,包定义路径与清单文件名可覆写
112
+ * @returns vite 插件实例
113
+ */
114
+ declare function distManifestPlugin(options?: DistManifestPluginOptions): Plugin;
115
+ //#endregion
116
+ //#region src/port-discovery.plugin.d.ts
117
+ /**
118
+ * 插件配置选项
119
+ */
120
+ type PortDiscoveryOptions = {
121
+ /**
122
+ * 要发现的后端服务名称,如 `data-server`
123
+ */
124
+ serviceName?: string;
125
+ /**
126
+ * 前端自身注册为的服务名称,如 `data-web`
127
+ */
128
+ registerAs?: string;
129
+ /**
130
+ * 等待后端服务的超时时间,单位毫秒
131
+ *
132
+ * @default 30000
133
+ */
134
+ timeout?: number;
135
+ /**
136
+ * 轮询间隔,单位毫秒
137
+ *
138
+ * @default 1000
139
+ */
140
+ pollInterval?: number;
141
+ /**
142
+ * 注册表配置选项
143
+ */
144
+ registryOptions?: PortRegistryOptions;
145
+ };
146
+ /**
147
+ * 创建 vite 端口发现插件
148
+ *
149
+ * @remarks
150
+ * 在 dev server 启动时自动从注册表发现后端服务端口,并将前端自身端口写入注册表,
151
+ * 后端端口变化经注册表 `watch` 自动同步代理目标
152
+ * @param options - 插件配置选项
153
+ * @returns vite 插件实例
154
+ */
155
+ declare const portDiscovery: (options?: PortDiscoveryOptions) => Plugin;
156
+ //#endregion
157
+ //#region src/proxy-target.utils.d.ts
158
+ /**
159
+ * 解析代理目标地址
160
+ *
161
+ * @remarks
162
+ * 供 `vite.config.ts` 中直接调用,在配置阶段获取后端服务地址,
163
+ * 必须是同步函数,因为 `vite.config.ts` 是同步执行的,
164
+ * 注册表目录解析复用 port 域注册表的 `resolveRegistryDir` 保持单一数据源
165
+ * @param serviceName - 后端服务名称
166
+ * @param options - 注册表配置选项
167
+ * @returns 代理目标地址,如 `http://localhost:52341`,未找到时返回 `http://localhost:0`
168
+ */
169
+ declare const resolveProxyTarget: (serviceName: string, options?: PortRegistryOptions) => string;
170
+ //#endregion
171
+ export { type ComposeDistManifestInput, type DistManifest, type DistManifestPluginOptions, type PortDiscoveryOptions, type WriteDistManifestInput, composeDistManifest, distManifestPlugin, portDiscovery, resolveGitCommit, resolveProxyTarget, writeDistManifest };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{execFileSync as e}from"node:child_process";import{existsSync as t,readFileSync as n,writeFileSync as r}from"node:fs";import i,{join as a}from"node:path";import*as o from"zod";import{createPortRegistry as s,resolveRegistryDir as c}from"@longzai-intelligence-port/registry";const l=o.object({name:o.string(),version:o.string()});function u(t=process.cwd()){try{return e(`git`,[`rev-parse`,`--short`,`HEAD`],{cwd:t,encoding:`utf-8`}).trim()}catch{return``}}function d(e={}){let t=e.pkgPath??i.join(process.cwd(),`package.json`),r=l.parse(JSON.parse(n(t,`utf-8`))),a=e.resolveCommit??u,o={name:r.name,version:r.version,builtAt:(/* @__PURE__ */ new Date()).toISOString()},s=a();return s!==``&&(o.gitCommit=s),o}function f(e){let t=e.manifestFileName??`manifest.json`;r(i.join(e.outDir,t),`${JSON.stringify(d({pkgPath:e.pkgPath}),null,2)}\n`,`utf-8`)}function p(e={}){let t=e.pkgPath??i.join(process.cwd(),`package.json`),r=e.manifestFileName??`manifest.json`;l.parse(JSON.parse(n(t,`utf-8`)));let a=i.resolve(process.cwd(),`dist`);return{name:`longzai-intelligence-dist-manifest`,apply:`build`,configResolved(e){a=e.build.outDir},closeBundle(){f({outDir:a,pkgPath:t,manifestFileName:r})}}}const m=e=>{let t=e.httpServer?.address();return t&&typeof t==`object`?t.port:e.config.server.port??5173},h=e=>{let t=e.httpServer?.address();return t&&typeof t==`object`&&t.address||`localhost`};function g(e){return typeof e==`object`&&!!e}const _=(e,t)=>g(e)?e[`x-service`]===t?!0:typeof e.target==`string`?e.target.includes(`localhost:0`)||e.target.includes(`127.0.0.1:0`):!1:!1,v=(e,t,n)=>{let r=e.config.server.proxy;if(r)for(let[,i]of Object.entries(r))typeof i==`object`&&i&&`target`in i&&(String(i.target).includes(t)||_(i,t))&&(i.target=n,i.ws&&(i.target=n),e.config.logger.info(`[端口发现] 代理目标已更新: ${t} -> ${n}`))},y=async(e,t,n,r)=>{let i=Date.now();for(;Date.now()-i<n;){let n=await t.resolve(e);if(n)return n;await new Promise(e=>setTimeout(e,r))}return null},b=e=>{let t=e?.serviceName,n=e?.registerAs,r=e?.timeout??3e4,i=e?.pollInterval??1e3,a=e?.registryOptions,o=null,c=null;return{name:`longzai-intelligence-port-discovery`,apply:`serve`,configureServer:async e=>{o=s(a);let l=m(e),u=h(e);if(n&&(await o.register(n,{port:l,host:u===`::`?`localhost`:u,pid:process.pid,startedAt:Date.now()}),e.config.logger.info(`[端口发现] 前端服务已注册: ${n} -> http://${u}:${l}`)),t){e.config.logger.info(`[端口发现] 正在等待后端服务: ${t}...`);let n=await y(t,o,r,i);if(n){let r=`http://${n.host===`0.0.0.0`?`localhost`:n.host}:${n.port}`;e.config.logger.info(`[端口发现] 后端服务已发现: ${t} -> ${r}`),v(e,t,r)}else e.config.logger.warn(`[端口发现] 等待后端服务超时: ${t},已等待 ${r}ms,代理目标可能不正确`);c=o.watch(t,n=>{if(n){let r=`http://${n.host===`0.0.0.0`?`localhost`:n.host}:${n.port}`;e.config.logger.info(`[端口发现] 后端服务端口变化: ${t} -> ${r}`),v(e,t,r)}else e.config.logger.warn(`[端口发现] 后端服务已下线: ${t}`)})}if(n){let t=e.httpServer,r=o,i=t?.close.bind(t);i&&t&&r&&(t.close=function(e){return r.deregister(n).catch(()=>{}),i(e)})}},closeBundle:async()=>{c&&=(c(),null),o&&n&&await o.deregister(n).catch(()=>{})}}},x=`http://localhost:0`,S=e=>{try{return process.kill(e,0),!0}catch{return!1}},C=(e,r)=>{let i=c(r),o=a(i,`${e}.json`);if(!t(o))return x;try{let e=n(o,`utf-8`),t=JSON.parse(e);return S(t.pid)?`http://${t.host===`0.0.0.0`?`localhost`:t.host}:${t.port}`:x}catch{return x}};export{d as composeDistManifest,p as distManifestPlugin,b as portDiscovery,u as resolveGitCommit,C as resolveProxyTarget,f as writeDistManifest};
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from '@longzai-intelligence-builder/esm';
2
+
3
+ /**
4
+ * 核心包构建配置
5
+ */
6
+ export default defineConfig();
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from '@longzai-intelligence-dev-tools/config';
2
+
3
+ export default defineConfig({
4
+ clean: {
5
+ plugins: ['turbo', 'tsgo'],
6
+ },
7
+ });
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from '@longzai-intelligence-oxlint/presets/library';
2
+
3
+ export default defineConfig();
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@longzai-intelligence-vite/plugins",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "Longzai Intelligence Vite 统一插件库",
5
+ "keywords": [
6
+ "dist-manifest",
7
+ "longzai-intelligence",
8
+ "plugins",
9
+ "port-discovery",
10
+ "vite"
11
+ ],
12
+ "license": "UNLICENSED",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "lzi-builder",
24
+ "build:prod": "NODE_ENV=production lzi-builder",
25
+ "prepublishOnly": "bun run build:prod",
26
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
27
+ "typecheck:app": "lzi-tsgo typecheck tsconfig/app.json",
28
+ "typecheck:node": "lzi-tsgo typecheck tsconfig/node.json",
29
+ "typecheck:test": "lzi-tsgo typecheck tsconfig/test.json",
30
+ "lint": "oxlint && oxfmt --check",
31
+ "lint:fix": "oxlint --fix && oxfmt",
32
+ "test": "bun test",
33
+ "test:watch": "bun test --watch",
34
+ "test:coverage": "bun test --coverage",
35
+ "clean": "lzi-dev-cli clean"
36
+ },
37
+ "dependencies": {
38
+ "@longzai-intelligence-port/registry": "0.0.1",
39
+ "zod": "^4.6.5"
40
+ },
41
+ "devDependencies": {
42
+ "@longzai-intelligence-electron-private/web-dist-upgrade": "^0.0.3",
43
+ "vite": "^8.3.2"
44
+ },
45
+ "peerDependencies": {
46
+ "vite": ">=5"
47
+ }
48
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * 产物版本清单插件回归测试
3
+ *
4
+ * 清单形态经升级域解析服务导出的真实 zod 校验锁死,字段真源取包定义文件,
5
+ * 治理沿革自 git-reviewer 仓 issue 0048
6
+ */
7
+
8
+ import { WebDistManifestSchema } from '@longzai-intelligence-electron-private/web-dist-upgrade';
9
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
10
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ import {
15
+ composeDistManifest,
16
+ distManifestPlugin,
17
+ writeDistManifest,
18
+ } from '@/dist-manifest.plugin.js';
19
+
20
+ /**
21
+ * 测试用临时目录
22
+ */
23
+ let tempDir: string;
24
+
25
+ beforeEach(async () => {
26
+ tempDir = await mkdtemp(join(tmpdir(), 'vite-plugins-manifest-test-'));
27
+ });
28
+
29
+ afterEach(async () => {
30
+ await rm(tempDir, { recursive: true, force: true });
31
+ });
32
+
33
+ /**
34
+ * 写入测试用包定义并返回其路径
35
+ *
36
+ * @param name - 包名
37
+ * @param version - 版本号
38
+ * @returns 包定义文件绝对路径
39
+ */
40
+ async function writeFixturePkg(name: string, version: string): Promise<string> {
41
+ /**
42
+ * 包定义文件路径
43
+ */
44
+ const pkgPath = join(tempDir, 'package.json');
45
+
46
+ await writeFile(pkgPath, JSON.stringify({ name, version }), 'utf-8');
47
+ return pkgPath;
48
+ }
49
+
50
+ describe('composeDistManifest', () => {
51
+ test('清单通过升级域解析服务的 zod 校验且字段取自包定义', async () => {
52
+ /**
53
+ * 包定义文件路径
54
+ */
55
+ const pkgPath = await writeFixturePkg('@longzai-intelligence-vite/plugins', '0.0.0');
56
+
57
+ /**
58
+ * 带提交短哈希的清单
59
+ */
60
+ const manifest = composeDistManifest({ pkgPath, resolveCommit: () => '947dd0f' });
61
+
62
+ expect(manifest.name).toBe('@longzai-intelligence-vite/plugins');
63
+ expect(manifest.version).toBe('0.0.0');
64
+ expect(manifest.gitCommit).toBe('947dd0f');
65
+ expect(manifest.builtAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
66
+ expect(Number.isNaN(Date.parse(manifest.builtAt))).toBe(false);
67
+ expect(WebDistManifestSchema.safeParse(manifest).success).toBe(true);
68
+ });
69
+
70
+ test('提交短哈希推导失败时省略该键且清单仍通过校验', async () => {
71
+ /**
72
+ * 包定义文件路径
73
+ */
74
+ const pkgPath = await writeFixturePkg('@longzai-intelligence-vite/plugins', '0.0.0');
75
+
76
+ /**
77
+ * 无提交短哈希的清单
78
+ */
79
+ const manifest = composeDistManifest({ pkgPath, resolveCommit: () => '' });
80
+
81
+ expect(Object.hasOwn(manifest, 'gitCommit')).toBe(false);
82
+ expect(WebDistManifestSchema.safeParse(manifest).success).toBe(true);
83
+ });
84
+
85
+ test('包定义路径缺省取进程工作目录下 package.json', async () => {
86
+ /**
87
+ * 原工作目录
88
+ */
89
+ const originalCwd = process.cwd();
90
+
91
+ process.chdir(tempDir);
92
+ try {
93
+ await writeFixturePkg('@demo/app', '1.2.3');
94
+
95
+ /**
96
+ * 以缺省路径组装的清单
97
+ */
98
+ const manifest = composeDistManifest({ resolveCommit: () => 'abc1234' });
99
+
100
+ expect(manifest.name).toBe('@demo/app');
101
+ expect(manifest.version).toBe('1.2.3');
102
+ } finally {
103
+ process.chdir(originalCwd);
104
+ }
105
+ });
106
+ });
107
+
108
+ describe('distManifestPlugin', () => {
109
+ test('插件名与作用阶段正确且钩子齐备', async () => {
110
+ /**
111
+ * 包定义文件路径
112
+ */
113
+ const pkgPath = await writeFixturePkg('@demo/app', '1.2.3');
114
+
115
+ /**
116
+ * 插件实例
117
+ */
118
+ const plugin = distManifestPlugin({ pkgPath });
119
+
120
+ expect(plugin.name).toBe('longzai-intelligence-dist-manifest');
121
+ expect(plugin.apply).toBe('build');
122
+ expect(typeof plugin.configResolved).toBe('function');
123
+ expect(typeof plugin.closeBundle).toBe('function');
124
+ });
125
+ });
126
+
127
+ describe('writeDistManifest', () => {
128
+ test('向产物目录写入指定文件名的清单', async () => {
129
+ /**
130
+ * 包定义文件路径
131
+ */
132
+ const pkgPath = await writeFixturePkg('@demo/app', '1.2.3');
133
+
134
+ /**
135
+ * 产物目录
136
+ */
137
+ const outDir = join(tempDir, 'dist');
138
+ await mkdir(outDir, { recursive: true });
139
+
140
+ writeDistManifest({ outDir, pkgPath, manifestFileName: 'version.json' });
141
+
142
+ /**
143
+ * 写盘后的清单内容
144
+ */
145
+ const written = await Bun.file(join(outDir, 'version.json')).text();
146
+
147
+ expect(JSON.parse(written).name).toBe('@demo/app');
148
+ expect(JSON.parse(written).version).toBe('1.2.3');
149
+ expect(written.endsWith('\n')).toBe(true);
150
+ });
151
+
152
+ test('写盘清单通过升级域解析服务的 zod 校验', async () => {
153
+ /**
154
+ * 包定义文件路径
155
+ */
156
+ const pkgPath = await writeFixturePkg('@demo/app', '1.2.3');
157
+
158
+ /**
159
+ * 产物目录
160
+ */
161
+ const outDir = join(tempDir, 'dist');
162
+ await mkdir(outDir, { recursive: true });
163
+
164
+ writeDistManifest({ outDir, pkgPath });
165
+
166
+ /**
167
+ * 写盘后的清单内容
168
+ */
169
+ const written = await Bun.file(join(outDir, 'manifest.json')).text();
170
+
171
+ expect(WebDistManifestSchema.safeParse(JSON.parse(written)).success).toBe(true);
172
+ });
173
+ });
@@ -0,0 +1,223 @@
1
+ /**
2
+ * 端口发现插件与代理目标解析回归测试
3
+ *
4
+ * 自 dev-tools 仓 `packages/vite-plugin` 承接迁入时的同构用例
5
+ */
6
+
7
+ import type { PortDiscoveryOptions } from '@/port-discovery.plugin.js';
8
+
9
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
10
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ import { portDiscovery } from '@/port-discovery.plugin.js';
15
+ import { resolveProxyTarget } from '@/proxy-target.utils.js';
16
+
17
+ /**
18
+ * 测试用临时注册表目录
19
+ */
20
+ let tempDir: string;
21
+
22
+ beforeEach(async () => {
23
+ tempDir = await mkdtemp(join(tmpdir(), 'vite-plugins-port-test-'));
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await rm(tempDir, { recursive: true, force: true });
28
+ });
29
+
30
+ describe('portDiscovery', () => {
31
+ test('插件名称正确', () => {
32
+ /**
33
+ * 插件实例
34
+ */
35
+ const plugin = portDiscovery();
36
+ expect(plugin.name).toBe('longzai-intelligence-port-discovery');
37
+ });
38
+
39
+ test('apply 属性为 serve', () => {
40
+ /**
41
+ * 插件实例
42
+ */
43
+ const plugin = portDiscovery();
44
+ expect(plugin.apply).toBe('serve');
45
+ });
46
+
47
+ test('完整配置时创建插件不报错', () => {
48
+ /**
49
+ * 插件配置
50
+ */
51
+ const options: PortDiscoveryOptions = {
52
+ serviceName: 'data-server',
53
+ registerAs: 'data-web',
54
+ timeout: 5000,
55
+ pollInterval: 500,
56
+ registryOptions: { registryDir: tempDir },
57
+ };
58
+
59
+ /**
60
+ * 插件实例
61
+ */
62
+ const plugin = portDiscovery(options);
63
+ expect(plugin.name).toBe('longzai-intelligence-port-discovery');
64
+ expect(plugin.apply).toBe('serve');
65
+ });
66
+ });
67
+
68
+ describe('resolveProxyTarget', () => {
69
+ test('能读取已注册的服务', async () => {
70
+ /**
71
+ * 服务注册信息
72
+ */
73
+ const serviceInfo = {
74
+ name: 'data-server',
75
+ port: 52341,
76
+ host: 'localhost',
77
+ pid: process.pid,
78
+ startedAt: Date.now(),
79
+ };
80
+
81
+ /**
82
+ * 写入注册文件
83
+ */
84
+ await mkdir(tempDir, { recursive: true });
85
+ await writeFile(
86
+ join(tempDir, 'data-server.json'),
87
+ JSON.stringify(serviceInfo, null, 2),
88
+ 'utf-8',
89
+ );
90
+
91
+ /**
92
+ * 解析代理目标
93
+ */
94
+ const target = resolveProxyTarget('data-server', { registryDir: tempDir });
95
+ expect(target).toBe('http://localhost:52341');
96
+ });
97
+
98
+ test('未注册时返回占位符', () => {
99
+ /**
100
+ * 解析不存在的服务
101
+ */
102
+ const target = resolveProxyTarget('non-existent', { registryDir: tempDir });
103
+ expect(target).toBe('http://localhost:0');
104
+ });
105
+
106
+ test('僵尸文件返回占位符', async () => {
107
+ /**
108
+ * 使用不存在的 pid 模拟僵尸文件
109
+ */
110
+ const zombieInfo = {
111
+ name: 'zombie-server',
112
+ port: 9999,
113
+ host: 'localhost',
114
+ pid: 99999999,
115
+ startedAt: Date.now(),
116
+ };
117
+
118
+ /**
119
+ * 写入僵尸注册文件
120
+ */
121
+ await mkdir(tempDir, { recursive: true });
122
+ await writeFile(
123
+ join(tempDir, 'zombie-server.json'),
124
+ JSON.stringify(zombieInfo, null, 2),
125
+ 'utf-8',
126
+ );
127
+
128
+ /**
129
+ * 解析僵尸服务应返回占位符
130
+ */
131
+ const target = resolveProxyTarget('zombie-server', { registryDir: tempDir });
132
+ expect(target).toBe('http://localhost:0');
133
+ });
134
+
135
+ test('0.0.0.0 主机替换为 localhost', async () => {
136
+ /**
137
+ * 使用 0.0.0.0 主机的服务
138
+ */
139
+ const serviceInfo = {
140
+ name: 'gateway-server',
141
+ port: 3000,
142
+ host: '0.0.0.0',
143
+ pid: process.pid,
144
+ startedAt: Date.now(),
145
+ };
146
+
147
+ /**
148
+ * 写入注册文件
149
+ */
150
+ await mkdir(tempDir, { recursive: true });
151
+ await writeFile(
152
+ join(tempDir, 'gateway-server.json'),
153
+ JSON.stringify(serviceInfo, null, 2),
154
+ 'utf-8',
155
+ );
156
+
157
+ /**
158
+ * 解析代理目标
159
+ */
160
+ const target = resolveProxyTarget('gateway-server', { registryDir: tempDir });
161
+ expect(target).toBe('http://localhost:3000');
162
+ });
163
+
164
+ test('损坏的 JSON 文件返回占位符', async () => {
165
+ /**
166
+ * 写入损坏的 JSON 文件
167
+ */
168
+ await mkdir(tempDir, { recursive: true });
169
+ await writeFile(join(tempDir, 'broken-server.json'), 'invalid json content', 'utf-8');
170
+
171
+ /**
172
+ * 解析损坏文件应返回占位符
173
+ */
174
+ const target = resolveProxyTarget('broken-server', { registryDir: tempDir });
175
+ expect(target).toBe('http://localhost:0');
176
+ });
177
+
178
+ test('DEV_PORTS_DIR 环境变量覆盖注册表目录', async () => {
179
+ /**
180
+ * 环境变量指定的临时目录
181
+ */
182
+ const envDir = await mkdtemp(join(tmpdir(), 'env-vite-plugins-test-'));
183
+
184
+ /**
185
+ * 服务注册信息
186
+ */
187
+ const serviceInfo = {
188
+ name: 'env-server',
189
+ port: 5555,
190
+ host: 'localhost',
191
+ pid: process.pid,
192
+ startedAt: Date.now(),
193
+ };
194
+
195
+ /**
196
+ * 写入注册文件
197
+ */
198
+ await writeFile(join(envDir, 'env-server.json'), JSON.stringify(serviceInfo, null, 2), 'utf-8');
199
+
200
+ /**
201
+ * 保存并设置环境变量
202
+ */
203
+ const originalValue = process.env.DEV_PORTS_DIR;
204
+ process.env.DEV_PORTS_DIR = envDir;
205
+
206
+ /**
207
+ * 解析代理目标
208
+ */
209
+ const target = resolveProxyTarget('env-server');
210
+ expect(target).toBe('http://localhost:5555');
211
+
212
+ /**
213
+ * 恢复环境变量
214
+ */
215
+ if (originalValue) {
216
+ process.env.DEV_PORTS_DIR = originalValue;
217
+ } else {
218
+ delete process.env.DEV_PORTS_DIR;
219
+ }
220
+
221
+ await rm(envDir, { recursive: true, force: true });
222
+ });
223
+ });