Developing Custom JavaScript Actions
While the GitHub Marketplace offers thousands of pre-built actions, there are times when your workflow requires logic specific to your business needs. Custom JavaScript Actions allow you to write your own automation logic using the Node.js ecosystem, making them faster and more flexible than Docker-based actions for many scenarios.
Why Choose JavaScript Actions?
JavaScript actions run directly on the runner machine (Ubuntu, Windows, or macOS) without the overhead of building a Docker container. This leads to faster execution times. They are ideal for interacting with the GitHub API, performing calculations, or manipulating files within your repository.
The Architecture of a JavaScript Action
A standard JavaScript action consists of three main components:
- action.yml: The metadata file that defines the inputs, outputs, and entry point.
- index.js: The main logic of your action written in JavaScript.
- node_modules: The dependencies required by your script.
Workflow Diagram: Execution Flow
Understanding how GitHub executes your code is crucial for debugging:
- 1. Runner reads action.yml to identify inputs.
- 2. Runner sets environment variables based on workflow inputs.
- 3. Node.js environment is initialized.
- 4. index.js executes using the GitHub Actions Toolkit.
- 5. Outputs are captured and passed back to the workflow.
Setting Up Your First Custom Action
To start, you need a new directory with a initialized Node.js project. You will also need the @actions/core and @actions/github packages.
1. Define the Metadata (action.yml)
name: 'Hello World Custom Action'
description: 'Greet someone and record the time'
inputs:
who-to-greet:
description: 'Who to greet'
required: true
default: 'World'
outputs:
time:
description: 'The time we greeted you'
runs:
using: 'node20'
main: 'dist/index.js'
2. Write the Action Logic (index.js)
Using the toolkit makes it easy to handle inputs and outputs safely.
const core = require('@actions/core');
const github = require('@actions/github');
try {
// Get input defined in action.yml
const nameToGreet = core.getInput('who-to-greet');
console.log(`Hello ${nameToGreet}!`);
// Set output defined in action.yml
const time = (new Date()).toTimeString();
core.setOutput("time", time);
// Get the JSON webhook payload for the event that triggered the workflow
const payload = JSON.stringify(github.context.payload, undefined, 2);
console.log(`The event payload: ${payload}`);
} catch (error) {
core.setFailed(error.message);
}
Handling Dependencies with @vercel/ncc
GitHub Actions do not run npm install automatically on the runner. You have two choices: commit the node_modules folder (not recommended) or bundle your code into a single file using a tool like ncc.
Run the following command to compile your code:
npx @vercel/ncc build index.js -o dist
This creates a dist/index.js file containing all your dependencies, which is what the action.yml points to.
Real-World Use Cases
- Custom Jira Integration: Automatically transition Jira tickets based on GitHub PR comments.
- Asset Optimization: Automatically compress images or minify CSS/JS files before they are deployed.
- Compliance Checks: Verify that every Pull Request follows specific internal naming conventions or contains required documentation files.
Common Mistakes to Avoid
- Forgetting to Rebuild: Changing
index.jsbut forgetting to runncc build. The runner will execute the old code indist/. - Hardcoding Values: Using hardcoded strings instead of inputs. Use
core.getInput()to keep actions reusable. - Missing Error Handling: Not using
core.setFailed(). If your script throws an error but you don't catch it, the workflow step might appear as "Success" even if it failed. - Large Dependencies: Including heavy libraries that slow down the action execution. Keep your custom actions lean.
Interview Notes for Developers
If you are interviewed for a DevOps or Senior Developer role, be prepared for these questions:
- Q: Difference between Docker and JavaScript actions? A: JS actions run faster and support all runner OS types natively, while Docker actions are more portable but slower and Linux-only.
- Q: How do you secure secrets in custom actions? A: Never log secrets to the console. Use
core.setSecret()to mask values in the logs. - Q: How do you version a custom action? A: Use Git tags (e.g., v1, v1.1.0). Users can then reference your action using
uses: user/repo@v1.
Internal Links for Further Learning
- Learn about Introduction to GitHub Actions to understand the basics.
- Explore Understanding Workflow Syntax to see how to implement your new action.
- Check out Managing Secrets in GitHub Actions to keep your custom actions secure.
Summary
Developing custom JavaScript actions is a powerful way to extend GitHub's capabilities. By using the @actions/core toolkit and bundling your code with ncc, you can create high-performance, reusable automation tools. Remember to define your inputs and outputs clearly in action.yml and always handle potential errors to ensure your CI/CD pipelines remain robust.