blob: 1df33393766e4164f385462448f2782c79fb8ae8 [file] [log] [blame]
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 };