I'm currently trying to make a simple command-line node program that allows for me to easily create a boilerplate for a lot of the React/Redux applications that I make. I'm using ShellJS to execute console commands (I've tried using Node's child_process, too.) The problem is getting cli-spinner to work with executing terminal commands. Here's my code:
#! /usr/bin/env node
var shell = require('shelljs');
var userArgs = process.argv.slice(2);
var folderName = userArgs[0];
var Spinner = require('cli-spinner').Spinner;
var depSpin = new Spinner('Installing dependencies.. %s');
depSpin.setSpinnerString(10);
shell.mkdir(folderName);
shell.cd(folderName);
depSpin.start();
// I expect for the spinner to start here (before the execution of the commands.)
shell.exec('npm init -y', {silent: true});
shell.exec('npm install --save babel-core babel-loader babel-preset-es2015 babel-preset-react react-dom react-redux redux webpack', {silent: true});
shell.exec('npm install --save-dev babel-preset-env webpack-dev-server', {silent: true});
depSpin.stop();
// Since ShellJS should run synchronously,
// the spinner should stop right after the last command finishes.
shell.touch('webpack.config.js');
shell.mkdir(['build', 'frontend']);
shell.cd('frontend');
shell.mkdir(['components', 'containers', 'reducers', 'store']);
shell.touch('app.js');
But when running the program, it just hangs without displaying anything while it installs the dependencies. It's the same as when the spinner code wasn't even in there. I've also tried removing the depSpin.stop(), which just makes the program hang forever on the spinner. I have a feeling that this issue is caused by a conflict between cli-spinner and ShellJS both using the terminal.
I was able to accomplish this effect by using child_process's spawn. I had to create a child_process.spawn and run all of the commands concatenated with &&. That freed up the console output to be exclusively the cli-spinner output. I then did an event handler for when the child process exited to stop the spinner.
#! /usr/bin/env node
var shell = require('shelljs');
var userArgs = process.argv.slice(2);
var folderName = userArgs[0];
var Spinner = require('cli-spinner').Spinner;
var depSpin = new Spinner('Installing dependencies.. %s');
var spawn = require('child_process').spawn;
var commandsDep = [
'cd ' + folderName,
'npm init -y',
'npm install --save babel-core babel-loader babel-preset-es2015 babel-preset-react react-dom react-redux redux webpack',
'npm install --save-dev babel-preset-env webpack-dev-server'
];
var depChild = spawn(commandsDep.join(' && '), {
shell: true
});
depSpin.setSpinnerString(18);
shell.mkdir(folderName);
shell.cd(folderName);
depSpin.start();
depChild.on('exit', () => {
depSpin.stop();
shell.exec('clear');
console.log('Installing dependencies.. ✓');
})
shell.touch('webpack.config.js');
shell.mkdir(['build', 'frontend']);
shell.cd('frontend');
shell.mkdir(['components', 'containers', 'reducers', 'store']);
shell.touch('app.js');
shell.cd('containers');
shell.touch(['AppContainer.js', 'Root.js']);
shell.cd('../reducers');
shell.touch('index.js');
shell.cd('../store');
shell.touch('configureStore.js');
Based on Owens' answer I made a helper method myself to run long commands with a spinner inline with normal shelljs commands;
const Spinner = require('cli-spinner').Spinner;
const spinner = new Spinner('installing.. %s');
spinner.setSpinnerString('|/-\\');
var spawn = require('child_process').spawn;
const longCommand = (command, onSuccess) => {
return new Promise((resolve, reject) => {
var process = spawn(command, { shell: true });
spinner.start();
process.on('exit', () => {
spinner.stop();
onSuccess();
resolve();
})
})
}
const npmInstall = async () => {
await longCommand("npm install", () => console.log(`NPM modules installed! 👍`))
// Other stuff
shell.mkdir('new')
}
npmInstall()
Related
I have following javascript class and writing unit test using mocha and sinon. When I run test case I see uncovered lines for 'return this._agentId;' and 'this._agentId = value;'.I am not sure how to cover these lines under test.I am using Istanbul test coverage tool to see coverage.
// Agentmessage.js
class AgentMessage {
constructor(agentId, message) {
this._agentId = agentId;
this._message = message;
}
get agentId() {
return this._agentId;
}
set agentId(value) {
this._agentId = value;
}
}
module.exports = AgentMessage;
// Agentmessage.test.js
'use strict';
const chai=require('chai');
const sinon=require('sinon');
var chaiAsPromised=require('chai-as-promised');
chai.use(chaiAsPromised).should();
const expect = chai.expect;
const agentMessage = require('../src/model/agentMessage');
describe('agentMessage test',function() {
let sandbox;
let agentMessageObj;
beforeEach(() => {
agentMessageObj = new agentMessage('agentId', 'message');
sandbox=sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('agentMessage set agentId Test',() => {
agentMessageObj.agentId = 'agentId';
expect(agentMessageObj.agentId).to.deep.equal('agentId');
});
it('agentMessage get agentId Test',() => {
expect(agentMessageObj.agentId).to.equal('agentId');
});
});
I am not seeing the same issue you are. I get 100% coverage.
You say istanbul but you are in fact using the nyc package correct? I think you'll find that the instanbul project suggests you use the nyc runner if you are not already.
Consider refreshing your environment if you are able.
rm -rf .nyc_output && rm -rf coverage && rm -rf node_modules
npm i --save-dev nyc mocha chai
If that does not clear things up, consider removing things, temporarily at least, that you are not using in these particular tests. sinon and chai-as-promised for example. Isolate the code. See if there are some conflicts there.
Try this similar code. I get full coverage.
./node_modules/.bin/nyc --reporter html ./node_modules/.bin/mocha test.js
test.js
const { expect } = require('chai')
const AgentMessage = require('./index');
describe('agentMessage test', function () {
let agentMessage;
beforeEach(function () {
agentMessage = new AgentMessage('agentId01', 'message02');
});
it('agentMessage set agentId Test', async function () {
agentMessage.agentId = 'agentId02';
expect(agentMessage.agentId).to.deep.equal('agentId02');
});
});
If after all of that, if it is still a problem, if you're using a more advanced configuration of nyc/istanbul, start stripping away that configuration and using default properties. See if you find the sweet/troubled part.
I want to create a nodejs file, that simple runs/wraps an executable binary file with all inputs and outputs.
For now at least on windows.
Why:
wanted to install an executable tool over npm install -g, to have it in console, without PATH changes. (npm global packages are included in PATH)
i used such solution:
const path = require("path");
const spawnSync = require('child_process').spawnSync;
const pathToMyExe = path.join(__dirname, 'bin', 'myfile.exe'); //just path to exe
const input = process.argv.slice(2); //minus "node" and "this js" arguments
spawnSync(pathToMyExe, input, {stdio: 'inherit'});
but for ".exe to PATH" problem, there is a simplier way (if you want windows only).
just set bin property in package.json to pathToExe.
https://docs.npmjs.com/files/package.json#bin
You can use the bin-wrapper npm package.
EDIT:
There's a better alternative called bin-manager.
Installation:
$ npm install --save bin-manager
Usage
const bmanager = require('bin-manager');
const base = 'https://github.com/imagemin/gifsicle-bin/raw/master/vendor';
const bin = bmanager('bin', 'gifsicle')
.src(base + '/macos/gifsicle', 'darwin')
.src(base + '/linux/x64/gifsicle', 'linux', 'x64')
.src(base + '/win/x64/gifsicle.exe', 'win32', 'x64')
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
bin.run(['--version'], (err, out) => {
if (err) {
console.log(error);
return;
}
console.log(out.stdout);
});
I am using the gulp and hercule package on node.js to transclude some plain text files. On Unix, everything seems to work fine. However, some coworkers are having issues running it on Windows. They are getting the following error message only when running on Windows:
[13:02:01] TypeError: Cannot read property 'toString' of null at Object.transcludeStringSync (D:\project\node_modules\hercule\lib\hercule.js:136:36)
I have tried the above with hercule#3.0.5 as well as hercule#2.0.5, and both packages give the above error. However, given that this occurs only on Windows and across many versions of the package, I suspect this issue has something to due with the Node.js installation or path.
The code that is using the hercule package:
var fs = require('fs');
var path = require('path');
var gulp = require('gulp');
var drakov = require('drakov');
var hercule = require('hercule');
gulp.task('mock', ['i18n','build_minify_no_tests'], function() {
var mockSpecificationTemplate= fs.readFileSync('test/mock/mock-template.apib','utf8');
var transcludedMockSpecification = hercule.transcludeStringSync(mockSpecificationTemplate, {
relativePath: path.resolve('../../../')
});
fs.writeFileSync('test/mock/mock.apib', transcludedMockSpecification, 'utf-8');
// Running mock server
var drakovArgv = {
sourceFiles: 'test/mock/mock.apib',
serverPort: 9000,
staticPaths: [
'../../'
],
discover: true,
watch: true
};
drakov.run(drakovArgv);
});
node and npm version information:
$ node -v
v6.3.0
$ npm -v
3.10.3
hercule.transcludeStringSync simply runs another hercule process and sends input to it:
const result = childProcess.spawnSync('../bin/hercule', syncArgs, syncOptions);
with the script ../bin/hercule:
#!/usr/bin/env node
"use strict";
require('../lib/main.js');
...obviously doesn't work on Windows
If that task must be synchronized, you may use the following function instead:
function transcludeStringSync(input, options) {
const {dirname, join} = require('path')
const hercule = join(dirname(require.resolve('hercule')), 'main')
const args = [hercule, '--reporter', 'json-err']
for (let name in options) {
args.push(`--${name}`, `--${options[name]}`)
}
const result = require('child_process').spawnSync('node', args, {input})
const err = result.stderr.toString()
if (err) throw new Error('Could not transclude input')
return result.stdout.toString()
}
I'm trying to integrate Gulp with Tape (https://github.com/substack/tape), the NodeJs test harness.
How can I do this? There doesn't seem to be an existing gulp plugin.
I've see this, but it looks really inelegant:
var shell = require('gulp-shell')
gulp.task('exec-tests', shell.task([
'tape test/* | faucet',
]));
gulp.task('autotest', ['exec-tests'], function() {
gulp.watch(['app/**/*.js', 'test/**/*.js'], ['exec-tests']);
});
I've tried this, which looks like it should work:
var tape = require('tape');
var spec = require('tap-spec');
gulp.task('test', function() {
return gulp.src(paths.serverTests, {
read: false
})
.pipe(tape.createStream())
.pipe(spec())
.pipe(process.stdout);
});
but I get a TypeError: Invalid non-string/buffer chunk error
Your "inelegant" answer is the best one. Not every problem can be best solved with streams, and using gulp just as a wrapper is not a sin.
Right, your task won't work because gulp streams are based on vinyl, a virtual file abstraction. I don't really think there's a good way of handling this in gulp, it seems like you should be using the tape API directly. I mean, you could put some gulp task sugar around it if you wish:
var test = require('tape');
var spec = require('tap-spec');
var path = require('path');
var gulp = require('gulp');
var glob = require('glob');
gulp.task('default', function () {
var stream = test.createStream()
.pipe(spec())
.pipe(process.stdout);
glob.sync('path/to/tests/**/*.js').forEach(function (file) {
require(path.resolve(file));
});
return stream;
});
Seems kind of messy to me; not only because we're not using any of gulp's streaming abstractions, but we're not even putting it into a way that could hook into a gulp pipeline afterwards. Furthermore, you can't get gulp's task finished message when using this code either. If anyone knows a way around that then, please, be my guest. :-)
I think I would prefer to use tape on the command line. But, if you want all of your build step tasks in your gulpfile this might be the route to go.
Just use code below and gulp tdd and having TDD :) with tape
const tapNotify = require('tap-notify');
const colorize = require('tap-colorize');
const tape = require('gulp-tape');
const through = require('through2');
gulp.task('test',function(){
process.stdout.write('\x1Bc');
const reporter = through.obj();
reporter.pipe(tapNotify({
passed: {title: 'ok', wait:false},
failed: {title: 'missing',wait:false}
}));
reporter
.pipe(colorize())
.pipe(process.stdout);
return gulp.src('test/**/*.js')
.pipe(tape({
outputStream: through.obj(),
reporter: reporter
}));
});
gulp.task('tdd', function() {
gulp.run('test');
gulp.watch(['app/scripts/**/*.js*', 'test/**/*.js'],['test']);
});
In a GitHub issue for tape jokeyrhyme mentions that gulp tasks can be Promises, and suggests a way to use that for running tape tests. Based upon that advice I've done this:
gulpfile.babel.js:
import glob from "glob";
gulp.task("test", () => {
let module = process.argv[process.argv.length - 1];
return new Promise(resolve => {
// Crude test for 'gulp test' vs. 'gulp test --module mod'
if (module !== "test") {
require(`./js/tape/${module}.js`);
resolve();
return;
}
glob.sync("./js/tape/*.js").forEach(f => require(f)));
resolve();
});
});
Looking at Ben's answer I suspect what I've done isn't very nice though, for one thing I've noticed that failing tests don't result in a non-zero exit code (although I've not tried Ben's approach to validate whether that does).
// npm i --save-dev gulp-tape
// npm i --save-dev faucet (just an example of using a TAP reporter)
import gulp from 'gulp';
import tape from 'gulp-tape';
import faucet from 'faucet';
gulp.task('test:js', () => {
return gulp.src('src/**/*test.js')
.pipe(tape({
reporter: faucet()
}));
});
Here's an example of my solution:
var gulp = require('gulp');
var tape = require('tape');
var File = require('vinyl');
var through = require('through2');
var exec = (require('child_process')).execSync;
function execShell(shcmd, opts) {
var out = '';
try {
out = exec(shcmd, opts);
} catch (e) {
if (e.error) throw e.error;
if (e.stdout) out = e.stdout.toString();
}
return out;
};
gulp.task('testreport', function(){
return gulp.src(
'testing/specs/tape_unit.js', {read: false}
).pipe(
through.obj(function(file, encoding, next) {
try{
// get tape's report
var tapout = execShell(
"./node_modules/.bin/tape " + file.path
);
// show the report in a console with tap-spec
execShell(
"./node_modules/.bin/tap-spec", { input: tapout, stdio: [null, 1, 2] }
);
// make a json report
var jsonout = execShell(
"./node_modules/.bin/tap-json", { input: tapout }
);
// do something with report's object
// or prepare it for something like Bamboo
var report = JSON.parse(jsonout.toString());
// continue the stream with the json report
next(null, new File({
path: 'spec_report.json',
contents: new Buffer(JSON.stringify(report, null, 2))
}));
}catch(err){ next(err) }
})
).pipe(
gulp.dest('testing/reports')
);
});
I have a nodejs file runner.node.js.
If I run node runner.node.js it works
But if I try tu run it with npm test (it's referenced in package.json):
"test": "node ./spec/runner.node.js"
or
"test": "spec/runner.node.js"
It says that the file isn't executable:
sh: 1: spec/runner.node.js: Permission denied
npm ERR! Test failed. See above for more details.
npm ERR! not ok code 0
If I set the file as executable it then says:
spec/runner.node.js: 1: spec/runner.node.js: Syntax error: word unexpected (expecting ")")
npm ERR! Test failed. See above for more details.
npm ERR! not ok code 0
while it still runs correctly with "node spec/runner.node.js"
The file is this:
console.log("Running Knockout tests in Node.js");
var fs = require('fs');
var jasmine = require('./lib/jasmine-1.2.0/jasmine');
// export jasmine globals
for (var key in jasmine) {
global[key] = jasmine[key];
}
// add our jasmine extensions to the exported globals
require('./lib/jasmine.extensions');
// export ko globals
if (process.argv.length > 2 && process.argv[2] == '--source') {
// equivalent of ../build/knockout-raw.js
global.DEBUG = true;
global.ko = global.koExports = {};
global.knockoutDebugCallback = function(sources) {
sources.unshift('build/fragments/extern-pre.js');
sources.push('build/fragments/extern-post.js');
eval(sources.reduce(function(all, source) {
return all + '\n' + fs.readFileSync(source);
}, ''));
};
require('../build/fragments/source-references');
} else {
global.ko = require('../build/output/knockout-latest.js');
}
// reference behaviors that should work out of browser
require('./arrayEditDetectionBehaviors');
require('./asyncBehaviors');
require('./dependentObservableBehaviors');
require('./expressionRewritingBehaviors');
require('./extenderBehaviors');
require('./mappingHelperBehaviors');
require('./observableArrayBehaviors');
require('./observableBehaviors');
require('./subscribableBehaviors');
// get reference to jasmine runtime
var env = jasmine.jasmine.getEnv();
// create reporter to return results
function failureFilter(item) {
return !item.passed();
}
env.addReporter({
reportRunnerResults:function (runner) {
var results = runner.results();
runner.suites().map(function (suite) {
// hack around suite results not having a description
var suiteResults = suite.results();
suiteResults.description = suite.description;
return suiteResults;
}).filter(failureFilter).forEach(function (suite) {
console.error(suite.description);
suite.getItems().filter(failureFilter).forEach(function (spec) {
console.error('\t' + spec.description);
spec.getItems().filter(failureFilter).forEach(function (expectation) {
console.error('\t\t' + expectation.message);
});
});
});
console.log("Total:" + results.totalCount + " Passed:" + results.passedCount + " Failed:" + results.failedCount);
process.exit(results.failedCount);
}
});
// good to go
env.execute();
Add
#/usr/bin/env node
as the first line in your file. This way, when run as an executable your OS will know that it shall use Node.js to run it (to be exactly: your OS will know that it shall use the first application called node to execute your script).