Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 16x 16x 8x 8x 6x 6x 4x 4x 2x 2x 2x 2x 1x 1x 1x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 193x 193x | import { getAllInstances } from '@clevercloud/client/esm/api/v2/application.js';
import { spawn } from 'node:child_process';
import { z } from 'zod';
import { config } from '../../config/config.js';
import { defineCommand } from '../../lib/define-command.js';
import { defineOption } from '../../lib/define-option.js';
import { selectAnswer } from '../../lib/prompts.js';
import * as Application from '../../models/application.js';
import { sendToApi } from '../../models/send-to-api.js';
import { runMarkerProtocol } from '../../models/ssh-marker-protocol.js';
import { aliasOption, appIdOrNameOption } from '../global.options.js';
export const sshCommand = defineCommand({
description: 'Connect to running instances through SSH',
since: '0.7.0',
options: {
identityFile: defineOption({
name: 'identity-file',
schema: z.string().optional(),
description: 'SSH identity file',
aliases: ['i'],
placeholder: 'identity-file',
}),
command: defineOption({
name: 'command',
schema: z.string().optional(),
description: 'Execute a command on the remote instance and exit',
aliases: ['c'],
placeholder: 'command',
}),
alias: aliasOption,
app: appIdOrNameOption,
},
args: [],
async handler(options) {
const { alias, app: appIdOrName, identityFile, command } = options;
const { appId, ownerId } = await Application.resolveId(appIdOrName, alias);
const instances = await getAllInstances({ id: ownerId, appId }).then(sendToApi);
if (instances.length === 0) {
throw new Error('No running instances found for this application');
}
let sshTarget;
if (instances.length === 1) {
sshTarget = instances[0].id;
} else if (process.stdin.isTTY) {
const choices = instances
.sort((a, b) => a.instanceNumber - b.instanceNumber)
.map((inst) => ({
name: `${inst.displayName} - Instance ${inst.instanceNumber} - ${inst.state} (${inst.id})`,
value: inst.id,
}));
sshTarget = await selectAnswer('Select an instance:', choices);
} else {
throw new Error('Multiple instances are running. Cannot select in non-interactive mode.');
}
const sshParams = [];
// -t: force PTY allocation (SSH skips it by default because appId is passed as a command for gateway routing)
if (command == null) {
sshParams.push('-t');
}
if (identityFile != null) {
sshParams.push('-i', identityFile);
}
sshParams.push(config.SSH_GATEWAY, sshTarget);
// Interactive session mode (spawn SSH with inherited stdio)
if (command == null) {
return new Promise((resolve, reject) => {
const sshProcess = spawn('ssh', sshParams, { stdio: 'inherit' });
sshProcess.on('exit', resolve);
sshProcess.on('error', reject);
});
}
// Single command mode (pipe stdio to filter gateway noise via a marker)
const sshProcess = spawn('ssh', sshParams, { stdio: 'pipe' });
const exitCode = await runMarkerProtocol({ child: sshProcess, command });
process.exit(exitCode);
},
});
|