Track bundled vendor runtime sources
This commit is contained in:
362
vendor/aigcpanel/electron/mapi/app/index.ts
vendored
Normal file
362
vendor/aigcpanel/electron/mapi/app/index.ts
vendored
Normal file
@@ -0,0 +1,362 @@
|
||||
import iconv from "iconv-lite";
|
||||
import { exec as _exec, spawn } from "node:child_process";
|
||||
import net from "node:net";
|
||||
import util from "node:util";
|
||||
import { AppConfig } from "../../../src/config";
|
||||
import {
|
||||
extraResolveBin,
|
||||
isLinux,
|
||||
isMac,
|
||||
isWin,
|
||||
platformArch,
|
||||
platformName,
|
||||
platformUUID,
|
||||
platformVersion,
|
||||
} from "../../lib/env";
|
||||
import { IconvUtil, ShellUtil, StrUtil } from "../../lib/util";
|
||||
import { Log } from "../log/index";
|
||||
|
||||
const exec = util.promisify(_exec);
|
||||
|
||||
const outputStringConvert = (outputEncoding: "utf8" | "cp936", data: any) => {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
if (outputEncoding === "utf8") {
|
||||
return data.toString();
|
||||
}
|
||||
let dataEncoding = "binary";
|
||||
if (Buffer.isBuffer(data)) {
|
||||
dataEncoding = IconvUtil.detect(data as any);
|
||||
if ("UTF-8" === dataEncoding) {
|
||||
return data.toString("utf8");
|
||||
}
|
||||
}
|
||||
// dataEncoding UTF-8 cp936
|
||||
// dataEncoding ISO-8859-1 cp936
|
||||
// console.log('dataEncoding', dataEncoding, outputEncoding)
|
||||
return iconv.decode(Buffer.from(data, dataEncoding as any), outputEncoding);
|
||||
};
|
||||
|
||||
const shell = async (
|
||||
command: string,
|
||||
option?: {
|
||||
cwd?: string;
|
||||
outputEncoding?: string;
|
||||
shell?: boolean;
|
||||
},
|
||||
) => {
|
||||
option = Object.assign(
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
outputEncoding: isWin ? "cp936" : "utf8",
|
||||
shell: true,
|
||||
},
|
||||
option,
|
||||
);
|
||||
const result = await exec(command, {
|
||||
env: { ...process.env },
|
||||
shell: option.shell,
|
||||
encoding: "binary",
|
||||
cwd: option["cwd"],
|
||||
} as any);
|
||||
return {
|
||||
stdout: outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
result.stdout,
|
||||
),
|
||||
stderr: outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
result.stderr,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const spawnShell = async (
|
||||
command: string | string[],
|
||||
option: {
|
||||
stdout?: (data: string, process: any) => void;
|
||||
stderr?: (data: string, process: any) => void;
|
||||
success?: (process: any) => void;
|
||||
error?: (msg: string, exitCode: number, process: any) => void;
|
||||
cwd?: string;
|
||||
outputEncoding?: string;
|
||||
env?: Record<string, any>;
|
||||
shell?: boolean;
|
||||
} | null = null,
|
||||
): Promise<{
|
||||
stop: () => void;
|
||||
send: (data: any) => void;
|
||||
result: () => Promise<string>;
|
||||
}> => {
|
||||
option = Object.assign(
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
outputEncoding: isWin ? "cp936" : "utf8",
|
||||
env: {},
|
||||
shell: true,
|
||||
},
|
||||
option,
|
||||
);
|
||||
let commandEntry = "",
|
||||
args = [];
|
||||
if (Array.isArray(command)) {
|
||||
commandEntry = command[0];
|
||||
args = command.slice(1);
|
||||
} else {
|
||||
args = ShellUtil.parseCommandArgs(command);
|
||||
commandEntry = args.shift() as string;
|
||||
}
|
||||
Log.info("App.spawnShell", {
|
||||
commandEntry,
|
||||
args,
|
||||
option: {
|
||||
cwd: option["cwd"],
|
||||
outputEncoding: option["outputEncoding"],
|
||||
},
|
||||
});
|
||||
const spawnProcess = spawn(commandEntry, args, {
|
||||
env: { ...process.env, ...option.env },
|
||||
cwd: option["cwd"],
|
||||
shell: option.shell,
|
||||
encoding: "binary",
|
||||
} as any);
|
||||
let end = false;
|
||||
let isSuccess = false;
|
||||
let exitCode = -1;
|
||||
const stdoutList: string[] = [];
|
||||
const stderrList: string[] = [];
|
||||
spawnProcess.stdout?.on("data", (data) => {
|
||||
// console.log('App.spawnShell.stdout', data)
|
||||
let dataString = outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
data,
|
||||
);
|
||||
Log.info("App.spawnShell.stdout", dataString);
|
||||
stdoutList.push(dataString);
|
||||
option.stdout?.(dataString, spawnProcess);
|
||||
});
|
||||
spawnProcess.stderr?.on("data", (data) => {
|
||||
// console.log('App.spawnShell.stderr', data)
|
||||
let dataString = outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
data,
|
||||
);
|
||||
Log.info("App.spawnShell.stderr", dataString);
|
||||
stderrList.push(dataString);
|
||||
option.stderr?.(dataString, spawnProcess);
|
||||
});
|
||||
spawnProcess.on("exit", (code, signal) => {
|
||||
// console.log('App.spawnShell.exit', code)
|
||||
Log.info("App.spawnShell.exit", { code, signal });
|
||||
exitCode = code;
|
||||
if (isWin) {
|
||||
if (0 === code || 1 === code) {
|
||||
isSuccess = true;
|
||||
}
|
||||
} else {
|
||||
if (null === code || 0 === code) {
|
||||
isSuccess = true;
|
||||
}
|
||||
}
|
||||
if (isSuccess) {
|
||||
option.success?.(spawnProcess);
|
||||
} else {
|
||||
option.error?.(
|
||||
`command ${command} failed with code ${code}`,
|
||||
exitCode,
|
||||
spawnProcess,
|
||||
);
|
||||
}
|
||||
end = true;
|
||||
});
|
||||
spawnProcess.on("error", (err) => {
|
||||
// console.log('App.spawnShell.error', err)
|
||||
Log.info("App.spawnShell.error", err);
|
||||
option.error?.(err.toString(), -1, spawnProcess);
|
||||
end = true;
|
||||
});
|
||||
return {
|
||||
stop: () => {
|
||||
Log.info("App.spawnShell.stop");
|
||||
if (isWin) {
|
||||
_exec(
|
||||
`taskkill /pid ${spawnProcess.pid} /T /F`,
|
||||
{
|
||||
encoding: "binary",
|
||||
},
|
||||
(err, stdout, stderr) => {
|
||||
if (stdout) {
|
||||
stdout = outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
stdout,
|
||||
);
|
||||
}
|
||||
if (stderr) {
|
||||
stderr = outputStringConvert(
|
||||
option.outputEncoding as any,
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
Log.info(
|
||||
"App.spawnShell.stop.taskkill",
|
||||
JSON.parse(JSON.stringify({ err, stdout, stderr })),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
spawnProcess.kill("SIGINT");
|
||||
}
|
||||
},
|
||||
send: (data) => {
|
||||
Log.info("App.spawnShell.send", data);
|
||||
spawnProcess.stdin.write(data);
|
||||
},
|
||||
result: async (): Promise<string> => {
|
||||
if (end) {
|
||||
return stdoutList.join("") + stderrList.join("");
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const watchEnd = () => {
|
||||
setTimeout(() => {
|
||||
if (!end) {
|
||||
watchEnd();
|
||||
return;
|
||||
}
|
||||
if (isSuccess) {
|
||||
resolve(stdoutList.join("") + stderrList.join(""));
|
||||
} else {
|
||||
reject(
|
||||
[
|
||||
`command ${command} failed with code ${exitCode} : `,
|
||||
stdoutList.join(""),
|
||||
stderrList.join(""),
|
||||
].join(""),
|
||||
);
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
watchEnd();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const spawnBinary = async (
|
||||
binary: string,
|
||||
args: string[],
|
||||
option: {
|
||||
stdout?: (data: string, process: any) => void;
|
||||
stderr?: (data: string, process: any) => void;
|
||||
success?: (process: any) => void;
|
||||
error?: (msg: string, exitCode: number, process: any) => void;
|
||||
cwd?: string;
|
||||
outputEncoding?: string;
|
||||
env?: Record<string, any>;
|
||||
shell?: boolean;
|
||||
} | null = null,
|
||||
): Promise<string> => {
|
||||
args.unshift(extraResolveBin(binary));
|
||||
const res = await Apps.spawnShell(args, {
|
||||
...(option || {}),
|
||||
shell: false,
|
||||
});
|
||||
return await res.result();
|
||||
};
|
||||
|
||||
const availablePortLock: {
|
||||
[port: number]: {
|
||||
lockKey: string;
|
||||
lockTime: number;
|
||||
};
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* 获取一个可用的端口
|
||||
* @param start 开始的端口
|
||||
* @param lockKey 锁定的key,避免其他进程获取,默认会创建一个随机的key
|
||||
* @param lockTime 锁定时间,避免在本次获取后未启动服务导致其他进程重复获取
|
||||
*/
|
||||
const availablePort = async (
|
||||
start: number,
|
||||
lockKey?: string,
|
||||
lockTime?: number,
|
||||
): Promise<number> => {
|
||||
lockKey = lockKey || StrUtil.randomString(8);
|
||||
lockTime = lockTime || 60;
|
||||
// expire lock
|
||||
const now = Date.now();
|
||||
for (const port in availablePortLock) {
|
||||
const lockInfo = availablePortLock[port];
|
||||
if (lockInfo.lockTime < now) {
|
||||
delete availablePortLock[port];
|
||||
}
|
||||
}
|
||||
for (let i = start; i < 65535; i++) {
|
||||
const available = await isPortAvailable(i, "0.0.0.0");
|
||||
const availableLocal = await isPortAvailable(i, "127.0.0.1");
|
||||
// console.log('isPortAvailable', i, available, availableLocal)
|
||||
if (available && availableLocal) {
|
||||
const lockInfo = availablePortLock[i];
|
||||
if (lockInfo) {
|
||||
if (lockInfo.lockKey === lockKey) {
|
||||
return i;
|
||||
} else {
|
||||
// other lockKey lock the port
|
||||
continue;
|
||||
}
|
||||
}
|
||||
availablePortLock[i] = {
|
||||
lockKey,
|
||||
lockTime: Date.now() + lockTime * 1000,
|
||||
};
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new Error("no available port");
|
||||
};
|
||||
|
||||
const isPortAvailable = async (
|
||||
port: number,
|
||||
host?: string,
|
||||
): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
server.listen(port, host);
|
||||
server.on("listening", () => {
|
||||
server.close();
|
||||
resolve(true);
|
||||
});
|
||||
server.on("error", () => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fixExecutable = async (executable: string) => {
|
||||
if (isMac || isLinux) {
|
||||
// chmod +x executable
|
||||
await shell(`chmod +x "${executable}"`);
|
||||
}
|
||||
};
|
||||
|
||||
const getUserAgent = () => {
|
||||
let param = [];
|
||||
param.push(`AppOpen/${AppConfig.name}/${AppConfig.version}`);
|
||||
param.push(
|
||||
`Platform/${platformName()}/${platformArch()}/${platformVersion()}/${platformUUID()}`,
|
||||
);
|
||||
return param.join(" ");
|
||||
};
|
||||
|
||||
export const Apps = {
|
||||
shell,
|
||||
spawnShell,
|
||||
spawnBinary,
|
||||
availablePort,
|
||||
isPortAvailable,
|
||||
fixExecutable,
|
||||
getUserAgent,
|
||||
};
|
||||
|
||||
export default Apps;
|
||||
Reference in New Issue
Block a user