GruntJS Configurable First Time Run - javascript

I am working on a Angular Demo Application and I want to automatize a lot of things.
It's some sort of a boilerplate, albeit a more complex one, and I want to make a config file in which we'll put API Keys and other stuff, and I want that file to be populated by Grunt with user interaction when the project is started for the first time.
Something like:
grunt build - it should ask the user directly in the console for the API keys, that will be inserted in the config file where I am defining some global constants for the entire App.
Is there such an example of functionality with Grunt ?

You can handle the questioning by using:
https://github.com/dylang/grunt-prompt
It is a nice little plugin that do one job and do it well. It put whatever value you have entered in the command line into variables: (example)
prompt: {
target: {
options: {
questions: [
{
config: 'key', // arbitrary name or config for any other grunt task
type: 'input', // list, checkbox, confirm, input, password
message: 'What is your API key?',
default: '', // default value if nothing is entered
when: function(answers) { return !grunt.file.exists('config.yml'); } // only ask this question when this function returns true
}
]
}
}
}
Then you can use the Grunt.file functions to write those values into files:
http://gruntjs.com/api/grunt.file#grunt.file.write
To orchestrate it, you will need to create a custom task: (example)
grunt.registerTask("my_config_task", function (arg) {
var key = arg || grunt.config('key');
grunt.file.write("config.yml", key);
});
grunt.registerTask('build', ['prompt', 'my_config_task']);
The writing will likely need refinement as you will, I guess, need to replace values and organise as a yml file or json object, etc...
Found one of the possible solutions while looking at the sources of grunt-bump. What are they doing is parsing the config file as a JSON object:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#L128
Replacing whatever values they need (as JSON) and overwrite the file with the object stringified:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#153
Seems to work well.

Related

Node.js Function not updating value after 1st invoke

I've recently taken interest in the Discord.js framework, and was designing a bot for a server. Apologies in advance for the messiness of the code.
The issue I'm facing is that after I first run the command, the the function is invoked, the value of ticketValue does not update to the update value in my JSON file.
const fs = require("fs");
module.exports = {
commands: ["ticket"],
minArgs: 1,
expectedArgs: "<message>",
callback: (message, arguments, text) => {
// Console Log to notify the function has been invoked.
console.log("FUNCTION RUN")
let jsondata = require("../ticketNum.json")
let ticketValue = jsondata.reportNews.toString()
// Turning the number into a 4 digit number.
for(let i = ticketValue.length; i<4;i++) {
ticketValue = `0${ticketValue}`
}
console.log(`#1 ${ticketValue}`)
// Creating the Discord Chanel
message.guild.channels.create(`report-incident-${ticketValue}`, {
type: 'text',
permissionOverwrites: [
{
id: message.author.id,
deny: ['VIEW_CHANNEL'],
},
],
})
// Adding one to the ticket value and storing it in a JSON file.
ticketValue = Number(ticketValue)+1
console.log(`TICKET VALUE = ${ticketValue}`)
fs.writeFile("./ticketNum.json",JSON.stringify({"reportNews": Number(ticketValue)}), err => {
console.log(`Done writing, value = ${ticketValue}`)
})
console.log(require("../ticketNum.json").reportNews.toString())
},
}
I believe this is due to something called require cache. You can either invalidate the cache for your JSON file each time you write to it, or preferably use fs.readFile to get the up-to-date contents.
Also worth noting that you are requiring from ../ticketNum.json but you are writing to ./ticketNum.json. This could also be a cause.
You seem to be using JSON files as a database and while that is perfectly acceptable depending on your project's scale, I would recommend something a little more polished like lowdb which stills uses local JSON files to store your data but provides a nicer API to work with.
You should only use require when the file is static while the app is running.
The caching require performs is really useful, especially when you are loading tons of modules with the same dependencies.
require also does some special stuff to look for modules locally, globally, etc. So you might see unintended things happen if a file is missing locally and require goes hunting.
These two things mean it's not a good replacement for the fs tools node provides for file access and manipulation.
Given this, you should use fs.readFileSync or one of the other read functions. You're already using fs to write, so that isn't a large lift in terms of changing the line or two where you have require in place or a read.

How to pass arguments/parameters to mocha tests invoked via Grunt

I have a Gruntfile.js through which i'm invoking mochaTest using grunt-mocha-test module. I can pass an argument/parameter to the gruntTask from command line but i'm struggling to pass the same parameter into the spec file running via the above module. The code looks like below,
mochaTest: {
test: {
options: {
reporter: 'spec',
quiet: false,
clearRequireCache: false,
clearCacheFilter: (key) => true,
noFail: false
},
src: [
'test/createSpec.js'
]
}
}
Task is registered like below,
grunt.registerTask('e2etest', function(scope) {
console.log(scope); // logs user/session based on the parameter passed
grunt.task.run('mochaTest');
});
// Above task is invoked like,
grunt e2etest:user
(or)
grunt e2etest:session
I need to pass this value (user/session) into mochaTest so it can be accessed inside the spec file. Fundamentally the aim is to run the createSpec.js file both for user and session, this values is parametrized inside the spec file and based on the value passed the suite would run.
Is there a possibility to do this? Please advise.
Please refer to this issue for detail, and I think the solution you need here is:
node <node flags here> node_modules/mocha/bin/_mocha <mocha arguments here>
You can utilize nodes process.argv to read the argument (i.e. user or session) from within the file named createSpec.js.
To better understand how, follow these steps:
At the top of createSpec.js add the following line of code:
console.log(process.argv);
Then run grunt e2etest:user via your CLI and you should see the following logged to your console:
[ 'node', '/usr/local/bin/grunt', 'e2etest:user' ]
Note: the information you want is positioned at index two of the array.
Now, delete the line we just added which reads console.log(process.argv); from createSpec.js.
createSpec.js
So, the steps above (1-3) illustrated that the arguments (user or session) can be accessed in createSpec.js utilizing process.argv. In which case you could do something like the following inside createSpec.js.
const argument = process.argv[2].split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}
Note, we're using process.argv[2].split(/:/).pop(); to extract either user or session from the array item positioned at index two, whose initial value will be either e2etest:user or e2etest:session respectively.
Gruntfile
Your createSpec.js file is now somewhat dependent on the grunt task named e2etest being invoked correctly. For example, if a user were to run grunt e2etest without providing the arguments then createSpec.js is not going to do much.
To enforce the correct usage of the e2etest task (i.e. it must be run using either grunt e2etest:user or grunt e2etest:session), you could change your task in your Gruntfile as follows:
grunt.registerTask('e2etest', function(scope) {
if (!scope || !(scope === 'user' || scope === 'session')) {
grunt.warn(`Must be invoked with: ${this.name}:user or ${this.name}:session`);
}
grunt.task.run('mochaTest');
});
The gist above initially checks that an argument has been provided and is either user or session. If the argument is incorrect or missing then grunt.warn is utilized to warn the user.
If your version of nodejs does not support ES6 Template literals then use grunt.warn as follows instead:
grunt.warn('Must be invoked with: ' + this.name + ':user or ' + this.name + ':session');
Additional comment
The code/gist shown in the createSpec.js section above will work if your use-case is exactly as you mention in your question. I.e. you invoke via the commandline using grunt e2etest:user or grunt e2etest:session. However, if that changes and you cannot guarantee that e2etest:user or e2etest:session will be exactly positioned at index two of the process.argv array, then you may need to do the following at the top of createSpec.js instead:
// Return the value in the array which starts with
// `e2etest` then extract the string after the colon `:`
const argument = process.argv.filter(arg => {
return arg.match(/^e2etest:.*/);
}).toString().split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}

Understanding gruntjs registerTask colon

I'm currently trying to learn gruntjs for dev and production build.
I want to assign a global config variable to determine stuff.
I have a simple initConfig :
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
}
});
grunt.registerTask('one', ['foo:bar']);
grunt.registerTask('two', ['foo:baz']);
My question is:
What exactly is the colon in my tasks doing? (foo:bar or foo:baz)
And what is the difference between a colon and a simple dot?
My Goal is to have a global variable set either to true or false for further processing:
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
},
awesomestuff: {
smth: GLOBAL ? 'yes' : 'no',
another: !Global ? 'DoDebug' : 'MakeRelease'
}
});
grunt.registerTask('one', ['foo:bar', 'awesomestuff']);
grunt.registerTask('two', ['foo:baz', 'awesomestuff']);
How would I achieve this?
Update
I got the global variable working somehow. By registering a new new task called init with an argument I can call it in an other task.
grunt.registerTask('init', 'Init', function(param) {
grunt.config('GLOBAL', param)
});
grunt.registerTask('one', ['init:true', 'foo:bar', 'awesomestuff']);
In this case the init task will be called with the vairable param set to true.
But the question is still:
Why would I use a colon insted of a dot to reference an object?
Why would I use a colon instead of a dot to reference an object?
To understand why, you firstly need to understand grunt task configurations and targets.
Single Target
To help you further understand this concept and terminology, take a look at this example configuration for a grunt plugin called grunt-contrib-copy. It's a plugin that copies files. Below is a snippet of that code:
grunt.initConfig({
copy: { // <-- Task
main: { // <-- Target
// ... <-- other configurations go here.
}
}
});
In this example above the Task is named copy and it includes a single Target named main.
To register this Task you would do so as follows:
grunt.registerTask('copyFiles', ['copy:main']);
and you would enter the following via your command line to run it:
$ grunt copyFiles
Multiple Targets
Grunt Tasks can also include more than one Target. Consider this example code below:
grunt.initConfig({
copy: {
js: {
// ... <-- Additional configurations for this Target go here.
},
css: {
// ... <-- Additional configurations for this Target go here.
}
}
});
You could register the example above as follows:
grunt.registerTask('copyJavaScriptFiles', ['copy:js']);
grunt.registerTask('copyCssFiles', ['copy:css']);
So, via the command line:
Running $ grunt copyJavaScriptFiles will copy all the JS files according to the configurations specified.
Running $ grunt copyCssFiles will copy all the CSS files according to the configurations specified.
If you wanted to copy both the JS and CSS files you could register a task as follows:
grunt.registerTask('copyAll', ['copy']);
And you would run it by entering $ grunt copyAll in your command line.
Notice in the last example it does not include any colon :. Grunt this time will run all the Targets in the copy Task, namely the js one and the css one.
And what is the difference between a colon and a simple dot?
Colon
Hopefully by now you can see what the colon : does. It is used to reference a particular Target within a Task and is typically only used when a Task has multiple Targets and you want to specifically reference one of them.
Simple dot
The simple dot is JavaScript's standard notation to access properties of an object. Google "JavaScript notation" to find out more about Dot Notation and Square Bracket Notation.
Within the context of your Gruntfile.js the dot notation is typically used to call the functions/methods/properties of the grunt object. For example:
grunt.initConfig({...});
grunt.loadNpmTasks(...);
grunt.registerTask(...);
EDIT 1 Updated the answer after the original post/question was updated.

Process files by custom Grunt task

I am new to grunt... I just tried to implement a custom task (using TypeScript) that shall iterate over a set of given files and do some processing. This is what I have so far...
function gruntFile(grunt: IGrunt): void {
grunt.registerMultiTask("custom", "...", () => {
this.files.forEach(function(next) {
...
});
});
var config: grunt.config.IProjectConfig = {
custom: {
files: [
"folder1/*.json",
"folder2/**/*.json"
]
}
};
grunt.initConfig(config);
}
(module).exports = gruntFile;
Currently I struggle with the configuration and how I can acccess the files array in my custom task handler function. Grunt gives me the error that it cannot read the property forEach of undefined. I also tried a configuration that looks like that...
var config = {
custom: {
files : [
{ src: "folder1/*.json" },
{ src: "folder2/**/*.json" }
]
}
};
Not sure about that, but I have seen that in some tutorials...
I have seen a couple of sample grunt-files already, but in each example the configuration looks a bit different, or files are used in conjunction with imported tasks and modules, so the samples do not show how the configured files are accessed. Any guidance that helps me to better understand how it works (and what I am doing wrong) is appreciated.
Update
I found out that I can query options via the config-property, but I am not sure if this is the right way to do it. In my task-handler I do this to query the list of configured files...
var files = grunt.config.get("custom.files");
...which returns the expected array (but I find it a bit odd to query options via a path expression). I realized that (by using TypeScript) the scope of this is not the context of the current task; that is the reason why files was always undefined. Changing the call to registerMutliTask to...
grunt.registerMultiTask("custom", "...", function() { ... });
...fixed this problem. I use wildcard characters in the path-expression; I was hoping that Grunt can expand those expressions and give me a list of all matching paths. Does this functionality exist, or do I have to create that on my own?
I was able to iterate over the configured files (file pattern) by using the following code...
grunt.registerMultiTask("custom", "...", function() {
grunt.file
.expand(this.data)
.forEach(function(file) {
...
});
});

Is it possible to prompt user for input in any grunt task?

I'm attempting to use Grunt to make a directory in a project for new posts to a blog. It would essentially create a directory inside of a posts directory that is named YYYYMMDDDD-PostNameInPascalCase.
In order to do this, I'd have to prompt the user for post name each time that I execute the task. I know that grunt-init prompts users for creating projects from project templates, but I'm curious if there's a way to do this inside the Gruntfile.js file for an already established project.
Any thoughts?
It's been a while since this question was last asked, but there is a project on Github that attempts to do what the questioner was looking for. It's called grunt-prompt and here's the url: https://github.com/dylang/grunt-prompt. It's basically a way to integrate prompts into the Gruntfile. From the project readme you would do something like:
grunt.initConfig({
prompt: {
target: {
options: {
questions: [
{
config: 'config.name', // arbitray name or config for any other grunt task
type: '<question type>', // list, checkbox, confirm, input, password
message: 'Question to ask the user',
default: 'value', // default value if nothing is entered
choices: 'Array|function(answers)',
validate: function(value), // return true if valid, error message if invalid
filter: function(value), // modify the answer
when: function(answers) // only ask this question when this function returns true
}
]
}
},
},
})
Yes, you can do something like this:
grunt.registerTask('makePost', 'Make a new post dir.', function(n) {
if (n == null) {
grunt.log.warn('Post name must be specified, like makePost:PostNameGoesHere.');
}
// Run other tasks here
grunt.task.run('foo:' + n, 'bar:' + n, 'baz:' + n);
});
For more infomation and source on how to pass some arguments look at the Grunt FAQ.

Categories

Resources