Files
ECC/docker/plugin-setup/interactive-plan.js
T
haelyraandGitHub d791457aca feat(docker): add hardened CLI test harness (#2625)
* feat(install): add hardened Docker test harness

* feat(docker): complete isolated CLI session lifecycle

* fix(docker): exercise packed public CLI offline

* fix(docker): close hardened harness review gaps

* test(docker): bound harness subprocesses
2026-08-06 16:52:39 -04:00

119 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const path = require('path');
const usage = `Usage: node docker/plugin-setup/interactive-plan.js [options] [-- command ...]
Emit the Docker side of the terminal-opener executable-plus-argv contract.
Options:
--container <name> Named running container (default: ecc-plugin-shell).
--workdir <path> Absolute container working directory (default: /workspace/project).
--json Emit compact JSON.
--help, -h Show this help.
-- command ... Interactive command (default: bash).
`;
function fail(message) {
const error = new Error(message);
error.exitCode = 2;
throw error;
}
function readValue(argv, index, option) {
const value = argv[index + 1];
if (!value || value === '--') {
fail(`Invalid ${option}: expected a value.`);
}
return value;
}
function parseArgs(argv) {
let container = 'ecc-plugin-shell';
let workdir = '/workspace/project';
let json = false;
let command = ['bash'];
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === '--') {
command = argv.slice(index + 1);
if (command.length === 0) {
fail('Invalid command: expected at least one argv entry after --.');
}
break;
}
if (argument === '--container') {
container = readValue(argv, index, '--container');
index += 1;
} else if (argument === '--workdir') {
workdir = readValue(argv, index, '--workdir');
index += 1;
} else if (argument === '--json') {
json = true;
} else if (argument === '--help' || argument === '-h') {
return { help: true };
} else {
fail(`Invalid option: ${argument}`);
}
}
if (container.length > 128 || !/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(container)) {
fail('Invalid container name. Use Docker name characters only.');
}
const normalizedWorkdir = path.posix.normalize(workdir);
if (
!path.posix.isAbsolute(workdir)
|| /[\r\n\0]/.test(workdir)
|| (
normalizedWorkdir !== '/workspace'
&& !normalizedWorkdir.startsWith('/workspace/')
)
) {
fail('Invalid workdir. Use an absolute path within /workspace.');
}
if (command.some((entry) => entry.length === 0 || /\0/.test(entry))) {
fail('Invalid command argv entry.');
}
return { command, container, help: false, json, workdir };
}
function buildPlan(options) {
return {
contractVersion: 1,
executable: 'docker',
argv: [
'exec',
'-it',
'-w',
options.workdir,
options.container,
...options.command,
],
};
}
function main() {
try {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
process.stdout.write(usage);
return;
}
const spacing = options.json ? 0 : 2;
process.stdout.write(`${JSON.stringify(buildPlan(options), null, spacing)}\n`);
} catch (error) {
process.stderr.write(`Error: ${error.message}\n`);
process.exitCode = error.exitCode || 1;
}
}
if (require.main === module) {
main();
}
module.exports = { buildPlan, parseArgs };