I've created a webpack configuration file that builds the app, runs a dev server using webpack-dev-server and runs the test suite as below.
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3000,
watchOptions: {
open: false
},
historyApiFallback: {
index: "/"
}
},
plugins: [
new WebpackShellPlugin({
onBuildEnd: [`./node_modules/.bin/cucumber-js ${testScope}`]
})
]
The command to start the whole thing is below
webpack-dev-server --config webpack.test.js
Everything is ok, but... what now? It never stops! I need some way to exit with a successful status when the test suite is ok. Otherwise, it would cause an infinite deploy (I use bitbucket pipelines to deploy the app).
Does someone know a way to exit the devServer execution after running a specific plugin?
Likely it is the difference between npm run start and npm start
When you use npm start, it runs node server.js which doesn't run inline with your console.
If you use npm run start, it executes the start script defined in your package.json using the shell of your operating system. Therefore, even if you are using Cygwin (or similar), it should still run it using cmd.exe.
Solution
Use npm run start instead of npm start and it should kill the process when you CTRL + C
I solved my own problem by creating a webpack plugin and added it to my config file. The plugin code is below. It runs the command I pass to it and exit the process, firing error if the command was successful or success if it was everything ok.
const spawn = require("child_process").spawn;
module.exports = class RunScriptAndExit {
constructor(command) {
this.command = command;
}
apply(compiler) {
compiler.plugin("after-emit", (compilation, callback) => {
const { command, args } = this.serializeScript(this.command);
const proc = spawn(command, args, { stdio: "inherit" });
proc.on("close", this.exit);
callback();
});
}
serializeScript(script) {
if (typeof script === "string") {
const [command, ...args] = script.split(" ");
return { command, args };
}
const { command, args } = script;
return { command, args };
}
exit(error, stdout, stderr) {
if (error) {
process.exit(1);
} else {
process.exit(0);
}
}
};
I added it to my plugin list as below:
plugins: [
new RunScriptAndExit(`./node_modules/.bin/cucumber-js ${testScope}`)
]
Related
I've got a .env file for a project that I'm working on, I won't reveal the whole file because it contains sensitive data, but, in that file I have an item called 'STATUS'.
NOTE: This is for a Discord bot,
The 'STATUS' variable looks like this: STATUS=DEVELOPMENT
In my code I have a handler that deploys commands to all servers or just my specific server relative to the value of 'STATUS'.
example:
if STATUS was equal to DEVELOPMENT than it would deploy the commands to the development server and if it was equal to PRODUCTION then it would deploy the commands to all of the servers the bot is in.
That code looks something like this:
if (STATUS == "DEVELOPMENT") {
Routes.applicationGuildCommands(CLIENT_ID, process.env.DEVELOPMENT_GUILD_ID),
{ body: slashCommands },
console.log(
chalk.yellow(`Slash Commands • Registered Locally to
the development server`)
);
} else if (STATUS == "PRODUCTION") {
await rest.put(
Routes.applicationCommands(CLIENT_ID),
{ body: slashCommands },
console.log(chalk.yellow("Slash Commands • Registered Globally"))
);
}
In my package.json file I would like to have two scripts that control if the commands get pushed to production or development.
example:
"scripts": {
"prod": "changes the 'STATUS' to 'PRODUCTION' and runs the file",
"dev": "changes the 'STATUS' to 'DEVELOPMENT' and runs the file"
},
You can just create a simple utility JS script to do the work:
// status.js
const fs = require("fs")
const path = require("path")
// get the first argument passed to this file:
const status = process.argv[3] // the first and second element will always be `node` and `filename.js`, respectively
if(!status) {
throw new Error("You must supply a status to change to")
}
const envPath = path.join(process.cwd(), ".env") // resolve to the directory calling this script
// parse the environment variable file
let env = fs.readFileSync(envPath)
env = env.split(/\r?\n/g) // optional linefeed character
let prevExists = false
for(let lineNumber in env) {
if(env[lineNumber].startsWith("STATUS=")) {
prevExists = true
env[lineNumber] = `STATUS=${status}`
break
}
}
if(!prevExists) env.push(`STATUS=${status}`)
const newEnv = env.join("\n")
fs.writeFileSync(envPath, newEnv)
console.log(`Successfully changed the status to "${status}"`)
Then in your package.json, you can put the following:
"scripts": {
"prod": "node status.js PRODUCTION && command to deploy server",
"dev": "node status.js DEVELOPMENT && command to deploy server"
}
I'm trying to run a discord bot that I found on GitHub. I use Windows, the program uses Docker, Node, and maybe some other things I'm not aware of.
When I run the program with "node start-bots.js", cmd gives me an error:
'BOT' is not recognized as an internal command or external command, an executable program or a command file.
I tried many things but nothing works...
Here is the code of the start-bot.js :
const exec = require('child_process').exec;
const bots = require('./bots.json');
const recreate = () => {
console.log(`🐳 Checking existing volumes...`);
exec(`docker volume ls -q`, (err, stdout, stderr) => {
const volumes = stdout.split('\n');
bots.forEach((bot) => {
console.log(`🐋 Starting ${bot.name}...`);
const start = () => {
exec(`BOT=${bot.name} VINTED_BOT_ADMIN_IDS=${bot.adminIDs}
VINTED_BOT_TOKEN=${bot.token} docker-compose -f docker-compose.yaml -p
bot-${bot.name} up -d`, (err, stdout, stderr) => {
if (err) {
console.error(`🐋 ${bot.name} failed to start.`);
console.error(err);
return;
}
console.log(stderr);
});
}
if (volumes.includes(`bot-${bot.name}`)) {
console.log(`📦 ${bot.name} database has been recovered!`);
start();
} else {
exec(`docker volume create bot-${bot.name}`, (err, stdout, stderr) => {
if (!err) {
console.log(`📦 ${bot.name} database has been created!`);
start();
} else console.error(err);
});
}
});
});
};
const restart = process.argv.includes('-restart');
if (restart) {
console.log('👋 Shutting down all bots...');
bots.forEach((bot) => {
exec(`docker-compose -p bot-${bot.name} stop`, (err, stdout, stderr) => {
if (!err) {
exec(`docker-compose -p bot-${bot.name} rm -f`, (err, stdout, stderr) => {
if (!err) {
console.log(`👋 Bot ${bot.name} has been shut down and
removed.`);
} else {
console.log(`👎 Failed to remove containers for bot
${bot.name}`);
}
});
}
});
});
} else {
recreate();
}
and and docker-compose.yaml :
version: '3.8'
services:
vinted-discord-bot:
image: vinted-discord-bot:3.17
environment:
VINTED_BOT_ADMIN_IDS: "${VINTED_BOT_ADMIN_IDS}"
VINTED_BOT_TOKEN: "${VINTED_BOT_TOKEN}"
POSTGRES_DB: vinted_bot
POSTGRES_USER: root
POSTGRES_PASSWORD: password
restart: always
depends_on:
- postgres
postgres:
image: postgres:14.1
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "root" ]
timeout: 45s
interval: 10s
retries: 10
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: password
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
external:
name: bot-${BOT}
and finally the bots.json :
[
{
"name": "thenameofthebot",
"token": "thetokenofthebot",
"adminIDs": "theidoftheadminserver"
}
]
Here is a screen of the error (in French):
You can find it on: https://github.com/Androz2091/vinted-discord-bot.
You need to make a binary for your program. In Node.js, you can create a binary in package.json.
Step 1: In your main file, you need to add a shebang (#!), and /usr/bin/env node after that. Important! This must be on the very first line of your main file.
#!/usr/bin/env node
Step 2: In your package.json file, add the following two keys, replacing index.js with your own main file.
{
"main": "index.js",
"bin": {
"BOT": "./index.js"
},
}
Step 3: Run the following command to apply it to your computer.
npm link
EDIT: If the above command doesn't work, run the following command.
npm install -g .
And that's it! It should work when you run the following command.
BOT
I tried to run a nodejs script with the built in child_process module and it works fine until i give it options. Specially when i add the env property to the options object.
let exec = require('child_process').exec;
exec('node random.js', { env: {} }, (err) => {
console.log(err);
})
Then i get this error: /bin/sh: 1: node: not found.
I have node installed with nvm, maybe that is the cause, but don't know why.
If you exec a new shell from your script this don't have the same environment of the parent shell (your script).
So you have to provide all the needed environment.
In your case I see 2 way you could do.
First: you create a node command with the full path:
let exec = require('child_process').exec;
let node_cmd = '/path/to/my/node/node';
exec(node_cmd + ' random.js', { env: {} }, (err) => {
console.log(err);
});
So you could use env variables to handle the path, or just change it when you need.
Second, pass the path variable to the command:
let exec = require('child_process').exec;
let env_variables = 'PATH='+process.env.PATH;
let cmd = env_variables + ' node random.js';
exec(cmd, { env: {} }, (err) => {
console.log(err);
});
Another way is using the dotenv package.
I need a complete guide or a good reference material to solve the running module commands within javascript file problem.
Say that I often run:
$ npm run webpack-dev-server --progress --colors -- files
How can I run this within a javascript file and execute with
$ node ./script.js
script.js
var webpackDevServer = require('webpack-dev-server');
// need help here
var result = webpackDevServer.execute({
progress: true,
colors: true,
}, files);
Answer
I do something like this for my Webpack bundles. You can simply use child_process.spawn to execute command-line programs and handle the process in a node script.
Here's an example:
var spawn = require('child_process').spawn
// ...
// Notice how your arguments are in an array of strings
var child = spawn('./node_modules/.bin/webpack-dev-server', [
'--progress',
'--colors',
'<YOUR ENTRY FILE>'
]);
child.stdout.on('data', function (data) {
process.stdout.write(data);
});
child.stderr.on('data', function (data) {
process.stdout.write(data);
});
child.on('exit', function (data) {
process.stdout.write('I\'m done!');
});
You can handle all of the events you like. This is a fairly powerful module that allows you to view the process' PID (child.pid) and even kill the process whenever you choose (child.kill()).
Addendum
A neat trick is to throw everything into a Promise. Here's a simplified example of what my version of script.js would look like:
module.exports = function () {
return new Promise(function (resolve, reject) {
var child = spawn('./node_modules/.bin/webpack', [
'-d'
]);
child.stdout.on('data', function (data) {
process.stdout.write(data);
});
child.on('error', function (data) {
reject('Webpack errored!');
});
child.on('exit', function () {
resolve('Webpack completed successfully');
});
});
}
Using this method, you can include your script.js in other files and make this code synchronous in your build system or whatever. The possibilities are endless!
Edit The child_process.exec also lets you execute command-line programs:
var exec = require('child_process').exec
// ...
var child = exec('webpack-dev-server --progress --colors <YOUR ENTRY FILES>',
function(err, stdout, stderr) {
if (err) throw err;
else console.log(stdout);
});
The accepted answer doesn't work on Windows and doesn't handle exit codes, so here's a fully featured and more concise version.
const spawn = require('child_process').spawn
const path = require('path')
function webpackDevServer() {
return new Promise((resolve, reject) => {
let child = spawn(
path.resolve('./node_modules/.bin/webpack-dev-server'),
[ '--progress', '--colors' ],
{ shell: true, stdio: 'inherit' }
)
child.on('error', reject)
child.on('exit', (code) => code === 0 ? resolve() : reject(code))
})
}
path.resolve() properly formats the path to the script, regardless of the host OS.
The last parameter to spawn() does two things. shell: true uses the shell, which appends .cmd on Windows, if necessary and stdio: 'inherit' passes through stdout and stderr, so you don't have to do it yourself.
Also, the exit code is important, especially when running linters and whatnot, so anything other than 0 gets rejected, just like in shell scripts.
Lastly, the error event occurs when the command fails to execute. When using the shell, the error is unfortunately always empty (undefined).
Do you need it to be webpack-dev-server? There is an equivalent webpack-dev-middleware for running within node/express:
'use strict';
let express = require('express');
let app = new express();
app.use(express.static(`${__dirname}/public`));
let webpackMiddleware = require('webpack-dev-middleware');
let webpack = require('webpack');
let webpackConfig = require('./webpack.config.js');
app.use(webpackMiddleware(webpack(webpackConfig), {}));
app.listen(3000, () => console.log(`Server running on port 3000...`));
https://github.com/webpack/webpack-dev-middleware
I have a node / angular project that uses npm for backend dependency management and bower for frontend dependency management. I'd like to use a grunt task to do both install commands. I haven't been able to figure out how to do it.
I made an attempt using exec, but it doesn't actually install anything.
module.exports = function(grunt) {
grunt.registerTask('install', 'install the backend and frontend dependencies', function() {
// adapted from http://www.dzone.com/snippets/execute-unix-command-nodejs
var exec = require('child_process').exec,
sys = require('sys');
function puts(error, stdout, stderr) { console.log(stdout); sys.puts(stdout) }
// assuming this command is run from the root of the repo
exec('bower install', {cwd: './frontend'}, puts);
});
};
When I cd into frontend, open up node, and run this code from the console, this works fine. What am I doing wrong in the grunt task?
(I also tried to use the bower and npm APIs, but couldn't make that work either.)
To install client side components during npm install at the same time than server side libs, you can add in your package.json
"dependencies": {
...
"bower" : ""
},
"scripts": {
...
"postinstall" : "bower install"
}
I prefer to make the difference between install and test/build
You need to tell grunt that you're using an async method (.exec) by calling the this.async() method, getting a callback, and calling that when exec is done.
This should work:
module.exports = function(grunt) {
grunt.registerTask('install', 'install the backend and frontend dependencies', function() {
var exec = require('child_process').exec;
var cb = this.async();
exec('bower install', {cwd: './frontend'}, function(err, stdout, stderr) {
console.log(stdout);
cb();
});
});
};
See Why doesn't my asynchronous task complete?
FYI, here is where i am for now.
You could also have taken the problem another way, i.e. let npm handle the execution of bower, and ultimately let grunt handle npm. See Use bower with heroku.
grunt.registerTask('install', 'install the backend and frontend dependencies', function() {
var async = require('async');
var exec = require('child_process').exec;
var done = this.async();
var runCmd = function(item, callback) {
process.stdout.write('running "' + item + '"...\n');
var cmd = exec(item);
cmd.stdout.on('data', function (data) {
grunt.log.writeln(data);
});
cmd.stderr.on('data', function (data) {
grunt.log.errorlns(data);
});
cmd.on('exit', function (code) {
if (code !== 0) throw new Error(item + ' failed');
grunt.log.writeln('done\n');
callback();
});
};
async.series({
npm: function(callback){
runCmd('npm install', callback);
},
bower: function(callback){
runCmd('bower install', callback);
}
},
function(err, results) {
if (err) done(false);
done();
});
});
Grunt task that does just this job (as per Sindre's solution above):
https://github.com/ahutchings/grunt-install-dependencies
Grunt task that does the bower install command :
https://github.com/yatskevich/grunt-bower-task
also , you can use
https://github.com/stephenplusplus/grunt-bower-install
to auto inject your dependencies into the index.html file