Download

Beta

Docs Blog Workflows Styles
GitHub

Creating Workflows
7 min

How to create workflows.

Workflows can be written in TypeScript or JavaScript. They are stored in the ~/.relagit/workflows directory. If you create workflows directly inside of the workflows folder, they must be contained in single modules and cannot import other modules.

However, we suggest you use a bundler like vite or esbuild to bundle your workflows into a single module. This allows you to leverage the full powers of TypeScript and import other modules and packages.

Using the CLI

RelaGit provides a CLI to help you scaffold new workflows. You can use it to create a new workflow, install dependencies, and build your workflow.

Using the CLI
pnpm create relagit

Building a workflow with Vite

This section will go over how to create a basic workflow using vite and TypeScript. You’ll learn about relagit: modules and how the hook system works.

Setting up vite

We’ll start by scaffolding a new vite project to work in. We’ll use pnpm as our package manager, but you can use npm or yarn if you prefer.

Terminal
pnpm init pnpm add -D vite typescript @relagit/vite relagit mkdir src touch src/index.ts src/native.ts vite.config.ts

We’ll then place the following code in vite.config.ts. This defines how vite will process our workflow. We’re using the @relagit/vite plugin to allow us to not have to worry about vite’s configuration.

vite.config.ts
import { const defineConfig: (options?: RelaGitViteOptions) => UserConfigdefineConfig } from "@relagit/vite"; export default function defineConfig(options?: Partial<{ entrypoints: { plugin: string; native?: string | undefined; }; external: string[]; }> | undefined): UserConfigdefineConfig();

Now, time to get onto the workflow itself.

Creating a basic workflow

Tip

RelaGit’s JavaScript runner defines a few custom modules that you can import or require from your workflow.

We’ll use these inside of our workflow to interact with the user’s repositories and Git.

Let’s start by importing Workflow and context from actions.

src/index.ts
import { const Workflow: new (options: WorkflowOptions) => void
Construct a new workflow runner
Workflow
, const context: () => Context
When run in a workflow, will return current information about the state of the application.
context
} from "relagit:actions";
export default new new Workflow(options: WorkflowOptions): voidWorkflow({});

We can now define some basic metadata that will identify our workflow.

src/index.ts
export default new new Workflow(options: WorkflowOptions): voidWorkflow({ WorkflowOptions.name: stringname: "My Workflow", WorkflowOptions.description?: string | undefineddescription: "A workflow that does something.", });

Workflow Hooks

We can now start defining our workflow’s hooks. Hooks are functions that are called at certain points in a repository’s lifecycle.

For example, the navigate hook is called when a user switches between a repository or file, while the commit hook is called after a user commits their changes.

Each hook has unique parameters that are passed to it. You can find the parameters for each hook in the workflow types.

src/index.ts
export default new new Workflow(options: WorkflowOptions): voidWorkflow({ WorkflowOptions.name: stringname: "My Workflow", WorkflowOptions.description?: string | undefineddescription: "A workflow that does something.", WorkflowOptions.hooks?: { navigate?: ((event: "navigate", params_0: Repository | undefined, params_1: GitFile | undefined) => void | Promise<void>) | undefined; ... 9 more ...; stash_pop?: ((event: "stash_pop", params_0: Repository) => void | Promise<...>) | undefined; } | undefinedhooks: { navigate?: ((event: "navigate", params_0: Repository | undefined, params_1: GitFile | undefined) => void | Promise<void>) | undefinednavigate(_: "navigate"_, repo: Repository | undefinedrepo,
file: GitFile | undefined
file
) {
}, commit?: ((event: "commit", params_0: Repository, params_1: { message: string; description: string; }) => void | Promise<void>) | undefinedcommit(_: "commit"_, repo: Repositoryrepo,
commit: { message: string; description: string; }
commit
) {
}, }, });

Native Modules

Our workflow will be a simple one that counts how many times a user has commit via RelaGit. But that brings up a new question: how do we interact with fs and other node modules?

To run native (main process) code, we’ll have to leverage the other file in our project: native.ts.

src/native.ts
import * as module "node:fs"fs from "node:fs"; import * as const path: PlatformPathpath from "node:path"; const const __dirname: string__dirname = const path: PlatformPathpath.path.PlatformPath.dirname(path: string): string
Return the directory name of a path. Similar to the Unix dirname command.
@parampath the path to evaluate.@throws{TypeError} if `path` is not a string.
dirname
(new var URL: new (url: string | URL, base?: string | URL | undefined) => URL
The URL interface represents an object providing static methods used for creating object URLs. [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) `URL` class is a global reference for `require('url').URL` https://nodejs.org/api/url.html#the-whatwg-url-api
@sincev10.0.0
URL
(import.meta.ImportMeta.url: stringurl).URL.pathname: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)
pathname
);
export async function function incrementCommitCount(): Promise<void>incrementCommitCount() { const const file: stringfile = const path: PlatformPathpath.path.PlatformPath.join(...paths: string[]): string
Join all arguments together and normalize the resulting path.
@parampaths paths to join.@throws{TypeError} if any of the path segments is not a string.
join
(const __dirname: string__dirname, "./storage.json");
if (!module "node:fs"fs.function existsSync(path: fs.PathLike): boolean
Returns `true` if the path exists, `false` otherwise. For detailed information, see the documentation of the asynchronous version of this API: {@link exists } . `fs.exists()` is deprecated, but `fs.existsSync()` is not. The `callback`parameter to `fs.exists()` accepts parameters that are inconsistent with other Node.js callbacks. `fs.existsSync()` does not use a callback. ```js import { existsSync } from 'fs'; if (existsSync('/etc/passwd')) console.log('The path exists.'); ```
@sincev0.1.21
existsSync
(const file: stringfile)) {
module "node:fs"fs.function writeFileSync(file: fs.PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options?: fs.WriteFileOptions | undefined): void
Returns `undefined`. The `mode` option only affects the newly created file. See {@link open } for more details. For detailed information, see the documentation of the asynchronous version of this API: {@link writeFile } .
@sincev0.1.29@paramfile filename or file descriptor
writeFileSync
(const file: stringfile, var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: ((this: any, key: string, value: any) => any) | undefined, space?: string | number | undefined): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
stringify
({ count: numbercount: 0 }));
} const const data: anydata = var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.parse(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): any
Converts a JavaScript Object Notation (JSON) string into an object.
@paramtext A valid JSON string.@paramreviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is.
parse
(module "node:fs"fs.function readFileSync(path: fs.PathOrFileDescriptor, options: BufferEncoding | { encoding: BufferEncoding; flag?: string | undefined; }): string (+2 overloads)
Synchronously reads the entire contents of a file.
@parampath A path to a file. If a URL is provided, it must use the `file:` protocol. If a file descriptor is provided, the underlying file will _not_ be closed automatically.@paramoptions Either the encoding for the result, or an object that contains the encoding and an optional flag. If a flag is not provided, it defaults to `'r'`.
readFileSync
(const file: stringfile, "utf-8"));
const data: anydata.count++; module "node:fs"fs.function writeFileSync(file: fs.PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options?: fs.WriteFileOptions | undefined): void
Returns `undefined`. The `mode` option only affects the newly created file. See {@link open } for more details. For detailed information, see the documentation of the asynchronous version of this API: {@link writeFile } .
@sincev0.1.29@paramfile filename or file descriptor
writeFileSync
(const file: stringfile, var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: ((this: any, key: string, value: any) => any) | undefined, space?: string | number | undefined): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
stringify
(const data: anydata));
}

This file will be run in the main process, so we can use node modules like fs and path without any issues. Now how do we access this function from our workflow?

We’ll use the native global value. This value is automatically assigned when RelaGit runs your workflow.

src/index.ts
import { const Workflow: new (options: WorkflowOptions) => void
Construct a new workflow runner
Workflow
, const context: () => Context
When run in a workflow, will return current information about the state of the application.
context
} from "relagit:actions";
export default new new Workflow(options: WorkflowOptions): voidWorkflow({ WorkflowOptions.name: stringname: "My Workflow", WorkflowOptions.description?: string | undefineddescription: "A workflow that does something.", WorkflowOptions.hooks?: { navigate?: ((event: "navigate", params_0: Repository | undefined, params_1: GitFile | undefined) => void | Promise<void>) | undefined; ... 9 more ...; stash_pop?: ((event: "stash_pop", params_0: Repository) => void | Promise<...>) | undefined; } | undefinedhooks: { commit?: ((event: "commit", params_0: Repository, params_1: { message: string; description: string; }) => void | Promise<void>) | undefinedcommit() { const native: { incrementCommitCount: () => Promise<void>; }native?.incrementComm
  • incrementCommitCount
}, }, });

Testing our workflow

Let’s test our workflow out. We’ll start by building it with vite.

Terminal
pnpm vite build

This will create a dist folder with our workflow inside of it. But there’s one last step before we can test it out.

You’ll need to add the filepath to your project’s folder to external.json as described in the Installing Workflows section.

At last, open up RelaGit and check to see your workflow in Settings > Workflows. If it’s there, you’re good to go!