Back to all posts

Integrating your own custom tools with Trunk Check

By Josh MarinacciDecember 12, 2023
Check

Trunk Check’s superpower is that it seamlessly leverages dozens of existing tools. Virtually every tool inside of Check is actually an external tool wrapped in a plugin. A plugin is a snippet of configuration code that lets Check automatically download, configure, and run other tools. In this tutorial I’ll walk you through creating a simple linter in Javascript, then move the tool and its config to a full GitHub project that can be versioned and auto installed. Let’s get started! 

Note: though this tutorial uses Javascript for the tool, it is applicable to any language.

In Trunk Check’s vocabulary, a plugin is just a snippet of configuration code. A plugin can include definitions of how to download a tool, how to run it with Trunk Check, and how to interpret the tool’s output into something that Trunk Check can understand. 

Trunk Check has several types of plugins, named based on what they do.  A linter is a plugin which checks some attribute of files and reports the problem. Linters (generally) do not modify files.  A formatter is a linter which does modify the files to adhere to some rule (ex: no semicolons in JS code, tabs vs. spaces, etc.).

For this tutorial we will make a tiny NodeJS script that checks if a filename contains the word ‘foo’. Since it only checks but does not modify the code, this script will be a linter.

Create a simple linter tool

First let’s create a simple script that checks if the filename passed in contains the word foo. Open a text editor and paste in this script. You’ll note that the script is getting the filename from process.argv. Inputs to the script are regular standard command line arguments.

1import process from "process"
2
3// throw errors on missing info
4if(process.argv.length < 3) throw new Error("missing filename")
5
6const filename = process.argv[2]
7if(filename.match(/foo/)) {
8    // on error, print to std and *also* exit with 1
9    console.log(`Warning. file has 'foo' in the
10     name '${filename}'`)
11    process.exit(1)
12}
13// on pass, print nothing and exit with 0
14process.exit(0)

Now let’s test this script on a file.

1node foofinder.js my_file.txt
2node foofinder.js my_foo_file.txt

We don’t need to use real filenames to test because this particular script only checks the passed in name, not the actual file on disk. The first line prints out nothing because there is no foo in the filename. The second line above does print output indicating it found a foo.

Using a Script as a Check Linter

All linter plugins must produce output in one of a few formats. The simplest is called pass_fail. A pass fail plugin either returns exit code 0 for success or 1 for failure and also prints some output about the problem to STDOUT. The input for our script above is a single filename to check. If no filename is passed in then the script throws an error. Since this is a NodeJS script, any uncaught error will automatically exit with 1 and an error message. Throwing errors on input and config failures is very important for debugging. Whenever a tool fails Trunk Check will automatically save the error output to a log file so that you can investigate why it failed. 

Note: We used the output format pass_fail for this script, but for more complex tools other formats are supported.

Now that we have our tool as a script, we can integrate it into a project as a Check plugin.  Let’s go to another repo where we have already set up Trunk Check. This means we already ran trunk init on it and it has a .trunk/ directory.

Open the trunk.yaml file for editing and put this definition of foofinder in the lint definitions section.

1lint:
2  ...  
3  definitions:
4     ...    
5   - name: foofinder
6     files: [ALL]
7     commands:
8      - name: lint
9         output: pass_fail
10         success_codes: [0, 1]
11         runtime: node
12         run: node ${workspace}/../trunk_plugin_tutorial/findfoo.js ${target}        
13      read_output_from: stdout

A plugin config defines both where the tool is and how to run it. In the definition above the config tells Trunk Check the name of the tool (foofinder), which file types it can be run on (ALL), what output format it uses (pass_fail), the exit codes it uses (which are always [0, 1]) . Finally it specifies which runtime to use (node), and the command to run it. The variable workspace represents the current workspace where we are running trunk check. The variable target represents the filename that will be passed to findfoo.js. The read_output_from section indicates that Check will get the tool's output from stdout (standard output)

Now enable the foofinder tool by typing trunk check enable foofinder on the command line in your project repo. This will modify the trunk.yaml to formally enable the plugin.

Now create an empty file in this repo that has the word foo in it. From most command lines you can run touch src/foo.ts to create an empty file.

Now run trunk check and you should see something like this:

$ trunk check

1$ trunk check
2Checking 100% [=============================>]  31/31  2.3s 
3  ISSUES  
4src/foo.ts:0:0
5 0:0  high  Warning. file has 'foo' in the name 'src/foo.ts'
6foofinder/error

Success! We have a working linter! If you had any trouble try running trunk check –verbose to get more detail about what is happening under the hood.

Move the tool to a local repo

Now that we have the above tool working we want to put the tool under version control and shart it with our team. 

Note: Trunk Check has an existing repo of plugins for users to share tools and integrations for one another. If you think your tool plugin would be good for the wider world, please feel free to contribute it there as well.

Let’s start by putting it in a local git repo on your machine and add this plugin.yaml to the root of the repo for the tool.

1version: 0.1
2lint:  
3 definitions:
4   - name: foofinder
5     files: [ALL]
6     runtime: node
7     commands:
8       - name: lint
9         output: pass_fail
10         success_codes: [0,1]
11         run: node ${plugin}/findfoo.js ${target}
12         read_output_from: stdout

This is the same tool definition as before, but it now uses the plugin variable to show where the script file is.

Back in the project repo we can add a reference to the plugin (after removing the other changes we made if you still have them) using the command below. This will connect your local plugin repo with your local project repo.

1trunk plugins add ../trunk_plugin_tutorial --id=foofinder

Now the trunk.yaml in your main project will have a reference to foofinder like this:

1plugins:  
2 sources:
3   - id: foofinder
4     local: ../trunk_plugin_tutorial

You will also need to enable the plugin in the enabled section of the yaml or with the command line: 

1trunk check enable foofinder

If all goes well, you can add a file with foo in the name and see it printed out, just like before.

Move the tool to a github repo

Now that we know the plugin is working, let’s push the plugin repo to GitHub and reference it using a URL so everyone can use it, like this:

1cd trunk_plugin_tutorial
2git push
3cd ../my_project
4trunk plugins add
5https://github.com/joshmarinacci/trunk_plugin_tutorial.git --id=foofinder

This will look for the latest release of your code. If you haven’t done a release, then you can create a release with the GitHub interface, or else reference a full commit sha or a git tag. Tags are generally the easiest. To create a tag, in your tool repo run:

1git tag v0.1.0
2git push origin v0.1.0

Back in your project repo run:

1trunk plugins add https://github.com/joshmarinacci/trunk_plugin_tutorial v0.1.0 --id=foofinder

Your project’s plugins section of the .trunk/trunk.yaml will now look like this:

1plugins: 
2 sources:
3  - id: foofinder
4   ref: v0.1.0
5   uri: https://github.com/joshmarinacci/trunk_plugin_tutorial  
6  ...

Again, make sure foofinder is enabled with:

1trunk check enable foofinder

Now when you run trunk check it will automatically download and cache foofinder from GitHub. Huzzah!

Conclusion

In this post we’ve seen how to integrate a custom script as a Linter in Trunk Check. Then how to publish the linter from a local directory or a GitHub repository. Depending on your needs you might want to keep it local or make it completely public. If you think your plugin would benefit the wider open source community please consider submitting it to Trunk’s public plugins repository.

Now that your linter script is working, you can further customize it, such as:



Try it yourself or
request a demo

Get started for free

Try it yourself or
Request a Demo

Free for first 5 users