@xec-sh/core
Version:
Universal shell execution engine
334 lines • 12.9 kB
JavaScript
import { unifiedConfig } from '../config/unified-config.js';
export class ConvenienceAPI {
constructor(engine) {
this.engine = engine;
}
async onHost(hosts, command, ...values) {
const hostArray = Array.isArray(hosts) ? hosts : [hosts];
if (hostArray.length === 1) {
const hostConfig = await this.resolveHost(hostArray[0]);
const $ssh = this.engine.ssh(hostConfig);
if (typeof command === 'string') {
const cmd = command;
return $ssh `${cmd}`;
}
else {
return $ssh(command, ...values);
}
}
const tasks = hostArray.map(async (host) => {
const hostConfig = await this.resolveHost(host);
const $ssh = this.engine.ssh(hostConfig);
if (typeof command === 'string') {
const cmd = command;
return $ssh `${cmd}`;
}
else {
return $ssh(command, ...values);
}
});
return Promise.all(tasks);
}
async in(target, command, ...values) {
if (target.startsWith('pod:')) {
const podName = target.substring(4);
const podConfig = await this.resolvePod(podName);
const $k8s = this.engine.k8s(podConfig);
if (!command) {
return $k8s `/bin/sh`;
}
if (typeof command === 'string') {
const cmd = command;
return $k8s `${cmd}`;
}
else {
return $k8s(command, ...values);
}
}
else {
const containerName = target.startsWith('container:')
? target.substring(10)
: target;
const containerConfig = await this.resolveContainer(containerName);
const $docker = this.engine.docker(containerConfig);
if (!command) {
return $docker `/bin/sh`;
}
if (typeof command === 'string') {
const cmd = command;
return $docker `${cmd}`;
}
else {
return $docker(command, ...values);
}
}
}
async copy(source, destination) {
const sourceInfo = this.parseLocation(source);
const destInfo = this.parseLocation(destination);
if (sourceInfo.type === 'local' && destInfo.type === 'local') {
await this.engine.run `cp -r ${source} ${destination}`;
return;
}
if (sourceInfo.type === 'ssh' || destInfo.type === 'ssh') {
if (sourceInfo.type === 'ssh' && destInfo.type === 'local') {
const hostConfig = await this.resolveHost(sourceInfo.host);
const $ssh = this.engine.ssh(hostConfig);
await $ssh.downloadFile(sourceInfo.path, destInfo.path);
}
else if (sourceInfo.type === 'local' && destInfo.type === 'ssh') {
const hostConfig = await this.resolveHost(destInfo.host);
const $ssh = this.engine.ssh(hostConfig);
await $ssh.uploadFile(sourceInfo.path, destInfo.path);
}
else {
throw new Error('Direct SSH to SSH copy not supported yet');
}
return;
}
if (sourceInfo.type === 'docker' || destInfo.type === 'docker') {
const containerName = sourceInfo.type === 'docker'
? sourceInfo.container
: destInfo.container;
const containerConfig = await this.resolveContainer(containerName);
const container = await this.engine.docker(containerConfig).start();
if (sourceInfo.type === 'docker' && destInfo.type === 'local') {
await container.copyFrom(sourceInfo.path, destInfo.path);
}
else if (sourceInfo.type === 'local' && destInfo.type === 'docker') {
await container.copyTo(sourceInfo.path, destInfo.path);
}
else {
throw new Error('Direct container to container copy not supported yet');
}
return;
}
if (sourceInfo.type === 'k8s' || destInfo.type === 'k8s') {
const podName = sourceInfo.type === 'k8s'
? sourceInfo.pod
: destInfo.pod;
const podConfig = await this.resolvePod(podName);
const k8sContext = this.engine.k8s(podConfig);
const pod = k8sContext.pod(podConfig.pod);
if (sourceInfo.type === 'k8s' && destInfo.type === 'local') {
await pod.copyFrom(sourceInfo.path, destInfo.path);
}
else if (sourceInfo.type === 'local' && destInfo.type === 'k8s') {
await pod.copyTo(sourceInfo.path, destInfo.path);
}
else {
throw new Error('Direct pod to pod copy not supported yet');
}
}
}
async forward(source, localPort) {
const parts = source.split(':');
if (parts[0] === 'pod') {
const [, podName, remotePort] = parts;
if (!podName || !remotePort) {
throw new Error('Invalid pod forward format. Use: pod:name:port');
}
const podConfig = await this.resolvePod(podName);
const k8sContext = this.engine.k8s(podConfig);
const pod = k8sContext.pod(podConfig.pod);
if (localPort) {
return pod.portForward(localPort, parseInt(remotePort, 10));
}
else {
return pod.portForwardDynamic(parseInt(remotePort, 10));
}
}
else if (parts[0] === 'container') {
const [, containerName, remotePort] = parts;
throw new Error('Docker port forwarding not implemented yet');
}
else {
const [hostName, remotePort] = parts;
if (!hostName || !remotePort) {
throw new Error('Invalid SSH tunnel format. Use: host:port');
}
const hostConfig = await this.resolveHost(hostName);
const $ssh = this.engine.ssh(hostConfig);
const tunnel = await $ssh.tunnel({
localPort: localPort || 0,
remoteHost: 'localhost',
remotePort: parseInt(remotePort, 10)
});
return tunnel;
}
}
async logs(source, options = {}) {
if (source.startsWith('container:')) {
const containerName = source.substring(10);
const containerConfig = await this.resolveContainer(containerName);
const container = await this.engine.docker(containerConfig).start();
if (options.follow && options.onData) {
await container.streamLogs(options.onData, {
follow: true,
tail: options.tail,
timestamps: options.timestamps
});
}
else {
return container.logs({
tail: options.tail,
timestamps: options.timestamps
});
}
}
else if (source.startsWith('pod:')) {
const podName = source.substring(4);
const podConfig = await this.resolvePod(podName);
const k8sContext = this.engine.k8s(podConfig);
const pod = k8sContext.pod(podConfig.pod);
if (options.follow && options.onData) {
return pod.streamLogs(options.onData, {
follow: true,
tail: options.tail,
timestamps: options.timestamps
});
}
else {
return pod.logs({
tail: options.tail,
timestamps: options.timestamps
});
}
}
else if (source.includes(':')) {
const [hostName, filePath] = source.split(':', 2);
if (!hostName || !filePath) {
throw new Error('Invalid SSH log source format. Use: host:path');
}
const hostConfig = await this.resolveHost(hostName);
const $ssh = this.engine.ssh(hostConfig);
if (options.follow) {
const tailCmd = `tail -f ${options.tail ? `-n ${options.tail}` : ''} ${filePath}`;
const proc = $ssh `${tailCmd}`;
if (options.onData) {
const stream = await proc;
options.onData(stream.stdout);
}
return proc;
}
else {
const tailCmd = `tail ${options.tail ? `-n ${options.tail}` : '-n 50'} ${filePath}`;
const result = await $ssh `${tailCmd}`;
return result.stdout;
}
}
else {
if (options.follow) {
const tailCmd = `tail -f ${options.tail ? `-n ${options.tail}` : ''} ${source}`;
const proc = this.engine.run `${tailCmd}`;
if (options.onData) {
const stream = await proc;
options.onData(stream.stdout);
}
return proc;
}
else {
const tailCmd = `tail ${options.tail ? `-n ${options.tail}` : '-n 50'} ${source}`;
const result = await this.engine.run `${tailCmd}`;
return result.stdout;
}
}
}
async smart(command) {
const parts = command.split(' ');
const firstPart = parts[0];
const restCommand = parts.slice(1).join(' ');
try {
const config = await unifiedConfig.load();
if (firstPart && config.hosts?.[firstPart]) {
const result = await this.onHost(firstPart, restCommand);
return Array.isArray(result) ? result[0] : result;
}
}
catch { }
try {
const config = await unifiedConfig.load();
if (firstPart && config.containers?.[firstPart]) {
return await this.in(firstPart, restCommand);
}
}
catch { }
try {
const config = await unifiedConfig.load();
if (firstPart && config.pods?.[firstPart]) {
return await this.in(`pod:${firstPart}`, restCommand);
}
}
catch { }
return await this.engine.run `${command}`;
}
async resolveHost(name) {
const config = await unifiedConfig.load();
const hostConfig = config.hosts?.[name];
if (!hostConfig) {
return { host: name };
}
return unifiedConfig.hostToSSHOptions(name);
}
async resolveContainer(name) {
const config = await unifiedConfig.load();
const containerConfig = config.containers?.[name];
if (!containerConfig) {
return { container: name };
}
return {
...containerConfig,
container: containerConfig.container || containerConfig.name || name
};
}
async resolvePod(name) {
const config = await unifiedConfig.load();
const podConfig = config.pods?.[name];
if (!podConfig) {
return { pod: name };
}
return unifiedConfig.podToK8sOptions(name);
}
parseLocation(location) {
if (location.startsWith('container:')) {
const [containerPart, ...pathParts] = location.substring(10).split(':');
return {
type: 'docker',
container: containerPart,
path: pathParts.join(':') || '/'
};
}
if (location.startsWith('pod:')) {
const [podPart, ...pathParts] = location.substring(4).split(':');
return {
type: 'k8s',
pod: podPart,
path: pathParts.join(':') || '/'
};
}
if (location.includes(':') && !location.match(/^[a-zA-Z]:\\/)) {
const [host, ...pathParts] = location.split(':');
return {
type: 'ssh',
host,
path: pathParts.join(':')
};
}
return {
type: 'local',
path: location
};
}
}
export function attachConvenienceMethods(engine) {
const api = new ConvenienceAPI(engine);
return Object.assign(engine, {
onHost: api.onHost.bind(api),
in: api.in.bind(api),
copy: api.copy.bind(api),
forward: api.forward.bind(api),
logs: api.logs.bind(api),
smart: api.smart.bind(api)
});
}
//# sourceMappingURL=convenience.js.map