Autocompleting Yarn Commands in zsh

Usually I use jq to discover what scripts a node project uses like this:

jq '.scripts' package.json

This is fine, and I can always find it be reverse-searching my shell history using C-r .scr but there's a more automated way to handle this. g-plane wrote a Rust package to handle autocompletions for Yarn in zsh... and it works wonderfully.

zsh yarn autocomplete example

tldr; https://github.com/g-plane/zsh-yarn-autocompletions

The run, remove, and add commands are auto-completable (and yarn add is even configurable!).

The fact that add is configurable is really nice because it means I can set it up with my favorite and most-used packages like [gatsby-mdx(https://github.com/ChristopherBiscardi/gatsby-mdx) and gatsby-plugin-emotion

Rust

We can see how it works by looking at src/scripts.rs. The code does what you might expect, finds a package.json, opens it, pulls the scripts key out (using serde), and returns it.

use std::env;
use std::fs::File;
use std::collections::HashMap;
extern crate serde;
extern crate serde_json;
#[derive(Deserialize)]
struct Pkg {
scripts: Option<HashMap<String, String>>,
}
pub fn fetch_npm_scripts() -> String {
let mut path = String::new();
match env::var("PWD") {
Ok(pwd) => {
path.push_str(&pwd);
path.push_str("/package.json");
}
Err(_) => {
return String::new();
}
}
let file = File::open(path);
if let Err(_) = file {
return String::new();
}
let package: Result<Pkg, _> = serde_json::from_reader(file.unwrap());
if let Err(_) = package {
return String::new();
}
let package = package.unwrap();
match package.scripts {
Some(scripts) => scripts
.keys()
.map(|script| script.to_string())
.collect::<Vec<String>>()
.join("
"),
None => String::new(),
}
}
#[test]
fn test_fetch_scripts() {
let output = fetch_npm_scripts();
let output = output.trim();
let mut scripts: Vec<&str> = output.split('
').collect();
scripts.sort();
assert_eq!(scripts, ["build", "commit", "dev", "lint"]);
}