Build the Apple Container extension
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
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user