Primitives Every Plugin Inherits
The CLI framework owns the dispatching; the kernel owns the shapes the dispatcher dispatches against. @frenchexdev/ddd-cli-core is the package that every CLI plugin imports — the abstract task base class, the plugin interface, the sorting and lookup primitives. The kernel is small, pure, and unchanged across the corpus's CLI evolution because the shapes it reifies are the ones that have been stable since Symfony 1.4 in 2007.
What ddd-cli-core Reifies
Three exports cover the kernel's responsibility. DddCliTask is the abstract base every task extends. It owns the namespace, name, description triple; the configure() hook subclasses use to declare arguments and options; the execute(args, options) abstract method that does the work. DddCliPlugin is the interface every plugin satisfies: an id, a tasks: readonly DddCliTask[], an optional priority. sortPlugins and findTask are the pure functions the dispatcher uses to resolve a task by its fullName.
The package is deliberately pure. No filesystem reads, no module imports beyond TypeScript types, no side effects. The plugins themselves construct their task lists at import time; the dispatcher consumes the resulting arrays. Discovery — find every @frenchexdev/ddd-cli-plugin-* in the workspace — is the responsibility of a future loader layer that has not yet shipped; the kernel's purity means that loader can be tested in isolation from the rest of the corpus.
The Task Shape
task.ts declares the DddCliTask abstract class. The Symfony 1.4 lineage is explicit in the file's comment — Direct transposition of sfBaseTask. The pair configure() (declare arguments and options) + execute(args, options) (do the work) has been the canonical shape for CLI tasks for two decades, and the kernel keeps it because it works.
export abstract class DddCliTask {
abstract readonly namespace: string;
abstract readonly name: string;
abstract readonly description: string;
protected argument(spec: CliArgumentSpec): this { /* ... */ }
protected option(spec: CliOptionSpec): this { /* ... */ }
configure(): void { /* override */ }
abstract execute(args: ReadonlyArray<string>, options: Readonly<Record<string, string | boolean>>): Promise<number>;
get fullName(): string { return `${this.namespace}:${this.name}`; }
}export abstract class DddCliTask {
abstract readonly namespace: string;
abstract readonly name: string;
abstract readonly description: string;
protected argument(spec: CliArgumentSpec): this { /* ... */ }
protected option(spec: CliOptionSpec): this { /* ... */ }
configure(): void { /* override */ }
abstract execute(args: ReadonlyArray<string>, options: Readonly<Record<string, string | boolean>>): Promise<number>;
get fullName(): string { return `${this.namespace}:${this.name}`; }
}The fullName getter computes the addressing key. Subclasses override configure() to declare argument and option shapes; they implement execute to do the work; they return an exit code (zero for success, non-zero for failure). The pattern is small, predictable, and survives.
The Plugin Shape
plugin.ts declares the plugin interface and the two utility functions. sortPlugins orders by priority descending then id lexically — higher-priority plugins win on duplicate task names, which lets a workspace override a built-in plugin's task by shipping a higher-priority plugin with the same task name. findTask walks plugins in that order and returns the first match.
The two functions are pure. They take input, return output, no I/O, no side effects, fully unit-testable. The discipline pays off when integration tests need to verify dispatch ordering: provide a list of plugins, assert the lookup result, no mocking required.
Cross-Links
- Consumed by every CLI Plugin — the plugin's tasks extend
DddCliTask. - Used by the CLI Framework's dispatcher to resolve tasks against the plugin list.
- Predates the M3+ patterns — like Config Module, the CLI kernel is the author's longest-running pattern, transposed across three or four eras of frameworks.
Back to the series index.