actionify
Create TypeSafe GitHub actions
Why?
Continuous integration (CI) is an essential safety net for any sustainable open source project. GitHub Actions have become the industry standard. One downside is the yaml configuration format. .yml
files can be error-prone and make reuse more difficult.
I decided to create this project after implementing a rust build pipeline for a rust
/ napi
project which needed to be compiled across multiple architectures. The .yml
files were complex and hard to maintain. By using this tool Iβve been able to reduce the complexity massively and allow for much simpler reuse of code.
Installation
This project requires a recent installation of deno
and is tested to run on versions greater than 1.24.x
. It may work on older versions but this is not guaranteed.
There are two ways of running this project:
1. Install globally
deno install -Af --name actionify https://deno.land/x/actionify@0.3.0/cli.ts
After this is run you will be able to run actionify
from the command line.
actionify # Autogenerates all the workflows using defaults
actionify --help # Prints the help menu
actionify check # Checks whether workflows are up to date and valid
deno
2. Run with deno run -Ar https://deno.land/x/actionify@0.3.0/cli.ts
VSCode Setup
If you are using vscode
you can install the deno extension which will provide you with syntax highlighting and autocompletion.
Enabling deno
for the whole project may cause interoperability issues if you are using a default TypeScript setup. The best way to get around this is to create a .vscode/settings.json
file in the root of your project and add the following:
{
"deno.enablePaths": [".github/"]
}
This restricts deno
support to the .github
folder and will fix most of the conflicts. Depending on your TypeScript setup you may also need to exclude the .github
folder via your tsconfig.json
file.
{
"compilerOptions": {
// ...
},
"exclude": [".github/**"]
}
Usage
The following setup uses the defaults. These can be customised as documented later in this readme.
The first thing to do is create the file .github/actionify.ts
file which will contain the configuration for creating all workflow actions. The example will be taken from the GitHub quickstart guide.
import {
defineWorkflows,
e,
step,
workflow,
} from "https://deno.land/x/actionify@0.3.0/mod.ts";
// Create the configuration for the CI workflow.
const ciWorkflow = workflow({ name: "GitHub Actions Demo", fileName: "ci" })
.on("push")
// Set the job and it's ID. The ID can be used to access the job context later.
.job("Explore-GitHub-Actions", (job) => {
return job.steps(...generateSteps());
});
// Create the configuration for the CI workflows. When the CLI is run this will be
// used to create the workflows.
export default defineWorkflows({
workflows: [ciWorkflow],
});
// Create a list of steps which will be used in the job.
function generateSteps() {
return [
step().run(
`echo "π The job was automatically triggered by a ${
e.wrap(e.ctx.github.event_name)
} event.`,
),
step().run(
`echo "π§ This job is now running on a ${
e.wrap(e.ctx.runner.os)
} server hosted by GitHub!"`,
),
step().run(
`echo "π The name of your branch is ${
e.wrap(e.ctx.github.ref)
} and your repository is ${e.wrap(e.ctx.github.repository)}."`,
),
step().name("Check out the repository code").uses("actions/checkout@v3"),
step().run(
`echo "π‘ The ${
e.wrap(e.ctx.github.repository)
} repository has been cloned to the runner."`,
),
step().run(
'echo "π₯οΈ The workflow is now ready to test your code on the runner."',
),
step().name("List files in your repository").run([
`ls ${e.wrap(e.ctx.github.workspace)}`,
]),
step().run((ctx) => {
return `echo "π This job's status is ${e.wrap(ctx.job.status)}."`;
}),
];
}
Once the above example is in place, you can run the following to generate all the workflow files.
With global installation
actionify
deno run
With deno run -Ar https://deno.land/x/actionify@0.3.0/cli.ts
This creates the following file: .github/workflows/ci.yml
# This file was autogenerated with actionify@0.0.0
# To update run:
# deno run -Ar https://deno.land/x/actionify@0.3.0/cli.ts
name: GitHub Actions Demo
'on':
push: null
jobs:
Explore-GitHub-Actions:
steps:
- run: echo "π The job was automatically triggered by a ${{ github.event_name }} event.
- run: echo "π§ This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: >-
echo "π The name of your branch is ${{ github.ref }} and your repository is ${{
github.repository }}."
- name: Check out the repository code
uses: actions/checkout@v3
- run: echo "π‘ The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "π₯οΈ The workflow is now ready to test your code on the runner."
- name: List files in your repository
run: ls ${{ github.workspace }}
- run: echo "π This job's status is ${{ job.status }}."
Roadmap
Better error handling
Right now itβs possible to create invalid workflow files.
- Users can create a workflow file with no jobs and no events. Both of which are required.
- Users can create a job with no runner (which is required) and no steps.
- Users can create empty steps which also donβt fail.
Any of the above will cause the workflow to fail on GitHub.
There are two ways of adding validation.
Validation can be added during:
- workflow generation throw an error when required properties are missing.
- TypeScript: meaning that only valid workflows can be added to
defineWorkflows
, only valid jobs can be added toWorkflow.job
and only valid steps can be added toJob.steps
.
Ideally both of these would be implemented to prevent workflows from failing on GitHub.
Community feedback on TypeScript API
It will be important to get community feedback on the TypeScript API. There are a several places that things could be improved, and design choices around making types more strict or making the API more intuitive.
Remote Actions
The main reason I created this project is to have type-safe control over GitHub actions. Eventually this will also mean that remote actions can be called in a fully type safe way, without ever needing to leave your code editor.
This could be fulfilled via a deno registry deployment allowing for remote actions to be imported and used in a type-safe way.
import checkout from "https://act.deno.dev/actions/checkout@3.0.2";
import {
defineWorkflows,
e,
workflow,
} from "https://deno.land/x/actionify@0.3.0/mod.ts";
const checkoutStep = checkout((ctx) => ({
repository: e.wrap(ctx.github.repository),
ref: e.wrap(ctx.github.ref),
token: e.wrap(ctx.github.token),
lfs: true,
})).env((ctx) => ({
GITHUB_TOKEN: e.wrap(ctx.secrets.GITHUB_TOKEN),
}));
const ci = workflow({ name: "ci" })
.on("push")
.job("Explore-GitHub-Actions", (job) => job.step(checkoutStep));
export default defineWorkflows({
workflows: [ci],
});
The remote actions will be dynamically generated and cached.
Support deno scripting
A user should be able to run write a function that runs directly within the function context. The script created would use deno as the runtime.
import { step } from "https://deno.land/x/actionify@0.3.0/mod.ts";
const scriptStep = step()
// @ts-expect-error
.script(async function (...args: number[]) {
// This runs the script in the context of the step and has access to the deno runtime.
const result = await Deno.readTextFile(
new URL(import.meta.resolve("./readme.md")),
);
// do something with the result
}, [1, 2, 3]);
Contributing
To contribute first update your cache with
deno task lock
This both generates the lockfile and makes sure the same cache is used for all contributors.
To check that all you code is working as expected, run:
deno task check
This will test, lint and check that formatting is correct.
created with scaffold