[S]cripted-r[ake] -- a JavaScript build tool similar to rake, or make.


[S]cripted-r[ake] -- a JavaScript build tool similar to rake, or make.


This package contains saké, a JavaScript build program that runs in node with capabilities similar to ruby's Rake.

Saké has the following features:

  1. Sakefiles(saké’sversion of Rakefiles) are completely defined in standard JavaScript (or CoffeeScript, for those who want an even more Rake-like feel).
  2. Flexible FileListsthat act like arrays but know about manipulating file names and paths.
  3. cleanand clobbertasks are available for tidying up.
  4. Asynchronoustask handling, with easy options for specifying synchronoustasks.
  5. Simple namespacingof tasks to break projects into discreet parts.
  6. Many utility methods for handling common build tasks (rm, rm_rf, mkdir, mkdir_p, sh, cat, etc...)


Install with npm

Download and install with the following:

npm install -g sake

Command-Line Usage

% sake -h

Usage: sake [TASKNAME] [ARGUMENTS ...] [ENV=VALUE ...] [options]

[TASKNAME]     Name of the task to run. Defaults to 'default'.
[ARGUMENTS ...]     Zero or more arguments to pass to the task invoked.
[ENV=VALUE ...]     Zero or more arguments to translate into environment variables.

   -f, --sakefile PATH       PATH to Sakefile to run instead of searching for one.
   -T, --tasks [PATTERN]     List tasks with descriptions (optionally, just those
                             matching PATTERN) and exit.
   -P, --prereqs [PATTERN]   List tasks and their prerequisites (optionally, just
                             those matching PATTERN) and exit.
   -r, --require MODULE      Require MODULE before executing Sakefile and expose the
                             MODULE under a sanitized namespace (i.e.: coffee-script
                             => [sake.]coffeeScript). Can be specified multiple
   -l, --sakelib PATH        Auto-include any .sake[.js|.coffeee] files in PATH.
                             (default is 'sakelib'.) Can be specified multiple times
   -n, --dry-run             Do a dry run without executing actions.
   -C, --no-chdir            Do not change directory to the Sakefile location.
   -N, --no-search           Do not search parent directories for a Sakefile.
   -G, --no-system           Do not use SAKE_PATH environment variable to search for
                             a Sakefile.
   -S, --sync                Make all standard tasks 'synchronous' by default.
   -d, --debug               Enable additional debugging output.
   -q, --quiet               Suppress informational messages.
   -V, --version             Print the version of sake and exit.
   -h, --help                Print this help information and exit.

If a Sakefile is not specified, sake searches the current directory, and all parent
directories, for one (unless -N, --no-search is set). Otherwise, if the SAKE_PATH
environment variable is defined it searches those path(s) (unless -G, --no-system is

If specified, or found through normal searching (not in SAKE_PATH(s)), sake changes the
process' current working directory to the directory of the found Sakefile (unless -C,
--no-chdir is set), otherwise it stays where it was run from.

Sake then invokes the specified TASKNAME, or the "default" one.

Sakefile can be one of "Sakefile", or "sakefile", with an optional extension of ".js",
or ".coffee".


These are installed when sakeis installed.

nomnom:    =1.5.x
async:     =0.1.x
resolve:   =0.2.x
proteus:   =0.1.x
wordwrap:  =0.0.x
glob:      =3.1.x
minimatch: =0.2.x

Development Dependencies

Installed when you run npm linkin the package directory.

mocha:  =0.3.x
should: =0.5.x
ejs:    =0.7.x

Sakefile Usage

Within a Sakefile, Saké's methods are exported to the global scope, so you can invoke them directly:

task("taskname", ["prereq1", "prereq2"], function (t) {
    // task action...

Within another node module you can require("sake")and access the methods on the exported object, or even run a block of code in the sakécontext (virtual machine):

var sake = require("sake");

sake.task("taskname", ["prereq1", "prereq2"], function (t) {
    // task action...

// The function passed to sake#run is compiled and run in the sake context.
// The function **will not** have access to any variables in this scope,
// nor, will variables leak from the function's scope into this one. () {
    var jsFiles = new FileList("src/js/**/*.js");


    task("script-min.js", jsFiles, function (t) {
        var cmd = "infuse " + (path) {
                return "-i " + path;
            }) + " -E";

        sh(cmd, function (err, result) {
            write(, result, "utf8");


The remainder of this documentation will assume that we are calling the methods from within a Sakefile.

Defining Tasks

The various task methods take the following arguments:

  1. taskname: string— naming the task
  2. prerequisites: optional
    • an arrayof:
      • stringtask name, or
      • a FileLists, or
      • a functionthat returns one of the above.
    • or, you can also pass a FileListin directly for prerequisites.
  3. action: an optionalfunctionthat will be called when the task is invoked. It will be passed the task instance as its first argument, followed by any arguments that it was invoked with (either from the command-line, or through code). Task arguments can also be accessed through the instance's argumentsproperty. i.e: t.arguments.

All task methods return the task instance.

If a task is already defined, it will be augmented by the additional arguments. So, this:

task("one", ["othertask"], function (t) {
    // do action one...

task("one", function (t) {
    // do action two...


Would result in a task "othertask" with no prerequisites, and no action, and a task "one" with "othertask" as a prerequisite and the two functions as its actions.

Notehow the dependent task was defined afterit was required as a prerequisite. Task prerequisites are not resolved until the task is invoked. This leads to flexibility in how to compose your tasks and prerequisite tasks.

Task Instance Properties and Methods

  • name {string}— the name of the task.
  • namespace {string}— the task's namespace.
  • type {string}— one of "task", "file-task", or "file-create-task"
  • fqn {string}— the fully qualified name of the task. i.e: "namespace:name".
  • prerequisites {array}— the list of prerequisite names for the task.
  • isNeeded {boolean}— whether or not this task needs to run.
  • timestamp {boolean}— the last modification time for the task.
  • invoke([args ...]) {Task}— invoke the task passing args to each action for the task. Will run any prerequisites first. Returns the task instance.
  • execute([args ...]) {Task}— Like invoke, but run the task even if it has already been run, or is not needed.
  • done()— signal that the current task's action is done running.
  • abort([msg], [exitCode])— abort the currently running task's actions, if exitCodeis specified sakéwill exit with that exit code and no other tasks will be processed. Otherwise, task processing will continue as normal.

Task Static Properties and Methods

  • namespace {string}— the current namespace.
  • invoke(name, [rest ...]) {Task}— invoke the named task and pass it the rest of the arguments.
  • get(name, [namespace]) {Task}— get the task name_, optionally start looking in _namespace. Will throw an error if it can not find a task.
  • lookup(name, [namespace]) {Task|null}— lookup a task with name_, optionally start looking in _namespace.
  • getAll() {array[Task]}— return all defined tasks.
  • has(name, [namespace]) {boolean}— does the task nameexist?
  • find(args, sortFn) {array[Task]}— search for a task. argscan be an object with keys specifying which properties to match on, and their values denoting the value to match. argscan also be a function that accepts a task and returns a boolean whether or not that task matches.

File Tasks

File tasks are created with the (appropriately named) filemethod. File tasks are only triggered if the file doesn't exist, or the modification time of any of its prerequisites is newer than itself.

file("path/to/some/file", function (t) {

The above task would only be triggered if path/to/some/filedid not exist.

The following:

file("combined/file/path", ["pathA", "pathB", "pathC"], function (t) {
    write(, cat(t.prerequisites), "utf8");

would be triggered if path/to/some/filedid not exist, or its modification time was earlier than any of its prerequisites (pathA, pathB, or pathC).

Directory Tasks

Directory tasks, created with the directorymethod, are tasks that will only be called if they do not exist. A task will be created for the named directory (and for all directories along the way) with the action of creating the directory.

Directory tasks do not take any prerequisitesor an actionwhen first defined, however, they may be augmented with such after they are created:


task("dir/path/to/create", ["othertask"], action (t) {
    //... do stuff

File Create Tasks

A file create task is a file task, that when used as a prerequisite, will be needed if, and only if, the file has not been created. Once created, it is not re-triggered if any of its prerequisites are newer, nor does it trigger any rebuilds of tasks that depend on it whenever the file is updated.

fileCreate("file/path/to/create.ext", ["pathA", "pathB"], function (t) {
    // create file...

(A)Synchronicity and Tasks

In sakéall task actions are assumed to be asynchronousand an action must call its task's donemethod to tell sakéthat it is done processing stuff.

task("long task", function (t) {
    sh("some long running shell command", function (err, result) {
        // do stuff...

Alternatively, you can use the Syncversion of a task to add a synchronousaction, and donewill be called for you after the function completes.

taskSync("longtask", function (t) {
    cp("some/dir/file.js", "other/dir/file.js");

There are Syncversions of all the core tasks: taskSync, fileSync, and fileCreateSync. There are also Asyncversions of each task: taskAsync, fileAsync, and fileCreateAsync. The actions created for a directorytask are synchronous. You can add asynchronoustask actions after the initial definition.

Thirdly, you can specify that all of the core task creation functions generate synchronousactions by setting saké's"sync" option to true, or by use of the command-line option -S, --sync.

sake.options.sync = true;

taskSync("longtask", function (t) {
    cp("some/dir/file.js", "other/dir/file.js");

//... define more synchronous tasks

//... and then revert to async
sake.options.sync = false;

Ultimately, it is up to the sakéscript author to correctly designate a task action as synchronousor asynchronous. Nothing prevents the running of an asynchronousfunction within a task's synchronousaction. If nothing is dependent on the result of that action, then no problem would occur. It's when other tasks rely on the completion of certain asynchronousactions, that problems may arise.

In the case of asynchronousactions, Sakéwill issue a WARNINGwhen it can not detect a done()call within that action.


Sakésupports simple name spacing of tasks. Simply prepend the namespace before the task name with a colon ":". This can be done when defining a task, or in the list of prerequisites for a task.

// Defines a task 'fuz' in the namespace 'foo' that depends on the task 'buz'
// in the namespace 'baz'
task("foo:fuz", ["baz:buz"], function (t) {

Then invoke the task as so:

% sake foo:fuz

You can also use the convenience function namespaceto wrap your task definitions for a particular namespace. Any namespace nakeddefinitions will first be tried in the local namespace, otherwise they will fall-back to the default namespace:

task("default", function (t) {

task("bazil", function (t) {

// define within the 'foo' namespace
namespace("foo", function () {

    // defines 'foo:foom' task
    task("foom", function (t) {


namespace("baz", function () {

    // this task is defined in the 'baz' namespace, and depends on the
    // 'foom' task from the 'foo' namespace
    task("bazil", ["foo:foom"], function (t) {
    // This task is also defined in the 'baz' namespace. It depends on 
    // the 'bazil' task, which is also defined in 'baz' namespace. It
    // also depends on the 'default' task in the 'default' (top-level)
    // namespace.
    task("buzil", ["bazil", "default"], function (t), {
    // If 'bazil' wasn't defined in the 'baz' namespace, it would resolve
    // to the 'default' namespace task.

Note:prerequisite names are tied to the defined task's namespace. i.e: If a task "foo:fuz" depends on "foom" sakéwill look in the "foo" namespace for "foom", and then the "default" namespace.

Note:although the namespacefunctions can be nested, sakédoes not track the hierarchy of namespacecalls -- if a dependent task is not found in the current task's namespace, it will look for it in the default namespace.

Passing Arguments to A Task

There are two ways to pass arguments to a task.

First, sakétranslates any arguments in the form of ENV=VALUEon the command-line into process.envvalues:

% sake build BUILD_TYPE=debug ENVIRONMENT=prod

Will set process.env.BUILD_TYPEto "debug" and process.env.ENVIRONMENTto "prod". The values are JSON parsed; so the values are translated into real JavaScript values (numbers, true, false, etc...).

Secondly, saképasses all non-option, and non-ENV=VALUE, looking arguments from the command-line to the invoked task:

% sake foo 3.14 pie true

Will invoke the "foo" task and pass to each of foo's actions (after the task instance itself) 3.14, "pie" and true. The arguments are also JSON parsed to get real JavaScript values.

task("foo", function (t, amt, word, flag) {
    log(amt);   // => 3.14
    log(word);  // => "pie"
    log(flag);  // => true

Note:This differs from how rake, and jakedo things. In sakéyou can only invoke one task on the command-line. All other arguments on the command-line are considered task arguments.

Including Other Saké Files

You can include other sakéfiles with the includeand loadmethods. The included files will be run in the sakécontext, so any variables declared will be set on the global sakécontext. This allows you to break your project build files up into discreet files. Just be aware of naming collisions.

All requireand include, or loadstatements, are resolved relative to the current file, so you can create your own hierarchy of build dependencies.

You can also add your own paths to the list of ones that sakéuses to resolve requires: [sake.]includePathsis an array of directory paths to search. They are tried in reverse order, so if you push a path on to the array that path will be tried first, followed by any others.

// in a Sakefile

require("some-module"); // will be tried in some/path/some-module.js

Also, the __dirnameand __filenameproperties are available in includedfiles to help resolve local includes.

var Path = require("path");

include(Path.join(__dirname, "include-dir/include-file"));

Sake Library

Sakéwill load any .sake, .sake.js, or .sake.coffeefiles located in a sakelibdirectory relative to the Sakefilebeing run. This directory name can be modified with the -l, --sakeliboption, and multiple directories can be specified. This allows you to re-use common tasks across multiple projects.

File Lists

FileLists are lists of file paths.

new FileList("*.scss");

Would contain all the file paths with a ".scss" extension in the top-level directory of your project.

You can use FileLists pretty much like an Array. You can iterate them (with forEach, filter, reduce, etc...), concatthem, splicethem, and you get back a new FileList object.

To add files, or glob patterns to them, use the #includemethod:

var fl = new FileList("*.scss");
fl.include("core.css", "reset.css", "*.css");

You can also exlucudefiles by Glob pattern, Regular Expressionpattern, or by use of a functionthat takes the file path as an argument and returns a truthyvalue to exclude a file.

// Exclude by RegExp

// Exclude by Glob pattern

// Exclude by function
fl.exclude(function (path) {
    return FS.statSync(path).mtime.getTime() < ( - 60 * 60 * 1000);

To get to the actual items of the FileList, use the #itemsproperty, or the #toArraymethod, to get a plain array back. You can also use the #getor #setmethods to retrieve or set an item.

FileLists are lazy, in that the actual file paths are not determined from the include and exclude patterns until the individual items are requested. This allows you to define a FileList and incrementally add patterns to it in the Sakefile file. The FileList paths will not be resolved until the task that uses it as a prerequisite actually asks for the final paths.

FileList Properties & Methods

  • existing— will return a new FileListwith all of the files that actually exist.
  • notExisting— will return a new FileListof all the files that do not exist.
  • extension(ext)— returns a new FileListwith all paths that match the given extension.
fl.extension(".scss").forEach(function (path) {
  • grep(pattern)— get a FileListof all the files that match the given pattern. patterncan be a plain String, a Globpattern, a RegExp, or a functionthat receives each path and can return a truthy value to include it.
  • clearExcludes()— clear all exclude patterns/functions.
  • clearIncludes()— clear all include patterns.

By default, a FileListexcludes directories. To allow directories call FileList#clearExcludes()before requesting any items.

The "clean" and "clobber" Tasks, and The CLEAN and CLOBBER FileLists

Within a Sakefile:

// defines the CLEAN FileList and 'clean' task

// defines the above, and also the CLOBBER FileTask and 'clobber' task

When the "clean" task is run, it will remove any files that have been included in the CLEAN FileList. "clobber" will remove any file, or directory, included in the CLOBBER FileList.

Saké Utility Functions

Saké defines a few utility functions to make life a little easier in an asynchronous world. Most of these are just wrappers for node's File System (require("fs")) utility methods.

Synchronous Utilities

  • mkdir(dirpath, mode="777")— create the dirpathdirectory, if it doesn't already exist.
  • mkdir_p(dirpath, mode="777"])— as above, but create all intermediate directories as needed.
  • rm(path, [path1, ..., pathN])— remove one or more paths from the file system.
  • rm_rf(path, [path1, ..., pathN])— as above, and remove directories and their contents.
  • cp(from, to)— copy a file from frompath to topath.
  • cp_r(from, to)— copy all files from frompath to topath.
  • mv(from, to)— move a file from frompath to topath.
  • ln(from, to)— create a hard link from frompath to topath.
  • ln_s(from, to)— create a symlink from frompath to topath.
  • cat(path, [path1, ..., pathN]) {string}— read all supplied paths and return their contents as a string. If an argument is an Arrayit will be expanded and those paths will be read.
  • readdir(path) {array[string]}— returns the files of directory path.
  • read(path, [enc]) {string|Buffer}— read the supplied file path. Returns a buffer, or a stringif encis given.
  • write(path, data, [enc], mode="w")— write the datato the supplied file path. datashould be a bufferor a stringif encis given. modeis a stringof either "w", for over write, or "a" for append.
  • slurp(path, [env]) {sring|Buffer}— alias for read
  • spit(path, data, [enc], mode="w")— alias for write

Asynchronous Utilities

  • sh(cmd, fn(error, result))— execute shell cmd. In the callback function, errorwill be a truthy value if there was an error, and resultwill contain the STDERR returned from cmd. Otherwise, resultwill contain the STDOUT from the cmd. If cmdis an array of shell commands, each one will be run before the next, and only when they all complete, or an error is encountered, will the callback fnbe called, and resultbe an array of the results of the individual commands.

Developer Notes

  • Due to an issue with npm v1.1.13 and up (see issue #2490), I had to move my code into the node_modules/sakedirectory and add a bundleDependenciesarray to the package.jsonfile.

Report an Issue


Copyright (c) 2012 Jerry Hamlet

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.




扫码加入 JavaScript 社区



欢迎加入 JavaScript 社区