@hyperfrontend/features/cliCLI
Programmatic entry point for the hyperfrontend features CLI — the init, build, and dev commands behind the hf bin.
import { runFeaturesCli } from '@hyperfrontend/features/cli'
const code = await runFeaturesCli({
argv: process.argv.slice(2),
cwd: process.cwd(),
stdout: process.stdout,
stderr: process.stderr,
})
Commands
| Command | Purpose |
|---|---|
init | Scaffolds the glue module, config, and contract types, then wires the entry import idempotently. |
build | Resolves feature.config.*, generates the shell package, bundles it, and packs a publishable tarball. |
dev | Resolves hf-dev.config.* and starts the dev server — one static server per app plus the debug UI. |
Config resolution
feature.config.* (and hf-dev.config.*) resolve through one tiered loader: .json via
@hyperfrontend/project-scope, and .js/.cjs/.mjs/.ts/.cts/.mts via native await import().
Every config key has a matching flag (--name, --version, --protocol, --out, --url), objects are
passed as path strings (--contract, --config), precedence is defaults < config file < flags, and
--ci/--yes run headlessly (erroring on any unresolved required key).
The optional display key declares the feature's presentation agreement — the display modes it
supports (first entry = default mode) and per-mode defaults — validated at build time and baked
into the generated shell, which composes only the declared modes:
{
"display": {
"modes": ["embedded", "dialog"],
"embedded": { "width": 320, "height": 240 }, // optional fixed footprint; omit to fill the container
"dialog": { "width": 480, "height": 360, "position": "center", "backdrop": "close" },
"closeOnEscape": true,
},
}API Reference
ƒ Functions
<baseName>.<ext> config file directly under a directory. Extensions are probed in the loader's documented order (JSON, then JS, then TS) so a single project never has its format silently chosen at random.
Parameters
Returns
stringnull when none exists.Example
Discovering a feature config in the current directory
const path = discoverConfigFile(process.cwd(), FEATURE_CONFIG_BASENAME)feature.config. / hf-dev.config. / .contract. file regardless of source format. JSON files parse through
@hyperfrontend/project-scope; .js/.cjs/.mjs and .ts/.cts/.mts resolve through native await import() (Node strips TypeScript types on supported runtimes), returning the module's default export when present. The caller is responsible for validating the shape.Parameters
| Name | Type | Description |
|---|---|---|
§absolutePath | string | Absolute path to the config or contract file. |
Returns
Promise<unknown>Example
Loading a TypeScript feature config
const config = await loadModuleFile('/abs/feature.config.ts')Parameters
| Name | Type | Description |
|---|---|---|
§argv | unknown | Argument list following the bin name (usually process.argv.slice(2)). |
Returns
ParsedArgsExample
Parsing a build invocation
parseCliArgs(['build', '--protocol', 'v2', '--out', './dist'])
// => { command: 'build', flags: { protocol: 'v2', out: './dist', ci: false, ... } }feature.config.* into a ResolvedFeatureConfig and its contract, applying defaults < config file < flags precedence where a flag replaces its whole top-level key (no deep merge).Parameters
| Name | Type | Description |
|---|---|---|
§options | ResolveBuildConfigOptions | The working directory and parsed flags. |
Returns
Promise<ResolvedBuildBundle>Example
Resolving from a discovered config plus a flag override
const { config, contract } = await resolveBuildConfig({ cwd: process.cwd(), flags })--out. A v1/v2 security protocol is required for production output; an explicit --protocol none builds only when paired with --allow-open, acknowledging the open channel. The staging dir is always removed.Parameters
| Name | Type | Description |
|---|---|---|
§options | RunBuildOptions | Flags, working directory, output sinks, and injectable deps. |
Returns
Promise<number>Example
Building a feature into ./dist
const code = await runBuild({ flags, cwd: process.cwd(), stdout: process.stdout, stderr: process.stderr })hf-dev.config.* through the shared tiered loader and starts the dev server: one static server per app plus the debug UI. After printing the server URLs the returned promise stays pending while the servers run; it resolves with the success code once a shutdown signal (SIGINT/SIGTERM, e.g. Ctrl-C) arrives and every server has closed cleanly.Parameters
| Name | Type | Description |
|---|---|---|
§options | RunDevOptions | Flags, working directory, output sinks, and injectable deps. |
Returns
Promise<number>Example
Starting the dev server in the current directory
const code = await runDev({ flags, cwd: process.cwd(), stdout: process.stdout, stderr: process.stderr })init, build, or dev. --help (and a missing command) print usage; an unknown command or flag fails with usage. The returned exit code is mapped to process.exit by the bin bootstrap.Parameters
| Name | Type | Description |
|---|---|---|
§options | RunFeaturesCliOptions | argv, working directory, and output sinks. |
Returns
Promise<number>Example
Running the build command programmatically
const code = await runFeaturesCli({ argv: ['build', '--protocol', 'v2'], cwd: process.cwd(), stdout: process.stdout, stderr: process.stderr })feature.config.json (plus a .d.ts bridge beside a JSON contract), and wires a marker-guarded import into the resolved entry file. Re-runs are idempotent and partial-apply-safe: machine-owned content (config, declaration bridge, marker block) is regenerated from merged inputs (defaults < existing config < flags), pristine glue is regenerated when the merged config changes while the contract content is unchanged, author-edited content is never clobbered, missing pieces are recreated, and the summary reports created, updated, and kept counts truthfully. Everything stages into one tree committed once, so failures before commit leave the workspace untouched. Honors --dry-run, and --ci/--yes require every value to come from flags.Parameters
| Name | Type | Description |
|---|---|---|
§options | RunInitOptions | Flags, working directory, output sinks, and injectable deps. |
Returns
Promise<number>Example
Scaffolding a feature non-interactively
const code = await runInit({
flags: { name: 'clock', contract: './clock.contract.json', entry: './src/main.ts', ci: true, yes: false, dryRun: false, help: false },
cwd: process.cwd(),
stdout: process.stdout,
stderr: process.stderr,
})◈ Interfaces
runBuild, defaulted for production and overridden in tests.Properties
readonly packTarball?:(packageDir: string) => stringreadonly resolveConfig?:(options: ResolveBuildConfigOptions) => Promise<ResolvedBuildBundle>Properties
--ci/--yes and --dry-run are the headless and preview toggles.runDev, defaulted for production and overridden in tests.Properties
readonly resolveConfig?:(options: ResolveDevConfigOptions) => Promise<ResolvedDevConfig>readonly startServer?:(config: ResolvedDevConfig, deps: DevServerDeps) => Promise<DevServerHandle>readonly waitForClose?:(handle: DevServerHandle) => Promise<void>SIGINT/SIGTERM, then closing every server.runInit, defaulted for production and overridden in tests.Properties
readonly commit?:(tree: Tree, options?: CommitOptions) => CommitResultreadonly createTreeFn?:(root: string, options?: CreateTreeOptions) => Treereadonly discoverEntries?:(directory: string) => unknownreadonly loadContract?:(absolutePath: string) => Promise<FeatureContract>Properties
Properties
Properties
readonly contract:FeatureContractreadonly protocolExplicit:booleanbuild invocation.Properties
readonly packTarball?:(packageDir: string) => stringreadonly resolveConfig?:(options: ResolveBuildConfigOptions) => Promise<ResolvedBuildBundle>dev invocation.Properties
readonly resolveConfig?:(options: ResolveDevConfigOptions) => Promise<ResolvedDevConfig>readonly startServer?:(config: ResolvedDevConfig, deps: DevServerDeps) => Promise<DevServerHandle>readonly waitForClose?:(handle: DevServerHandle) => Promise<void>SIGINT/SIGTERM, then closing every server.Properties
init invocation.Properties
readonly commit?:(tree: Tree, options?: CommitOptions) => CommitResultreadonly createTreeFn?:(root: string, options?: CreateTreeOptions) => Treereadonly discoverEntries?:(directory: string) => unknownreadonly loadContract?:(absolutePath: string) => Promise<FeatureContract>● Variables
--help and on an unknown command. Documents the three commands and the shared flag surface so the headless (
--ci) path is discoverable without reading the docs.