Auto is a TypeScript-powered command-line automation tool.
MIT License
Auto is a TypeScript-powered command-line automation tool. Write and execute global and project-local scripts.
It's kind of like zx, but:
Use Auto to:
External resources:
tsconfig.json
.Auto is published on NPM as @andrei.fyi/auto. If you want to get the standalone experience, with global script support, you should install it globally. If you only want to use it inside a project, you should install it locally.
# install Auto globally
npm install -g @andrei.fyi/auto # or pnpm, yarn, or your favourite tool
# install Auto locally (and add a "auto run [script-id]" script in package.json)
npm install -D @andrei.fyi/auto
You can interact with Auto's CLI by running auto
in your terminal:
auto ls
- list the available scripts for the current contextauto run <script-id>
- run a script by id
auto repl
- enter the REPLThis is a quick guide of how to setup Auto in your project:
Install Auto as a development dependency.
npm install -D @andrei.fyi/auto
Create an auto
or .auto
directory inside the project root.
Run auto ls
and allow Auto to auto-configure ./auto/tsconfig.json
.
Create a script file ./auto/hello.ts
import "auto";
export default auto({
id: "hello",
title: "Say Hello",
run() {
console.log("Hello from an Auto script!");
},
});
Run auto ls
inside your project and notice how your script is listed.
Run auto run hello
to execute the script.
When Auto runs, it first tries to find your scripts.
First it looks for a global script repository, a directory located at:
~/.config/auto
on Linux~/Library/Preferences/auto
on macOS%APPDATA%\auto\Config
on Windows (why are you doing this to yourself?)
Second, it tries to resolve the root of the project you may be in, based on your cwd
.
To do this, it scans up the file tree until it finds a project marker pattern, or reaches the root.
If there's no match, the current directory is considered the project root.
Currently the patterns it looks for are:
[package.json
, go.mod
, Makefile
, .git
]
Third, it looks for a local script repository, which is an auto
or .auto
directory, in the project root.
Fourth it loads all Auto scripts from both your global and local repositories.
If there is a local script that has the same id
as a global script it overrides it.
If there are two scripts that share the same id
, and the same locality, it panics.
Check out the examples library for reference.
Auto scripts are TypeScript modules that have their default
export the result of an auto(...)
call.
The auto(...)
function is injected at runtime by import "auto"
, which also imports the global types.
Both the import "auto"
and export default auto(...)
are required for scripts to work.
This is the skeleton of an Auto script:
import "auto";
export default auto({
id: "my-script",
title: "My Script",
params: {
myParam: {
title: "Component Name",
type: "string", // "string" | "number" | "boolean"
required: true,
// defaultValue: ({ project, params }) => string|undefined
},
},
// isValid: (project) => project.hasDependency("something"),
run: async ({ cwd, project, params, files, self, t, files, fileMap }) => {
// ^ contextual variables and helpers
// instructions
},
});
To get a better understanding how it all ties together, you can look at the type of a script:
export type Script<P extends Record<string, ParamType>> = {
id: string;
title?: string;
params?: Params<P>;
isValid?: (project: Project) => boolean;
run: (args: {
cwd: string;
fileMap: Record<string, string>;
files: { path: string; content: string }[];
params: { [K in keyof P]: ParamValueType<P[K]> };
project: Project;
self: Script<P>;
t: (text: string, params?: Record<string, string | number | boolean>) => string;
}) => void;
};
Your script's run()
function is called with a dictionary that contains the following contextual variables and helpers:
cwd: string
- The directory you called auto
from.fileMap: Proxy<{}>
- proxied map that resolves the content of files, relative to the script file
special.ts
, and there is a directory interests
next to it, and there's a file named rule.txt
inside it, you can get its content by accessing fileMap["interests/rule.txt"]
.files: { path, content }[]
- the deeply-listed list of files inside the directory the current script is in.
files
params: Record<key,value>
- dictionary of your script's param keys and user-provided values.project: Project
- The current Project
, a small abstraction that provides some helpers:
Project.isGoProject
- true
in a Go projectProject.isTypeScriptProject
- true
in a TypeScript projectProject.isJavaScriptProject
- true
in a JavaScript projectProject.isNodeProject
- true
in a Node.js project (shallow, checks for @types/node
)Project.dependencies
- { name, version }[]
of dependencies for Go and JS projectsProject.hasDependency(name, version?)
- checks if the project has a dependencyProject.hasAnyDependency(names)
- checks if the project has any of a list of dependencies (by name)Project.resolvePath(...paths)
- resolve a path relative to the project rootProject.hasPath(...paths)
- resolve and check if a project-relative path existsProject.hasFile(...paths)
- resolve and check if a project-relative path is a fileProject.readFile(path)
- read a project-relative fileProject.readJSON(path)
- read a project-relative JSON fileProject.writeFile(path, content)
- write a project-relative fileProject.hasDirectory(...paths)
- resolve and check if a project-relative path is a directoryProject.createDirectory(path)
- create a project-relative directoryt(text, data?: { [key]: value })
- helper string templating function that replaces __key__
with the provided value
params
dictionary.self: Script
- the current script itself, useful for introspection Auto injects many helpers into the global scope.
External helpers:
$
, $$
, execa
, execaSync
- process execution utilities provided by execa
chalk
- chalk
fs
- fs-extra
glob
- glob
lodash
- lodash
which
- which
inquirer
- @inquirer/prompts, also aliased to prompt
Internal helpers:
sleep(milliseconds: number): Promise<void>
cwd()
, cd()
, pwd
Contributions to the Auto project are welcome! Whether it's a bug report, a new feature, or feedback on the project, I'd love to hear from you.