123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- #!/usr/bin/env node
- var Emitter = require('events').EventEmitter,
- forEach = require('async-foreach').forEach,
- Gaze = require('gaze'),
- meow = require('meow'),
- util = require('util'),
- path = require('path'),
- glob = require('glob'),
- sass = require('../lib'),
- render = require('../lib/render'),
- watcher = require('../lib/watcher'),
- stdout = require('stdout-stream'),
- stdin = require('get-stdin'),
- fs = require('fs');
- /**
- * Initialize CLI
- */
- var cli = meow({
- pkg: '../package.json',
- version: sass.info,
- help: [
- 'Usage:',
- ' node-sass [options] <input.scss>',
- ' cat <input.scss> | node-sass [options] > output.css',
- '',
- 'Example: Compile foobar.scss to foobar.css',
- ' node-sass --output-style compressed foobar.scss > foobar.css',
- ' cat foobar.scss | node-sass --output-style compressed > foobar.css',
- '',
- 'Example: Watch the sass directory for changes, compile with sourcemaps to the css directory',
- ' node-sass --watch --recursive --output css',
- ' --source-map true --source-map-contents sass',
- '',
- 'Options',
- ' -w, --watch Watch a directory or file',
- ' -r, --recursive Recursively watch directories or files',
- ' -o, --output Output directory',
- ' -x, --omit-source-map-url Omit source map URL comment from output',
- ' -i, --indented-syntax Treat data from stdin as sass code (versus scss)',
- ' -q, --quiet Suppress log output except on error',
- ' -v, --version Prints version info',
- ' --output-style CSS output style (nested | expanded | compact | compressed)',
- ' --indent-type Indent type for output CSS (space | tab)',
- ' --indent-width Indent width; number of spaces or tabs (maximum value: 10)',
- ' --linefeed Linefeed style (cr | crlf | lf | lfcr)',
- ' --source-comments Include debug info in output',
- ' --source-map Emit source map (boolean, or path to output .map file)',
- ' --source-map-contents Embed include contents in map',
- ' --source-map-embed Embed sourceMappingUrl as data URI',
- ' --source-map-root Base path, will be emitted in source-map as is',
- ' --include-path Path to look for imported files',
- ' --follow Follow symlinked directories',
- ' --precision The amount of precision allowed in decimal numbers',
- ' --error-bell Output a bell character on errors',
- ' --importer Path to .js file containing custom importer',
- ' --functions Path to .js file containing custom functions',
- ' --help Print usage info'
- ].join('\n')
- }, {
- boolean: [
- 'error-bell',
- 'follow',
- 'indented-syntax',
- 'omit-source-map-url',
- 'quiet',
- 'recursive',
- 'source-map-embed',
- 'source-map-contents',
- 'source-comments',
- 'watch'
- ],
- string: [
- 'functions',
- 'importer',
- 'include-path',
- 'indent-type',
- 'linefeed',
- 'output',
- 'output-style',
- 'precision',
- 'source-map-root'
- ],
- alias: {
- c: 'source-comments',
- i: 'indented-syntax',
- q: 'quiet',
- o: 'output',
- r: 'recursive',
- x: 'omit-source-map-url',
- v: 'version',
- w: 'watch'
- },
- default: {
- 'include-path': process.cwd(),
- 'indent-type': 'space',
- 'indent-width': 2,
- linefeed: 'lf',
- 'output-style': 'nested',
- precision: 5,
- quiet: false,
- recursive: true
- }
- });
- /**
- * Is a Directory
- *
- * @param {String} filePath
- * @returns {Boolean}
- * @api private
- */
- function isDirectory(filePath) {
- var isDir = false;
- try {
- var absolutePath = path.resolve(filePath);
- isDir = fs.statSync(absolutePath).isDirectory();
- } catch (e) {
- isDir = e.code === 'ENOENT';
- }
- return isDir;
- }
- /**
- * Get correct glob pattern
- *
- * @param {Object} options
- * @returns {String}
- * @api private
- */
- function globPattern(options) {
- return options.recursive ? '**/*.{sass,scss}' : '*.{sass,scss}';
- }
- /**
- * Create emitter
- *
- * @api private
- */
- function getEmitter() {
- var emitter = new Emitter();
- emitter.on('error', function(err) {
- if (options.errorBell) {
- err += '\x07';
- }
- console.error(err);
- if (!options.watch) {
- process.exit(1);
- }
- });
- emitter.on('warn', function(data) {
- if (!options.quiet) {
- console.warn(data);
- }
- });
- emitter.on('info', function(data) {
- if (!options.quiet) {
- console.info(data);
- }
- });
- emitter.on('log', stdout.write.bind(stdout));
- return emitter;
- }
- /**
- * Construct options
- *
- * @param {Array} arguments
- * @param {Object} options
- * @api private
- */
- function getOptions(args, options) {
- var cssDir, sassDir, file, mapDir;
- options.src = args[0];
- if (args[1]) {
- options.dest = path.resolve(args[1]);
- } else if (options.output) {
- options.dest = path.join(
- path.resolve(options.output),
- [path.basename(options.src, path.extname(options.src)), '.css'].join('')); // replace ext.
- }
- if (options.directory) {
- sassDir = path.resolve(options.directory);
- file = path.relative(sassDir, args[0]);
- cssDir = path.resolve(options.output);
- options.dest = path.join(cssDir, file).replace(path.extname(file), '.css');
- }
- if (options.sourceMap) {
- if(!options.sourceMapOriginal) {
- options.sourceMapOriginal = options.sourceMap;
- }
- // check if sourceMap path ends with .map to avoid isDirectory false-positive
- var sourceMapIsDirectory = options.sourceMapOriginal.indexOf('.map', options.sourceMapOriginal.length - 4) === -1 && isDirectory(options.sourceMapOriginal);
- if (options.sourceMapOriginal === 'true') {
- options.sourceMap = options.dest + '.map';
- } else if (!sourceMapIsDirectory) {
- options.sourceMap = path.resolve(options.sourceMapOriginal);
- } else if (sourceMapIsDirectory) {
- if (!options.directory) {
- options.sourceMap = path.resolve(options.sourceMapOriginal, path.basename(options.dest) + '.map');
- } else {
- sassDir = path.resolve(options.directory);
- file = path.relative(sassDir, args[0]);
- mapDir = path.resolve(options.sourceMapOriginal);
- options.sourceMap = path.join(mapDir, file).replace(path.extname(file), '.css.map');
- }
- }
- }
- return options;
- }
- /**
- * Watch
- *
- * @param {Object} options
- * @param {Object} emitter
- * @api private
- */
- function watch(options, emitter) {
- var handler = function(files) {
- files.added.forEach(function(file) {
- var watch = gaze.watched();
- Object.keys(watch).forEach(function (dir) {
- if (watch[dir].indexOf(file) !== -1) {
- gaze.add(file);
- }
- });
- });
- files.changed.forEach(function(file) {
- if (path.basename(file)[0] !== '_') {
- renderFile(file, options, emitter);
- }
- });
- files.removed.forEach(function(file) {
- gaze.remove(file);
- });
- };
- var gaze = new Gaze();
- gaze.add(watcher.reset(options));
- gaze.on('error', emitter.emit.bind(emitter, 'error'));
- gaze.on('changed', function(file) {
- handler(watcher.changed(file));
- });
- gaze.on('added', function(file) {
- handler(watcher.added(file));
- });
- gaze.on('deleted', function(file) {
- handler(watcher.removed(file));
- });
- }
- /**
- * Run
- *
- * @param {Object} options
- * @param {Object} emitter
- * @api private
- */
- function run(options, emitter) {
- if (!Array.isArray(options.includePath)) {
- options.includePath = [options.includePath];
- }
- if (options.directory) {
- if (!options.output) {
- emitter.emit('error', 'An output directory must be specified when compiling a directory');
- }
- if (!isDirectory(options.output)) {
- emitter.emit('error', 'An output directory must be specified when compiling a directory');
- }
- }
- if (options.sourceMapOriginal && options.directory && !isDirectory(options.sourceMapOriginal) && options.sourceMapOriginal !== 'true') {
- emitter.emit('error', 'The --source-map option must be either a boolean or directory when compiling a directory');
- }
- if (options.importer) {
- if ((path.resolve(options.importer) === path.normalize(options.importer).replace(/(.+)([\/|\\])$/, '$1'))) {
- options.importer = require(options.importer);
- } else {
- options.importer = require(path.resolve(options.importer));
- }
- }
- if (options.functions) {
- if ((path.resolve(options.functions) === path.normalize(options.functions).replace(/(.+)([\/|\\])$/, '$1'))) {
- options.functions = require(options.functions);
- } else {
- options.functions = require(path.resolve(options.functions));
- }
- }
- if (options.watch) {
- watch(options, emitter);
- } else if (options.directory) {
- renderDir(options, emitter);
- } else {
- render(options, emitter);
- }
- }
- /**
- * Render a file
- *
- * @param {String} file
- * @param {Object} options
- * @param {Object} emitter
- * @api private
- */
- function renderFile(file, options, emitter) {
- options = getOptions([path.resolve(file)], options);
- if (options.watch && !options.quiet) {
- emitter.emit('info', util.format('=> changed: %s', file));
- }
- render(options, emitter);
- }
- /**
- * Render all sass files in a directory
- *
- * @param {Object} options
- * @param {Object} emitter
- * @api private
- */
- function renderDir(options, emitter) {
- var globPath = path.resolve(options.directory, globPattern(options));
- glob(globPath, { ignore: '**/_*', follow: options.follow }, function(err, files) {
- if (err) {
- return emitter.emit('error', util.format('You do not have permission to access this path: %s.', err.path));
- } else if (!files.length) {
- return emitter.emit('error', 'No input file was found.');
- }
- forEach(files, function(subject) {
- emitter.once('done', this.async());
- renderFile(subject, options, emitter);
- }, function(successful, arr) {
- var outputDir = path.join(process.cwd(), options.output);
- if (!options.quiet) {
- emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir));
- }
- process.exit();
- });
- });
- }
- /**
- * Arguments and options
- */
- var options = getOptions(cli.input, cli.flags);
- var emitter = getEmitter();
- /**
- * Show usage if no arguments are supplied
- */
- if (!options.src && process.stdin.isTTY) {
- emitter.emit('error', [
- 'Provide a Sass file to render',
- '',
- 'Example: Compile foobar.scss to foobar.css',
- ' node-sass --output-style compressed foobar.scss > foobar.css',
- ' cat foobar.scss | node-sass --output-style compressed > foobar.css',
- '',
- 'Example: Watch the sass directory for changes, compile with sourcemaps to the css directory',
- ' node-sass --watch --recursive --output css',
- ' --source-map true --source-map-contents sass',
- ].join('\n'));
- }
- /**
- * Apply arguments
- */
- if (options.src) {
- if (isDirectory(options.src)) {
- options.directory = options.src;
- }
- run(options, emitter);
- } else if (!process.stdin.isTTY) {
- stdin(function(data) {
- options.data = data;
- options.stdin = true;
- run(options, emitter);
- });
- }
|