UNPKG

16.9 kBPlain TextView Raw
1#!/usr/bin/env node
2"use strict";
3
4var path = require('path');
5var fs = require('../lib/less-node/fs').default;
6var os = require('os');
7var utils = require('../lib/less/utils');
8var Constants = require('../lib/less/constants');
9
10var less = require('../lib/less-node').default;
11
12var errno;
13var mkdirp;
14
15try {
16 errno = require('errno');
17} catch (err) {
18 errno = null;
19}
20
21var pluginManager = new less.PluginManager(less);
22var fileManager = new less.FileManager();
23var plugins = [];
24var queuePlugins = [];
25var args = process.argv.slice(1);
26var silent = false;
27var verbose = false;
28var options = less.options;
29options.plugins = plugins;
30options.reUsePluginManager = true;
31var sourceMapOptions = {};
32var continueProcessing = true;
33
34var checkArgFunc = function checkArgFunc(arg, option) {
35 if (!option) {
36 console.error("".concat(arg, " option requires a parameter"));
37 continueProcessing = false;
38 process.exitCode = 1;
39 return false;
40 }
41
42 return true;
43};
44
45var checkBooleanArg = function checkBooleanArg(arg) {
46 var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
47
48 if (!onOff) {
49 console.error(" unable to parse ".concat(arg, " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no"));
50 continueProcessing = false;
51 process.exitCode = 1;
52 return false;
53 }
54
55 return Boolean(onOff[2]);
56};
57
58var parseVariableOption = function parseVariableOption(option, variables) {
59 var parts = option.split('=', 2);
60 variables[parts[0]] = parts[1];
61};
62
63var sourceMapFileInline = false;
64
65function printUsage() {
66 less.lesscHelper.printUsage();
67
68 pluginManager.Loader.printUsage(plugins);
69 continueProcessing = false;
70}
71
72function render() {
73 if (!continueProcessing) {
74 return;
75 }
76
77 var input = args[1];
78
79 if (input && input != '-') {
80 input = path.resolve(process.cwd(), input);
81 }
82
83 var output = args[2];
84 var outputbase = args[2];
85
86 if (output) {
87 output = path.resolve(process.cwd(), output);
88 }
89
90 if (options.sourceMap) {
91 sourceMapOptions.sourceMapInputFilename = input;
92
93 if (!sourceMapOptions.sourceMapFullFilename) {
94 if (!output && !sourceMapFileInline) {
95 console.error('the sourcemap option only has an optional filename if the css filename is given');
96 console.error('consider adding --source-map-map-inline which embeds the sourcemap into the css');
97 process.exitCode = 1;
98 return;
99 } // its in the same directory, so always just the basename
100
101
102 if (output) {
103 sourceMapOptions.sourceMapOutputFilename = path.basename(output);
104 sourceMapOptions.sourceMapFullFilename = "".concat(output, ".map");
105 } // its in the same directory, so always just the basename
106
107
108 if ('sourceMapFullFilename' in sourceMapOptions) {
109 sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
110 }
111 } else if (options.sourceMap && !sourceMapFileInline) {
112 var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename);
113 var mapDir = path.dirname(mapFilename);
114 var outputDir = path.dirname(output); // find the path from the map to the output file
115
116 sourceMapOptions.sourceMapOutputFilename = path.join(path.relative(mapDir, outputDir), path.basename(output)); // make the sourcemap filename point to the sourcemap relative to the css file output directory
117
118 sourceMapOptions.sourceMapFilename = path.join(path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
119 }
120
121 if (sourceMapOptions.sourceMapURL && sourceMapOptions.disableSourcemapAnnotation) {
122 console.error('You cannot provide flag --source-map-url with --source-map-no-annotation.');
123 console.error('Please remove one of those flags.');
124 process.exitcode = 1;
125 return;
126 }
127 }
128
129 if (sourceMapOptions.sourceMapBasepath === undefined) {
130 sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
131 }
132
133 if (sourceMapOptions.sourceMapRootpath === undefined) {
134 var pathToMap = path.dirname((sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename) || '.');
135 var pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename || '.');
136 sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
137 }
138
139 if (!input) {
140 console.error('lessc: no input files');
141 console.error('');
142 printUsage();
143 process.exitCode = 1;
144 return;
145 }
146
147 var ensureDirectory = function ensureDirectory(filepath) {
148 var dir = path.dirname(filepath);
149 var cmd;
150 var existsSync = fs.existsSync || path.existsSync;
151
152 if (!existsSync(dir)) {
153 if (mkdirp === undefined) {
154 try {
155 mkdirp = require('make-dir');
156 } catch (e) {
157 mkdirp = null;
158 }
159 }
160
161 cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
162 cmd(dir);
163 }
164 };
165
166 if (options.depends) {
167 if (!outputbase) {
168 console.error('option --depends requires an output path to be specified');
169 process.exitCode = 1;
170 return;
171 }
172
173 process.stdout.write("".concat(outputbase, ": "));
174 }
175
176 if (!sourceMapFileInline) {
177 var writeSourceMap = function writeSourceMap() {
178 var output = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
179 var onDone = arguments.length > 1 ? arguments[1] : undefined;
180 var filename = sourceMapOptions.sourceMapFullFilename;
181 ensureDirectory(filename);
182
183 fs.writeFile(filename, output, 'utf8', function (err) {
184 if (err) {
185 var description = 'Error: ';
186
187 if (errno && errno.errno[err.errno]) {
188 description += errno.errno[err.errno].description;
189 } else {
190 description += "".concat(err.code, " ").concat(err.message);
191 }
192
193 console.error("lessc: failed to create file ".concat(filename));
194 console.error(description);
195 process.exitCode = 1;
196 } else {
197 less.logger.info("lessc: wrote ".concat(filename));
198 }
199
200 onDone();
201 });
202 };
203 }
204
205 var writeSourceMapIfNeeded = function writeSourceMapIfNeeded(output, onDone) {
206 if (options.sourceMap && !sourceMapFileInline) {
207 writeSourceMap(output, onDone);
208 } else {
209 onDone();
210 }
211 };
212
213 var writeOutput = function writeOutput(output, result, onSuccess) {
214 if (options.depends) {
215 onSuccess();
216 } else if (output) {
217 ensureDirectory(output);
218
219 fs.writeFile(output, result.css, {
220 encoding: 'utf8'
221 }, function (err) {
222 if (err) {
223 var description = 'Error: ';
224
225 if (errno && errno.errno[err.errno]) {
226 description += errno.errno[err.errno].description;
227 } else {
228 description += "".concat(err.code, " ").concat(err.message);
229 }
230
231 console.error("lessc: failed to create file ".concat(output));
232 console.error(description);
233 process.exitCode = 1;
234 } else {
235 less.logger.info("lessc: wrote ".concat(output));
236
237 onSuccess();
238 }
239 });
240 } else if (!options.depends) {
241 process.stdout.write(result.css);
242 onSuccess();
243 }
244 };
245
246 var logDependencies = function logDependencies(options, result) {
247 if (options.depends) {
248 var depends = '';
249
250 for (var i = 0; i < result.imports.length; i++) {
251 depends += "".concat(result.imports[i], " ");
252 }
253
254 console.log(depends);
255 }
256 };
257
258 var parseLessFile = function parseLessFile(e, data) {
259 if (e) {
260 console.error("lessc: ".concat(e.message));
261 process.exitCode = 1;
262 return;
263 }
264
265 data = data.replace(/^\uFEFF/, '');
266 options.paths = [path.dirname(input)].concat(options.paths);
267 options.filename = input;
268
269 if (options.lint) {
270 options.sourceMap = false;
271 }
272
273 sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
274
275 if (options.sourceMap) {
276 options.sourceMap = sourceMapOptions;
277 }
278
279 less.logger.addListener({
280 info: function info(msg) {
281 if (verbose) {
282 console.log(msg);
283 }
284 },
285 warn: function warn(msg) {
286 // do not show warning if the silent option is used
287 if (!silent) {
288 console.warn(msg);
289 }
290 },
291 error: function error(msg) {
292 console.error(msg);
293 }
294 });
295
296 less.render(data, options).then(function (result) {
297 if (!options.lint) {
298 writeOutput(output, result, function () {
299 writeSourceMapIfNeeded(result.map, function () {
300 logDependencies(options, result);
301 });
302 });
303 }
304 }, function (err) {
305 if (!options.silent) {
306 console.error(err.toString({
307 stylize: options.color && less.lesscHelper.stylize
308 }));
309 }
310
311 process.exitCode = 1;
312 });
313 };
314
315 if (input != '-') {
316 fs.readFile(input, 'utf8', parseLessFile);
317 } else {
318 process.stdin.resume();
319 process.stdin.setEncoding('utf8');
320 var buffer = '';
321 process.stdin.on('data', function (data) {
322 buffer += data;
323 });
324 process.stdin.on('end', function () {
325 parseLessFile(false, buffer);
326 });
327 }
328}
329
330function processPluginQueue() {
331 var x = 0;
332
333 function pluginError(name) {
334 console.error("Unable to load plugin ".concat(name, " please make sure that it is installed under or at the same level as less"));
335 process.exitCode = 1;
336 }
337
338 function pluginFinished(plugin) {
339 x++;
340 plugins.push(plugin);
341
342 if (x === queuePlugins.length) {
343 render();
344 }
345 }
346
347 queuePlugins.forEach(function (queue) {
348 var context = utils.clone(options);
349 pluginManager.Loader.loadPlugin(queue.name, process.cwd(), context, less.environment, fileManager).then(function (data) {
350 pluginFinished({
351 fileContent: data.contents,
352 filename: data.filename,
353 options: queue.options
354 });
355 }).catch(function () {
356 pluginError(queue.name);
357 });
358 });
359} // self executing function so we can return
360
361
362(function () {
363 args = args.filter(function (arg) {
364 var match;
365 match = arg.match(/^-I(.+)$/);
366
367 if (match) {
368 options.paths.push(match[1]);
369 return false;
370 }
371
372 match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
373
374 if (match) {
375 arg = match[1];
376 } else {
377 return arg;
378 }
379
380 switch (arg) {
381 case 'v':
382 case 'version':
383 console.log("lessc ".concat(less.version.join('.'), " (Less Compiler) [JavaScript]"));
384 continueProcessing = false;
385 break;
386
387 case 'verbose':
388 verbose = true;
389 break;
390
391 case 's':
392 case 'silent':
393 silent = true;
394 break;
395
396 case 'l':
397 case 'lint':
398 options.lint = true;
399 break;
400
401 case 'strict-imports':
402 options.strictImports = true;
403 break;
404
405 case 'h':
406 case 'help':
407 printUsage();
408 break;
409
410 case 'x':
411 case 'compress':
412 options.compress = true;
413 break;
414
415 case 'insecure':
416 options.insecure = true;
417 break;
418
419 case 'M':
420 case 'depends':
421 options.depends = true;
422 break;
423
424 case 'max-line-len':
425 if (checkArgFunc(arg, match[2])) {
426 options.maxLineLen = parseInt(match[2], 10);
427
428 if (options.maxLineLen <= 0) {
429 options.maxLineLen = -1;
430 }
431 }
432
433 break;
434
435 case 'no-color':
436 options.color = false;
437 break;
438
439 case 'js':
440 options.javascriptEnabled = true;
441 break;
442
443 case 'no-js':
444 console.error('The "--no-js" argument is deprecated, as inline JavaScript ' + 'is disabled by default. Use "--js" to enable inline JavaScript (not recommended).');
445 break;
446
447 case 'include-path':
448 if (checkArgFunc(arg, match[2])) {
449 // ; supported on windows.
450 // : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2
451 options.paths = match[2].split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':').map(function (p) {
452 if (p) {
453 return path.resolve(process.cwd(), p);
454 }
455 });
456 }
457
458 break;
459
460 case 'line-numbers':
461 if (checkArgFunc(arg, match[2])) {
462 options.dumpLineNumbers = match[2];
463 }
464
465 break;
466
467 case 'source-map':
468 options.sourceMap = true;
469
470 if (match[2]) {
471 sourceMapOptions.sourceMapFullFilename = match[2];
472 }
473
474 break;
475
476 case 'source-map-rootpath':
477 if (checkArgFunc(arg, match[2])) {
478 sourceMapOptions.sourceMapRootpath = match[2];
479 }
480
481 break;
482
483 case 'source-map-basepath':
484 if (checkArgFunc(arg, match[2])) {
485 sourceMapOptions.sourceMapBasepath = match[2];
486 }
487
488 break;
489
490 case 'source-map-inline':
491 case 'source-map-map-inline':
492 sourceMapFileInline = true;
493 options.sourceMap = true;
494 break;
495
496 case 'source-map-include-source':
497 case 'source-map-less-inline':
498 sourceMapOptions.outputSourceFiles = true;
499 break;
500
501 case 'source-map-url':
502 if (checkArgFunc(arg, match[2])) {
503 sourceMapOptions.sourceMapURL = match[2];
504 }
505
506 break;
507
508 case 'source-map-no-annotation':
509 sourceMapOptions.disableSourcemapAnnotation = true;
510 break;
511
512 case 'rp':
513 case 'rootpath':
514 if (checkArgFunc(arg, match[2])) {
515 options.rootpath = match[2].replace(/\\/g, '/');
516 }
517
518 break;
519
520 case 'ie-compat':
521 console.warn('The --ie-compat option is deprecated, as it has no effect on compilation.');
522 break;
523
524 case 'relative-urls':
525 console.warn('The --relative-urls option has been deprecated. Use --rewrite-urls=all.');
526 options.rewriteUrls = Constants.RewriteUrls.ALL;
527 break;
528
529 case 'ru':
530 case 'rewrite-urls':
531 var m = match[2];
532
533 if (m) {
534 if (m === 'local') {
535 options.rewriteUrls = Constants.RewriteUrls.LOCAL;
536 } else if (m === 'off') {
537 options.rewriteUrls = Constants.RewriteUrls.OFF;
538 } else if (m === 'all') {
539 options.rewriteUrls = Constants.RewriteUrls.ALL;
540 } else {
541 console.error("Unknown rewrite-urls argument ".concat(m));
542 continueProcessing = false;
543 process.exitCode = 1;
544 }
545 } else {
546 options.rewriteUrls = Constants.RewriteUrls.ALL;
547 }
548
549 break;
550
551 case 'sm':
552 case 'strict-math':
553 console.warn('The --strict-math option has been deprecated. Use --math=strict.');
554
555 if (checkArgFunc(arg, match[2])) {
556 if (checkBooleanArg(match[2])) {
557 options.math = Constants.Math.PARENS;
558 }
559 }
560
561 break;
562
563 case 'm':
564 case 'math':
565 var m = match[2];
566 if (checkArgFunc(arg, m)) {
567 if (m === 'always') {
568 console.warn('--math=always is deprecated and will be removed in the future.');
569 options.math = Constants.Math.ALWAYS;
570 } else if (m === 'parens-division') {
571 options.math = Constants.Math.PARENS_DIVISION;
572 } else if (m === 'parens' || m === 'strict') {
573 options.math = Constants.Math.PARENS;
574 } else if (m === 'strict-legacy') {
575 console.warn('--math=strict-legacy has been removed. Defaulting to --math=strict');
576 options.math = Constants.Math.PARENS;
577 }
578 }
579
580 break;
581
582 case 'su':
583 case 'strict-units':
584 if (checkArgFunc(arg, match[2])) {
585 options.strictUnits = checkBooleanArg(match[2]);
586 }
587
588 break;
589
590 case 'global-var':
591 if (checkArgFunc(arg, match[2])) {
592 if (!options.globalVars) {
593 options.globalVars = {};
594 }
595
596 parseVariableOption(match[2], options.globalVars);
597 }
598
599 break;
600
601 case 'modify-var':
602 if (checkArgFunc(arg, match[2])) {
603 if (!options.modifyVars) {
604 options.modifyVars = {};
605 }
606
607 parseVariableOption(match[2], options.modifyVars);
608 }
609
610 break;
611
612 case 'url-args':
613 if (checkArgFunc(arg, match[2])) {
614 options.urlArgs = match[2];
615 }
616
617 break;
618
619 case 'plugin':
620 var splitupArg = match[2].match(/^([^=]+)(=(.*))?/);
621 var name = splitupArg[1];
622 var pluginOptions = splitupArg[3];
623 queuePlugins.push({
624 name: name,
625 options: pluginOptions
626 });
627 break;
628
629 default:
630 queuePlugins.push({
631 name: arg,
632 options: match[2],
633 default: true
634 });
635 break;
636 }
637 });
638
639 if (queuePlugins.length > 0) {
640 processPluginQueue();
641 } else {
642 render();
643 }
644})();
\No newline at end of file