Skip to content

Repository files navigation

Nova

npm version npm downloads node license

Nova is an extensible Next.js development toolkit and CLI for creating, generating, managing, and maintaining production-ready applications.

Run one command, answer a few prompts, and get a fully configured Next.js application with a modern architecture, authentication, internationalization, forms, validation, API infrastructure, testing, UI frameworks, CMS integrations, and optional plugins — instead of starting from an empty App Router project and re-inventing the same infrastructure on every new codebase.

npx @darkalpha/nova my-app

Or install Nova globally:

npm install -g @darkalpha/nova

Then use the nova command:

nova my-app

Already have a project and just want to bolt on a feature later? Nova can do that too:

cd my-app
nova add prisma redis

Not sure what a plugin actually adds before you commit to it? Ask the CLI directly:

nova plugins prisma

Table of Contents


Install

Use it directly with npx — no install required:

npx @darkalpha/nova my-app

Or install Nova globally:

npm install -g @darkalpha/nova

Then use the nova command:

nova my-app

Requirements: Node.js >=18.18.0, and one of npm, pnpm, yarn, or bun installed on your machine (you choose which one Nova uses per-project during setup).


Quick Start

# Scaffold a new app
npx @darkalpha/nova my-app

cd my-app
cp .env.example .env

npm install   # only needed if you skipped install during setup
npm run dev

Open http://localhost:3000 — your app is running with locale-aware routing (/en, /fa), a working auth reference implementation with token rotation, and a typed API layer ready to point at your backend via API_BASE_URL.

You can also run Nova with no arguments for a fully interactive setup:

nova

Nova will prompt you for the project name, package manager, UI library, and any optional add-ons before generating anything — and will refuse to generate a plugin combination it knows is invalid (see Plugins and Add-ons) before writing a single file.

Later, if you decide you need another feature, run nova add from inside the project instead of regenerating it — see Adding Features to an Existing Project. If you're not sure what a plugin does first, run nova plugins <name> — see Inspecting Plugins.


Why Nova?

Most Next.js starters provide a basic application and leave important architectural decisions to the developer: How should auth token refresh work? Where do validation schemas live? How is the API client structured? Should translations be typed? Nova answers these questions up front so you don't have to relitigate them on every new project.

Batteries Included

Start with a complete architecture instead of an empty App Router project. Depending on your configuration, Nova can provide:

  • Authentication
  • Token refresh and rotation
  • Internationalization
  • Form validation
  • API infrastructure
  • Database access
  • Data fetching
  • State management
  • Testing
  • Observability
  • Security
  • Docker support

Composable Plugins

Nova is designed around a plugin and add-on architecture.

Choose only the features your project needs.

For example:

Next.js
  +
shadcn/ui
  +
Prisma
  +
Better Auth
  +
Redis
  +
Strapi
  +
TanStack Query

Or keep your application minimal:

Next.js
  +
shadcn/ui
  +
TypeScript

And if you didn't pick everything up front, nova add lets you layer plugins onto an existing project whenever the need comes up.

Swappable Technologies

Nova is designed to avoid unnecessary vendor lock-in.

You can choose between multiple technologies for different parts of your application.

Examples include:

  • shadcn/ui
  • Material UI
  • Chakra UI
  • Ant Design
  • Mantine
  • HeroUI
  • DaisyUI
  • Headless UI

And:

  • Prisma or Drizzle (mutually exclusive — pick one)
  • Redis
  • Strapi
  • OpenAPI
  • TanStack Query
  • Zustand
  • React Hook Form
  • Zod

Documented Architecture

Generated projects include documentation explaining the architecture and important design decisions.

Documentation may include:

docs/
├── folder-structure.md
├── authentication.md
├── api-layer.md
├── forms.md
├── adding-a-feature.md
├── nova-add-command.md
└── ...

Major modules also ship their own local README.md (for example src/lib/api/, src/lib/auth/, src/features/auth/, src/i18n/) so the reasoning behind non-obvious decisions travels with the code itself, not just in a top-level doc nobody re-reads six months later.

Not a black box

Everything Nova generates is plain, ordinary Next.js/React/TypeScript code that you own outright the moment it's written to disk. There's no runtime dependency on the nova package inside your generated app, no telemetry, and no CLI daemon watching your project. If you want to rip out a feature, delete the files and the corresponding dependency — that's it.

Predictable, validated generation

Nova's own generator (not the apps it produces) is built to fail safely and predictably:

  • Plugin selections are validated before any file is written, using declared metadata about what conflicts with what and what requires what.
  • Every generation runs as a planned sequence of operations; if something fails partway through, Nova rolls back the partially-created project directory instead of leaving a broken half-generated app on disk.
  • Every feature's package.json footprint (dependencies, devDependencies, scripts) is declared exactly once and consumed by both full generation and nova add, so the two paths can never quietly disagree about what a plugin installs.

See Generator Internals for details.


Features

Nova provides a flexible foundation for building modern Next.js applications.

  • Next.js App Router
  • React and TypeScript
  • Feature-first architecture
  • Internationalization with RTL support
  • Authentication and token rotation
  • Better Auth integration
  • React Hook Form and Zod
  • Type-safe API infrastructure
  • Multiple UI frameworks
  • Headless CMS integrations
  • Database integrations (Prisma or Drizzle)
  • Data fetching and state management
  • Testing tools
  • Storybook
  • Docker and Docker Compose
  • PWA support
  • Email and local SMTP development
  • Rich text editing
  • Animations
  • Charts and data visualization
  • Observability and error tracking
  • Security features
  • Health and readiness endpoints
  • Extensible plugin architecture with declared metadata and validation
  • Incremental feature addition to existing projects (nova add)
  • Plugin introspection from the CLI (nova plugins)

Creating a Project

When creating a project, Nova guides you through the setup process.

You can configure:

  1. Project name
  2. Package manager
  3. UI framework
  4. Optional plugins and add-ons
  5. Dependency installation
  6. Git initialization

Before any files are written, Nova validates your plugin selection against each plugin's declared constraints (requires/conflicts/supported UI libraries) — for example, Prisma and Drizzle cannot both be enabled, since they'd both try to own DATABASE_URL-backed schema and migrations. If something's incompatible, you'll get a clear error instead of a partially broken project.

After generation:

cd my-app

Create your environment file:

cp .env.example .env

Install dependencies if you skipped installation during setup:

npm install

Start the development server:

npm run dev

Your application is now ready for development.


CLI Usage

Nova supports project creation, plugin management, and safe project maintenance.

nova [project-name] [options]
nova add <feature...> [options]
nova plugins [feature]
nova remove <plugin...> [--path <dir>] [--force]
nova init | info | doctor | validate | clean | diff [--path <dir>]
nova status [--path <dir>] [--json]
nova upgrade | repair [--path <dir>]
nova list [search-term]
nova search <term>

Options

-h, --help       Show this help message
-v, --version    Print the installed version

Common options

--path, -p <dir>   Target project directory (default: current directory)
--force, -f        Overwrite files that already exist instead of skipping them
--json             Print structured output (maintenance and discovery commands)

Examples

nova my-app
nova
nova add prisma redis
nova add drizzle
nova add tanstack-query --path ./my-app
nova plugins
nova plugins drizzle
nova init --path ./my-app
nova doctor --path ./my-app
nova remove drizzle --path ./my-app
nova search database
nova status --path ./my-app --json

nova init creates .nova.json, Nova's project manifest. New projects and nova add create it automatically. It records selected plugins and lets remove, validate, doctor, and upgrade operate without guessing from user source files. remove intentionally preserves generated files; it removes only tracked package entries and manifest metadata.

Project names must contain only:

  • Lowercase letters
  • Numbers
  • Dashes
  • Underscores

The project name follows:

^[a-z0-9-_]+$

Invalid names (spaces, path separators, uppercase letters, ..) are rejected before any files are written, whether passed as a CLI argument or typed into the interactive prompt.

Running in CI

When CI is set in the environment, Nova automatically skips the "install dependencies now?" confirmation prompt so the process can run non-interactively, and its internal logger switches to a quieter, CI-friendly output level:

CI=true npx @darkalpha/nova my-app

You'll still want to run the package manager's install command afterward as a separate CI step.


Project Maintenance

Nova tracks its own changes in a small .nova.json manifest. It is created automatically for new projects and after nova add; use nova init once to adopt an existing compatible project. The manifest records the package manager, UI library, and plugins selected through Nova. It does not claim ownership of handwritten application code.

Command Purpose
nova status Show project identity and a health summary.
nova doctor Check Node.js, package metadata, required files, lockfile presence, and plugin validation.
nova validate Run plugin validations without writing project files.
nova info Print project, framework, package-manager, UI, and tracked-plugin details.
nova list --installed List only plugins recorded in the target project's manifest.
nova remove <plugin> Remove tracked package entries and manifest metadata; generated files remain intact.
nova upgrade Reconcile tracked plugin dependency declarations with Nova's current manifests.
nova repair Repair deterministic metadata drift and a missing .env.example.
nova diff Report detectable baseline drift, such as missing baseline files or unavailable plugin metadata.
nova clean [--dry-run] Remove generated caches such as .next, .turbo, and Nova cache files.

Maintenance commands accept --path <dir>. Discovery and maintenance commands also support --json, making them suitable for CI checks:

nova doctor --path ./my-app --json
nova status --path ./my-app --json
nova clean --path ./my-app --dry-run --json

nova remove is intentionally conservative: it never deletes generated source or configuration files, because they may have been edited after generation. Review those files separately after removing a plugin, then run your package manager's install command.


Adding Features to an Existing Project (nova add)

nova add copies an addon's files into a project that already exists on disk and merges its dependencies/scripts into that project's package.json. It's the incremental counterpart to running nova <name> from scratch — useful when you decide you want Prisma, Drizzle, Redis, Sentry, or any other plugin after the fact, without regenerating the whole app.

Usage

nova add <feature...> [options]

If you omit the feature list, you get an interactive multiselect with the same options as the initial generator prompt.

Options

Flag Description
--path, -p <dir> Target project directory (default: current directory)
--force, -f Overwrite files that already exist instead of skipping them

Examples

# Run from inside the project
cd my-app
nova add prisma redis

# Or target another directory
nova add tanstack-query --path ./my-app

# Re-copy files even if they already exist
nova add sentry --force

Feature names accept either the camelCase key (tanstackQuery) or the kebab-case addon folder name (tanstack-query) — both resolve to the same feature.

What it does

  1. Reads the target's package.json (the directory must already be a Node/Next.js project — nova add never creates a project itself).
  2. Detects whether the project keeps its code under src/ or at the project root, and copies each addon's files accordingly — any file the addon authors under src/... is copied without the src/ prefix when the target project has no src/ directory.
  3. Creates whatever intermediate folders are needed (lib/redis, lib/db, emails/, .storybook/, etc.) — nothing needs to exist beforehand.
  4. Merges the addon's dependencies / devDependencies into package.json (a newer pinned version wins), and adds any new scripts entries without overwriting a script you've already customized. These additions come from a single shared source (src/featureContributions.ts) — the exact same data full generation uses, so a feature installs identically whether you selected it up front or added it later.
  5. Skips files that already exist in the project, so re-running nova add is safe — pass --force if you deliberately want the shipped template version back.
  6. Validates the requested selection against declared plugin constraints (requires/conflicts) before writing anything — e.g. nova add drizzle fails fast with an actionable error if Prisma was already requested in the same command.

Plugin lifecycle hooks and any plugin-declared templates, patches, environment variables, and documentation run as part of nova add. Successful additions are recorded in .nova.json for safe later maintenance.

Known limitations

  • Switching UI libraries (mui, chakra, ant, ...) isn't supported via nova add — that requires rewriting the app's provider tree, which is only handled during initial generation.
  • Config-file wiring that the generator applies automatically at scaffold time (e.g. output: "standalone" in next.config.mjs for Docker, or wrapping next.config.mjs for Sentry/PWA/Bundle Analyzer) is not re-applied by nova add. Files are copied and dependencies are added, but you may need to wire a couple of lines into next.config.mjs by hand — check that feature's docs/*.md in the generated project for the exact snippet.
  • Conflict detection for nova add currently only checks the features requested in that single command against each other — it does not yet cross-reference plugins already installed in the target project (tracked in .nova.json) before writing files. Running nova add drizzle against a project that already has Prisma installed will not be automatically blocked; check nova info/nova plugins for existing plugins before adding a conflicting one.
  • Run your package manager's install command afterwards; nova add only updates package.json, it doesn't install anything.

Full reference: docs/nova-add-command.md inside any generated project.


Inspecting Plugins (nova plugins)

Before adding a plugin — or just to understand what one already in your project does — you can ask the CLI directly instead of reading source files:

nova plugins

Lists every available plugin with its description, any requires/conflicts constraints, which UI libraries it's restricted to (if any), and a summary of what it adds to package.json.

nova plugins drizzle

Shows the same detail for a single plugin.

This command reads from the exact same metadata (src/generator/pluginMetadata.ts) and package contribution data (src/featureContributions.ts) that generation itself uses — there's nothing plugin-specific to keep in sync by hand, and the output can never drift from what nova <name> or nova add actually do.


What's Included

Every generated project includes a production-oriented foundation.

Framework

  • Next.js App Router
  • React
  • TypeScript
  • Strict TypeScript configuration

Styling

  • Tailwind CSS
  • shadcn/ui-style primitives
  • Optional alternative UI frameworks

Internationalization

  • next-intl
  • Locale-prefixed routing
  • English and Persian locales by default
  • RTL-ready architecture

Authentication

The base architecture supports:

  • HTTP-only cookies
  • Access and refresh tokens
  • Refresh token rotation
  • Concurrent refresh request coalescing

Better Auth can also be selected as an alternative authentication solution.

Forms

  • React Hook Form
  • Zod
  • Shared validation schemas
  • Client and server validation

API Layer

Generated applications can include typed API clients with:

  • Retries
  • Timeouts
  • Interceptors
  • Authentication handling
  • Automatic token refresh

Theming

  • Light mode
  • Dark mode
  • System theme
  • next-themes

Plugins and Add-ons

Nova's plugin ecosystem is designed to be composable.

The available options depend on the version of Nova you are using. Every plugin listed below can be selected either during initial generation, or added afterward with nova add (see Adding Features to an Existing Project), and inspected at any time with nova plugins (see Inspecting Plugins).

Data and Backend

  • Prisma ORM
  • Drizzle ORM
  • Better Auth
  • Redis
  • Strapi CMS
  • OpenAPI typed client

Data Fetching and State

  • TanStack Query
  • TanStack Table
  • Zustand
  • MSW API mocking

Testing

  • Vitest
  • Playwright
  • Cypress
  • Storybook

Content and Communication

  • React Email
  • Mailpit
  • Tiptap rich text editor

Infrastructure and Operations

  • Docker
  • Docker Compose
  • Husky
  • lint-staged
  • PWA
  • Bundle Analyzer
  • Sentry
  • Health and readiness endpoints
  • Security headers

Design and UX

  • Design System
  • Animations
  • Recharts

Plugin summary table

Category Options
Data and Backend Prisma ORM, Drizzle ORM, Better Auth, Redis, Strapi CMS, OpenAPI typed client
Data Fetching and State TanStack Query, TanStack Table, Zustand, MSW API mocking
Testing Vitest, Playwright, Cypress, Storybook
Content and Communication React Email, Mailpit, Tiptap rich text editor
Infrastructure and Operations Docker, Docker Compose, Husky + lint-staged, PWA, Bundle Analyzer, Sentry, Health/readiness endpoints, Security headers
Design and UX Design System, Animations, Recharts

Each plugin is a self-contained overlay under templates/addons/<name>. Enabling it copies its files on top of the base template and automatically wires in the required dependencies, scripts, and environment variables — there's no manual wiring step after generation. The same addon folders back nova add, so a feature behaves identically whether you selected it at scaffold time or added it later.

Each plugin also carries declarative metadata (src/generator/pluginMetadata.ts) describing its name, description, and any requires/conflicts/supportedUI constraints. Nova validates your full selection against this metadata before writing any files, so an incompatible combination fails fast with a clear error rather than partway through generation.

Known plugin conflicts

Plugin A Plugin B Reason
Prisma ORM (prisma) Drizzle ORM (drizzle) Both own DATABASE_URL-backed schema/migrations and would contribute colliding db:* scripts — pick one ORM per project.

If you need to evaluate both, generate two separate projects (or use nova add against two throwaway scaffolds) rather than trying to enable both in one.


UI Frameworks

Nova supports multiple UI approaches.

shadcn/ui

The default UI approach.

Provides:

  • Tailwind CSS
  • Radix UI primitives
  • Source-based components
  • Accessible components

Select:

shadcn

Material UI

Provides:

  • MUI components
  • Theme configuration
  • App Router integration
  • Cache provider

Select:

mui

Chakra UI

Provides:

  • Chakra provider
  • Theme configuration
  • Component examples

Select:

chakra

Ant Design

Provides:

  • Ant Design components
  • ConfigProvider
  • Theme support
  • Component examples

Select:

ant

Mantine

Provides:

  • Mantine provider
  • Theme configuration
  • Mantine components

Select:

mantine

HeroUI

Provides:

  • HeroUI provider
  • Modern component primitives
  • Theme integration

Select:

hero

DaisyUI

Provides:

  • DaisyUI
  • Tailwind integration
  • Theme configuration

Select:

daisy

Headless UI

Provides:

  • Headless UI primitives
  • Tailwind integration
  • Heroicons

Select:

headless

UI framework summary table

Select Library Notes
shadcn shadcn/ui (default) Tailwind CSS, Radix UI primitives, source-based, accessible components
mui Material UI MUI components, theme configuration, App Router cache provider
chakra Chakra UI Chakra provider, theme configuration, component examples
ant Ant Design Ant Design components, ConfigProvider, theme support
mantine Mantine Mantine provider, theme configuration, components
hero HeroUI HeroUI provider, modern component primitives
daisy DaisyUI Tailwind plugin, theme presets
headless Headless UI Headless UI primitives, Tailwind integration, Heroicons

Note: switching UI library after generation is only supported by re-generating the project — nova add cannot swap the provider tree for you (see Known limitations above).


Project Architecture

Generated applications follow a feature-first architecture.

src/
├── app/
│   └── [locale]/
│
├── components/
│   ├── ui/
│   ├── layout/
│   ├── forms/
│   └── providers/
│
├── features/
│   ├── auth/
│   └── dashboard/
│
├── lib/
│   ├── api/
│   ├── auth/
│   ├── prisma/
│   ├── db/
│   ├── validations/
│   ├── cache/
│   └── helpers/
│
├── services/
│
├── utils/
│
├── i18n/
│
└── messages/
    ├── en/
    └── fa/

Full folder layout

For reference, the complete generated src/ tree (with every optional folder included) looks like this:

src/
├── app/            # App Router routes, grouped under [locale]
├── actions/         # Cross-cutting Server Actions not tied to one feature
├── components/
│   ├── ui/           # shadcn/ui-style primitives (button, input, card...)
│   ├── common/        # Small generic UI (spinner, empty-state)
│   ├── layout/         # Header, footer, theme/locale switchers
│   ├── forms/           # Form wrapper, field, error components
│   └── providers/        # Client-side provider wrappers (theme, etc)
├── features/         # Feature-first modules (auth, dashboard, profile...)
│   └── <feature>/
│       ├── components/
│       ├── hooks/
│       ├── actions/
│       ├── schemas/
│       └── types/
├── hooks/            # App-wide reusable hooks
├── lib/
│   ├── api/            # Type-safe fetch client(s) + interceptors
│   ├── auth/            # Token rotation, session helpers
│   ├── prisma/           # Prisma client singleton (if enabled)
│   ├── db/                # Drizzle client + schema (if enabled instead of Prisma)
│   ├── validations/       # Shared zod schemas
│   ├── constants/          # App-wide constants
│   ├── helpers/             # cn() and other small helpers
│   └── cache/                # Cache tag registry
├── services/          # Business-shaped API calls, built on lib/api
├── utils/             # Pure utility functions (formatting, arrays, strings)
├── config/            # Site config, validated env
├── providers/         # Composition root for all client providers
├── styles/            # Global CSS
├── types/             # Shared TypeScript types
├── messages/           # next-intl translation JSON per locale
└── i18n/               # next-intl routing/navigation/request config

Note: if a project doesn't use a src/ directory (e.g. create-next-app with the "Use src/ directory?" prompt declined), nova add detects this automatically and remaps addon files to the project root instead — see Adding Features to an Existing Project.

Architecture Rules

Features

Own everything specific to a product area.

features/
└── users/
    ├── components/
    ├── actions/
    ├── schemas/
    └── types/

Lib

Contains cross-cutting infrastructure.

Examples:

  • API
  • Authentication
  • Database
  • Caching
  • Validation

Components

Contains reusable UI without business-specific logic.

Services

Contains business-oriented operations built on top of the API layer.

Features should call services rather than directly calling the API transport layer.

This keeps business logic independent from the underlying HTTP implementation.

Rule of thumb

  • features/ owns anything specific to one product area — start here for new work.
  • lib/ owns cross-cutting infrastructure (HTTP, auth, DB).
  • components/ owns UI with no business logic.
  • services/ is the only place allowed to call lib/api directly for business data; features call services, not api, so the transport can change independently.

Repository Structure

The Nova repository itself is organized as follows:

.
├── bin/
│   └── nova.js
│
├── src/
│   ├── index.ts
│   ├── add.ts
│   ├── prompts.ts
│   ├── generator.ts
│   ├── generator/
│   │   ├── context.ts
│   │   ├── logger.ts
│   │   ├── errors.ts
│   │   ├── hooks.ts
│   │   ├── operations.ts
│   │   ├── pluginMetadata.ts
│   │   ├── pluginInfo.ts
│   │   ├── validators.ts
│   │   ├── verifyManifestSync.ts
│   │   └── patchers/
│   │       ├── types.ts
│   │       ├── nextConfigPatcher.ts
│   │       ├── providerPatcher.ts
│   │       ├── middlewarePatcher.ts
│   │       └── index.ts
│   ├── plugin/
│   │   ├── types.ts
│   │   ├── registry.ts
│   │   ├── legacyAdapter.ts
│   │   ├── dependencyGraph.ts
│   │   ├── applyTemplates.ts
│   │   ├── applyPatches.ts
│   │   ├── applyEnv.ts
│   │   ├── applyDocs.ts
│   │   ├── runHooks.ts
│   │   ├── validate.ts
│   │   ├── prompts.ts
│   │   └── nativePlugins/
│   │       ├── prisma.ts
│   │       ├── drizzle.ts
│   │       ├── dockerCompose.ts
│   │       └── securityHeaders.ts
│   ├── addonRegistry.ts
│   ├── featureContributions.ts
│   ├── featurePackageAdditions.ts
│   ├── packageMerge.ts
│   ├── projectStructure.ts
│   ├── packageManifest.ts
│   └── types.ts
│
├── packages/
│   └── core/
│       └── src/
│           ├── fs.ts
│           ├── pmCommands.ts
│           ├── prompts.ts
│           └── logger.ts
│
├── templates/
│   ├── base/
│   ├── addons/
│   └── ui/
│
├── scripts/
│   ├── smoke-test.mjs
│   └── verify-package-manifest-sync.ts
│
└── docs/
    └── migration/
  • bin/nova.js — the published CLI entrypoint (used by npm start or npx).
  • src/index.ts — the CLI entrypoint; dispatches between the nova [project-name] generation flow, nova add <feature...>, and nova plugins [feature].
  • src/generator/index.ts — high-level generation orchestration: validates the plugin selection, builds an operation plan (copy base template, copy selected addons, copy UI overlay), executes it with rollback on failure, writes package.json, and runs the config patchers and remaining single-purpose helpers (Storybook preview, DaisyUI Tailwind patch, README, lint-staged, docker-compose).
  • src/generator/ — generator internals, one concern per module (see Generator Internals below).
  • src/plugin/ — the native plugin engine (PluginManifest, registry, dependency graph, template/patch/env/doc application, lifecycle hooks). New plugins with richer contributions (self-declared env vars, docs, patches, prompts) are authored here under nativePlugins/, e.g. drizzle.ts.
  • src/add.ts — the nova add implementation: copies addon files into an existing project (with src/-prefix remapping) and merges dependencies/scripts into its package.json, running the same plugin engine (dependency graph, validation, templates, patches, env, docs) full generation uses.
  • src/addonRegistry.ts — single source of truth mapping each FeatureKey to its addon folder name, shared by generator/index.ts, add.ts, and pluginInfo.ts.
  • src/featureContributions.ts — single source of truth for what each feature contributes to package.json (dependencies/devDependencies/scripts), consumed by full generation, nova add, and nova plugins.
  • src/featurePackageAdditions.ts — thin backward-compatible re-export of featureContributions.ts for any code still importing the old name.
  • src/packageMerge.ts — merges an addon's dependency/script additions into an existing project's package.json without clobbering scripts you've already customized.
  • src/projectStructure.ts — detects whether a target project uses a src/ directory and remaps addon file paths accordingly for nova add.
  • src/prompts.ts — the interactive @clack/prompts-based CLI flow, shared between initial generation and the nova add feature multiselect.
  • src/packageManifest.ts — builds a brand-new generated project's package.json (scripts + dependencies) from the selected feature set and featureContributions.ts.
  • packages/core/@nova/core, a framework-agnostic workspace package with zero feature-specific logic: template/filesystem copying, package-manager command resolution, prompt cancel-handling, and logging. Built once and bundled into the CLI at publish time.
  • templates/base/ — the complete base Next.js App Router project that every generated app starts from.
  • templates/addons/ — one folder per optional feature; each overlays files on top of templates/base when enabled, and is reused verbatim by nova add.
  • templates/ui/ — one folder per alternative UI library, overlaid last so provider wiring and examples land correctly.
  • scripts/verify-package-manifest-sync.ts — regression guard confirming packageManifest.ts's output for every feature matches featureContributions.ts; runs in CI and prepublishOnly.

Generator Internals

Nova's own generator (as distinct from the apps it produces) went through a Phase 1 hardening pass focused on making it robust, predictable, and easy to extend as more plugins are added. The pieces:

Module Responsibility
src/generator/context.ts Builds a single, frozen GeneratorContext (paths, resolved UI library, logger, dry-run flag) threaded through generation instead of recomputing values or relying on mutable module state.
src/generator/logger.ts A small structured logger (debug/verbose/info/success/warn/error/step) with a CI-aware minimum log level, replacing scattered console.log calls.
src/generator/errors.ts Typed error classes (InvalidProjectNameError, DirectoryNotEmptyError, PluginConflictError, MissingPluginDependencyError, OperationExecutionError, ...) so failures are identifiable and carry actionable messages instead of generic Errors.
src/generator/pluginMetadata.ts Declarative per-plugin metadata: name, description, requires, conflicts, supportedUI. The single place new cross-plugin constraints get declared.
src/generator/validators.ts validatePluginSelection() checks a feature selection against pluginMetadata.ts before any files are written.
src/generator/pluginInfo.ts Joins addonRegistry.ts, pluginMetadata.ts, and featureContributions.ts into one queryable view per plugin; powers nova plugins.
src/generator/operations.ts Represents file operations (mkdir, copyDir, writeFile, writeJson) as plain data (an OperationPlan) rather than closures, executed sequentially by executePlan(). On failure, rollbackTargetDir() removes the partially-generated project directory instead of leaving it half-built.
src/generator/hooks.ts A minimal HookRegistry supporting beforeGenerate / afterGenerate / beforePlugin / afterPlugin lifecycle hooks, so future commands can observe generation without editing generator internals.
src/generator/patchers/*.ts Config patching for next.config.mjs, the provider tree (app-providers.tsx), and middleware.ts, expressed as ordered, declarative contribution lists (feature flag → transform) instead of inline if chains in generator.ts.
src/generator/verifyManifestSync.ts Regression guard confirming buildPackageJson()'s output matches featureContributions.ts per feature — exercised by npm run verify:manifest-sync.
src/plugin/* The richer, self-describing plugin engine: PluginManifest (templates/patches/env/docs/prompts/hooks), PluginRegistry, resolveDependencyGraph() (requires/conflicts/cycles), and applier modules invoked by both full generation and nova add. New plugins with non-trivial contributions (like Drizzle's env entry) are authored here.

Design principles carried through all of the above:

  • Single source of truth per concern. A feature's package.json footprint lives in exactly one place (featureContributions.ts); a plugin's cross-plugin constraints live in exactly one place (pluginMetadata.ts); config patches live in exactly one place per target file (patchers/*.ts).
  • Validate before writing. Plugin selection is checked against declared constraints before the target directory is even created.
  • Plan, then execute. File operations are built as a data structure first and executed second, which is what makes rollback-on-failure possible and will make a future --dry-run flag a small addition rather than a rewrite.
  • No behavior change for existing users. Every refactor in this pass preserves byte-identical generated output for every existing feature/UI combination — verified against scripts/smoke-test.mjs.

This foundation is what a future nova doctor, nova upgrade, or nova remove command would build on: pluginInfo.ts already answers "what does this plugin touch," pluginMetadata.ts already answers "what does this plugin require or conflict with," and the hook registry already gives a place to add cross-cutting behavior without touching generator.ts again.


Add-on Architecture

Nova uses an overlay-based add-on architecture.

Each add-on lives under:

templates/addons/<name>

When an add-on is enabled, its files are applied on top of the base template.

For example:

templates/
├── base/
│
└── addons/
    ├── prisma/
    ├── drizzle/
    ├── redis/
    ├── strapi/
    ├── sentry/
    └── playwright/

This allows features to be composed without requiring complex template generation logic.

Add-on files can intentionally override base files when required.

Examples include:

  • middleware.ts
  • next.config.mjs
  • app-providers.tsx

The goal is to keep each plugin isolated, explicit, and easy to maintain. There is no deep merge logic for file overlays — an overlay file simply replaces the base file at the same path, so it's always obvious, by reading the addon folder, exactly what it changes. (Config patching — as opposed to file overlaying — is handled separately and declaratively; see Generator Internals.)

UI library overlays (templates/ui/*) are applied last, after all selected addons, so provider wiring (e.g. wrapping <AppProviders> with <MuiProvider> or <ChakraAppProvider>) is consistent regardless of which other addons were selected.

The exact same templates/addons/<name> folders power nova add: when adding a feature to an existing project, Nova copies the same files, only remapping the src/ prefix if the target project doesn't use a src/ directory. The one thing nova add does not replay is the config-patching step (next.config.mjs, middleware.ts, provider-tree wiring) that generateProject performs automatically via src/generator/patchers/*.ts for legacy contributions — plugins migrated to the native src/plugin/ engine (like Drizzle's env contribution, or Security Headers' patches) do replay through nova add, since applyPluginPatches/appendPluginEnvContributions/writePluginDocs/applyPluginTemplates are shared by both generateProject and addFeaturesToProject.


Example Configurations

Minimal marketing or landing site

UI:      shadcn
Plugins: (none)

SaaS app with a Postgres backend (Prisma)

UI:      shadcn
Plugins: Prisma, Better Auth, TanStack Query, Sentry, Docker

SaaS app with a Postgres backend (Drizzle)

UI:      shadcn
Plugins: Drizzle, Better Auth, TanStack Query, Sentry, Docker

Content-driven site backed by a headless CMS

UI:      mantine
Plugins: Strapi CMS, React Email, Mailpit

Internal dashboard or admin tool

UI:      mui
Plugins: TanStack Query, TanStack Table, Recharts, Zustand

Fully-loaded reference build (mirrors the CLI's own smoke test)

UI:      mui
Plugins: Prisma, Better Auth, TanStack Query, Cypress, Vitest, Storybook,
         Docker, Husky, PWA, Bundle Analyzer, Zustand, MSW, React Email,
         Playwright, Sentry, OpenAPI

Started minimal, grew into a SaaS app (using nova add)

nova my-app                     # UI: shadcn, no plugins
cd my-app
nova add drizzle betterAuth     # add DB + auth later
nova add tanstackQuery sentry   # add data fetching + monitoring later

Not sure what a plugin does before adding it

nova plugins prisma
nova plugins drizzle
nova add prisma sentry

Documentation Generated With Your Project

Every scaffolded app ships with its own docs/ folder so the architecture is explained in place, not just in this README:

docs/
├── folder-structure.md
├── authentication.md
├── api-layer.md
├── forms.md
├── validation.md
├── server-actions.md
├── internationalization.md
├── environment-variables.md
├── deployment.md
├── adding-a-feature.md
├── nova-add-command.md
├── drizzle.md            (if Drizzle is selected)
└── ...

Plus module-local README.md files inside src/lib/api/, src/lib/auth/, src/lib/db/ (if Drizzle is selected), src/features/auth/, and src/i18n/ explaining the reasoning behind non-obvious implementation choices — for example why token refresh coalesces concurrent requests, or why the root layout.tsx is intentionally minimal.

docs/nova-add-command.md specifically documents the nova add workflow from inside a generated project — usage, options, what it does under the hood, and its known limitations — so the reasoning travels with the code even if this README changes later.


Environment Variables

Generated projects include a .env.example covering every variable the scaffold understands, grouped by category:

  • App — public URL/name, safe to expose via NEXT_PUBLIC_*
  • DatabaseDATABASE_URL, used by Prisma or Drizzle, whichever is enabled
  • Authentication — token secrets/TTLs for the custom rotation system, or Better Auth secrets if that module is enabled
  • External APIs — base URL/timeout for src/lib/api
  • Sentry — DSNs and trace sample rates, when Sentry is enabled

.env itself is never committed — only .env.example is tracked in generated projects. When you add a new environment variable, add it to both .env and .env.example together, and to src/config/env.ts if the app should fail fast at boot when it's missing.

When a plugin is added later via nova add, remember to also copy over any new variables its docs/*.md mentions — for plugins on the native src/plugin/ engine (e.g. Drizzle), nova add appends any missing keys to .env.example automatically via appendPluginEnvContributions; for legacy addons this may still require a manual copy.


Deployment

Vercel (recommended default) — push to a connected git repository; Vercel auto-detects Next.js. Set the environment variables from .env.example in the Vercel project settings.

Docker (if selected during generation)

docker build -t app .
docker run -p 3000:3000 --env-file .env app

The generated Dockerfile uses a multi-stage build (deps → build → runtime) and Next.js output: "standalone" for a minimal production image.

Every variable in .env.example must be set in production — missing required variables fail fast at boot if you're using the generated src/config/env.ts validation.

Provider-specific deployment tooling (Vercel/AWS/Cloudflare/Railway/Render config generators, nova deploy) is planned but not yet implemented — see Roadmap.


FAQ

Does Nova lock me into a specific backend? No. The generated API layer (src/lib/api) is a thin, typed fetch wrapper you point at any backend via API_BASE_URL. Nothing in the generated code assumes a specific server framework or database.

Should I pick Prisma or Drizzle? Both are optional and mutually exclusive — Nova will reject a selection with both enabled. Pick Prisma if you want Prisma Studio, its generated client, and its larger example ecosystem; pick Drizzle if you want a smaller runtime, SQL-first schema definitions, and no separate client-generation step. See nova plugins drizzle and nova plugins prisma, or each plugin's docs/*.md in a generated project, for the full tradeoff writeup.

Can I remove a feature after generating the project? Yes. Everything Nova writes is plain, readable TypeScript/React you own outright — delete the files and the corresponding dependency entries in package.json. There's no hidden runtime tying the app back to the nova CLI package. nova remove <plugin> automates the package.json/manifest side of this.

Can I add a feature I skipped, without regenerating the whole project? Yes — that's exactly what nova add <feature...> is for. Run it from inside the project (or point it at another directory with --path), and it copies the addon's files and merges its dependencies/scripts into your existing package.json. See Adding Features to an Existing Project.

How do I know what a plugin will actually add before I run it? Run nova plugins <feature>. It shows the plugin's description, any requires/conflicts constraints, supported UI libraries, and a summary of its package.json footprint — all sourced from the same data generation itself uses.

Does nova add overwrite files I've already customized? No, by default it skips any file that already exists at the destination. Pass --force if you deliberately want the shipped template version back, overwriting your local changes.

What happens if generation fails partway through? Nova rolls back: since it only ever generates into a directory it just created (or confirmed was empty), a failed generation removes that directory rather than leaving a half-built project behind. This is also what happens if you request an invalid combination like prisma + drizzle — the error is raised before any file is written, so nothing is left on disk.

Does Nova modify my project after generation, or phone home? No. Nova runs once, at generation time (or once per nova add invocation), entirely locally. There's no CLI daemon, no telemetry, and no ongoing dependency on nova/@darkalpha/nova inside the generated app.

Can I use Nova in CI? Yes — pass a project name as a CLI argument and set CI=true in the environment; Nova will skip the interactive "install now?" confirmation, use a quieter log level, and generate non-interactively.

Which package managers are supported? pnpm, npm, yarn, and bun. Whichever you choose during setup is used consistently for the generated scripts and documented install/dev commands in the project's own README.md — including plugin-contributed scripts like Drizzle's db:generate/db:migrate/db:push/db:studio, which call drizzle-kit directly rather than assuming npm.

What if I select a UI library other than shadcn — do I still get shadcn's primitives? The templates/ui/<library> overlay is applied after the base template and after your selected addons, wiring in that library's provider and a couple of example components. shadcn-style primitives in src/components/ui remain in the tree unless you remove them; feel free to delete what you don't use.

Can I switch UI libraries with nova add? No — switching UI libraries requires rewriting the provider tree, which is only handled during initial generation. nova add will warn you if you pass a UI library name as a feature.

Can I add a plugin that isn't listed? Yes — see Adding a New Plugin. Plugins are just overlay folders under templates/addons/<name> plus a feature flag, a metadata entry, and a package.json contribution, so adding one to a fork of Nova is a small, mechanical change, and it automatically becomes available to the initial generator, nova add, and nova plugins.


Troubleshooting

Directory "my-app" already exists and is not empty. Nova refuses to generate into a non-empty directory to avoid clobbering existing files. Pick a different project name or empty the target directory first.

No package.json found in "<dir>" when running nova add. nova add only works inside an existing Node/Next.js project — it never scaffolds one. Run it from the project root, or pass the correct directory with --path.

nova add says a file was skipped. That means the destination file already existed. This is intentional so your customizations aren't silently overwritten — pass --force if you want the addon's version instead.

nova add warns "looks like a UI library, not a feature." UI library switching (mui, chakra, ant, mantine, hero, daisy, headless) isn't supported incrementally — see Known limitations.

Generation fails with a plugin conflict or missing-dependency error (e.g. Prisma + Drizzle). Nova validates your plugin selection against declared metadata before writing anything. Run nova plugins <feature> for each plugin involved to see its requires/conflicts list, then adjust your selection to include only one of the conflicting plugins.

TypeScript can't resolve @nova/core while developing Nova itself. Run npm install at the repository root first — @nova/core is a real workspace package, and both the editor's TypeScript server and tsc --noEmit need the workspace symlink (or the paths mapping in tsconfig.json) to resolve it.

Git init or dependency install fails after generation. Nova continues even if git init/git commit or the install step fails, and prints a warning with the exact command to run manually (e.g. pnpm install) inside the generated project directory.


Development

Clone the repository and install dependencies:

npm install

Run the CLI in development mode:

npm run dev

Build the project:

npm run build

Run type checking:

npm run typecheck

Verify the package manifest data hasn't drifted:

npm run verify:manifest-sync

Run the compiled CLI:

npm start

If the project uses the @nova/core workspace package, run npm install from the repository root before running development, build, or type-check commands.


Testing the Generator

Nova includes end-to-end smoke tests for generated projects, plus a dedicated regression guard for package manifest data.

Run the smoke tests:

node scripts/smoke-test.mjs

The smoke tests generate multiple project configurations and verify that the expected files are created.

Test scenarios may include:

  • Default configuration
  • Full feature configuration
  • Different UI frameworks
  • Infrastructure plugins
  • Redis
  • Mailpit
  • Health checks
  • Security headers
  • Docker Compose
  • Drizzle ORM (files, scripts, dependencies, .nova.json tracking)
  • The Prisma/Drizzle conflict, asserting generation fails and leaves no partial project directory on disk

Run the manifest sync check:

npm run verify:manifest-sync

This confirms buildPackageJson()'s output for every feature matches src/featureContributions.ts — the single source of truth both full generation and nova add read from. Since both paths consume the same data, this should always pass; it exists as a regression guard against a future change accidentally bypassing that shared source.

When adding or modifying a plugin, update the relevant smoke tests. If your change affects nova add behavior specifically (e.g. new package additions in src/featureContributions.ts), verify it manually against a scaffolded project until incremental-add coverage is added to the smoke suite.


Adding a New Plugin

To add a new plugin:

  1. Create the plugin template under:
templates/addons/<plugin-name>
  1. Add the corresponding feature key to:
src/types.ts
  1. Register the plugin's addon folder in:
src/addonRegistry.ts
  1. Add the plugin's dependencies and scripts once, in:
src/featureContributions.ts

Both src/packageManifest.ts (full generation) and nova add read from this file automatically — there is nothing further to duplicate.

  1. Add descriptive metadata, and any real requires/conflicts/supportedUI constraints, in:
src/generator/pluginMetadata.ts

This also makes the plugin show up correctly in nova plugins.

  1. If the plugin needs richer contributions — its own env variables, docs, config patches, or prompts — author it as a native manifest in:
src/plugin/nativePlugins/<plugin-name>.ts

and register it in src/plugin/nativePlugins/index.ts's NATIVE_PLUGINS array (see drizzle.ts for a minimal example built on top of steps 2–5). Plugins that only need a file overlay + package.json contribution don't need this step — they're picked up automatically via the legacy adapter.

  1. If the plugin needs to patch next.config.mjs, the provider tree, or middleware.ts and hasn't migrated to a native manifest, add a contribution to the relevant file in:
src/generator/patchers/

rather than adding a new if branch to generator/index.ts.

  1. Add plugin-specific environment variables (to .env.example in the relevant template if unconditional, or via a native manifest's env array if plugin-specific, and document them).

  2. Add documentation (a docs/<plugin>.md in the plugin's own addon folder, plus a mention in this README's plugin tables).

  3. Add smoke tests in scripts/smoke-test.mjs.

  4. Run npm run verify:manifest-sync to confirm the plugin's package.json contribution is consistent.

  5. Verify that the plugin works independently, both via nova <name> and via nova add <plugin-name> against an existing project, and check nova plugins <plugin-name> renders sensibly.

  6. Verify compatibility with related plugins.

  7. Run the complete test suite (npm run typecheck && npm run verify:manifest-sync && node scripts/smoke-test.mjs).

Because a feature's package.json contribution now lives in exactly one file (src/featureContributions.ts), it's no longer possible for full generation and nova add to silently drift apart for a given plugin — npm run verify:manifest-sync exists specifically to catch a regression here.


Roadmap

The following are planned but not yet implemented — they are not selectable today via nova, nova add, or any prompt, and no code in this repository claims otherwise:

  • tRPC — type-safe router/procedures integration with the App Router, evaluated alongside (not replacing) the existing src/lib/api layer.
  • GraphQL — a client/server integration appropriate for Next.js, with schema organization and typed codegen.
  • Supabase — client/server SDK integration, with explicit compatibility rules against Better Auth, Prisma, and Drizzle.
  • React Native templates — a mobile project template, likely requiring a ProjectTemplate/ProjectType concept distinct from the current Next.js-only generator so future non-Next.js targets don't turn the CLI into framework-specific conditionals.
  • Cloud deployment providers — a deployment/<provider> architecture (starting with a small number of providers) generating deployment configuration; not an authenticated, live "click to deploy" integration unless and until explicitly documented as such.

Each will land as its own self-contained plugin (or, for React Native, a new template concept) following the same process as Adding a New Plugin and the Drizzle ORM plugin above, one at a time, with its own tests and documentation before the next begins.


Contributing

Contributions are welcome.

Before submitting changes:

npm install
npm run typecheck
npm run verify:manifest-sync
npm run build
node scripts/smoke-test.mjs

When contributing a new plugin or UI integration:

  • Keep the plugin isolated.
  • Avoid unnecessary dependencies.
  • Follow the existing architecture (see Generator Internals).
  • Declare package.json contributions once, in src/featureContributions.ts.
  • Declare plugin metadata and any real constraints in src/generator/pluginMetadata.ts.
  • Prefer a native plugin manifest (src/plugin/nativePlugins/) over a new if branch in generator/index.ts.
  • Update documentation.
  • Add tests.
  • Add smoke-test coverage.
  • Document plugin conflicts and dependencies.
  • Keep generated applications production-ready.
  • Run npm run verify:manifest-sync before opening a PR that touches package.json contributions.

Prefer small, focused changes.


Changelog

See CHANGELOG.md for a full history of changes to Nova itself (not the projects it generates).


Links


License

MIT

About

Extensible CLI for building production-ready Next.js applications with plugins and modern architecture.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages