Files
Docker/uniapp/security/scripts/nox.mjs
T
2026-10-29 16:44:09 +08:00

240 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Nox(夜神模拟器)运行/测试助手
*
* 背景:Nox 自带 adb(协议36) 与 HBuilderX 自带 adb(协议41) 会互相 kill server 造成设备掉线,
* 因此本脚本统一使用 HBuilderX 的 adb,并通过 TCP `connect 127.0.0.1:62001` 挂载 Nox。
*
* 两种在 Nox 上测试本项目的方式:
* 1) test :构建/复用 H5 产物 -> 局域网静态服务 -> adb 唤起 Nox 浏览器打开(零打包,当前即可用)
* 2) native:调用 HBuilderX CLI `launch app-android` 运行到安卓基座(需先在 HBuilderX 里以 uni-app 方式导入本项目)
*
* 用法:
* node scripts/nox.mjs connect
* node scripts/nox.mjs serve [--build] [--port 5199]
* node scripts/nox.mjs open [--url http://ip:port]
* node scripts/nox.mjs test [--build] [--port 5199] [--shot]
* node scripts/nox.mjs screenshot
* node scripts/nox.mjs native
*/
import { spawn, spawnSync } from "node:child_process";
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
// ---- 可配置项(均可用环境变量覆盖)----
const HBX_HOME = process.env.HBX_HOME || "C:\\apps\\HBuilderX";
const ADB =
process.env.NOX_ADB ||
path.join(HBX_HOME, "plugins", "launcher-tools", "tools", "adbs", "adb.exe");
const CLI = process.env.HBX_CLI || path.join(HBX_HOME, "cli.exe");
const NOX_SERIAL = process.env.NOX_SERIAL || "127.0.0.1:62001";
const PROJECT_NAME = process.env.NOX_PROJECT || "security";
const H5_DIST = path.join(ROOT, "dist", "build", "h5");
const args = process.argv.slice(2);
const cmd = args[0] || "test";
const getFlag = (name, def) => {
const i = args.indexOf(`--${name}`);
if (i === -1) return def;
const next = args[i + 1];
return next && !next.startsWith("--") ? next : true;
};
const hasFlag = (name) => args.includes(`--${name}`);
function lanIp() {
const ifaces = os.networkInterfaces();
for (const name of Object.keys(ifaces)) {
for (const info of ifaces[name] || []) {
if (info.family === "IPv4" && !info.internal) return info.address;
}
}
return "127.0.0.1";
}
function adb(...adbArgs) {
return spawnSync(ADB, adbArgs, { encoding: "utf8" });
}
function connect() {
const r = adb("connect", NOX_SERIAL);
console.log((r.stdout || "") + (r.stderr || ""));
const list = adb("devices");
console.log(list.stdout || "");
if (!(list.stdout || "").includes(NOX_SERIAL)) {
console.error(`[nox] 未发现设备 ${NOX_SERIAL},请确认夜神模拟器已启动。`);
process.exit(1);
}
return true;
}
const MIME = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".mjs": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".map": "application/json; charset=utf-8",
};
function serve(port) {
if (!fs.existsSync(path.join(H5_DIST, "index.html"))) {
console.error(
`[nox] 未找到 H5 产物:${H5_DIST},请先执行 build:h5 或加 --build。`,
);
process.exit(1);
}
const server = http.createServer((req, res) => {
const urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
let filePath = path.join(H5_DIST, urlPath === "/" ? "index.html" : urlPath);
if (!filePath.startsWith(H5_DIST)) {
res.writeHead(403);
return res.end("forbidden");
}
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
filePath = path.join(filePath, "index.html");
}
if (!fs.existsSync(filePath)) {
// SPA 兜底:未知路径回 index.html
filePath = path.join(H5_DIST, "index.html");
}
res.writeHead(200, {
"Content-Type":
MIME[path.extname(filePath).toLowerCase()] ||
"application/octet-stream",
});
fs.createReadStream(filePath).pipe(res);
});
server.listen(port, "0.0.0.0", () => {
console.log(
`[nox] 静态服务已启动:http://${lanIp()}:${port} (Ctrl+C 停止)`,
);
});
return server;
}
function openBrowser(url) {
const r = adb(
"-s",
NOX_SERIAL,
"shell",
"am",
"start",
"-a",
"android.intent.action.VIEW",
"-d",
url,
);
console.log((r.stdout || "") + (r.stderr || ""));
}
function screenshot() {
const out = path.join(ROOT, "unpackage", "nox-screenshot.png");
fs.mkdirSync(path.dirname(out), { recursive: true });
const r = spawnSync(ADB, ["-s", NOX_SERIAL, "exec-out", "screencap", "-p"], {
encoding: "buffer",
maxBuffer: 64 * 1024 * 1024,
});
if (r.error || !r.stdout || r.stdout.length === 0) {
console.error(
"[nox] 截图失败:设备可能离线,先执行 node scripts/nox.mjs connect 后重试",
);
return;
}
fs.writeFileSync(out, r.stdout);
console.log(`[nox] 截图已保存:${out}(${r.stdout.length} 字节)`);
}
function run(cmdName, cmdArgs) {
const r = spawnSync(cmdName, cmdArgs, { encoding: "utf8", shell: false });
console.log((r.stdout || "") + (r.stderr || ""));
return r;
}
function buildH5() {
console.log("[nox] 构建 H5 产物 ...");
const npmCmd = process.platform === "win32" ? "npm" : "npm";
const r = spawnSync(npmCmd, ["run", "build:h5"], {
cwd: ROOT,
stdio: "inherit",
shell: true,
});
if (r.status !== 0) {
console.error("[nox] 构建失败");
process.exit(1);
}
}
function native() {
console.log(
"[nox] 通过 HBuilderX CLI 运行到安卓基座(需项目已在 HBuilderX 中识别为 uni-app)...",
);
run(CLI, [
"launch",
"app-android",
"--project",
PROJECT_NAME,
"--deviceId",
NOX_SERIAL,
"--playground",
"standard",
]);
}
const port = Number(getFlag("port", 5199));
switch (cmd) {
case "connect":
connect();
break;
case "serve":
if (hasFlag("build")) buildH5();
connect();
serve(port);
break;
case "open":
connect();
openBrowser(getFlag("url", `http://${lanIp()}:${port}`));
break;
case "screenshot":
connect();
screenshot();
break;
case "native":
connect();
native();
break;
case "test":
default: {
if (hasFlag("build")) buildH5();
connect();
const server = serve(port);
const url = `http://${lanIp()}:${port}`;
setTimeout(() => {
openBrowser(url);
console.log(`[nox] 已在 Nox 浏览器打开:${url}`);
if (hasFlag("shot")) setTimeout(screenshot, 4000);
}, 800);
process.on("SIGINT", () => {
server.close();
process.exit(0);
});
break;
}
}