| 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 }; |