Summary
Version 4 focuses on full type-safety for $-based fluent shell execution and modularization of its fluent interface.
createShell() will now provide $ with output-mode-dependent handles while keeping result behavior unchanged.
The FluentPromise abstraction will be split into its own file (and prepared for future npm release).
Motivation
-
Simplify usage — createShell() instantly returns a fluent interface without .asFluent().
-
Strengthen TypeScript inference:
live → handle exposes only .result()
capture / all → full PromiseLike<string> chain with .toLines(), .parse(), .safeParse().
-
Keep compatibility with existing ExecutionResult design (stdout/stderr remain null in live).
-
Decouple the fluent interface into an independent reusable module (FluentPromise).
Key Changes
-
createShell()
- Returns a
Shell instance with a $ property (fluent executor) by default.
.run() / .safeRun() remain functional but are marked @deprecated.
-
DollarFunction (Fluent entrypoint)
-
Type System Enhancements
-
Conditional HandleForMode<M>:
type HandleForMode<M extends OutputMode> =
CaptureForMode<M> extends true
? (BaseHandle<M> & FluentPromise)
: BaseHandle<M>;
-
Keeps original ExecutionResult, StrictResult, and SafeResult untouched.
-
FluentPromise Extraction
-
New file: src/fluent/FluentPromise.ts
-
Defines the reusable fluent helper surface:
export type FluentPromise = PromiseLike<string> & {
toLines(): Promise<string[]>;
parse<T extends StandardSchemaV1>(schema: T): Promise<InferOutput<T>>;
safeParse<T extends StandardSchemaV1>(schema: T): Promise<ValidationResult<InferOutput<T>>>;
};
-
Shell imports this for its fluent handles.
-
Future npm target: @thaitype/fluent-promise
-
Runtime Behavior (unchanged)
live uses inherit I/O; returns null outputs.
capture / all buffer as before.
safeRun() and .result() never throw.
File Structure
src/
fluent/
FluentPromise.ts
index.ts
shell/
Shell.ts
standard-schema.ts
index.ts
Examples
const shell = createShell({ outputMode: 'capture' });
const $ = shell.$;
// capture / all modes
const msg = await $`echo hello`; // "hello"
const cfg = await $('cat package.json', { outputMode: 'all' }).parse(ConfigSchema);
// live mode
const liveShell = createShell({ outputMode: 'live' });
const $live = liveShell.$;
const res = await $live`npm run dev`.result(); // OK, no toLines()
Migration Notes
- Existing
.run() / .safeRun() continue working but show @deprecated hint.
- No runtime changes: scripts using
capture / all remain identical.
FluentPromise can be imported directly for independent reuse.
Future Plans
- Publish
@thaitype/fluent-promise package.
- Add optional stream hooks (
tap()) or stdin piping utilities.
- Evaluate typed pipeline chaining for non-shell async tasks.
Summary
Version 4 focuses on full type-safety for
$-based fluent shell execution and modularization of its fluent interface.createShell()will now provide$with output-mode-dependent handles while keeping result behavior unchanged.The
FluentPromiseabstraction will be split into its own file (and prepared for future npm release).Motivation
Simplify usage —
createShell()instantly returns a fluent interface without.asFluent().Strengthen TypeScript inference:
live→ handle exposes only.result()capture/all→ fullPromiseLike<string>chain with.toLines(),.parse(),.safeParse().Keep compatibility with existing
ExecutionResultdesign (stdout/stderrremainnullinlive).Decouple the fluent interface into an independent reusable module (
FluentPromise).Key Changes
createShell()Shellinstance with a$property (fluent executor) by default..run()/.safeRun()remain functional but are marked@deprecated.DollarFunction(Fluent entrypoint)Tagged template and function-call overloads return mode-specific handle types.
Example:
Type System Enhancements
Conditional
HandleForMode<M>:Keeps original
ExecutionResult,StrictResult, andSafeResultuntouched.FluentPromiseExtractionNew file:
src/fluent/FluentPromise.tsDefines the reusable fluent helper surface:
Shell imports this for its fluent handles.
Future npm target:
@thaitype/fluent-promiseRuntime Behavior (unchanged)
liveusesinheritI/O; returnsnulloutputs.capture/allbuffer as before.safeRun()and.result()never throw.File Structure
Examples
Migration Notes
.run()/.safeRun()continue working but show@deprecatedhint.capture/allremain identical.FluentPromisecan be imported directly for independent reuse.Future Plans
@thaitype/fluent-promisepackage.tap()) or stdin piping utilities.