child_process#fork TypeScript Examples

The following examples show how to use child_process#fork. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.spec.ts    From volar with MIT License 6 votes vote down vote up
describe(`vue-tsc`, () => {
	it(`vue-tsc no errors`,  () => new Promise((resolve, reject) => {
		const cp = fork(
			binPath,
			['--noEmit'],
			{
				silent: true,
				cwd: path.resolve(__dirname, '../../vue-test-workspace')
			},
		);

		cp.stdout.setEncoding('utf8');
		cp.stdout.on('data', (data) => {
			console.log(data);
		});
		cp.stderr.setEncoding('utf8');
		cp.stderr.on('data', (data) => {
			console.error(data);
		});

		cp.on('exit', (code) => {
			if(code === 0) {
				resolve();
			} else {
				reject(new Error(`Exited with code ${code}`));
			}
		});
 	}), 40_000);
});
Example #2
Source File: index.ts    From double-agent with MIT License 6 votes vote down vote up
public listen(port: number, callback?: () => void) {
    this.port = port;
    this.listenCallback = callback;

    this.child = fork(`${__dirname}/child`, [], { stdio: ['ignore', 'inherit', 'pipe', 'ipc'] });
    this.child.stderr.setEncoding('utf8');

    this.child.on('error', err => {
      console.log('ERROR from tls child process', err);
      this.emit('error', err);
    });

    this.child.on('message', this.handleChildMessage.bind(this));
    this.child.stderr.on('data', this.handleOpenSslOutput.bind(this));

    this.child.send({
      start: { ...this.options, port },
    });
  }
Example #3
Source File: apps.ts    From disco-cube-daemon with MIT License 6 votes vote down vote up
forkApp = (name: string, jsFilePath: string, args: string[]): RunningApp => {
  const logger = log4js.getLogger(name);
  logger.debug(`starting..`);

  let proc: ChildProcess | undefined = undefined;
  try {
    proc = fork(jsFilePath, args, {
      stdio: "pipe",
    });

    if (!proc) throw new Error(`fork failed, no proc`);
    if (!proc.stdout) throw new Error(`fork failed, no proc.stdout`);
    if (!proc.stderr) throw new Error(`fork failed, no proc.stderr`);

    proc.stdout.on("data", (data) => {
      logger.debug(`stdout: ${data}`);
    });

    proc.stderr.on("data", (data) => {
      logger.error(`stderr: ${data}`);
    });

    proc.on("close", (code) => {
      logger.debug(`child process exited with code ${code}`);
    });
  } catch (e) {
    logger.error(`spawn error`, e);
  }

  return {
    send: (data: any) => {
      proc?.send(data);
    },
    stop: () => {
      logger.debug(`stopping..`);
      if (proc) kill(proc.pid);
    },
  };
}
Example #4
Source File: fork.ts    From cli with MIT License 6 votes vote down vote up
forkNode = (modulePath, args = [], options: any = {}) => {
  options.stdio = options.stdio || 'inherit';
  const proc: any = fork(modulePath, args, options);
  gracefull(proc);
  return new Promise((resolve, reject) => {
    proc.once('exit', (code: any) => {
      childs.delete(proc);
      if (code !== 0) {
        const err: any = new Error(
          modulePath + ' ' + args + ' exit with code ' + code
        );
        err.code = code;
        reject(err);
      } else {
        resolve(proc);
      }
    });
  });
}
Example #5
Source File: TSServer.ts    From typescript-strict-plugin with MIT License 6 votes vote down vote up
constructor() {
    this._responseEventEmitter = new EventEmitter();
    this._responseCommandEmitter = new EventEmitter();
    const tsserverPath = require.resolve('typescript/lib/tsserver');

    const server = fork(tsserverPath, {
      stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
      // env: { TSS_LOG: '-logToFile true -file ./ts.log -level verbose' }, // creates tsserver log from tests
    });
    this._exitPromise = new Promise((resolve, reject) => {
      server.on('exit', (code: string) => resolve(code));
      server.on('error', (reason: string) => reject(reason));
    });
    server.stdout?.setEncoding('utf-8');
    server.stdout?.on('data', (data: string) => {
      const [, , res] = data.split('\n');
      const obj = JSON.parse(res) as ServerResponse;
      if (obj.type === 'event') {
        this._responseEventEmitter.emit(obj.event, obj);
      } else if (obj.type === 'response') {
        this._responseCommandEmitter.emit(obj.command, obj);
      }
      this.responses.push(obj);
    });
    this._isClosed = false;
    this._server = server;
    this._seq = 0;
    this.responses = [];
  }
Example #6
Source File: baseManager.ts    From Cromwell with MIT License 6 votes vote down vote up
startService = async ({ path, name, args, dir, sync, command, watchName, onVersionChange }: {
    path: string;
    name: string;
    args?: string[];
    dir?: string;
    sync?: boolean;
    command?: string;
    watchName?: keyof TServiceVersions;
    onVersionChange?: () => Promise<void>;
}): Promise<ChildProcess> => {
    if (! await fs.pathExists(path)) {
        logger.error('Base manager::startService: could not find startup script at: ', path);
        throw new Error();
    }

    const child = fork(path, args ?? [], {
        stdio: sync ? 'inherit' : 'pipe',
        cwd: dir ?? process.cwd(),
    });
    await saveProcessPid(name, process.pid, child.pid);
    serviceProcesses[name] = child;

    if (getStoreItem('environment')?.mode === 'dev' || command === 'build')
        child?.stdout?.on('data', buff => console.log(buff?.toString?.() ?? buff)); // eslint-disable-line

    child?.stderr?.on('data', buff => console.error(buff?.toString?.() ?? buff));

    if (watchName && onVersionChange) {
        startWatchService(watchName, onVersionChange);
    }
    return child;
}
Example #7
Source File: malagu-main.ts    From malagu with MIT License 5 votes vote down vote up
async function execute() {
    const pkg = CommandUtil.getPkg();
    let projectPath = pkg.rootComponentPackage.malaguComponent?.projectPath;
    projectPath = projectPath ? resolve(process.cwd(), projectPath) : process.cwd();
    try {
        const malaguMainPath = require.resolve('@malagu/cli/lib/malagu-main', { paths: [ projectPath ] });
        if (dirname(malaguMainPath) !== __dirname) {
            const subprocess = fork(malaguMainPath, argv, { stdio: 'inherit', cwd: projectPath });
            subprocess.on('exit', exitListener);
            subprocess.on('error', () => process.exit(-1));
            return;
        }
    } catch (error) {
        if (error?.code !== 'MODULE_NOT_FOUND') {
            throw error;
        }
    }

    if (current?.killed === false) {
        current.removeListener('exit', exitListener);
        current.kill();
    }
    const { runtime, framework, settings } = await RuntimeUtil.initRuntime(projectPath);

    const nodePaths = Array.from(new Set<string>([
        join(projectPath, 'node_modules'),
        join(projectPath, '..', 'node_modules'),
        join(projectPath, '..', '..', 'node_modules'),
        join(projectPath, '..', '..', '..', 'node_modules')
    ]));

    process.env.MALAGU_RFS = JSON.stringify({ runtime, settings, framework });
    const runtimePath = PathUtil.getRuntimePath(runtime);
    if (runtimePath !== projectPath) {
        nodePaths.push(join(runtimePath, 'node_modules'));
    }
    process.env.NODE_PATH = nodePaths.join(delimiter);
    const malaguPath = require.resolve('@malagu/cli/lib/malagu', { paths: [ __dirname ] });
    current = fork(malaguPath, argv, { stdio: 'inherit', cwd: projectPath });
    // eslint-disable-next-line no-null/no-null
    current.on('exit', exitListener);
    current.on('error', () => process.exit(-1));
    current.on('message', (messageEvent: MessageEvent<Data>) => {
        if (messageEvent.type === 'cliContext') {
            const { components, webpackHookModules, configHookModules, buildHookModules,
                serveHookModules, deployHookModules, propsHookModules, infoHookModules } = messageEvent.data;
            const files = [
                ...(webpackHookModules || []).map(m => m.path),
                ...(configHookModules || []).map(m => m.path),
                ...(buildHookModules || []).map(m => m.path),
                ...(serveHookModules || []).map(m => m.path),
                ...(deployHookModules || []).map(m => m.path),
                ...(propsHookModules || []).map(m => m.path),
                ...(infoHookModules || []).map(m => m.path),
                ...(components || []).reduce<string[]>((prev, curr) => prev.concat(curr.configFiles), [])
            ];
            watchpack.watch({
                files: files.map(f => f?.split('/').join(sep)),
                aggregateTimeout: 1000
            });
            watchpack.on('aggregated', () => {
                execute();
            });
        }
    });
}
Example #8
Source File: cli.test.ts    From cardano-launcher with Apache License 2.0 5 votes vote down vote up
describe('CLI tests', () => {
  const killTest = (args: string[]) => async (): Promise<void> => {
    setupExecPath();
    const stateDir = (
      await tmp.dir({ unsafeCleanup: true, prefix: 'launcher-cli-test' })
    ).path;
    const proc = fork(
      path.resolve(__dirname, '..', '..', 'bin', 'cardano-launcher'),
      args.concat([stateDir]),
      {
        stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
        env: process.env,
      }
    );
    let nodePid: number | null = null;
    let walletPid: number | null = null;
    proc.on('message', (message: Message) => {
      testLogger.info('received message', message);
      if (message.node) {
        nodePid = message.node;
      }
      if (message.wallet) {
        walletPid = message.wallet;
      }
    });
    await delay(1000);
    expect(nodePid).not.toBeNull();
    expect(walletPid).not.toBeNull();
    proc.kill();
    await delay(1000);
    if (nodePid) {
      expectProcessToBeGone(nodePid, 9);
    }
    if (walletPid) {
      expectProcessToBeGone(walletPid, 9);
    }
  };

  it(
    'when the parent process is killed, cardano-node gets stopped - Shelley',
    killTest(['shelley', 'testnet', getShelleyConfigDir('testnet')])
  );
});
Example #9
Source File: withServerNoVM.spec.ts    From Assistive-Webdriver with MIT License 5 votes vote down vote up
describe("with server and no vm", () => {
  let subProcess: ChildProcess;

  beforeAll(async () => {
    const serverPath = require.resolve(
      "assistive-playwright-server/assistive-playwright-server"
    );
    subProcess = fork(serverPath, [
      "--http-port",
      "8885",
      "--tcp-port",
      "8886"
    ]);
    await waitPort({ port: 8885, timeout: 10000 });
  }, 30000);

  afterAll(async () => {
    subProcess.kill("SIGINT");
    await new Promise(done => subProcess.once("exit", done));
  });

  it("should create a page in chromium", async () => {
    const { chromium } = connectRemotePlaywright("http://localhost:8885");
    const instance = await chromium.launch();
    try {
      const context = await instance.newContext();
      try {
        const page = await context.newPage();
        try {
          const response = await page.goto("http://localhost:8885");
          const json = await response!.json();
          expect(json).toEqual({ server: "assistive-playwright-server" });
        } finally {
          await page.close();
        }
      } finally {
        await context.close();
      }
    } finally {
      await instance.close();
    }
  });

  it("should receive messages from the screen reader", async () => {
    const screenReader = await ScreenReaderClient.create(
      "ws://localhost:8885/screen-reader"
    );
    expect(screenReader.connected).toBe(true);
    const socketTCP = createConnection(8886, "127.0.0.1");
    await new Promise(done => socketTCP.on("connect", done));
    socketTCP.write("Hello world!\n");
    await wait(100);
    expect(await screenReader.waitForMessage(/Hello (\w+)!/)).toEqual([
      "Hello world!"
    ]);
    const nextMessage = screenReader.waitForMessage(/How are (\w+)?/);
    const listener = jest.fn();
    nextMessage.then(listener);
    await wait(100);
    expect(listener).not.toHaveBeenCalled();
    socketTCP.write("How are you?\n");
    expect(await nextMessage).toEqual(["How are you?"]);
    await new Promise<void>(done => socketTCP.end(done));
    screenReader.disconnect();
    await wait(100);
    expect(screenReader.connected).toBe(false);
  });
});
Example #10
Source File: process.ts    From DefinitelyTyped-tools with MIT License 5 votes vote down vote up
export function runWithChildProcesses<In>({
  inputs,
  commandLineArgs,
  workerFile,
  nProcesses,
  handleOutput,
}: RunWithChildProcessesOptions<In>): Promise<void> {
  return new Promise(async (resolve, reject) => {
    const nPerProcess = Math.floor(inputs.length / nProcesses);
    let processesLeft = nProcesses;
    let rejected = false;
    const allChildren: ChildProcess[] = [];
    for (let i = 0; i < nProcesses; i++) {
      const lo = nPerProcess * i;
      const hi = i === nProcesses - 1 ? inputs.length : lo + nPerProcess;
      let outputsLeft = hi - lo; // Expect one output per input
      if (outputsLeft === 0) {
        // No work for this process to do, so don't launch it
        processesLeft--;
        continue;
      }
      const child = fork(workerFile, commandLineArgs, {
        execArgv: await getChildProcessExecArgv(i),
      });
      allChildren.push(child);
      child.send(inputs.slice(lo, hi));
      child.on("message", (outputMessage) => {
        handleOutput(outputMessage as unknown);
        assert(outputsLeft > 0);
        outputsLeft--;
        if (outputsLeft === 0) {
          assert(processesLeft > 0);
          processesLeft--;
          if (processesLeft === 0) {
            resolve();
          }
          child.kill();
        }
      });
      child.on("disconnect", () => {
        if (outputsLeft !== 0) {
          fail(new Error(`disconnect with ${outputsLeft} outputs left`));
        }
      });
      child.on("close", () => {
        assert(rejected || outputsLeft === 0);
      });
      child.on("error", fail);
    }

    function fail(e: Error): void {
      rejected = true;
      for (const child of allChildren) {
        child.kill();
      }
      reject(e);
    }
  });
}
Example #11
Source File: index.ts    From cli with MIT License 5 votes vote down vote up
private async checkTsType() {
    const cwd = this.core.cwd;
    fork(require.resolve('../js/typeCheck'), [JSON.stringify({ cwd })], {
      cwd,
    });
  }
Example #12
Source File: server-manager.ts    From Cromwell with MIT License 5 votes vote down vote up
makeServer = async (init?: boolean): Promise<ServerInfo> => {
    const info: ServerInfo = {};
    const env = loadEnv();
    if (env.envMode === 'dev') logger.info('Proxy manager: Launching new API server...');
    const serverId = getRandStr(8);
    info.id = serverId;

    const buildPath = getServerBuildPath();
    if (!buildPath || !fs.existsSync(buildPath)) {
        const msg = 'Proxy manager: could not find server build at: ' + buildPath;
        logger.error(msg);
        throw new Error(msg);
    }

    const serverProc = fork(
        buildPath,
        [
            env.scriptName,
            activeServer.proxyPort ? `proxy-port=${activeServer.proxyPort}` : '',
            init ? '--init' : '',
        ],
        { stdio: 'inherit', cwd: process.cwd() }
    );

    parentRegisterChild(serverProc, info);
    info.childInst = serverProc;
    let hasReported = false;

    await new Promise(done => {
        setTimeout(() => {
            if (!hasReported) done(false);
        }, 90000);

        serverProc.on('message', (message) => {
            let msg;
            try {
                msg = JSON.parse(String(message));
            } catch (error) {
                logger.error(error);
            }

            if (msg.message === restartMessage) {
                restartServer();
            }
            if (msg.message === serverMessages.onStartMessage) {
                if (process.send) process.send(serverMessages.onStartMessage);
                info.port = msg.port
                hasReported = true;
                done(true);
            }

            if (msg.message === serverMessages.onStartErrorMessage) {
                if (process.send) process.send(serverMessages.onStartErrorMessage);
                hasReported = true;
                done(false);
            }
        });
    });

    if (!info.port) throw new Error('Proxy manager: Failed to start API server');
    try {
        await tcpPortUsed.waitUntilUsed(info.port, 500, 8000);
    } catch (error) {
        logger.error(error);
    }

    madeServers[serverId] = info;
    return info;
}
Example #13
Source File: wikiWorker.ts    From TidGi-Desktop with Mozilla Public License 2.0 5 votes vote down vote up
function executeZxScript(file: IZxFileInput, zxPath: string): Observable<IZxWorkerMessage> {
  /** this will be observed in src/services/native/index.ts */
  return new Observable<IZxWorkerMessage>((observer) => {
    observer.next({ type: 'control', actions: ZxWorkerControlActions.start });

    void (async function executeZxScriptIIFE() {
      try {
        let filePathToExecute = '';
        if ('fileName' in file) {
          const temporaryDirectory = await mkdtemp(`${tmpdir()}${path.sep}`);
          filePathToExecute = path.join(temporaryDirectory, file.fileName);
          await writeFile(filePathToExecute, file.fileContent);
        } else if ('filePath' in file) {
          filePathToExecute = file.filePath;
        }
        const execution = fork(zxPath, [filePathToExecute], { silent: true });

        execution.on('close', function (code) {
          observer.next({ type: 'control', actions: ZxWorkerControlActions.ended, message: `child process exited with code ${String(code)}` });
        });
        execution.stdout?.on('data', (stdout: Buffer) => {
          // if there are multiple console.log, their output will be concatenated into this stdout. And some of them are not intended to be executed. We use TW_SCRIPT_SEPARATOR to allow user determine the range they want to execute in the $tw context.
          const message = String(stdout);
          const zxConsoleLogMessages = extractTWContextScripts(message);
          // log and execute each different console.log result.
          zxConsoleLogMessages.forEach(({ messageType, content }) => {
            if (messageType === 'script') {
              observer.next({ type: 'execution', message: content });
              if (wikiInstance === undefined) {
                observer.next({ type: 'stderr', message: `Error in executeZxScript(): $tw is undefined` });
              } else {
                const context = getTWVmContext(wikiInstance);
                const twExecutionResult = executeScriptInTWContext(content, context);
                observer.next({ type: 'stdout', message: twExecutionResult.join('\n\n') });
              }
            } else {
              observer.next({ type: 'stdout', message: content });
            }
          });
        });
        execution.stderr?.on('data', (stdout: Buffer) => {
          observer.next({ type: 'stderr', message: String(stdout) });
        });
        execution.on('error', (error) => {
          observer.next({ type: 'stderr', message: `${error.message} ${error.stack ?? ''}` });
        });
      } catch (error) {
        const message = `zx script's executeZxScriptIIFE() failed with error ${(error as Error).message} ${(error as Error).stack ?? ''}`;
        observer.next({ type: 'control', actions: ZxWorkerControlActions.error, message });
      }
    })();
  });
}
Example #14
Source File: rollupPlugin.ts    From elderjs with MIT License 4 votes vote down vote up
devServer = ({
  elderConfig,
  forceStart = false,
}: {
  elderConfig: SettingsOptions;
  forceStart: boolean;
}) => {
  /**
   * Dev server bootstrapping and restarting.
   */
  let childProcess: ChildProcess;
  let bootingServer = false;

  function startOrRestartServer(count = 0) {
    if (!isDev && !forceStart) return;
    if (!bootingServer) {
      bootingServer = true;

      const serverJs = path.resolve(process.cwd(), elderConfig.srcDir, './server.js');

      if (!fs.existsSync(serverJs)) {
        console.error(`No server file found at ${serverJs}, unable to start dev server.`);
        return;
      }

      setTimeout(() => {
        // prevent multiple calls
        if (childProcess) childProcess.kill('SIGINT');
        bootingServer = false;
        childProcess = fork(serverJs);
        childProcess.on('exit', (code) => {
          if (code !== null) {
            console.log(`> Elder.js process exited with code ${code}`);
          }
        });
        childProcess.on('error', (err) => {
          console.error(err);
          if (count < 1) {
            startOrRestartServer(count + 1);
          }
        });
      }, 10);
    }
  }

  function handleChange(watchedPath) {
    const parsed = path.parse(watchedPath);
    if (parsed.ext !== '.svelte') {
      // prevents double reload as the compiled svelte templates are output
      startOrRestartServer();
    }
  }

  function startWatcher() {
    // notes: This is hard to reason about.
    // This should only after the initial client rollup as finished as it runs last. The srcWatcher should then live between reloads
    // until the watch process is killed.
    //
    // this should watch the ./src, elder.config.js, and the client side folders... trigging a restart of the server when something changes
    // We don't want to change when a svelte file changes because it will cause a double reload when rollup outputs the rebundled file.

    if ((isDev || forceStart) && !srcWatcher) {
      srcWatcher = chokidar.watch(
        [
          path.resolve(process.cwd(), './src'),
          path.resolve(process.cwd(), './elder.config.js'),
          path.join(elderConfig.$$internal.distElder, 'assets', sep),
          path.join(elderConfig.$$internal.distElder, 'svelte', sep),
          path.join(elderConfig.$$internal.ssrComponents, 'components', sep),
          path.join(elderConfig.$$internal.ssrComponents, 'layouts', sep),
          path.join(elderConfig.$$internal.ssrComponents, 'routes', sep),
        ],
        {
          ignored: '*.svelte',
          usePolling: !/^(win32|darwin)$/.test(process.platform),
        },
      );

      srcWatcher.on('change', (watchedPath) => handleChange(watchedPath));
      srcWatcher.on('add', (watchedPath) => handleChange(watchedPath));
    }
  }
  return {
    startWatcher,
    childProcess,
    startOrRestartServer,
  };
}
Example #15
Source File: process.ts    From DefinitelyTyped-tools with MIT License 4 votes vote down vote up
export function runWithListeningChildProcesses<In extends Serializable>({
  inputs,
  commandLineArgs,
  workerFile,
  nProcesses,
  cwd,
  handleOutput,
  crashRecovery,
  crashRecoveryMaxOldSpaceSize = DEFAULT_CRASH_RECOVERY_MAX_OLD_SPACE_SIZE,
  handleStart,
  handleCrash,
  softTimeoutMs = Infinity,
}: RunWithListeningChildProcessesOptions<In>): Promise<void> {
  return new Promise(async (resolve, reject) => {
    let inputIndex = 0;
    let processesLeft = nProcesses;
    let rejected = false;
    const runningChildren = new Set<ChildProcess>();
    const maxOldSpaceSize = getMaxOldSpaceSize(process.execArgv) || 0;
    const startTime = Date.now();
    for (let i = 0; i < nProcesses; i++) {
      if (inputIndex === inputs.length) {
        processesLeft--;
        continue;
      }

      const processIndex = nProcesses > 1 ? i + 1 : undefined;
      let child: ChildProcess;
      let crashRecoveryState = CrashRecoveryState.Normal;
      let currentInput: In;

      const onMessage = (outputMessage: unknown) => {
        try {
          const oldCrashRecoveryState = crashRecoveryState;
          crashRecoveryState = CrashRecoveryState.Normal;
          handleOutput(outputMessage as {}, processIndex);
          if (inputIndex === inputs.length || Date.now() - startTime > softTimeoutMs) {
            stopChild(/*done*/ true);
          } else {
            if (oldCrashRecoveryState !== CrashRecoveryState.Normal) {
              // retry attempt succeeded, restart the child for further tests.
              console.log(`${processIndex}> Restarting...`);
              restartChild(nextTask, process.execArgv);
            } else {
              nextTask();
            }
          }
        } catch (e) {
          onError(e as Error);
        }
      };

      const onClose = async () => {
        if (rejected || !runningChildren.has(child)) {
          return;
        }

        try {
          // treat any unhandled closures of the child as a crash
          if (crashRecovery) {
            switch (crashRecoveryState) {
              case CrashRecoveryState.Normal:
                crashRecoveryState = CrashRecoveryState.Retry;
                break;
              case CrashRecoveryState.Retry:
                // skip crash recovery if we're already passing a value for --max_old_space_size that
                // is >= crashRecoveryMaxOldSpaceSize
                crashRecoveryState =
                  maxOldSpaceSize < crashRecoveryMaxOldSpaceSize
                    ? CrashRecoveryState.RetryWithMoreMemory
                    : (crashRecoveryState = CrashRecoveryState.Crashed);
                break;
              default:
                crashRecoveryState = CrashRecoveryState.Crashed;
            }
          } else {
            crashRecoveryState = CrashRecoveryState.Crashed;
          }

          if (handleCrash) {
            handleCrash(currentInput, crashRecoveryState, processIndex);
          }

          switch (crashRecoveryState) {
            case CrashRecoveryState.Retry:
              await restartChild(resumeTask, process.execArgv);
              break;
            case CrashRecoveryState.RetryWithMoreMemory:
              await restartChild(resumeTask, [
                ...getExecArgvWithoutMaxOldSpaceSize(),
                `--max_old_space_size=${crashRecoveryMaxOldSpaceSize}`,
              ]);
              break;
            case CrashRecoveryState.Crashed:
              crashRecoveryState = CrashRecoveryState.Normal;
              if (inputIndex === inputs.length || Date.now() - startTime > softTimeoutMs) {
                stopChild(/*done*/ true);
              } else {
                await restartChild(nextTask, process.execArgv);
              }
              break;
            default:
              assert.fail(`${processIndex}> Unexpected crashRecoveryState: ${crashRecoveryState}`);
          }
        } catch (e) {
          onError(e as Error);
        }
      };

      const onError = (err?: Error) => {
        child.removeAllListeners();
        runningChildren.delete(child);
        fail(err);
      };

      const startChild = async (taskAction: () => void, execArgv: string[]) => {
        try {
          child = fork(workerFile, commandLineArgs, { cwd, execArgv: await getChildProcessExecArgv(i, execArgv) });
          runningChildren.add(child);
        } catch (e) {
          fail(e as Error);
          return;
        }

        try {
          let closed = false;
          const thisChild = child;
          const onChildClosed = () => {
            // Don't invoke `onClose` more than once for a single child.
            if (!closed && child === thisChild) {
              closed = true;
              onClose();
            }
          };
          const onChildDisconnectedOrExited = () => {
            if (!closed && thisChild === child) {
              // Invoke `onClose` after enough time has elapsed to allow `close` to be triggered.
              // This is to ensure our `onClose` logic gets called in some conditions
              const timeout = 1000;
              setTimeout(onChildClosed, timeout);
            }
          };
          child.on("message", onMessage);
          child.on("close", onChildClosed);
          child.on("disconnect", onChildDisconnectedOrExited);
          child.on("exit", onChildDisconnectedOrExited);
          child.on("error", onError);
          taskAction();
        } catch (e) {
          onError(e as Error);
        }
      };

      const stopChild = (done: boolean) => {
        try {
          assert(runningChildren.has(child), `${processIndex}> Child not running`);
          if (done) {
            processesLeft--;
            if (processesLeft === 0) {
              resolve();
            }
          }
          runningChildren.delete(child);
          child.removeAllListeners();
          child.kill();
        } catch (e) {
          onError(e as Error);
        }
      };

      const restartChild = async (taskAction: () => void, execArgv: string[]) => {
        try {
          assert(runningChildren.has(child), `${processIndex}> Child not running`);
          console.log(`${processIndex}> Restarting...`);
          stopChild(/*done*/ false);
          await startChild(taskAction, execArgv);
        } catch (e) {
          onError(e as Error);
        }
      };

      const resumeTask = () => {
        try {
          assert(runningChildren.has(child), `${processIndex}> Child not running`);
          child.send(currentInput);
        } catch (e) {
          onError(e as Error);
        }
      };

      const nextTask = () => {
        try {
          assert(runningChildren.has(child), `${processIndex}> Child not running`);
          currentInput = inputs[inputIndex];
          inputIndex++;
          if (handleStart) {
            handleStart(currentInput, processIndex);
          }
          child.send(currentInput);
        } catch (e) {
          onError(e as Error);
        }
      };

      await startChild(nextTask, process.execArgv);
    }

    function fail(err?: Error): void {
      if (!rejected) {
        rejected = true;
        for (const child of runningChildren) {
          try {
            child.removeAllListeners();
            child.kill();
          } catch {
            // do nothing
          }
        }
        const message = err ? `: ${err.message}` : "";
        reject(new Error(`Something went wrong in ${runWithListeningChildProcesses.name}${message}`));
      }
    }
  });
}
Example #16
Source File: index.ts    From cli with MIT License 4 votes vote down vote up
private start() {
    return new Promise<void>(async resolve => {
      const options = this.getOptions();
      this.spin = new Spin({
        text: this.started ? 'Midway Restarting' : 'Midway Starting',
      });
      if (!options.silent) {
        this.spin.start();
      }

      let tsNodeFast = {};
      if (options.fast) {
        tsNodeFast = {
          TS_NODE_FILES: 'true',
          TS_NODE_TRANSPILE_ONLY: 'true',
        };
      }

      let execArgv = [];
      let MIDWAY_DEV_IS_DEBUG;

      if (options.ts) {
        let fastRegister;
        if (typeof options.fast === 'string' && options.fast !== 'true') {
          const pluginName = `@midwayjs/cli-plugin-${options.fast}`;
          this.core.debug('faster pluginName', pluginName);
          try {
            const pkg = require.resolve(`${pluginName}/package.json`);
            fastRegister = join(pkg, `../js/${options.fast}-register.js`);
            this.core.debug('fastRegister', fastRegister);
            if (!existsSync(fastRegister)) {
              fastRegister = '';
            }
          } catch {
            throw new Error(
              `please install @midwayjs/cli-plugin-${options.fast} to using fast mode '${options.fast}'`
            );
          }
          if (fastRegister) {
            execArgv = ['-r', fastRegister];
          }
        }

        if (!fastRegister) {
          execArgv = ['-r', 'ts-node/register'];
          if (this.tsconfigJson?.compilerOptions?.baseUrl) {
            execArgv.push('-r', 'tsconfig-paths/register');
          }
        }
      }

      if (options.debug) {
        const debugStr = options.debug.toString();
        const port = /^\d+$/.test(debugStr) ? options.debug : '9229';
        this.core.debug('Debug port:', port);
        const portIsUse: boolean = await checkPort(port);
        if (portIsUse) {
          console.log(`\n\nDebug port ${port} is in use\n\n`);
        } else {
          MIDWAY_DEV_IS_DEBUG = port;
          execArgv.push(`--inspect=${port}`);
        }
      }

      this.child = fork(require.resolve('./child'), [JSON.stringify(options)], {
        cwd: this.core.cwd,
        env: {
          IN_CHILD_PROCESS: 'true',
          TS_NODE_FILES: 'true',
          MIDWAY_DEV_IS_DEBUG,
          ...tsNodeFast,
          ...process.env,
        },
        silent: true,
        execArgv,
      });

      if (this.options.ts && this.options.fast === 'swc') {
        this.checkTsType();
      }

      const dataCache = [];
      this.child.stdout.on('data', data => {
        if (this.restarting) {
          dataCache.push(data);
        } else {
          process.stdout.write(data);
        }
      });
      this.child.stderr.on('data', data => {
        this.error(data.toString());
      });
      this.child.on('message', msg => {
        if (msg.type === 'started') {
          this.childProcesslistenedPort = msg.port;

          this.spin.stop();
          while (dataCache.length) {
            process.stdout.write(dataCache.shift());
          }
          this.restarting = false;
          if (msg.startSuccess) {
            if (!this.started) {
              this.started = true;
              this.displayStartTips(options);
            }
          }
          resolve();
        } else if (msg.type === 'error') {
          this.spin.stop();
          this.error(msg.message || '');
        } else if (msg.id) {
          if (this.processMessageMap[msg.id]) {
            this.processMessageMap[msg.id](msg.data);
            delete this.processMessageMap[msg.id];
          }
        }
      });
    });
  }
Example #17
Source File: e2eWithoutVM.spec.ts    From Assistive-Webdriver with MIT License 4 votes vote down vote up
describe("e2e without VM", () => {
  let subProcess: ChildProcess;

  beforeAll(async () => {
    subProcess = fork(resolve(__dirname, "../assistive-playwright-server"), [
      "--http-port",
      "8883",
      "--tcp-port",
      "8884"
    ]);
    await waitPort({ port: 8883, timeout: 10000 });
  }, 30000);
  afterAll(async () => {
    subProcess.kill("SIGINT");
    await new Promise(done => subProcess.once("exit", done));
  });

  it("should accept /screen-reader websocket connections", async () => {
    const socketWeb = new Websocket("ws://127.0.0.1:8883/screen-reader");
    await new Promise(
      (done, error) => ((socketWeb.onopen = done), (socketWeb.onerror = error))
    );
    const socketTCP = createConnection(8884, "127.0.0.1");
    await new Promise(done => socketTCP.on("connect", done));
    const messages: string[] = [];
    let callback: (e: void) => void;
    socketWeb.onmessage = arg => {
      messages.push(arg.data.toString("utf8"));
      if (callback) callback();
    };
    const waitForMessage = async () => {
      if (messages.length === 0) {
        await new Promise(done => (callback = done));
      }
      return messages.shift();
    };
    await new Promise(done =>
      socketTCP.write("something\nsecond line\nbegin", done)
    );
    expect(await waitForMessage()).toEqual("something");
    expect(await waitForMessage()).toEqual("second line");
    await new Promise(done => socketTCP.write(" end\n", done));
    expect(await waitForMessage()).toEqual("begin end");
    await new Promise(done => socketTCP.write("other text\n", done));
    expect(await waitForMessage()).toEqual("other text");
    await new Promise<void>(done => socketTCP.end(done));
    socketWeb.close();
    await new Promise(done => (socketWeb.onclose = done));
  });

  it("should not accept cross-origin websocket connections", async () => {
    const socketWeb = new Websocket("ws://127.0.0.1:8883/screen-reader", {
      origin: "http://example.com"
    });
    await expect(
      new Promise(
        (done, error) => (
          (socketWeb.onopen = done), (socketWeb.onerror = error)
        )
      )
    ).rejects.toBeTruthy();
  });

  it("should not accept cross-origin POST requests", async () => {
    const request = await fetch("http://127.0.0.1:8883/browser", {
      method: "POST",
      headers: {
        Origin: "http://example.com"
      },
      body: JSON.stringify({ options: {}, browser: "chromium" })
    });
    expect(request.status).toBe(403);
    expect(request.ok).toBeFalsy();
  });

  it("should not accept cross-origin DELETE requests", async () => {
    const request = await fetch(
      "http://127.0.0.1:8883/browser/1234567890123456789012345678901234567890123456789012345678901234",
      {
        method: "DELETE",
        headers: {
          Origin: "http://example.com"
        }
      }
    );
    expect(request.status).toBe(403);
    expect(request.ok).toBeFalsy();
  });

  it("should return 404 for DELETE requests not matching an instance", async () => {
    const request = await fetch(
      "http://127.0.0.1:8883/browser/1234567890123456789012345678901234567890123456789012345678901234",
      {
        method: "DELETE"
      }
    );
    expect(request.status).toBe(404);
    expect(request.ok).toBeFalsy();
  });

  it("should accept POST and DELETE requests to create and delete browser instances", async () => {
    const postRequest = await fetch("http://127.0.0.1:8883/browser", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ options: {}, browser: "chromium" })
    });
    expect(postRequest.ok).toBeTruthy();
    const { id } = (await postRequest.json()) as any;
    expect(id).toMatch(/^[0-9a-f]{64}$/i);
    const deleteRequest = await fetch(`http://127.0.0.1:8883/browser/${id}`, {
      method: "DELETE"
    });
    expect(deleteRequest.status).toBe(200);
    expect(deleteRequest.ok).toBeTruthy();
  }, 30000);

  const checkInvalid = async (object: any, error: string) => {
    const postRequest = await fetch("http://127.0.0.1:8883/browser", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(object)
    });
    expect(postRequest.ok).toBeFalsy();
    expect(postRequest.status).toBe(400);
    const json = await postRequest.text();
    expect(json).toContain(error);
  };

  it("should validate POST requests to create a browser instances", async () => {
    await checkInvalid({}, "must have required property 'browser'");
    await checkInvalid(
      { browser: "unknown", options: {} },
      "must be equal to one of the allowed values"
    );
    await checkInvalid(
      { browser: "firefox", options: {}, extraOption: {} },
      "must NOT have additional properties"
    );
    await checkInvalid(
      { browser: "firefox", options: { invalidExtraProperty: "ok" } },
      "must NOT have additional properties"
    );
  });
});