ms-vite-plugin 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +205 -0
- package/dist/device.d.ts +32 -0
- package/dist/device.js +325 -0
- package/dist/project.d.ts +35 -0
- package/dist/project.js +478 -0
- package/dist/ws-manager.d.ts +110 -0
- package/dist/ws-manager.js +367 -0
- package/package.json +9 -3
package/dist/project.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runOnDevice = runOnDevice;
|
|
40
|
+
exports.runUIOnDevice = runUIOnDevice;
|
|
41
|
+
exports.stopOnDevice = stopOnDevice;
|
|
42
|
+
const fsExtra = __importStar(require("fs-extra"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const build_1 = require("./build");
|
|
45
|
+
const crc_1 = __importDefault(require("crc"));
|
|
46
|
+
const ws_manager_1 = require("./ws-manager");
|
|
47
|
+
/**
|
|
48
|
+
* 读取并校验设备连接参数
|
|
49
|
+
* @param options 命令选项
|
|
50
|
+
* @returns 返回标准化后的设备地址信息
|
|
51
|
+
* @example
|
|
52
|
+
* parseHttpOptions({ ip: "192.168.1.10", port: "9800" })
|
|
53
|
+
*/
|
|
54
|
+
function parseHttpOptions(options) {
|
|
55
|
+
const ip = options.ip?.trim();
|
|
56
|
+
const portText = (options.port ?? "9800").trim();
|
|
57
|
+
const port = Number.parseInt(portText, 10);
|
|
58
|
+
if (!ip) {
|
|
59
|
+
throw new Error("请通过 --ip 指定设备 IP 地址");
|
|
60
|
+
}
|
|
61
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
62
|
+
throw new Error(`无效端口: ${portText}`);
|
|
63
|
+
}
|
|
64
|
+
return { ip, port };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 解析 WS 服务参数
|
|
68
|
+
* @param options 命令选项
|
|
69
|
+
* @returns 返回 wsPort 与等待设备连接超时时间
|
|
70
|
+
* @example
|
|
71
|
+
* parseWsOptions({ wsPort: "31111", wsWaitMs: "30000" })
|
|
72
|
+
*/
|
|
73
|
+
function parseWsOptions(options) {
|
|
74
|
+
const wsPortText = (options.wsPort ?? "31111").trim();
|
|
75
|
+
const wsWaitText = (options.wsWaitMs ?? "30000").trim();
|
|
76
|
+
const wsPort = Number.parseInt(wsPortText, 10);
|
|
77
|
+
const wsWaitMs = Number.parseInt(wsWaitText, 10);
|
|
78
|
+
if (!Number.isInteger(wsPort) || wsPort < 1 || wsPort > 65535) {
|
|
79
|
+
throw new Error(`无效 ws 端口: ${wsPortText}`);
|
|
80
|
+
}
|
|
81
|
+
if (!Number.isInteger(wsWaitMs) || wsWaitMs < 1) {
|
|
82
|
+
throw new Error(`无效 ws 等待时间: ${wsWaitText}`);
|
|
83
|
+
}
|
|
84
|
+
return { wsPort, wsWaitMs };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 解析传输方式
|
|
88
|
+
* @param options 命令选项
|
|
89
|
+
* @returns 返回 http 或 ws
|
|
90
|
+
* @example
|
|
91
|
+
* parseTransport({ transport: "ws" })
|
|
92
|
+
*/
|
|
93
|
+
function parseTransport(options) {
|
|
94
|
+
const transport = (options.transport ?? "http").trim().toLowerCase();
|
|
95
|
+
if (transport !== "http" && transport !== "ws") {
|
|
96
|
+
throw new Error(`无效 transport: ${transport}`);
|
|
97
|
+
}
|
|
98
|
+
return transport;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 计算文件的 CRC32 值
|
|
102
|
+
* @param filePath 文件路径
|
|
103
|
+
* @returns CRC32 十六进制字符串(8位)
|
|
104
|
+
* @example
|
|
105
|
+
* await calculateCrc32("/tmp/a.txt")
|
|
106
|
+
*/
|
|
107
|
+
async function calculateCrc32(filePath) {
|
|
108
|
+
const fileBuffer = await fsExtra.readFile(filePath);
|
|
109
|
+
const crc32 = crc_1.default.crc32(fileBuffer);
|
|
110
|
+
return crc32.toString(16).padStart(8, "0");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 递归收集目录中的所有文件
|
|
114
|
+
* @param rootDir 需要遍历的根目录
|
|
115
|
+
* @param bundlePrefix 输出到设备时的目录前缀
|
|
116
|
+
* @returns 返回可同步文件列表(含 size 与 crc32)
|
|
117
|
+
* @example
|
|
118
|
+
* await collectFilesInDir("/tmp/project/ui", "ui")
|
|
119
|
+
*/
|
|
120
|
+
async function collectFilesInDir(rootDir, bundlePrefix) {
|
|
121
|
+
const files = [];
|
|
122
|
+
async function walk(dir) {
|
|
123
|
+
const entries = await fsExtra.readdir(dir, { withFileTypes: true });
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const fullPath = path.join(dir, entry.name);
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
await walk(fullPath);
|
|
128
|
+
}
|
|
129
|
+
else if (entry.isFile()) {
|
|
130
|
+
const relPath = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
131
|
+
const stat = await fsExtra.stat(fullPath);
|
|
132
|
+
files.push({
|
|
133
|
+
path: fullPath,
|
|
134
|
+
bundlePath: `${bundlePrefix}/${relPath}`,
|
|
135
|
+
size: stat.size,
|
|
136
|
+
crc32: await calculateCrc32(fullPath),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (await fsExtra.pathExists(rootDir)) {
|
|
142
|
+
await walk(rootDir);
|
|
143
|
+
}
|
|
144
|
+
return files;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 读取项目运行所需的元信息
|
|
148
|
+
* @param workspacePath 项目根目录
|
|
149
|
+
* @returns 返回 projectName、appVersion 与 packageJsonPath
|
|
150
|
+
* @example
|
|
151
|
+
* await getProjectMeta("/Users/demo/project")
|
|
152
|
+
*/
|
|
153
|
+
async function getProjectMeta(workspacePath) {
|
|
154
|
+
const packageJsonPath = path.join(workspacePath, "package.json");
|
|
155
|
+
const packageJson = await fsExtra.readJSON(packageJsonPath);
|
|
156
|
+
const projectName = packageJson.name;
|
|
157
|
+
const appVersion = packageJson.appVersion;
|
|
158
|
+
if (!projectName) {
|
|
159
|
+
throw new Error("请在 package.json 中添加 name 字段");
|
|
160
|
+
}
|
|
161
|
+
if (!appVersion) {
|
|
162
|
+
throw new Error("请在 package.json 中添加 appVersion 字段");
|
|
163
|
+
}
|
|
164
|
+
return { projectName, appVersion, packageJsonPath };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 收集运行前需要同步的文件
|
|
168
|
+
* @param workspacePath 项目根目录
|
|
169
|
+
* @returns 返回项目文件清单(含 size 与 crc32)
|
|
170
|
+
* @example
|
|
171
|
+
* await collectProjectFiles("/Users/demo/project")
|
|
172
|
+
*/
|
|
173
|
+
async function collectProjectFiles(workspacePath) {
|
|
174
|
+
let scriptPath = path.join(workspacePath, "dist", "scripts");
|
|
175
|
+
const isPy = await fsExtra.pathExists(path.join(workspacePath, "scripts", "main.py"));
|
|
176
|
+
if (isPy) {
|
|
177
|
+
scriptPath = path.join(workspacePath, "scripts");
|
|
178
|
+
}
|
|
179
|
+
const uiPath = path.join(workspacePath, "ui");
|
|
180
|
+
const resPath = path.join(workspacePath, "res");
|
|
181
|
+
const packageJsonPath = path.join(workspacePath, "package.json");
|
|
182
|
+
const files = [
|
|
183
|
+
{
|
|
184
|
+
path: packageJsonPath,
|
|
185
|
+
bundlePath: "package.json",
|
|
186
|
+
size: (await fsExtra.stat(packageJsonPath)).size,
|
|
187
|
+
crc32: await calculateCrc32(packageJsonPath),
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
const [scriptFiles, uiFiles, resFiles] = await Promise.all([
|
|
191
|
+
collectFilesInDir(scriptPath, "scripts"),
|
|
192
|
+
collectFilesInDir(uiPath, "ui"),
|
|
193
|
+
collectFilesInDir(resPath, "res"),
|
|
194
|
+
]);
|
|
195
|
+
files.push(...scriptFiles, ...uiFiles, ...resFiles);
|
|
196
|
+
return files;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 执行项目构建(使用 build.ts 的 dev 模式)
|
|
200
|
+
* @param workspacePath 项目根目录
|
|
201
|
+
* @returns 构建成功时返回 Promise<void>
|
|
202
|
+
* @example
|
|
203
|
+
* await runDevBuild("/Users/demo/project")
|
|
204
|
+
*/
|
|
205
|
+
async function runDevBuild(workspacePath) {
|
|
206
|
+
await (0, build_1.buildAll)(true, workspacePath);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 获取设备上的文件列表
|
|
210
|
+
* @param ip 设备 IP 地址
|
|
211
|
+
* @param port 设备端口
|
|
212
|
+
* @param projectName 项目名称
|
|
213
|
+
* @returns 设备当前文件信息列表
|
|
214
|
+
* @example
|
|
215
|
+
* await listDeviceFiles("192.168.1.10", 9800, "demo")
|
|
216
|
+
*/
|
|
217
|
+
async function listDeviceFiles(ip, port, projectName) {
|
|
218
|
+
const url = new URL(`http://${ip}:${port}/api/files`);
|
|
219
|
+
url.searchParams.set("projectName", projectName);
|
|
220
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
throw new Error(`文件列表请求失败: ${response.statusText}`);
|
|
223
|
+
}
|
|
224
|
+
const data = (await response.json());
|
|
225
|
+
return Array.isArray(data) ? data : [];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 通过 WS 获取设备上的文件列表
|
|
229
|
+
* @param projectName 项目名称
|
|
230
|
+
* @returns 设备当前文件信息列表
|
|
231
|
+
* @example
|
|
232
|
+
* await listDeviceFilesByWs("demo")
|
|
233
|
+
*/
|
|
234
|
+
async function listDeviceFilesByWs(projectName) {
|
|
235
|
+
const result = await ws_manager_1.WSManager.request("files", { projectName });
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
throw new Error(`文件列表请求失败: ${String(result.message ?? "unknown")}`);
|
|
238
|
+
}
|
|
239
|
+
const data = result.data;
|
|
240
|
+
return Array.isArray(data) ? data : [];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 将项目文件增量同步到设备
|
|
244
|
+
* @param workspacePath 项目根目录
|
|
245
|
+
* @param ip 设备 IP 地址
|
|
246
|
+
* @param port 设备端口
|
|
247
|
+
* @returns 同步完成后返回 Promise<void>
|
|
248
|
+
* @example
|
|
249
|
+
* await syncProjectToDevice("/Users/demo/project", "192.168.1.10", 9800)
|
|
250
|
+
*/
|
|
251
|
+
async function syncProjectToDevice(workspacePath, ip, port) {
|
|
252
|
+
const { projectName, appVersion } = await getProjectMeta(workspacePath);
|
|
253
|
+
console.log("🔎 正在获取设备文件列表...");
|
|
254
|
+
const deviceFiles = await listDeviceFiles(ip, port, projectName);
|
|
255
|
+
const deviceFileMap = new Map(deviceFiles.map((file) => [file.path, file]));
|
|
256
|
+
console.log("📂 正在分析项目文件...");
|
|
257
|
+
const files = await collectProjectFiles(workspacePath);
|
|
258
|
+
const filesToSync = [];
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
const deviceFile = deviceFileMap.get(file.bundlePath);
|
|
261
|
+
const requireSyncFile = file.bundlePath.includes("main.js") ||
|
|
262
|
+
file.bundlePath.includes("main.py");
|
|
263
|
+
if (!deviceFile) {
|
|
264
|
+
filesToSync.push(file);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const changed = deviceFile.size !== file.size ||
|
|
268
|
+
deviceFile.crc32.toLowerCase() !== file.crc32.toLowerCase();
|
|
269
|
+
if (requireSyncFile || changed) {
|
|
270
|
+
filesToSync.push(file);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
console.log(`📦 发现 ${filesToSync.length} 个文件需要同步`);
|
|
274
|
+
if (filesToSync.length === 0) {
|
|
275
|
+
console.log("✅ 无需同步文件");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
let syncedCount = 0;
|
|
279
|
+
for (const file of filesToSync) {
|
|
280
|
+
syncedCount += 1;
|
|
281
|
+
console.log(`⬆️ [${syncedCount}/${filesToSync.length}] 同步 ${file.bundlePath}`);
|
|
282
|
+
const binary = await fsExtra.readFile(file.path);
|
|
283
|
+
const url = new URL(`http://${ip}:${port}/debug/patchSync`);
|
|
284
|
+
url.searchParams.set("path", file.bundlePath);
|
|
285
|
+
url.searchParams.set("projectName", projectName);
|
|
286
|
+
url.searchParams.set("appVersion", appVersion);
|
|
287
|
+
const response = await fetch(url.toString(), {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/octet-stream",
|
|
291
|
+
},
|
|
292
|
+
body: binary,
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
throw new Error(`同步失败: ${file.bundlePath} (状态码: ${response.status})`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
console.log(`✅ 同步完成,共 ${filesToSync.length} 个文件`);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 通过 WS 将项目文件增量同步到设备
|
|
302
|
+
* @param workspacePath 项目根目录
|
|
303
|
+
* @returns 同步完成后返回 Promise<void>
|
|
304
|
+
* @example
|
|
305
|
+
* await syncProjectToDeviceByWs("/Users/demo/project")
|
|
306
|
+
*/
|
|
307
|
+
async function syncProjectToDeviceByWs(workspacePath) {
|
|
308
|
+
const { projectName, appVersion } = await getProjectMeta(workspacePath);
|
|
309
|
+
console.log("🔎 正在获取设备文件列表...");
|
|
310
|
+
const deviceFiles = await listDeviceFilesByWs(projectName);
|
|
311
|
+
const deviceFileMap = new Map(deviceFiles.map((file) => [file.path, file]));
|
|
312
|
+
console.log("📂 正在分析项目文件...");
|
|
313
|
+
const files = await collectProjectFiles(workspacePath);
|
|
314
|
+
const filesToSync = [];
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
const deviceFile = deviceFileMap.get(file.bundlePath);
|
|
317
|
+
const requireSyncFile = file.bundlePath.includes("main.js") ||
|
|
318
|
+
file.bundlePath.includes("main.py");
|
|
319
|
+
if (!deviceFile) {
|
|
320
|
+
filesToSync.push(file);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const changed = deviceFile.size !== file.size ||
|
|
324
|
+
deviceFile.crc32.toLowerCase() !== file.crc32.toLowerCase();
|
|
325
|
+
if (requireSyncFile || changed) {
|
|
326
|
+
filesToSync.push(file);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
console.log(`📦 发现 ${filesToSync.length} 个文件需要同步`);
|
|
330
|
+
if (filesToSync.length === 0) {
|
|
331
|
+
console.log("✅ 无需同步文件");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
let syncedCount = 0;
|
|
335
|
+
for (const file of filesToSync) {
|
|
336
|
+
syncedCount += 1;
|
|
337
|
+
console.log(`⬆️ [${syncedCount}/${filesToSync.length}] 同步 ${file.bundlePath}`);
|
|
338
|
+
const binary = await fsExtra.readFile(file.path);
|
|
339
|
+
const result = await ws_manager_1.WSManager.request("patchSync", {
|
|
340
|
+
path: file.bundlePath,
|
|
341
|
+
data: binary.toString("base64"),
|
|
342
|
+
projectName,
|
|
343
|
+
appVersion,
|
|
344
|
+
});
|
|
345
|
+
if (!result.success) {
|
|
346
|
+
throw new Error(`同步失败: ${file.bundlePath} ${String(result.message ?? "")}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
console.log(`✅ 同步完成,共 ${filesToSync.length} 个文件`);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 发送设备动作请求
|
|
353
|
+
* @param action 动作名称,支持 run 与 stop
|
|
354
|
+
* @param ip 设备 IP 地址
|
|
355
|
+
* @param port 设备端口
|
|
356
|
+
* @returns 请求成功时返回 Promise<void>
|
|
357
|
+
* @example
|
|
358
|
+
* await requestDeviceAction("stop", "192.168.1.10", 9800)
|
|
359
|
+
*/
|
|
360
|
+
async function requestDeviceAction(action, ip, port) {
|
|
361
|
+
const url = `http://${ip}:${port}/api/${action}`;
|
|
362
|
+
const response = await fetch(url, { method: "GET" });
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
throw new Error(`${action} 失败,状态码: ${response.status}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* 通过 WS 发送设备动作请求
|
|
369
|
+
* @param action 动作名称,支持 run、runUI、stop
|
|
370
|
+
* @returns 请求成功时返回 Promise<void>
|
|
371
|
+
* @example
|
|
372
|
+
* await requestDeviceActionByWs("run")
|
|
373
|
+
*/
|
|
374
|
+
async function requestDeviceActionByWs(action) {
|
|
375
|
+
const result = await ws_manager_1.WSManager.request(action);
|
|
376
|
+
if (!result.success) {
|
|
377
|
+
throw new Error(`${action} 失败: ${String(result.message ?? "unknown")}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 确保 WS 服务可用并已连接设备
|
|
382
|
+
* @param options 命令选项
|
|
383
|
+
* @returns 返回 Promise<void>
|
|
384
|
+
* @example
|
|
385
|
+
* await ensureWsReady({ wsPort: "31111", wsWaitMs: "30000" })
|
|
386
|
+
*/
|
|
387
|
+
async function ensureWsReady(options) {
|
|
388
|
+
const { wsPort, wsWaitMs } = parseWsOptions(options);
|
|
389
|
+
const ws = ws_manager_1.WSManager.get();
|
|
390
|
+
if (!ws.isStarted()) {
|
|
391
|
+
await ws.start(wsPort);
|
|
392
|
+
}
|
|
393
|
+
if (!ws_manager_1.WSManager.isConnected()) {
|
|
394
|
+
console.log("⌛ 等待设备通过 WS 连接...");
|
|
395
|
+
await ws.waitForClient(wsWaitMs);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 在设备上运行项目(先同步后运行)
|
|
400
|
+
* @param options 命令选项
|
|
401
|
+
* @returns 执行完成后返回 Promise<void>
|
|
402
|
+
* @example
|
|
403
|
+
* await runOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
404
|
+
*/
|
|
405
|
+
async function runOnDevice(options) {
|
|
406
|
+
const workspacePath = options.path
|
|
407
|
+
? path.resolve(options.path)
|
|
408
|
+
: process.cwd();
|
|
409
|
+
const transport = parseTransport(options);
|
|
410
|
+
console.log(`🔧 开始构建项目: ${workspacePath}`);
|
|
411
|
+
await runDevBuild(workspacePath);
|
|
412
|
+
if (transport === "ws") {
|
|
413
|
+
await ensureWsReady(options);
|
|
414
|
+
console.log("📦 开始通过 WS 同步项目到设备");
|
|
415
|
+
await syncProjectToDeviceByWs(workspacePath);
|
|
416
|
+
console.log("🚀 开始通过 WS 运行项目");
|
|
417
|
+
await requestDeviceActionByWs("run");
|
|
418
|
+
console.log("✅ 运行请求已发送");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const { ip, port } = parseHttpOptions(options);
|
|
422
|
+
console.log(`📦 开始同步项目到设备: ${ip}:${port}`);
|
|
423
|
+
await syncProjectToDevice(workspacePath, ip, port);
|
|
424
|
+
console.log(`🚀 开始运行项目: ${ip}:${port}`);
|
|
425
|
+
await requestDeviceAction("run", ip, port);
|
|
426
|
+
console.log("✅ 运行请求已发送");
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 在设备上预览 UI(先同步后调用 runUI)
|
|
430
|
+
* @param options 命令选项
|
|
431
|
+
* @returns 执行完成后返回 Promise<void>
|
|
432
|
+
* @example
|
|
433
|
+
* await runUIOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
434
|
+
*/
|
|
435
|
+
async function runUIOnDevice(options) {
|
|
436
|
+
const workspacePath = options.path
|
|
437
|
+
? path.resolve(options.path)
|
|
438
|
+
: process.cwd();
|
|
439
|
+
const transport = parseTransport(options);
|
|
440
|
+
console.log(`🔧 开始构建项目: ${workspacePath}`);
|
|
441
|
+
await runDevBuild(workspacePath);
|
|
442
|
+
if (transport === "ws") {
|
|
443
|
+
await ensureWsReady(options);
|
|
444
|
+
console.log("📦 开始通过 WS 同步项目到设备");
|
|
445
|
+
await syncProjectToDeviceByWs(workspacePath);
|
|
446
|
+
console.log("🖼️ 开始通过 WS 预览 UI");
|
|
447
|
+
await requestDeviceActionByWs("runUI");
|
|
448
|
+
console.log("✅ UI 预览请求已发送");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const { ip, port } = parseHttpOptions(options);
|
|
452
|
+
console.log(`📦 开始同步项目到设备: ${ip}:${port}`);
|
|
453
|
+
await syncProjectToDevice(workspacePath, ip, port);
|
|
454
|
+
console.log(`🖼️ 开始预览 UI: ${ip}:${port}`);
|
|
455
|
+
await requestDeviceAction("runUI", ip, port);
|
|
456
|
+
console.log("✅ UI 预览请求已发送");
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 停止设备上的项目
|
|
460
|
+
* @param options 命令选项
|
|
461
|
+
* @returns 执行完成后返回 Promise<void>
|
|
462
|
+
* @example
|
|
463
|
+
* await stopOnDevice({ ip: "192.168.1.10", port: "9800" })
|
|
464
|
+
*/
|
|
465
|
+
async function stopOnDevice(options) {
|
|
466
|
+
const transport = parseTransport(options);
|
|
467
|
+
if (transport === "ws") {
|
|
468
|
+
await ensureWsReady(options);
|
|
469
|
+
console.log("🛑 开始通过 WS 停止项目");
|
|
470
|
+
await requestDeviceActionByWs("stop");
|
|
471
|
+
console.log("✅ 停止请求已发送");
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const { ip, port } = parseHttpOptions(options);
|
|
475
|
+
console.log(`🛑 开始停止项目: ${ip}:${port}`);
|
|
476
|
+
await requestDeviceAction("stop", ip, port);
|
|
477
|
+
console.log("✅ 停止请求已发送");
|
|
478
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS 响应数据结构
|
|
3
|
+
*/
|
|
4
|
+
export type WsResponse = {
|
|
5
|
+
requestId: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
message?: unknown;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* CLI 版 WebSocket 管理器
|
|
12
|
+
*/
|
|
13
|
+
export declare class WSManager {
|
|
14
|
+
private static instance;
|
|
15
|
+
private wss;
|
|
16
|
+
private client;
|
|
17
|
+
private checkInterval;
|
|
18
|
+
private readonly hostIp;
|
|
19
|
+
private port;
|
|
20
|
+
private pending;
|
|
21
|
+
/**
|
|
22
|
+
* 获取 WS 管理器单例
|
|
23
|
+
* @returns 返回单例实例
|
|
24
|
+
* @example
|
|
25
|
+
* const ws = WSManager.get()
|
|
26
|
+
*/
|
|
27
|
+
static get(): WSManager;
|
|
28
|
+
/**
|
|
29
|
+
* 判断是否已有设备连接
|
|
30
|
+
* @returns 已连接返回 true,否则 false
|
|
31
|
+
* @example
|
|
32
|
+
* WSManager.isConnected()
|
|
33
|
+
*/
|
|
34
|
+
static isConnected(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 静态请求入口
|
|
37
|
+
* @param type 请求类型
|
|
38
|
+
* @param message 请求参数
|
|
39
|
+
* @param timeoutMs 超时时间(毫秒)
|
|
40
|
+
* @returns 返回设备响应
|
|
41
|
+
* @example
|
|
42
|
+
* await WSManager.request("runtime_status", {}, 5000)
|
|
43
|
+
*/
|
|
44
|
+
static request(type: string, message?: Record<string, unknown>, timeoutMs?: number): Promise<WsResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* 获取监听地址
|
|
47
|
+
* @returns 返回 ws 地址
|
|
48
|
+
* @example
|
|
49
|
+
* ws.getAddress()
|
|
50
|
+
*/
|
|
51
|
+
getAddress(): string;
|
|
52
|
+
/**
|
|
53
|
+
* 判断服务器是否已启动
|
|
54
|
+
* @returns 已启动返回 true,否则 false
|
|
55
|
+
* @example
|
|
56
|
+
* ws.isStarted()
|
|
57
|
+
*/
|
|
58
|
+
isStarted(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* 启动 WS 服务
|
|
61
|
+
* @param port 指定端口,默认 31111
|
|
62
|
+
* @returns 启动完成后返回 Promise<void>
|
|
63
|
+
* @example
|
|
64
|
+
* await ws.start(31111)
|
|
65
|
+
*/
|
|
66
|
+
start(port?: number): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* 等待设备连接
|
|
69
|
+
* @param timeoutMs 超时时间(毫秒)
|
|
70
|
+
* @returns 成功连接后返回 Promise<void>
|
|
71
|
+
* @example
|
|
72
|
+
* await ws.waitForClient(30000)
|
|
73
|
+
*/
|
|
74
|
+
waitForClient(timeoutMs?: number): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* 发送请求并等待响应
|
|
77
|
+
* @param type 请求类型
|
|
78
|
+
* @param message 请求消息
|
|
79
|
+
* @param timeoutMs 超时时间(毫秒)
|
|
80
|
+
* @returns 返回设备响应
|
|
81
|
+
* @example
|
|
82
|
+
* await ws.request("files", {}, 60000)
|
|
83
|
+
*/
|
|
84
|
+
request(type: string, message?: Record<string, unknown>, timeoutMs?: number): Promise<WsResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* 停止 WS 服务
|
|
87
|
+
* @returns 停止完成后返回 Promise<void>
|
|
88
|
+
* @example
|
|
89
|
+
* await ws.stop()
|
|
90
|
+
*/
|
|
91
|
+
stop(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* 启动运行状态轮询
|
|
94
|
+
* @returns 无返回值
|
|
95
|
+
* @example
|
|
96
|
+
* ws.startStatusInterval()
|
|
97
|
+
*/
|
|
98
|
+
private startStatusInterval;
|
|
99
|
+
/**
|
|
100
|
+
* 停止运行状态轮询
|
|
101
|
+
* @returns 无返回值
|
|
102
|
+
* @example
|
|
103
|
+
* ws.stopStatusInterval()
|
|
104
|
+
*/
|
|
105
|
+
private stopStatusInterval;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 对齐原有命名导出
|
|
109
|
+
*/
|
|
110
|
+
export declare const WebSocketServerManager: typeof WSManager;
|