Components

Let's start with the simplest of tasks:

Add an entry to ~/.ssh/config on your local computer.

First, we'll define some parameters we'll use:

<fabula>
export default {
  host: 'server',
  hostname: '1.2.3.4',
  username: 'ubuntu',
  privateKey: '/path/to/key'
}
</fabula>

The <fabula> block should contain an ES module.

Next we can add a local append command to the <commands> section:

<fabula>
export default {
  fail: false,
  host: 'server',
  hostname: '1.2.3.4',
  username: 'ubuntu',
  privateKey: '/path/to/key'
}
</fabula>

<commands>
# Append a new line
local echo >> ~/.ssh/config

# Append specified block of text
local append ~/.ssh/config:
  Host <%= host %>
      Hostname <%= hostname %>
      User <%= user %>
      IdentityFile <%= privateKey %>
      StrictHostKeyChecking no
</commands>

Save as add-to-ssh.fab and run with:

fabula add-to-ssh

Settings

The <fabula> block should contain an ES module. The module can either export an object containing all settings, or an optionally async function that returns such an object.

<fabula>
export default async (fabula) => ({
  configOption: await fabula.prompt('Config option:'),
})
</fabula>

<commands>
local echo <%= configOption %>
</commands>

Syntax

Any Bash statement will work inside <commands> (like the first one), but a few others are recognized by handlers using Fabula's modular commands API, which cause them to process differently.

As stated before, any line that starts with local is ran on the local machine. But in this snippet, we're also using Fabula's special append command, which allows you to define an indented block of text (that gets automatically dedented when parsed) and append it to a file.

Similarly, local write ... would overwrite the contents of the file, instead of simply appending to it. Both append and write are special commands provided by Fabula.

Prepend

You can append a command within the <commands> tag to automatically prepepend it every command within the block. Note that sudo is never prepended to special or custom commands, because those run under the same permission as the Fabula task is running on.

<commands local sudo>
apt update -y
apt install nginx -y
write /tmp/file:
  test contents
</commands>

Would be interpreted as:

<commands>
local sudo apt update -y
local sudo apt install nginx -y
local write /tmp/file:
  test contents
</commands>

Strings

As simple as these blocks of inline text can be (way prettier than using native Bash at least), they can still be kind of funky for some people. Fabula offers yet another alternative to this case, which is to add a <string> block with an identifier, that can later be referenced by an alternatively recognizable syntax for the special append command:

<fabula>
export default {
  host: 'server',
  hostname: '1.2.3.4',
  username: 'ubuntu',
  privateKey: '/path/to/key'
}
</fabula>

<string id="sshConfig">
Host <%= host %>
    Hostname <%= hostname %>
    User <%= username %>
    IdentityFile <%= privateKey %>
    StrictHostKeyChecking no
</string>

<commands>
# Append a new line
local echo >> ~/.ssh/config

# Append specified string
local append ~/.ssh/config strings.sshConfig
</commands>

Notice that in this case local append ~/.ssh/config is not followed by a colon and block of text, but rather strings.sshConfig.

Every string block is made available internally in the global settings object as strings.$id, which in this case, is referenced internally by the append command handler.

Dedent modifier

You can force dedention of text to the number of total white spaces in the first line by appending the dedented modifier.

<string id="config" dedented>
  White space at the beginning will be removed.
</string>

Learn more about commands and custom commands in the next section.

Chaining

Since a Fabula component may itself run other components, you may want to pass the settings and context of the parent component down to its children.

Use a dot as second parameter to fabula to do so:

<commands>
fabula . tasks/another-task
fabula . tasks/some-other-task
</commands>

When tasks/another-task runs, all settings from the caller component will be merged with its own settings -- the latter having precedence.

The rest of this page refers to unimplemented Fabula features.

Work has already started for these features to work and the following bits of documentation are currently serving as a specification.

Alternatively, you may also use a Fabula component settings function. If the default export in the <fabula> block is a function, it will run before any commands and also allow you to access its parent settings and current execution state through its second and third arguments:

<fabula>
export default (fabula) => {
  consola.info('Commands ran so far:', fabula.history.length)
  return { ...fabula.settings, newSetting: 'foobar' }
}
</fabula>

The history parameter is an Array with the result objects of every command executed up to that point. Each result object contains code, stdout, stderr and cmd (the Fabula object representing the parsed command).

The first parameter contains the Fabula helper object, which currently only provides access to settings, history and abort().