cli API
Everything the package exports, by the path you import it from. Open an export for its signature and options.
@evolve-framework/cli
The evolve binary, the Subcommand base class and defineCommands for packages that ship commands, and the loader for running the CLI programmatically.
Classes
SubcommandBase class for an evolve subcommand.
import { Subcommand } from "@evolve-framework/cli";import { Subcommand, type RunContext } from "@evolve-framework/cli";
export class Eject extends Subcommand {
static command = "eject";
static description = "Eject built-in blocks into your project";
static args = {
all: { type: "boolean", description: "Eject every block" },
dest: { type: "string", default: "src/blocks" },
};
async run(ctx: RunContext): Promise<void> {
const blocks = ctx.args._ as string[]; // variadic positionals
// ...read from ctx.packageRoot, write to ctx.cwd
}
}
The statics are declared here purely for typing (declare, so they emit no
runtime code); every concrete subcommand provides its own command and,
optionally, description / args.
run is optional because a command that only holds
Subcommand.subcommands has no work of its own. Declaring neither is
refused when the command tree is built, naming the package it came from --
a command that can be dispatched to nothing is a mistake worth hearing about
before somebody types it.
new Subcommand()| Method | Description |
|---|---|
run | The work this command does. |
Functions
buildCommandMapIndex a list of SubcommandClasses by their command name, rejecting classes with no name and duplicate names.
import { buildCommandMap } from "@evolve-framework/cli";function buildCommandMap( source: string, classes: SubcommandClass[],): Map<string, CommandLoader>Returns Map<string, CommandLoader>
createEvolveCommandBuild the citty root command from one or more loaded sources: one subcommand per declared SubcommandClass.
import { createEvolveCommand } from "@evolve-framework/cli";Several sources can contribute at once — the CLI's built-ins, the project's
own commands and those of every package it depends on — and each keeps its
own packageRoot, so a command still reads assets from the package that
declared it.
function createEvolveCommand( loaded: LoadedPackage | LoadedPackage[], cwd: string,): CommandDefReturns CommandDef
defineCommandsIdentity helper a package uses to declare the commands it provides, with the right type.
import { defineCommands } from "@evolve-framework/cli";export default defineCommands(Eject, TfExport);
function defineCommands(...commands: SubcommandClass[]): SubcommandClass[]Returns SubcommandClass[]
discoverDependencyCommandsCollect the commands contributed by the packages the project in cwd depends on, so evolve messages build works without naming the package the command came from.
import { discoverDependencyCommands } from "@evolve-framework/cli";Only the project's declared dependencies are considered, never a walk of
node_modules. A transitive dependency that happens to declare
evolve.commands has not been adopted by the project, and inheriting its
commands would make the CLI's surface depend on a package nobody chose. It
also keeps the scan proportional to the manifest rather than to the tree.
A dependency that is absent from node_modules or whose manifest cannot be
read is skipped: a pruned optional dependency is not this CLI's problem, and
failing here would take down every unrelated command with it. A dependency
that does declare commands is another matter — a broken declaration is a bug
worth surfacing, so it throws.
function discoverDependencyCommands(cwd: string): Promise<LoadedPackage[]>Returns Promise<LoadedPackage[]>
discoverWorkspaceCommandsThe commands every workspace of the monorepo rooted at cwd contributes, each bound to the workspace it belongs to.
import { discoverWorkspaceCommands } from "@evolve-framework/cli";Without this a monorepo root offers nothing: the services are where the engine packages are installed, and the root depends on none of them. Every command was reachable only from the directory that had it, so knowing a command existed meant knowing which service to stand in first.
A workspace contributes what it declares itself and what its dependencies declare, which is exactly what standing in it would have offered.
function discoverWorkspaceCommands(cwd: string): Promise<LoadedPackage[]>Returns Promise<LoadedPackage[]>
extractPackagePull the source-selecting flags out of the raw argument list, returning them and the remaining args to hand to citty for subcommand dispatch.
import { extractPackage } from "@evolve-framework/cli";--package <name> (or -p) names an installed package, and --filter <name> (or -F) a workspace of the monorepo this was invoked in. They
answer different questions -- which of two dependencies, and which of two
services -- so a repository where one command name appears twice for each
reason can say which it means.
function extractPackage( argv: string[],): { packageName?: string; filter?: string; rest: string[];}Returns { packageName?: string; filter?: string; rest: string[]; }
loadPackageCommandsLoad the subcommands a package declares via its evolve.commands entry in package.json.
import { loadPackageCommands } from "@evolve-framework/cli";function loadPackageCommands( packageName: string, cwd: string,): Promise<LoadedPackage>Returns Promise<LoadedPackage>
loadProjectCommandsLoad the subcommands the project in cwd declares for itself.
import { loadProjectCommands } from "@evolve-framework/cli";The counterpart to loadPackageCommands. That one resolves a
published package through the node_modules chain, which an application
cannot use on itself: nothing depends on an application, so it is in no
one's node_modules. It declares commands the same way, through
evolve.commands, and this reads them straight off the manifest in cwd.
cwd only, never a walk up the tree. Package managers run a script with the
cwd set to the package it belongs to, so this resolves to the service the
command was invoked from; walking up would silently pick a monorepo root's
commands whenever a service's own manifest declared none.
Returns undefined when there is no manifest or it declares no commands — having none is the normal case, not an error.
function loadProjectCommands(cwd: string): Promise<LoadedPackage | undefined>Returns Promise<LoadedPackage | undefined>
runEntry point for the evolve binary.
import { run } from "@evolve-framework/cli";With --package <name>, dispatches to that package's commands and nothing
else — an explicit request for one package's surface, and the way to pick a
side when two packages contribute the same command name.
Without it, the CLI's built-ins are joined by two other sources. The project
in cwd contributes whatever its own evolve.commands declares: that is how
an application puts its server and its jobs behind one entry point
(evolve serve, evolve sync algolia) instead of a file path per task,
which --package cannot express, since an application is in no one's
node_modules and cannot be resolved by name. Its declared dependencies
contribute theirs, so installing a package is all it takes for
evolve messages build to work — naming the package it came from is an
implementation detail no invocation should have to carry.
function run(argv: string[], cwd: string): Promise<void>Returns Promise<void>
workspaceDirectoriesThe workspace directories of the monorepo rooted at cwd, or none when it is not one.
import { workspaceDirectories } from "@evolve-framework/cli";function workspaceDirectories(cwd: string): string[]Returns string[]
Constants
CLI_PACKAGE_NAMEPackage name commands are attributed to when they are built into the CLI.
import { CLI_PACKAGE_NAME } from "@evolve-framework/cli";const CLI_PACKAGE_NAME: "@evolve-framework/cli"Types
CommandLoaderProduces the class backing one subcommand.
import { type CommandLoader } from "@evolve-framework/cli";export type CommandLoader = () => Promise<SubcommandClass>;LoadedPackageThe commands a single package declares, resolved and ready to dispatch.
import { type LoadedPackage } from "@evolve-framework/cli";export type LoadedPackage = { packageName: string; packageRoot: string; commands: Map<string, CommandLoader>; cwd?: string;};RunContextEverything a subcommand needs to know about the invocation it is running inside.
import { type RunContext } from "@evolve-framework/cli";export type RunContext = { args: Record<string, unknown>; packageName: string; packageRoot: string; cwd: string;};SubcommandClassThe static shape the loader and runner rely on: a constructable Subcommand that carries its command metadata on the class itself.
import { type SubcommandClass } from "@evolve-framework/cli";export type SubcommandClass = (new () => Subcommand) & { command: string; description?: string; args?: CommandArgs; subcommands?: SubcommandClass[]; workspaceIndependent?: boolean;};
