Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
@haltcase/run
Advanced tools
Flexible, function-based task runner where command line options are props
@haltcase/run
· Flexible, function-based task runner where command line options are props.
Install @haltcase/run
:
# pnpm
pnpm add --save-dev @haltcase/run
# npm
npm install --save-dev @haltcase/run
# yarn
yarn add --dev @haltcase/run
Create a file in a scripts folder at the root of your project and export your tasks — they're just functions! ¹
// scripts/tasks.js
export const hello = ({ name }) => {
console.log(`Hello, ${name}!`);
};
Now you can execute these functions and pass options from the command line:
pnpm hr tasks hello --name World
# → Hello, World!
Task files are simple JavaScript or TypeScript files located (by default) in
the scripts
folder within your current working directory. Organize them
however you like.
.
└── scripts/
├── build.js
├── database-tasks.ts
└── dev.ts
To execute a task within one of these files, pass the name of the file and the name of the task. For example:
pnpm hr database-tasks seed
[!NOTE] It is allowed (although confusing) to have multiple task files with the same base name, however you must supply a file extension to make it unambiguous which file you are referring to.
✖ Found multiple task files with the name 'miscellaneous' Rename the ambiguous files or specify an extension and try again C:\dev\sources\js\skrrt\scripts\miscellaneous.js C:\dev\sources\js\skrrt\scripts\miscellaneous.ts C:\dev\sources\js\skrrt\scripts\miscellaneous.mjs
[!TIP] If you want
*.js
task files to use a different module type from your root project, add apackage.json
to your scripts folder that specifies"type": "module"
or"type": "commonjs"
.
While TypeScript or ESM are much more highly recommended, you can also write
task files in CommonJS. Use the *.cjs
file extension or create a package.json
file in your scripts folder with "type": "commonjs"
and use exports
.
// scripts/greetings.cjs
exports.hello = ({ name }) => {
console.log(`Hello, ${name}!`);
};
While tasks can be as simple as an exported function, they're capable of more.
So far, all tasks have been simple functions. However, you can get type-safety
by using the task
function from @haltcase/run
:
// scripts/hello.ts
import type { ParsedOptions } from "@haltcase/run";
import { task } from "@haltcase/run";
interface HelloProps extends ParsedOptions {
name: string;
}
export const hello = task<HelloProps>(({ name }) => {
console.log(`Hello, ${name}!`);
});
Tasks can return a Promise:
export const getUser = async ({ id }) => {
const userResponse = await fetch(
`https://fakerapi.it/api/v2/custom?_quantity=1&id=${id}&email=email&website=website`
);
const { data } = await userResponse.json();
console.log(data[0]);
};
Aside from named options of the form --option
, task functions also receive
positional or unnamed arguments as an array of strings in the _
property.
// scripts/greetings.js
export const hello = ({ name, _ }) => {
console.log(`Hello, ${name}! ${_.join(" ")}`);
};
pnpm hr greetings hello --name World Welcome to the cosmos!
# → Hello, World! Welcome to the cosmos!
Everything following a --
option terminator will be treated as positional:
pnpm hr greetings hello --name World -- --ThisIsNotAnOption
# → Hello, World! --ThisIsNotAnOption
Tasks also receive environment variables as the env
property:
// scripts/greetings.ts
export const hello = ({ env, name }) => {
if (env.GREET_LOUDLY) {
console.log(`HELLO, ${name.toUpperCase()}! LOUD GREETINGS TO YOU.`);
} else {
console.log(`Hello, ${name}. Quiet greetings to you.`);
}
};
Example using sh
syntax to set an environment variable for a command:
pnpm hr greetings hello --name World
# → Hello, World. Quiet greetings to you.
GREET_LOUDLY=true pnpm hr greetings hello --name World
# → HELLO, WORLD! LOUD GREETINGS TO YOU.
You can also use other typical methods for loading an environment, including
dotenv-cli
:
dotenv -c development -- pnpm hr greetings hello --name World
# → HELLO, WORLD! LOUD GREETINGS TO YOU.
By default, env
is a full reference to Node's process.env
and is a fairly
loose dictionary from string keys to values that are string | undefined
. If
you want to validate specific environment variables, use
task.strict
.
Each task additionally receives a second argument, giving you access to shell execution powered by Execa.
export const build = async ({ mode }, { $ }) => {
const { stdout } = await $`vite build --mode ${mode}`;
console.log(`stdout = ${stdout}`);
};
See API for full API details.
Using task.strict
, you can supply a Zod schema to move validations out of
your task's logic. If you transform the input, your task function's TypeScript
types will infer the output type.
You can configure @haltcase/run
using a haltcase.run.{extension}
file in
your current working directory.
Configuration is loaded using c12 — see the documentation there for the list of supported configuration locations, file types, and other features.
// haltcase.run.ts
import { HaltcaseRunConfig } from "@haltcase/run";
export default {
taskDirectory: "./scripts",
quiet: false
} satisfies HaltcaseRunConfig;
You can also set options in the haltcase.run
property of your package.json:
{
// ...
"haltcase.run": {
"taskDirectory": "./scripts",
"quiet": false
}
}
You will likely want to create a tsconfig.json
file in your scripts folder,
for example:
{
// if you want to extend your root config
"extends": "../tsconfig.json",
"compilerOptions": {
// if you want to allow non-TypeScript task files
"allowJs": true,
"noEmit": true
},
"include": [
// feel free to remove extensions here
"**/*.ts",
"**/*.mts",
"**/*.cts",
"**/*.js",
"**/*.mjs",
"**/*.cjs"
],
"exclude": ["node_modules"]
}
Command line option parsing is done with Node's util.parseArgs
,
but all options are treated as string
types. In other words, all options
expect values, meaning the following usage will throw an error:
pnpm hr greetings hello --name
If you want to pass a boolean value for an option, pass true
/false
and
parse it yourself or use task.strict
, for example using the
Zod schema: z.string().transform(value => value === "true")
.
task
task
is a very light wrapper around a function that provides improved type inference.
task<TOptions>(fn: Task<T>): BrandedTask<T>
Usage:
import type { ParsedOptions } from "@haltcase/run";
import { task } from "@haltcase/run";
// without providing a type parameter
export const defaultOptions = task((options) => {
console.log(options._); // ok, `_: string[]`
console.log(options.name); // ok, but `name: unknown` and no hints
});
interface CustomOptions extends ParsedOptions {
name: string;
}
// providing a type parameter
export const customOptions = task<CustomOptions>((options) => {
console.log(options.name); // ok, `name: string`
});
[!IMPORTANT] Because all command line arguments are strings, you should only use
string
as the value type for options with this method. If you want to be stricter about value types by validating or transforming them, implement the parsing yourself or usetask.strict
with a Zod schema.
Task
The type for a task function, which accepts two arguments: the parsed command
line options and a utilities
object providing tools for shell execution.
<TOptions = ParsedOptions> = (
options: TOptions,
utilities: TaskUtilities
) => unknown;
TaskUtilities
Properties:
$
– Runa command using Execa's script mode.command
– Run a command using Execa (e.g., shell command or script).exec
– Same as command
, but inherits the parent process' stdio streams by default.task.strict
Provide the shape for a Zod schema as the first argument and a task function as the second. The task function will receive the safely parsed and validated output of the Zod schema, and its types will be inferred automatically.
Note that you supply a plain object for the shape rather than a full Zod schema.
task.strict<TShape, TSchema?>(shape: TShape, fn: Task<z.infer<TSchema>>): BrandedTaskStrict<z.infer<TSchema>>
Usage:
// scripts/strict-tasks.ts
import { task } from "@haltcase/run";
import { z } from "zod";
export const printCharacter = task.strict(
{
// positional/unnamed arguments
_: z.array(z.string()),
// environment variables
env: z.object({}).passthrough(),
name: z.string(),
armorClass: z.coerce.number()
},
async ({ name, armorClass }) => {
console.log(`🎲 ${name}\n🛡️ ${armorClass}`);
}
);
This also allows you to parse, validate, and safely type the _
property
beyond an array of strings and the env
property more strictly than a simple
reference to Node's process.env
. For instance:
// scripts/extra.ts
import { task } from "@haltcase/run";
import { z } from "zod";
export const fun = task.strict(
{
_: z.array(z.string()).transform((values) => values.length),
// note: `z.object` strips unspecified keys by default
env: z.object({
SECRET_KEY: z.string().min(8)
})
},
async ({ _, env }) => {
console.log(`Positionals count (_) = ${_}`);
console.log(`SECRET_KEY = ${env.SECRET_KEY}`);
}
);
pnpm hr extra fun because there are more words
# → 5
# → undefined
SECRET_KEY=abcdefgh pnpm hr extra fun because there are more words
# → 5
# → abcdefgh
FAQs
Flexible, function-based task runner where command line options are props
We found that @haltcase/run demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.