"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

function _getenv() {
  const data = _interopRequireDefault(require("getenv"));

  _getenv = function () {
    return data;
  };

  return data;
}

function ProjectUtils() {
  const data = _interopRequireWildcard(require("../project/ProjectUtils"));

  ProjectUtils = function () {
    return data;
  };

  return data;
}

function _Logger() {
  const data = _interopRequireDefault(require("../Logger"));

  _Logger = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

class PackagerLogsStream {
  constructor({
    projectRoot,
    getCurrentOpenProjectId,
    updateLogs,
    onStartBuildBundle,
    onProgressBuildBundle,
    onFinishBuildBundle,
    getSnippetForError
  }) {
    _defineProperty(this, "_projectRoot", void 0);

    _defineProperty(this, "_getCurrentOpenProjectId", void 0);

    _defineProperty(this, "_updateLogs", void 0);

    _defineProperty(this, "_logsToAdd", []);

    _defineProperty(this, "_bundleBuildChunkID", null);

    _defineProperty(this, "_onStartBuildBundle", void 0);

    _defineProperty(this, "_onProgressBuildBundle", void 0);

    _defineProperty(this, "_onFinishBuildBundle", void 0);

    _defineProperty(this, "_bundleBuildStart", null);

    _defineProperty(this, "_getSnippetForError", void 0);

    _defineProperty(this, "_handleBundleTransformEvent", chunk => {
      const msg = chunk.msg;

      if (msg.type === 'bundle_build_started') {
        chunk._metroEventType = 'BUILD_STARTED';

        this._handleNewBundleTransformStarted(chunk);
      } else if (msg.type === 'bundle_transform_progressed') {
        chunk._metroEventType = 'BUILD_PROGRESS';

        if (this._bundleBuildChunkID) {
          this._handleUpdateBundleTransformProgress(chunk);
        } else {
          this._handleNewBundleTransformStarted(chunk);
        }
      } else if (msg.type === 'bundle_build_failed') {
        chunk._metroEventType = 'BUILD_FAILED';

        if (!this._bundleBuildChunkID) {// maybe?
        } else {
          this._handleUpdateBundleTransformProgress(chunk);
        }
      } else if (msg.type === 'bundle_build_done') {
        chunk._metroEventType = 'BUILD_DONE';

        if (!this._bundleBuildChunkID) {// maybe?
        } else {
          this._handleUpdateBundleTransformProgress(chunk);
        }
      }
    });

    _defineProperty(this, "_enqueueFlushLogsToAdd", () => {
      this._updateLogs(logs => {
        if (this._logsToAdd.length === 0) {
          return logs;
        }

        let nextLogs = logs.concat(this._logsToAdd);
        this._logsToAdd = [];
        return nextLogs;
      });
    });

    _defineProperty(this, "_cleanUpNodeErrors", chunk => {
      if (typeof chunk.msg === 'object') {
        return chunk;
      }

      if (chunk.msg.match(/\(node:.\d*\)/)) {
        // Example: (node:13817) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): SyntaxError: SyntaxError /Users/brent/universe/apps/new-project-template/main.js: Unexpected token (10:6)
        // The first part of this is totally useless, so let's remove it.
        if (chunk.msg.match(/UnhandledPromiseRejectionWarning/)) {
          chunk.msg = chunk.msg.replace(/\(node:.*\(rejection .*\):/, '');

          if (chunk.msg.match(/SyntaxError: SyntaxError/)) {
            chunk.msg = chunk.msg.replace('SyntaxError: ', '');
          }
        } else if (chunk.msg.match(/DeprecationWarning/)) {
          chunk.msg = '';
        }
      }

      return chunk;
    });

    this._projectRoot = projectRoot;

    this._getCurrentOpenProjectId = getCurrentOpenProjectId || (() => 1);

    this._updateLogs = updateLogs; // Optional properties in case the consumer wants to handle updates on
    // its own, eg: for a progress bar

    this._onStartBuildBundle = onStartBuildBundle;
    this._onProgressBuildBundle = onProgressBuildBundle;
    this._onFinishBuildBundle = onFinishBuildBundle; // Optional function for creating custom code frame snippet
    // (e.g. with terminal colors) from a syntax error.

    this._getSnippetForError = getSnippetForError;

    this._attachLoggerStream();
  }

  _attachLoggerStream() {
    let projectId = this._getCurrentOpenProjectId();

    ProjectUtils().attachLoggerStream(this._projectRoot, {
      stream: {
        write: chunk => {
          if (chunk.tag !== 'metro' && chunk.tag !== 'expo') {
            return;
          } else if (this._getCurrentOpenProjectId() !== projectId) {
            // TODO: We should be confident that we are properly unsubscribing
            // from the stream rather than doing a defensive check like this.
            return;
          }

          chunk = this._maybeParseMsgJSON(chunk);
          chunk = this._cleanUpNodeErrors(chunk);

          if (chunk.tag === 'metro') {
            this._handleMetroEvent(chunk);
          } else if (typeof chunk.msg === 'string' && chunk.msg.match(/\w/) && chunk.msg[0] !== '{') {
            this._enqueueAppendLogChunk(chunk);
          }
        }
      },
      type: 'raw'
    });
  }

  _handleMetroEvent(originalChunk) {
    const chunk = { ...originalChunk
    };
    let {
      msg
    } = chunk;

    if (typeof msg === 'string') {
      if (msg.includes('HTTP/1.1') && !_getenv().default.boolish('EXPO_DEBUG', false)) {// Do nothing with this message - we want to filter out network requests logged by Metro.
      } else {
        // If Metro crashes for some reason, it may log an error message as a plain string to stderr.
        this._enqueueAppendLogChunk(chunk);
      }

      return;
    }

    switch (msg.type) {
      // Bundle transform events
      case 'bundle_build_started':
      case 'bundle_transform_progressed':
      case 'bundle_build_failed':
      case 'bundle_build_done':
        this._handleBundleTransformEvent(chunk);

        return;

      case 'initialize_started':
        chunk._metroEventType = 'METRO_INITIALIZE_STARTED';
        chunk.msg = msg.port ? `Starting Metro Bundler on port ${msg.port}.` : 'Starting Metro Bundler.';
        break;

      case 'initialize_done':
        chunk.msg = `Metro Bundler ready.`;
        break;

      case 'initialize_failed':
        {
          // SDK <=22
          let code = msg.error.code;
          chunk.msg = code === 'EADDRINUSE' ? `Metro Bundler can't listen on port ${msg.port}. The port is in use.` : `Metro Bundler failed to start. (code: ${code})`;
          break;
        }

      case 'bundling_error':
        chunk.msg = this._formatModuleResolutionError(msg.error) || this._formatBundlingError(msg.error) || msg;
        chunk.level = _Logger().default.ERROR;
        break;

      case 'transform_cache_reset':
        chunk.msg = 'Your JavaScript transform cache is empty, rebuilding (this may take a minute).';
        break;

      case 'hmr_client_error':
        chunk.msg = `A WebSocket client got a connection error. Please reload your device to get HMR working again.`;
        break;

      case 'global_cache_disabled':
        if (msg.reason === 'too_many_errors') {
          chunk.msg = 'The global cache is now disabled because it has been failing too many times.';
        } else if (msg.reason === 'too_many_misses') {
          chunk.msg = `The global cache is now disabled because it has been missing too many consecutive keys.`;
        } else {
          chunk.msg = `The global cache is now disabled. Reason: ${msg.reason}`;
        }

        break;

      case 'worker_stdout_chunk':
        chunk.msg = this._formatWorkerChunk('stdout', msg.chunk);
        break;

      case 'worker_stderr_chunk':
        chunk.msg = this._formatWorkerChunk('stderr', msg.chunk);
        break;
      // Ignored events.

      case 'dep_graph_loading':
      case 'dep_graph_loaded':
      case 'global_cache_error':
        return;

      default:
        chunk.msg = `Unrecognized event: ${JSON.stringify(msg)}`;
        break;
    }

    this._enqueueAppendLogChunk(chunk);
  } // Any event related to bundle building is handled here


  _handleNewBundleTransformStarted(chunk) {
    if (this._bundleBuildChunkID) {
      return;
    }

    this._bundleBuildChunkID = chunk.id;
    this._bundleBuildStart = new Date();
    chunk.msg = 'Building JavaScript bundle';

    if (this._onStartBuildBundle) {
      this._onStartBuildBundle(chunk);
    } else {
      this._enqueueAppendLogChunk(chunk);
    }
  }

  _handleUpdateBundleTransformProgress(progressChunk) {
    const msg = progressChunk.msg;
    let percentProgress;
    let bundleComplete = false;

    if (msg.type === 'bundle_build_done') {
      percentProgress = 100;
      bundleComplete = true;

      if (this._bundleBuildStart) {
        const duration = new Date().getTime() - this._bundleBuildStart.getTime();

        progressChunk.msg = `Building JavaScript bundle: finished in ${duration}ms.`;
      } else {
        progressChunk.msg = `Building JavaScript bundle: finished.`;
      }
    } else if (msg.type === 'bundle_build_failed') {
      percentProgress = -1;
      bundleComplete = true;
      progressChunk.msg = `Building JavaScript bundle: error`;
      progressChunk.level = _Logger().default.ERROR;
    } else if (msg.type === 'bundle_transform_progressed') {
      percentProgress = Math.floor(msg.transformedFileCount / msg.totalFileCount * 100);
      progressChunk.msg = `Building JavaScript bundle: ${percentProgress}%`;
    } else {
      return;
    }

    if (this._bundleBuildChunkID) {
      progressChunk.id = this._bundleBuildChunkID;
    }

    if (this._onProgressBuildBundle) {
      this._onProgressBuildBundle(percentProgress, this._bundleBuildStart, progressChunk);

      if (bundleComplete) {
        if (this._onFinishBuildBundle && this._bundleBuildStart) {
          const error = msg.type === 'bundle_build_failed' ? 'Build failed' : null;

          this._onFinishBuildBundle(error, this._bundleBuildStart, new Date(), progressChunk);
        }

        this._bundleBuildStart = null;
        this._bundleBuildChunkID = null;
      }
    } else {
      this._updateLogs(logs => {
        if (!logs || !logs.length) {
          return [];
        }

        logs.forEach(log => {
          if (log.id === this._bundleBuildChunkID) {
            log.msg = progressChunk.msg;
          }
        });

        if (bundleComplete) {
          this._bundleBuildChunkID = null;
        }

        return [...logs];
      });
    }
  }

  _formatModuleResolutionError(error) {
    if (!error.message) {
      return null;
    }

    const match = /^Unable to resolve module `(.+?)`/.exec(error.message);
    const originModulePath = error.originModulePath;

    if (!match || !originModulePath) {
      return null;
    }

    const moduleName = match[1];

    const relativePath = _path().default.relative(this._projectRoot, originModulePath);

    const DOCS_PAGE_URL = 'https://docs.expo.io/versions/latest/introduction/faq/#can-i-use-nodejs-packages-with-expo';

    if (NODE_STDLIB_MODULES.includes(moduleName)) {
      if (originModulePath.includes('node_modules')) {
        return `The package at "${relativePath}" attempted to import the Node standard library module "${moduleName}". It failed because React Native does not include the Node standard library. Read more at ${DOCS_PAGE_URL}`;
      } else {
        return `You attempted attempted to import the Node standard library module "${moduleName}" from "${relativePath}". It failed because React Native does not include the Node standard library. Read more at ${DOCS_PAGE_URL}`;
      }
    }

    return `Unable to resolve "${moduleName}" from "${relativePath}"`;
  }

  _formatBundlingError(error) {
    let message = error.message;

    if (!message && Array.isArray(error.errors) && error.errors.length) {
      message = error.errors[0].description;
    }

    if (!message) {
      return null;
    }

    message = _chalk().default.red(message);
    let snippet = this._getSnippetForError && this._getSnippetForError(error) || error.snippet;

    if (snippet) {
      message += `\n${snippet}`;
    }

    return message;
  }

  _formatWorkerChunk(origin, chunk) {
    const lines = chunk.split('\n');

    if (lines.length >= 1 && lines[lines.length - 1] === '') {
      lines.splice(lines.length - 1, 1);
    }

    return lines.map(line => `transform[${origin}]: ${line}`).join('\n');
  }

  _enqueueAppendLogChunk(chunk) {
    if (!chunk.shouldHide) {
      this._logsToAdd.push(chunk);

      this._enqueueFlushLogsToAdd();
    }
  }

  _maybeParseMsgJSON(chunk) {
    try {
      let parsedMsg = JSON.parse(chunk.msg);
      chunk.msg = parsedMsg;
    } catch (e) {// non-JSON message
    }

    return chunk;
  }

}

exports.default = PackagerLogsStream;
const NODE_STDLIB_MODULES = ['assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
//# sourceMappingURL=../__sourcemaps__/logs/PackagerLogsStream.js.map