{"id":22382426,"url":"https://github.com/jcoreio/promake","last_synced_at":"2025-07-31T03:31:48.027Z","repository":{"id":54703168,"uuid":"112044002","full_name":"jcoreio/promake","owner":"jcoreio","description":"modern, Promise-based JS make tool that can target anything, not just files","archived":false,"fork":false,"pushed_at":"2023-12-14T20:19:56.000Z","size":1393,"stargazers_count":4,"open_issues_count":4,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-08T14:56:23.831Z","etag":null,"topics":["es2015"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcoreio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-26T00:18:46.000Z","updated_at":"2022-06-02T19:46:34.000Z","dependencies_parsed_at":"2023-12-14T21:29:47.717Z","dependency_job_id":"687917e9-2fc5-4116-afbf-9adfa3108051","html_url":"https://github.com/jcoreio/promake","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/jcoreio/promake","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcoreio%2Fpromake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcoreio%2Fpromake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcoreio%2Fpromake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcoreio%2Fpromake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcoreio","download_url":"https://codeload.github.com/jcoreio/promake/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcoreio%2Fpromake/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267983371,"owners_count":24176058,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-31T02:00:08.723Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["es2015"],"created_at":"2024-12-05T00:12:58.264Z","updated_at":"2025-07-31T03:31:47.700Z","avatar_url":"https://github.com/jcoreio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# promake\n\n[![CircleCI](https://circleci.com/gh/jcoreio/promake.svg?style=svg)](https://circleci.com/gh/jcoreio/promake)\n[![Coverage Status](https://codecov.io/gh/jcoreio/promake/branch/master/graph/badge.svg)](https://codecov.io/gh/jcoreio/promake)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![npm version](https://badge.fury.io/js/promake.svg)](https://badge.fury.io/js/promake)\n\nPromise-based JS make clone that can target anything, not just files\nThis is my personal skeleton for creating an ES2015 library npm package. You are welcome to use it.\n\n\u003c!-- toc --\u003e\n\n- [Why promake? Why not `jake`, `sake`, etc?](#why-promake--why-not-jake-sake-etc)\n\n* [Quick start](#quick-start)\n  - [Install promake](#install-promake)\n  - [Create a make script](#create-a-make-script)\n  - [Run the make script](#run-the-make-script)\n* [API Reference](#api-reference)\n  - [`class Promake`](#class-promake)\n    - [`rule(targets, [prerequisites], [recipe], [options])`](#ruletargets-prerequisites-recipe-options)\n    - [`hashRule(algorithm, target, prerequisites, [recipe], [options])`](#hashrulealgorithm-target-prerequisites-recipe-options)\n    - [`task(name, [prerequisites], [recipe])`](#taskname-prerequisites-recipe)\n    - [`make(target)`](#maketarget)\n    - [`exec(command, [options])`](#execcommand-options)\n    - [`spawn(command, [args], [options])`](#spawncommand-args-options)\n    - [`cli(argv = process.argv, [options])`](#cliargv--processargv-options)\n    - [`log(verbosity, ...args)`](#logverbosity-args)\n    - [`logStream(verbosity, stream)`](#logstreamverbosity-stream)\n    - [`static Verbosity`](#static-verbosity)\n  - [`class Rule`](#class-rule)\n    - [`promake`](#promake)\n    - [`targets`](#targets)\n    - [`prerequisites`](#prerequisites)\n    - [`args`](#args)\n    - [`description([newDescription])`](#descriptionnewdescription)\n    - [`make(executionContext?: ExecutionContext)`](#makeexecutioncontext-executioncontext)\n    - [`then(onResolved, [onRejected])`](#thenonresolved-onrejected)\n    - [`catch(onRejected)`](#catchonrejected)\n    - [`finally(onFinally)`](#finallyonfinally)\n  - [The `Resource` interface](#the-resource-interface)\n  - [The `HashResource` interface](#the-hashresource-interface)\n* [How to](#how-to)\n  - [Glob files](#glob-files)\n  - [Perform File System Operations](#perform-file-system-operations)\n  - [Execute Shell Commands](#execute-shell-commands)\n  - [Pass args through to a shell command](#pass-args-through-to-a-shell-command)\n  - [Make Tasks Prerequisites of Other Tasks](#make-tasks-prerequisites-of-other-tasks)\n  - [Depend on Values of Environment Variables](#depend-on-values-of-environment-variables)\n  - [List available tasks](#list-available-tasks)\n* [Examples](#examples)\n  - [Transpiling files with Babel](#transpiling-files-with-babel)\n  - [Basic Webapp](#basic-webapp)\n\n\u003c!-- tocstop --\u003e\n\n## Why promake? Why not `jake`, `sake`, etc?\n\nI wouldn't have introduced a new build tool if I hadn't thought I could significantly improve on what others offer.\nHere is what promake does that others don't:\n\n#### Promise-based interface\n\nAll other JS build tools I've seen have a callback-based API, which is much more cumbersome to use on modern JS VMs\nthan promises and `async`/`await`\n\n#### Supports arbitrary resource types as targets and prerequistes\n\nIn addition to files. For instance you could have a rule that only builds a given docker image if some files or other\ndocker images have been updated since the last image was created.\n\n#### No inversion of control container like `jake`, `mocha`, etc.\n\nYou tell it to run the CLI in your script, instead of running your script via a CLI. This means:\n\n- You can easily use ES2015 and Coffeescript since you control the script\n- It doesn't pollute the global namespace with its own methods like `jake` does\n- It's obvious how to split your rule and task definitions into multiple files\n- You could even use it in the browser for optimizing various chains of contingent operations\n\n# Quick start\n\n### Install promake\n\n```sh\nnpm install --save-dev promake\n```\n\n### Create a make script\n\nSave the following file as `promake` (or whatever name you want):\n\n```js\n#!/usr/bin/env node\n\nconst Promake = require('promake')\n\nconst { task, cli } = new Promake()\n\ntask('hello', () =\u003e console.log('hello world!'))\n\ncli()\n```\n\nMake the script executable:\n\n```\n\u003e chmod +x promake\n```\n\n### Run the make script\n\n```\n\u003e ./promake hello\nhello world!\n```\n\n# API Reference\n\n## `class Promake`\n\n`Promake` is just a class that you instantiate, add rules and tasks to, and then tell what to run. All of its methods\nare autobound, so you can write `const {task, rule, cli} = new Promake()` and then run the methods directly without any\nproblems.\n\n`const Promake = require('promake')`\n\n### `rule(targets, [prerequisites], [recipe], [options])`\n\nCreates a rule that indicates that `targets` can be created from `prerequisites` by running the given `recipe`.\nIf all `targets` exist and are newer than all `prerequisites`, `promake` will assume they are up-to-date and skip\nthe `recipe`.\n\nIf there is another rule for a given prerequisite, `promake` will run that rule first before running the recipe for this\nrule. If any prerequisite doesn't exist and there is no rule for it, the build will fail.\n\n##### `target` (required) and `prerequisites` (optional)\n\nThese can be:\n\n- a `string` (strings are always interpreted as file system paths relative to the working directory)\n- an object conforming to [the `Resource` interface](#the-resource-interface)\n- (_`prerequisites` only_) another `Rule`, which is the same as adding that `Rule`'s own `targets` as prerequisites.\n- or an array of the above\n\n**Warning**: glob patterns (e.g. `src/**/*.js`) in `targets` or `prerequisites` will not be expanded; instead you must\nglob yourself and pass in the array of matching files. See [Glob Files](#glob-files) for an example of how to do so.\n\n##### `recipe` (optional)\n\nA function that should ensure that `targets` get created or updated. It will be called with one argument: the\n[`Rule`](#class-rule) being run.\n\nIf `recipe` returns a `Promise`,\n`promake` will wait for it to resolve before moving on to the next rule or task. If the `recipe` throws an Error or\nreturns a `Promise` that rejects, the build will fail.\n\n##### `options` (optional)\n\n- `runAtLeastOnce` - if true, the `recipe` will be run at least once, even if the `targets` are apparently up-to-date.\n  This is useful for rules that need to look at the contents of targets to decide whether to update them.\n\n#### Returns\n\nThe created [`Rule`](#class Rule).\n\nYou can get the `Rule` for a given target by calling `rule(target)` (without `prerequisites` or `recipe`), but it will\nthrow and `Error` if no such `Rule` exists, or you call it with multiple targets.\n\n### `hashRule(algorithm, target, prerequisites, [recipe], [options])`\n\nCreates a rule that determines if it needs to be run by testing if the hash of\nthe prerequisites has changed (is different than the previous has written in\n`target`, or `target` doesn't exist yet). After it does run, it will write the\nhash to `target`.\n\nThis was created to help with CI builds where timestamps can't be used to\ndetermine whether something cached from the previous build needs to be rebuilt.\n\n##### `algorithm` (required)\n\nThe [`crypto.createHash`](http://devdocs.io/node/crypto#crypto_crypto_createhash_algorithm_options) algorithm to use.\n\n##### `target` (required)\n\nThis must be a `string` or `FileResource`, to which the hash of the\n`prerequisites` will be written.\n\n##### `prerequisites` (required)\n\nThis must be an array of strings (file paths) or objects conforming to [the `HashResource` interface](#the-hashresource-interface)\n**Warning**: glob patterns (e.g. `src/**/*.js`) in `targets` or `prerequisites` will not be expanded; instead you must\nglob yourself and pass in the array of matching files. See [Glob Files](#glob-files) for an example of how to do so.\n\n##### `recipe` (optional)\n\nA function that should ensure that `targets` get created or updated. It will be called with one argument: the\n[`Rule`](#class-rule) being run.\n\nIf `recipe` returns a `Promise`,\n`promake` will wait for it to resolve before moving on to the next rule or task. If the `recipe` throws an Error or\nreturns a `Promise` that rejects, the build will fail.\n\n##### `options` (optional)\n\n- `runAtLeastOnce` - if true, the `recipe` will be run at least once, even if the `targets` are apparently up-to-date.\n  This is useful for rules that need to look at the contents of targets to decide whether to update them.\n\n#### Returns\n\nThe created [`Rule`](#class Rule).\n\n### `task(name, [prerequisites], [recipe])`\n\nCreates a task, which is really just a `rule`, but can be run by `name` from the CLI regardless of whether `name` is an\nactual file that exists, similar to a [phony target in `make`](https://www.gnu.org/software/make/manual/make.html#Phony-Targets).\n\nTask names take precedence over file names when specifying what to build in CLI options.\n\nYou can set the description for the task by calling [`.description()`](#descriptionnewdescription)\non the returned `Rule`. This description will be printed alongside the task\nif you call the CLI without any targets. For example:\n\n```js\ntask('clean', () =\u003e require('fs-extra').remove('build')).description(\n  'removes build output'\n)\n```\n\n##### `name`\n\nThe name of the task\n\n##### `prerequisites` (optional)\n\nThese take the same form as for a `rule`, and if given, `promake` will ensure that they exist and are\nup-to-date before the task is running, running any rules applicable to the `prerequisites` as necessary.\n\n**Warning**: putting the `name` of another task in `prerequisites` does not work because all `string`s in\n`prerequisites` are interpreted as files. See\n[Make Tasks Prerequisites of Other Tasks](#make-tasks-prerequisites-of-other-tasks) for more details.\n\n##### `recipe` (optional)\n\nIf given, it will be run any time the task is requested, even if the `prerequisites` are up-to-date.\n`recipe` will be called with one argument: the [`Rule`](#class-rule) being run.\n\nIf `recipe` returns a `Promise`,\n`promake` will wait for it to resolve before moving on to the next rule or task. If the `recipe` throws an Error or\nreturns a `Promise` that rejects, the build will fail.\n\n#### Returns\n\nThe created [`Rule`](#class Rule).\n\nCalling `task(name)` without any `prerequisites` or `recipe` looks up and returns the previously created task `Rule` for\n`name`, but _it will throw an `Error`_ if no such task exists.\n\n### `make(target)`\n\nMakes the given target if necessary.\n\n##### `target`\n\nThe name of a task or file, or an object conforming to the `Resource` interface\n\n#### Returns\n\nA `Promise` that will be resolved when the target recipe succeeds (or doesn't need to be rerun) or rejects when the target\nrecipe fails.\n\n### `exec(command, [options])`\n\nThis is a wrapper for [`exec` from `promisify-child-process`](https://github.com/itsjustcon/node-promisify-child-process#exec)\nwith a bit of extra logic to handle logging. It has the same\nAPI as [`child_process`](http://devdocs.io/node/child_process#child_process_child_process_exec_command_options_callback)\nbut the returned `ChildProcess` also has `then` and `catch` methods like a `Promise`, so it can be `await`ed.\n\n### `spawn(command, [args], [options])`\n\nThis is a wrapper for [`spawn` from `promisify-child-process`](https://github.com/itsjustcon/node-promisify-child-process#spawn)\nwith a bit of extra logic to handle logging. It has the same\nAPI as [`child_process`](http://devdocs.io/node/child_process#child_process_child_process_spawn_command_args_options)\nbut the returned `ChildProcess` also has `then` and `catch` methods like a `Promise`, so it can be `await`ed.\n\n### `cli(argv = process.argv, [options])`\n\nRuns the command-line interface for the given arguments, which should include requested targets\n(names of files or tasks).\nUnless `options.exit === false`, after running all requested targets, it will exit the process with a code\nof 0 if the build succeeded, and nonzero if the build failed.\n\nIf no targets are requested, prints usage info and the list of available tasks and exits with a code of 0.\n\n##### `argv` (optional, default: `process.argv`)\n\nThe command-line arguments. May include:\n\n- Task names - these tasks will be run, in the order requested\n- File names - rules for these files will be run, in the order requested\n- `--quiet`, `-q`: suppress output\n\nAs long as none of the args you want to pass don't correspond to a target name,\nyou can just add them after the target:\n\n```\nrunDocker --rm --env FOO=BAR\n```\n\nBut you can pass any arbitrary args to the rule for a target by adding\n`-- args...` after the rule:\n\n```\nrunDocker -- --rm --env FOO=BAR\n```\n\nIf you want to pass args to multiple rules, put another `--` after the args to a rule:\n\n```\nrunDocker -- --rm --env FOO=BAR -- runNpm -- install --save-dev somepackage\n```\n\n(args to rule for `runDocker`: `--rm --env FOO=BAR`, args to rule for `runNpm`: `install --save-dev somepackage`)\n\nIf, god forbit, you want to pass `--` as an arg to a rule, use `----`:\n\n```\nrunNpm -- nyc ---- --grep something\n```\n\n(args to rule for `runNpm` become `nyc -- --grep something`)\n\n##### `options` (optional)\n\nAn object that may have the following properties:\n\n- `exit` - unless this is `false`, `cli()` will exit once it has finished running the requested tasks and file rules.\n\n#### Returns\n\nA `Promise` that will resolve when `Promake` finishes running the requested tasks and file rules, or throw if it fails\n(but this is only useful if `options.exit === false` to prevent `cli()` from calling `process.exit` when it's done).\n\n### `log(verbosity, ...args)`\n\nLogs `...args` to `console.error` unless `verbosity` is higher than the user requested.\n\n#### `verbosity`\n\nOne of the enum constants in [`Promake.Verbosity`](#static-verbosity).\n\n#### `...args`\n\nThe things to log\n\n### `logStream(verbosity, stream)`\n\nPipes `stream` to `stderr` unless `verbosity` is higher than the user requested.\n\n#### `verbosity`\n\nOne of the enum constants in [`Promake.Verbosity`](#static-verbosity).\n\n#### `stream`\n\nAn instance of [`stream.Readable`](http://devdocs.io/node/stream#stream_class_stream_readable).\n\n### `static Verbosity`\n\nAn enumeration of verbosity levels for logging: has keys `QUIET`, `DEFAULT`, and `HIGH`.\n\n## `class Rule`\n\nThis is an instance of a rule created by `Promake.rule` or `Promake.task`. It has the following properties:\n\n### `promake`\n\nThe instance of `Promake` this rule was created in.\n\n### `targets`\n\nThe normalized array of resources this rule produces.\n\n### `prerequisites`\n\nThe normalized array of resources that must be made before running this rule.\n\n### `args`\n\nAny args for this rule (from the [CLI](#cliargv--processargv-options), usually)\n\n### `description([newDescription])`\n\nGets or sets the description of this rule. If you provide an argument,\nsets the description and returns this rule. Otherwise, returns the\ndescription.\n\n### `make(executionContext?: ExecutionContext)`\n\nStarts running this rule if it hasn't already been run in `executionContext`. An `ExecutionContext` will be created\nif none was passed. Returns a `Promise` that will resolve or reject when the rule finished running successfully or\nfailed.\n\n### `then(onResolved, [onRejected])`\n\nSame as calling `make().then(onResolved, onRejected)`.\n\n### `catch(onRejected)`\n\nSame as calling `make().catch(onRejected)`.\n\n### `finally(onFinally)`\n\nSame as calling `make().finally(onFinally)`.\n\n## The `Resource` interface\n\nThis is an abstraction that allows `promake` to apply the same build logic to input and output resources of any type,\nnot just files. (Internally, `promake` converts all `strings` in `targets` and `prerequisites` to `FileResource`s.)\n\n**Warning**: due to the semantics of JS [Maps](http://devdocs.io/javascript/global_objects/map), two `Resource`\ninstances are always considered different, even if they represent the same resource. So if you are using non-file\n`Resource`s, you should only create and use a single instance for a given resource.\n\nCurrently, instances need to define only one method:\n\n##### `lastModified(): Promise\u003c?number\u003e`\n\nIf the resource doesn't exist, the returned `Promise` should resolve to `null` or `undefined`.\nOtherwise, it should resolve to the resource's last modified time, in milliseconds.\n\n## The `HashResource` interface\n\nThis is an abstraction that allows `promake` to apply the same build logic to input and output resources of any type,\nnot just files. (Internally, `promake` converts all `strings` in `targets` and `prerequisites` to `FileResource`s.)\n\nCurrently, instances need to define only one method:\n\n##### `updateHash(hash: Hash): Promise\u003cany\u003e`\n\nIf the resource exists, the given `hash` should be updated with whatever data\nfrom the resource is relevant (e.g. the contents of a file, which is what\n`FileResource`'s implementation of `updateHash` does)\n\n# How to\n\n## Glob files\n\n`promake` has no built-in globbing; you must pass arrays of files to `rule`s and `task`s. This is easy with the\n`glob` package:\n\n```sh\nnpm install --save-dev glob\n```\n\nIn your promake script:\n\n```js\nconst glob = require('glob').sync\nconst srcFiles = glob('src/**/*.js')\nconst libFiles = srcFiles.map((file) =\u003e file.replace(/^src/, 'lib'))\nrule(libFiles, srcFiles, () =\u003e {\n  /* code that compiles srcFiles to libFiles */\n})\n```\n\n## Perform File System Operations\n\nI recommend using [`fs-extra`](https://github.com/jprichardson/node-fs-extra):\n\n```sh\nnpm install --save-dev fs-extra\n```\n\nTo perform a single operation in a task, you can just return the `Promise` from async `fs-extra` operations:\n\n```js\nconst fs = require('fs-extra')\nrule(dest, src, () =\u003e fs.copy(src, dest))\n```\n\nTo perform multiple operations one after another, you can use an async lambda and `await` each operation:\n\n```js\nconst path = require('path')\nconst fs = require('fs-extra')\nrule(dest, src, async () =\u003e {\n  await fs.mkdirs(path.dirname(dest)))\n  await fs.copy(src, dest))\n})\n```\n\n## Execute Shell Commands\n\nUse the [`exec` method](#execcommand-options)\nor the [`spawn` method](#spawncommand-args-options) of your `Promake` instance.\n\n```js\nconst { rule, exec, spawn } = new Promake()\n```\n\nTo run a single command in a task, you can just return the result of `exec` or `spawn` because it is Promise-like:\n\n```js\nrule(dest, src, () =\u003e exec(`cp ${src} ${dest}`))\n```\n\nTo run multiple commands, you can use an async lambda and `await` each `exec` or `spawn` call:\n\n```js\nrule(dest, src, async () =\u003e {\n  await exec(`cp ${src} ${dest}`)\n  await exec(`git add ${dest}`)\n  await exec(`git commit -m \"update ${dest}\"`)\n})\n```\n\n## Pass args through to a shell command\n\nThe args from the CLI are avaliable on [`Rule.args`](#args):\n\n```js\nconst { rule, spawn } = new Promake()\n\ntask('npm', (rule) =\u003e spawn('npm', rule.args))\n```\n\nAnd run your task with:\n\n```\n./promake npm -- install --save-dev somepackage\n```\n\nSee [CLI documentation](#cliargv--processargv-options) for more details.\n\n## Make Tasks Prerequisites of Other Tasks\n\nPutting the `name` of another task in the `prerequisites` of `rule` or `task` does not work because all `string`s in\n`prerequisites` are interpreted as files.\n\nInstead, you can just include the `Rule` returned by `rule` or `task` in the `prerequisites` of another. For example:\n\n```js\nconst serverTask = task('server', [...serverBuildFiles, ...universalBuildFiles])\nconst clientTask = task('client', clientBuildFiles)\ntask('build', [serverTask, clientTask])\n```\n\nOr you can call `task(name)` to get a reference to the previously created `Rule`:\n\n```js\ntask('server', [...serverBuildFiles, ...universalBuildFiles])\ntask('client', clientBuildFiles)\ntask('build', [task('server'), task('client')])\n```\n\nSometimes I like to use the following structure for defining an `all` task:\n\n```js\ntask('all', [\n  task('server', [...serverBuildFiles, ...universalBuildFiles]),\n  task('client', clientBuildFiles),\n])\n```\n\n## Depend on Values of Environment Variables\n\nUse the [`promake-env` package](https://github.com/jcoreio/promake-env):\n\n```sh\nnpm install --save-dev promake-env\n```\n\n```js\nconst {rule, exec} = new Promake()\nconst envRule = require('promake-env').envRule(rule)\n\nconst src = ...\nconst lib = ...\nconst buildEnv = 'lib/.buildEnv'\n\nenvRule(buildEnv, ['NODE_ENV', 'BABEL_ENV'])\nrule(lib, [...src, buildEnv], () =\u003e exec('babel src/ --out-dir lib'))\n```\n\n## List available tasks\n\nRun the CLI without specifying any targets. For instance if your\nbuild file is `promake`, run:\n\n```\n\u003e ./promake\npromake CLI, version X.X.X\nhttps://github.com/jcoreio/promake/tree/vX.X.X\n\nUsage:\n  ./\u003cscript\u003e [options...] [tasks...]\n\nOptions:\n  -q, --quiet       suppress output\n  -v, --verbose     verbose output\n\nTasks:\n  build             build server and client\n  build:client\n  build:server\n  clean             remove all build output\n```\n\n# Examples\n\n## Transpiling files with Babel\n\nInstall `glob`:\n\n```sh\nnpm install --save-dev glob\n```\n\nCreate the following promake script:\n\n```js\n#!/usr/bin/env node\n\nconst Promake = require('promake')\nconst glob = require('glob').sync\n\nconst srcFiles = glob('src/**/*.js')\nconst libFiles = srcFiles.map((file) =\u003e file.replace(/^src/, 'lib'))\nconst libPrerequisites = [...srcFiles, '.babelrc', ...glob('src/**/.babelrc')]\n\nconst { rule, task, exec, cli } = new Promake()\nrule(libFiles, libPrerequisites, () =\u003e exec(`babel src/ --out-dir lib`))\ntask('build', libFiles)\n\ncli()\n```\n\nThe `libFiles` `rule` tells `promake`:\n\n- That running the recipe will create the files in `libFiles`\n- That it should only run the recipe if a file in `libFiles` is older than a file in `libPrerequistes`\n\nIf you want to run `babel` separately on each file, so that it doesn't rebuild any files that haven't changed, you can\ncreate a rule for each file:\n\n```js\nsrcFiles.forEach((srcFile) =\u003e {\n  const libFile = srcFile.replace(/^src/, 'lib')\n  rule(libFile, [srcFile, '.babelrc'], () =\u003e\n    exec(`babel ${srcFile} -o ${libFile}`)\n  )\n})\n```\n\nHowever, I don't recommend this because `babel-cli` takes time to start up and this will generally be much slower than\njust recompiling the entire directory in a single `babel` command.\n\n## Basic Webapp\n\nThis is an example promake script for a webapp with the following structure:\n\n- `build/`\n  - `assets/`\n    - `client.bundle.js` (client webpack bundle)\n  - `server/` (compiled output of `src/server`)\n  - `universal/` (compiled output of `src/universal`)\n  - `.clientEnv` (environment variables for last client build)\n  - `.dockerEnv` (environment variables for last docker build)\n  - `.serverEnv` (environment variables for last server build)\n  - `.universalEnv` (environment variables for last universal build)\n- `src/`\n  - `client/`\n  - `server/`\n  - `universal/` (code shared by `client` and `server`)\n- `.babelrc`\n- `.dockerignore`\n- `Dockerfile`\n- `webpack.config.js`\n\n```js\n#!/usr/bin/env node\n\nconst Promake = require('promake')\nconst glob = require('glob').sync\nconst fs = require('fs-extra')\n\nconst serverEnv = 'build/.serverEnv'\nconst serverSourceFiles = glob('src/server/**/*.js')\nconst serverBuildFiles = serverSourceFiles.map((file) =\u003e\n  file.replace(/^src/, 'build')\n)\nconst serverPrerequistes = [\n  ...serverSourceFiles,\n  serverEnv,\n  '.babelrc',\n  ...glob('src/server/**/.babelrc'),\n]\n\nconst universalEnv = 'build/.universalEnv'\nconst universalSourceFiles = glob('src/universal/**/*.js')\nconst universalBuildFiles = universalSourceFiles.map((file) =\u003e\n  file.replace(/^src/, 'build')\n)\nconst universalPrerequistes = [\n  ...universalSourceFiles,\n  universalEnv,\n  '.babelrc',\n  ...glob('src/universal/**/.babelrc'),\n]\n\nconst clientEnv = 'build/.clientEnv'\nconst clientPrerequisites = [\n  ...universalSourceFiles,\n  ...glob('src/client/**/*.js'),\n  ...glob('src/client/**/*.css'),\n  clientEnv,\n  '.babelrc',\n  ...glob('src/client/**/.babelrc'),\n]\nconst clientBuildFiles = ['build/assets/client.bundle.js']\n\nconst dockerEnv = 'build/.dockerEnv'\n\nconst { rule, task, cli, exec } = new Promake()\nconst envRule = require('promake-env').envRule(rule)\n\nenvRule(serverEnv, ['NODE_ENV', 'BABEL_ENV'])\nenvRule(universalEnv, ['NODE_ENV', 'BABEL_ENV'])\nenvRule(clientEnv, ['NODE_ENV', 'BABEL_ENV', 'NO_UGLIFY', 'CI'])\nenvRule(dockerEnv, ['NPM_TOKEN'])\n\nrule(serverBuildFiles, serverPrerequistes, () =\u003e\n  exec('babel src/server/ --out-dir build/server')\n)\nrule(universalBuildFiles, universalPrerequistes, () =\u003e\n  exec('babel src/universal/ --out-dir build/universal')\n)\nrule(clientBuildFiles, clientPrerequisites, async () =\u003e {\n  await fs.mkdirs('build')\n  await exec('webpack --progress --colors')\n})\n\ntask('server', [...serverBuildFiles, ...universalBuildFiles]),\n  task('client', clientBuildFiles),\n  task(\n    'docker',\n    [task('server'), task('client'), 'Dockerfile', '.dockerignore', dockerEnv],\n    () =\u003e exec(`docker build . --build-arg NPM_TOKEN=${process.env.NPM_TOKEN}`)\n  )\n\ntask('clean', () =\u003e fs.remove('build'))\n\ncli()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcoreio%2Fpromake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcoreio%2Fpromake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcoreio%2Fpromake/lists"}