Code coverage report for src/server/PHPServer.ts

# All files / src/server PHPServer.ts

85.29% Statements 29/34
75% Branches 30/40
85.71% Functions 12/14
85.29% Lines 29/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    1171x
    1x

    1x

    12x

    36x

    13x

    12x

    12x

    12x

    5x
    5x

    19x

    13x

    13x

    13x
    1x

    12x

    12x

    12x

    12x

    12x
    36x

    36x
    12x

    12x

    24x
    12x
    1x

    12x

     import { spawn, ChildProcess, spawnSync } from 'child_process';
    import Messages from '../Messages';

    type ServerEvent = 'data' | 'error' | 'close';

    type ServerEventHandler = (data: string) => void;

    export interface PHPServerConfig {
      host: string;
      port: number;
      rootPath: string;
      relativePath: string;
      phpExecutablePath: string;
      phpRouterPath?: string;
      phpConfigPath?: string;
    }

    export default class PHPServer {
      private process?: ChildProcess;
      private listeners: Array<[ServerEvent, ServerEventHandler]> = [];
      private config?: PHPServerConfig;

      constructor() {}

      on(event: ServerEvent, handler: ServerEventHandler): void {
        this.listeners.push([event, handler]);
      }

      start(config: PHPServerConfig): void {
        this.throwIfPHPIsNotAvailable(config.phpExecutablePath, config.rootPath);

        this.config = config;

        this.process = spawn(
          config.phpExecutablePath,
          this.getProcessArgs(config),
          {
            cwd: config.rootPath,
          }
        );

        this.setupProcessListeners();
      }

      stop(): void {
        this.process?.kill();
        this.process = undefined;
      }

      isRunning() {
        return !!this.process;
      }

      getCurrentConfig(): PHPServerConfig | undefined {
        return this.config;
      }

      private throwIfPHPIsNotAvailable(
        phpExecutablePath: string,
        rootPath: string
      ): void {
        const { status } = spawnSync(phpExecutablePath, ['--version'], {
          cwd: rootPath,
        });

        if (status !== 0) {
          throw new Error(Messages.PHP_NOT_FOUND);
        }
      }

      private getProcessArgs(config: PHPServerConfig): string[] {
        const args: string[] = [
          '-S',
          `${config.host}:${config.port}`,
          '-t',
          config.relativePath,
        ];

        Iif (config.phpConfigPath) {
          args.push('-c');
          args.push(config.phpConfigPath);
        }

        Iif (config.phpRouterPath) {
          args.push(config.phpRouterPath);
        }

        return args;
      }

      private setupProcessListeners() {
        this.listeners.forEach(([event, eventHandler]) => {
          this.setupSingleProcessListener(event, eventHandler);
        });
      }

      private setupSingleProcessListener(
        event: ServerEvent,
        eventHandler: ServerEventHandler
      ) {
        if (event === 'data') {
          this.process?.stdout?.on(event, (data: Buffer): void => {
            eventHandler(data.toString());
          });
          this.process?.stderr?.on(event, (data: Buffer): void => {
            eventHandler(data.toString());
          });
        } else if (event === 'error') {
          this.process?.on(event, (error: Error) => {
            eventHandler(error.stack || error.message);
          });
        } else {
          this.process?.on(event, eventHandler);
        }
      }
    }