Auto is a TypeScript-powered command-line automation tool.
Write and execute global and project-local scripts.
It's kind of like zx, but:
- TypeScript-focused
- Scripts are organized in repositories (no shebangs)
- With many goodies
Use Auto to:
- Generate files and projects
- Create interactive scripts
- Create context-aware automations
External resources:
- TypeScript-powered: Write scripts with TypeScript and enjoy full type-checking support.
- Global and local scripts: Share scripts between projects or define project-specific scripts.
- Script parameters: Auto turns script parameters into interactive prompts.
- Script validation & filtering: Control the conditions for which a script is valid.
- Global utilities and helpers: Auto injects several useful packages and custom helpers as globals.
- Context awareness: Auto collects information about the project you're currently in.
- Auto-config: Prompts to create a script repo and auto-configure
tsconfig.json
. - REPL: Useful REPL to explore the execution context, available globals, and scripts.
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 byid
auto repl
- enter the REPL
This 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 calledauto
from.fileMap: Proxy<{}>
- proxied map that resolves the content of files, relative to the script file- For example, if your script is
special.ts
, and there is a directoryinterests
next to it, and there's a file namedrule.txt
inside it, you can get its content by accessingfileMap["interests/rule.txt"]
.
- For example, if your script is
files: { path, content }[]
- the deeply-listed list of files inside the directory the current script is in.- Note: the current script file will not be included in
files
- Note: the current script file will not be included in
params: Record<key,value>
- dictionary of your script's param keys and user-provided values.project: Project
- The currentProject
, 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 directory
t(text, data?: { [key]: value })
- helper string templating function that replaces__key__
with the provided value- Note: If no data is provided, it uses the
params
dictionary.
- Note: If no data is provided, it uses the
self: Script
- the current script itself, useful for introspectionand meditation
Auto injects many helpers into the global scope.
External helpers:
$
,$$
,execa
,execaSync
- process execution utilities provided by execachalk
- chalkfs
- fs-extraglob
- globlodash
- lodashwhich
- whichinquirer
- @inquirer/prompts, also aliased toprompt
Internal helpers:
sleep(milliseconds: number): Promise<void>
- shell-like helpers:
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.