Как создать GUI для исполняемого exe файла CLI версии?

Имеется пакет gifski, у него есть CLI версия для windows, необходимо сделать простой GUI чтобы не вводить команды в терминал.

Как с помощью того же NodeJS можно собрать приложение на Electron, которое бы обращалось к данному файлу и выполняло команды?

Как правильно сформулировать свой вопрос для поиска в google? Не имею соображений к чему подступиться чтобы вопрос задать


Ответы (1 шт):

Автор решения: nörbörnën

Исходники демонстрационного приложения https://github.com/norbornen/electron-terminal-command-wrapper


Создать -приложение, которое запускает консольную утилиту, довольно просто!

  1. Берёшь любой скелет electron-приложения или делаешь его сам:

package.json

{
  "name": "electron-terminal-command-wrapper",
  "version": "1.0.0",
  "main": "src/main/index.js",
  "license": "MIT",
  "keywords": [
    "electron",
    "command line",
    "wrapper"
  ],
  "scripts": {
    "rebuild": "electron-rebuild",
    "start": "electron . --enable-logging",
    "dev": "NODE_ENV=development npm run start",

    "build": "echo 'dummy script'",
    "electron-build": "yarn build && electron-builder",
    "build:app": "yarn electron-build --dir",
    "build:all": "yarn electron-build build -mwl",
    "build:mac": "yarn electron-build build --mac",
    "build:linux": "yarn electron-build build --linux",
    "build:windows": "yarn electron-build build --windows --x64"
  },
  "dependencies": {
    "electron-is-dev": "^1.2.0",
    "execa": "^5.0.0"
  },
  "devDependencies": {
    "@types/electron": "^1.6.10",
    "@types/electron-is-dev": "^1.1.1",
    "@types/node": "^14.14.19",
    "electron": "^11.1.1",
    "electron-builder": "^22.9.1",
    "electron-rebuild": "^2.3.4",
    "eslint": "^7.17.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-plugin-import": "^2.22.1"
  },
  "build": {
    "appId": "org.dev.electron-terminal-command-wrapper",
    "productName": "etcmdwrapper",
    "artifactName": "${productName}.${ext}",
    "directories": {
      "output": "release"
    },
    "files": [
      "src/**/*"
    ],
    "win": {
      "target": [
        "portable"
      ]
    },
    "linux": {
      "target": [
        "deb",
        "AppImage"
      ],
      "category": "Development"
    }
  }
}
  1. Пишешь main-часть, пуcкач приложения:
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');

/** @type {BrowserWindow} */
let win;

function createMainWindow() {
  win = new BrowserWindow({
    width: 600,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
    }
  });

  const filepath = path.resolve(__dirname, '../render/index.html');
  win.loadURL(`file://${filepath}`);

  if (isDev) {
    win.webContents.openDevTools();
  }

  win.once('closed', () => {
    win = null;
  });
}

app.on('ready', () => {
  createMainWindow();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

  1. Пишешь render-компоненту, которая по ipc умеет общаться с main-ом:
<!DOCTYPE html>
<html lang="en">

<head>
  <title>Terminal Command Run Electron Wrapper</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script type="text/javascript">
    const { ipcRenderer } = require('electron');
    const CHANNEL_NAME = 'channelCmd';

    ipcRenderer.on(CHANNEL_NAME, (event, data) => {
      const el = document.getElementById('console');
      el.innerText = data;
    });

    function runIt(ev) {
      ev.preventDefault();

      const formEl = document.getElementById('exampleForm');
      const formData = new FormData(formEl);
      const command = (formData.get('command_command') || '').trim();
      const options = (formData.get('command_options') || '').trim();
      const argument = (formData.get('command_argument') || '').trim();

      if (/\S/.test(command)) {
        const data = { command };
        if (/\S/.test(options)) {
          data.options = options;
        }
        if (/\S/.test(argument)) {
          data.argument = argument;
        }
        
        ipcRenderer.send(CHANNEL_NAME, data);
      }
    }
  </script>
</head>

<body style="background: white">
  <h2>Terminal Command Run Electron Wrapper</h2>
  <div>
    <form id="exampleForm" onsubmit="return false">
      <div style="margin: 16px 0;"><input type="text" name="command_command" placeholder="command" style="padding: 5px;"></div>
      <div style="margin: 16px 0;"><input type="text" name="command_options" placeholder="command options" style="padding: 5px;"></div>
      <div style="margin: 16px 0;"><input type="text" name="command_argument" placeholder="command arguments" style="padding: 5px;"></div>
      <div style="margin: 16px 0;">
        <button onclick="runIt(event)">Run command</button>
        <button type="reset">Reset</button>
      </div>
    </form>
  </div>

  <hr>
  <pre id="console" style="padding: 10px; background-color: #eeeeee;"></pre>
</body>

</html>
  1. Теперь - самая главная часть, запуск команды.

Так как я не знаю ничего про gifski, я сделал пускач любой терминальной команды (для linux/macos):

const execa = require('execa');
...
app.on('ready', () => {
  createMainWindow();
  initIpc();
});
...
function initIpc() {
  ipcMain.on('channelCmd', async (event, args) => {
    const { command, options, argument } = typeof args === 'string' ? JSON.parse(args) : args;

    try {
      const { stdout, stderr } = await execa(command, [options, argument].filter((x) => typeof x === 'string' && /\S/.test(x)));
      console.log(stdout);
      console.log(stderr);
      event.reply('channelCmd', stdout || stderr);
    } catch (err) {
      console.log(err);
      event.reply('channelCmd', err.stack || err.message);
    }
  });
}

Можно было пойти через child_process.spawn или child_process.exec, но я взял пакет execa всё того же Sindre Sorhus (автора gifski (но не алгоритма конвертации)), чтобы упростить себе жизнь и не писать простыни обработчиков.

Тебе вообще нужно будет просто узнать как туда поставить gifski и подсунуть options и arguments для запуска.


Тяп-ляп и voilà введите сюда описание изображения

→ Ссылка