ow
This commit is contained in:
1 parent
e9af73007e
commit
2909e9b0af
28 files changed
+17789
No files matched your search
@@ -0,0 +1,239 @@
|
||||
#!/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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user