Skip to main content

Changelog

Record individual changes and release versioned changelogs with a single command that guides you through each step.

Summary

The changelog command combines two workflows into one guided flow:

  • Recording a change (what happened).
  • Releasing accumulated changes (bump versions and write a CHANGELOG.md).

In interactive mode, a menu lets you pick which action to take. In non-interactive mode, the --record and --release flags drive the same workflows from scripts and CI/CD pipelines.

Recorded entries are stored as individual .md files in a .changelog/ directory at the monorepo root. When released, the entries are consumed, versions are bumped in each affected package.json, and a CHANGELOG.md is written (or updated) in the workspace directory.

Why Use This Command?

  • Combines the record-and-release lifecycle into one command instead of separate tools.
  • Interactive prompts guide you through package selection, category, description, and SemVer bump type. No flags to memorize.
  • The --record and --release flags enable the same workflow in CI/CD without prompts.
  • The dry run option lets you preview every change before any file is touched.
  • Supports both SemVer and CalVer strategies, configured per-project in nova.config.json.

Use Cases

  • Solo projects — Record a change and release it in the same session without learning a separate versioning tool.
  • Team workflows — Each contributor records changes in their PR. A maintainer runs the release step when ready.
  • CI/CD automation — Use --record in a pipeline step to capture changes, then --release in a publish step to bump versions and write changelogs.
  • Migration from Changesets — Replace changeset add with nova utility changelog --record and changeset version with nova utility changelog --release.

Requirements

  • Node.js runtime — Use any Node.js LTS release with either the installed nova CLI or npx.
  • Project root — Run the command from the directory containing the top-level package.json (monorepos included).
  • Nova config — A valid nova.config.json must exist. Recording a change requires a non-freezable workspace; releasing can also stamp freezable workspaces when there are no entries.

Usage

You can run this command in two ways:

Options

FlagDescription
--recordRecord a change. Prompts for missing values unless all flags are provided.
--releaseRelease accumulated changes.
-p, --package <name>Package name for recording.
-c, --category <category>Category: updated, fixed, added, removed.
-b, --bump <type>Bump type: major, minor, patch. Semver only — not available in CalVer mode.
-m, --message <message>Change description.
-d, --dry-runRun without writing any files.

How It Works

Record Flow

  1. Select package — Auto-selects if only one non-freezable workspace exists. Otherwise prompts.
  2. Select category — Choose from Updated, Fixed, Added, or Removed.
  3. Enter description — Free-text description of the change.
  4. Select bump type — Pre-selected based on category (Added/Updated = minor, Fixed = patch, Removed = major). Override if needed. (Skipped in CalVer mode.)
  5. Save — Writes an entry file to .changelog/ with a random adjective-noun-verb filename.

Release Flow

The validation and preview steps finish before Nova writes any release files. Version bumps and changelog updates run for releasing non-freezable workspaces, while sentinel stamping also runs for freezable workspaces; consumed entries are deleted only after every workspace succeeds.

  1. Read entries — Collects all .md files from .changelog/.
  2. Validate release history — Confirms the configured strategy agrees with the root and non-freezable workspace changelogs.
  3. Preflight lock-step versions — When settings.lockStepVersioning is true, reads every non-freezable workspace's package.json, validates its version against the configured strategy, and confirms all of them start at the same version. Nova stops before writing if a file is missing, malformed, or drifted.
  4. Lock-step expansion — Includes every non-freezable workspace in the release, even when it had no recorded entries. All workspaces move to the same computed version. Workspaces without entries receive a "No changes." section in their CHANGELOG.md.
  5. Show summary — Lists entries grouped by package with the computed version bump (highest bump wins).
  6. Confirm — Interactive only. Non-interactive mode (--release) skips this step.
  7. Bump version — Updates the version field in each affected package.json. Android app workspaces also synchronize Gradle versionName and advance versionCode once for every released changelog entry. Apple app workspaces synchronize MARKETING_VERSION in the root project.yml when present, otherwise in checked-in .xcodeproj files; CURRENT_PROJECT_VERSION advances by the same entry count.
  8. Write CHANGELOG.md — Prepends a new version section. Creates the file if it does not exist. Android and Apple app headings include the resulting native build number as ## X.Y.Z (build) - YYYY-MM-DD.
  9. Sync exact package references — Updates exact-pinned references to co-released packages in workspace and template package.json files. Ranges and workspace protocols are left unchanged.
  10. Stamp sentinels — Recursively discovers files owned by each configured workspace and replaces the documentation sentinel for each supported language. JavaScript, TypeScript, CSS, Astro, Java, Kotlin, PHP, Svelte, and Vue use @since UNRELEASED or @deprecated UNRELEASED; C# uses <remarks>Since: UNRELEASED</remarks>; Go and Rust use Since: UNRELEASED documentation comments; Python uses a Since: docstring section; and Swift DocC uses - Since: UNRELEASED. Extensionless scripts with a shebang are supported for JSDoc-style tags as well. Releasing non-freezable workspaces use their new version, while freezable workspaces use 0.0.0 without a version bump or changelog. Workspace boundaries, templates, documentation examples, project assets, fixtures, symlinks, embedded repositories, dependencies, caches, and generated output remain untouched. Prerelease versions (containing a hyphen) are skipped. A scanning or stamping failure leaves pending entries in place.
  11. Clean up — Deletes consumed entry files from .changelog/ only after every workspace has completed successfully.

When .changelog/ has no entries, --release still stamps freezable workspaces at 0.0.0. This includes every supported file owned by the workspace without crossing into declared child workspaces. A dry run writes nothing.

Non-Zero Exit Code

The command exits with code 1 when validation fails (invalid flags, missing config, unknown package). CI pipelines that use this command will fail the step on errors.

UNRELEASED Sentinel Convention

When you add or change a public API member, use the language's literal UNRELEASED documentation sentinel rather than a hand-written version number. The release step stamps the real version at release time, so you never have to guess or update the tag manually.

  • JavaScript, TypeScript, CSS, Astro, Java, Kotlin, PHP, Svelte, and Vue use @since UNRELEASED; supported JSDoc-style languages may also use @deprecated UNRELEASED.
  • C# XML documentation uses <remarks>Since: UNRELEASED</remarks>.
  • Go declaration documentation uses // Since: UNRELEASED.
  • Python docstrings use a Since: section whose indented value is UNRELEASED.
  • Rustdoc uses /// Since: UNRELEASED.
  • Swift DocC uses /// - Since: UNRELEASED.
  • Never write a concrete version (e.g., @since 1.3.0) for unreleased code — it will be wrong if the bump type changes before release.
  • Concrete versions are only appropriate for members that have already shipped in a published release.
src/my-feature.ts
ts
// Before release (what you write):

/**
 * Returns the current config version.
 *
 * @since UNRELEASED
 */
export function getConfigVersion(): string { ... }

// After `nova utility changelog --release` (what gets stamped):

/**
 * Returns the current config version.
 *
 * @since 1.4.0
 */
export function getConfigVersion(): string { ... }

Entry File Format

Each recorded change is stored as an individual .md file in .changelog/:

.changelog/brave-cats-jump.md
markdown
---
package: "@cbnventures/nova"
category: added
bump: minor
---

New ESLint rule for switch case blocks.

CalVer Mode

When settings.versionStrategy is "calver" in nova.config.json, the changelog command switches to calendar-based versioning:

  • Version format: YYYY.M.MICRO — full year, unpadded month, micro counter that resets each new month.
  • No bump field: Changelog entries omit the bump field entirely. The --bump flag is not available in CalVer mode.
  • Automatic version: New non-freezable workspaces start at the current UTC YYYY.M.0 period. Their first release keeps that .0 version when no release history exists. Later releases in the same UTC month increment the micro counter, while a new month resets it to 0.
  • Current-version validation: A non-freezable workspace must already use YYYY.M.MICRO; 0.0.0 is reserved exclusively for freezable workspaces. Nova rejects invalid numbers, unsafe integers, and dates later than the current UTC month before calculating the next version.
  • Heading format: CalVer changelog headings omit the date suffix since the date is already embedded in the version number.
.changelog/brave-cats-jump.md
markdown
---
package: "demo-envoy"
category: added
---

Added dark mode support.
templates/demo-envoy/CHANGELOG.md
markdown
# demo-envoy

## 2026.8.0

### ADDED
- Added dark mode support.

Strategy Lock

Before the configuration wizard opens or the changelog command records or releases a change, Nova scans the root CHANGELOG.md and every non-freezable workspace's CHANGELOG.md. Existing version headings lock settings.versionStrategy to the strategy already used by the project. Valid SemVer prerelease and build metadata remain part of SemVer history. Missing changelog files and freezable workspaces are ignored.

Nova stops with a non-zero exit code when the configured strategy conflicts with that history, when the project mixes SemVer and CalVer release headings, or when a numeric release heading does not match either supported format. A project with no release headings remains unlocked, so the configuration wizard can still change its strategy.

CHANGELOG.md Format

Released entries are written in the following format. Headings are ordered as UPDATED, FIXED, ADDED, REMOVED.

Empty headings are omitted. When lock-step versioning is enabled and a package had no recorded changes, the version section contains "No changes." instead of category headings.

packages/nova/CHANGELOG.md
markdown
# @cbnventures/nova

## 1.3.0 - 2026-02-25

### FIXED
- Crash in configure command.

### ADDED
- New ESLint rule for switch case blocks.
packages/docusaurus-preset-nova/CHANGELOG.md (lock-step, no changes)
markdown
# @cbnventures/docusaurus-preset-nova

## 1.3.0 - 2026-02-25

No changes.

Native app workspaces use the same category order while including the Android versionCode or Apple build number:

apps/apple/CHANGELOG.md
markdown
# example-app-apple

## 1.4.1 (451) - 2026-10-19

### FIXED
- Kept the native app version synchronized during release.

Examples

Migration from Changesets

For users migrating from the Changesets workflow, Nova provides equivalent flags under nova utility changelog:

CommandEquivalentWhat It Does
nova utility changelog --recordchangeset addRecord a change with -p, -c, -b, -m flags.
nova utility changelog --releasechangeset versionRelease accumulated changes, bump versions, and stamp @since/@deprecated UNRELEASED tags in source.

Shorten long commands with scripts

Nova bundles many tools in one package, so commands can be verbose. Shorten them by adding aliases to the "scripts" field in your root package.json (the one at the monorepo root):

package.json
json
{
  "scripts": {
    "changelog:record": "nova utility changelog --record",
    "changelog:release": "nova utility changelog --release"
  }
}

Then run npm run changelog:record or npm run changelog:release.

Troubleshooting

  • "No eligible (non-freezable) workspaces found" — Confirm that nova.config.json has at least one workspace with a trackable or distributable workspace policy.
  • "Cannot use --record and --release together" — These flags are mutually exclusive. Run them as separate commands.
  • "Non-interactive record requires --package, --category, --bump, and --message" — In semver mode, all four flags are required when using --record. In CalVer mode, only --package, --category, and --message are required (no --bump). Omit --record for interactive mode.
  • "No changelog entries found" — The .changelog/ directory is empty or does not exist. No versions or changelogs change, but a non-dry-run release still stamps freezable workspace sentinels at 0.0.0.
  • "Package not found in nova.config.json" — The --package value must match the name field of a non-freezable workspace in nova.config.json.
  • "Configured version strategy ... conflicts with existing ... release history" — Keep settings.versionStrategy aligned with the format already recorded in the root or a non-freezable workspace changelog. Do not rewrite published history merely to switch strategies.
  • "Lock-step versioning requires every non-freezable workspace to start at the same version" — Align the listed workspace versions, then run the release again. Nova has not written any release files when this check fails.
  • "UNRELEASED sentinel survived stamping" — The release step found a supported @since UNRELEASED or @deprecated UNRELEASED tag directly inside a workspace root or one of Nova's supported source trees that was not replaced. Check the file paths in the error, correct the source problem, and re-run the release; pending entry files remain available.