Merge pull request #567 from pikasTech/fix/v02-device-pod-windows-host-cli-launch
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].internalService, "hwlab-device-pod");
|
||||||
assert.equal(dispatches[0].body.params.gatewaySessionId, "gws_devicepod_test");
|
assert.equal(dispatches[0].body.params.gatewaySessionId, "gws_devicepod_test");
|
||||||
assert.equal(dispatches[0].body.params.input.cwd, "F:\\Work\\Project");
|
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 {
|
} finally {
|
||||||
await service.stop();
|
await service.stop();
|
||||||
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
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);
|
assert.equal(jobResponse.status, 202);
|
||||||
await waitForJobStatus(service.port, "device-pod-test", "job_options_test", "completed");
|
await waitForJobStatus(service.port, "device-pod-test", "job_options_test", "completed");
|
||||||
const command = dispatchedBody.params.input.command;
|
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"));
|
const decoded = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
||||||
assert.deepEqual(decoded.args, [
|
assert.equal(decoded.devicePodId, "device-pod-test");
|
||||||
"debug-probe", "download", "start",
|
assert.equal(decoded.route.gatewaySessionId, "gws_test");
|
||||||
"--capture-uart", "uart/1",
|
|
||||||
"--capture-duration-ms", "8000",
|
|
||||||
"--port", "COM4",
|
|
||||||
"--baud-rate", "921600"
|
|
||||||
]);
|
|
||||||
} finally {
|
} finally {
|
||||||
await service.stop();
|
await service.stop();
|
||||||
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
||||||
|
|||||||
@@ -383,13 +383,44 @@ function deviceHostCommand(profile, job) {
|
|||||||
const payload = {
|
const payload = {
|
||||||
devicePodId: profile.devicePodId || job.devicePodId,
|
devicePodId: profile.devicePodId || job.devicePodId,
|
||||||
profile,
|
profile,
|
||||||
profilePath: `.device-pod/.runtime/${job.id}.json`,
|
|
||||||
hostCli: profile.route?.hostCli || "node tools/device-host-cli.mjs",
|
hostCli: profile.route?.hostCli || "node tools/device-host-cli.mjs",
|
||||||
args
|
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 profileJsonB64 = Buffer.from(JSON.stringify(payload.profile), "utf8").toString("base64");
|
||||||
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
return shellJoin([...splitCommand(payload.hostCli), "--profile-json-b64", profileJsonB64, "--pod-id", payload.devicePodId, ...payload.args]);
|
||||||
return `node -e "${runner}" "${encoded}"`;
|
}
|
||||||
|
|
||||||
|
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 = {}) {
|
function deviceHostArgs(intent, args = {}) {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ function parseGlobalCli(argv) {
|
|||||||
i += 1;
|
i += 1;
|
||||||
} else if (item.startsWith('--profile=')) {
|
} else if (item.startsWith('--profile=')) {
|
||||||
options.profile = cleanArg(item.slice('--profile='.length));
|
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') {
|
} else if (item === '--pod-id') {
|
||||||
options.podId = cleanArg(argv[i + 1]);
|
options.podId = cleanArg(argv[i + 1]);
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -93,6 +98,7 @@ function profilePath(root = workspaceRoot(), podId = selectedPodId()) {
|
|||||||
|
|
||||||
function loadProfile() {
|
function loadProfile() {
|
||||||
const root = workspaceRoot();
|
const root = workspaceRoot();
|
||||||
|
if (GLOBAL_CLI.options.profileJsonB64) return loadInlineProfile(root);
|
||||||
const requestedProfile = GLOBAL_CLI.options.profile || process.env.DEVICE_POD_PROFILE || '';
|
const requestedProfile = GLOBAL_CLI.options.profile || process.env.DEVICE_POD_PROFILE || '';
|
||||||
const file = requestedProfile ? (path.isAbsolute(requestedProfile) ? requestedProfile : path.resolve(root, requestedProfile)) : profilePath(root);
|
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}`);
|
if (!fs.existsSync(file)) throw new Error(`device-pod profile not found: ${file}`);
|
||||||
@@ -105,6 +111,18 @@ function loadProfile() {
|
|||||||
return profile;
|
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 = '.') {
|
function resolveWorkspacePath(profile, rel = '.') {
|
||||||
const root = profile.__workspaceRoot;
|
const root = profile.__workspaceRoot;
|
||||||
const raw = String(rel || '.').replace(/^\/+/, '');
|
const raw = String(rel || '.').replace(/^\/+/, '');
|
||||||
|
|||||||
Reference in New Issue
Block a user