Commit 7bf952ed authored by Ivan Mazhukin's avatar Ivan Mazhukin

add vscode extension sources

parent 94b10240
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
}
]
}
.vscode/**
.gitignore
*.vsix
# EPM Docker Test Runner
Расширение VS Code с кнопками для запуска `epm-docker-test.sh`.
## Использование
1. Откройте эту папку в VS Code: `vscode-extension`.
2. Нажмите `F5`, чтобы запустить Extension Development Host.
3. В открывшемся окне откройте репозиторий с `epm-docker-test.sh`.
4. Используйте панель `EPM Test` на боковой панели активности или кнопку `$(beaker) EPM Test` в строке состояния.
Расширение запускает команды в обычном терминале VS Code, поэтому вывод и интерактивные сообщения остаются видимыми.
## Установка
Для повседневного локального использования без упаковки:
```bash
mkdir -p ~/.vscode/extensions/eter.epm-docker-test-runner-0.1.0
rsync -a --delete ./ ~/.vscode/extensions/eter.epm-docker-test-runner-0.1.0/
```
После этого выполните в VS Code команду `Developer: Reload Window`.
Установка через VSIX-пакет:
```bash
cd vscode-extension
npm install -g @vscode/vsce
vsce package
code --install-extension epm-docker-test-runner-0.1.0.vsix
```
Этот же VSIX можно установить через команду VS Code `Extensions: Install from VSIX...`.
## Действия
- `Run app on systems`: спрашивает приложение и целевые системы.
- `Run app on systems parallel`: спрашивает приложение, затем даёт выбрать пресет или вручную ввести системы, и передаёт `--parallel`.
- `Run app preset`: спрашивает приложение и пресет `main`, `russian` или `all`.
- `Run exec command`: спрашивает shell-команду и целевые системы или пресет.
- `./bin/epm play <app>`: запускает локальный `epm play` вне Docker.
- `./bin/epm play --latest <app>`: запускает локальный `epm play --latest` вне Docker.
- `Rerun last command`: повторяет последнюю сгенерированную команду.
- `Open log folder`: открывает `${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test` или настроенный каталог логов.
Когда расширение спрашивает имя приложения, оно пытается определить его по активному файлу, если файл находится в `play.d`, `pack.d` или `repack.d`. Сначала проверяются простые shell-переменные вроде `PRODUCT=rstudio` или `PKGNAME=rstudio`; если они не найдены, используется имя файла без расширения. Если приложение найдено, расширение показывает кликабельные варианты `OK` и `Cancel`; имя приложения можно поправить в поле выбора перед нажатием `OK`.
## Избранное
Закреплённые команды можно добавить в настройки рабочей области:
```json
{
"epmDockerTest.favorites": [
{
"label": "ayugram main",
"app": "ayugram",
"preset": "main"
},
{
"label": "os-release fedora",
"exec": "cat /etc/os-release | head -3",
"systems": ["fedora"]
}
]
}
```
## Настройки
Основные настройки:
- `epmDockerTest.scriptPath`: путь к `epm-docker-test.sh`; пустое значение означает автоопределение.
- `epmDockerTest.workingDirectory`: рабочий каталог команды.
- `epmDockerTest.defaultSystems`: системы по умолчанию для запусков app/exec.
- `epmDockerTest.defaultPreset`: пресет по умолчанию для запусков по пресету.
- `epmDockerTest.defaultMode`: `auto`, `local` или `remote`.
- `epmDockerTest.latest`: передавать `--latest`.
- `epmDockerTest.parallelJobs`: передавать `-j N`, если значение больше `1`.
- `epmDockerTest.localEpmRoot`: локальное дерево eepm для команд `./bin/epm play` вне контейнера.
- `epmDockerTest.eepmDir`, `epmDockerTest.eepmSource`, `epmDockerTest.remoteHost`, `epmDockerTest.remoteUser`, `epmDockerTest.builderUser`, `epmDockerTest.builderPath`, `epmDockerTest.logRoot`: соответствуют одноимённым опциям скрипта.
Для Docker-тестов автоопределение предпочитает ближайший родительский каталог активного файла, в котором есть и `bin/epm`, и `epm-docker-test.sh`. Этот каталог используется как рабочий каталог команды, а его `epm-docker-test.sh` используется как запускаемый скрипт.
const fs = require('fs');
const os = require('os');
const path = require('path');
const vscode = require('vscode');
const EXTENSION_ID = 'epmDockerTest';
const TERMINAL_NAME = 'EPM Docker Test';
let lastRun = undefined;
let terminal = undefined;
let extensionContext = undefined;
function activate(context) {
extensionContext = context;
const provider = new ActionsProvider();
vscode.window.registerTreeDataProvider('epmDockerTest.actions', provider);
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
status.name = 'EPM Docker Test';
status.text = '$(beaker) EPM Test';
status.tooltip = 'Run epm-docker-test.sh';
status.command = 'epmDockerTest.runQuick';
status.show();
context.subscriptions.push(
status,
vscode.commands.registerCommand('epmDockerTest.runQuick', runQuick),
vscode.commands.registerCommand('epmDockerTest.runApp', () => runApp(false)),
vscode.commands.registerCommand('epmDockerTest.runAppParallel', () => runApp(true)),
vscode.commands.registerCommand('epmDockerTest.runPreset', runPreset),
vscode.commands.registerCommand('epmDockerTest.runExec', runExec),
vscode.commands.registerCommand('epmDockerTest.runLocalPlay', () => runLocalPlay(false)),
vscode.commands.registerCommand('epmDockerTest.runLocalPlayLatest', () => runLocalPlay(true)),
vscode.commands.registerCommand('epmDockerTest.rerunLast', rerunLast),
vscode.commands.registerCommand('epmDockerTest.openLogs', openLogs),
vscode.commands.registerCommand('epmDockerTest.refresh', () => provider.refresh()),
vscode.commands.registerCommand('epmDockerTest.configure', () => {
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:eter.epm-docker-test-runner');
}),
vscode.commands.registerCommand('epmDockerTest.runFavorite', (favorite) => runFavorite(favorite)),
vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration(EXTENSION_ID)) {
provider.refresh();
}
})
);
}
function deactivate() {}
class ActionsProvider {
constructor() {
this.emitter = new vscode.EventEmitter();
this.onDidChangeTreeData = this.emitter.event;
}
refresh() {
this.emitter.fire();
}
getTreeItem(item) {
return item;
}
getChildren(item) {
if (item) {
return [];
}
const favorites = getConfig().get('favorites', []);
const favoriteItems = favorites
.filter((favorite) => favorite && favorite.label)
.map((favorite) => commandItem(
favorite.label,
'epmDockerTest.runFavorite',
favorite,
new vscode.ThemeIcon('star-full')
));
const builtIns = [
commandItem('Run app on systems', 'epmDockerTest.runApp', undefined, new vscode.ThemeIcon('run')),
commandItem('Run app on systems parallel', 'epmDockerTest.runAppParallel', undefined, new vscode.ThemeIcon('run-all')),
commandItem('Run app preset', 'epmDockerTest.runPreset', undefined, new vscode.ThemeIcon('list-selection')),
commandItem('Run exec command', 'epmDockerTest.runExec', undefined, new vscode.ThemeIcon('terminal')),
separatorItem('Local EPM'),
commandItem('./bin/epm play <app>', 'epmDockerTest.runLocalPlay', undefined, new vscode.ThemeIcon('play')),
commandItem('./bin/epm play --latest <app>', 'epmDockerTest.runLocalPlayLatest', undefined, new vscode.ThemeIcon('cloud-download')),
separatorItem('Tools'),
commandItem('Rerun last command', 'epmDockerTest.rerunLast', undefined, new vscode.ThemeIcon('debug-restart')),
commandItem('Open log folder', 'epmDockerTest.openLogs', undefined, new vscode.ThemeIcon('folder-opened')),
commandItem('Configure', 'epmDockerTest.configure', undefined, new vscode.ThemeIcon('settings-gear'))
];
return [...favoriteItems, ...builtIns];
}
}
function commandItem(label, command, argument, iconPath) {
const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None);
item.iconPath = iconPath;
item.command = {
command,
title: label,
arguments: argument === undefined ? [] : [argument]
};
return item;
}
function separatorItem(label) {
const item = new vscode.TreeItem(`----- ${label} -----`, vscode.TreeItemCollapsibleState.None);
item.iconPath = new vscode.ThemeIcon('dash');
return item;
}
async function runQuick() {
const choice = await vscode.window.showQuickPick([
{ label: '$(run) Run app on systems', command: 'epmDockerTest.runApp' },
{ label: '$(run-all) Run app on systems parallel', command: 'epmDockerTest.runAppParallel' },
{ label: '$(list-selection) Run app preset', command: 'epmDockerTest.runPreset' },
{ label: '$(terminal) Run exec command', command: 'epmDockerTest.runExec' },
{ label: '$(play) Local ./bin/epm play', command: 'epmDockerTest.runLocalPlay' },
{ label: '$(cloud-download) Local ./bin/epm play --latest', command: 'epmDockerTest.runLocalPlayLatest' },
{ label: '$(debug-restart) Rerun last command', command: 'epmDockerTest.rerunLast' },
{ label: '$(folder-opened) Open log folder', command: 'epmDockerTest.openLogs' }
], {
placeHolder: 'Choose an epm-docker-test action'
});
if (choice) {
await vscode.commands.executeCommand(choice.command);
}
}
async function runApp(parallel) {
const app = await promptForApp({
title: parallel ? 'EPM Docker Test Parallel' : 'EPM Docker Test',
prompt: 'Application name for epm play',
fallbackKey: 'lastApp'
});
if (!app) {
return;
}
if (parallel) {
const target = await pickParallelTarget();
if (!target) {
return;
}
await setWorkspaceState('lastApp', app);
if (target.preset) {
await executeTest({ app, preset: target.preset, parallel });
return;
}
await setWorkspaceState('lastSystems', target.systems);
await executeTest({ app, systems: splitArgs(target.systems), parallel });
return;
}
const systems = await promptForSystems({
title: 'EPM Docker Test',
prompt: 'Target systems separated by spaces',
fallback: getWorkspaceState('lastSystems', getConfig().get('defaultSystems', 'fedora'))
});
if (!systems) {
return;
}
await setWorkspaceState('lastApp', app);
await setWorkspaceState('lastSystems', systems);
await executeTest({ app, systems: splitArgs(systems), parallel });
}
async function pickParallelTarget() {
const defaultPreset = getConfig().get('defaultPreset', 'main');
const presetItems = ['main', 'russian', 'all']
.filter((preset) => preset !== defaultPreset)
.map((preset) => ({
label: `$(list-selection) Preset: ${preset}`,
preset
}));
const choice = await vscode.window.showQuickPick([
{
label: `$(list-selection) Preset: ${defaultPreset}`,
description: 'Default preset',
preset: defaultPreset
},
...presetItems,
{
label: '$(edit) Custom systems...',
description: 'Type target systems manually',
custom: true
},
{
label: '$(close) Отмена',
cancel: true
}
], {
title: 'EPM Docker Test Parallel',
placeHolder: 'Choose preset or custom target systems'
});
if (!choice || choice.cancel) {
return undefined;
}
if (choice.preset) {
return { preset: choice.preset };
}
const systems = await promptForSystems({
title: 'EPM Docker Test Parallel',
prompt: 'Target systems separated by spaces',
fallback: getWorkspaceState('lastSystems', getConfig().get('defaultSystems', 'fedora'))
});
return systems ? { systems } : undefined;
}
async function runPreset() {
const app = await promptForApp({
title: 'EPM Docker Test',
prompt: 'Application name for epm play',
fallbackKey: 'lastApp'
});
if (!app) {
return;
}
const preset = await pickPreset(getConfig().get('defaultPreset', 'main'));
if (!preset) {
return;
}
await setWorkspaceState('lastApp', app);
await executeTest({ app, preset });
}
async function runExec() {
const exec = await vscode.window.showInputBox({
title: 'EPM Docker Test',
prompt: 'Shell command to run in the container',
placeHolder: 'cat /etc/os-release | head -3',
value: getWorkspaceState('lastExec', '')
});
if (!exec) {
return;
}
const targetKind = await vscode.window.showQuickPick([
{ label: 'Systems', description: 'Type one or more target systems' },
{ label: 'Preset', description: 'Use main, russian, or all' }
], {
title: 'EPM Docker Test',
placeHolder: 'Choose target type'
});
if (!targetKind) {
return;
}
await setWorkspaceState('lastExec', exec);
if (targetKind.label === 'Preset') {
const preset = await pickPreset(getConfig().get('defaultPreset', 'main'));
if (preset) {
await executeTest({ exec, preset });
}
return;
}
const systems = await promptForSystems({
title: 'EPM Docker Test',
prompt: 'Target systems separated by spaces',
fallback: getWorkspaceState('lastSystems', getConfig().get('defaultSystems', 'fedora'))
});
if (systems) {
await setWorkspaceState('lastSystems', systems);
await executeTest({ exec, systems: splitArgs(systems) });
}
}
async function runLocalPlay(latest) {
const eepmRoot = resolveWorkspacePath(getConfig().get('localEpmRoot', '/home/vano/eter/static2/eepm'));
const epm = path.join(eepmRoot, 'bin', 'epm');
if (!fs.existsSync(epm)) {
vscode.window.showErrorMessage(`Could not find local epm: ${epm}`);
return;
}
const inferred = inferAppFromActiveEditor(eepmRoot);
const app = await promptForApp({
title: latest ? 'Local epm play --latest' : 'Local epm play',
prompt: 'Application name for local ./bin/epm play',
fallbackKey: 'lastLocalApp',
inferred
});
if (!app) {
return;
}
const args = ['play'];
if (latest) {
args.push('--latest');
}
args.push(app);
const command = [
'cd',
shellQuote(eepmRoot),
'&&',
'./bin/epm',
...args.map(shellQuote)
].join(' ');
await setWorkspaceState('lastLocalApp', app);
lastRun = command;
await setWorkspaceState('lastCommand', command);
sendToTerminal(command);
}
async function promptForApp(options) {
const inferred = options.inferred || inferAppFromActiveEditor(resolveWorkspacePath(getConfig().get('localEpmRoot', '/home/vano/eter/static2/eepm')));
const fallback = getWorkspaceState(options.fallbackKey, getWorkspaceState('lastApp', ''));
const initialValue = inferred?.app || fallback;
if (inferred?.app) {
return promptForInferredApp(options, inferred);
}
return vscode.window.showInputBox({
title: options.title,
prompt: inferred
? `${options.prompt} (inferred from ${inferred.source})`
: options.prompt,
value: initialValue,
placeHolder: 'rstudio'
});
}
function promptForInferredApp(options, inferred) {
return new Promise((resolve) => {
const quickPick = vscode.window.createQuickPick();
const okItem = { label: '', description: inferred.source, action: 'ok' };
const cancelItem = { label: '$(close) Отмена', action: 'cancel' };
let settled = false;
const finish = (value) => {
if (settled) {
return;
}
settled = true;
resolve(value);
};
const updateOkItem = (value) => {
const app = value.trim() || inferred.app;
okItem.label = `$(check) OK: ${app}`;
quickPick.items = [okItem, cancelItem];
};
quickPick.title = options.title;
quickPick.placeholder = `${options.prompt} (inferred from ${inferred.source})`;
quickPick.value = inferred.app;
quickPick.matchOnDescription = true;
updateOkItem(quickPick.value);
quickPick.onDidChangeValue(updateOkItem);
quickPick.onDidAccept(() => {
const selected = quickPick.selectedItems[0];
if (selected?.action === 'cancel') {
quickPick.hide();
finish(undefined);
return;
}
const app = quickPick.value.trim() || inferred.app;
quickPick.hide();
finish(app);
});
quickPick.onDidHide(() => {
quickPick.dispose();
finish(undefined);
});
quickPick.show();
});
}
function promptForSystems(options) {
return promptForEditableValue({
title: options.title,
prompt: options.prompt,
value: options.fallback,
fallback: 'fedora',
okLabel: 'OK',
cancelLabel: 'Отмена'
});
}
function promptForEditableValue(options) {
return new Promise((resolve) => {
const quickPick = vscode.window.createQuickPick();
const okItem = { label: '', action: 'ok' };
const cancelItem = { label: `$(close) ${options.cancelLabel}`, action: 'cancel' };
let settled = false;
const finish = (value) => {
if (settled) {
return;
}
settled = true;
resolve(value);
};
const currentValue = () => quickPick.value.trim() || options.fallback;
const updateOkItem = () => {
okItem.label = `$(check) ${options.okLabel}: ${currentValue()}`;
quickPick.items = [okItem, cancelItem];
};
quickPick.title = options.title;
quickPick.placeholder = options.prompt;
quickPick.value = options.value || options.fallback;
updateOkItem();
quickPick.onDidChangeValue(updateOkItem);
quickPick.onDidAccept(() => {
const selected = quickPick.selectedItems[0];
if (selected?.action === 'cancel') {
quickPick.hide();
finish(undefined);
return;
}
quickPick.hide();
finish(currentValue());
});
quickPick.onDidHide(() => {
quickPick.dispose();
finish(undefined);
});
quickPick.show();
});
}
async function rerunLast() {
const command = getWorkspaceState('lastCommand', lastRun);
if (!command) {
vscode.window.showWarningMessage('No epm-docker-test command has been run yet.');
return;
}
sendToTerminal(command);
}
async function runFavorite(favorite) {
if (!favorite) {
return;
}
await executeTest({
label: favorite.label,
app: favorite.app,
systems: favorite.systems,
preset: favorite.preset,
exec: favorite.exec,
favoriteArgs: favorite.args
});
}
async function executeTest(request) {
const resolved = resolvePaths();
if (!resolved) {
return;
}
if (!request.exec && !request.app) {
vscode.window.showErrorMessage('Favorite must define either app or exec.');
return;
}
if (!request.preset && (!request.systems || request.systems.length === 0)) {
vscode.window.showErrorMessage('Test command must define systems or preset.');
return;
}
const args = buildCommonArgs();
if (Array.isArray(request.favoriteArgs)) {
args.push(...request.favoriteArgs);
}
if (request.parallel && !args.includes('--parallel') && !args.includes('-j')) {
args.push('--parallel');
}
if (request.exec) {
args.push('--exec', request.exec);
if (request.preset) {
args.push('--preset', request.preset);
} else {
args.push(...request.systems);
}
} else {
args.push(request.app);
if (request.preset) {
args.push('--preset', request.preset);
} else {
args.push(...request.systems);
}
}
return finishExecution(resolved, args);
}
async function finishExecution(resolved, args) {
const command = [
'cd',
shellQuote(resolved.cwd),
'&&',
shellQuote(resolved.script),
...args.map(shellQuote)
].join(' ');
lastRun = command;
await setWorkspaceState('lastCommand', command);
sendToTerminal(command);
}
function buildCommonArgs() {
const config = getConfig();
const args = [];
const mode = config.get('defaultMode', 'auto');
const eepmSource = config.get('eepmSource', 'local');
const parallelJobs = Number(config.get('parallelJobs', 0));
if (mode && mode !== 'auto') {
args.push(`--${mode}`);
}
if (eepmSource && eepmSource !== 'local') {
args.push('--eepm-source', eepmSource);
}
addStringArg(args, '--eepm-dir', config.get('eepmDir', ''));
addStringArg(args, '--remote-host', config.get('remoteHost', ''));
addStringArg(args, '--remote-user', config.get('remoteUser', ''));
addStringArg(args, '--builder-user', config.get('builderUser', ''));
addStringArg(args, '--builder-path', config.get('builderPath', ''));
addStringArg(args, '--log-root', config.get('logRoot', ''));
if (config.get('latest', false)) {
args.push('--latest');
}
if (parallelJobs > 1) {
args.push('-j', String(parallelJobs));
}
const additionalArgs = config.get('additionalArgs', []);
if (Array.isArray(additionalArgs)) {
args.push(...additionalArgs.filter((value) => typeof value === 'string' && value.length > 0));
}
return args;
}
function addStringArg(args, flag, value) {
if (typeof value === 'string' && value.trim()) {
args.push(flag, value.trim());
}
}
async function pickPreset(defaultPreset) {
return vscode.window.showQuickPick(['main', 'russian', 'all'], {
title: 'EPM Docker Test',
placeHolder: `Choose a preset (${defaultPreset})`
});
}
async function openLogs() {
const logRoot = configuredLogRoot();
if (!fs.existsSync(logRoot)) {
const answer = await vscode.window.showInformationMessage(
`Log folder does not exist yet: ${logRoot}`,
'Open Parent'
);
if (answer !== 'Open Parent') {
return;
}
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(path.dirname(logRoot)), {
forceNewWindow: true
});
return;
}
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(logRoot), {
forceNewWindow: true
});
}
function configuredLogRoot() {
const configured = getConfig().get('logRoot', '');
if (configured && configured.trim()) {
return expandHome(configured.trim());
}
return path.join(process.env.XDG_STATE_HOME || path.join(os.homedir(), '.local', 'state'), 'epm-docker-test');
}
function resolvePaths() {
const config = getConfig();
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const configuredScript = config.get('scriptPath', '').trim();
const configuredCwd = config.get('workingDirectory', '').trim();
const detectedEepmRoot = configuredCwd ? undefined : findEepmRootFromActiveEditor();
const script = configuredScript
? resolveWorkspacePath(configuredScript)
: findScript(workspaceFolder, detectedEepmRoot);
if (!script || !fs.existsSync(script)) {
vscode.window.showErrorMessage('Could not find epm-docker-test.sh. Set epmDockerTest.scriptPath.');
return undefined;
}
const cwd = configuredCwd
? resolveWorkspacePath(configuredCwd)
: detectedEepmRoot || workspaceFolder || path.dirname(script);
return { script, cwd };
}
function findScript(workspaceFolder, detectedEepmRoot) {
const candidates = [];
if (detectedEepmRoot) {
candidates.push(path.join(detectedEepmRoot, 'epm-docker-test.sh'));
}
if (workspaceFolder) {
candidates.push(
path.join(workspaceFolder, 'epm-docker-test.sh'),
path.join(workspaceFolder, 'epm-docker-test', 'epm-docker-test.sh')
);
}
candidates.push(path.resolve(__dirname, '..', 'epm-docker-test.sh'));
return candidates.find((candidate) => fs.existsSync(candidate));
}
function findEepmRootFromActiveEditor() {
const editor = vscode.window.activeTextEditor;
const filePath = editor?.document?.uri?.scheme === 'file' ? editor.document.uri.fsPath : undefined;
if (!filePath) {
return undefined;
}
let current;
try {
current = fs.statSync(filePath).isDirectory() ? filePath : path.dirname(filePath);
} catch {
return undefined;
}
while (current && current !== path.dirname(current)) {
if (
fs.existsSync(path.join(current, 'bin', 'epm')) &&
fs.existsSync(path.join(current, 'epm-docker-test.sh'))
) {
return current;
}
current = path.dirname(current);
}
return undefined;
}
function inferAppFromActiveEditor(eepmRoot) {
const editor = vscode.window.activeTextEditor;
const filePath = editor?.document?.uri?.scheme === 'file' ? editor.document.uri.fsPath : undefined;
if (!filePath) {
return undefined;
}
const dirName = path.basename(path.dirname(filePath));
if (!['play.d', 'pack.d', 'repack.d'].includes(dirName)) {
return undefined;
}
const baseName = path.basename(filePath, path.extname(filePath));
const parsed = inferAppFromFileContent(filePath);
const app = parsed || baseName;
const relative = path.relative(eepmRoot, filePath);
const source = relative && !relative.startsWith('..') ? relative : filePath;
return { app, source };
}
function inferAppFromFileContent(filePath) {
try {
const text = fs.readFileSync(filePath, 'utf8').slice(0, 8192);
for (const variable of ['PRODUCT', 'PKGNAME', 'APP', 'APPNAME']) {
const match = text.match(new RegExp(`^${variable}=(["']?)([A-Za-z0-9._+-]+)\\1\\s*$`, 'm'));
if (match) {
return match[2];
}
}
} catch {
return undefined;
}
return undefined;
}
function resolveWorkspacePath(value) {
const expanded = expandHome(value);
if (path.isAbsolute(expanded)) {
return expanded;
}
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd();
return path.join(workspaceFolder, expanded);
}
function expandHome(value) {
if (value === '~') {
return os.homedir();
}
if (value.startsWith('~/')) {
return path.join(os.homedir(), value.slice(2));
}
return value;
}
function sendToTerminal(command) {
if (!terminal || terminal.exitStatus) {
terminal = vscode.window.createTerminal(TERMINAL_NAME);
}
terminal.show();
terminal.sendText(command, true);
}
function shellQuote(value) {
const stringValue = String(value);
if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(stringValue)) {
return stringValue;
}
return `'${stringValue.replace(/'/g, `'\\''`)}'`;
}
function splitArgs(value) {
return value.split(/\s+/).map((item) => item.trim()).filter(Boolean);
}
function getConfig() {
return vscode.workspace.getConfiguration(EXTENSION_ID);
}
function getWorkspaceState(key, fallback) {
return extensionContext?.workspaceState.get(key, fallback) ?? fallback;
}
async function setWorkspaceState(key, value) {
await extensionContext?.workspaceState.update(key, value);
}
module.exports = {
activate,
deactivate
};
{
"name": "epm-docker-test-runner",
"displayName": "EPM Docker Test Runner",
"description": "VS Code buttons for running epm-docker-test.sh test commands.",
"version": "0.1.0",
"publisher": "eter",
"engines": {
"vscode": "^1.85.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onStartupFinished",
"onView:epmDockerTest.actions",
"onCommand:epmDockerTest.runQuick",
"onCommand:epmDockerTest.runApp",
"onCommand:epmDockerTest.runAppParallel",
"onCommand:epmDockerTest.runPreset",
"onCommand:epmDockerTest.runExec",
"onCommand:epmDockerTest.rerunLast",
"onCommand:epmDockerTest.runLocalPlay",
"onCommand:epmDockerTest.runLocalPlayLatest"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "epmDockerTest.runQuick",
"title": "EPM Docker Test: Run..."
},
{
"command": "epmDockerTest.runApp",
"title": "EPM Docker Test: Run App on Systems"
},
{
"command": "epmDockerTest.runAppParallel",
"title": "EPM Docker Test: Run App on Systems in Parallel"
},
{
"command": "epmDockerTest.runPreset",
"title": "EPM Docker Test: Run App Preset"
},
{
"command": "epmDockerTest.runExec",
"title": "EPM Docker Test: Run Exec Command"
},
{
"command": "epmDockerTest.runLocalPlay",
"title": "EPM Docker Test: Local epm play"
},
{
"command": "epmDockerTest.runLocalPlayLatest",
"title": "EPM Docker Test: Local epm play --latest"
},
{
"command": "epmDockerTest.rerunLast",
"title": "EPM Docker Test: Rerun Last Command"
},
{
"command": "epmDockerTest.openLogs",
"title": "EPM Docker Test: Open Log Folder"
},
{
"command": "epmDockerTest.refresh",
"title": "EPM Docker Test: Refresh"
},
{
"command": "epmDockerTest.configure",
"title": "EPM Docker Test: Configure"
}
],
"configuration": {
"title": "EPM Docker Test",
"properties": {
"epmDockerTest.scriptPath": {
"type": "string",
"default": "",
"description": "Path to epm-docker-test.sh. Empty means auto-detect in the workspace."
},
"epmDockerTest.workingDirectory": {
"type": "string",
"default": "",
"description": "Working directory for test commands. Empty means the workspace folder or script directory."
},
"epmDockerTest.defaultSystems": {
"type": "string",
"default": "fedora",
"description": "Default systems used by Run App on Systems."
},
"epmDockerTest.defaultPreset": {
"type": "string",
"enum": [
"main",
"russian",
"all"
],
"default": "main",
"description": "Default preset used by Run App Preset."
},
"epmDockerTest.defaultMode": {
"type": "string",
"enum": [
"auto",
"local",
"remote"
],
"default": "auto",
"description": "Default runner mode."
},
"epmDockerTest.latest": {
"type": "boolean",
"default": false,
"description": "Pass --latest to epm play."
},
"epmDockerTest.parallelJobs": {
"type": "number",
"default": 0,
"minimum": 0,
"description": "Parallel jobs. 0 disables -j/--parallel, 1 runs serially, values greater than 1 pass -j N."
},
"epmDockerTest.eepmDir": {
"type": "string",
"default": "",
"description": "Optional --eepm-dir value."
},
"epmDockerTest.eepmSource": {
"type": "string",
"enum": [
"local",
"builder64"
],
"default": "local",
"description": "Value for --eepm-source."
},
"epmDockerTest.remoteHost": {
"type": "string",
"default": "",
"description": "Optional --remote-host value."
},
"epmDockerTest.remoteUser": {
"type": "string",
"default": "",
"description": "Optional --remote-user value."
},
"epmDockerTest.builderUser": {
"type": "string",
"default": "",
"description": "Optional --builder-user value."
},
"epmDockerTest.builderPath": {
"type": "string",
"default": "",
"description": "Optional --builder-path value."
},
"epmDockerTest.logRoot": {
"type": "string",
"default": "",
"description": "Optional --log-root value."
},
"epmDockerTest.localEpmRoot": {
"type": "string",
"default": "/home/vano/eter/static2/eepm",
"description": "Path to the local eepm tree used by local ./bin/epm play commands."
},
"epmDockerTest.additionalArgs": {
"type": "array",
"default": [],
"items": {
"type": "string"
},
"description": "Extra arguments passed before the app/exec target."
},
"epmDockerTest.favorites": {
"type": "array",
"default": [],
"description": "Pinned commands shown as buttons in the EPM Test activity view.",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"app": {
"type": "string"
},
"systems": {
"type": "array",
"items": {
"type": "string"
}
},
"preset": {
"type": "string",
"enum": [
"main",
"russian",
"all"
]
},
"exec": {
"type": "string"
},
"args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"viewsContainers": {
"activitybar": [
{
"id": "epmDockerTest",
"title": "EPM Test",
"icon": "resources/epm-test.svg"
}
]
},
"views": {
"epmDockerTest": [
{
"id": "epmDockerTest.actions",
"name": "Actions"
}
]
},
"menus": {
"view/title": [
{
"command": "epmDockerTest.refresh",
"when": "view == epmDockerTest.actions",
"group": "navigation"
}
]
}
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" stroke="#c5c5c5" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round" transform="translate(6.25 0.6) scale(0.48)">
<path d="M9 3h6"/>
<path d="M10 3v5l-5.2 8.4A3 3 0 0 0 7.4 21h9.2a3 3 0 0 0 2.6-4.6L14 8V3"/>
<path d="M7.5 15h9"/>
</g>
<text x="12" y="21.2" fill="#c5c5c5" font-family="Arial, sans-serif" font-size="7.2" font-weight="700" text-anchor="middle">epm</text>
</svg>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment