import { command } from 'yargs';
import _get from 'lodash/fp/get';
import { readJsonSync, ensureDir, remove as remove$1, outputJSONSync, emptyDir, pathExists, copy, readFileSync, readJSONSync, pathExistsSync, existsSync as existsSync$1 } from 'fs-extra';
import { get, set } from 'env-dot-prop';
import humanize from 'humanize-string';
import titleize from 'titleize';
import { realpathSync, existsSync } from 'fs';
import { join, resolve, delimiter, isAbsolute, relative, parse, extname, normalize, dirname } from 'path';
import { sync } from 'resolve';
import logger__default, { error, Signale, warn, fatal, log } from 'signale';
import { __rest } from 'tslib';
import { getParsedData, headingsFromAst, parseMdx } from 'docz-utils/lib/mdast';
import { compiled, touch } from 'docz-utils/lib/fs';
import glob from 'fast-glob';
import { createHash } from 'crypto';
import slugify from '@sindresorhus/slugify';
import _isFunction from 'lodash/fp/isFunction';
import pReduce from 'p-reduce';
import getPkgRepo from 'get-pkg-repo';
import findup from 'find-up';
import WS from 'ws';
import _merge from 'lodash/fp/merge';
import _omit from 'lodash/fp/omit';
import { loadFrom, load, finds } from 'load-cfg';
import detectPort from 'detect-port';
import Config from 'webpack-chain';
import frontmatter from 'remark-frontmatter';
import remarkDocz from 'remark-docz';
import rehypeDocz from 'rehype-docz';
import slug from 'rehype-slug';
import webpack, { IgnorePlugin, HotModuleReplacementPlugin } from 'webpack';
import webpackBarPlugin from 'webpackbar';
import { minify } from 'html-minifier';
import miniHtmlWebpack, { generateCSSReferences, generateJSReferences } from 'mini-html-webpack-plugin';
import manifestPlugin from 'webpack-manifest-plugin';
import watchMissingNodeModules from 'react-dev-utils/WatchMissingNodeModulesPlugin';
import 'react-dev-utils/ModuleNotFoundPlugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { TemplateTag, replaceResultTransformer, trimResultTransformer, oneLineTrim, html as html$1 } from 'common-tags';
import * as TerserPlugin from 'terser-webpack-plugin';
import getCacheIdentifier from 'react-dev-utils/getCacheIdentifier';
import WebpackDevServer from 'webpack-dev-server';
import { prepareUrls, createCompiler } from 'react-dev-utils/WebpackDevServerUtils';
import { static as static$1 } from 'express';
import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware';
import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware';
import ignoredFiles from 'react-dev-utils/ignoredFiles';
import chalk$1 from 'chalk';
import chokidar from 'chokidar';
import equal from 'fast-deep-equal';
import get$1 from 'lodash/get';
import _propEq from 'lodash/fp/propEq';
import externalProptypesHandler from 'react-docgen-external-proptypes-handler';
import actualNameHandler from 'react-docgen-actual-name-handler';
import reactDocgen from 'react-docgen';
import _entries from 'lodash/fp/entries';
import _contains from 'lodash/fp/contains';
import _prop from 'lodash/fp/prop';
import _isEmpty from 'lodash/fp/isEmpty';
import reactDocgenTs from 'react-docgen-typescript';
import ts$1 from 'typescript';
import spawn$1 from 'cross-spawn';

const ensureSlash = (filepath, needsSlash) => {
  const hasSlash = filepath.endsWith('/');

  if (hasSlash && !needsSlash) {
    return filepath.substr(filepath, filepath.length - 1);
  } else if (!hasSlash && needsSlash) {
    return `${filepath}/`;
  } else {
    return filepath;
  }
};
const root = realpathSync(process.cwd());
const resolveApp = to => resolve(root, to);
const resolveOwn = to => resolve(__dirname, '../', to);
const templates = join(sync('docz-core'), '../templates');
const packageJson = resolveApp('package.json');
const servedPath = base => ensureSlash(base, true);
const docz = resolveApp('.docz');
const app = resolve(docz, 'app/');
const cache = resolve(docz, 'cache/');
const appPublic = resolve(docz, 'public/');
const appNodeModules = resolveApp('node_modules');
const appPackageJson = resolveApp('package.json');
const appYarnLock = resolveOwn('yarn.lock');
const ownNodeModules = resolveOwn('node_modules');
const getDist = dest => join(root, dest);
const distPublic = dest => join(dest, 'public/');
const importsJs = resolve(app, 'imports.js');
const rootJs = resolve(app, 'root.jsx');
const indexJs = resolve(app, 'index.jsx');
const indexHtml = resolve(app, 'index.html');
const db = resolve(app, 'db.json');

var paths = /*#__PURE__*/Object.freeze({
  ensureSlash: ensureSlash,
  root: root,
  resolveApp: resolveApp,
  resolveOwn: resolveOwn,
  templates: templates,
  packageJson: packageJson,
  servedPath: servedPath,
  docz: docz,
  app: app,
  cache: cache,
  appPublic: appPublic,
  appNodeModules: appNodeModules,
  appPackageJson: appPackageJson,
  appYarnLock: appYarnLock,
  ownNodeModules: ownNodeModules,
  getDist: getDist,
  distPublic: distPublic,
  importsJs: importsJs,
  rootJs: rootJs,
  indexJs: indexJs,
  indexHtml: indexHtml,
  db: db
});

const getEnv = (val, defaultValue = null) => get(val, defaultValue, {
  parse: true
});

const getInitialTitle = pkg => {
  const name = _get('name', pkg) || 'MyDoc';
  return titleize(humanize(name.replace(/^@.*\//, '')));
};

const getInitialDescription = pkg => _get('description', pkg) || 'My awesome app using docz';

const setArgs = yargs => {
  const pkg = readJsonSync(appPackageJson, {
    throws: false
  });
  return yargs.option('base', {
    type: 'string',
    default: getEnv('docz.base', '/')
  }).option('source', {
    alias: 'src',
    type: 'string',
    default: getEnv('docz.source', './')
  }).option('files', {
    type: 'string',
    default: getEnv('docz.files', '**/*.{md,markdown,mdx}')
  }).option('ignore', {
    type: 'array',
    default: getEnv('docz.ignore', [])
  }).option('public', {
    type: 'string',
    default: getEnv('docz.public', '/public')
  }).option('dest', {
    alias: 'd',
    type: 'string',
    default: getEnv('docz.dest', '.docz/dist')
  }).option('editBranch', {
    alias: 'eb',
    type: 'string',
    default: getEnv('docz.edit.branch', 'master')
  }).option('config', {
    type: 'string',
    default: getEnv('docz.config', '')
  }).option('title', {
    type: 'string',
    default: getEnv('docz.title', getInitialTitle(pkg))
  }).option('description', {
    type: 'string',
    default: getEnv('docz.description', getInitialDescription(pkg))
  }).option('theme', {
    type: 'string',
    default: getEnv('docz.theme', 'theme')
  }).option('typescript', {
    alias: 'ts',
    type: 'boolean',
    default: getEnv('docz.typescript', false)
  }).option('propsParser', {
    type: 'boolean',
    default: getEnv('docz.props.parser', true)
  }).option('wrapper', {
    type: 'string',
    default: getEnv('docz.wrapper', null)
  }).option('indexHtml', {
    type: 'string',
    default: getEnv('docz.index.html', null)
  }).option('debug', {
    type: 'boolean',
    default: getEnv('docz.debug', false)
  }).option('clearConsole', {
    type: 'boolean',
    default: getEnv('docz.clear.console', true)
  }).option('host', {
    type: 'string',
    default: getEnv('docz.host', '127.0.0.1')
  }).option('port', {
    alias: 'p',
    type: 'number',
    default: getEnv('docz.port', 3000)
  }).option('websocketHost', {
    type: 'string',
    default: getEnv('docz.websocket.host', '127.0.0.1')
  }).option('websocketPort', {
    type: 'number',
    default: getEnv('docz.websocket.port', 60505)
  }).option('native', {
    type: 'boolean',
    default: getEnv('docz.native', false)
  }).option('codeSandbox', {
    type: 'boolean',
    default: getEnv('docz.codeSandbox', true)
  }).option('sourcemaps', {
    type: 'boolean',
    default: getEnv('docz.sourcemaps', true)
  }).option('separator', {
    type: 'string',
    default: getEnv('docz.separator', '-')
  }).option('open', {
    alias: 'o',
    describe: 'auto open browser in dev mode',
    type: 'boolean',
    default: false
  });
};

const populateNodePath = () => {
  // We support resolving modules according to `NODE_PATH`.
  // It works similar to `NODE_PATH` in Node itself:
  // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
  // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
  // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
  // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
  // We also resolve them to make sure all tools using them work consistently.
  set('node.path', get('node.path', '').split(delimiter).filter(folder => folder && !isAbsolute(folder)).map(folder => resolve(root, folder)).join(delimiter));
};

const configDotEnv = () => {
  const NODE_ENV = get('node.env');
  const dotenv = resolveApp('.env');
  const dotenvFiles = [`${dotenv}.${NODE_ENV}.local`, `${dotenv}.${NODE_ENV}`, // Don't include `.env.local` for `test` environment
  // since normally you expect tests to produce the same
  // results for everyone
  NODE_ENV !== 'test' && `${dotenv}.local`, dotenv]; // Load environment variables from .env* files. Suppress warnings using silent
  // if this file is missing. dotenv will never modify any environment variables
  // that have already been set.  Variable expansion is supported in .env files.
  // https://github.com/motdotla/dotenv

  dotenvFiles.filter(Boolean).forEach(dotenvFile => {
    require('dotenv').config({
      path: dotenvFile
    });
  });
};

const setEnv = env => {
  set('babel.env', env);
  set('node.env', env);
  configDotEnv();
  populateNodePath();
};
const getClientEnvironment = publicUrl => {
  const APP_TEST = /^(REACT_APP_)|(ANGULAR_APP_)|(VUE_APP_)|(DOCZ_)/i;
  const raw = Object.keys(process.env).filter(key => APP_TEST.test(key)).reduce((env, key) => {
    env[key] = process.env[key];
    return env;
  }, {
    // Useful for determining whether we’re running in production mode. Most
    // importantly, it switches React into the correct mode.
    NODE_ENV: get('node.env') || 'development',
    // Useful for resolving the correct path to static assets in `public`. For
    // example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. This should
    // only be used as an escape hatch. Normally you would put images into the `src`
    // and `import` them in code to get their
    PUBLIC_URL: publicUrl
  });
  const stringified = {
    'process.env': Object.keys(raw).reduce((env, key) => {
      env[key] = JSON.stringify(raw[key]);
      return env;
    }, {})
  };
  return {
    raw,
    stringified
  };
};

const createId = file => createHash('md5').update(file).digest('hex');

const mountRoute = (base, route) => {
  if (base === '/') return route;
  const baseHasSlash = base.endsWith('/');
  if (baseHasSlash) return base.substr(0, base.length - 1) + route;
  return base + route;
};

class Entry {
  constructor(ast, file, src, config) {
    const filepath = this.getFilepath(file, src);
    const parsed = getParsedData(ast);
    const name = this.getName(filepath, parsed);
    this.id = createId(file);
    this.filepath = filepath;
    this.link = '';
    this.slug = this.slugify(filepath, config.separator);
    this.route = this.getRoute(parsed, config.base);
    this.name = name;
    this.menu = parsed.menu || '';
    this.headings = headingsFromAst(ast);
    this.settings = parsed;
  }

  setLink(url) {
    if (url) {
      this.link = url.replace('{{filepath}}', this.filepath);
    }
  }

  getFilepath(file, src) {
    const srcPath = resolve(root, src);
    const filepath = relative(srcPath, file);

    if (process.platform === 'win32') {
      return filepath.split('\\').join('/');
    }

    return filepath;
  }

  getName(filepath, parsed) {
    const filename = humanize(parse(filepath).name);
    return parsed && parsed.name ? parsed.name : filename;
  }

  slugify(filepath, separator) {
    const ext = extname(filepath);
    const fileWithoutExt = filepath.replace(ext, '');
    return slugify(fileWithoutExt, {
      separator
    });
  }

  getRoute(parsed, base) {
    const parsedRoute = _get('route', parsed);

    const route = parsedRoute || `/${this.slug}`;
    return mountRoute(base, route);
  }

}

class Plugin {
  constructor(p) {
    this.setConfig = p.setConfig;
    this.modifyBundlerConfig = p.modifyBundlerConfig;
    this.modifyBabelRc = p.modifyBabelRc;
    this.modifyFiles = p.modifyFiles;
    this.onPreCreateApp = p.onPreCreateApp;
    this.onCreateWebpackChain = p.onCreateWebpackChain;
    this.onCreateApp = p.onCreateApp;
    this.onServerListening = p.onServerListening;
    this.onPreBuild = p.onPreBuild;
    this.onPostBuild = p.onPostBuild;
    this.onPreRender = p.onPreRender;
    this.onPostRender = p.onPostRender;
  }

  static runPluginsMethod(plugins) {
    return (method, ...args) => {
      if (plugins && plugins.length > 0) {
        for (const plugin of plugins) {
          const fn = _get(method, plugin);

          _isFunction(fn) && fn(...args);
        }
      }
    };
  }

  static propsOfPlugins(plugins) {
    return prop => plugins && plugins.length > 0 ? plugins.map(p => _get(prop, p)).filter(Boolean) : [];
  }

  static reduceFromPlugins(plugins) {
    return (method, initial, ...args) => {
      return [...(plugins || [])].reduce((obj, plugin) => {
        const fn = _get(method, plugin);

        return fn && _isFunction(fn) ? fn(obj, ...args) : obj;
      }, initial);
    };
  }

  static reduceFromPluginsAsync(plugins) {
    return (method, initial, ...args) => {
      return pReduce([...(plugins || [])], (obj, plugin) => {
        const fn = _get(method, plugin);

        return Promise.resolve(fn && _isFunction(fn) ? fn(obj, ...args) : obj);
      }, initial);
    };
  }

}
function createPlugin(factory) {
  return new Plugin(factory);
}

const parseRepo = () => {
  try {
    const pkg = readJsonSync(appPackageJson);
    return getPkgRepo(pkg);
  } catch (err) {
    return null;
  }
};
const getRepoUrl = () => {
  const repo = parseRepo();
  return repo && (repo.browsetemplate && repo.browsetemplate.replace('{domain}', repo.domain).replace('{user}', repo.user).replace('{project}', repo.project).replace('{/tree/committish}', '') || repo.browse && repo.browse());
};

const getBitBucketPath = (branch, relative) => {
  const querystring = `?mode=edit&spa=0&at=${branch}&fileviewer=file-view-default`;
  const filepath = join(`/src/${branch}`, relative, `{{filepath}}`);
  return `${filepath}${querystring}`;
};

const getTree = (repo, branch, relative) => {
  const defaultPath = join(`/edit/${branch}`, relative, `{{filepath}}`);
  const bitBucketPath = getBitBucketPath(branch, relative);
  if (repo && repo.type === 'bitbucket') return bitBucketPath;
  return defaultPath;
};

const getRepoEditUrl = (src, branch) => {
  try {
    const repo = parseRepo();
    const project = parse(findup.sync('.git')).dir;
    const root$1 = join(root, src);
    const relative$1 = relative(project, root$1);
    const tree = getTree(repo, branch, relative$1);
    return repo && repo.browsetemplate && repo.browsetemplate.replace('{domain}', repo.domain).replace('{user}', repo.user).replace('{project}', repo.project).replace('{/tree/committish}', tree);
  } catch (err) {
    return null;
  }
};

const fromTemplates = file => join(templates, file);

const mapToObj = map => Array.from(map.entries()).reduce((obj, [key, value]) => Object.assign({}, obj, {
  [`${key}`]: value
}), {});

const matchFilesWithSrc = config => files => {
  const src = relative(root, config.src);
  return files.map(file => file.startsWith(src) ? file : join(src, file));
};

const writeAppFiles = async (config, dev) => {
  const {
    plugins,
    theme
  } = config;
  const props = Plugin.propsOfPlugins(plugins);
  const onPreRenders = props('onPreRender');
  const onPostRenders = props('onPostRender');
  const isProd = !dev;
  const root = await compiled(fromTemplates('root.tpl.js'), {
    minimize: false
  });
  const js = await compiled(fromTemplates('index.tpl.js'), {
    minimize: false
  });
  const websocketUrl = `ws://${config.websocketHost}:${config.websocketPort}`;
  const rawRootJs = root({
    theme,
    isProd,
    wrapper: config.wrapper,
    websocketUrl
  });
  const rawIndexJs = js({
    onPreRenders,
    onPostRenders,
    isProd
  });
  await remove$1(rootJs);
  await remove$1(indexJs);
  await touch(rootJs, rawRootJs);
  await touch(indexJs, rawIndexJs);
};

class Entries {
  static async writeApp(config, dev) {
    await ensureDir(app);
    await writeAppFiles(config, dev);
  }

  static async writeImports(map) {
    const imports = await compiled(fromTemplates('imports.tpl.js'));
    const rawImportsJs = imports({
      entries: Object.values(map)
    });
    await touch(join(app, 'imports.js'), rawImportsJs);
  }

  constructor(config) {
    this.repoEditUrl = getRepoEditUrl(config.src, config.editBranch);
    this.all = new Map();

    this.get = async () => this.getMap(config);
  }

  async getMap(config) {
    const {
      src,
      files: pattern,
      ignore,
      plugins,
      mdPlugins
    } = config;
    const arr = Array.isArray(pattern) ? pattern : [pattern];
    const toMatch = matchFilesWithSrc(config);
    const files = await glob(toMatch(arr), {
      ignore: ['**/node_modules/**'].concat(ignore),
      onlyFiles: true,
      unique: true,
      nocase: true,
      matchBase: true
    });

    const createEntry = async file => {
      try {
        const ast = await parseMdx(file, mdPlugins);
        const entry = new Entry(ast, file, src, config);
        if (this.repoEditUrl) entry.setLink(this.repoEditUrl);

        const {
          settings
        } = entry,
              rest = __rest(entry, ["settings"]);

        return Object.assign({}, settings, rest);
      } catch (err) {
        error(err);
        return null;
      }
    };

    const reduce = Plugin.reduceFromPlugins(plugins);
    const modifiedFiles = reduce('modifyFiles', files);
    const map = new Map();
    const entries = await Promise.all(modifiedFiles.map(createEntry).filter(Boolean));

    for (const entry of entries) {
      if (entry) {
        map.set(entry.filepath, entry);
      }
    }

    this.all = map;
    return mapToObj(map);
  }

}

class DataServer {
  constructor() {
    this.states = new Set();
    this.state = new Map();
    this.listeners = new Set();
  }

  register(states) {
    for (const state of states) this.states.add(state);

    return this;
  }

  async start() {
    const setState = (key, val) => this.setState(key, val);

    const getState = () => this.getState();

    await Promise.all(Array.from(this.states).map(async state => {
      if (!_isFunction(state.start)) return;
      return state.start({
        setState,
        getState
      });
    }));
  }

  close() {
    for (const state of this.states) {
      _isFunction(state.close) && state.close();
    }
  }

  onStateChange(listener) {
    this.listeners.add(listener);
    return () => this.listeners.clear();
  }

  getState() {
    return this.mapToObject(this.state);
  }

  setState(key, val) {
    const prev = _get(key, this.getState());

    const next = typeof val === 'function' ? val(prev) : val;
    this.state.set(key, next);
    this.writeDbFile();
    this.listeners.forEach(listener => {
      listener({
        type: `state.${key}`,
        payload: next
      });
    });
  }

  async writeDbFile() {
    outputJSONSync(db, this.mapToObject(this.state), {
      spaces: 2
    });
  }

  mapToObject(map) {
    return Array.from(map.entries()).reduce((obj, [key, val]) => Object.assign({}, obj, {
      [key]: val
    }), {});
  }

}

const onSignal = cb => {
  const signals = ['SIGINT', 'SIGTERM'];

  for (const sig of signals) {
    process.on(sig, async () => {
      await cb();
      process.exit();
    });
  }
};

const isSocketOpened = socket => socket.readyState === WS.OPEN;

const sender = socket => (type, payload) => {
  if (socket && isSocketOpened(socket)) {
    socket.send(JSON.stringify({
      type,
      payload
    }));
  }
};

class Socket {
  constructor(server, host, port) {
    if (server) {
      this.client = new WS.Server({
        server,
        host,
        port
      });
    }
  }

  onConnection(listener) {
    if (!this.client) return;
    this.client.on('connection', socket => {
      const emit = sender(socket);
      const subs = listener(socket, emit);

      const handleClose = async () => {
        subs();
        socket.terminate();
      };

      this.client.on('close', handleClose);
      onSignal(handleClose);
    });
  }

}

const toOmit = ['_', '$0', 'version', 'help'];
const htmlContext = {
  lang: 'en',
  favicon: 'https://cdn-std.dprcdn.net/files/acc_649651/LUKiMl'
};
const doczRcBaseConfig = {
  htmlContext,
  themeConfig: {},
  docgenConfig: {},
  filterComponents: files => files.filter(filepath => /\/[A-Z]\w*\.(js|jsx|ts|tsx)$/.test(filepath)),
  modifyBundlerConfig: config => config,
  modifyBabelRc: babelrc => babelrc,
  onCreateWebpackChain: () => null,
  menu: [],
  plugins: [],
  mdPlugins: [],
  hastPlugins: [],
  ignore: ['**/readme.md', '**/changelog.md', '**/code_of_conduct.md', '**/contributing.md', '**/license.md']
};
const getBaseConfig = (argv, custom) => {
  const initial = _omit(toOmit, argv);

  const base = Object.assign({}, initial, doczRcBaseConfig, {
    paths
  });
  return _merge(base, custom);
};
const parseConfig = async (argv, custom) => {
  const port = await detectPort(argv.port);
  const websocketPort = await detectPort(argv.websocketPort);
  const defaultConfig = getBaseConfig(argv, Object.assign({
    port,
    websocketPort,
    htmlContext
  }, custom));
  const config = argv.config ? loadFrom(resolve(argv.config), defaultConfig) : load('docz', defaultConfig);
  const reduceAsync = Plugin.reduceFromPluginsAsync(config.plugins);
  return reduceAsync('setConfig', config);
};

class Bundler {
  constructor(params) {
    const {
      args,
      config,
      server,
      build
    } = params;
    const run = Plugin.runPluginsMethod(args.plugins);
    this.args = args;
    this.config = config;
    this.server = server;
    this.builder = build;
    this.hooks = {
      onCreateWebpackChain(config, dev, args) {
        run('onCreateWebpackChain', config, dev, args);
      },

      onPreCreateApp(app) {
        run('onPreCreateApp', app);
      },

      onCreateApp(app) {
        run('onCreateApp', app);
      },

      onServerListening(server) {
        run('onServerListening', server);
      }

    };
  }

  async mountConfig(env) {
    const {
      plugins
    } = this.args;
    const isDev = env !== 'production';
    const reduce = Plugin.reduceFromPlugins(plugins);
    const userConfig = await this.config(this.hooks);
    const config = reduce('modifyBundlerConfig', userConfig, isDev, this.args);
    return this.args.modifyBundlerConfig(config, isDev, this.args);
  }

  async createApp(config) {
    return this.server(config, this.hooks);
  }

  async build(config) {
    const dist = getDist(this.args.dest);
    const publicDir = join(root, this.args.public);

    if (root === resolve(dist)) {
      logger__default.fatal(new Error('Unexpected option: "dest" cannot be set to the current working directory.'));
      process.exit(1);
    }

    await this.builder(config, dist, publicDir);
  }

}

const excludeNodeModules = filepath => /node_modules/.test(filepath) || /@babel(?:\/|\\{1,2})runtime/.test(filepath);

const sourceMaps = (config, args) => {
  const srcPath = resolve(root, args.src);
  config.module.rule('sourcemaps').test(/\.(js|mjs|jsx|ts|tsx|md|mdx)$/).include.add(srcPath).add(app).end().exclude.add(excludeNodeModules).end().use('sourcemaps').loader(require.resolve('source-map-loader')).end().enforce('pre');
};

const addScriptLoaders = opts => {
  const {
    rule,
    threadLoader = true,
    babelrc,
    args
  } = opts;
  return rule.when(!args.debug, rule => rule.use('cache-loader').loader(require.resolve('cache-loader')).options({
    cacheDirectory: cache
  })).when(Boolean(threadLoader), rule => rule.use('thread-loader').loader(require.resolve('thread-loader')).options({
    workers: require('os').cpus().length - 1
  })).use('babel-loader').loader(require.resolve('babel-loader')).options(babelrc).end();
};

const js = (config, args, babelrc) => {
  const srcPath = resolve(root, args.src);
  const rule = config.module.rule('js').test(/\.(jsx?|mjs)$/).include.add(srcPath).add(app).end().exclude.add(excludeNodeModules).end();
  addScriptLoaders({
    rule,
    babelrc,
    args
  });
};
const ts = (config, args, babelrc) => {
  const srcPath = resolve(root, args.src);
  const rule = config.module.rule('ts').test(/\.tsx?$/).include.add(srcPath).add(app).end().exclude.add(excludeNodeModules).end();
  addScriptLoaders({
    rule,
    babelrc,
    args
  });
};
const mdx = (config, args, babelrc) => {
  const {
    mdPlugins,
    hastPlugins
  } = args;
  const srcPath = resolve(root, args.src);
  const rule = config.module.rule('mdx').test(/\.(md|markdown|mdx)$/).include.add(srcPath).add(root).add(app).end().exclude.add(excludeNodeModules).end();
  addScriptLoaders({
    rule,
    babelrc,
    args,
    threadLoader: false
  }).use('mdx-loader').loader(require.resolve('@mdx-js/loader')).options({
    remarkPlugins: mdPlugins.concat([[frontmatter, {
      type: 'yaml',
      marker: '-'
    }], remarkDocz]),
    rehypePlugins: hastPlugins.concat([[rehypeDocz, {
      root: root,
      useCodeSandbox: args.codeSandbox
    }], slug])
  });
};
const INLINE_LIMIT = 10000;
const images = config => {
  config.module.rule('images').test(/\.(png|jpe?g|gif)(\?.*)?$/).use('url-loader').loader(require.resolve('url-loader')).options({
    limit: INLINE_LIMIT,
    name: `static/img/[name].[hash:8].[ext]`
  });
};
const svg = config => {
  config.module.rule('svg').test(/\.(svg)(\?.*)?$/).use('file-loader').loader(require.resolve('file-loader')).options({
    name: `static/img/[name].[hash:8].[ext]`
  });
};
const media = config => {
  config.module.rule('media').test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/).use('url-loader').loader(require.resolve('url-loader')).options({
    limit: INLINE_LIMIT,
    name: `static/media/[name].[hash:8].[ext]`
  });
};
const fonts = config => {
  config.module.rule('fonts').test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i).use('url-loader').loader(require.resolve('url-loader')).options({
    limit: INLINE_LIMIT,
    name: `static/fonts/[name].[hash:8].[ext]`
  });
};

const wrapItems = item => Object.keys(item).map(key => `${key}="${item[key]}"`).join(' ');

const generateTags = template => (items = []) => items.map(template).join('');

const generateMetaTags = generateTags(item => `<meta ${wrapItems(item)}>`);
const generateLinkTags = generateTags(item => `<link ${wrapItems(item)}>`);
const generateScriptTags = generateTags(item => `<script ${wrapItems(item)}></script>`);

const generateRawTags = (items = []) => {
  if (typeof items === 'string' || items instanceof String) return items;
  return items.map(item => item).join('');
};

const getHtmlFilepath = indexHtml => indexHtml ? resolve(root, indexHtml) : fromTemplates('index.tpl.html');

const getPublicUrl = (config, dev) => {
  const prefix = config.base === '/' ? '' : config.base;
  return dev ? prefix : `${prefix}/public`;
};

const emptyLineTrim = new TemplateTag(replaceResultTransformer(/^\s*[\r\n]/gm, ''), trimResultTransformer);
const htmlTemplate = async indexHtml => compiled(getHtmlFilepath(indexHtml), {
  minimize: false,
  escape: false
});
const parseHtml = ({
  config,
  ctx,
  dev,
  template
}) => {
  const {
    title,
    description
  } = config;
  const {
    publicPath,
    css,
    js,
    lang = 'en',
    favicon,
    head = [],
    body = [],
    trimWhitespace
  } = ctx;
  const headStr = `
    ${favicon ? `<link rel="icon" type="image/x-icon" href="${favicon}">` : ''}
    ${head.meta ? generateMetaTags(head.meta) : ''}
    ${head.links ? generateLinkTags(head.links) : ''}
    ${head.raw ? generateRawTags(head.raw) : ''}
    ${head.scripts ? generateScriptTags(head.scripts) : ''}
    ${generateCSSReferences(css, publicPath)}`;
  const footerStr = `
    ${body.raw ? generateRawTags(body.raw) : ''}
    ${body.scripts ? generateScriptTags(body.scripts) : ''}
    ${generateJSReferences(js, publicPath)}`;
  const doc = html$1(template({
    title,
    description,
    lang,
    head: headStr,
    footer: footerStr,
    publicUrl: getPublicUrl(config, dev)
  }));
  return trimWhitespace ? oneLineTrim(doc) : emptyLineTrim(doc);
};

const assets = (config, args, env) => {
  const isProd = env === 'production';
  const base = servedPath(args.base);
  const publicPath = isProd ? base : '/';
  config.plugin('assets-plugin').use(manifestPlugin, [{
    publicPath,
    fileName: 'assets.json'
  }]);
};
const analyzer = config => {
  config.plugin('bundle-analyzer').use(BundleAnalyzerPlugin, [{
    generateStatsFile: true,
    openAnalyzer: false,
    analyzerMode: 'static'
  }]);
};
const injections = (config, args, env) => {
  const {
    stringify
  } = JSON;
  const base = servedPath(args.base);

  const plugin = require('webpack/lib/DefinePlugin');

  config.plugin('injections').use(plugin, [Object.assign({}, getClientEnvironment(base).stringified, {
    NODE_ENV: stringify(env),
    DOCZ_BASE_URL: stringify(base)
  })]);
};
const ignore = config => {
  config.plugin('ignore-plugin').use(IgnorePlugin, [/(regenerate\-unicode\-properties)|(elliptic)/, /node_modules/]);
};
const hot = config => {
  config.plugin('hot-module-replacement').use(HotModuleReplacementPlugin, [{
    multiStep: true
  }]);
};
const html = async (config, args, env) => {
  const dev = env !== 'production';
  const template = await htmlTemplate(args.indexHtml);
  config.plugin('html-plugin').use(miniHtmlWebpack, [{
    context: Object.assign({}, args.htmlContext, {
      trimWhitespace: true
    }),
    template: ctx => {
      const doc = parseHtml({
        ctx,
        dev,
        template,
        config: args
      });
      return dev ? doc : minify(doc, {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      });
    }
  }]);
};
const webpackBar = (config, args) => {
  config.plugin('webpackbar').use(webpackBarPlugin, [{
    name: 'Docz',
    color: '#41b883'
  }]);
};
const watchNodeModulesPlugin = config => {
  config.plugin('watch-missing-node-modules').use(watchMissingNodeModules, [appNodeModules]);
};

const minifier = (config, args) => {
  config.optimization.minimizer('js').use(TerserPlugin, [{
    terserOptions: {
      parse: {
        ecma: 8
      },
      compress: {
        ecma: 5,
        warnings: false,
        comparisons: false
      },
      mangle: {
        safari10: true
      },
      output: {
        ecma: 5,
        comments: false,
        ascii_only: true
      }
    },
    parallel: true,
    cache: !args.debug,
    sourceMap: args.sourcemaps
  }]);
};

const getBabelConfig = async (args, env, typescript) => {
  const isProd = env === 'production';
  const isDev = env === 'development';
  const localBabelRc = load('babel', {
    presets: [],
    plugins: []
  }, false, true);
  const presets = [[require.resolve('babel-preset-react-app'), {
    typescript,
    flow: !args.typescript
  }]];
  const defaultPlugins = [[require.resolve('babel-plugin-export-metadata'), {
    notUseSpecifiers: args.notUseSpecifiers
  }], [require.resolve('babel-plugin-named-asset-import'), {
    loaderMap: {
      svg: {
        ReactComponent: '@svgr/webpack?-prettier,-svgo![path]'
      }
    }
  }]];

  const config = _merge(localBabelRc, {
    presets,
    babelrc: false,
    cacheCompression: args.debug ? false : isProd,
    cacheDirectory: !args.debug,
    cacheIdentifier: args.debug ? null : getCacheIdentifier(isProd ? 'production' : isDev && 'development', ['docz', 'docz-core']),
    compact: isProd,
    customize: require.resolve('babel-preset-react-app/webpack-overrides'),
    plugins: defaultPlugins.concat(!isProd ? [require.resolve('react-hot-loader/babel')] : [])
  });

  const reduce = Plugin.reduceFromPlugins(args.plugins);
  const newConfig = reduce('modifyBabelRc', config, args);
  return args.modifyBabelRc(newConfig, args);
};

/* eslint-disable @typescript-eslint/camelcase */
const createConfig = (args, env) => async hooks => {
  const {
    debug
  } = args;
  const config = new Config();
  const isProd = env === 'production';
  const base = servedPath(args.base);
  const dist = getDist(args.dest);
  const srcPath = resolve(root, args.src);
  const publicPath = isProd ? base : '/';
  /**
   * general
   */

  config.context(root);
  config.set('mode', env);
  config.when(args.sourcemaps, cfg => cfg.devtool(isProd ? 'source-map' : 'cheap-module-eval-source-map'), cfg => cfg.devtool(false));
  config.node.merge({
    child_process: 'empty',
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  });
  /**
   * output
   */

  const outputProd = output => output.filename('static/js/[name].[hash].js').sourceMapFilename('static/js/[name].[hash].js.map').chunkFilename('static/js/[name].[chunkhash:8].js');

  const outputDev = output => output.filename('static/js/[name].js').sourceMapFilename('static/js/[name].js.map');

  config.output.pathinfo(true).path(resolve(root, dist)).publicPath(publicPath).when(isProd, outputProd, outputDev).crossOriginLoading('anonymous').devtoolModuleFilenameTemplate(info => resolve(info.resourcePath).replace(/\\/g, '/'));
  /**
   * entries
   */

  config.entry('app').when(!isProd, entry => entry.add(require.resolve('react-dev-utils/webpackHotDevClient'))).add(indexJs);
  /**
   * resolve
   */

  config.resolve.set('symlinks', true);
  config.resolve.extensions.add('.web.js').add('.mjs').add('.js').add('.json').add('.web.jsx').add('.jsx').add('.mdx').end();
  config.resolve.alias.set('react-native$', 'react-native-web');

  const inYarnWorkspaces = __dirname.includes('/docz/core/docz-core');

  const doczDependenciesDir = inYarnWorkspaces ? join(__dirname, '../../../../node_modules') : ownNodeModules;
  config.resolve.modules.add('node_modules').add(doczDependenciesDir).add(srcPath).add(root).merge(get('node.path').split(delimiter).filter(Boolean));
  config.resolveLoader.set('symlinks', true).modules // prioritize our own
  .add('node_modules').add(doczDependenciesDir).add(root);
  /**
   * loaders
   */

  const jsBabelrc = await getBabelConfig(args, env);
  const tsBabelrc = await getBabelConfig(args, env, true);
  config.when(args.sourcemaps, cfg => sourceMaps(cfg, args));
  js(config, args, jsBabelrc);
  mdx(config, args, jsBabelrc);
  images(config);
  svg(config);
  media(config);
  fonts(config);
  await html(config, args, env);
  assets(config, args, env);
  ignore(config);
  injections(config, args, env);
  isProd && hot(config);
  config.when(debug, cfg => analyzer(cfg));
  config.when(!isProd, cfg => watchNodeModulesPlugin(cfg));
  config.when(!debug && !isProd, cfg => webpackBar(cfg, args));
  /**
   * typescript setup
   */

  config.when(args.typescript, cfg => {
    cfg.resolve.extensions.prepend('.ts').prepend('.tsx').end();
    ts(cfg, args, tsBabelrc);
  });
  /**
   * optimization
   */

  config.optimization.nodeEnv(env).namedModules(true).minimize(isProd).splitChunks({
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'all'
      }
    }
  });
  config.performance.hints(false);
  config.when(isProd, cfg => minifier(cfg, args));
  hooks.onCreateWebpackChain(config, !isProd, args);
  args.onCreateWebpackChain(config, !isProd, args);
  return config.toConfig();
};

const devServerConfig = (hooks, args) => {
  const srcPath = resolve(root, args.src);
  const publicDir = resolve(root, args.public);
  const nonExistentDir = resolve(__dirname, 'non-existent');
  return {
    publicPath: '/',
    compress: true,
    logLevel: args.debug ? 'debug' : 'silent',
    clientLogLevel: args.debug ? 'info' : 'none',
    contentBase: [nonExistentDir],
    watchContentBase: true,
    hot: true,
    quiet: !args.debug,
    open: true,
    watchOptions: {
      ignored: ignoredFiles(srcPath)
    },
    overlay: false,
    host: args.host,
    port: args.port,
    historyApiFallback: {
      disableDotRule: true
    },
    disableHostCheck: true,

    before(app, server) {
      app.use('/public', static$1(publicDir));
      app.use(evalSourceMapMiddleware(server));
      app.use(errorOverlayMiddleware());
      hooks.onPreCreateApp(app);
    },

    after(app) {
      hooks.onCreateApp(app);
    }

  };
};

const useYarn = existsSync(appYarnLock);
const server = args => async (config, hooks) => ({
  start: async () => {
    const serverConfig = devServerConfig(hooks, args);
    const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';

    const appName = require(packageJson).name;

    const useTypescript = args.typescript;
    const urls = prepareUrls(protocol, args.host, args.port);
    const devSocket = {
      warnings: warnings => devServer.sockWrite(devServer.sockets, 'warnings', warnings),
      errors: errors => devServer.sockWrite(devServer.sockets, 'errors', errors)
    };
    const compiler = createCompiler({
      appName,
      config,
      devSocket,
      urls,
      useYarn,
      useTypescript,
      webpack
    });
    const devServer = new WebpackDevServer(compiler, serverConfig);
    return devServer.listen(args.port, args.host, err => {
      if (err) return logger__default.fatal(err);
      hooks.onServerListening(devServer);
    });
  }
});

const FSR = require('react-dev-utils/FileSizeReporter');

const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const {
  measureFileSizesBeforeBuild,
  printFileSizesAfterBuild
} = FSR;
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;

const hasCiEnvVar = () => get('ci', false, {
  parse: true
});

const copyPublicFolder = async (dest, publicDir) => {
  if (await pathExists(publicDir)) {
    await copy(publicDir, distPublic(dest), {
      dereference: true,
      filter: file => file !== indexHtml
    });
  }
};

const compile = config => new Promise((resolve, reject) => {
  let compiler;

  try {
    compiler = webpack(config);
  } catch (err) {
    onError(err);
  }

  compiler && compiler.run((err, stats) => {
    if (err) reject(err);
    resolve(stats);
  });
});

const builder = async (config, previousFileSizes) => new Promise(async (resolve, reject) => {
  try {
    const stats = await compile(config);
    const messages = formatWebpackMessages(stats.toJson({}, true));

    if (messages.errors.length) {
      return reject(new Error(messages.errors.join('\n\n')));
    }

    if (hasCiEnvVar() && messages.warnings.length) {
      warn('\nTreating warnings as errors because process.env.CI = true.\n' + 'Most CI servers set it automatically.\n');
      return reject(new Error(messages.warnings.join('\n\n')));
    }

    return resolve({
      stats,
      previousFileSizes,
      warnings: messages.warnings
    });
  } catch (err) {
    reject(err);
  }
});

const onSuccess = (dist, {
  stats,
  previousFileSizes,
  warnings
}) => {
  if (warnings.length) {
    log();
    warn('Compiled with warnings.\n');
    warn(warnings.join('\n\n'));
    warn('\nSearch for the ' + chalk$1.underline(chalk$1.yellow('keywords')) + ' to learn more about each warning.');
    warn('To ignore, add ' + chalk$1.cyan('// eslint-disable-next-line') + ' to the line before.\n');
  }

  log();
  log(`File sizes after gzip:\n`);
  printFileSizesAfterBuild(stats, previousFileSizes, dist, WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_CHUNK_GZIP_SIZE);
  log();
};

const onError = err => {
  log();
  fatal(err);
  process.exit(1);
  log();
};

const build = async (config, dist, publicDir) => {
  const interactive = new Signale({
    interactive: true,
    scope: 'build'
  });

  try {
    interactive.start('Creating an optimized bundle');
    await ensureDir(dist);
    const previousFileSizes = await measureFileSizesBeforeBuild(dist);
    await emptyDir(dist);
    await copyPublicFolder(dist, publicDir);
    const result = await builder(config, previousFileSizes);
    interactive.success('Build successfully created');
    onSuccess(dist, result);
  } catch (err) {
    fatal(chalk$1.red('Failed to compile.\n'));
    onError(err);
  }
};

const bundler = (args, env) => new Bundler({
  args,
  build,
  config: createConfig(args, env),
  server: server(args)
});

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

var chalk = require('chalk');

var execSync = require('child_process').execSync;

var spawn = require('cross-spawn');

var opn = require('opn'); // https://github.com/sindresorhus/opn#app


var OSX_CHROME = 'google chrome';
const Actions = Object.freeze({
  NONE: 0,
  BROWSER: 1,
  SCRIPT: 2
});

function getBrowserEnv() {
  // Attempt to honor this environment variable.
  // It is specific to the operating system.
  // See https://github.com/sindresorhus/opn#app for documentation.
  const value = process.env.BROWSER;
  let action;

  if (!value) {
    // Default.
    action = Actions.BROWSER;
  } else if (value.toLowerCase().endsWith('.js')) {
    action = Actions.SCRIPT;
  } else if (value.toLowerCase() === 'none') {
    action = Actions.NONE;
  } else {
    action = Actions.BROWSER;
  }

  return {
    action,
    value
  };
}

function executeNodeScript(scriptPath, url) {
  const extraArgs = process.argv.slice(2);
  const child = spawn('node', [scriptPath, ...extraArgs, url], {
    stdio: 'inherit'
  });
  child.on('close', code => {
    if (code !== 0) {
      console.log();
      console.log(chalk.red('The script specified as BROWSER environment variable failed.'));
      console.log(chalk.cyan(scriptPath) + ' exited with code ' + code + '.');
      console.log();
      return;
    }
  });
  return true;
}

function startBrowserProcess(browser, url) {
  // If we're on OS X, the user hasn't specifically
  // requested a different browser, we can try opening
  // Chrome with AppleScript. This lets us reuse an
  // existing tab when possible instead of creating a new one.
  const shouldTryOpenChromeWithAppleScript = process.platform === 'darwin' && (typeof browser !== 'string' || browser === OSX_CHROME);

  if (shouldTryOpenChromeWithAppleScript) {
    try {
      // Try our best to reuse existing tab
      // on OS X Google Chrome with AppleScript
      execSync('ps cax | grep "Google Chrome"');
      execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', {
        cwd: __dirname,
        stdio: 'ignore'
      });
      return true;
    } catch (err) {// Ignore errors.
    }
  } // Another special case: on OS X, check if BROWSER has been set to "open".
  // In this case, instead of passing `open` to `opn` (which won't work),
  // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
  // https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768


  if (process.platform === 'darwin' && browser === 'open') {
    browser = undefined;
  } // Fallback to opn
  // (It will always open new tab)


  try {
    var options = {
      app: browser,
      wait: false
    };
    opn(url, options).catch(() => {}); // Prevent `unhandledRejection` error.

    return true;
  } catch (err) {
    return false;
  }
}
/**
 * Reads the BROWSER environment variable and decides what to do with it. Returns
 * true if it opened a browser or ran a node.js script, otherwise false.
 */


function openBrowser(url) {
  const {
    action,
    value
  } = getBrowserEnv();

  switch (action) {
    case Actions.NONE:
      // Special case: BROWSER="none" will prevent opening completely.
      return false;

    case Actions.SCRIPT:
      return executeNodeScript(value, url);

    case Actions.BROWSER:
      return startBrowserProcess(value, url);

    default:
      throw new Error('Not implemented.');
  }
}

const mapToArray = (map = []) => Object.entries(map).map(entry => entry && {
  key: entry[0],
  value: entry[1]
}).filter(Boolean);

const updateEntries = entries => async p => {
  const prev = _get('entries', p.getState());

  const map = await entries.get();

  if (map && !equal(prev, map)) {
    await Entries.writeImports(map);
    p.setState('entries', mapToArray(map));
  }
};

const state = (entries, config, dev) => {
  const src = relative(root, config.src);
  const files = Array.isArray(config.files) ? config.files.map(filePath => join(src, filePath)) : join(src, config.files);
  const ignored = config.watchIgnore || /(((^|[\/\\])\..+)|(node_modules))/;
  const watcher = chokidar.watch(files, {
    cwd: root,
    ignored,
    persistent: true
  });
  watcher.setMaxListeners(Infinity);
  return {
    id: 'entries',
    start: async params => {
      const update = updateEntries(entries);
      await update(params);

      if (dev) {
        watcher.on('add', async () => update(params));
        watcher.on('change', async () => update(params));
        watcher.on('unlink', async () => update(params));
        watcher.on('raw', async (event, path, details) => {
          if (details.event === 'moved' && details.type === 'directory') {
            await update(params);
          }
        });
      }
    },
    close: () => {
      watcher.close();
    }
  };
};

const getInitialConfig = config => {
  const pkg = readJsonSync(appPackageJson, {
    throws: false
  });
  const repoUrl = getRepoUrl();
  return {
    title: config.title,
    description: config.description,
    menu: config.menu,
    version: get$1(pkg, 'version'),
    repository: repoUrl,
    native: config.native,
    codeSandbox: config.codeSandbox,
    themeConfig: config.themeConfig,
    separator: config.separator
  };
};

const update = async (params, initial, {
  config
}) => {
  const next = config ? loadFrom(resolve(config), initial, true, true) : load('docz', initial, true, true);
  params.setState('config', next);
};

const state$1 = (config, dev) => {
  const initial = getInitialConfig(config);
  const glob = config.config || finds('docz');
  const ignored = config.watchIgnore || /(((^|[\/\\])\..+)|(node_modules))/;
  const watcher = chokidar.watch(glob, {
    cwd: root,
    ignored,
    persistent: true
  });
  watcher.setMaxListeners(Infinity);
  return {
    id: 'config',
    start: async params => {
      const fn = async () => update(params, initial, config);

      await update(params, initial, config);

      if (dev) {
        watcher.on('add', fn);
        watcher.on('change', fn);
        watcher.on('unlink', fn);
      }
    },
    close: () => {
      watcher.close();
    }
  };
};

const throwError = err => {
  logger__default.fatal(`Error parsing static types`);
  logger__default.error(err);
};

const jsParser = (files, config) => {
  const resolver = config.docgenConfig.resolver || reactDocgen.resolver.findAllExportedComponentDefinitions;

  const parseFilepathProps = filepath => {
    const handlers = reactDocgen.defaultHandlers.concat([externalProptypesHandler(filepath), actualNameHandler]);

    try {
      const code = readFileSync(filepath, 'utf-8');
      const props = reactDocgen.parse(code, resolver, handlers);
      return {
        key: normalize(filepath),
        value: props
      };
    } catch (err) {
      if (config.debug) throwError(err);
      return null;
    }
  };

  return files.map(parseFilepathProps).filter(Boolean);
};

const digest = str => createHash('md5').update(str).digest('hex');

const cacheFilepath = join(cache, 'propsParser.json');

const readCacheFile = () => readJSONSync(cacheFilepath, {
  throws: false
});

function checkFilesOnCache(files) {
  const cache = readCacheFile();
  if (_isEmpty(cache)) return files;
  return files.filter(filepath => {
    const normalized = normalize(filepath);
    const hash = digest(readFileSync(normalized, 'utf-8'));

    const found = _get(normalized, cache);

    return found && hash !== found.hash;
  });
}

function writePropsOnCache(items) {
  const cache = readCacheFile();
  const newCache = items.reduce((obj, {
    key: filepath,
    value
  }) => {
    const normalized = normalize(filepath);
    const hash = digest(readFileSync(normalized, 'utf-8'));
    return Object.assign({}, obj, {
      [normalized]: {
        hash,
        props: value
      }
    });
  }, {});
  outputJSONSync(cacheFilepath, Object.assign({}, cache, newCache));
}

function getPropsOnCache() {
  const cache = readCacheFile();

  if (_isEmpty(cache)) {
    warn('Any cache was found with your props definitions');
    warn("We'll parse your components to get props from them");
    warn('Depending on your components, this could take while...');
    return [];
  }

  return Object.entries(cache).map(([key, value]) => ({
    key,
    value: _get('props', value)
  }));
}

const mergeWithCache = (cache, props) => {
  const keys = props.map(_prop('key'));
  return cache.filter(item => !_contains(item.key, keys)).concat(props);
};

const removeFromCache = filepath => {
  const cache = readCacheFile();
  outputJSONSync(cacheFilepath, _omit(filepath, cache));
};

const getInitialFilesMap = () => {
  const cache = readCacheFile();
  if (_isEmpty(cache)) return new Map();
  const map = new Map();

  _entries(cache).forEach(([filepath]) => {
    const exist = pathExistsSync(filepath);

    if (!exist) {
      removeFromCache(filepath);
    } else {
      map.set(filepath, {
        text: readFileSync(filepath, 'utf-8'),
        version: 0
      });
    }
  });

  return map;
};

let languageService = null;
const filesMap = getInitialFilesMap();

function getTSConfigFile(tsconfigPath) {
  const basePath = dirname(tsconfigPath);
  const configFile = ts$1.readConfigFile(tsconfigPath, ts$1.sys.readFile);
  return ts$1.parseJsonConfigFileContent(configFile.config, ts$1.sys, basePath, {}, tsconfigPath);
}

function loadFiles(filesToLoad) {
  filesToLoad.forEach(filepath => {
    const normalized = normalize(filepath);
    const found = filesMap.get(normalized);
    filesMap.set(normalized, {
      text: readFileSync(normalized, 'utf-8'),
      version: found ? found.version + 1 : 0
    });
  });
}

function createServiceHost(compilerOptions, files) {
  return {
    getScriptFileNames: () => {
      return [...files.keys()];
    },
    getScriptVersion: fileName => {
      const file = files.get(fileName);
      return file && file.version.toString() || '';
    },
    getScriptSnapshot: fileName => {
      if (!existsSync$1(fileName)) {
        return undefined;
      }

      let file = files.get(fileName);

      if (file === undefined) {
        const text = readFileSync(fileName).toString();
        file = {
          version: 0,
          text
        };
        files.set(fileName, file);
      }

      return ts$1.ScriptSnapshot.fromString(file.text);
    },
    getCurrentDirectory: () => process.cwd(),
    getCompilationSettings: () => compilerOptions,
    getDefaultLibFileName: options => ts$1.getDefaultLibFilePath(options),
    fileExists: ts$1.sys.fileExists,
    readFile: ts$1.sys.readFile,
    readDirectory: ts$1.sys.readDirectory
  };
}

const parseFiles = (files, config, tsconfig) => {
  const opts = {
    propFilter(prop) {
      if (prop.parent == null) return true;
      const propFilter = config.docgenConfig.propFilter;
      const val = propFilter && _isFunction(propFilter) && propFilter(prop);
      return !prop.parent.fileName.includes('node_modules') || Boolean(val);
    },

    componentNameResolver(exp, source) {
      const componentNameResolver = config.docgenConfig.resolver;
      const val = componentNameResolver && _isFunction(componentNameResolver) && componentNameResolver(exp, source);
      return val;
    }

  };
  loadFiles(files);
  const parser = reactDocgenTs.withCustomConfig(tsconfig, opts);

  const compilerOptions = _get('options', getTSConfigFile(tsconfig));

  const programProvider = () => {
    if (languageService) return languageService.getProgram();
    const servicesHost = createServiceHost(compilerOptions, filesMap);
    const documentRegistry = ts$1.createDocumentRegistry();
    languageService = ts$1.createLanguageService(servicesHost, documentRegistry);
    return languageService.getProgram();
  };

  return files.map(filepath => ({
    key: normalize(filepath),
    value: parser.parseWithProgramProvider(filepath, programProvider)
  }));
};

const tsParser = (files, config, tsconfig) => {
  if (!tsconfig) return null;
  const filesToLoad = checkFilesOnCache(files);
  const propsOnCache = getPropsOnCache();
  if (!filesToLoad.length) return propsOnCache;
  const next = parseFiles(filesToLoad, config, tsconfig);
  writePropsOnCache(next);
  return mergeWithCache(propsOnCache, next);
};

const docgen = async (files, config) => {
  const tsconfig = await findup('tsconfig.json', {
    cwd: root
  });
  return config.typescript ? tsParser(files, config, tsconfig) : jsParser(files, config);
};

const getPattern = config => {
  const {
    ignore,
    src: source,
    typescript: ts,
    docgenConfig: docgenConfig
  } = config;
  const src = relative(root, docgenConfig.searchPath ? docgenConfig.searchPath : source);
  return ignore.map(entry => `!**/${entry}`).concat([join(src, ts ? '**/*.{ts,tsx}' : '**/*.{js,jsx,mjs}'), '!**/node_modules', '!**/doczrc.js']);
};

const removeFilepath = (items, filepath) => items.filter(item => item.key !== filepath);

const initial = (config, pattern) => async p => {
  const {
    filterComponents
  } = config;
  const files = await glob(pattern, {
    cwd: root
  });
  const filtered = filterComponents ? filterComponents(files) : files;
  const metadata = await docgen(filtered, config);
  p.setState('props', metadata);
};

const change = (p, config) => async filepath => {
  const prev = _get('props', p.getState());

  const metadata = await docgen([filepath], config);
  const filtered = metadata.filter(_propEq('key', filepath));
  const next = removeFilepath(prev, filepath).concat(filtered);
  p.setState('props', next);
};

const remove = p => async filepath => {
  const prev = _get('props', p.getState());

  const next = removeFilepath(prev, filepath);
  p.setState('props', next);
};

const state$2 = (config, dev) => {
  const pattern = getPattern(config);
  const ignored = config.watchIgnore || /(((^|[\/\\])\..+)|(node_modules))/;
  const watcher = chokidar.watch(pattern, {
    cwd: root,
    ignored,
    persistent: true
  });
  watcher.setMaxListeners(Infinity);
  return {
    id: 'props',
    start: async params => {
      const addInitial = initial(config, pattern);
      await addInitial(params);

      if (dev) {
        watcher.on('change', change(params, config));
        watcher.on('unlink', remove(params));
      }
    },
    close: () => {
      watcher.close();
    }
  };
};



var index = /*#__PURE__*/Object.freeze({
  entries: state,
  config: state$1,
  props: state$2
});

process.setMaxListeners(Infinity);
const dev = async args => {
  const env = get('node.env');
  const config = await parseConfig(args);
  const bundler$1 = bundler(config, env);
  const entries = new Entries(config);
  const {
    websocketHost,
    websocketPort
  } = config;
  const bundlerConfig = await bundler$1.mountConfig(env);
  const app = await bundler$1.createApp(bundlerConfig);

  try {
    await Entries.writeApp(config, true);
    await Entries.writeImports((await entries.get()));
  } catch (err) {
    fatal('Failed to build your files');
    error(err);
    process.exit(1);
  }

  const server = await app.start();
  const dataServer = new DataServer();
  const socket = new Socket(server, websocketHost, websocketPort);
  if (config.propsParser) dataServer.register([state$2(config, true)]);
  dataServer.register([state$1(config, true), state(entries, config, true)]);

  try {
    await dataServer.start();
    if (args.open || args.o) openBrowser(`http://${config.host}:${config.port}`);
  } catch (err) {
    fatal('Failed to process data server');
    error(err);
    dataServer.close();
    process.exit(1);
  }

  socket.onConnection((_, emit) => {
    const subscribe = dataServer.onStateChange(action => {
      emit(action.type, action.payload);
    });
    return () => subscribe();
  });
  onSignal(async () => {
    dataServer.close();
    server.close();
  });
  server.on('close', async () => {
    dataServer.close();
  });
};

const build$1 = async args => {
  const env = get('node.env');
  const config = await parseConfig(args);
  const entries = new Entries(config);
  const bundler$1 = bundler(config, env);
  const bundlerConfig = await bundler$1.mountConfig(env);
  const run = Plugin.runPluginsMethod(config.plugins);
  const dataServer = new DataServer();
  if (config.propsParser) dataServer.register([state$2(config)]);
  dataServer.register([state$1(config), state(entries, config)]);

  try {
    await Entries.writeApp(config, false);
    await Entries.writeImports((await entries.get()));
    await dataServer.start();
    await run('onPreBuild', config);
    await bundler$1.build(bundlerConfig);
    await run('onPostBuild', config);
    dataServer.close();
  } catch (err) {
    error(err);
    process.exit(1);
    dataServer.close();
  }
};

const serve = async args => {
  const config = await parseConfig(args);
  const dist = getDist(config.dest);
  spawn$1.sync('serve', ['-s', dist], {
    stdio: 'inherit'
  });
};

const cli = () => {
  return command('dev', 'initialize docz dev server', setArgs, async args => {
    setEnv('development');
    await dev(args);
  }).command('build', 'build dir as static site', setArgs, async args => {
    setEnv('production');
    await build$1(args);
    process.exit();
  }).command('serve', 'serve dir as static site', setArgs, async args => {
    setEnv('production');
    await build$1(args);
    await serve(args);
    process.exit();
  }).demandCommand().help().wrap(72).epilog('for more information visit https://github.com/pedronauck/docz').showHelpOnFail(false, 'whoops, something went wrong! run with --help').argv;
};

/** cli exports */

export { DataServer, Entries, Entry, Plugin, cli, createPlugin, getBaseConfig, parseConfig, setArgs, index as states };