1 | 'use strict'
|
2 | const updateNotifier = require('update-notifier')
|
3 | const meow = require('meow')
|
4 | const processFiles = require('./process-files')
|
5 | const normalizeNewline = require('normalize-newline')
|
6 | const relative = require('relative')
|
7 | const normalizePath = require('normalize-path')
|
8 | const chalk = require('chalk')
|
9 | const fs = require('fs')
|
10 | const path = require('path')
|
11 | const test = require('tape')
|
12 | const tapDiff = require('@zkochan/tap-diff')
|
13 | const readPkgUp = require('@zkochan/read-pkg-up')
|
14 | const mos = require('mos-processor')
|
15 | const defaultPlugins = require('./default-plugins')
|
16 | const resolve = require('resolve')
|
17 |
|
18 | const cwd = process.cwd()
|
19 | const stdout = process.stdout
|
20 |
|
21 | const cli = meow([
|
22 | 'Usage',
|
23 | ' mos [test] [files]',
|
24 | '',
|
25 | 'Options',
|
26 | ' --init Add mos to your project',
|
27 | ' --help, -h Display usage',
|
28 | ' --version, -v Display version',
|
29 | ' --tap Generate TAP output when testing markdown files',
|
30 | '',
|
31 | ' -x="<exclude-pattern>"',
|
32 | ' Exclude pattern',
|
33 | '',
|
34 | 'Examples',
|
35 | ' mos',
|
36 | ' mos test',
|
37 | ' mos test README.md',
|
38 | ' mos --init',
|
39 | ' mos --init README.md',
|
40 | '',
|
41 | 'Tips:',
|
42 | ' Add `mos test` to your `scripts.test` property in `package.json`',
|
43 | ].join('\n'), {
|
44 | alias: {
|
45 | help: 'h',
|
46 | version: 'v',
|
47 | },
|
48 | })
|
49 |
|
50 | updateNotifier({pkg: cli.pkg}).notify()
|
51 |
|
52 | if (cli.flags.init) {
|
53 | require('mos-init')()
|
54 | return
|
55 | }
|
56 |
|
57 | const highlightPath = chalk.bgBlack.yellow
|
58 |
|
59 | const isTest = ~['test', 't'].indexOf((cli.input[0] || '').toLowerCase())
|
60 |
|
61 | const processMD = md => readPkgUp({ cwd: md.filePath })
|
62 | .then(result => {
|
63 | const allDeps = new Set(Object
|
64 | .keys(result.pkg.dependencies || {})
|
65 | .concat(Object.keys(result.pkg.devDependencies || {})))
|
66 |
|
67 | result.pkg.mos = result.pkg.mos || {}
|
68 |
|
69 | const pkgPlugins = (result.pkg.mos.plugins || [])
|
70 | .map(plugin => plugin instanceof Array
|
71 | ? { name: plugin[0], options: plugin[1] || {} }
|
72 | : { name: plugin, options: {}}
|
73 | )
|
74 | .map(plugin => {
|
75 | const namespacedName = 'mos-plugin-' + plugin.name
|
76 | if (allDeps.has(namespacedName)) {
|
77 | return Object.assign({}, plugin, {name: namespacedName})
|
78 | }
|
79 | if (allDeps.has(plugin.name)) {
|
80 | return plugin
|
81 | }
|
82 | throw new Error(`${plugin.name} is not in the dependencies`)
|
83 | })
|
84 | .map(plugin => Object.assign(plugin, {path: resolve.sync(plugin.name, { basedir: path.dirname(md.filePath) })}))
|
85 | .map(plugin => Object.assign(plugin, {path: normalizePath(plugin.path)}))
|
86 | .map(plugin => Object.assign(plugin, {register: require(plugin.path)}))
|
87 |
|
88 | const defaultPluginsWithOpts = defaultPlugins.reduce((defPlugins, defPlugin) => {
|
89 | const defPluginName = defPlugin.attributes.pkg && defPlugin.attributes.pkg.name || defPlugin.attributes.name
|
90 | const options = result.pkg.mos[defPluginName] || result.pkg.mos[defPluginName.replace(/^mos-plugin-/, '')]
|
91 | if (options === false) {
|
92 | return defPlugins
|
93 | }
|
94 | return defPlugins.concat({
|
95 | register: defPlugin,
|
96 | options: options
|
97 | })
|
98 | }, [])
|
99 |
|
100 | return mos(md, defaultPluginsWithOpts.concat(pkgPlugins))
|
101 | })
|
102 | .then(processor => processor.process())
|
103 |
|
104 | const mdExtensions = ['markdown', 'mdown', 'mkdn', 'mkd', 'md']
|
105 | const files = cli.input[isTest ? 1 : 0]
|
106 | const pattern = files
|
107 | ? path.resolve(cwd, files)
|
108 | : path.resolve(cwd, `{/**/,/}*.{${mdExtensions.join()}}`)
|
109 | const ignorePattern = cli.flags.x
|
110 | ? path.resolve(cwd, cli.flags.x)
|
111 | : null
|
112 |
|
113 | if (isTest) {
|
114 | if (cli.flags.tap !== true) {
|
115 | test.createStream()
|
116 | .pipe(tapDiff())
|
117 | .pipe(stdout)
|
118 | }
|
119 | test('markdown', t => {
|
120 | processFiles({
|
121 | process: processMD,
|
122 | pattern,
|
123 | ignorePattern,
|
124 | afterEachRender (opts) {
|
125 | const relativePath = normalizePath(getRelativePath(opts.filePath))
|
126 | t.equal(normalizeNewline(opts.newMD), normalizeNewline(opts.currentMD), relativePath)
|
127 | },
|
128 | })
|
129 | .then(() => t.end())
|
130 | .catch(err => { throw err })
|
131 | })
|
132 | return
|
133 | }
|
134 | processFiles({
|
135 | process: processMD,
|
136 | pattern,
|
137 | ignorePattern,
|
138 | afterEachRender (opts) {
|
139 | if (normalizeNewline(opts.newMD) !== normalizeNewline(opts.currentMD)) {
|
140 | fs.writeFileSync(opts.filePath, opts.newMD, {
|
141 | encoding: 'utf8',
|
142 | })
|
143 | const relativePath = normalizePath(getRelativePath(opts.filePath))
|
144 | console.log('updated', highlightPath(relativePath))
|
145 | }
|
146 | },
|
147 | })
|
148 | .catch(err => { throw err })
|
149 |
|
150 | function getRelativePath (filePath) {
|
151 | return relative(cwd, filePath)
|
152 | }
|