Skip to content

Commit f093891

Browse files
author
Alexander Droste
committed
include build lib to allow direct install from this repo
1 parent 75d9bec commit f093891

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+8575
-1
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/lib
1+
#/lib
22
coverage
33
.nyc_output
44
/node_modules

lib/commands/build.js

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const streamArray = require('stream-array');
4+
const sharedOptions = require('./shared_options');
5+
const path = require('path');
6+
const fs = require('fs');
7+
const vfs = require('vinyl-fs');
8+
const chokidar = require('chokidar');
9+
const documentation = require('../');
10+
const _ = require('lodash');
11+
12+
module.exports.command = 'build [input..]';
13+
module.exports.describe = 'build documentation';
14+
15+
/**
16+
* Add yargs parsing for the build command
17+
* @param {Object} yargs module instance
18+
* @returns {Object} yargs with options
19+
* @private
20+
*/
21+
module.exports.builder = Object.assign(
22+
{},
23+
sharedOptions.sharedOutputOptions,
24+
sharedOptions.sharedInputOptions,
25+
{
26+
output: {
27+
describe:
28+
'output location. omit for stdout, otherwise is a filename ' +
29+
'for single-file outputs and a directory name for multi-file outputs like html',
30+
default: 'stdout',
31+
alias: 'o'
32+
}
33+
}
34+
);
35+
36+
/*
37+
* The `build` command. Requires either `--output` or the `callback` argument.
38+
* If the callback is provided, it is called with (error, formattedResult);
39+
* otherwise, formatted results are outputted based on the value of `--output`.
40+
*
41+
* The former case, with the callback, is used by the `serve` command, which is
42+
* just a thin wrapper around this one.
43+
*/
44+
module.exports.handler = function build(argv) {
45+
let watcher;
46+
argv._handled = true;
47+
48+
if (!argv.input.length) {
49+
try {
50+
argv.input = [
51+
JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8'))
52+
.main || 'index.js'
53+
];
54+
} catch (e) {
55+
throw new Error(
56+
'documentation was given no files and was not run in a module directory'
57+
);
58+
}
59+
}
60+
61+
if (argv.f === 'html' && argv.o === 'stdout') {
62+
throw new Error(
63+
'The HTML output mode requires a destination directory set with -o'
64+
);
65+
}
66+
67+
function generator() {
68+
return documentation
69+
.build(argv.input, argv)
70+
.then(comments =>
71+
documentation.formats[argv.format](comments, argv).then(onFormatted)
72+
)
73+
.catch(err => {
74+
/* eslint no-console: 0 */
75+
if (err instanceof Error) {
76+
console.error(err.stack);
77+
} else {
78+
console.error(err);
79+
}
80+
process.exit(1);
81+
});
82+
}
83+
84+
function onFormatted(output) {
85+
if (argv.watch) {
86+
updateWatcher();
87+
}
88+
89+
if (argv.output === 'stdout') {
90+
if (argv.watch) {
91+
// In watch mode, clear the screen first to make updated outputs
92+
// obvious.
93+
process.stdout.write('\u001b[2J');
94+
}
95+
process.stdout.write(output);
96+
} else if (Array.isArray(output)) {
97+
streamArray(output).pipe(vfs.dest(argv.output));
98+
} else {
99+
fs.writeFileSync(argv.output, output);
100+
}
101+
}
102+
103+
function updateWatcher() {
104+
if (!watcher) {
105+
watcher = chokidar.watch(argv.input);
106+
watcher.on('all', _.debounce(generator, 300));
107+
}
108+
documentation
109+
.expandInputs(argv.input, argv)
110+
.then(files =>
111+
watcher.add(
112+
files.map(data => (typeof data === 'string' ? data : data.file))
113+
)
114+
);
115+
}
116+
117+
return generator();
118+
};

lib/commands/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
/*
4+
* Maps command name to a command plugin module. Each command plugin module
5+
* must export a function that takes (documentation, parsedArgs), where
6+
* documentation is just the main module (index.js), and parsedArgs is
7+
* { inputs, options, command, commandOptions }
8+
*
9+
* Command modules should also export a `description`, which will be used in
10+
* the main CLI help, and optionally a `parseArgs(yargs, parentArgv)` function
11+
* to parse additional arguments.
12+
*/
13+
14+
module.exports = {
15+
build: require('./build'),
16+
serve: require('./serve'),
17+
lint: require('./lint'),
18+
readme: require('./readme')
19+
};

lib/commands/lint.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const documentation = require('../');
4+
const fs = require('fs');
5+
const path = require('path');
6+
const sharedOptions = require('./shared_options');
7+
8+
/* eslint no-console: 0 */
9+
10+
module.exports.command = 'lint [input..]';
11+
module.exports.description = 'check for common style and uniformity mistakes';
12+
module.exports.builder = {
13+
shallow: sharedOptions.sharedInputOptions.shallow
14+
};
15+
16+
/**
17+
* Wrap around the documentation.lint method and add the additional
18+
* behavior of printing to stdout and setting an exit status.
19+
*
20+
* @param {Object} argv cli arguments
21+
* @returns {undefined} has side-effects
22+
* @private
23+
*/
24+
module.exports.handler = function(argv) {
25+
argv._handled = true;
26+
if (!argv.input.length) {
27+
try {
28+
argv.input = [
29+
JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8'))
30+
.main || 'index.js'
31+
];
32+
} catch (e) {
33+
throw new Error(
34+
'documentation was given no files and was not run in a module directory'
35+
);
36+
}
37+
}
38+
documentation
39+
.lint(argv.input, argv)
40+
.then(lintOutput => {
41+
if (lintOutput) {
42+
console.log(lintOutput);
43+
process.exit(1);
44+
} else {
45+
process.exit(0);
46+
}
47+
})
48+
.catch(err => {
49+
/* eslint no-console: 0 */
50+
console.error(err);
51+
process.exit(1);
52+
});
53+
};

lib/commands/readme.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const remark = require('remark');
5+
const path = require('path');
6+
const documentation = require('../');
7+
const sharedOptions = require('./shared_options');
8+
const inject = require('mdast-util-inject');
9+
const chalk = require('chalk');
10+
const disparity = require('disparity');
11+
const getReadmeFile = require('../get-readme-file');
12+
13+
module.exports.command = 'readme [input..]';
14+
module.exports.description = 'inject documentation into your README.md';
15+
16+
const defaultReadmeFile = getReadmeFile('.');
17+
18+
/**
19+
* Add yargs parsing for the readme command
20+
* @param {Object} yargs module instance
21+
* @returns {Object} yargs with options
22+
* @private
23+
*/
24+
module.exports.builder = Object.assign(
25+
{},
26+
sharedOptions.sharedOutputOptions,
27+
sharedOptions.sharedInputOptions,
28+
{
29+
'readme-file': {
30+
describe: 'The markdown file into which to inject documentation',
31+
default: defaultReadmeFile
32+
},
33+
section: {
34+
alias: 's',
35+
describe:
36+
'The section heading after which to inject generated documentation',
37+
required: true
38+
},
39+
'diff-only': {
40+
alias: 'd',
41+
describe:
42+
'Instead of updating the given README with the generated documentation,' +
43+
' just check if its contents match, exiting nonzero if not.',
44+
default: false
45+
},
46+
quiet: {
47+
alias: 'q',
48+
describe: 'Quiet mode: do not print messages or README diff to stdout.',
49+
default: false
50+
}
51+
}
52+
);
53+
54+
/**
55+
* Insert API documentation into a Markdown readme
56+
* @private
57+
* @param {Object} argv args from the CLI option parser
58+
* @returns {undefined} has the side-effect of writing a file or printing to stdout
59+
*/
60+
module.exports.handler = function readme(argv) {
61+
argv._handled = true;
62+
63+
if (!argv.input.length) {
64+
try {
65+
argv.input = [
66+
JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8'))
67+
.main || 'index.js'
68+
];
69+
} catch (e) {
70+
throw new Error(
71+
'documentation was given no files and was not run in a module directory'
72+
);
73+
}
74+
}
75+
76+
argv.noReferenceLinks = true;
77+
argv.format = 'remark';
78+
/* eslint no-console: 0 */
79+
const log = (...data) => {
80+
if (!argv.q) {
81+
console.log.apply(console, data);
82+
}
83+
};
84+
85+
const readmeContent = fs.readFileSync(argv.readmeFile, 'utf8');
86+
87+
documentation
88+
.build(argv.input, argv)
89+
.then(comments => documentation.formats.remark(comments, argv))
90+
.then(docsAst =>
91+
remark()
92+
.use(plugin, {
93+
section: argv.section,
94+
toInject: JSON.parse(docsAst)
95+
})
96+
.process(readmeContent)
97+
)
98+
.then(file => {
99+
const diffOutput = disparity.unified(readmeContent, file.contents, {
100+
paths: [argv.readmeFile, argv.readmeFile]
101+
});
102+
if (!diffOutput.length) {
103+
log(`${argv.readmeFile} is up to date.`);
104+
process.exit(0);
105+
}
106+
107+
if (argv.d) {
108+
log(
109+
chalk.bold(`${argv.readmeFile} needs the following updates:`),
110+
`\n${diffOutput}`
111+
);
112+
process.exit(1);
113+
} else {
114+
log(chalk.bold(`Updating ${argv.readmeFile}`), `\n${diffOutput}`);
115+
}
116+
117+
fs.writeFileSync(argv.readmeFile, file.contents);
118+
})
119+
.catch(err => {
120+
console.error(err);
121+
process.exit(1);
122+
});
123+
};
124+
125+
// wrap the inject utility as an remark plugin
126+
function plugin(options) {
127+
return function transform(targetAst, file, next) {
128+
if (!inject(options.section, targetAst, options.toInject)) {
129+
return next(new Error(`Heading ${options.section} not found.`));
130+
}
131+
next();
132+
};
133+
}

0 commit comments

Comments
 (0)