Tasks
Tasks are a project-level feature to define a project command system backed by shell scripts. Tasks are used to implement development workflows and are accessible through the projen CLI as subcommands.
The following example defines a task named "hello" which executes the shell
command echo hello, world!
:
const hello = project.addTask("hello");
hello.exec("echo hello, world!");
Run npx projen
and the task will be available in the CLI:
$ npx projen hello
๐ค hello | echo hello, world!
hello, world!
You can also define some metadata and the first exec step declaratively:
const projen = require("projen");
const hello = project.addTask("hello", {
description: "say hello",
category: projen.tasks.TaskCategory.TEST,
exec: "echo hello, world!",
});
Stepsโ
Tasks can include any number of steps:
hello.exec("echo step number 2");
// a name can be added to a step if desired
hello.exec("echo foo bar", { name: 'print the text "foo bar"' });
The --inspect
option can be used to display the contents of a task:
$ projen hello --inspect
echo hello, world!
echo step number 2
echo foo bar
If a step fails, the task will fail and all subsequent steps will not be executed.
You can also add steps to the beginning of a task:
const hello = project.addTask("hello");
hello.exec("echo hello");
hello.prepend("echo world");
Then:
$ projen hello 2> /dev/null
world
hello
Subtasksโ
Tasks can also spawn sub-tasks as a step:
const world = project.addTask("world");
world.exec("echo world!");
const hello = project.addTask("hello");
hello.exec("echo hello");
hello.spawn(world);
The output will be:
$ projen hello
๐ค hello | echo hello
hello
๐ค hello ยป world | echo world!
world!
$ projen hello --inspect
echo hello
world:
echo world!
Environmentโ
Environment variables can be defined at the project level (for all tasks), the task level, or the task step level:
project.tasks.addEnvironment("FOO", "hello");
const hello = project.addTask("hello");
hello.env("BAR", "beautiful");
hello.exec("echo $FOO, $BAR $BAZ!", { env: { BAZ: "world" } });
Then:
$ projen hello
๐ค hello | echo $FOO, $BAR $BAZ!
hello, beautiful world!
You can also evaluate environment variable values from a shell command:
const hello = project.addTask("hello");
hello.env("TIME", "$(date)");
hello.exec("echo current time is $TIME");
Then:
$ projen hello
๐ค hello | echo current time is $TIME
current time is Tue Dec 1 09:32:33 IST 2020
Conditionsโ
The condition
option includes a command that determines if the task is
executed. If the command exits successfully (with a zero exit code), steps will
be executed. Otherwise, the task will be skipped (successfully).
const hello = project.addTask("hello", {
condition: '[ -n "$CI" ]', // only execute if the CI environment variable is defined
exec: "echo running in a CI environment",
});
Then:
$ projen hello
๐ค hello | condition: [ -n "$CI" ]
๐ค hello | condition exited with non-zero - skipping
$ CI=1 projen hello
๐ค hello | condition: [ -n "$CI" ]
๐ค hello | echo running in a CI environment
running in a CI environment
The condition
option can also be specified on individual task steps, for more
granular control over task execution behavior:
const hello = project.addTask("hello", {
steps: [
{ exec: "running in a CI environment", condition: '[ -n "$CI" ]' },
{ exec: "not running in a CI environment", condition: '[ ! -n "$CI" ]' },
],
});
Then:
$ projen hello
๐ค hello | condition: [ -n "$CI" ]
๐ค hello | condition exited with non-zero - skipping
๐ค hello | condition: [ ! -n "$CI" ]
๐ค hello | echo not running in a CI environment
not running in a CI environment
$ CI=1 projen hello
๐ค hello | condition: [ -n "$CI" ]
๐ค hello | echo running in a CI environment
running in a CI environment
๐ค hello | condition: [ ! -n "$CI" ]
๐ค hello | condition exited with non-zero - skipping
Tasks as npm scriptsโ
By default, npm scripts in NodeProject
s (or derivatives) are implemented by delegating the
command to the projen CLI:
{
"scripts": {
"compile": "npx projen compile"
}
}
This means that when yarn compile
or npm run compile
are executed, the
projen CLI will be invoked and the task will be executed.
You can see a list of all steps in a task from the command line by passing
the --inspect
flag, e.g. yarn compile --inspect
.
Overriding Tasksโ
The Project-defined tasks can be overridden by the user using the reset()
method. For example, the user may prefer
running tests on a Python project using nose2
instead of pytest
, and the current PythonProject
implementation uses pytest
by default. The user can override the test
task to use nose2
instead:
const testTask = project.tasks.tryFind("test");
if (testTask) {
testTask.reset("nose2 tests/");
}
If you're using .projenrc.py
, the example would look like this:
test_task = project.tasks.try_find("test")
if test_task:
test_task.reset("nose2 tests/", receive_args=True) # Passes through any arguments passed to the task
Extending Tasksโ
Similar to overriding tasks, you can extend tasks by adding additional steps to the end of the task:
project.tasks.tryFind("build")?.exec("echo Build completed successfully.");
Saying thingsโ
There is an additional say
step that can be used to print a message to the console:
project.tasks.tryFind("build")!.say("Build completed successfully.");
Once the task is complete, an additional message will be printed to the console:
... other build steps here
๐พ build | Build completed successfully.
Patching an existing task vs. creating a new taskโ
The choice between patching an existing task and creating a new task is largely personal or organizational preference. However, there are some general guidelines:
- If you're adding a new task that is conceptually similar to an existing task, it's probably
best to patch the existing task. For example, if you're running tests using
pytest
and also leveraging Cypress for end-to-end testing, it's probably best to add a new step to the existingtest
task to run Cypress tests. - If you're combining tasks that already exist with a new series of tasks that are not very
similar, it is probably best to add a new task. An example would be if you have specific linters
you run during CI, you may want to create a
lint
task.- The exception to the above guideline is when you want to run everything as part of an existing
CI/CD pipeline. In that case, you may want to use the existing
build
task to spawn a a customci
task, or simply add steps. It's largely up to your preference, but keep in mind the default CI/CD workflows for projen GitHub projects already executesbuild
.
- The exception to the above guideline is when you want to run everything as part of an existing
CI/CD pipeline. In that case, you may want to use the existing