Turns the empty scaffold into a working Nova extension for Apple Container and container-compose, in the shape of the Docker Suite extension. Sidebar (Scripts/Sidebar): - System, Containers, Images, Volumes and Networks sections - Containers grouped into compose projects via the com.docker.compose.project label container-compose stamps; project rows drive compose up/down/build against the matching workspace file - Lifecycle, shell, logs, inspect, browse and copy commands per resource - Apple Container publishes no event stream, so the sidebar polls while visible and only reloads a section when a fingerprint of its data changes, which keeps selection and expansion intact Hostnames (Scripts/Hostnames.js): - A container's hostname is <name>.<domain> and the domain lives in Apple Container's config.toml, which has no CLI setter, so the Hostname Domain preference reads and writes that file directly - Changing it offers to register the domain with macOS and restart the services; the write preserves the file's other tables and its mode Language support: - Dockerfile and Compose syntaxes backed by tree-sitter grammars built by Tools/build-syntaxes.sh, pinned to revisions that generate ABI 14 - The Compose syntax is named dockercompose because that is the languageId docker-language-server recognises compose files by - docker-language-server is downloaded on first use rather than bundled, at roughly 40 MB Icons are generated by Tools/make-icons.py so they can be regenerated rather than hand-maintained. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UnSBcR5Lywz5Fbj5FmVbfc
90 lines
1.9 KiB
JavaScript
90 lines
1.9 KiB
JavaScript
//
|
|
// Thin promise wrappers around Nova's Process API.
|
|
//
|
|
|
|
// Runs a command to completion and resolves with its captured output.
|
|
// Rejects with an Error carrying the exit status and stderr when the
|
|
// command fails, so callers can surface a useful message.
|
|
function exec(path, args, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let stdout = "";
|
|
let stderr = "";
|
|
let process;
|
|
|
|
try {
|
|
process = new Process(path, {
|
|
args: args,
|
|
cwd: options.cwd,
|
|
env: options.env,
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
} catch (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
|
|
process.onStdout((line) => {
|
|
stdout += line;
|
|
});
|
|
|
|
process.onStderr((line) => {
|
|
stderr += line;
|
|
});
|
|
|
|
process.onDidExit((status) => {
|
|
if (status === 0 || options.ignoreStatus) {
|
|
resolve({ status: status, stdout: stdout, stderr: stderr });
|
|
return;
|
|
}
|
|
|
|
let error = new Error(cleanMessage(stderr) || `${path} exited with status ${status}.`);
|
|
error.status = status;
|
|
error.stdout = stdout;
|
|
error.stderr = stderr;
|
|
reject(error);
|
|
});
|
|
|
|
try {
|
|
process.start();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Runs a command and parses its stdout as JSON.
|
|
async function execJSON(path, args, options = {}) {
|
|
let result = await exec(path, args, options);
|
|
|
|
if (!result.stdout.trim()) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(result.stdout);
|
|
} catch (error) {
|
|
throw new Error(`Unable to parse output of ${path}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Apple Container prefixes failures with "Error: " and appends a hint about
|
|
// starting the system services. Keep the first meaningful line only.
|
|
function cleanMessage(stderr) {
|
|
if (!stderr) {
|
|
return "";
|
|
}
|
|
|
|
let line = stderr
|
|
.split("\n")
|
|
.map((entry) => entry.trim())
|
|
.filter((entry) => entry.length > 0)[0];
|
|
|
|
if (!line) {
|
|
return "";
|
|
}
|
|
|
|
return line.replace(/^Error:\s*/, "");
|
|
}
|
|
|
|
module.exports = { exec, execJSON, cleanMessage };
|