1 | #!/usr/bin/env node
|
2 | /**
|
3 | * Copyright (c) Facebook, Inc. and its affiliates.
|
4 | *
|
5 | * This source code is licensed under the MIT license found in the
|
6 | * LICENSE file in the root directory of this source tree.
|
7 | */
|
8 |
|
9 | var fs = require('fs');
|
10 | var path = require('path');
|
11 |
|
12 | var flowRemoveTypes = require('./index');
|
13 |
|
14 | var usage = 'Usage: flow-remove-types [options] [sources] \n' +
|
15 |
|
16 | '\nOptions:\n' +
|
17 | ' -h, --help Show this message\n' +
|
18 | ' -v, --version Prints the current version of flow-remove-types\n' +
|
19 | ' -i, --ignore Paths to ignore, Regular Expression\n' +
|
20 | ' -x, --extensions File extensions to transform\n' +
|
21 | ' -o, --out-file The file path to write transformed file to\n' +
|
22 | ' -d, --out-dir The directory path to write transformed files within\n' +
|
23 | ' -a, --all Transform all files, not just those with a @flow comment\n' +
|
24 | ' -p, --pretty Remove flow types without replacing with spaces, \n' +
|
25 | ' producing prettier output but may require using source maps\n' +
|
26 | ' --ignore-uninitialized-fields\n' +
|
27 | ' Removes uninitialized class fields (`foo;`, `foo: string;`)\n' +
|
28 | ' completely rather than only removing the type. THIS IS NOT\n' +
|
29 | ' SPEC COMPLIANT! Instead, use `declare foo: string;` for\n' +
|
30 | ' type-only fields.\n' +
|
31 | ' -m, --sourcemaps Also output source map files. Optionally pass "inline"\n' +
|
32 | ' -q, --quiet Does not produce any output concerning successful progress.\n' +
|
33 |
|
34 | '\nExamples:\n' +
|
35 |
|
36 | '\nTransform one file:\n' +
|
37 | ' flow-remove-types --out-file output.js input.js\n' +
|
38 |
|
39 | '\nTransform many files:\n' +
|
40 | ' flow-remove-types --out-dir out/ input1.js input2.js\n' +
|
41 |
|
42 | '\nTransform files in directory:\n' +
|
43 | ' flow-remove-types --out-dir out/ indir/\n' +
|
44 |
|
45 | '\nTransform files with source maps:\n' +
|
46 | ' flow-remove-types --out-dir out/ indir/ --sourcemaps\n' +
|
47 |
|
48 | '\nTransform files with inline source maps:\n' +
|
49 | ' flow-remove-types --out-dir out/ indir/ --sourcemaps inline\n' +
|
50 |
|
51 | '\nTransform stdin:\n' +
|
52 | ' cat input.js | flow-remove-types > output.js\n';
|
53 |
|
54 | var _memo = {};
|
55 |
|
56 | function mkdirp(dirpath) {
|
57 | if (_memo[dirpath]) {
|
58 | return;
|
59 | }
|
60 | _memo[dirpath] = true;
|
61 | try {
|
62 | fs.mkdirSync(dirpath);
|
63 | } catch (err) {
|
64 | if (err.code === 'ENOENT') {
|
65 | mkdirp(path.dirname(dirpath));
|
66 | fs.mkdirSync(dirpath);
|
67 | } else {
|
68 | try {
|
69 | stat = fs.statSync(dirpath);
|
70 | } catch (ignored) {
|
71 | throw err;
|
72 | }
|
73 | if (!stat.isDirectory()) {
|
74 | throw err;
|
75 | }
|
76 | }
|
77 | }
|
78 | }
|
79 |
|
80 | // Collect arguments
|
81 | var ignore = /node_modules/;
|
82 | var extensions = [ '.js', '.mjs', '.cjs', '.jsx', '.flow', '.es6' ];
|
83 | var outDir;
|
84 | var outFile;
|
85 | var all;
|
86 | var pretty;
|
87 | var ignoreUninitializedFields;
|
88 | var sourceMaps;
|
89 | var inlineSourceMaps;
|
90 | var quiet;
|
91 | var sources = [];
|
92 | var i = 2;
|
93 | while (i < process.argv.length) {
|
94 | var arg = process.argv[i++];
|
95 | if (arg === '-h' || arg === '--help') {
|
96 | process.stdout.write(usage);
|
97 | process.exit(0);
|
98 | } else if (arg === '-v' || arg === '--version') {
|
99 | process.stdout.write('v' + require('./package').version);
|
100 | process.exit(0);
|
101 | } else if (arg === '-i' || arg === '--ignore') {
|
102 | ignore = new RegExp(process.argv[i++]);
|
103 | } else if (arg === '-x' || arg === '--extensions') {
|
104 | extensions = process.argv[i++].split(',');
|
105 | } else if (arg === '-o' || arg === '--out-file') {
|
106 | outFile = process.argv[i++];
|
107 | } else if (arg === '-d' || arg === '--out-dir') {
|
108 | outDir = process.argv[i++];
|
109 | } else if (arg === '-a' || arg === '--all') {
|
110 | all = true;
|
111 | } else if (arg === '-p' || arg === '--pretty') {
|
112 | pretty = true;
|
113 | } else if (arg === '--ignore-uninitialized-fields') {
|
114 | ignoreUninitializedFields = true;
|
115 | } else if (arg === '-m' || arg === '--sourcemaps') {
|
116 | sourceMaps = true;
|
117 | if (process.argv[i] === 'inline') {
|
118 | inlineSourceMaps = true;
|
119 | i++;
|
120 | }
|
121 | } else if (arg === '-q' || arg === '--quiet') {
|
122 | quiet = true;
|
123 | } else {
|
124 | sources.push(arg);
|
125 | }
|
126 | }
|
127 |
|
128 | function info(msg) {
|
129 | if (!quiet) {
|
130 | process.stderr.write(msg);
|
131 | }
|
132 | }
|
133 |
|
134 | function error(msg) {
|
135 | process.stderr.write('\n\033[31m ' + msg + '\033[0m\n\n');
|
136 | process.exit(1);
|
137 | }
|
138 |
|
139 | // Validate arguments
|
140 | if (outDir && outFile) {
|
141 | error('Only specify one of --out-dir or --out-file');
|
142 | }
|
143 |
|
144 | if (outDir && sources.length === 0) {
|
145 | error('Must specify files when providing --out-dir');
|
146 | }
|
147 |
|
148 | if (!outDir && !outFile && sourceMaps && !inlineSourceMaps) {
|
149 | error('Must specify either an output path or inline source maps');
|
150 | }
|
151 |
|
152 | // Ensure all sources exist
|
153 | for (var i = 0; i < sources.length; i++) {
|
154 | try {
|
155 | var stat = fs.lstatSync(sources[i]);
|
156 | if (sources.length > 1 && !stat.isFile()) {
|
157 | error('Source "' + sources[i] + '" is not a file.');
|
158 | }
|
159 | } catch (err) {
|
160 | error('Source "' + sources[i] + '" does not exist.');
|
161 | }
|
162 | }
|
163 |
|
164 | // Process stdin if no sources were provided
|
165 | if (sources.length === 0) {
|
166 | var content = '';
|
167 | process.stdin.setEncoding('utf-8');
|
168 | process.stdin.resume();
|
169 | process.stdin.on('data', function (str) { content += str; });
|
170 | process.stdin.on('end', function () {
|
171 | transformAndOutput(content, outFile);
|
172 | });
|
173 | return;
|
174 | }
|
175 |
|
176 | var isDirSource = sources.length === 1 && fs.statSync(sources[0]).isDirectory();
|
177 |
|
178 | if ((sources.length > 1 || isDirSource) && !outDir) {
|
179 | error('Multiple files require providing --out-dir');
|
180 | }
|
181 |
|
182 | // Process multiple files
|
183 | for (var i = 0; i < sources.length; i++) {
|
184 | var source = sources[i];
|
185 | var stat = fs.lstatSync(source);
|
186 | if (stat.isDirectory()) {
|
187 | var files = fs.readdirSync(source);
|
188 | for (var j = 0; j < files.length; j++) {
|
189 | var subSource = path.join(source, files[j]);
|
190 | if (!ignore || !ignore.test(subSource)) {
|
191 | sources.push(subSource);
|
192 | }
|
193 | }
|
194 | } else if (stat.isFile() && extensions.indexOf(path.extname(source)) !== -1) {
|
195 | if (outDir) {
|
196 | outFile = path.join(outDir, isDirSource ? path.relative(sources[0], source) : source);
|
197 | mkdirp(path.dirname(outFile));
|
198 | }
|
199 | var content = fs.readFileSync(source, 'utf8');
|
200 | transformAndOutput(content, outFile, source);
|
201 | }
|
202 | }
|
203 |
|
204 | function transformAndOutput(content, outFile, source) {
|
205 | var fileName = source || '<stdin>';
|
206 | var result = transformSource(content, fileName);
|
207 | var code = result.toString();
|
208 |
|
209 | if (sourceMaps) {
|
210 | var map = result.generateMap();
|
211 | delete map.file;
|
212 | map.sources[0] = fileName;
|
213 |
|
214 | if (source) {
|
215 | delete map.sourcesContent;
|
216 | if (outFile) {
|
217 | map.sources[0] = path.join(path.relative(path.dirname(outFile), path.dirname(source)), path.basename(source));
|
218 | }
|
219 | } else {
|
220 | map.sourcesContent = [content];
|
221 | }
|
222 |
|
223 | code += '\n//# sourceMappingURL=' + (inlineSourceMaps ?
|
224 | 'data:application/json;charset=utf-8;base64,' + btoa(JSON.stringify(map)) :
|
225 | path.basename(outFile) + '.map'
|
226 | ) + '\n';
|
227 | }
|
228 |
|
229 | if (outFile) {
|
230 | fs.writeFileSync(outFile, code);
|
231 | info(fileName + '\n \u21B3 \033[32m' + outFile + '\033[0m\n');
|
232 | if (sourceMaps && !inlineSourceMaps) {
|
233 | var mapOutFile = outFile + '.map';
|
234 | fs.writeFileSync(mapOutFile, JSON.stringify(map) + '\n');
|
235 | info('\033[2m \u21B3 \033[32m' + mapOutFile + '\033[0m\n');
|
236 | }
|
237 | } else {
|
238 | process.stdout.write(code);
|
239 | }
|
240 | }
|
241 |
|
242 | function btoa(str) {
|
243 | // There are 5.x versions of Node that have `Buffer.from` but don't have the
|
244 | // `Buffer.from(string)` overload, so check for other new methods to be sure.
|
245 | return (Buffer.from && Buffer.alloc && Buffer.allocUnsafe
|
246 | ? Buffer.from(str)
|
247 | : new Buffer(str)
|
248 | ).toString('base64');
|
249 | }
|
250 |
|
251 | function transformSource(content, filepath) {
|
252 | try {
|
253 | return flowRemoveTypes(content, {
|
254 | all: all,
|
255 | pretty: pretty,
|
256 | ignoreUninitializedFields: ignoreUninitializedFields,
|
257 | });
|
258 | } catch (error) {
|
259 | if (error.loc) {
|
260 | var line = error.loc.line - 1;
|
261 | var col = error.loc.column;
|
262 | var text = content.split(/\r\n?|\n|\u2028|\u2029/)[line];
|
263 | process.stderr.write(
|
264 | filepath + '\n' +
|
265 | ' \u21B3 \033[31mSyntax Error: ' + error.message + '\033[0m\n' +
|
266 | ' \033[90m' + line + ': \033[0m' +
|
267 | text.slice(0, col) + '\033[7;31m' + text[col] + '\033[0m' + text.slice(col + 1) + '\n'
|
268 | );
|
269 | process.exit(1);
|
270 | }
|
271 | throw error;
|
272 | }
|
273 | }
|