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.
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 CLIpnpm create relagit
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.
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.
Terminalpnpm 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.tsimport {
const defineConfig: (options?: RelaGitViteOptions) => UserConfig
defineConfig } from "@relagit/vite"; export defaultfunction defineConfig(options?: Partial<{ entrypoints: { plugin: string; native?: string | undefined; }; external: string[]; }> | undefined): UserConfig
defineConfig();
Now, time to get onto the workflow itself.
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.tsimport {
const Workflow: new (options: WorkflowOptions) => void
Construct a new workflow runnerWorkflow,const context: () => Context
When run in a workflow, will return current information about the state of the application.context } from "relagit:actions"; export default newnew Workflow(options: WorkflowOptions): void
Workflow({});
We can now define some basic metadata that will identify our workflow.
src/index.tsexport default new
new Workflow(options: WorkflowOptions): void
Workflow({WorkflowOptions.name: string
name: "My Workflow",WorkflowOptions.description?: string | undefined
description: "A workflow that does something.", });
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.tsexport default new
new Workflow(options: WorkflowOptions): void
Workflow({WorkflowOptions.name: string
name: "My Workflow",WorkflowOptions.description?: string | undefined
description: "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; } | undefined
hooks: {navigate?: ((event: "navigate", params_0: Repository | undefined, params_1: GitFile | undefined) => void | Promise<void>) | undefined
navigate(_: "navigate"
_,repo: Repository | undefined
repo,file: GitFile | undefined
file) { },commit?: ((event: "commit", params_0: Repository, params_1: { message: string; description: string; }) => void | Promise<void>) | undefined
commit(_: "commit"
_,repo: Repository
repo,commit: { message: string; description: string; }
commit) { }, }, });
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.tsimport * as
module "node:fs"
fs from "node:fs"; import * asconst path: PlatformPath
path from "node:path"; constconst __dirname: string
__dirname =const path: PlatformPath
path.path.PlatformPath.dirname(path: string): string
Return the directory name of a path. Similar to the Unix dirname command.dirname(newvar 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-apiURL(import.meta.ImportMeta.url: string
url).URL.pathname: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)pathname); export async functionfunction incrementCommitCount(): Promise<void>
incrementCommitCount() { constconst file: string
file =const path: PlatformPath
path.path.PlatformPath.join(...paths: string[]): string
Join all arguments together and normalize the resulting path.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.'); ```existsSync(const file: string
file)) {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 } .writeFileSync(const file: string
file,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.stringify({count: number
count: 0 })); } constconst data: any
data =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.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.readFileSync(const file: string
file, "utf-8"));const data: any
data.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 } .writeFileSync(const file: string
file,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.stringify(const data: any
data)); }
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.tsimport {
const Workflow: new (options: WorkflowOptions) => void
Construct a new workflow runnerWorkflow,const context: () => Context
When run in a workflow, will return current information about the state of the application.context } from "relagit:actions"; export default newnew Workflow(options: WorkflowOptions): void
Workflow({WorkflowOptions.name: string
name: "My Workflow",WorkflowOptions.description?: string | undefined
description: "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; } | undefined
hooks: {commit?: ((event: "commit", params_0: Repository, params_1: { message: string; description: string; }) => void | Promise<void>) | undefined
commit() {const native: { incrementCommitCount: () => Promise<void>; }
native?.incrementComm}, }, });
- incrementCommitCount
Let’s test our workflow out. We’ll start by building it with vite.
Terminalpnpm 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
. If it’s there, you’re good to go!