Как программно запустить / остановить Metro Bundler - PullRequest
0 голосов
/ 11 октября 2018

Я пытаюсь настроить Непрерывную интеграцию для проекта React-Native и столкнуться с некоторыми проблемами при сквозном тестировании, особенно в отношении пакета Metro.

Кажется, что используется сценарий реагироватьв этом случае ненадежно:

  • Сборка iOS самопроизвольно порождает упаковщик в новом терминале и оставляет его работающим после сборки.
  • Сборка Android опирается на работающий экземпляр, которыйнеобходимо предварительно запустить вручную.
  • Пакет не может быть остановлен с помощью других средств, кроме сигнализации (Ctrl + C или kill).
  • Нет синхронизации со сборкой, чтобы гарантировать, что пакетготов к обработке при запуске приложения.

Я хотел бы написать собственный сценарий, который может запускать Metro, запускать тесты, когда сервер будет готов, и, наконец, остановить сервер для очистки среды.

1 Ответ

0 голосов
/ 15 октября 2018

Метро-компоновщик должен работать как отдельный процесс, чтобы иметь возможность обслуживать запросы.Это можно сделать с помощью Дочерний процесс: Spawn и обеспечить правильную очистку возвращаемого объекта.

Вот базовый сценарий, который одновременно запускает и Metro, и Gradle и ждет, пока обаготов на основе их записи журнала.

'use strict';

const cp = require('child_process');
const fs = require('fs');
const readline = require('readline');

// List of sub processes kept for proper cleanup
const children = {};

async function asyncPoint(ms, callback = () => {}) {
  return await new Promise(resolve => setTimeout(() => {
    resolve(callback());
  }, ms));
}

async function fork(name, cmd, args, {readyRegex, timeout} = {}) {

  return new Promise((resolve) => {

    const close = () => {
      delete children[name];
      resolve(false);
    };

    if(timeout) {
      setTimeout(() => close, timeout);
    }

    const child = cp.spawn(
      cmd,
      args,
      {
        silent: false,
        stdio: [null, 'pipe', 'pipe'],
      },
    );

    child.on('close', close);
    child.on('exit', close);
    child.on('error', close);

    const output = fs.createWriteStream(`./volatile-build-${name}.log`);

    const lineCb = (line) => {
      console.log(`[${name}] ${line}`);
      output.write(line+'\n');
      if (readyRegex && line.match(readyRegex)) {
        resolve(true);
      }
    };

    readline.createInterface({
      input: child.stdout,
    }).on('line', lineCb);

    readline.createInterface({
      input: child.stderr,
    }).on('line', lineCb);

    children[name] = child;
  });
}

async function sighandle() {
  console.log('\nClosing...');
  Object.values(children).forEach(child => child.kill('SIGTERM'));
  await asyncPoint(1000);
  process.exit(0);
}

function setSigHandler() {
  process.on('SIGINT', sighandle);
  process.on('SIGTERM', sighandle);
}

async function main() {

  setSigHandler();

  // Metro Bundler
  const metroSync = fork(
    'metro',
    process.argv0,
    [ // args
      './node_modules/react-native/local-cli/cli.js', 
      'start',
    ],
    { // options
      readyRegex: /Loading dependency graph, done./,
      timeout: 60000,
    }
  );

  // Build APK
  const buildSync = fork(
    'gradle',
    './android/gradlew', 
    [ // args
      `--project-dir=${__dirname}/android`,
      'assembleDebug',
    ],
    { // options
      readyRegex: /BUILD SUCCESSFUL/,
      timeout: 300000,
    }
  );

  if (await metroSync && await buildSync) {

    // TODO: Run tests here

  }

  sighandle();
}

main();
...