@runforest/node-sass-watcher
Version:
SCSS watcher with post-processing.
243 lines (202 loc) • 5.7 kB
JavaScript
const fs = require('fs');
const path = require('path');
const exec = require('child_process').exec;
const yargs = require('yargs/yargs');
const Watcher = require('./watcher');
class Client {
name;
yargs;
static messages = {
NO_INPUT_PATH: 'Error: no input path specified',
EXTRA_OUTPUT_PATH: 'Error: only one output file is allowed',
EXTRA_ROOT_DIR: 'Error: only one root dir is allowed',
EXTRA_COMMAND: 'Error: only one command is allowed',
};
constructor(name) {
this.name = name || '$0';
this.initArgs();
this.parseArgs();
this.initWatcher();
}
initArgs = () => {
this.yargs = yargs(process.argv.slice(2))
.usage('Usage: ' + this.name + ' <input> [options]')
.command('$0 [input]', false)
.option('c', {
alias: 'command',
describe: 'Pass a command to execute; Shell syntax allowed',
type: 'string',
requiresArg: true,
})
.option('o', {
alias: 'output',
describe: 'Output CSS file path',
type: 'string',
requiresArg: true,
})
.option('r', {
alias: 'root-dir',
describe: 'Directory to watch for addition/deletion of the files',
type: 'string',
default: process.cwd(),
requiresArg: true,
})
.option('I', {
alias: 'include-path',
describe: 'Path to look for imported files; use multiple if needed',
type: 'string',
requiresArg: true,
})
.option('e', {
alias: 'include-extensions',
describe: 'File extensions to watch',
type: 'array',
default: Watcher.defaultExtensions,
})
.option('v', {
alias: 'verbose',
describe: 'Verbosity level',
type: 'count',
})
.help()
.alias('h', 'help')
.version()
.alias('V', 'version')
.wrap(null)
.strict();
};
parseArgs = () => {
const argv = this.yargs.argv;
if (process.argv.length === 2) {
this.yargs.showHelp();
process.exit(1);
}
if (!argv.input || argv.input.length === 0) {
console.error(Client.messages.NO_INPUT_PATH);
process.exit(1);
}
this.inputPath = path.resolve(argv.input);
this.outputPath = argv.output;
if (this.outputPath instanceof Array) {
console.error(Client.messages.EXTRA_OUTPUT_PATH);
process.exit(1);
}
if (this.outputPath) {
this.outputPath = path.resolve(this.outputPath);
}
this.includePaths = argv.includePath ? [].concat(argv.includePath) : [];
if (process.env.SASS_PATH) {
this.includePaths = this.includePaths.concat(
process.env.SASS_PATH.split(/:/).map((f) => path.resolve(f)),
);
}
this.includePaths = this.includePaths.map((includePath) =>
path.resolve(includePath),
);
this.includeExtensions = argv.includeExtensions;
this.rootDir = argv.rootDir;
if (this.rootDir instanceof Array) {
console.error(Client.messages.EXTRA_ROOT_DIR);
process.exit(1);
}
if (this.rootDir) {
this.rootDir = path.resolve(this.rootDir);
}
this.verbosity = argv.verbose;
this.command = argv.command;
if (this.command instanceof Array) {
console.error(Client.messages.EXTRA_COMMAND);
process.exit(1);
}
};
initWatcher = () => {
this.watcher = new Watcher(this.inputPath, {
rootDir: this.rootDir,
includePaths: this.includePaths,
includeExtensions: this.includeExtensions,
verbosity: this.verbosity,
});
this.watcher.on('init', this.processUpdate.bind(this));
this.watcher.on('update', this.processUpdate.bind(this));
};
processUpdate = () => {
if (this.command) {
this.processCommand();
} else {
this.processOutput();
}
if (this.verbosity === 1) {
process.stderr.write('.');
}
};
processCommand = () => {
const inputPath = this.inputPath;
const outputPath = this.outputPath;
let command = this.command.replace('<input>', inputPath);
if (outputPath) {
command = command.replace('<output>', outputPath);
}
const subprocess = exec(command, (err, stdout, stderr) => {
if (stderr) {
console.error(stderr);
}
if (err) {
console.error(
'Error: command "%s" exited with exit code %d',
command,
err.code,
);
return;
}
if (stdout) {
this.output(stdout);
}
});
fs.createReadStream(inputPath).pipe(subprocess.stdin);
};
processCommand = () => {
const inputPath = this.inputPath;
const outputPath = this.outputPath;
let command = this.command.replace('<input>', inputPath);
if (outputPath) {
command = command.replace('<output>', outputPath);
}
const subprocess = exec(command, (err, stdout, stderr) => {
if (stderr) {
console.error(stderr);
}
if (err) {
console.error(
'Error: command "%s" exited with exit code %d',
command,
err.code,
);
return;
}
if (stdout) {
this.output(stdout);
}
});
fs.createReadStream(inputPath).pipe(subprocess.stdin);
};
processOutput = () => {
fs.readFile(this.inputPath, (err, data) => {
if (err) {
throw err;
}
this.output(data.toString());
});
};
output = (data) => {
const outputPath = this.outputPath;
if (outputPath) {
fs.writeFileSync(outputPath, data);
} else {
process.stdout.write(data);
}
};
run = () => {
this.watcher.run();
};
}
module.exports = Client;