Local packages

Local packages let you reuse tasks across multiple run definitions. This is most commonly used for sharing dependency setup across CI and sandboxes, but it can also be used any time you want to reduce duplication across run definitions.

A local package is a YAML file in your .rwx/packages directory that looks like a run definition file, except it declares package: true and cannot declare on or base. Here's an example of a local package that sets up Node and installs dependencies:

# .rwx/packages/setup.yml
package: true

parameters:
  node-version:
    description: The version of Node to install
    required: true

tool-cache:
  vault: my-vault

tasks:
  - key: node
    call: nodejs/install 1.1.15
    with:
      node-version: ${{ params.node-version }}

  - key: yarn
    use: node
    run: npm install -g yarn@v1

  - key: node-modules
    use: [package.use, yarn]
    tool-cache: node-modules-node-${{ params.node-version }}
    run: yarn install
    filter:
      - package.json
      - yarn.lock

Like published packages, local packages are invoked using the call key, and their parameters are supplied with with:

base:
  image: ubuntu:24.04
  config: rwx/base 1.1.1

tasks:
  - key: code
    call: git/clone 2.0.7

  - key: setup
    use: code
    call: ${{ run.dir }}/packages/setup.yml
    with:
      node-version: 22.18.0

  - key: test
    use: setup
    run: ...

Local packages inherit their base layers from their caller. They cannot define their own base configuration.

Dependencies

A task that calls a local package can use other tasks in its run:

- key: setup
  use: code
  call: ${{ run.dir }}/packages/setup.yml

Tasks inside a local package do not automatically use the dependencies supplied to the local package call. They must declare use: package.use in order to use those dependencies. If a task inside a local package does not use package.use, then that task will run directly from the base layers of the caller. When using package.use, you can combine the package's dependencies with dependencies on tasks inside the package, such as in the following example:

package: true

tasks:
  - key: write-file
    run: echo a > file.txt

  - key: ls
    use: [package.use, write-file]
    run: ls -a | tee ls-output.txt # this will list file.txt and any files from the dependencies supplied to the local package

Note that package.use must always be the first dependency specified in use. If the above example tried to do use: [write-file, package.use], the ls task would error and inform you that package.use must come first.

From the calling run, tasks can use the local package:

tasks:
  - key: files
    call: ${{ run.dir }}/packages/files.yml # this is the package in the example immediately above this one

  - key: test
    use: files
    run: ...

Using a local package uses all of the tasks inside that local package. You can optionally use specific tasks inside the local package instead of the entire package. For example:

tasks:
  - key: files
    call: ${{ run.dir }}/packages/files.yml # this is the package in the example two above this one

  - key: use-file
    use: files.write-file
    run: ls -a # this will list `file.txt` but not `ls-output.txt`

When using specific tasks inside a local package, a task will start as soon as the specific tasks it depends on are complete. It does not wait for the entire local package to complete.

Dynamic local packages

RWX supports dynamic local packages in addition to referencing a package in your .rwx directory. To do so, first run a task that writes the local package to a file and upload it as an artifact. Then, you can reference that artifact in the call expression: call: ${{ tasks.generate-local-package.artifacts.definition }}. For a full example, take a look at the following tasks:

tasks:
  - key: generate-local-package
    run: |
      cat << EOF > $RWX_ARTIFACTS/package.yml
      package: true

      tasks:
        - key: my-local-package-task
          run: echo "I'm a local package"
      EOF

  - key: call-local-package
    call: ${{ tasks.generate-local-package.artifacts['package.yml'] }}