npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

grunt-thrall

v0.0.4

Published

Grunt task orchestrator/warchief

Downloads

10

Readme

grunt-thrall

Build Status Code Climate Test Coverage Dependency Status

Grunt task orchestrator/warchief

"The beginning of wisdom is the statement 'I do not know.' The person who cannot make that statement is one who will never learn anything. And I have prided myself on my ability to learn." - Thrall in Cycle of Hatred, page 77

Why?

When a project uses lots of Grunt Tasks, the Gruntfile.js tends to get really messy and hard to maintain.

With grunt-angular-toolbox, we tried to extract this complexity into a toolbox that handles all grunt related things for a main project.

This works fine, but the toolbox itself was still spaghetti-code-ish and hard to understand and maintain for most users.

Thrall contains all orchestration logic and is 100% test covered. This allows consuming packages to focus on task definition without having to worry to much about configuration loading and option handling.

Usage

Install the module:

npm install grunt-thrall --save-dev

// gruntfile.js
module.exports = function(grunt) {
	var thrall = require('grunt-thrall');
	
	thrall.init({
		/* see config */	
	});	
};

This can be used for any project or grunt plugin. See:

API

thrall.init(config)

  • Load all grunt plugins, specified in the projects package.json (heavily inspired by load-grunt-tasks)
  • merge defaults, provided in config with settings is grunt.config
  • Search for custom tasks, specified in the tasks/ directory
  • Dynamically load configuration for grunt plugins used by those tasks from config/ directory

Config

Required string: dir

thrall.init({dir: __dirname + 'myTasks' /* ,... */ });

The basic directory for custom tasks and grunt plugin configuration.

Expects subdir tasks/ for custom tasks and config/ for grunt plugin configuration to be present.

Required string: basePath

thrall.init({basePath: __dirname /* ,... */ });

The projects base path.

Used to findup node_modules/grunt-*/tasks/* when auto-loading grunt plugins.

Required object: grunt

thrall.init({grunt: grunt /* ,... */ });

The currently running grunt instance.

string: name

thrall.init({name: 'myProject' /* ,... */ });

Defaults to config.pkg.name project name from package.json

This is also the key for custom configuration that is merged with the defaults

// pseudo-code
var config = _.merge(config.getDefaults(), grunt.config(config.name));

boolean: loadDevDependencies

thrall.init({loadDevDependencies: false /* ,... */ });

Default: true

Whether or not to include devDependencies from package.json when auto-loading grunt plugins.

boolean: loadDependencies

thrall.init({loadDependencies: true /* ,... */ });

Default: false

Whether or not to include dependencies from package.json when auto-loading grunt plugins.

object: module

thrall.init({
	module: {
		myHelper: ['factory', require('./helpers/myHelper')]
	}
	/* ,... */ 
});

Default: {}

Task definitions, grunt plugin configurations and getDefaults are being invoked using node-di providing basic node modules.

When you need a custom helper, it can be registered here.

See DI for further informations.

function: getDefaults

thrall.init({
	getDefaults: function(/* di here */) {
		return {
			foo: 'bar'
		}
	}
	/* ,... */ 
});

Default configuration will be merged and be available as grunt.config(config.name).

Task Factories

every file in config.dir/tasks/ is expected to export a factory function, returning a task definition object. The name will be generated by the path relative to the tasks dir.

Factories are being invoked using node-di, see DI for further informations.

Naming

// tasks/foo/bar.js
module.exports = function(/* di here */) {
	return {};
};

will register task foo:bar that, when can be called with grunt foo:bar and does nothing.

string/array: description

module.exports = function() {
	return {
		description: [
			'this is the bar tasks',
			'it will foo.'
		]
		/* ... */
	};
};

Task description, which is displayed by grunt --help. Arrays will be .join('\n')-ed.

array: run

Subtasks to run by this task.

// tasks/foo/bar.js
module.exports = function() {
	return {
		/* ... */
		run: [
			'jshint:src',
			'mochaTest'
		]
	};
};

Will load for grunt plugin configurations from config/jshint/src.js and config/mochaTest.js (see Configuration Factories) and execute both subtasks when grunt foo:bar is called.

runIf blocks

A runIf block can add tasks to the cue based on grunt configuration.

module.exports = function() {
	return {
		/* ... */
		run: [
			'other:task',
			{
				if: 'coverage.enabled',
				task: ['coverage']
			},
			{
				if: [
					(null != 1),
					'foo.bar'
				],
				task: 'report',
				else: 'say:goodbye'
			}
		]
	};
};

In the above example:

  • the coverage task will only run when grunt.config.get('coverage.enabled') returns a truthy value.
  • the report task will run when grunt.config.get('foo.bar') is truthy, (and null != 1 which is of cause true, too)
  • when grunt.config.get('foo.bar') is falsy the say:goodbye task runs instead

All expressions and config values in an if-array need to be true in order to run the task. There is no OR operator or !-negation.

This works well with options.

object: options

Map CLI options, environment variables and grunt modifiers to grunt config.

// tasks/foo/bar.js
module.exports = function() {
	return {
		/* ... */
		options: {
			coverage: 'coverage.enabled'
		}
	};
};

grunt foo:bar --coverage will set the grunt.config('coverage.enabled') to true.

// tasks/foo/bar.js
module.exports = function() {
	return {
		/* ... */
		options: {
			'demo-port': {
				env: 'DEMO_PORT',
				alias: 'port',
				key: 'foo.demoPort'
			}
		}
	};
};

either of

  • grunt foo:bar --demo-port=7000
  • grunt foo:bar --demo=7000
  • DEMO_PORT=7000 grunt foo:bar

will set the grunt.config('foo.demoPort') to 7000.

// tasks/foo/bar.js
module.exports = function() {
	return {
		/* ... */
		options: {
			coverage: {
				grunt: ':coverage',
				key: 'coverage.enabled'
			}
		}
	};
};

grunt foo:bar:coverage will set grunt.config('coverage.enabled') to true.

function: runFilter

Filter that may manipulate the tasks cue before execution.

// tasks/foo/bar.js
module.exports = function() {
	return {
		/* ... */
		run: ['foo', 'bar'],
		runFilter: function(tasks, args) {
			if (args[0] === 'baz') {
				tasks.shift();
			}
			return tasks;
		}
	};
};

In this case, when grunt foo:bar:baz is called, only the foo subtask will run.

Configuration Factories

every file in config.dir/config/ is expected to export a factory function, returning a configuration object. The name has to match the path that this configuration will be placed at, in the grunt config.

Factories are being invoked using node-di, see DI for further informations.

// config/jshint/src.js
module.exports = function(/* di here */) {
	return {
		options: {
			ignores: ['**/*.coffee'],
			jshintrc: true,
		},
		src: [
			'<%= my.src.files.js %>'
		]
	};
};

This is similar to the following standard configuration, only that it's split in to a lot of small files, with is more easy to maintain for big projects.

grunt.initConfig({
	jshint: {
		src: {
			options: {
				ignores: ['**/*.coffee'],
				jshintrc: true,
			},
			src: [
				'<%= my.src.files.js %>'
			]
		}
	}
});

DI

getDefaults, Task Factories and Configuration Factories are being invoked with a node-di module, providing the following services:

merged callback

/* ... */
getDefaults: function(merged) {
	merged(function(mergedConfig) {
		mergedConfig.foo = 'baz';
	});
	return {foo: 'bar'};
}

MIT License