npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fluojs/cli

v3.0.3

Published

The Fluo CLI — bootstrap, generate, migrate, and inspect commands for application development.

Readme

@fluojs/cli

The canonical CLI for fluo — bootstrap new applications, generate components and React page types, export runtime inspection data, and run code transforms.

Canonical command vocabulary

  • Scaffold with fluo new; create remains a compatibility alias.
  • Preview any supported write command with --dry-run. It preserves that command's own plan payload and performs no writes, dependency installation, git initialization, or CLI update check.
  • Use fluo doctor for read-only diagnostics. info is a compatibility alias; analyze is a separate project summary; none installs or self-updates. fluo upgrade reports latest CLI state and migration guidance, but is not read-only: in an interactive TTY with a newer version it may offer, then after explicit approval run, the package-manager global CLI install. Other interactive non-preview commands may offer the same approved self-update. --dry-run previews and help/version paths skip update checking.
  • Use --with-slice-test for module and resource generator slice tests. The former --with-test flag is not accepted. Programmatic GenerateOptions.withTest was removed; migrate callers to withSliceTest.
  • Use migration transform kinds (imports, injectable, scope, bootstrap, testing, tsconfig) in --only and --skip; JSON transforms and each file's appliedTransforms use the same tokens.
  • --install and --no-install take precedence over programmatic installation settings. --package-manager selects scaffold or package-workflow tools and is rejected for lifecycle commands where it cannot affect execution.

Preparing for the coordinated Node 24 release? Follow the consumer migration guide before upgrading packages.

Table of Contents

Installation

pnpm add -g @fluojs/cli

Or run directly without installation:

pnpm dlx @fluojs/cli new my-app

Release Contract

  • @fluojs/cli and every generated Node HTTP, mixed, microservice, and React SSR + Fastify project require Node.js >=24.0.0 <27. Node builds target node24 and Node starters use @types/node@^24.0.0. Bun and Deno starters retain native engines; the Workers Node engine constrains only local CLI/Wrangler tooling. Apply the Node.js migration before upgrading.
  • @fluojs/cli requires Node.js >=24.0.0 <27; generated Bun, Deno, and Cloudflare Workers starters may target non-Node runtimes, but the CLI process itself runs on Node.js. Generated Node HTTP and mixed projects declare Node.js >=24.0.0 <27 so listener-level RFC QUERY reaches framework dispatch; Node versions below 24 and Node 27+ are excluded. Node microservice-only projects retain their independent >=24.0.0 <27 floor.
  • inspect resolves @fluojs/runtime only when inspection runs and only from the inspected project's dependency tree. Install a runtime version compatible with your active Node.js version before using fluo inspect; other CLI commands remain available across the CLI's full Node.js >=24.0.0 <27 range.
  • Interactive new flows and optional interactive inspect --mermaid guidance resolve @clack/prompts only when needed. Both interactive and non-interactive commands support the CLI Node.js >=24.0.0 <27 range.
  • The CLI and generated Node.js starter toolchain use tsx@^4.23.1; generated gRPC starters require @grpc/grpc-js@^1.14.4. Refresh existing project lockfiles when adopting these patched floors.
  • The supported install paths are the global package (npm install -g @fluojs/cli, pnpm add -g @fluojs/cli, bun add -g @fluojs/cli, or yarn global add @fluojs/cli) and the no-install runner (pnpm dlx @fluojs/cli ...).
  • The published fluo bin is the ./bin/fluo.mjs wrapper declared in package.json; that wrapper loads the dist-built CLI entrypoint at ../dist/cli.js.

Version Inspection

Check the installed CLI version without triggering the interactive update check:

fluo version
fluo --version
fluo -v

Update Checks

When fluo runs in an interactive TTY, it checks the public npm latest dist-tag for @fluojs/cli using a local cache so every invocation does not hit the registry. If a newer version is available, the CLI asks whether to install it. Declining continues the current command with the installed version; accepting updates the global CLI with the package manager that appears to own the current installation (npm install -g, pnpm add -g, bun add -g, or yarn global add) and then restarts fluo with the same arguments under the updated binary. If the installer cannot be inferred, the CLI falls back to npm install -g @fluojs/cli@<latest> because npm owns the default Node.js global installation path.

fluo new and its fluo create alias attempt a fresh interactive latest-version check before scaffolding, even when the normal update-check cache is still fresh. This keeps first-run project creation aligned with newly published starter behavior, while day-to-day commands such as fluo dev, fluo build, fluo generate, and fluo inspect continue to reuse the cached latest-version result until the normal TTL expires. Pure help and version inspection paths (fluo help <command>, <command> --help, fluo version, fluo --version, and fluo -v) print immediately without running the interactive update check.

The update check is skipped in CI, non-TTY output, npm-script contexts, rerun-after-update contexts, registry/network failures, and explicit opt-out paths. Use --no-update-check for one invocation, or set FLUO_NO_UPDATE_CHECK=1 when automation must never prompt.

When to Use

  • Bootstrapping: When starting a new project with a standard, verifiable structure.
  • Generation: To create modules, controllers, services, and repositories with consistent naming and automatic wiring.
  • Code transforms: When aligning an existing codebase with fluo's standard decorator model.
  • Inspection: To export runtime snapshot data and delegate graph viewing or rendering to Studio-owned helpers.

Quick Start

Node HTTP and mixed starters now generate FluoFactory.create(AppModule, { adapter }) followed by app.listen(), with explicit createConsoleApplicationLogger() and createNodeShutdownSignalRegistration() imports from @fluojs/platform-nodejs. Fastify/Express starters declare that direct dependency rather than relying on a transitive import. Factory owns middleware defaults and startup-failure cleanup; the host callback owns process signals. Existing generated apps can follow the HTTP Factory migration. Context-only/microservice APIs and not-yet-migrated host-specific starter recipes retain their separate contracts.

1. Create a new project

Scaffold a complete starter application in seconds.

fluo new my-app --package-manager pnpm
cd my-app
pnpm dev

This example pins pnpm, so every successful scaffold ends with the same completion block:

Done.
Next steps:
  cd ./my-app
  pnpm dev  # runs fluo dev

In non-interactive output, an install prints Installing dependencies with pnpm... and the package-manager output before that block, while --no-install prints Skipping dependency installation.. In an interactive terminal, the wizard reports the same install state through its status UI (Dependencies installed or Dependency installation skipped) instead of those non-interactive stdout lines, then prints the common completion block. A completed pnpm install writes pnpm-lock.yaml; --no-install leaves it absent until you run pnpm install in the generated project. If you omit --package-manager pnpm, the CLI selects a manager from the current invocation or workspace signals and falls back to pnpm, so both the next-step command and the lockfile become manager-specific; other managers write their own lockfile instead of pnpm-lock.yaml when installation runs.

fluo create is an alias for fluo new. Use fluo version, fluo help <command>, fluo doctor/fluo info/fluo analyze, fluo add, and fluo upgrade for version checks, command help, diagnostics, first-party package shortcuts, and upgrade guidance.

Generated Node.js dev, build, and start package scripts delegate to fluo dev, fluo build, and fluo start. The CLI owns the Node-oriented lifecycle command, prepends the project-local node_modules/.bin when invoking local toolchain binaries, and defaults NODE_ENV to development for dev and production for build/start when the caller has not set it explicitly. Bun, Deno, and Workers generated dev scripts keep the same fluo dev abstraction but default to Bun, Deno, or Wrangler native watch loops to reduce Node-supervised dev processes; use fluo dev --runner fluo (or FLUO_DEV_RUNNER=fluo) when you need the fluo-owned restart boundary for its debounce/hash reporter contract. Their production/deployment scripts are runtime-native: Bun uses bun build ./src/main.ts --outdir ./dist --target bun and bun dist/main.js, Deno uses deno compile --allow-env --allow-net --output dist/app src/main.ts and ./dist/app, and Workers exposes Wrangler preview/deploy scripts instead of start. The generated Deno AppModule snapshots application-owned environment values with Deno.env.toObject(), so its dev and compiled commands retain broad --allow-env; signal listeners do not require a separate Deno permission. By default, fluo dev and fluo start show app logs only (application stdout/stderr) so the lifecycle output shape is unified where the CLI owns the process boundary. Use --reporter pretty when you want concise fluo-branded lifecycle status and app │-prefixed application stdout/stderr, and use --verbose (or FLUO_VERBOSE=1) when you need raw runtime/tooling watcher output for debugging.

Generated starters set their @fluojs/cli devDependency from the generator CLI package version that created the project, so lifecycle scripts such as pnpm dev, pnpm build, and pnpm start keep using the same CLI behavior that scaffolded the starter instead of a stale hard-coded range.

In published mode, every other internal @fluojs/* dependency and dev dependency uses ^<package version> from that package's release manifest. The CLI build generates this metadata after Changesets versioning and ships it in dist, so an unpacked CLI does not need the monorepo or a registry lookup to scaffold. Versions remain independent, including React's 0.x line; they are not all set to the CLI's major. This applies to every HTTP, microservice, mixed, React, Bun, Deno, and Workers starter. Internal local-sandbox tarball overrides still take precedence.

Generated non-Deno standard starter vite.config.ts files import fluoDecoratorsPlugin() from @fluojs/vite, while the React SSR + Vite starter applies the same plugin in vite.server.config.ts. The React starter keeps decorator-bearing declarations in src/app.ts and JSX rendering in .tsx modules, so the supported .ts transform boundary stays explicit. Decorator transform updates therefore ship through the maintained Vite package instead of being copied inline into every new project.

New non-Deno projects declare Vite ^8.2.2, Vitest ^4.1.11, and @vitest/coverage-v8 ^4.1.11. Generated ESM Vite configs use build.rolldownOptions; Babel handles standard decorators through fluoDecoratorsPlugin() before Rolldown/Oxc processes application code, while vitest.config.ts uses fluoDecoratorsPlugin({ sourceMaps: true, transformBoundary: 'test' }) with @fluojs/core/metadata-preload. Direct Oxc/esbuild decorator processing is not supported. Node.js >=24.0.0 <27 and runtime-native starter metadata are unchanged. Existing projects are not rewritten; adopt the generated toolchain baseline explicitly when upgrading.

When upgrading an existing starter, remove the ignore: ['src/**/*.test.ts'] rule from babel.config.cjs. Generated Babel configs no longer exclude test files, so the testing plugin also transforms decorators declared inside tests before Vitest 4 executes them.

Generated standard non-Deno HTTP starters use a TDD-first Vitest layout: fast greeting unit tests and greeting.slice.test.ts stay colocated under src/greeting/, app dispatch tests stay in src/app.test.ts, and the default e2e-style request-pipeline tests live in test/app.e2e.test.ts with Test.createApp({ rootModule }) plus app.request(...).send(). The React starter instead includes focused streamed SSR, DOM hydration, and production Playwright hydration tests. Its test:browser script starts the built Fastify server and fails on missing assets, hydration warnings, or navigation that bypasses the server-owned route.

For generated Node.js application projects, fluo dev runs through a fluo-owned restart boundary by default. The runner watches source and common config inputs, debounces atomic-save bursts, hashes file content before restarting, loads .env for each Node app child process it spawns, and ignores noisy output/cache paths such as node_modules, dist, .git, .fluo, coverage, cache folders, and editor swap files. Pressing Ctrl+S without changing file content should not restart the app. Planned restarts and terminal shutdown first send SIGTERM to the current app child, then force-kill it after a bounded grace period so a non-cooperative child cannot hang the restart supervisor indefinitely. On terminal app child exit or crash outside a planned restart, the runner closes watchers, clears the pending restart timer and paths, unregisters its SIGINT/SIGTERM handlers, and exits with the child terminal code. A terminal error from either the primary recursive watcher or a fallback directory watcher, a required source target that is missing or inaccessible before watcher registration, or an unavailable required fallback source watcher after recursive watching is unavailable follows that same cleanup path. This covers total fallback acquisition failure, partial failure after sibling watchers are acquired, and failure for a directory discovered while the fallback watcher is running; the runner terminates the current app child within the existing bound and exits with code 1 instead of leaving the child or sibling watchers running. This is full-process restart-on-watch, not module-level HMR; config watch reloads are a separate in-process config concern, and future HMR work must document which modules can be safely hot-swapped. Use fluo dev --raw-watch or FLUO_DEV_RAW_WATCH=1 when you need the runtime-native Node watcher for debugging. Generated Bun/Deno/Workers projects delegate watch/reload behavior to bun --watch, deno run --watch --allow-env --allow-net src/main.ts, or wrangler dev by default; the Deno command preserves access to every application-owned environment key consumed through Deno.env.toObject(). Use fluo dev --runner fluo or FLUO_DEV_RUNNER=fluo when those projects should return to the fluo-owned restart runner, and use FLUO_DEV_WATCH_IGNORE=path,pattern to add extra ignored paths for that runner.

fluo new supports Node.js + Fastify, Express, and raw Node.js HTTP application starters on the same Node-oriented install/build flow:

fluo new my-app --shape application --transport http --runtime node --platform fastify
fluo new my-express-app --shape application --transport http --runtime node --platform express
fluo new my-node-app --shape application --transport http --runtime node --platform nodejs

The raw Node starter creates its adapter with NodeHttpApplicationAdapter.create(options), then calls FluoFactory.create(AppModule, { adapter }) and app.listen(). The host owns app.close() and explicit signal registration/cleanup. Follow the Node adapter creation migration when moving from run-helper middleware/logger/signal defaults. Existing projects are not rewritten.

The application matrix also includes runtime-native Bun, Deno, and Cloudflare Workers starters with runtime-specific entrypoints, scripts, and dependency sets:

fluo new my-bun-app --shape application --transport http --runtime bun --platform bun
fluo new my-deno-app --shape application --transport http --runtime deno --platform deno
fluo new my-worker-app --shape application --transport http --runtime cloudflare-workers --platform cloudflare-workers

The Cloudflare Workers template calls CloudflareWorkerApplicationHost.create(AppModule) and exports export default { fetch: worker.fetch }. The host owns first-request bootstrap, retry, generation replacement, and drain recovery. Each generation internally creates its adapter, calls FluoFactory.create(AppModule, { adapter }), and awaits no-socket app.listen(). Use the host's { fromEnv } overload when the first Worker environment must select bootstrap configuration.

Select the official HTTP-first React SSR + Vite application with the named starter flag:

fluo new my-react-app --starter react-vite-ssr

This starter fixes the schema to Node.js + Fastify HTTP. Run pnpm dev, open /products/sku-42?preview=true, and edit src/page.tsx; page UI no longer needs to carry Vite assets, the document shell, or the server/client route snapshot wiring. The explicit @Router(...) / @Path(...) handler in src/app.ts returns that page as one ReactElement, so the existing HTTP dispatcher remains authoritative.

Generated application wiring stays visible instead of becoming a framework abstraction: src/entry-server.tsx owns the replaceable ReactPageRenderer and ReactServerEntry creation, src/react-app.tsx shares one document and ReactClientRouterProvider composition between server and client, src/entry-client.tsx calls hydrateRoot(...), and src/main.ts uses src/load-manifest.ts to load the generated Vite manifest before @fluojs/react/vite parses it. Missing build output, incompatible entry selectors, and hydration mismatches identify those exact application files and the lifecycle command to rerun. Generated Link output remains a real anchor and router.push(...) performs full-document navigation through the HTTP dispatcher. The starter intentionally excludes RSC, Server Functions, file routing, a client route table, SPA document swapping, prefetch, and a data cache.

fluo new also exposes microservice starter paths. TCP is the default when you omit --transport, and the starter matrix includes runnable Redis Streams, NATS, Kafka, RabbitMQ, MQTT, and gRPC variants with transport-specific dependencies, env templates, and entrypoints:

fluo new my-microservice --shape microservice --transport tcp --runtime node --platform none
fluo new my-redis-streams-service --shape microservice --transport redis-streams --runtime node --platform none
fluo new my-nats-service --shape microservice --transport nats --runtime node --platform none
fluo new my-kafka-service --shape microservice --transport kafka --runtime node --platform none
fluo new my-rabbitmq-service --shape microservice --transport rabbitmq --runtime node --platform none
fluo new my-mqtt-service --shape microservice --transport mqtt --runtime node --platform none
fluo new my-grpc-service --shape microservice --transport grpc --runtime node --platform none

Supported --shape microservice --transport starter values are exactly tcp, redis-streams, nats, kafka, rabbitmq, mqtt, and grpc. Use redis-streams for the maintained Redis-backed starter, or add @fluojs/redis manually after scaffolding when you need broader Redis integration patterns.

The NATS/Kafka/RabbitMQ starter contracts stay explicit about external brokers and caller-owned client libraries. Generated projects wire nats + JSONCodec(), kafkajs producer/consumer collaborators, and amqplib publisher/consumer collaborators directly in src/app.ts so the starter contract is runnable without pretending the base fluo packages hide those dependencies. Those broker clients are created lazily by the generated transport wrapper when the Fluo lifecycle starts listening, sends, or emits; importing src/app.ts for fluo inspect, tests, or static tooling does not connect to a broker or open external resources before the application lifecycle owns teardown. During shutdown, each wrapper still attempts to close its owned clients when delegated transport shutdown fails. Kafka partial connection failures disconnect acquired clients, and RabbitMQ channel creation failures close the acquired connection before the startup error is rethrown.

The starter matrix also includes a mixed single-package starter: one Fastify HTTP app with an attached TCP microservice in the same generated project.

fluo new my-mixed-app --shape mixed --transport tcp --runtime node --platform fastify

When fluo new runs in an interactive TTY, the wizard uses the same flags/config model. It first offers the standard backend and React SSR + Vite named starters. The standard branch then asks for the shape-first path (application -> runtime + HTTP platform, microservice -> transport); the React branch uses its fixed Node.js + Fastify HTTP contract. Both continue with the maintained tooling preset, package-manager choice, dependency installation, and git initialization. Non-interactive flags and programmatic runNewCommand(...) calls use the same resolved defaults.

By default, fluo new my-app writes to ./my-app. Use --target-directory <path> when the project name and destination path should differ, or when automation should write to an explicit directory:

fluo new my-app --target-directory ./apps/api

Scaffolding refuses to overwrite conflicting files in a non-empty target by default. Add --force only when you intentionally want generated files with different contents to be overwritten:

fluo new my-app --target-directory ./apps/api --force

Use --dry-run when you want to preview the fully resolved starter without side effects:

fluo new my-app --shape application --runtime node --platform fastify --dry-run
fluo new my-react-app --starter react-vite-ssr --dry-run
fluo new my-service --shape microservice --transport tcp --dry-run
fluo new my-mixed-app --shape mixed --dry-run

Plan preview mode resolves the same named starter, project name, shape, runtime, platform, transport, tooling preset, package manager, install choice, and git choice as a real scaffold. It prints both the selected starter and recipe plus their dependency sets, then exits without creating files, installing dependencies, or initializing a git repository.

For a docs-level table that separates the shipped starter matrix (standard Node.js Fastify/Express/raw Node.js HTTP, React SSR + Vite on Node.js + Fastify, Bun, Deno, Cloudflare Workers, TCP/Redis Streams/NATS/Kafka/RabbitMQ/MQTT/gRPC microservices, plus mixed) from the remaining broader adapter ecosystem, see the fluo new support matrix. Package-level integrations such as @fluojs/redis remain part of the broader ecosystem, but they are not extra fluo new --transport starter flags.

2. Generate a feature

Generate a feature slice; some schematics auto-register in the module, while others are files-only and must be wired manually.

fluo generate module users
fluo generate module users --with-slice-test
fluo generate resource users
fluo generate resource users --with-slice-test
fluo generate e2e users
fluo generate controller users
fluo generate service users
fluo generate request-dto users CreateUser
fluo generate service users --dry-run

Supported generator kinds and aliases are controller/co, e2e, guard/gu, interceptor/in, middleware/mi, module/mo, repo/repository, request-dto/req, resource/resrc, response-dto/res, and service/s.

Auto-registered generators are controller, service, repo, guard, interceptor, and middleware. Files-only generators are e2e, module, request-dto, response-dto, and resource.

fluo generate module <name> --with-slice-test adds a *.slice.test.ts that compiles the authored module with Test.createTestingModule({ rootModule }). fluo generate resource <name> creates a complete feature slice with a module, controller, service, repository, request DTO, response DTO, and tests; add --with-slice-test to include a resource-level slice test that demonstrates provider override and service resolution. It is a files-only/manual-activation generator: it does not wire the resource module into a parent module automatically, and it should not be treated as a nest g resource equivalent that activates routes for you. Import the generated module when you are ready to activate the slice.

fluo generate e2e <name> writes test/<name>.e2e.test.ts with Test.createApp({ rootModule: AppModule }) and imports AppModule from the default starter root module at ../src/app, so request-pipeline tests live in the same app-level test area as generated starters. Use generated unit tests for direct class behavior, slice tests for DI wiring and overrides, and e2e tests for routes, guards, interceptors, DTO validation, and response writing through the virtual app.

Existing tests must replace the removed createTestApp and createTestingModule imports with import { Test } from '@fluojs/testing', then call Test.createApp(...) and Test.createTestingModule(...). Options, builder chains, request assertions, and public test types remain unchanged. After compilation, put module assertions in try and await testingModule.container.dispose() in finally; app tests likewise use await app.close() in finally. Regenerate templates or migrate existing files explicitly; upgrading the CLI does not rewrite them.

Request DTO generation accepts the feature directory separately from the DTO class name, so multiple input contracts such as CreateUser and UpdateUser can live inside the same src/users/ slice.

Add --dry-run to preview the same target resolution, skipped or overwritten file decisions, module auto-registration plan, files-only wiring status, and next-step hint without creating directories, writing files, or updating modules. --force still changes existing-file plan entries from SKIP to OVERWRITE when content would change, and --target-directory scopes the preview to that source directory exactly as it does for a real run.

Generator discovery is intentionally limited to the built-in @fluojs/cli/builtin collection. External package-owned or app-local generator collections are deferred: fluo generate does not scan config files, load arbitrary packages, follow package discovery conventions, or execute workspace-owned collection code. This keeps generator metadata, option schemas, help output, and file-write boundaries deterministic and testable while preserving the shipped generator contract.

Common Patterns

Diagnostics and project scripts

Use doctor/info when you need to debug the installed CLI, npm dist-tags, update-check cache state, runtime, and project scripts:

fluo doctor
fluo info
fluo analyze

fluo analyze stays read-only and points to deeper inspect --report and migrate --json workflows. For generated Node.js projects, fluo dev, fluo build, and fluo start run the generated lifecycle directly with environment defaults and project-local toolchain binaries. For generated Bun, Deno, and Cloudflare Workers projects, fluo dev defaults to the runtime-owned watch loop while fluo dev --runner fluo restores the CLI-owned restart boundary; production/deployment scripts are the generated package scripts (bun dist/main.js, ./dist/app, or Wrangler preview/deploy). Use --dry-run to preview lifecycle commands:

fluo dev --dry-run
fluo build --dry-run
fluo start --dry-run

fluo dev --dry-run also reports the watch boundary. Generated Node projects show Watch mode: fluo-restart by default, Node --raw-watch and FLUO_DEV_RAW_WATCH=1 show Watch mode: native-watch, and Bun/Deno/Workers projects show Watch mode: runtime-native-watch by default. Use --runner fluo or FLUO_DEV_RUNNER=fluo in Bun/Deno/Workers projects to show Watch mode: fluo-restart and restore the fluo-owned restart runner.

Runtime-connected Studio devtool

Use fluo dev --studio when you want the local React Studio devtool attached to the running app instead of exporting static HTML/JSON first:

fluo dev --studio
fluo dev --studio --studio-port 51234
fluo dev --studio --dry-run

The CLI starts a local Studio sidecar, prints a tokenized URL, keeps restart lifecycle events flowing through the sidecar, and injects an explicit Studio config into the Node app child before the app imports @fluojs/runtime. Studio live mode requires the fluo-owned Node restart runner; fluo dev --studio rejects --raw-watch, --runner native, and FLUO_DEV_RUNNER=native so lifecycle events cannot be split from the CLI restart boundary. The sidecar serves the packaged @fluojs/studio/viewer React app when that optional package is installed. Runtime package source never reads process.env directly; it publishes live graph/routes/request/timing/diagnostic events only when CLI-injected Studio config is present.

Security defaults are local-only: the sidecar binds 127.0.0.1, runtime ingestion and browser state/SSE APIs require generated tokens, CORS is not enabled by default, and request bodies are not captured by default. The sidecar settles ingestion requests with a bounded error completion when a local client closes the socket after sending only a partial request body, so a malformed local client cannot hang sidecar work indefinitely. When StudioSidecar.close() starts while an authenticated ingestion body is still incomplete, the sidecar closes only its active ingestion sockets, ends tracked SSE responses through the existing close path, and shares one deterministic teardown across repeated or concurrent close calls. Completed ordinary requests are not part of that active-ingestion ownership set, and a client that remains open with a partial body cannot keep CLI shutdown pending indefinitely.

Runtime support for the MVP is explicit:

| Runtime target | fluo dev --studio status | | --- | --- | | Node dev runner | Full support target. | | Bun | Not enabled for this MVP; fluo dev --studio rejects Bun projects until a dedicated bridge is implemented and verified. Export Studio-compatible static artifacts with fluo inspect <module-path> --json --output <path> or fluo inspect <module-path> --report --output <path> instead. | | Deno | Not enabled for this MVP; fluo dev --studio rejects Deno projects until a dedicated bridge is implemented and verified. | | Cloudflare Workers | Unsupported for this MVP unless a worker bridge is added and tested. |

Use reporter flags when you need to tune the CLI process boundary rather than runtime app logging:

# Default: child stdout/stderr only, no fluo lifecycle UI
fluo dev

# Opt-in pretty lifecycle UI + app │ prefixes
fluo dev --reporter pretty

# Raw runtime/tooling output for debugging (watcher banners, native dev UI, etc.)
fluo dev --reporter stream
fluo dev --verbose
FLUO_VERBOSE=1 fluo dev

# Suppress wrapper/tool status while keeping child stderr and failures visible
fluo build --reporter silent

Runtime application logs are configured separately through ApplicationLogger, for example createConsoleApplicationLogger({ mode: 'minimal', level: 'warn' }) or createJsonApplicationLogger() from @fluojs/platform-nodejs.

Use fluo add <package> for first-party package installation shortcuts and fluo upgrade for CLI/latest-version and migration guidance:

fluo add studio --dev --dry-run
fluo upgrade

Use --dev as the canonical development-dependency option; -D is its typing shortcut.

Decorator Codemods

Run codemods to align your codebase with TC39 standard decorators.

# Preview changes (dry-run)
fluo migrate ./src
fluo migrate ./src --dry-run
fluo migrate ./src --json

# Apply transformations
fluo migrate ./src --apply
fluo migrate ./src --apply --json
fluo migrate ./src --apply --only imports,injectable
fluo migrate ./src --apply --skip testing

Migration preview is the default; --dry-run is its explicit canonical form, while --apply is the only switch that writes files. The canonical --only and --skip tokens are imports, injectable, scope, bootstrap, testing, and tsconfig. Legacy inject-params and tests inputs remain accepted for existing scripts, but JSON transforms and appliedTransforms always emit injectable and testing.

Use --json when CI jobs, dashboards, or migration reports need a stable machine-readable result. Human output remains the default. JSON mode writes only the structured report to stdout on success, while parser errors and invalid flag combinations still write their message to stderr and return exit code 1 without partial JSON output. The report uses schemaVersion: 1 and includes mode (dry-run or apply), dryRun, apply, enabled transforms, scannedFiles, changedFiles, aggregate warningCount, and per-file metadata with filePath, changed, appliedTransforms, warningCount, and warnings including category labels and source line numbers.

Review every warning before rerunning with --apply. Warnings are manual follow-up items rather than permission for an automatic rewrite to be accepted blindly; use the NestJS migration guide as the post-codemod checklist for each warning category.

Adapter-independent transforms (imports, injectable, scope, testing, and tsconfig) run without an HTTP adapter. Bootstrap rewrites never infer a platform: without a selected platform, the codemod retains NestFactory.create(AppModule) and its listen(port) call and emits a required adapter-selection warning. The only automatic bootstrap rewrite requires explicit --platform express and exactly one numeric-literal single-argument app.listen(port). Host, callback, string, environment-derived, and multiple-listen forms remain untouched with a warning for manual migration. Install @fluojs/platform-express and express before compiling the migrated application:

fluo migrate ./src --apply --platform express

# Leave bootstrap unchanged while applying only adapter-independent transforms
fluo migrate ./src --apply --only imports,injectable,scope,testing,tsconfig

Key Transformations:

  • Rewrites imports from @nestjs/common to @fluojs/core or @fluojs/http.
  • Rewrites bootstrap patterns only after explicit platform selection and folds supported listen(port) calls into fluo runtime startup conventions.
  • Migrates constructor parameter @Inject(...) usage into fluo-compatible dependency declarations.
  • Removes @Injectable() and maps scopes to @Scope().
  • Migrates test templates toward @fluojs/testing helpers where the codemod can do so safely.
  • Updates tsconfig.json to disable experimentalDecorators and rewrites baseUrl-backed path aliases to TS6-safe paths entries.

Runtime Inspection

Export your application structure and troubleshoot initialization issues without making the CLI own graph rendering.

# Store a versioned report as the default inspect artifact recipe
fluo inspect ./src/app.module.ts --report --output artifacts/inspect-report.json

# Export Mermaid through the optional Studio renderer
fluo inspect ./src/app.module.ts --mermaid

# Export snapshot for @fluojs/studio
fluo inspect ./src/app.module.ts --format json > snapshot.json

# Write the same JSON snapshot to a CI artifact path without shell redirection
fluo inspect ./src/app.module.ts --json --output artifacts/inspect-snapshot.json

# Include bootstrap timing next to the runtime-produced snapshot
fluo inspect ./src/app.module.ts --timing

# Emit a support triage report with summary, snapshot, diagnostics, and timing
fluo inspect ./src/app.module.ts --report --output artifacts/inspect-report.json

# Inspect a named module export; defaults to AppModule
fluo inspect ./src/app.module.ts --export AdminModule --json

--format json is explicitly equivalent to --json: either writes exactly one JSON document to stdout while runtime diagnostics go to stderr, and any other --format value is rejected. Mermaid output follows the same stream boundary: its graph is the only stdout payload, and bootstrap diagnostics always go to stderr.

The runtime produces the inspection snapshot. fluo inspect accepts generated TypeScript source modules such as ./src/app.ts or ./src/app.module.ts through an explicit TypeScript loader boundary, while existing .js and .mjs module paths continue to load through native Node.js ESM. The CLI bootstraps an adapterless application so the authoritative HTTP dispatcher descriptors are available, then adds the runtime-owned routes projection to JSON, timing envelopes, and report snapshots. The CLI owns inspect orchestration, JSON serialization, report wrapping, and --output <path> artifact writes; Studio owns snapshot parsing, filtering, connection inspection, viewer rendering, and Mermaid graph semantics. Use fluo inspect <module-path> --report --output <path> as the default recipe for newly stored artifacts. fluo inspect still serializes the snapshot as JSON by default when no output mode flag is provided, and raw snapshots, timing envelopes, and legacy timing readers remain supported for compatibility. fluo inspect --mermaid delegates snapshot-to-Mermaid rendering to the optional @fluojs/studio contract. --export <name> selects the module export to bootstrap and defaults to AppModule; --timing records bootstrap timing diagnostics next to the JSON snapshot output, including when --timing is provided without an explicit --json flag, and --report wraps the runtime-produced snapshot with a stable summary for CI/support triage. --timing cannot be combined with Mermaid output. --output <path> writes the selected inspect payload to an explicit artifact path instead of stdout; it does not make the inspected application writable or change module graph state beyond the normal bootstrap/close cycle.

Each routes[] entry contains id, kind, method, effective path, optional effective version, parameter names in params, and the originating module, controller, and handler. React @Path(...) handlers use kind: 'react-page'; ordinary HTTP handlers use kind: 'http'. The artifact never contains parameter values, request bodies, cookies, headers, or query values, and the route projection does not participate in matching, conflict detection, or dispatch. Install Studio in the project that runs the command when you need Mermaid output:

pnpm add -D @fluojs/studio

If Studio is missing, CI and other non-interactive runs fail fast with install guidance instead of prompting or running a package manager. Interactive runs may ask whether you want to install Studio, but fluo inspect does not run installs unless an explicit install flow is implemented and approved.

React Page Type Generation

Generate application-owned, path-only React page types and absolute href builders from the bootstrap-resolved route catalog:

fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts
fluo typegen ./src/admin.ts --export AdminModule --output ./src/generated/admin-pages.ts
fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --check
fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --watch

--export defaults to AppModule. The command loads TypeScript source through the CLI loader, bootstraps the application, reads app.dispatcher.describeRoutes(), calls createReactPageCatalog(...) and generateReactPageTypes(...), then closes the application. Output paths are resolved from the current working directory. A missing file is reported as CREATE, stale content as UPDATE, and byte-identical content as UNCHANGED. Writes publish one complete temporary file with an atomic rename, and UNCHANGED never rewrites the target.

Default generation evaluates the application and matching tooling namespaces in one short-lived child process, waits for that process to exit, and only then checks or publishes the result. Repeated watch generations therefore do not retain application module graphs or TypeScript loader resources in the watcher process. Programmatic callers that provide TypegenCommandRuntimeOptions.loadReactTypegenModules intentionally keep generation in the caller process; the returned namespaces remain authoritative for TypeScript, .js, and .mjs inputs.

--check performs the same authoritative bootstrap and generation but never writes the target. It compares exact bytes and reports one stable status. UNCHANGED goes to stdout with exit code 0; MISSING, STALE, MALFORMED, and UNSUPPORTED_VERSION go to stderr with exit codes 2, 3, 4, and 5. A current-version target must be one complete canonical generated body: syntax or structural corruption is MALFORMED, while a complete artifact from an older catalog is STALE. Argument, bootstrap, generation, filesystem, and other command failures use exit code 1. The root package exports TYPEGEN_EXIT_CODES for programmatic callers.

--watch is the bounded development integration. The CLI installs the recursive application-module watcher before startup generation. It prints WATCHING <directory> only after that generation and one coalesced rerun for any changes observed while it ran have completed successfully. Filesystem bursts after readiness are coalesced for 100 ms, generations are serialized, and events for the output or its temporary files are ignored. Each generation evaluates a current application module graph, including changed native .js and .mjs dependencies, before the authoritative bootstrap. A regeneration failure prints ERROR <output>: <message>, preserves the last valid artifact, and waits for a later change. A watcher failure exits with code 1 after cleanup. Every source event received while a generation or its owned artifact commit is active invalidates that work; its output cannot publish before the coalesced successor completes. SIGINT and SIGTERM close the watcher, remove signal handlers, cancel the active owned generation (a child process or caller-process bootstrap), and abort its owned artifact commit before either can publish. Caller-process cancellation waits for asynchronous bootstrap and application close to settle before watch exits with code 0; a child that does not exit after SIGTERM is force-killed after the bounded grace period. Files outside the module directory are intentionally outside this watch boundary; run the command again or choose a module path at the intended source root instead of expecting source scanning or a second route discovery system.

The generated reactPageRoutes object keys routes by stable catalog id. Its dynamic href(...), link(...), push(...), and replace(...) methods require all path params and URI-encode each value; static methods accept no params. Spread route.link(params) into the existing real-anchor Link, or pass the existing ReactRouter to route.push(router, params) / route.replace(router, params):

const productRoute = reactPageRoutes['GET /products/:productId ProductRouter show'];

<Link {...productRoute.link({ productId })}>Product</Link>;
productRoute.push(router, { productId });
productRoute.replace(router, { productId });

These generated methods resolve to ordinary absolute href strings before the existing HTTP-first client APIs run. They do not add a runtime route table, matcher, relative-route model, or SPA navigation. Existing href(...), Link href, and router string/URL calls remain supported. Versioned routes fail explicitly because the catalog cannot distinguish URI versioning from header, media-type, or custom version strategies. See the @fluojs/react path-only typegen contract.

Public API

The package can be used programmatically to trigger CLI actions from within other tools.

| Export | Description | |---|---| | runCli(argv?, options?) | Main entry point to execute any CLI command. | | CliRuntimeOptions | Type for runCli(...) runtime overrides such as streams, cwd, environment, registry metadata, and update-check hooks. | | newUsage() | Returns the current fluo new usage text for help surfaces and tests. | | runNewCommand(argv, options?) | Programmatic access to the project scaffolding logic. | | NewCommandRuntimeOptions | Type for runNewCommand(...) runtime overrides such as prompts, filesystem writes, dependency installation, and git initialization; runCli(...) also accepts these overrides when it dispatches new or create. Monorepo-local starter dependency overrides are internal sandbox harness details, not part of this public type. | | CliPromptCancelledError, isCliPromptCancelledError(...) | Stable sentinel and version-aware predicate for caller-supplied prompt hooks to report normal cancellation across compatible duplicate copies; plain lookalikes remain command failures. | | runGenerateCommand(kind, name, baseDirectory, options?) | Programmatic access to the built-in schematic generator and module auto-registration planner. | | GenerateOptions | Type for programmatic generator options. withTest was removed; use withSliceTest for module and resource slice tests. | | GenerateResult | Type for generator results, including changed files, dry-run plan entries, module wiring metadata, and next-step hints. | | GeneratePlanEntry / GeneratePlanAction | Types for dry-run and write-plan path actions returned by runGenerateCommand(...). | | GeneratedFile | Type describing generated file paths and in-memory content before writes. | | GeneratorKind | Union type of all supported generator types (e.g., 'controller', 'service'). | | ModuleRegistration | Type describing controller, provider, or middleware module wiring metadata from generator runs. | | inspectUsage() | Returns the current fluo inspect usage text for help surfaces and tests. | | runInspectCommand(argv, options?) | Programmatic access to inspect orchestration, compiled route JSON/report emission, and Studio Mermaid delegation. | | InspectCommandRuntimeOptions | Type for runInspectCommand(...) and runCli(...) inspect runtime overrides such as cwd, streams, prompts, and Studio renderer loading. | | typegenUsage() | Returns the current fluo typegen usage text for help surfaces and tests. | | TYPEGEN_EXIT_CODES | Stable SUCCESS, ERROR, MISSING, STALE, MALFORMED, and UNSUPPORTED_VERSION process codes used by typegen automation. | | runTypegenCommand(argv, options?) | Programmatic access to bootstrap-resolved React page generation, non-mutating checks, and bounded watch mode. | | TypegenCommandRuntimeOptions | Type for runTypegenCommand(...) and runCli(...) typegen runtime overrides such as cwd, streams, and tooling module loading. Supplying loadReactTypegenModules selects caller-process generation with those namespaces; omitting it uses short-lived generation children. |

Programmatic entry points preserve caller process ownership. runCli(...), runNewCommand(...), runInspectCommand(...), and runTypegenCommand(...) return numeric exit codes instead of calling process.exit(...); typegen callers can compare those results with TYPEGEN_EXIT_CODES. Prompt cancellation resolves as exit code 0 through the command runner, and setup actions such as dependency installation or git initialization only run when the resolved fluo new options request them. runGenerateCommand(...) returns a structured GenerateResult; pass dryRun: true to preview generated file and module-wiring actions without writing files. Caller-supplied prompt hooks can throw CliPromptCancelledError from the public package entrypoint to express normal cancellation without depending on CLI-internal files.

Related Packages

  • @fluojs/runtime: The underlying engine that produces inspection snapshots during bootstrap-safe runtime inspection.
  • @fluojs/studio: The web-based UI for viewing inspect --json exports and the canonical renderer used by inspect --mermaid.
  • @fluojs/testing: Used by generated test templates for integration and E2E testing.
  • @fluojs/vite: Provides the generated starter Vite decorator transform plugin.
  • Canonical Runtime Package Matrix: The source of truth for official runtime/package combinations.

Example Sources