-
-
Notifications
You must be signed in to change notification settings - Fork 110
/
cli.ts
executable file
·175 lines (144 loc) · 4.12 KB
/
cli.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env node
// --
// Packages
import arg from 'arg';
import chalk from 'chalk';
import { watch, WatchOptions } from 'chokidar';
import getPort from 'get-port';
import getStdin from 'get-stdin';
import Listr from 'listr';
import path from 'path';
import { PackageJson } from '.';
import { Config, defaultConfig } from './lib/config';
import { closeBrowser } from './lib/generate-output';
import { help } from './lib/help';
import { convertMdToPdf } from './lib/md-to-pdf';
import { closeServer, serveDirectory } from './lib/serve-dir';
import { validateNodeVersion } from './lib/validate-node-version';
// --
// Configure CLI Arguments
export const cliFlags = arg({
'--help': Boolean,
'--version': Boolean,
'--basedir': String,
'--watch': Boolean,
'--watch-options': String,
'--stylesheet': [String],
'--css': String,
'--document-title': String,
'--body-class': [String],
'--page-media-type': String,
'--highlight-style': String,
'--marked-options': String,
'--html-pdf-options': String,
'--pdf-options': String,
'--launch-options': String,
'--gray-matter-options': String,
'--port': Number,
'--md-file-encoding': String,
'--stylesheet-encoding': String,
'--as-html': Boolean,
'--config-file': String,
'--devtools': Boolean,
// aliases
'-h': '--help',
'-v': '--version',
'-w': '--watch',
});
// --
// Run
main(cliFlags, defaultConfig).catch((error) => {
console.error(error);
process.exit(1);
});
// --
// Define Main Function
async function main(args: typeof cliFlags, config: Config) {
process.title = 'md-to-pdf';
if (!validateNodeVersion()) {
throw new Error('Please use a Node.js version that satisfies the version specified in the engines field.');
}
if (args['--version']) {
return console.log((require('../package.json') as PackageJson).version);
}
if (args['--help']) {
return help();
}
/**
* 1. Get input.
*/
const files = args._;
const stdin = await getStdin();
if (files.length === 0 && !stdin) {
return help();
}
/**
* 2. Read config file and merge it into the config object.
*/
if (args['--config-file']) {
try {
const configFile: Partial<Config> = require(path.resolve(args['--config-file']));
config = {
...config,
...configFile,
pdf_options: { ...config.pdf_options, ...configFile.pdf_options },
};
} catch (error) {
console.warn(chalk.red(`Warning: couldn't read config file: ${path.resolve(args['--config-file'])}`));
console.warn(error instanceof SyntaxError ? error.message : error);
}
}
/**
* 3. Start the file server.
*/
if (args['--basedir']) {
config.basedir = args['--basedir'];
}
config.port = args['--port'] ?? (await getPort());
const server = await serveDirectory(config);
/**
* 4. Either process stdin or create a Listr task for each file.
*/
if (stdin) {
await convertMdToPdf({ content: stdin }, config, { args })
.finally(async () => {
await closeBrowser();
await closeServer(server);
})
.catch((error: Error) => {
throw error;
});
return;
}
const getListrTask = (file: string) => ({
title: `generating ${args['--as-html'] ? 'HTML' : 'PDF'} from ${chalk.underline(file)}`,
task: async () => convertMdToPdf({ path: file }, config, { args }),
});
await new Listr(files.map(getListrTask), { concurrent: true, exitOnError: false })
.run()
.then(async () => {
if (args['--watch']) {
console.log(chalk.bgBlue('\n watching for changes \n'));
const watchOptions = args['--watch-options']
? (JSON.parse(args['--watch-options']) as WatchOptions)
: config.watch_options;
watch(files, watchOptions).on('change', async (file) =>
new Listr([getListrTask(file)], { exitOnError: false }).run().catch(console.error),
);
} else {
await closeBrowser();
await closeServer(server);
}
})
.catch((error: Error) => {
/**
* In watch mode the error needs to be shown immediately because the `main` function's catch handler will never execute.
*
* @todo is this correct or does `main` actually finish and the process is just kept alive because of the file server?
*/
if (args['--watch']) {
return console.error(error);
}
throw error;
});
}