Сайт

Exec

sdk.exec запускает исполняемые файлы на машине пользователя. Для этого требуется разрешение exec в манифесте — без него каждый вызов завершается ошибкой. Если разрешение объявлено, перед первым использованием пользователю показывается запрос (см. Permissions).

{ "contributes": { "permissions": ["exec"] } }

sdk.exec работает только в десктопном приложении — в веб-приложении каждый вызов завершается ошибкой. Каждая команда и её вывод также появляются в терминале задач, названным по id вашего расширения (терминал не открывается автоматически), если только пользователь не установил terminal.commandOutput в off.

execute — выполнить до конца

execute(executable: string, args?: readonly string[], options?: ExtensionExecOptions): Promise<ExtensionExecResult>

| Опция | Тип | По умолчанию | Описание | | --- | --- | --- | --- | | cwd | string | рабочая директория приложения | Рабочая директория. | | env | Record<string, string> | — | Дополнительные переменные окружения поверх окружения приложения. На macOS и Linux PATH берётся из login shell пользователя. | | timeout | number | без таймаута | Миллисекунды. При превышении процесс завершается, а вызов отклоняется. | | maxBuffer | number | 10 МБ | Максимум байт, собираемых на поток (stdout, stderr). При превышении процесс завершается, а вызов отклоняется. | | shell | string \| boolean | без shell | true запускает через shell по умолчанию; строка задаёт shell. | | encoding | BufferEncoding | 'utf8' | Как декодируется вывод. |

Результат имеет тип ExtensionExecResult: { stdout, stderr, exitCode, duration }, duration указывается в миллисекундах. Ненулевой код выхода не приводит к отклонению вызова, нужно проверять exitCode вручную. Вызов отклоняется в случае, если:

  • разрешение exec не объявлено или было отклонено;
  • приложение не десктопное;
  • процесс не удаётся запустить (например, исполняемый файл не найден);
  • превышен timeout или maxBuffer;
  • процесс завершён сигналом (в том числе через kill());
  • исполняемый файл или аргумент содержит сегмент пути ...
const log = await sdk.window.createLogOutputChannel('My Extension')

const result = await sdk.exec.execute('git', ['status', '--porcelain'], { encoding: 'utf-8' })
if (result.exitCode === 0) {
  log?.info(result.stdout)
}
ExtensionExecOptions · ExtensionExecResult
interface ExtensionExecOptions {
  cwd?: string
  env?: Record<string, string>
  timeout?: number            // мс
  maxBuffer?: number          // байт на поток
  shell?: string | boolean
  encoding?: BufferEncoding
}

interface ExtensionExecResult {
  stdout: string
  stderr: string
  exitCode: number
  duration: number            // мс
}

start — длительный процесс в потоковом режиме

start(executable: string, args?: readonly string[], options?: ExtensionExecOptions): ExtensionExecHandle
kill(pid: number): Promise<void>

start сразу возвращает дескриптор и передаёт вывод по мере его появления. Он принимает те же опции и завершается по тем же причинам, что и execute, ошибки передаются через свойство result.

| Элемент | Тип | Описание | | --- | --- | --- | | executionId | SdkExecExecutionId | Идентификатор этого запуска, доступен сразу. | | pid | Promise<number> | Выполняется после старта процесса. Не выполняется никогда, если процесс не запустился. | | onStdout / onStderr | Event<string> | Чанки вывода, декодированные по encoding. | | result | Promise<SdkExecStartResponse> | { executionId, stdout, stderr, exitCode, duration } после завершения процесса. Отклоняется так же, как execute. | | kill() | Promise<void> | Отправляет SIGTERM. До старта процесса отклоняется; после его завершения ничего не делает. Если процесс завершился из-за сигнала, result отклоняется. | | dispose() | void | Отсоединяет только слушателей вывода — процесс продолжает работать, а result всё равно завершится. Слушатели также освобождаются, когда result завершается. |

sdk.exec.kill(pid) отправляет SIGTERM процессу, запущенному приложением. pid, который приложение не запускало, игнорируется.

const output = await sdk.window.createOutputChannel('npm install')

const handle = sdk.exec.start('npm', ['install'])

const sub = handle.onStdout((chunk) => output?.append(chunk))

const result = await handle.result
sub.dispose()
handle.dispose()

Процессы не завершаются при деактивации расширения — сохраняйте дескрипторы и завершайте процессы в deactivate:

import type { ExtensionExecHandle } from '@fragmenta/extension'

let server: ExtensionExecHandle | undefined

export function activate() {
  server = sdk.exec.start('node', ['server.js'])
}

export async function deactivate() {
  await server?.kill().catch(() => {}) // отклоняется, если процесс так и не запустился
}
ExtensionExecHandle · SdkExecStartResponse
type SdkExecExecutionId = string // branded-строка

interface ExtensionExecHandle {
  readonly executionId: SdkExecExecutionId
  readonly pid: Promise<number>
  readonly onStdout: Event<string>
  readonly onStderr: Event<string>
  readonly result: Promise<SdkExecStartResponse>
  kill(): Promise<void>
  dispose(): void
}

interface SdkExecStartResponse {
  executionId: SdkExecExecutionId
  stdout: string
  stderr: string
  exitCode: number
  duration: number            // мс
}