* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { exec } from 'child_process';
* Result of executing a command via osascript
*/
export interface OsascriptExecResult {
pid: number;
output: string;
}
* Result of executing a shell command
*/
export interface ExecResult {
stdout: string;
stderr: string;
output: string;
}
* Executes a shell command
*
* @param command Command to execute
* @param timeout Command timeout in milliseconds. Default is 10000
* @returns The execution result
*/
export function execCommand(command: string, timeout: number = 10000): Promise<ExecResult> {
return new Promise((resolve, reject) => {
exec(command, { timeout }, (error, stdout, stderr) => {
let out = stdout ?? '';
let err = stderr ?? '';
let output = out + err;
if (error) {
reject(new Error(
`Command execution failed.\n` +
`Command: ${command}\n` +
`Error: ${error.message}\n` +
`Output:\n${output}`,
));
return;
}
resolve({
stdout: out,
stderr: err,
output
});
});
});
}
* Executes a command via osascript (typically for commands requiring privileges)
*
* @param command Command to execute
* @param readFlag Flag used to detect when the command is ready
* @param prompt Optional prompt message shown in the password dialog
* @param timeout Command timeout in milliseconds. Default is 10000
* @returns The execution result
*/
export async function execCommandWithOsascript(command: string, readFlag: string, prompt: string = '',
timeout: number = 10000): Promise<OsascriptExecResult> {
let tmpDir = os.tmpdir();
let logFile = path.join(tmpDir, `cjmp_debug_tunnel_${Date.now()}.log`);
let pidFile = path.join(tmpDir, `cjmp_debug_tunnel_${Date.now()}.pid`);
fs.writeFileSync(logFile, '');
let shellCmd = `${command} > "${logFile}" 2>&1 & echo $! > "${pidFile}"`;
let osaCommand = `osascript -e 'do shell script "${shellCmd.replace(/\n/g, ' ').replace(/"/g, '\\"')}" with administrator privileges with prompt "${prompt}"'`;
exec(osaCommand);
return new Promise((resolve, reject) => {
let startTime = Date.now();
let timer = setInterval(() => {
try {
if (!fs.existsSync(pidFile)) {
return;
}
let pid = Number(fs.readFileSync(pidFile, 'utf8').trim());
if (!pid) {
return;
}
let content = fs.readFileSync(logFile, 'utf8');
if (content.includes(readFlag)) {
clearInterval(timer);
fs.unlinkSync(logFile);
fs.unlinkSync(pidFile);
resolve({
pid,
output: content,
});
}
if (Date.now() - startTime > timeout) {
clearInterval(timer);
reject(new Error('Tunnel start timeout.'));
}
} catch (err) {
// do nothing
}
}, 100);
});
}