ms-vite-plugin 1.0.1 → 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/build.js +2 -2
- 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 +13 -7
package/dist/build.js
CHANGED
|
@@ -42,7 +42,6 @@ const child_process_1 = require("child_process");
|
|
|
42
42
|
const fsExtra = __importStar(require("fs-extra"));
|
|
43
43
|
const os = __importStar(require("os"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
|
-
const vite_1 = require("vite");
|
|
46
45
|
const vite_plugin_bundle_obfuscator_1 = __importDefault(require("vite-plugin-bundle-obfuscator"));
|
|
47
46
|
async function zipDirectory(source, out) {
|
|
48
47
|
const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } });
|
|
@@ -167,6 +166,7 @@ const buildAll = async (isDev = true, workspacePath) => {
|
|
|
167
166
|
const isPy = await fsExtra.pathExists(path.join(workspacePath, "scripts", "main.py"));
|
|
168
167
|
if (isJs) {
|
|
169
168
|
console.log("🚀 开始构建JavaScript项目...");
|
|
169
|
+
const { build } = await import("vite");
|
|
170
170
|
const obfuscatorConfigPath = path.join(workspacePath, "obfuscator.json");
|
|
171
171
|
if (!(await fsExtra.pathExists(obfuscatorConfigPath))) {
|
|
172
172
|
console.error("❌ 错误: 当前目录不是有效的 快点JS 项目");
|
|
@@ -180,7 +180,7 @@ const buildAll = async (isDev = true, workspacePath) => {
|
|
|
180
180
|
const entryFiles = await findEntryFiles(baseDir, baseDir);
|
|
181
181
|
await fsExtra.remove(outputDir);
|
|
182
182
|
for (const entry of entryFiles) {
|
|
183
|
-
await
|
|
183
|
+
await build({
|
|
184
184
|
root: workspacePath,
|
|
185
185
|
build: {
|
|
186
186
|
lib: {
|
package/dist/cli.js
CHANGED
|
@@ -35,9 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
const commander_1 = require("commander");
|
|
38
|
+
const os = __importStar(require("os"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const build_1 = require("./build");
|
|
40
41
|
const fsExtra = __importStar(require("fs-extra"));
|
|
42
|
+
const project_1 = require("./project");
|
|
43
|
+
const ws_manager_1 = require("./ws-manager");
|
|
44
|
+
const WS_PID_FILE = path.join(os.tmpdir(), "ms-cli-ws-server.json");
|
|
41
45
|
/**
|
|
42
46
|
* 检查工作目录是否为有效的 KuaiJS 项目
|
|
43
47
|
* @param workspacePath 工作目录路径
|
|
@@ -87,6 +91,159 @@ async function buildCommand(options) {
|
|
|
87
91
|
process.exit(1);
|
|
88
92
|
}
|
|
89
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* run 命令处理函数
|
|
96
|
+
* @param options 命令选项
|
|
97
|
+
* @returns 请求完成后退出
|
|
98
|
+
* @example
|
|
99
|
+
* ms run --ip 192.168.1.100 --port 9800 --path ./
|
|
100
|
+
*/
|
|
101
|
+
async function runCommand(options) {
|
|
102
|
+
try {
|
|
103
|
+
await (0, project_1.runOnDevice)(options);
|
|
104
|
+
console.log("✅ 运行请求已发送");
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("❌ 运行失败:", error instanceof Error ? error.message : error);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* runUI 命令处理函数
|
|
113
|
+
* @param options 命令选项
|
|
114
|
+
* @returns 请求完成后退出
|
|
115
|
+
* @example
|
|
116
|
+
* ms run-ui --ip 192.168.1.100 --port 9800 --path ./
|
|
117
|
+
*/
|
|
118
|
+
async function runUICommand(options) {
|
|
119
|
+
try {
|
|
120
|
+
await (0, project_1.runUIOnDevice)(options);
|
|
121
|
+
console.log("✅ UI 预览请求已发送");
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error("❌ UI 预览失败:", error instanceof Error ? error.message : error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* stop 命令处理函数
|
|
130
|
+
* @param options 命令选项
|
|
131
|
+
* @returns 请求完成后退出
|
|
132
|
+
* @example
|
|
133
|
+
* ms stop --ip 192.168.1.100 --port 9800
|
|
134
|
+
*/
|
|
135
|
+
async function stopCommand(options) {
|
|
136
|
+
try {
|
|
137
|
+
await (0, project_1.stopOnDevice)(options);
|
|
138
|
+
console.log("✅ 停止请求已发送");
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error("❌ 停止失败:", error instanceof Error ? error.message : error);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* ws-start 命令处理函数
|
|
147
|
+
* @param options 命令选项
|
|
148
|
+
* @returns 常驻直到收到退出信号
|
|
149
|
+
* @example
|
|
150
|
+
* ms ws-start --ws-port 31111
|
|
151
|
+
*/
|
|
152
|
+
async function wsStartCommand(options) {
|
|
153
|
+
try {
|
|
154
|
+
const wsPortText = (options.wsPort ?? "31111").trim();
|
|
155
|
+
const wsPort = Number.parseInt(wsPortText, 10);
|
|
156
|
+
if (!Number.isInteger(wsPort) || wsPort < 1 || wsPort > 65535) {
|
|
157
|
+
throw new Error(`无效 ws 端口: ${wsPortText}`);
|
|
158
|
+
}
|
|
159
|
+
const ws = ws_manager_1.WSManager.get();
|
|
160
|
+
await ws.start(wsPort);
|
|
161
|
+
await fsExtra.writeJSON(WS_PID_FILE, {
|
|
162
|
+
pid: process.pid,
|
|
163
|
+
port: wsPort,
|
|
164
|
+
address: ws.getAddress(),
|
|
165
|
+
startedAt: Date.now(),
|
|
166
|
+
});
|
|
167
|
+
console.log("按 Ctrl+C 停止 WS 服务");
|
|
168
|
+
await new Promise((resolve) => {
|
|
169
|
+
const shutdown = async () => {
|
|
170
|
+
process.off("SIGINT", shutdown);
|
|
171
|
+
process.off("SIGTERM", shutdown);
|
|
172
|
+
await ws.stop();
|
|
173
|
+
await fsExtra.remove(WS_PID_FILE);
|
|
174
|
+
resolve();
|
|
175
|
+
};
|
|
176
|
+
process.on("SIGINT", shutdown);
|
|
177
|
+
process.on("SIGTERM", shutdown);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
console.error("❌ WS 服务启动失败:", error instanceof Error ? error.message : error);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* ws-stop 命令处理函数
|
|
187
|
+
* @returns 请求完成后退出
|
|
188
|
+
* @example
|
|
189
|
+
* ms ws-stop
|
|
190
|
+
*/
|
|
191
|
+
async function wsStopCommand() {
|
|
192
|
+
try {
|
|
193
|
+
if (!(await fsExtra.pathExists(WS_PID_FILE))) {
|
|
194
|
+
console.log("WS 服务未运行");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const info = await fsExtra.readJSON(WS_PID_FILE);
|
|
198
|
+
const pid = Number(info.pid);
|
|
199
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
200
|
+
await fsExtra.remove(WS_PID_FILE);
|
|
201
|
+
throw new Error("WS PID 文件损坏");
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
process.kill(pid, "SIGTERM");
|
|
205
|
+
console.log(`✅ 已发送停止信号到 WS 进程: ${pid}`);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
console.log("WS 进程不存在,清理 PID 文件");
|
|
209
|
+
}
|
|
210
|
+
await fsExtra.remove(WS_PID_FILE);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error("❌ WS 服务停止失败:", error instanceof Error ? error.message : error);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* ws-status 命令处理函数
|
|
219
|
+
* @returns 请求完成后退出
|
|
220
|
+
* @example
|
|
221
|
+
* ms ws-status
|
|
222
|
+
*/
|
|
223
|
+
async function wsStatusCommand() {
|
|
224
|
+
if (!(await fsExtra.pathExists(WS_PID_FILE))) {
|
|
225
|
+
console.log("服务状态: stopped");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const info = await fsExtra.readJSON(WS_PID_FILE);
|
|
229
|
+
const pid = Number(info.pid);
|
|
230
|
+
const address = String(info.address || "unknown");
|
|
231
|
+
let running = false;
|
|
232
|
+
try {
|
|
233
|
+
process.kill(pid, 0);
|
|
234
|
+
running = true;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
running = false;
|
|
238
|
+
}
|
|
239
|
+
if (!running) {
|
|
240
|
+
await fsExtra.remove(WS_PID_FILE);
|
|
241
|
+
console.log("服务状态: stopped");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
console.log(`服务状态: running (pid=${pid})`);
|
|
245
|
+
console.log(`WS 地址: ${address}`);
|
|
246
|
+
}
|
|
90
247
|
// 配置 CLI 程序
|
|
91
248
|
commander_1.program.name("ms-cli").description("快点JS 构建工具").version("0.0.7");
|
|
92
249
|
// 构建命令
|
|
@@ -96,5 +253,53 @@ commander_1.program
|
|
|
96
253
|
.option("-d, --dev", "开发模式构建 (包含 source map)")
|
|
97
254
|
.option("-p, --path <path>", "指定项目路径")
|
|
98
255
|
.action(buildCommand);
|
|
256
|
+
// 运行命令
|
|
257
|
+
commander_1.program
|
|
258
|
+
.command("run")
|
|
259
|
+
.description("在设备上运行项目")
|
|
260
|
+
.option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
|
|
261
|
+
.option("--port <port>", "设备端口 (默认: 9800)", "9800")
|
|
262
|
+
.option("-t, --transport <transport>", "传输方式: http|ws", "http")
|
|
263
|
+
.option("--ws-port <wsPort>", "WS 服务端口 (默认: 31111)", "31111")
|
|
264
|
+
.option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
|
|
265
|
+
.option("-p, --path <path>", "指定项目路径")
|
|
266
|
+
.action(runCommand);
|
|
267
|
+
// UI 预览命令
|
|
268
|
+
commander_1.program
|
|
269
|
+
.command("run-ui")
|
|
270
|
+
.description("在设备上预览 UI")
|
|
271
|
+
.option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
|
|
272
|
+
.option("--port <port>", "设备端口 (默认: 9800)", "9800")
|
|
273
|
+
.option("-t, --transport <transport>", "传输方式: http|ws", "http")
|
|
274
|
+
.option("--ws-port <wsPort>", "WS 服务端口 (默认: 31111)", "31111")
|
|
275
|
+
.option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
|
|
276
|
+
.option("-p, --path <path>", "指定项目路径")
|
|
277
|
+
.action(runUICommand);
|
|
278
|
+
// 停止命令
|
|
279
|
+
commander_1.program
|
|
280
|
+
.command("stop")
|
|
281
|
+
.description("停止设备上的项目")
|
|
282
|
+
.option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
|
|
283
|
+
.option("--port <port>", "设备端口 (默认: 9800)", "9800")
|
|
284
|
+
.option("-t, --transport <transport>", "传输方式: http|ws", "http")
|
|
285
|
+
.option("--ws-port <wsPort>", "WS 服务端口 (默认: 31111)", "31111")
|
|
286
|
+
.option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
|
|
287
|
+
.action(stopCommand);
|
|
288
|
+
// WS 服务启动命令
|
|
289
|
+
commander_1.program
|
|
290
|
+
.command("ws-start")
|
|
291
|
+
.description("启动 CLI 内置 WS 服务")
|
|
292
|
+
.option("--ws-port <wsPort>", "WS 服务端口 (默认: 31111)", "31111")
|
|
293
|
+
.action(wsStartCommand);
|
|
294
|
+
// WS 服务停止命令
|
|
295
|
+
commander_1.program
|
|
296
|
+
.command("ws-stop")
|
|
297
|
+
.description("停止 CLI 内置 WS 服务")
|
|
298
|
+
.action(wsStopCommand);
|
|
299
|
+
// WS 服务状态命令
|
|
300
|
+
commander_1.program
|
|
301
|
+
.command("ws-status")
|
|
302
|
+
.description("查看 CLI 内置 WS 服务状态")
|
|
303
|
+
.action(wsStatusCommand);
|
|
99
304
|
// 解析命令行参数
|
|
100
305
|
commander_1.program.parse();
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设备命令选项
|
|
3
|
+
*/
|
|
4
|
+
export interface DeviceCliOptions {
|
|
5
|
+
ip: string;
|
|
6
|
+
port?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 在设备上运行项目(先同步后运行)
|
|
11
|
+
* @param options 命令选项
|
|
12
|
+
* @returns 执行完成后返回 Promise<void>
|
|
13
|
+
* @example
|
|
14
|
+
* await runOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
15
|
+
*/
|
|
16
|
+
export declare function runOnDevice(options: DeviceCliOptions): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 在设备上预览 UI(先同步后调用 runUI)
|
|
19
|
+
* @param options 命令选项
|
|
20
|
+
* @returns 执行完成后返回 Promise<void>
|
|
21
|
+
* @example
|
|
22
|
+
* await runUIOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
23
|
+
*/
|
|
24
|
+
export declare function runUIOnDevice(options: DeviceCliOptions): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 停止设备上的项目
|
|
27
|
+
* @param options 命令选项
|
|
28
|
+
* @returns 执行完成后返回 Promise<void>
|
|
29
|
+
* @example
|
|
30
|
+
* await stopOnDevice({ ip: "192.168.1.10", port: "9800" })
|
|
31
|
+
*/
|
|
32
|
+
export declare function stopOnDevice(options: DeviceCliOptions): Promise<void>;
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runOnDevice = runOnDevice;
|
|
37
|
+
exports.runUIOnDevice = runUIOnDevice;
|
|
38
|
+
exports.stopOnDevice = stopOnDevice;
|
|
39
|
+
const fsExtra = __importStar(require("fs-extra"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const build_js_1 = require("./build.js");
|
|
42
|
+
/**
|
|
43
|
+
* 读取并校验设备连接参数
|
|
44
|
+
* @param options 命令选项
|
|
45
|
+
* @returns 返回标准化后的设备地址信息
|
|
46
|
+
* @example
|
|
47
|
+
* parseDeviceOptions({ ip: "192.168.1.10", port: "9800" })
|
|
48
|
+
*/
|
|
49
|
+
function parseDeviceOptions(options) {
|
|
50
|
+
const ip = options.ip?.trim();
|
|
51
|
+
const portText = (options.port ?? "9800").trim();
|
|
52
|
+
const port = Number.parseInt(portText, 10);
|
|
53
|
+
if (!ip) {
|
|
54
|
+
throw new Error("请通过 --ip 指定设备 IP 地址");
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
57
|
+
throw new Error(`无效端口: ${portText}`);
|
|
58
|
+
}
|
|
59
|
+
return { ip, port };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 计算 Buffer 的 CRC32 值
|
|
63
|
+
* @param input 二进制输入数据
|
|
64
|
+
* @returns CRC32 十六进制字符串(8位)
|
|
65
|
+
* @example
|
|
66
|
+
* calculateCrc32FromBuffer(Buffer.from("hello"))
|
|
67
|
+
*/
|
|
68
|
+
function calculateCrc32FromBuffer(input) {
|
|
69
|
+
let crc = 0xffffffff;
|
|
70
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
71
|
+
crc ^= input[i];
|
|
72
|
+
for (let j = 0; j < 8; j += 1) {
|
|
73
|
+
if ((crc & 1) === 1) {
|
|
74
|
+
crc = (crc >>> 1) ^ 0xedb88320;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
crc >>>= 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const finalValue = (crc ^ 0xffffffff) >>> 0;
|
|
82
|
+
return finalValue.toString(16).padStart(8, "0");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 计算文件的 CRC32 值
|
|
86
|
+
* @param filePath 文件路径
|
|
87
|
+
* @returns CRC32 十六进制字符串(8位)
|
|
88
|
+
* @example
|
|
89
|
+
* await calculateCrc32("/tmp/a.txt")
|
|
90
|
+
*/
|
|
91
|
+
async function calculateCrc32(filePath) {
|
|
92
|
+
const fileBuffer = await fsExtra.readFile(filePath);
|
|
93
|
+
return calculateCrc32FromBuffer(fileBuffer);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 递归收集目录中的所有文件
|
|
97
|
+
* @param rootDir 需要遍历的根目录
|
|
98
|
+
* @param bundlePrefix 输出到设备时的目录前缀
|
|
99
|
+
* @returns 返回可同步文件列表(含 size 与 crc32)
|
|
100
|
+
* @example
|
|
101
|
+
* await collectFilesInDir("/tmp/project/ui", "ui")
|
|
102
|
+
*/
|
|
103
|
+
async function collectFilesInDir(rootDir, bundlePrefix) {
|
|
104
|
+
const files = [];
|
|
105
|
+
async function walk(dir) {
|
|
106
|
+
const entries = await fsExtra.readdir(dir, { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const fullPath = path.join(dir, entry.name);
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
await walk(fullPath);
|
|
111
|
+
}
|
|
112
|
+
else if (entry.isFile()) {
|
|
113
|
+
const relPath = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
114
|
+
const stat = await fsExtra.stat(fullPath);
|
|
115
|
+
files.push({
|
|
116
|
+
path: fullPath,
|
|
117
|
+
bundlePath: `${bundlePrefix}/${relPath}`,
|
|
118
|
+
size: stat.size,
|
|
119
|
+
crc32: await calculateCrc32(fullPath),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (await fsExtra.pathExists(rootDir)) {
|
|
125
|
+
await walk(rootDir);
|
|
126
|
+
}
|
|
127
|
+
return files;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 读取项目运行所需的元信息
|
|
131
|
+
* @param workspacePath 项目根目录
|
|
132
|
+
* @returns 返回 projectName、appVersion 与 packageJsonPath
|
|
133
|
+
* @example
|
|
134
|
+
* await getProjectMeta("/Users/demo/project")
|
|
135
|
+
*/
|
|
136
|
+
async function getProjectMeta(workspacePath) {
|
|
137
|
+
const packageJsonPath = path.join(workspacePath, "package.json");
|
|
138
|
+
const packageJson = await fsExtra.readJSON(packageJsonPath);
|
|
139
|
+
const projectName = packageJson.name;
|
|
140
|
+
const appVersion = packageJson.appVersion;
|
|
141
|
+
if (!projectName) {
|
|
142
|
+
throw new Error("请在 package.json 中添加 name 字段");
|
|
143
|
+
}
|
|
144
|
+
if (!appVersion) {
|
|
145
|
+
throw new Error("请在 package.json 中添加 appVersion 字段");
|
|
146
|
+
}
|
|
147
|
+
return { projectName, appVersion, packageJsonPath };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 收集运行前需要同步的文件
|
|
151
|
+
* @param workspacePath 项目根目录
|
|
152
|
+
* @returns 返回项目文件清单(含 size 与 crc32)
|
|
153
|
+
* @example
|
|
154
|
+
* await collectProjectFiles("/Users/demo/project")
|
|
155
|
+
*/
|
|
156
|
+
async function collectProjectFiles(workspacePath) {
|
|
157
|
+
const scriptDistPath = path.join(workspacePath, "dist", "scripts");
|
|
158
|
+
const scriptSrcPath = path.join(workspacePath, "scripts");
|
|
159
|
+
const uiPath = path.join(workspacePath, "ui");
|
|
160
|
+
const resPath = path.join(workspacePath, "res");
|
|
161
|
+
const packageJsonPath = path.join(workspacePath, "package.json");
|
|
162
|
+
const useDistScripts = await fsExtra.pathExists(scriptDistPath);
|
|
163
|
+
const scriptPath = useDistScripts ? scriptDistPath : scriptSrcPath;
|
|
164
|
+
const files = [
|
|
165
|
+
{
|
|
166
|
+
path: packageJsonPath,
|
|
167
|
+
bundlePath: "package.json",
|
|
168
|
+
size: (await fsExtra.stat(packageJsonPath)).size,
|
|
169
|
+
crc32: await calculateCrc32(packageJsonPath),
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
const [scriptFiles, uiFiles, resFiles] = await Promise.all([
|
|
173
|
+
collectFilesInDir(scriptPath, "scripts"),
|
|
174
|
+
collectFilesInDir(uiPath, "ui"),
|
|
175
|
+
collectFilesInDir(resPath, "res"),
|
|
176
|
+
]);
|
|
177
|
+
files.push(...scriptFiles, ...uiFiles, ...resFiles);
|
|
178
|
+
return files;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 获取设备上的文件列表
|
|
182
|
+
* @param ip 设备 IP 地址
|
|
183
|
+
* @param port 设备端口
|
|
184
|
+
* @param projectName 项目名称
|
|
185
|
+
* @returns 设备当前文件信息列表
|
|
186
|
+
* @example
|
|
187
|
+
* await listDeviceFiles("192.168.1.10", 9800, "demo")
|
|
188
|
+
*/
|
|
189
|
+
async function listDeviceFiles(ip, port, projectName) {
|
|
190
|
+
const url = new URL(`http://${ip}:${port}/api/files`);
|
|
191
|
+
url.searchParams.set("projectName", projectName);
|
|
192
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
throw new Error(`文件列表请求失败: ${response.statusText}`);
|
|
195
|
+
}
|
|
196
|
+
const data = (await response.json());
|
|
197
|
+
return Array.isArray(data) ? data : [];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 将项目文件增量同步到设备
|
|
201
|
+
* @param workspacePath 项目根目录
|
|
202
|
+
* @param ip 设备 IP 地址
|
|
203
|
+
* @param port 设备端口
|
|
204
|
+
* @returns 同步完成后返回 Promise<void>
|
|
205
|
+
* @example
|
|
206
|
+
* await syncProjectToDevice("/Users/demo/project", "192.168.1.10", 9800)
|
|
207
|
+
*/
|
|
208
|
+
async function syncProjectToDevice(workspacePath, ip, port) {
|
|
209
|
+
const { projectName, appVersion } = await getProjectMeta(workspacePath);
|
|
210
|
+
console.log("🔎 正在获取设备文件列表...");
|
|
211
|
+
const deviceFiles = await listDeviceFiles(ip, port, projectName);
|
|
212
|
+
const deviceFileMap = new Map(deviceFiles.map((file) => [file.path, file]));
|
|
213
|
+
console.log("📂 正在分析项目文件...");
|
|
214
|
+
const files = await collectProjectFiles(workspacePath);
|
|
215
|
+
const filesToSync = [];
|
|
216
|
+
for (const file of files) {
|
|
217
|
+
const deviceFile = deviceFileMap.get(file.bundlePath);
|
|
218
|
+
const requireSyncFile = file.bundlePath.includes("main.js") ||
|
|
219
|
+
file.bundlePath.includes("main.py");
|
|
220
|
+
if (!deviceFile) {
|
|
221
|
+
filesToSync.push(file);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const changed = deviceFile.size !== file.size ||
|
|
225
|
+
deviceFile.crc32.toLowerCase() !== file.crc32.toLowerCase();
|
|
226
|
+
if (requireSyncFile || changed) {
|
|
227
|
+
filesToSync.push(file);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
console.log(`📦 发现 ${filesToSync.length} 个文件需要同步`);
|
|
231
|
+
if (filesToSync.length === 0) {
|
|
232
|
+
console.log("✅ 无需同步文件");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
let syncedCount = 0;
|
|
236
|
+
for (const file of filesToSync) {
|
|
237
|
+
syncedCount += 1;
|
|
238
|
+
console.log(`⬆️ [${syncedCount}/${filesToSync.length}] 同步 ${file.bundlePath}`);
|
|
239
|
+
const binary = await fsExtra.readFile(file.path);
|
|
240
|
+
const url = new URL(`http://${ip}:${port}/debug/patchSync`);
|
|
241
|
+
url.searchParams.set("path", file.bundlePath);
|
|
242
|
+
url.searchParams.set("projectName", projectName);
|
|
243
|
+
url.searchParams.set("appVersion", appVersion);
|
|
244
|
+
const response = await fetch(url.toString(), {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
"Content-Type": "application/octet-stream",
|
|
248
|
+
},
|
|
249
|
+
body: binary,
|
|
250
|
+
});
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
throw new Error(`同步失败: ${file.bundlePath} (状态码: ${response.status})`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
console.log(`✅ 同步完成,共 ${filesToSync.length} 个文件`);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 发送设备动作请求
|
|
259
|
+
* @param action 动作名称,支持 run 与 stop
|
|
260
|
+
* @param ip 设备 IP 地址
|
|
261
|
+
* @param port 设备端口
|
|
262
|
+
* @returns 请求成功时返回 Promise<void>
|
|
263
|
+
* @example
|
|
264
|
+
* await requestDeviceAction("stop", "192.168.1.10", 9800)
|
|
265
|
+
*/
|
|
266
|
+
async function requestDeviceAction(action, ip, port) {
|
|
267
|
+
const url = `http://${ip}:${port}/api/${action}`;
|
|
268
|
+
const response = await fetch(url, { method: "GET" });
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
throw new Error(`${action} 失败,状态码: ${response.status}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 在设备上运行项目(先同步后运行)
|
|
275
|
+
* @param options 命令选项
|
|
276
|
+
* @returns 执行完成后返回 Promise<void>
|
|
277
|
+
* @example
|
|
278
|
+
* await runOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
279
|
+
*/
|
|
280
|
+
async function runOnDevice(options) {
|
|
281
|
+
const workspacePath = options.path
|
|
282
|
+
? path.resolve(options.path)
|
|
283
|
+
: process.cwd();
|
|
284
|
+
const { ip, port } = parseDeviceOptions(options);
|
|
285
|
+
console.log(`🔧 开始构建项目: ${workspacePath}`);
|
|
286
|
+
await (0, build_js_1.buildAll)(true, workspacePath);
|
|
287
|
+
console.log(`📦 开始同步项目到设备: ${ip}:${port}`);
|
|
288
|
+
await syncProjectToDevice(workspacePath, ip, port);
|
|
289
|
+
console.log(`🚀 开始运行项目: ${ip}:${port}`);
|
|
290
|
+
await requestDeviceAction("run", ip, port);
|
|
291
|
+
console.log("✅ 运行请求已发送");
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* 在设备上预览 UI(先同步后调用 runUI)
|
|
295
|
+
* @param options 命令选项
|
|
296
|
+
* @returns 执行完成后返回 Promise<void>
|
|
297
|
+
* @example
|
|
298
|
+
* await runUIOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
299
|
+
*/
|
|
300
|
+
async function runUIOnDevice(options) {
|
|
301
|
+
const workspacePath = options.path
|
|
302
|
+
? path.resolve(options.path)
|
|
303
|
+
: process.cwd();
|
|
304
|
+
const { ip, port } = parseDeviceOptions(options);
|
|
305
|
+
console.log(`🔧 开始构建项目: ${workspacePath}`);
|
|
306
|
+
await (0, build_js_1.buildAll)(true, workspacePath);
|
|
307
|
+
console.log(`📦 开始同步项目到设备: ${ip}:${port}`);
|
|
308
|
+
await syncProjectToDevice(workspacePath, ip, port);
|
|
309
|
+
console.log(`🖼️ 开始预览 UI: ${ip}:${port}`);
|
|
310
|
+
await requestDeviceAction("runUI", ip, port);
|
|
311
|
+
console.log("✅ UI 预览请求已发送");
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 停止设备上的项目
|
|
315
|
+
* @param options 命令选项
|
|
316
|
+
* @returns 执行完成后返回 Promise<void>
|
|
317
|
+
* @example
|
|
318
|
+
* await stopOnDevice({ ip: "192.168.1.10", port: "9800" })
|
|
319
|
+
*/
|
|
320
|
+
async function stopOnDevice(options) {
|
|
321
|
+
const { ip, port } = parseDeviceOptions(options);
|
|
322
|
+
console.log(`🛑 开始停止项目: ${ip}:${port}`);
|
|
323
|
+
await requestDeviceAction("stop", ip, port);
|
|
324
|
+
console.log("✅ 停止请求已发送");
|
|
325
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设备命令选项
|
|
3
|
+
*/
|
|
4
|
+
export interface DeviceCliOptions {
|
|
5
|
+
ip?: string;
|
|
6
|
+
port?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
transport?: string;
|
|
9
|
+
wsPort?: string;
|
|
10
|
+
wsWaitMs?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 在设备上运行项目(先同步后运行)
|
|
14
|
+
* @param options 命令选项
|
|
15
|
+
* @returns 执行完成后返回 Promise<void>
|
|
16
|
+
* @example
|
|
17
|
+
* await runOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
18
|
+
*/
|
|
19
|
+
export declare function runOnDevice(options: DeviceCliOptions): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* 在设备上预览 UI(先同步后调用 runUI)
|
|
22
|
+
* @param options 命令选项
|
|
23
|
+
* @returns 执行完成后返回 Promise<void>
|
|
24
|
+
* @example
|
|
25
|
+
* await runUIOnDevice({ ip: "192.168.1.10", port: "9800", path: "./demo" })
|
|
26
|
+
*/
|
|
27
|
+
export declare function runUIOnDevice(options: DeviceCliOptions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* 停止设备上的项目
|
|
30
|
+
* @param options 命令选项
|
|
31
|
+
* @returns 执行完成后返回 Promise<void>
|
|
32
|
+
* @example
|
|
33
|
+
* await stopOnDevice({ ip: "192.168.1.10", port: "9800" })
|
|
34
|
+
*/
|
|
35
|
+
export declare function stopOnDevice(options: DeviceCliOptions): Promise<void>;
|