@zamatica/bootstrap
v0.1.1
Published
App-identity + commander integration on top of @zamatica/configuration. Wires CLI subcommands to configuration parameters with env-var fallbacks. Optional NestJS subpath provides daemon-bootstrap helpers, log bridge, and ready-made Health/Version modules.
Downloads
465
Readme
@zamatica/bootstrap
App-identity, commander integration, and lifecycle on top of @zamatica/configuration. Drop-in startup library for TypeScript apps that need a CLI and runtime configuration.
The NestJS-specific helpers (daemon bootstrap, Health/Version modules, log bridge, HTTP listener) live behind subpath exports — see NestJS subpaths below.
Install
bun add @zamatica/bootstrap
# Optional peers (only needed if you use the /nestjs subpaths):
bun add @nestjs/common @nestjs/coreWhat the core gives you
bootstrap({...})— sets up app identity (name/acronym/description/env-var prefix), wires the prefix intoConfigurationService, builds a commander program, and auto-attaches all@CliCommanddecorated classes plus the built-ingenerate-dot-envandprint-configcommands. One-shot per process.@CliCommand({ name, description, aliases? })— class decorator that registers a subcommand.@Configuration(...)parameters on the same class become commander options on that subcommand, with env-var fallbacks.detectMode({ args, isTtyAttached, appCliVerbs? })— pure helper for two-mode (CLI vs daemon) executables. Recognizes--cli/--daemon, universal CLI flags (--help,-h,-V,--version), universal verbs (help,generate-dot-env,print-config), and any app-specific verbs you pass in.generate-dot-env <target>— built-in subcommand that walks all registered@Configurationkeys and appends a.envskeleton to the target file. Preserves any existingKEY=valuelines.print-config— built-in subcommand that prints every registered@Configurationparameter to stdout. Default format is human-readable (grouped by registering class);--jsonemits an array of objects;--envemits a.envskeleton (same shape asgenerate-dot-envwrites, but to stdout). Side-effect-free.CONFIGURATION_BASE_DIRECTORY— universal directory parameter, registered automatically (mode-specific defaults are applied by the consumer's mode bootstrap).
Quick example
A purely-CLI app with a custom subcommand:
import { bootstrap, CliCommand } from '@zamatica/bootstrap';
import {
Configuration,
ConfigurationParameter,
ConfigurationService,
} from '@zamatica/configuration';
// `targetName` and `@Configuration`'s first argument MUST match — they're the
// shared key that lets commander find this class's parameters in the registry.
// See the `targetName` JSDoc for why explicit beats `target.name` when bundling.
@CliCommand({ name: 'process', description: 'Process the queue once and exit', targetName: 'ProcessCommand' })
@Configuration(
'ProcessCommand',
ConfigurationParameter.named('BATCH_SIZE')
.number()
.default(50)
.description('Items per batch')
.build(),
)
class ProcessCommand {
static run() {
const batchSize = ConfigurationService.get<number>('BATCH_SIZE');
console.log(`processing ${batchSize} items`);
}
}
const { run } = bootstrap({
name: 'My Service',
acronym: 'MYS',
description: 'A queue-processing service.',
});
await run();Then:
mys process --batch-size 100 # → processing 100 items
MYS_BATCH_SIZE=100 mys process # → processing 100 items
mys process # → processing 50 items (default)
mys generate-dot-env .env # → writes .env skeleton for every registered key
mys print-config # → human-readable list of every registered parameter
mys print-config --json # → same data as JSON
mys --help # → commander help with all subcommandsNestJS subpaths
Three subpath exports keep the NestJS dependency optional and let non-HTTP daemons stay clean:
| Subpath | Contents | Auto-registered config |
|---|---|---|
| @zamatica/bootstrap/nestjs | Base helpers usable by any NestJS app regardless of transport: bootstrapNestDaemon, HealthModule, VersionModule, attachNestJsLogger. | None HTTP-related. |
| @zamatica/bootstrap/nestjs/http | HTTP-listener layer: bootstrapNestHttpDaemon, startHttpListener, HttpListenerConfig. Pulls in @nestjs/common/@nestjs/core. | HTTP_LISTEN_PORT, HTTP_LISTEN_HOST, HTTP_LISTENER_CONFIG_FILE (the last is reserved for upcoming YAML/HTTPS support). |
| @zamatica/bootstrap/nestjs/http/config | Just HttpListenerConfig and its parameter list — no NestJS imports. | Same three keys as above. |
Daemons that don't expose HTTP (MQTT-only, polling, scheduler-only, hybrid) import only the base subpath — the HTTP_LISTEN_* parameters never surface in their --help or generate-dot-env output.
The /http/config subpath is for CLI mode in dual-mode (CLI + daemon) executables. CLI bootstrap can side-effect-import it so generate-dot-env and print-config see the same complete parameter set the daemon registers, without dragging NestJS into CLI-mode startup. See the apps/headless template's cli/bootstrap.ts for the pattern.
HTTP-only daemon (the most common case)
import { bootstrapNestHttpDaemon } from '@zamatica/bootstrap/nestjs/http';
import { AppModule } from './app.module.js';
await bootstrapNestHttpDaemon({
identity: { name: 'My Service', acronym: 'MYS', description: '...' },
appModule: AppModule,
defaultConfigDir: '/etc/mys',
fallbackConfigDir: '~/.mys', // typically computed via os.homedir()
versionLabel: `v${APP_VERSION} (${GIT_SHA})`,
});
// Listens on HTTP_LISTEN_PORT (default 8080) / HTTP_LISTEN_HOST (default 0.0.0.0).Non-HTTP daemon (MQTT, polling, schedulers)
import { bootstrapNestDaemon } from '@zamatica/bootstrap/nestjs';
const handle = await bootstrapNestDaemon({
identity, appModule, defaultConfigDir, fallbackConfigDir,
});
// Set up MQTT clients, schedulers, etc. on `handle.app`.
await handle.app.init();Mixed (HTTP + MQTT, etc.)
import { bootstrapNestDaemon } from '@zamatica/bootstrap/nestjs';
import { startHttpListener } from '@zamatica/bootstrap/nestjs/http';
const handle = await bootstrapNestDaemon({ ... });
// wire your microservice / MQTT client / etc. here
await startHttpListener(handle.app, {
logger: handle.logger,
versionLabel: `v${APP_VERSION} (${GIT_SHA})`,
});Drop-in modules
import { HealthModule, VersionModule } from '@zamatica/bootstrap/nestjs';
// In your AppModule:
@Module({
imports: [
HealthModule, // GET /health
VersionModule.forRoot({ version, sha, built }), // GET /version
/* ...your modules... */
],
})
export class AppModule {}Pre-bootstrap log bridge
Any logging that happens before bootstrapNestDaemon finishes (e.g., from ConfigurationService.register decorator side-effects at module load) is buffered. bootstrapNestDaemon calls attachNestJsLogger for you, so the buffered entries flush into NestJS's logger automatically. If you need to attach manually (e.g., you're not using bootstrapNestDaemon):
import { attachNestJsLogger } from '@zamatica/bootstrap/nestjs';
const app = await NestFactory.create(AppModule, { logger });
attachNestJsLogger(logger); // flushes the pre-bootstrap bufferThe bridge module is intentionally in its own file (src/nestjs/logger-bridge.ts) and the subpath; non-NestJS consumers don't pull @nestjs/common types into their type graph.
One-shot
bootstrap({...}) and bootstrapNestDaemon({...}) may each only be called once per process. A second call throws. This matches the model of "one app identity per running executable."
See also
@zamatica/configuration— the underlying parameter registry and resolution layer.@zamatica/logger-configuration—LOG_LEVELS/LOG_PREFIX/DISABLE_LOG_COLORINGparsing, used internally bybootstrapNestDaemonto configure NestJS'sConsoleLogger.
