fix: make device pod host cli launch windows safe
This commit is contained in:
@@ -222,7 +222,8 @@ test("device pod executor dispatches internal jobs through cloud-api gateway ada
|
||||
assert.equal(dispatches[0].internalService, "hwlab-device-pod");
|
||||
assert.equal(dispatches[0].body.params.gatewaySessionId, "gws_devicepod_test");
|
||||
assert.equal(dispatches[0].body.params.input.cwd, "F:\\Work\\Project");
|
||||
assert.match(dispatches[0].body.params.input.command, /^node -e /u);
|
||||
assert.match(dispatches[0].body.params.input.command, /^node tools\\device-host-cli\.mjs --profile-json-b64 /u);
|
||||
assert.equal(dispatches[0].body.params.input.command.includes("node -e"), false);
|
||||
} finally {
|
||||
await service.stop();
|
||||
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
||||
@@ -269,15 +270,14 @@ test("device-pod executor maps v0.1 CLI options to device-host-cli argv", async
|
||||
assert.equal(jobResponse.status, 202);
|
||||
await waitForJobStatus(service.port, "device-pod-test", "job_options_test", "completed");
|
||||
const command = dispatchedBody.params.input.command;
|
||||
const encoded = command.match(/"([A-Za-z0-9+/=]+)"$/u)[1];
|
||||
assert.equal(command.includes("node -e"), false);
|
||||
assert.match(command, /^node tools\/device-host-cli\.mjs --profile-json-b64 /u);
|
||||
assert.match(command, / --pod-id device-pod-test debug-probe download start /u);
|
||||
assert.match(command, / --capture-uart uart\/1 --capture-duration-ms 8000 --port COM4 --baud-rate 921600$/u);
|
||||
const encoded = command.match(/--profile-json-b64 ([A-Za-z0-9+/=]+)/u)[1];
|
||||
const decoded = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
||||
assert.deepEqual(decoded.args, [
|
||||
"debug-probe", "download", "start",
|
||||
"--capture-uart", "uart/1",
|
||||
"--capture-duration-ms", "8000",
|
||||
"--port", "COM4",
|
||||
"--baud-rate", "921600"
|
||||
]);
|
||||
assert.equal(decoded.devicePodId, "device-pod-test");
|
||||
assert.equal(decoded.route.gatewaySessionId, "gws_test");
|
||||
} finally {
|
||||
await service.stop();
|
||||
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
||||
|
||||
@@ -383,13 +383,44 @@ function deviceHostCommand(profile, job) {
|
||||
const payload = {
|
||||
devicePodId: profile.devicePodId || job.devicePodId,
|
||||
profile,
|
||||
profilePath: `.device-pod/.runtime/${job.id}.json`,
|
||||
hostCli: profile.route?.hostCli || "node tools/device-host-cli.mjs",
|
||||
args
|
||||
};
|
||||
const runner = "const cp=require('node:child_process'),fs=require('node:fs'),path=require('node:path');const p=JSON.parse(Buffer.from(process.argv[1],'base64').toString('utf8'));fs.mkdirSync(path.dirname(p.profilePath),{recursive:true});fs.writeFileSync(p.profilePath,JSON.stringify(p.profile,null,2)+'\\n','utf8');function split(s){const out=[];let q=null,b='';for(const ch of String(s)){if(q){if(ch===q)q=null;else b+=ch}else if(ch.charCodeAt(0)===39||ch.charCodeAt(0)===34)q=ch;else if(/\\s/u.test(ch)){if(b){out.push(b);b=''}}else b+=ch}if(b)out.push(b);return out}const parts=split(p.hostCli||'node tools/device-host-cli.mjs');const child=cp.spawn(parts[0],parts.slice(1).concat(['--profile',p.profilePath,'--pod-id',p.devicePodId],p.args),{stdio:'inherit',shell:false});child.on('exit',(c)=>process.exit(c??0));child.on('error',(e)=>{console.error(e.message);process.exit(127)});";
|
||||
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
||||
return `node -e "${runner}" "${encoded}"`;
|
||||
const profileJsonB64 = Buffer.from(JSON.stringify(payload.profile), "utf8").toString("base64");
|
||||
return shellJoin([...splitCommand(payload.hostCli), "--profile-json-b64", profileJsonB64, "--pod-id", payload.devicePodId, ...payload.args]);
|
||||
}
|
||||
|
||||
function splitCommand(value) {
|
||||
const out = [];
|
||||
let quote = null;
|
||||
let buffer = "";
|
||||
for (const char of String(value || "")) {
|
||||
if (quote) {
|
||||
if (char === quote) quote = null;
|
||||
else buffer += char;
|
||||
} else if (char === "'" || char === '"') {
|
||||
quote = char;
|
||||
} else if (/\s/u.test(char)) {
|
||||
if (buffer) {
|
||||
out.push(buffer);
|
||||
buffer = "";
|
||||
}
|
||||
} else {
|
||||
buffer += char;
|
||||
}
|
||||
}
|
||||
if (buffer) out.push(buffer);
|
||||
return out.length ? out : ["node", "tools/device-host-cli.mjs"];
|
||||
}
|
||||
|
||||
function shellJoin(parts) {
|
||||
return parts.map(shellQuote).join(" ");
|
||||
}
|
||||
|
||||
function shellQuote(value) {
|
||||
const text = String(value ?? "");
|
||||
if (/^[A-Za-z0-9_./:=@\\-]+$/u.test(text)) return text;
|
||||
return `"${text.replace(/(["\\])/gu, "\\$1")}"`;
|
||||
}
|
||||
|
||||
function deviceHostArgs(intent, args = {}) {
|
||||
|
||||
@@ -20,6 +20,11 @@ function parseGlobalCli(argv) {
|
||||
i += 1;
|
||||
} else if (item.startsWith('--profile=')) {
|
||||
options.profile = cleanArg(item.slice('--profile='.length));
|
||||
} else if (item === '--profile-json-b64') {
|
||||
options.profileJsonB64 = cleanArg(argv[i + 1]);
|
||||
i += 1;
|
||||
} else if (item.startsWith('--profile-json-b64=')) {
|
||||
options.profileJsonB64 = cleanArg(item.slice('--profile-json-b64='.length));
|
||||
} else if (item === '--pod-id') {
|
||||
options.podId = cleanArg(argv[i + 1]);
|
||||
i += 1;
|
||||
@@ -93,6 +98,7 @@ function profilePath(root = workspaceRoot(), podId = selectedPodId()) {
|
||||
|
||||
function loadProfile() {
|
||||
const root = workspaceRoot();
|
||||
if (GLOBAL_CLI.options.profileJsonB64) return loadInlineProfile(root);
|
||||
const requestedProfile = GLOBAL_CLI.options.profile || process.env.DEVICE_POD_PROFILE || '';
|
||||
const file = requestedProfile ? (path.isAbsolute(requestedProfile) ? requestedProfile : path.resolve(root, requestedProfile)) : profilePath(root);
|
||||
if (!fs.existsSync(file)) throw new Error(`device-pod profile not found: ${file}`);
|
||||
@@ -105,6 +111,18 @@ function loadProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
function loadInlineProfile(root) {
|
||||
const profile = JSON.parse(Buffer.from(GLOBAL_CLI.options.profileJsonB64, 'base64').toString('utf8'));
|
||||
const actualPodId = profile.devicePodId || profile.podId;
|
||||
const requestedPodId = GLOBAL_CLI.options.podId || process.env.DEVICE_POD_ID || '';
|
||||
if (requestedPodId && actualPodId && actualPodId !== requestedPodId) throw new Error(`device-pod profile mismatch: requested ${requestedPodId}, profile ${actualPodId}`);
|
||||
const runtimePath = path.join(devicePodDir(root), '.runtime', `${actualPodId || requestedPodId || DEFAULT_POD_ID}.json`);
|
||||
writeJson(runtimePath, profile);
|
||||
profile.__profilePath = runtimePath;
|
||||
profile.__workspaceRoot = path.resolve(profile.workspaceRoot || root);
|
||||
return profile;
|
||||
}
|
||||
|
||||
function resolveWorkspacePath(profile, rel = '.') {
|
||||
const root = profile.__workspaceRoot;
|
||||
const raw = String(rel || '.').replace(/^\/+/, '');
|
||||
|
||||
Reference in New Issue
Block a user