Grunt environment variables are not set until all tasks are loaded

I am using npm grunt env and load-grunt-config modules in my project. grunt env processes the environment variables for you, and load-grunt-config processes, well, loads the grunt configuration for you. You can put your tasks in other files, then load-grunt-config will link them and load grunt and use them for you. You can also create the aliases.js file, with tasks that you want to combine into one task, working one after another. It is similar to the grunt.registerTask task in the original Gruntfile.js . I put all my grunt tasks in a separate grunt/ folder in the root folder with the main Gruntfile , without additional subfolders, as suggested by load-grunt-config README.md on Github. Here is my slimmed-down Gruntfile :

 module.exports = function(grunt) { 'use strict'; require('time-grunt')(grunt); // function & property declarations grunt.initConfig({ pkg: grunt.file.readJSON('package.json') }); require('load-grunt-config')(grunt, { init: true, loadGruntConfig: { scope: 'devDependencies', pattern: ['grunt-*', 'time-grunt'] } }); }; 

Theoretically, setting all of these files to the correct path for loading load-grunt-config should be exactly the same as with Gruntfile.js . However, it seems I'm a little confused. It seems that the environment variables set in the env task are not set for subsequent grunt tasks, but set by time, the node processes its tasks, in this case the express server.

Task

grunt env :

 module.exports = { // environment variable values for developers // creating/maintaining site dev: { options: { add: { NODE_ENV: 'dev', MONGO_PORT: 27017, SERVER_PORT: 3000 } } } }; 

grunt-shell-spawn task:

 // shell command tasks module.exports = { // starts up MongoDB server/daemon mongod: { command: 'mongod --bind_ip konneka.org --port ' + (process.env.MONGO_PORT || 27017) + ' --dbpath C:/MongoDB/data/db --ipv6', options: { async: true, // makes this command asynchronous stdout: false, // does not print to the console stderr: true, // prints errors to the console failOnError: true, // fails this task when it encounters errors execOptions: { cwd: '.' } } } }; 

grunt express task:

 module.exports = { // default options options: { hostname: '127.0.0.1', // allow connections from localhost port: (process.env.SERVER_PORT || 3000), // default port }, prod: { options: { livereload: true, // automatically reload server when express pages change // serverreload: true, // run forever-running server (do not close when finished) server: path.resolve(__dirname, '../backend/page.js'), // express server file bases: 'dist/' // watch files in app folder for changes } } }; 

aliases.js file ( grunt-load-config way to combine tasks so that they run one after another):

 module.exports = { // starts forever-running server with "production" environment server: ['env:prod', 'shell:mongod', 'express:prod', 'express-keepalive'] }; 

part of backend/env/prod.js (Express configuration for the environment loaded if NODE_ENV set to "prod" is modeled after MEAN.JS ):

 'use strict'; module.exports = { port: process.env.SERVER_PORT || 3001, dbUrl: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://konneka.org:' + (process.env.MONGO_PORT || 27018) + '/mean' }; 

part of backend/env/dev.js (express environment configuration for the dev environment, loaded if the `NODE_ENV variable is not set or is set to" dev "):

 module.exports = { port: process.env.SERVER_PORT || 3000, dbUrl: 'mongodb://konneka.org:' + (process.env.MONGO_PORT || 27017) + '/mean-dev' }; 

backend/page.js (my Express configuration page, also modeled after MEAN.JS):

 'use strict'; var session = require('express-session'); var mongoStore = require('connect-mongo')(session); var express = require('express'); var server = express(); ... // create the database object var monServer = mongoose.connect(environ.dbUrl); // create a client-server session, using a MongoDB collection/table to store its info server.use(session({ resave: true, saveUninitialized: true, secret: environ.sessionSecret, store: new mongoStore({ db: monServer.connections[0].db, // specify the database these sessions will be saved into auto_reconnect: true }) })); ... // listen on port related to environment variable server.listen(process.env.SERVER_PORT || 3000); module.exports = server; 

When I start grunt server , I get:

 $ cd /c/repos/konneka/ && grunt server Running "env:prod" (env) task Running "shell:mongod" (shell) task Running "express:prod" (express) task Running "express-server:prod" (express-server) task Web server started on port:3000, hostname: 127.0.0.1 [pid: 3996] Running "express-keepalive" task Fatal error: failed to connect to [konneka.org:27018] Execution Time (2014-08-15 18:05:31 UTC) loading tasks 38.3s β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 79% express-server:prod 8.7s β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 18% express-keepalive 1.2s β–ˆβ–ˆ 2% Total 48.3s 

Now I can’t get the database to connect in the first place, but so far I’m not paying attention. Note that the server is running on port 3000, which means that during the execution of the grunt express:prod SERVER_PORT not installed, so the port gets the value 3000. There are many other similar examples in which the environment variable is not set, so my application uses the value default. However, note that session trying to connect to the database on port 27018 (and does not work), so MONGO_PORT is ultimately installed.

If I just tried the grunt server task, I could write it to load-grunt-config , running the tasks in parallel, and not one after another or some other error, but even when I try to execute tasks one at a time, one, for example, running grunt env:prod shell:mongod express-server:prod express-keepalive , I get similar (incorrect) results, so either grunt or grunt env also runs tasks in parallel or something else happens.

What's going on here? Why are environment variables not set correctly for subsequent grunt tasks? When are they eventually installed, and why then, and not at another time? How can I get them to install grunt for the tasks themselves, and not after, assuming there is even a way?

+5
source share
2 answers

The solution is pretty obvious once you figure it out, so start from the beginning:

Problem

You use load-grunt-config to load a set of modules (objects that define tasks) and combine them into one module (object) and pass it along with Grunt. To better understand what load-grunt-config does, take a moment through the source (these are just three files) . So instead of writing:

 // filename: Gruntfile.js grunt.initConfig({ foo: { a: { options: {}, } }, bar: { b: { options: {}, } } }); 

You can write this:

 // filename: grunt/foo.js module.exports = { a: { options: {}, } } // filename: grunt/bar.js module.exports = { b: { options: {}, } } // filename: Gruntfile.js require('load-grunt-config')(grunt); 

Basically, this way you can split the Grunt configuration into several files and have a more "supported" one. But you need to understand that these two approaches are semantically equivalent. That is, you can expect them to behave the same.

Thus, when you write the following *:

(* I reduced the problem by trying to make this answer a little more general and reduce noise. I excluded things like loading tasks and passing parameters aloud, but the error should still be the same. Also note that I changed the values ​​of the environment variables , because that the default value was the same as what was set.)

 // filename: grunt/env.js module.exports = { dev: { options: { add: { // These values are different for demo purposes NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } } } }; // filename: grunt/shell.js module.exports = { mongod: { command: 'mongod --port ' + (process.env.MONGO_PORT || 27017) } }; // filename: grunt/aliases.js module.exports = { server: ['env:prod', 'shell:mongod'] }; // filename: Gruntfile.js module.exports = function (grunt) { require('load-grunt-config')(grunt); }; 

You can consider the above as shown below:

 module.exports = function (grunt) { grunt.initConfig({ env: { dev: { options: { add: { NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } } } }, shell: { mongod: { command: 'mongod --port ' + (process.env.MONGO_PORT || 27017) } } }); grunt.registerTask('server', ['env:dev', 'shell:mongod']); }; 

Now do you see the problem? What command do you expect shell:mongod to run? Correct answer:

 mongod --port 27017 

Where do you want to execute:

 mongo --port dev_mongo_port 

The problem is that during the evaluation (process.env.MONGO_PORT || 27017) environment variables are not set yet (i.e. before the env:dev task is completed).

Decision

Take a good look at the working configuration of Grunt before splitting it into several files:

 module.exports = function (grunt) { grunt.initConfig({ env: { dev: { options: { add: { NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } } } }, shell: { mongod: { command: 'mongod --port ${MONGO_PORT:-27017}' } } }); grunt.registerTask('server', ['env:dev', 'shell:mongod']); }; 

Now, when you run shell:mongod , the command will contain ${MONGO_PORT:-27017} , and Bash (or just sh) will look for the environment variable that you set before it (i.e. env:dev ).

It's good that everything is good and useful for the shell:mongod , but what about other tasks like Express?

You will need to move away from environment variables (if you do not want to configure them before calling Grunt). Why? Take this Grunt configuration, for example:

 module.exports = function (grunt) { grunt.initConfig({ env: { dev: { options: { add: { NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } } } }, express: { options: { hostname: '127.0.0.1' port: (process.env.SERVER_PORT || 3000) }, prod: { options: { livereload: true server: path.resolve(__dirname, '../backend/page.js'), bases: 'dist/' } } } }); grunt.registerTask('server', ['env:dev', 'express:prod']); }; 

Which port will contain the express:prod task configuration? 3000 To do this, you need to specify the value that you defined in the above task. How you do this is up to you. You could:

  • Separate the env configuration and specify its values

     module.exports = function (grunt) { grunt.config('env', { dev: { options: { add: { NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } } } }); grunt.config('express', { options: { hostname: '127.0.0.1' port: '<%= env.dev.options.add.SERVER_PORT %>' } }); grunt.registerTask('server', ['env:dev', 'express:prod']); }; 

    But you will notice that the semantics of the env task are not delayed here because it no longer represents the configuration of the task. You can use your own design object:

     module.exports = function (grunt) { grunt.config('env', { dev: { NODE_ENV: 'dev', MONGO_PORT: 'dev_mongo_port', SERVER_PORT: 'dev_server_port' } }); grunt.config('express', { options: { hostname: '127.0.0.1' port: '<%= env.dev.SERVER_PORT %>' } }); grunt.registerTask('server', ['env:dev', 'express:prod']); }; 
  • Pass grunt argument to indicate which configurator it should use

  • Have several configuration files (e.g. Gruntfile.js.dev and Gruntfile.js.prod ) and rename them as needed
  • Read the development configuration file (e.g. grunt.file.readJSON('config.development.json') ) if it exists, and return to the production configuration file if it does not exist
  • Some better way not listed here

But all of the above should achieve the same end result.

+6
source

This seems to be the essence of what you are trying to do, and it works for me. The important part was what I mentioned in my comment - to bind the environment task before performing other tasks.

Gruntfile.js

 module.exports = function(grunt) { // Do grunt-related things in here grunt.loadNpmTasks('grunt-env'); grunt.initConfig({ env: { dev: { PROD : 'http://production.server' } } }); grunt.registerTask('printEnv', 'prints a message with an env var', function() { console.log('Env var in subsequent grunt task: ' + process.env.PROD) } ); grunt.registerTask('prod', ['env:dev', 'printEnv']); }; 

grunt prod output

 Running "env:dev" (env) task Running "printEnv" task Env var in subsequent grunt task: http://production.server Done, without errors. 
+4
source

Source: https://habr.com/ru/post/1200439/


All Articles