Custom grunt plugin not playing nice with grunt-watch - gruntjs

I'm developing a custom grunt extension that reloads a chrome tab. It works fine when I use it within the plugin's own folder, but then when I try to download it from NPM and use it in another project, it goes bonkers.
I included it as such:
grunt.loadNpmTasks('grunt-chrome-extension-reload');
My custom task code, located in the tasks folder of the plugin, is as such:
/*
* grunt-chrome-extension-reload
* https://github.com/freedomflyer/grunt-chrome-extension-reload
*
* Copyright (c) 2014 Spencer Gardner
* Licensed under the MIT license.
*/
'use strict';
module.exports = function(grunt) {
var chromeExtensionTabId = 0;
grunt.initConfig({
/**
Reloads tab in chrome with id of chromeExtensionTabId
Called after correct tab number is found from chrome-cli binary.
*/
exec: {
reloadChromeTab: {
cmd: function() {
return chromeExtensionTabId ? "chrome-cli reload -t " + chromeExtensionTabId : "chrome-cli open chrome://extensions && chrome-cli reload";
}
}
},
/**
Executes "chrome-cli list tabs", grabs stdout, and finds open extension tabs ID's.
Sets variable chromeExtensionTabId to the first extension tab ID
*/
external_daemon: {
getExtensionTabId: {
options: {
verbose: true,
startCheck: function(stdout, stderr) {
// Find any open tab in Chrome that has the extensions page loaded, grab ID of tab
var extensionTabMatches = stdout.match(/\[\d{1,5}\] Extensions/);
if(extensionTabMatches){
var chromeExtensionTabIdContainer = extensionTabMatches[0].match(/\[\d{1,5}\]/)[0];
chromeExtensionTabId = chromeExtensionTabIdContainer.substr(1, chromeExtensionTabIdContainer.length - 2);
console.log("Chrome Extension Tab #: " + chromeExtensionTabId);
}
return true;
}
},
cmd: "chrome-cli",
args: ["list", "tabs"]
}
}
});
grunt.registerTask('chrome_extension_reload', function() {
grunt.task.run(['external_daemon:getExtensionTabId', 'exec:reloadChromeTab']);
});
};
So, when I run it in an external project with grunt watch, grunt spits out this error a few hundred times before quitting (endless loop?)
Running "watch" task
Waiting...Verifying property watch exists in config...ERROR
>> Unable to process task.
Warning: Required config property "watch" missing.
Fatal error: Maximum call stack size exceeded
Interestingly, can not even call my plugin within the watch task, and the problem persists. Only by removing grunt.loadNpmTasks('grunt-chrome-extension-reload'); can I get rid of the issue, which basically means that the code inside my task is wrong. Any ideas?

grunt.initConfig() is intended for end users. As it will completely erase any existing config (including your watch config) and replace with the config you're initializing. Thus when your plugin is ran it replaces the entire config with the exec and external_daemon task configs.
Try using grunt.config.set() instead. As it only sets a given part of the config rather than erasing the entire thing.
But a better pattern for a plugin is to let the user determine the config. Just have a plugin handle the task. In other words, avoid setting the config for the user.

Related

Getting TestCafe to recognize dotenv variables

I might be mixing up concepts, but I'd read that it's possible to get TestCafe to recognize variables of the form process.env.MY_COOL_VARIABLE. Also for my Vue.js frontend (built using Vue-CLI, which uses dotenv under the hood), I found I could make a file in .env.test for test values like so:
VUE_APP_MY_COOL_VARIABLE
which I would then access in my test code like so:
test('my fixture', async (t) => {
...
await t
.click(mySelector.find('.div').withText(process.env.VUE_APP_MY_COOL_VARIABLE));
...
}
However, I get the following error:
"text" argument is expected to be a string or a regular expression, but it was undefined.
Seems like my environment variables aren't getting picked up. I build my code like so: vue-cli-service build --mode test.
TestCafe doesn't provide support for .env files out of the box. You can create a test file that will require the dotenv module and load your configuration file:
// enable-dotenv.test.js
require('dotenv').config({ path: '.my.env' });
testcafe chrome enable-dotenv.test.js tests/
Here's how I solved my issue. When debugging, I did a console.log of process.env and noticed that the variable that vue recognizes wasn't visible during testcafe's run. From our package.json:
"test:ui:run": "VUE_APP_MY_COOL_VARIABLE=ui-test yarn build:test && testcafe -a ../ui-test-server.sh chrome",
Also this bit of javascript is run by both the test and mainline code, so I had to use a conditional.
import * as dotenv from 'dotenv';
if (process.env.npm_package_scripts_test_ui_run) { // are we running a testcafe script
dotenv.config({ path: '.env.test' });
}
Have you tried process.env[VUE_APP_MY_COOL_VARIABLE]? It's worth noting that everything in dotenv comes back as a string so you may need to do the casting yourself. For example:
function getEnvVariableValue(envVariable: string) {
// Cast to boolean
if (envVariableValue.toUpperCase() === "TRUE") {
return true;
} else if (envVariableValue.toUpperCase() === "FALSE") {
return false;
// Cast to number
} else if (!isNaN(Number(envVariableValue))) {
return Number(envVariableValue);
} else {
return envVariableValue;
}
}
You can also try creating a .env file in the root folder to see if it picks it that way. I use dotenv in my project directly by including it in the package.json as a dependency and it works this way.

Grunt relative file path globbing

Is it possible to use Globbing partially on a directory in a file path?
I have a grunt-contrib-less task set up, the file path for my task looks something like this:
files: {
"../../application/user/themes/some-theme-5.1.1.5830/css/main.css": "less/base.less",
}
However the version number in the relative path may sometime change, such as:
files: {
"../../application/user/themes/some-theme-5.1.1.5831/css/main.css": "less/base.less",
}
Ideally I'd like to something like this:
files: {
"../../application/user/themes/some-theme-*/css/main.css": "less/base.less",
}
Is there a way of doing this? With the above syntax it stops searching after the asterisk.
One potential solution to achieve this is to utilize grunts --options feature.
When running a grunt task via the command line it is possible to specify an additional options value.
In your scenario you could pass in the version number of the folder name that is going to change. (I.e. In your case the part that you tried to specify using the asterisk character (*) E.g. '5.1.1.5830'
Caveat: For this solution to be of any use it does require knowing what that value, (the version number), of the destination folder is upfront prior to running the task via the command line.
Example Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
themesFolder: {
namePart: '0.0.0.0' // <-- If no option is passed via the CLI this name will be used.
},
less: {
production: {
options: {
// ...
},
files: {
// The destination path below utilizes a grunt template for the part
// of the folder name that will change. E.g. '5.1.1.0'
'../../application/user/themes/some-theme-<%= themesFolder.name %>/css/main.css': 'less/base.less'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('saveFolderNameFromOption', 'Uses the option provided to configure name part.', function(n) {
var themesFolder = grunt.option('themesFolder');
if (themesFolder) {
// Store the option value so it can be referenced in the less task.
grunt.config('themesFolder.namePart', themesFolder);
}
});
grunt.registerTask('processLess', ['saveFolderNameFromOption', 'less:production']);
};
Running the ProcessLess task
Run the task via the command line as follows:
$ grunt processLess --themesFolder=5.1.1.5830
Note: The additional option that is specified. Namely: --themesFolder=5.1.1.5830
When using the above command the .css output will be directed to the following path:
'../../application/user/themes/some-theme-5.1.1.5830/css/main.css': 'less/base.less'
Now, each time you run the task you modify the options accordingly.
Benefits: By providing the version number as an option via the CLI will avoid having to reconfigure your Gruntfile.js each time it is run.

Grunt-complexity on all the files in a directory

I'd like to run Grunt-Complexity on all the files in a directory?
I'd like to get this kind of output.
Is there a way?
My js files are all under a subdirectory called "js".
Here's my gruntfile:
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
// Task configuration.
complexity: {
generic: {
src: ['grunt.js', 'js/*'],
//exclude: ['doNotTest.js'],
options: {
breakOnErrors: false,
jsLintXML: 'report.xml', // create XML JSLint-like report
checkstyleXML: 'checkstyle.xml', // create checkstyle report
pmdXML: 'pmd.xml', // create pmd report
errorsOnly: false, // show only maintainability errors
cyclomatic: [3, 7, 12], // or optionally a single value, like 3
halstead: [8, 13, 20], // or optionally a single value, like 8
maintainability: 100,
hideComplexFunctions: false, // only display maintainability
broadcast: false // broadcast data over event-bus
}
}
}
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-complexity');
// Default task.
grunt.registerTask('default', 'complexity');
};
I'm simply calling this by typing
grunt
from the command line.
then if I type this
grunt complexity js/*
I get
Warning: Task "js/AgencyMediaController.js" not found. Use --force to continue.
Aborted due to warnings.
And AgencyMediaController.js is the first file in my js directory. So it's having a look and listing the files, but then it crashes.
Thanx!
example:
for all js file in JS folder:
src: ['js/**/*.js']
for ass .scss files in scss folder:
src: ['scss/**/*.scss']
I suggest for you create a config for your src folder can be easy in future folder changes in future projects:
sample:
var src;
config.src = src = {
sassMain : 'scss/main.scss',
distFolder : 'public/stylesheets/lovelycss.dist.css',
devFolder : 'public/stylesheets/lovelycss.dev.css',
libFolder : 'lib/**/*.js',
sassFolder : 'scss/**/*.scss',
spriteCssFolder : 'scss/helpers/_sprite.scss',
spriteDestImg : 'public/images/sprite/spritesheet.png',
spriteSrc : 'public/images/min/*.{png,jpg,gif}',
imageminCwd : 'public/images/',
imageminDest : 'public/images/min'
};
//grunt Watch ===============================
config.watch = {
scripts: {
files: ["<%= src.libFolder %>", "<%= src.sassFolder %>"]
,tasks: ["dev", "sass:dist"]
//,tasks: ["dev",'sass:dist']
}
}
I hope that helped you.
It's been quite a long while since I asked this question. I just ran into the same issue again and found the answer so here it is:
In the end it turned out to be that one of the files I was trying to analyse was causing the crash. This particular Javascript environment allows for C-like preprocessor directives and the Javascript file had something like this:
var mySettings = {
//#ifdef FOO_CONSTANT
setting : constants.FOO_SETTING
//#endif
//#ifdef BAR_CONSTANT
setting : constants.BAR_SETTING
//#endif
};
I guess the problem is that if this is read as strictly Javascript, the preprocessor directives are just plane comments, and there's a comma missing between the two properties, so Grunt complexity is unable to read this because of a syntax error. Using --force makes no difference BTW.
The annoying part is that this is all the error shows:
$ grunt --force
Running "complexity:generic" (complexity) task
Warning: undefined: Unexpected token, expected , (17570:1) Used --force, continuing.
Done, but with warnings.
So while it does say expected , (175:1) it doesn't say in which of the several Javascript files in this project the problem was found!
Just adding exclude: ['path/to/MyFileWithPreprocessorDirectives.js'] to Gruntfile.js in order to exclude this file from the analysis gets me around the problem.

Error 'cannot find name ...' using karma-typescript-preprocessor plugin

I am trying to automate unit test execution using grunt karma and karma-typescript-preprocessor.
However, when I run 'grunt watch', karma outputs the following error :
ERROR [preprocessor.typescript]: /home/loic/Code/appName/src/app/app.spec.ts.ktp.ts(15,13): error TS2304: Cannot find name 'expect'.
This error happens with a lot of names : 'describe, angular, expect 'etc
The strange thing is that when i run the command line 'tsc /path/to/app.spec.ts', the new js file is created, there is no error.
below my karma.conf.js :
module.exports = function ( karma ) {
karma.set({
/**
* From where to look for files, starting with the location of this file.
*/
basePath: '../',
typescriptPreprocessor: {
// options passed to the typescript compiler
options: {
sourceMap: false, // (optional) Generates corresponding .map file.
target: 'ES5', // (optional) Specify ECMAScript target version: 'ES3' (default), or 'ES5'
module: 'amd', // (optional) Specify module code generation: 'commonjs' or 'amd'
noImplicitAny: false, // (optional) Warn on expressions and declarations with an implied 'any' type.
noResolve: true, // (optional) Skip resolution and preprocessing.
removeComments: true // (optional) Do not emit comments to output.
},
// transforming the filenames
transformPath: function(path) {
return path.replace(/\.ts$/, '.js');
}
},
/**
* This is the list of file patterns to load into the browser during testing.
*/
files: [
<% scripts.forEach( function ( file ) { %>'<%= file %>',
<% }); %>
'src/**/*.ts'
],
exclude: [
'src/assets/**/*.ts',
'src/typeScript/**/*.ts'
],
frameworks: [ 'jasmine' ],
plugins: [ 'karma-jasmine', 'karma-firefox-launcher', 'karma-typescript-preprocessor' ],
preprocessors: {
'**/*.ts': 'typescript'
},
/**
* How to report, by default.
*/
reporters: 'dots',
/**
* On which port should the browser connect, on which port is the test runner
* operating, and what is the URL path for the browser to use.
*/
port: 9018,
runnerPort: 9100,
urlRoot: '/',
/**
* Disable file watching by default.
*/
autoWatch: false,
/**
* The list of browsers to launch to test on. This includes only "Firefox" by
* default, but other browser names include:
* Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS
*
* Note that you can also use the executable name of the browser, like "chromium"
* or "firefox", but that these vary based on your operating system.
*
* You may also leave this blank and manually navigate your browser to
* http://localhost:9018/ when you're running tests. The window/tab can be left
* open and the tests will automatically occur there during the build. This has
* the aesthetic advantage of not launching a browser every time you save.
*/
browsers: [
'Firefox'
]
});
};
Any help would be appreciated
Try removing the line
noResolve: true,
from the typescriptPreprocessor configuration. It seems to cause the compiler not to resolve your references properly.
I had the same issue, and I ended up doing gulp tasks to run in sequence typescript compile and karma test.
Set the 'noResolve' value to false, and add the references
I also comment about this here
https://github.com/sergeyt/karma-typescript-preprocessor/issues/29
What helped me is adding
///<reference path="../node_modules/karma-typescript-preprocessor/typings/jasmine/jasmine.d.ts"/>
On the beginning of each test file.
When I tried adding
typescriptPreprocessor: {
typings: ['path/to/typings/jasmine.d.ts']
...
I was getting errors complaining about duplicate definitions...

How can I skip a grunt task if a directory is empty

I'm using grunt-contrib's concat and uglify modules to process some javascript. Currently if src/js/ is empty, they will still create an (empty) concat'd file, along with the minified version and a source map.
I want to task to detect if the src/js/ folder is empty before proceeding, and if it is, then the task should skip (not fail). Any ideas how to do this?
The solution may not be the prettiest, but could give you an idea. You'll need to run something like npm install --save-dev glob first. This is based on part of the Milkshake project you mentioned.
grunt.registerTask('build_js', function(){
// get first task's `src` config property and see
// if any file matches the glob pattern
if (grunt.config('concat').js.src.some(function(src){
return require('glob').sync(src).length;
})) {
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});
A gist for comparison: https://gist.github.com/kosmotaur/61bff2bc807b28a9fcfa
With this plugin:
https://www.npmjs.org/package/grunt-file-exists
You can check file existence. (I didn't try, but the source looks like supporting grunt expands. (*, ** ...)
For example like this::
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js']
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
'maintask',
]);
But maybe if the file doesn't exist it will fail with error instead of simple skip.
(I didn't test it.)
If this is a problem you can modify a bit the source of this plugin to run the related task if the file exists:
The config:
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js'],
options: {tasks: ['maintask']}
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
]);
And you should add this:
grunt.task.run(options.tasks);
In this file:
https://github.com/alexeiskachykhin/grunt-file-exists/blob/master/tasks/fileExists.js
after this line:
grunt.log.ok();
Maybe this is just a more up-to-date answer as the others are more than a year old, but you don't need a plugin for this; you can use grunt.file.expand to test if files matching a certain globbing pattern exist.
Update of #Kosmotaur's answer (path is just hard-code here though for simplicity):
grunt.registerTask('build_js', function(){
// if any file matches the glob pattern
if (grunt.file.expand("subdir/**/*.js").length) { /** new bit here **/
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});

Resources