@worktif/unix
v0.1.42
Published
A TypeScript-based Unix shell automation library for command-line operations, scripting, and terminal management with comprehensive CLI integration
Maintainers
Readme
@worktif/unix
TypeScript-first Unix automation and CLI orchestration toolkit for Node.js. It provides a strongly-typed process runner, composable pipelines, environment validation, and a modular CLI framework suitable for production automation in enterprise systems.
- Minimal runtime, tree-shakeable
- Predictable process lifecycles with typed states
- Structured logging, DI-friendly architecture
- Works well in CI/CD, containers, and developer machines
Overview
@worktif/unix is a low-level but ergonomic utility designed to standardize and harden shell/process automation in
TypeScript projects. It abstracts common pitfalls of child processes (timeouts, signals, exit handling, environment
composition), provides typed state machines for multi-step jobs, and exposes a small but powerful API for building CLI
modules.
Unix package exists to:
- Eliminate ad-hoc shell scripts and brittle process chains
- Provide typed, reusable orchestration primitives
- Unify logging/observability for automation flows
- Enable modular CLIs with predictable argument handling
Typical targets include: build systems, asset pipelines, deployment hooks, code generation workflows, and platform automation tasks in financial, cloud, and distributed systems.
Installation
npm install @worktif/unix
# or
yarn add @worktif/unix
# or
pnpm add @worktif/unixKey Features
- Type-safe process orchestration
- Run multi-step jobs with explicit states, signal handling, and structured messaging
- Pluggable handlers for success/failure transitions
- Composable CLI modules
- Define commands/flags declaratively; consistent parsing patterns
- Extend via class inheritance with minimal boilerplate
- Production-grade logging
- Structured logger with consistent formatting for local and CI
- Log level controls (including debug) and stage-aware behavior
- Environment validation
- zod-based schemas for environment typing/validation
- DI-ready bundle
- Container integration for controlled lifecycle and extensibility
- Build tool compatibility
- Works with esbuild, tsc, webpack pipelines
- Minimal footprint
- No heavy runtime dependencies; designed for tree shaking
Requirements:
- Node.js >= 20
- TypeScript 5.8.x – recommended for types and DX
Usage
Below examples demonstrate typical patterns. Adjust to your environment.
Example 1: Run a single command with a custom handler
import { UnixModule } from '@worktif/unix';
type State = { workdir?: string };
const runner = new UnixModule<State>({
argv: process.argv,
unixInstance: {}, // your stateful instance if needed
defaults: {
handler: async (code, signal, state) => {
// on each step close: merge and return state
return state?.common ?? {};
},
logs: { type: 'local', serviceName: 'example:runner' },
},
});
await runner.run<State>(
[
{
definition: () => ({
command: 'node',
args: ['-v'],
options: { stdio: 'inherit' },
}),
signal: () => ({
message: { succeeded: 'Node printed version.', failed: 'Node failed.' },
handler: async () => ({ workdir: process.cwd() }),
}),
},
],
{
state: { common: {}, steps: {} },
handler: async (_code, _signal, state) => state?.common ?? {},
},
);Example 2: Multi-step pipeline with environment overrides
import { UnixModule } from '@worktif/unix';
type BuildState = { buildDir?: string };
const build = new UnixModule<BuildState>({
argv: process.argv,
unixInstance: {},
defaults: {
handler: async (_c, _s, state) => state?.common ?? {},
logs: { type: 'local', serviceName: 'example:build' },
},
});
await build.run<BuildState>(
[
{
definition: (state) => ({
command: 'mkdir',
args: ['-p', state.common.buildDir!],
options: { stdio: 'inherit' },
}),
signal: () => ({
message: { succeeded: 'Build dir created.', failed: 'Build dir create failed.' },
handler: async () => ({}),
}),
},
{
definition: (state) => ({
command: 'bash',
args: ['-lc', `echo "BUILDING" > ${state.common.buildDir}/artifact.txt`],
options: {
stdio: 'inherit',
env: { ...process.env, NODE_ENV: 'production' },
},
}),
signal: () => ({
message: { succeeded: 'Artifact generated.', failed: 'Artifact failed.' },
handler: async () => ({}),
}),
},
],
{
state: { common: { buildDir: './out/dist' }, steps: {} },
},
);Example 3: Define CLI flags and use structured parsing
import { UnixModule } from '@worktif/unix';
import type { UnixModuleCommandArgv } from '@worktif/unix';
type DeployState = { bucket?: string };
class DeployModule extends UnixModule<DeployState> {
constructor(argv: string[]) {
super({
argv,
unixInstance: {},
defaults: {
handler: async (_c, _s, state) => state?.common ?? {},
logs: { type: 'local', serviceName: 'example:deploy' },
},
cmdArgv: {
deploy: {
text: 'deploy', simple: true, argv: {
bucket: { text: 'bucket', double: true },
}
},
} as unknown as UnixModuleCommandArgv<any>,
});
this.setup({
argv,
unixInstance: this['unixInstance'],
defaults: this['defaults'],
cmdArgv: this['cmdArgv'],
});
}
public async deploy() {
await this.run<DeployState>(
[
{
definition: (state) => ({
command: 'echo',
args: [`Sync to s3://${state.common.bucket}`],
options: { stdio: 'inherit' },
}),
signal: () => ({
message: { succeeded: 'Synced.', failed: 'Sync failed.' },
handler: async () => ({}),
}),
},
],
{
state: { common: {}, steps: {} },
handler: async (_c, _s, state) => state?.common ?? {},
},
);
}
}
// Usage: node app.js deploy --bucket my-bucket
new DeployModule(process.argv).watch({
defaults: { handler: async (_c, _s, state) => state?.common ?? {}, logs: { type: 'local' } },
});API Reference
Type signatures are simplified for readability; refer to generated typings for exact definitions.
UnixModule<State, Defaults>constructor(options: UnixModuleOptions<State, Defaults>)run(processes, initState): Promise<UnixProcessState<State>>processes: Array<{ definition(state) => UnixChildProcessFnOptions; signal(state) => UnixChildProcessOptions<State>; exit?: boolean }>initState: { state: { common: T; steps: Record<string, T> }, handler?: UnixChildProcessHandler<State> }
runCommand(process, state, index): Promise<UnixProcessState<State>>watch(options): voidsetSuppliers({ logs }): void
Types
UnixChildProcessFnOptionscommand: stringargs?: (string | undefined)[]options?: SpawnOptionsdescription?: string
UnixChildProcessHandler<State> = (code, signal, state?) => State | Promise<State>UnixChildProcessOptions<State>handler: UnixChildProcessHandler<State>message?: { succeeded?: string; failed?: string }
UnixProcessState<State>common: State & Record<string, any>steps: Record<string, State & Record<string, any>>
UnixModuleOptions<State, Defaults>argv: string[]unixInstance: Statedefaults: { handler: UnixChildProcessHandler<State>; logs: { type: keyof LoggerCliFormatter; serviceName?: string } } & Partial<Defaults>cmdArgv?: UnixModuleCommandArgv<any>
UnixModuleCommandArgv<T>Record<string, { text: string; simple?: boolean; double?: boolean; argv?: UnixModuleCommandArgv<any> }>
Environment
EnvConfigUnix- Validates env via zod schema; extend to enforce required variables
Notes:
- Logging uses a structured logger with stage-aware formatting.
- Signals are mapped to human-readable messages; failures can terminate based on step configuration.
Use Cases
- CI/CD pipelines
- Deterministic, typed build and deployment steps with robust logging
- Secure batch jobs
- Run multi-step data processing with explicit failure handlers
- Internal developer tooling
- Replace shell scripts with maintainable TypeScript modules
- Cloud build/deploy
- Orchestrate compilers, bundlers, and cloud CLIs consistently
- Monorepo automation
- Standardized hooks to compile, verify, and publish packages
Design Principles
- Functional composability
- Steps are pure definitions that return command + signals; handlers return next state
- Explicit state
- State travels through steps; no hidden globals
- Fail-fast with clear semantics
- Each step declares success/failure messages and termination behavior
- Low latency and minimal overhead
- Uses Node child_process directly; avoids heavy abstractions
- Type safety and IDE-first
- Strong typing across public APIs; rich IntelliSense
- Environment hygiene
- Explicit env passing; easy to validate/lock via zod
Installation and Tooling
- Build: esbuild + TypeScript 5.8.x declarations
- Docs: TypeDoc (Markdown + HTML)
- Node.js compatibility: 20+
- Package manager: npm (works with yarn/pnpm)
Commands (common project scripts):
- Build:
npm run build - Generate docs:
npm run docs - Types only:
npm run types - Publish:
npm run publish:npm
Best Practices
- Always define messages and handlers for each step to keep logs actionable
- Keep env minimal and explicit per step
- Prefer double-dash flags for machine execution; single for human ergonomics
- Use
watch()when exposing CLI methods; verify method existence at startup - Validate environment in process bootstrap via EnvConfigUnix
Performance
- No runtime parsing frameworks; thin abstractions over spawn()
- Suitable for high-throughput CI jobs and containerized environments
- Minimal logging overhead; configurable log level for debug traces
Contributing
This section is intended for external publishers responsible for releasing the package to npm. Follow the sequence precisely to ensure auditability, semantic versioning integrity, and a clean release trail.
- Authenticate to the scoped registry
npm login --scope=@worktif- If you encounter a TLS/registry error, set the registry explicitly:
npm config set registry https://registry.npmjs.org/
- Complete your enhancement
- Implement and locally validate your changes (types, build, docs as applicable).
- Open a Pull Request (PR)
- Submit your changes for review.
- Await approval before proceeding.
- Merge the PR
- After approval, merge into main using your standard merge policy.
- Synchronize your local main
git checkout maingit pullto ensure you’re up to date.
- Prepare a release branch
- Create a branch using the release template:
releases/v[your.semantic.version-[pre+[meta]]]-next-release-description
- Create a branch using the release template:
- Bump the version
- Update the package version according to SemVer (major/minor/patch).
- Commit the version bump to the release branch
- Commit only the version change (and any generated artifacts if required by your policy).
- Push the release branch
- Push the branch to the remote to trigger any CI gates.
- Open a Release PR
- Create a PR from the release branch to main.
- Await approval and required checks.
- Merge the Release PR
- Merge into main after approvals and passing checks.
- Final synchronization
- Pull the latest changes from main locally.
- Validate the version in package.json
- Ensure the version reflects the intended release.
- Publish
- If the version was not increased (npm will reject):
- Bump the version, commit, and then run yarn run publish:npm.
- If the version has been increased and publishing fails unexpectedly:
- Contact the maintainer at [email protected] with context (command output, Node/npm versions, CI logs).
- If the version was not increased (npm will reject):
Successful publish output resembles:
+ @worktif/unix@[your.semantic.version-[pre+[meta]]]
✨ Done in 28.81s.Security:
- Do not commit secrets; rely on environment variables or secret managers
- Report vulnerabilities privately via the issue tracker’s security channel
License
This project is licensed under the Elastic License 2.0.
- See
LICENSEfor the full license text. - See
THIRD_PARTY_LICENSES.txtfor third-party attributions and license texts.
Maintainers / Contact
- Maintainer: Raman Marozau, [email protected]
- Organization: Work Target Insight Function
- Issues: https://bitbucket.org/worktif/npm-unix/issues
