> ## Documentation Index
> Fetch the complete documentation index at: https://stars-components.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration Guide

> Upgrade paths between Stars Components versions and package replacements.

This page collects the upgrade paths between Stars Components packages and their versions. Every section is
self-contained — read the one matching the move you are making.

* [`@wolfstar/http-framework` v4 → v5](#v5) <Badge type="tip" text="stable" />
* [`@wolfstar/http-framework` v3 → v4](#v4) <Badge type="tip" text="stable" />
* [`@wolfstar/http-framework-i18n` → `@wolfstar/plugin-i18next`](#i18next)
* [`@wolfstar/logger` → `@wolfstar/plugin-logger`](#logger)

<h2 id="v5">
  Migrating from v4 to v5
</h2>

::: info Current releases
The v5 build defaults are stable in `@wolfstar/http-framework@5.0.0`. The matching developer tooling is
`@wolfstar/cli@1.0.0`.
:::

v5 is a small, mostly opt-in release. The only breaking change to the framework's public API is the removal of the
previously-unannounced `@wolfstar/http-framework/fetch` submodule, replaced by a `fetch()` method on `Client` itself.
Everything else is either purely additive (`experimental.enableNitro`) or an internal error-handling change
(`nostics` diagnostics) that only matters to code that caught `ConfigError`/`CliError` by name.

### Changes Included in v5

| Landed in                                                             | Change                                                                                                                |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [#207](https://github.com/wolfstar-project/stars-components/pull/207) | `Client#fetch`, the Fetch-based counterpart of `listen()`; `experimental.enableNitro` builds and serves through Nitro |
| [#205](https://github.com/wolfstar-project/stars-components/pull/205) | `ConfigError`/`CliError` replaced by `nostics` `Diagnostic`s (`configDiagnostics`/`cliDiagnostics`)                   |

### Upgrading the Packages

::: code-group

```bash [pnpm] theme={"system"}
pnpm add @wolfstar/http-framework@^5
pnpm add -D @wolfstar/cli@^1
```

```bash [npm] theme={"system"}
npm install @wolfstar/http-framework@^5
npm install --save-dev @wolfstar/cli@^1
```

```bash [yarn] theme={"system"}
yarn add @wolfstar/http-framework@^5
yarn add --dev @wolfstar/cli@^1
```

```bash [bun] theme={"system"}
bun add @wolfstar/http-framework@^5
bun add --dev @wolfstar/cli@^1
```

:::

No `stars.config.*` changes are required to move from v4 to v5 — the configuration shape is unchanged.

### Replacing `@wolfstar/http-framework/fetch` with `Client#fetch`

The experimental, previously-unannounced `@wolfstar/http-framework/fetch` submodule is removed. Projects that imported
`createFetchHandler`, `FetchHandler` or `FetchHandlerOptions` from it to adapt `Client` to a Fetch-based runtime switch
to the new `fetch()` method on `Client` itself — a method on the instance you already have, rather than a separate
adapter module to import and wire up:

```diff theme={"system"}
-import { createFetchHandler } from '@wolfstar/http-framework/fetch';
-import { Client } from '@wolfstar/http-framework';
+import { Client } from '@wolfstar/http-framework';

 const client = new Client({ discordToken, discordPublicKey });
 await client.load();

-const handler = createFetchHandler(client, { postPath: '/interactions' });
 export default {
-	fetch: handler
+	fetch: (request: Request) => client.fetch(request, { postPath: '/interactions' })
 };
```

`Client#fetch(request, options?)` runs the exact same signature verification, routing and replies `listen()`'s
`node:http` server runs, without binding a port. The Discord public key given at construction is imported once and
reused across every call, the same lifetime `listen()` gives its own signing key — use it for anything that speaks
Fetch instead of `node:http` (Nitro, a Worker, `Bun.serve`, `Deno.serve`, Vite's own dev middleware).

### Building Through Nitro <Badge type="tip" text="optional" />

`experimental.enableNitro` is now implemented: `stars dev`/`stars build` build and serve the bot through
[Nitro](https://nitro.build) v3's own Vite plugin instead of refusing with `EXPERIMENT_UNAVAILABLE`. It is purely
opt-in and requires `experimental.enableVite` as well, since Nitro v3 is itself a Vite plugin rather than a separate
build step:

```typescript theme={"system"}
export default defineConfig({
	experimental: {
		enableVite: true,
		enableNitro: true,
		nitro: {
			// 'node-server' by default; deployable to anything Nitro targets — 'cloudflare-module', 'aws-lambda',
			// 'vercel', 'netlify', 'bun', 'deno-deploy', and more.
			preset: 'node-server'
		}
	}
});
```

Install `vite` and `nitro` as `devDependencies`. `stars build` now produces `build.outDir` (`.output` by default
under Nitro, instead of `dist`) laid out for the configured preset rather than a `node:http` process; `stars dev`
rebuilds and restarts on every change, the same as the other build tools.

::: warning The entry's default export changes
The generated Nitro server entry imports the entry file's **default export** and calls `.fetch(request)` on it — the
entry must export the already-`load()`ed `Client` instance, not call `listen()`:

```typescript theme={"system"}
import { Client } from '@wolfstar/http-framework';

const client = new Client({
	discordToken: process.env.DISCORD_TOKEN,
	discordPublicKey: process.env.DISCORD_PUBLIC_KEY
});
await client.load();

export default client;
```

:::

Vite's native `resolve.tsconfigPaths` is turned on automatically under Nitro (see
[Nitro's import alias guide](https://nitro.build/examples/import-alias)), so a project's own
`tsconfig.json#paths`/package.json `imports` aliases — including the ones `stars prepare`'s generated
`.stars/tsconfig.json` adds — keep working without a `vite-tsconfig-paths` plugin.

### Catching Configuration and CLI Errors <Badge type="tip" text="optional" />

`ConfigError`/`ConfigErrorOptions` (from `@wolfstar/http-framework/config`) and `CliError`/`CliErrorOptions` (from
`@wolfstar/cli`) are removed. Every `stars.config.*` validation and load failure, and every condition `@wolfstar/cli`
itself rejects, is now built from a catalog (`configDiagnostics`, `cliDiagnostics`, both still exported) and thrown as
a [`nostics`](https://github.com/vercel-labs/nostics) `Diagnostic` — a stable, typed code with a `why`, an actionable
`fix`, and a docs link, instead of ad hoc `code`/`hint`/`path`/`file` fields:

```diff theme={"system"}
-import { ConfigError } from '@wolfstar/http-framework/config';
+import { Diagnostic } from 'nostics';
 import { loadStarsConfig } from '@wolfstar/http-framework/config';

 try {
  await loadStarsConfig({ cwd: process.cwd() });
 } catch (error) {
-	if (error instanceof ConfigError) {
-		console.error(error.code, error.path, error.file, error.hint);
+	if (error instanceof Diagnostic) {
+		console.error(error.code, error.message, error.sources);
  }
 }
```

The option path that used to live on `.path` is folded directly into the diagnostic's message; the configuration file
that used to live on `.file` is now in `.sources`. `@wolfstar/cli` itself still renders these the same way it always
has (`formatError`/`exitCodeOf` keep their existing exports and behaviour) and still exits with code `2` for every
`stars.config.*` diagnostic and `3` for `BUILD_FAILED`, unchanged from v4 — this only matters for code that imported
`ConfigError`/`CliError` by name. See the [configuration error reference](/documentation/errors/config) and
[CLI error reference](/documentation/errors/cli) for the full code catalogs.

### Checklist

* [ ] `@wolfstar/http-framework` upgraded to v5 and `@wolfstar/cli` upgraded to v1
* [ ] `createFetchHandler`/`FetchHandler`/`FetchHandlerOptions` imports from `@wolfstar/http-framework/fetch` replaced
  with `client.fetch(request, options?)`
* [ ] `instanceof ConfigError` / `instanceof CliError` checks replaced with `instanceof Diagnostic` (from `nostics`)
* [ ] `.path`/`.file` reads on caught errors replaced with `.message`/`.sources`
* [ ] Optional: `experimental.enableVite` and `experimental.enableNitro` configured, `vite` and `nitro` installed as
  `devDependencies`, and the entry's default export changed from calling `listen()` to the loaded `Client`
  instance

<h2 id="v4">
  Migrating from v3 to v4
</h2>

<Info>
  **Current releases**

  The v4 build defaults are stable in `@wolfstar/http-framework@4.0.1`. The matching developer tooling is
  `@wolfstar/cli@0.6.0`; projects generated by `@wolfstar/create-http-framework@2.5.1` already use this layout.
</Info>

v4 makes the convention-first workflow introduced during the later v3 releases the default. Most runtime APIs and
imports from `@wolfstar/http-framework` are unchanged; the migration primarily consolidates development and build
configuration around `stars.config.*` and the `stars` CLI.

### Changes Included in v4

| Landed in                                                             | Change                                                                                          |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [#158](https://github.com/wolfstar-project/stars-components/pull/158) | Typed `stars.config.*`, the `stars` CLI, dev tooling and experimental build flags               |
| [#159](https://github.com/wolfstar-project/stars-components/pull/159) | Built-in `container.logger`                                                                     |
| [#164](https://github.com/wolfstar-project/stars-components/pull/164) | Build configuration moved into `stars.config.*`; compatibility version 4 and auto imports       |
| [#168](https://github.com/wolfstar-project/stars-components/pull/168) | Pinned `stars dev` panel, folded/searchable logs and custom `dev.banner`                        |
| [#182](https://github.com/wolfstar-project/stars-components/pull/182) | v4 defaults, development mode, conventional env/locales discovery and on-demand tunnel shortcut |
| [#191](https://github.com/wolfstar-project/stars-components/pull/191) | Generated `.stars/tsconfig.json` with aliases and framework compiler defaults                   |
| [#192](https://github.com/wolfstar-project/stars-components/pull/192) | Automatic `/register` activation for installed `@wolfstar/plugin-*` packages                    |

### Upgrade the Framework and Add the `stars` CLI

The developer workflow moves into a separate package, `@wolfstar/cli`, which ships the `stars` binary. The framework
itself keeps owning the configuration — the same split Nuxt has between `nuxt`/`defineNuxtConfig` and `nuxi`.

<CodeGroup>
  ```bash pnpm theme={"system"}
  pnpm add @wolfstar/http-framework@^4
  pnpm add -D @wolfstar/cli
  ```

  ```bash npm theme={"system"}
  npm install @wolfstar/http-framework@^4
  npm install --save-dev @wolfstar/cli
  ```

  ```bash yarn theme={"system"}
  yarn add @wolfstar/http-framework@^4
  yarn add --dev @wolfstar/cli
  ```

  ```bash bun theme={"system"}
  bun add @wolfstar/http-framework@^4
  bun add --dev @wolfstar/cli
  ```
</CodeGroup>

<Warning>
  **Node requirement**

  `@wolfstar/http-framework` still supports Node.js 20 or newer. `@wolfstar/cli` requires **Node.js 22 or newer**,
  inherited from [Ink](https://github.com/vadimdemedes/ink), which renders the interactive `stars dev` UI. It is a
  `devDependency`, so it constrains the development environment, not the deployed bot.
</Warning>

### Creating `stars.config.ts`

Add a `stars.config.{ts,mts,cts,js,mjs,cjs}` file at the project root. `defineConfig` comes from the framework's new
`config` subpath, not from the CLI:

```typescript theme={"system"}
// stars.config.ts
import { defineConfig } from '@wolfstar/http-framework/config';

export default defineConfig({
	// Entry, build output, tsdown, auto imports, env files and src/locales are conventional.
});
```

Every option has a default: `entry` falls back to the first of `src/main.ts`, `src/main.js`, `src/index.ts`,
`src/index.js` that exists. For a TypeScript entry, `build.tool: 'auto'` now selects `tsdown`; choose
`build: { tool: 'tsc' }` explicitly if the project must keep the TypeScript compiler as its emitter. A minimal v4
project can export `defineConfig({})`; `future.compatibilityVersion: 4` is no longer necessary.

`@wolfstar/http-framework/config` is side-effect free — importing it, or a `stars.config.ts` that imports it, never
starts the bot. Invalid options raise a `ConfigError` carrying a stable `code`, the offending option `path`, the
`file` it came from, and an actionable `hint`; `stars` exits with code `2` on those.

The resolved configuration is readable from any tool, without depending on the CLI:

```typescript theme={"system"}
import { loadStarsConfig } from '@wolfstar/http-framework/config';

const config = await loadStarsConfig({ cwd: process.cwd() });
console.log(config.entry, config.build.output);
```

### Replacing the `package.json` Scripts

The per-language, per-build-tool script wiring collapses into two commands that read `stars.config.*`:

```diff theme={"system"}
 {
	"scripts": {
-		"build": "tsdown",
-		"dev": "pnpm run build --onSuccess \"pnpm run start\"",
-		"watch": "pnpm run build --watch",
-		"watch:start": "pnpm run build --watch --onSuccess \"pnpm run start\"",
+		"build": "stars build",
+		"dev": "stars dev",
 		"start": "node dist/main.js",
-		"generate:i18n": "i18next-type-generator ./src/locales/en-US/ ./src/@types/i18next.d.ts"
+		"generate:i18n": "stars codegen"
	}
 }
```

TypeScript projects built with `tsc` drop `tsc-watch` from their `devDependencies` — `stars dev` runs `tsc -b --watch`
itself. JavaScript projects drop `node --watch src/main.js` and keep no `build` script at all (`build.tool: 'none'`).

`start` is unchanged: production still runs the built entry point directly, `stars` is a development-only tool.

Run `stars prepare` explicitly after installing development dependencies and before a standalone typecheck on a fresh
checkout. Do not put it in a root `postinstall`: production-only installs such as `npm ci --omit=dev` still run that
lifecycle script after omitting the development-only `@wolfstar/cli`, so the install would fail because `stars` is not
available. `stars dev` and `stars build` already regenerate the files they need.

### Move `tsdown` Configuration into `stars.config.ts`

Compatibility version 4 uses `stars.config.*` as the only build configuration. A standalone `tsdown.config.*` or a
`package.json#tsdown` field now raises `TSDOWN_CONFIG_FILE_UNSUPPORTED` instead of being silently ignored.

Most bot projects can delete `tsdown.config.ts`: Stars supplies entries for every source file next to the main entry,
unbundled ESM output for Node, sourcemaps, treeshaking, external dependencies, the configured output directory and
extension, the project tsconfig, Nuxt-style aliases, auto imports, and a copy of `src/locales` in `dist/locales`.
Move only non-conventional overrides into the top-level `tsdown` block:

```typescript theme={"system"}
export default defineConfig({
	tsdown: {
		alias: { '#shared': './src/shared' },
		dts: true
	}
});
```

If the configuration cannot be migrated in one step, temporarily keep the old file with
`future: { compatibilityVersion: 3 }`. In this legacy mode, Stars loads the standalone config and merges the
`stars.config.ts` `tsdown` block over it. Remove the old file and the compatibility override together when finished.

### The New Framework Subpaths

v3 exposed a single entry point. The package now has three export subpaths:

| Subpath                                 | What it exports                                                    |
| --------------------------------------- | ------------------------------------------------------------------ |
| `@wolfstar/http-framework`              | Unchanged — `Client`, pieces, container, everything you use today  |
| `@wolfstar/http-framework/config`       | `defineConfig`, `loadStarsConfig`, `ConfigError`, the config types |
| `@wolfstar/http-framework/auto-imports` | `autoImports()`, the `tsdown` plugin backing auto imports          |

No existing import changes: the root subpath keeps the same specifier and the same exports.

### `stars` Commands

| Command                   | What it replaces                                                            |
| ------------------------- | --------------------------------------------------------------------------- |
| `stars dev`               | `watch` / `watch:start` / `tsc-watch` / `node --watch` wiring               |
| `stars build`             | Calling `tsdown` or `tsc -b` directly                                       |
| `stars info [--json]`     | Nothing — prints the resolved configuration, auto imports and environment   |
| `stars codegen [--check]` | A hand-written `i18next-type-generator` invocation                          |
| `stars prepare`           | Nothing — generates `.stars/tsconfig.json` and the auto imports declaration |
| `stars prepare --check`   | Nothing — verifies both generated files without writing them                |
| `stars commands`          | Ad-hoc scripts deleting stale application commands from Discord             |

`--config <file>` points at a configuration file and `--cwd <dir>` changes the working directory; both work on every
command. `stars --help` and `stars --version` never load the configuration machinery, so they stay fast.

`stars dev` builds, starts the bot with `STARS_DEV=1` and `NODE_ENV=development`, and restarts it after every successful build. Failed builds keep the previous
process alive and wait for the next change. On a terminal it renders an interactive UI (lifecycle, uptime, restart
reason, build state, URL, health, filtered logs, `r` restart, `c` clear, `f`/`e` filters, `h` help, `q` quit); it falls
back to plain prefixed lines with `--no-tui`, `STARS_TUI=plain`, in CI, or when stdout is not a TTY. Both modes honour
`NO_COLOR` and stop the bot cleanly on `SIGINT`/`SIGTERM`.

<Warning>
  **`hmr` and `stars dev` overlap**

  The bot runs as a child `node` process with `STARS_DEV=1` in its environment, and `stars dev` restarts the whole
  process on every build. Leave the framework's own `hmr` client option **disabled** while using `stars dev`.
</Warning>

`stars commands` covers the gap the registry cannot: renamed or deleted commands stay deployed on Discord until
something removes them.

```bash theme={"system"}
stars commands list                 # global commands
stars commands list --guild 1234    # a guild's commands
stars commands clean                # checklist wizard, then a confirmation
stars commands clean --name ping    # delete one, asking first
```

It reads `DISCORD_TOKEN` and `DISCORD_APPLICATION_ID` (or `APPLICATION_ID`) from the environment or the project's
`.env`, the same place the bot reads them from. Outside a terminal, `clean` refuses to run without `--yes` or `--name`.

### Dev Loop Options <Badge type="tip" text="optional" />

Three `dev` options round out the loop, all off by default except the log file:

```typescript theme={"system"}
export default defineConfig({
	entry: 'src/main.ts',
	build: { tool: 'tsdown' },
	dev: {
		// A type checker next to the bot, reported on the UI's `tsc` channel. Never blocks a build.
		typecheck: { checker: 'golar' },
		// A cloudflared quick tunnel so Discord can reach the interactions endpoint.
		tunnel: true,
		// Where the session's logs are mirrored; `false` disables it.
		logFile: '.stars/dev.log'
	}
});
```

* **`dev.typecheck`** brings back the type safety a `tsdown` build skips. `checker` is `tsc` (watch mode), `golar`
  (`golar tsc`, watch mode), `tsz` (no watch mode, so re-run after every build), or `auto` — the default, `golar` when
  the project depends on it and `tsc` otherwise. Type errors are reported without blocking builds or restarts.
* **`dev.tunnel`** exposes the interactions endpoint publicly: `true` opens a `cloudflared` quick tunnel (a new
  hostname on every run), a string is an https URL you already serve and the CLI only probes.
  `dev.tunnel.updateEndpoint` writes that URL to the Discord application's `interactions_endpoint_url` — opt-in,
  because it edits a live application.
* **`dev.logFile`** (default `.stars/dev.log`) mirrors the session's logs to disk, so a run can be read back once the
  terminal UI is gone.

`dev.url` needs no configuration: it is detected from `HTTP_PORT` (environment variable, `src/.env*`/`.env*`, or
`dev.env`) or `3000`, the way Vite's and Nuxt's dev servers do, and `localhost` is swapped for `127.0.0.1` when that is
what is actually reachable. Set it explicitly only to override, e.g. `dev: { url: 'http://192.168.1.5:3000' }`.

Environment discovery now checks `src/.env*` as well as root `.env*` files. Conventional `src/locales` assets are
copied to `dist/locales` automatically and watched during development, so remove custom copy/watch hooks that only do
those jobs. In the TUI, press `t` to open or close a quick tunnel without setting `dev.tunnel`; keep `dev.tunnel` when
the tunnel should open automatically at startup.

### Auto Imports <Badge type="tip" text="optional" />

Nuxt-style auto imports make the framework's exports and the project's own modules usable without an `import`
statement. They are injected at build time by the `autoImports()` rolldown plugin, so they **require the `tsdown`
build tool** — `tsc` and `none` have no transform step to hook into. They are on by default with `tsdown`, and
`imports: false` turns them off.

```typescript theme={"system"}
export default defineConfig({
	entry: 'src/main.ts',
	build: { tool: 'tsdown' },
	imports: {
		dirs: ['src/lib/**', 'src/utils/**'],
		presets: ['@wolfstar/http-framework', '@wolfstar/env-utilities'],
		exclude: [],
		dts: '.stars/imports.d.ts'
	}
});
```

`Client`, `Message`, `Plugin` and `Store` are never auto-imported, even when a preset exports them: the names are
generic enough that project code likely declares its own. Import them explicitly, as today.

### Generated TypeScript Configuration

`stars prepare` now generates both the auto imports declaration and `.stars/tsconfig.json`. The generated config
contains the Sapphire base, extra-strict and decorator options, plus bundler settings aligned with Nitro. For `tsdown`
and Vite it also contains TypeScript paths matching `~`/`@` (the entry directory), `~~`/`@@` (the project root), and
custom filesystem aliases from `stars.config.ts`.

Replace manually duplicated compiler defaults, `include` entries and alias paths by extending it:

```json theme={"system"}
{
	"extends": "./.stars/tsconfig.json"
}
```

Keep project-specific `compilerOptions` next to `extends`. An explicit `include` or `compilerOptions.paths` replaces
the inherited value, so remove those keys unless that is intentional. Do not edit `.stars/tsconfig.json`; `stars dev`
and `stars build` regenerate it. Run `stars prepare` explicitly before editor/typecheck use on a fresh checkout. Use
`stars prepare --check` in CI only after generation.

### Plugin Registration and the Built-in Logger

Bundler builds now activate every `@wolfstar/plugin-*` package found in `dependencies` or `optionalDependencies` by
injecting its `/register` side-effect entry before the application entry. Remove matching manual imports such as:

```diff theme={"system"}
-import '@wolfstar/plugin-i18next/register';
```

This automation applies to `tsdown` and Vite builds. It deliberately ignores `devDependencies`; a runtime plugin must
be a runtime dependency. Projects using `build.tool: 'tsc'` or `'none'` do not have a bundler transform and must keep
explicit `/register` imports.

The framework also provides `container.logger` without `@wolfstar/logger`. Remove the deprecated package and custom
setup module when it only installed the standard logger. Configure the built-in logger through the client when needed:

```typescript theme={"system"}
import { Client, LogLevel } from '@wolfstar/http-framework';

const client = new Client({ logger: { level: LogLevel.Debug } });
```

### Ignoring `.stars/`

The CLI writes generated TypeScript configuration, the auto imports declaration and the dev log into a `.stars/`
directory at the project root. Add it to `.gitignore`:

```gitignore theme={"system"}
.stars/
```

### Checklist

* [ ] `@wolfstar/http-framework` upgraded to v4 and `@wolfstar/cli` added as a `devDependency`
* [ ] Development environment upgraded to Node.js 22 or newer
* [ ] `stars.config.ts` created with `defineConfig` from `@wolfstar/http-framework/config`
* [ ] Standalone `tsdown.config.*` or `package.json#tsdown` removed; custom options moved to `stars.config.ts`
* [ ] `dev` / `build` scripts replaced with `stars dev` / `stars build`
* [ ] `watch`, `watch:start` and the `tsc-watch` dependency removed
* [ ] `stars prepare` runs before standalone typechecking on a fresh checkout, without a production `postinstall`
* [ ] `generate:i18n` replaced with `stars codegen`, verified with `stars codegen --check`
* [ ] The framework's `hmr` client option disabled while developing with `stars dev`
* [ ] `tsconfig.json` extends `./.stars/tsconfig.json`; duplicated generated aliases/options removed
* [ ] Redundant env discovery and `src/locales` copy/watch hooks removed
* [ ] Manual `@wolfstar/plugin-*/register` imports removed for bundler builds only
* [ ] Deprecated `@wolfstar/logger` setup replaced with the built-in `container.logger`
* [ ] `.stars/` added to `.gitignore`
* [ ] `stars info` output reviewed — it prints the configuration exactly as the commands resolve it
* [ ] `stars prepare`, the project typecheck and `stars build` pass
* [ ] Optional: `dev.typecheck`, `dev.tunnel` and auto imports configured

<h2 id="i18next">
  Migrating to `@wolfstar/plugin-i18next`
</h2>

[`@wolfstar/http-framework-i18n`](/packages/http-framework-i18n) is **deprecated**. Its successor is
[`@wolfstar/plugin-i18next`](/packages/plugin-i18next), an official plugin for
[`@wolfstar/http-framework`](/packages/http-framework) that lives in the
[`wolfstar-project/plugins`](https://github.com/wolfstar-project/plugins/tree/main/packages/plugin-i18next)
repository. No further releases are planned for the old package.

<Warning>
  **Prerequisites**

  `@wolfstar/plugin-i18next` requires `@wolfstar/http-framework@^3.1.0` and pulls in `i18next@^25.8.18`.
</Warning>

The plugin keeps the same typed-key philosophy (`T` / `FT`, `resolveKey`, `applyLocalizedBuilder`) but replaces the
manual `load()` + `init()` bootstrap with the framework's plugin lifecycle, and moves the loaded state onto
`container.i18n`.

### Overview of the Changes

| Area                   | `@wolfstar/http-framework-i18n`                 | `@wolfstar/plugin-i18next`                                      |
| ---------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
| Bootstrap              | Manual `await load(...)` then `await init(...)` | `import '@wolfstar/plugin-i18next/register'` + `i18n` options   |
| State                  | Module-level sets (`loadedLocales`, …)          | `container.i18n` (`InternationalizationHandler`)                |
| Locales directory      | Any path passed to `load()`                     | `i18n.defaultLanguageDirectory`, defaults to `<root>/languages` |
| Formatters             | `addFormatters(...)`                            | `i18n.formatters` client option                                 |
| Language resolution    | Interaction payload only                        | Interaction payload **or** `i18n.fetchLanguage`                 |
| Hot reload             | Not available                                   | `i18n.hmr.enabled`                                              |
| `i18next`              | `^22.5.1`                                       | `^25.8.18`                                                      |
| Relationship to client | Standalone module                               | Framework plugin, requires `@wolfstar/http-framework@^3.1.0`    |

### Swapping the Dependency

<CodeGroup>
  ```bash pnpm theme={"system"}
  pnpm remove @wolfstar/http-framework-i18n
  pnpm add @wolfstar/plugin-i18next
  ```

  ```bash npm theme={"system"}
  npm uninstall @wolfstar/http-framework-i18n
  npm install @wolfstar/plugin-i18next
  ```

  ```bash yarn theme={"system"}
  yarn remove @wolfstar/http-framework-i18n
  yarn add @wolfstar/plugin-i18next
  ```

  ```bash bun theme={"system"}
  bun remove @wolfstar/http-framework-i18n
  bun add @wolfstar/plugin-i18next
  ```
</CodeGroup>

`@wolfstar/http-framework` is a peer dependency of the plugin, so keep it in your `dependencies`.

### Bootstrap Moved to the Plugin Lifecycle

The old package required you to load the locales and initialize `i18next` yourself, before registering commands:

```typescript theme={"system"}
// Before
import { addFormatters, init, load } from '@wolfstar/http-framework-i18n';
import { Client } from '@wolfstar/http-framework';

await load(new URL('locales', import.meta.url));

addFormatters(
	{ name: 'uppercase', format: (value) => value.toUpperCase() }, //
	{ name: 'lowercase', format: (value) => value.toLowerCase() }
);

await init();

const client = new Client();
await client.load();
```

The plugin does all of that for you. Import the side-effecting `register` entrypoint **before** the client is created,
and move the configuration into the `i18n` client option:

```typescript theme={"system"}
// After
import '@wolfstar/plugin-i18next/register';
import { Client } from '@wolfstar/http-framework';

const client = new Client({
	i18n: {
		// Optional, defaults to `<root>/languages`:
		defaultLanguageDirectory: new URL('languages', import.meta.url).pathname,
		defaultName: 'en-US',
		formatters: [
			{ name: 'uppercase', format: (value) => value.toUpperCase() }, //
			{ name: 'lowercase', format: (value) => value.toLowerCase() }
		]
	}
});
await client.load();
```

<Tip>
  The plugin registers a `preLoad` hook that awaits `container.i18n.init()` **before** the stores load, so command builders can still be localized
  at registration time — the ordering the manual `await init()` used to guarantee.
</Tip>

### Locales Directory Renamed to `languages`

The old package took whatever path you passed to `load()`; the plugin defaults to `<root>/languages`. Either rename the
directory or keep your own path via `defaultLanguageDirectory`.

The layout itself is unchanged: one directory per language, every nested `.json` file is a namespace.

```text theme={"system"}
languages/
├── en-US/
│   ├── default.json
│   └── commands/
│       └── ping.json
└── es-ES/
    ├── default.json
    └── commands/
        └── ping.json
```

### Module Specifier Renamed

`T`, `FT`, `resolveKey`, `resolveUserKey`, `getSupportedLanguageName`, `getSupportedUserLanguageName`,
`getSupportedLanguageT`, `getSupportedUserLanguageT`, `supportedLanguages`, `isSupportedDiscordLocale`,
`getLocalizedData`, `applyNameLocalizedBuilder`, `applyDescriptionLocalizedBuilder`, `applyLocalizedBuilder` and
`createSelectMenuChoiceName` keep the same names and signatures. For most files the migration is a single
find-and-replace of the module specifier:

```diff theme={"system"}
-import { FT, T, resolveKey } from '@wolfstar/http-framework-i18n';
+import { FT, T, resolveKey } from '@wolfstar/plugin-i18next';
```

### Removed APIs

| Removed                        | Replacement                                                                  |
| ------------------------------ | ---------------------------------------------------------------------------- |
| `load(directory)`              | `i18n.defaultLanguageDirectory` client option                                |
| `init(options)`                | Handled by the plugin's `preLoad` hook; raw options go to `i18n.i18next`     |
| `addFormatters(...formatters)` | `i18n.formatters` client option                                              |
| `getT(locale)`                 | `container.i18n.getT(locale)`                                                |
| `loadedLocales`                | `container.i18n.languages` (a `Map<string, TFunction>`)                      |
| `loadedNamespaces`             | `container.i18n.namespaces`                                                  |
| `loadedPaths`                  | Derived from `i18n.defaultLanguageDirectory`; extra paths via `i18n.backend` |
| `loadedFormatters`             | `container.i18n.options.formatters`                                          |
| `Formatter`                    | `I18nextFormatter`                                                           |

```diff theme={"system"}
-import { getT, loadedLocales } from '@wolfstar/http-framework-i18n';
+import { container } from '@wolfstar/http-framework';

-const t = getT('en-US');
-const isLoaded = loadedLocales.has('es-ES');
+const t = container.i18n.getT('en-US');
+const isLoaded = container.i18n.languages.has('es-ES');
```

### New APIs

| Added                   | What it does                                                                 |
| ----------------------- | ---------------------------------------------------------------------------- |
| `fetchLanguage(target)` | Resolves the language through `container.i18n.fetchLanguage`, with fallbacks |
| `fetchT(target)`        | `getT` over the result of `fetchLanguage`                                    |
| `fetchKey(target, key)` | Asynchronous `resolveKey` honouring the `fetchLanguage` hook                 |
| `createLocalizedChoice` | Localized `APIApplicationCommandOptionChoice` for `setChoices`               |
| `container.i18n`        | The `InternationalizationHandler` instance                                   |

### Per-Guild Languages <Badge type="tip" text="optional" />

The old package could only read the locales Discord puts on the interaction payload. If you store a language per guild,
you can now plug a resolver in and use the asynchronous `fetch*` helpers.

The resolver can be declared up-front as part of the `i18n` client option:

```typescript theme={"system"}
import '@wolfstar/plugin-i18next/register';
import { Client } from '@wolfstar/http-framework';

const client = new Client({
	i18n: {
		fetchLanguage: async (context) => {
			if (!context.guildId) return null;
			const guild = await database.getGuild(context.guildId);
			return guild?.language ?? null;
		}
	}
});
await client.load();
```

Or assigned later on the handler, which is handy when the resolver depends on something only available after the client
is created (a database connection, for example):

```typescript theme={"system"}
import { container } from '@wolfstar/http-framework';

container.i18n.fetchLanguage = async (context) => {
	if (!context.guildId) return null;
	const guild = await database.getGuild(context.guildId);
	return guild?.language ?? null;
};
```

Both forms feed the same hook — the client option is applied to the handler on creation, and a later assignment
overwrites it. Either way the asynchronous helpers pick it up:

```typescript theme={"system"}
import { fetchKey, fetchT } from '@wolfstar/plugin-i18next';

const t = await fetchT(interaction);
const content = await fetchKey(interaction, 'commands/ping:success');
```

`resolveKey` and `resolveUserKey` stay synchronous and keep reading the interaction payload only, so existing call sites
do not change behaviour.

### Hot Module Replacement <Badge type="tip" text="optional" />

```typescript theme={"system"}
const client = new Client({
	i18n: {
		hmr: { enabled: true }
	}
});
```

When enabled, the languages directory is watched with chokidar and `container.i18n.reloadResources()` runs on every
change or deletion.

### Upgrading `i18next` from 22 to 25

The plugin depends on `i18next@^25`. If your project pins `i18next` itself, bump it and review the
[i18next migration notes](https://www.i18next.com/misc/migration-guide). In practice the typed-key helpers absorb most
of the surface, but note that `TFunction` is now generic over namespace and key prefix, so explicitly annotated
`TFunction` variables may need their type arguments updated.

### Checklist

* [ ] `@wolfstar/http-framework-i18n` removed from `package.json`
* [ ] `@wolfstar/plugin-i18next` added, `@wolfstar/http-framework` on `^3.1.0` or newer
* [ ] `import '@wolfstar/plugin-i18next/register'` is the **first** import of the entry point
* [ ] `load()` / `init()` / `addFormatters()` calls removed, options moved to `new Client({ i18n })`
* [ ] Locales directory named `languages`, or `defaultLanguageDirectory` set
* [ ] Module specifiers updated across the codebase
* [ ] `getT` / `loadedLocales` call sites moved to `container.i18n`

<h2 id="logger">
  Migrating to `@wolfstar/plugin-logger`
</h2>

`@wolfstar/logger` is deprecated. Replace it with
[`@wolfstar/plugin-logger`](https://github.com/wolfstar-project/plugins/tree/main/packages/plugin-logger), which
provides the same framework logger interface through the plugin lifecycle and adds support for multiple transports.

<Warning>
  **Prerequisites**

  `@wolfstar/plugin-logger` requires Node.js 20 or newer and `@wolfstar/http-framework@^3.4.0`.
</Warning>

If console output is all you need, `@wolfstar/http-framework` already provides `container.logger`; remove the old
package without installing the plugin. Install `@wolfstar/plugin-logger` when you need transport configuration,
Sentry integration, or a Consola, Evlog, or Winston backend.

### Overview of the Changes

| Area              | `@wolfstar/logger`                          | `@wolfstar/plugin-logger`                                    |
| ----------------- | ------------------------------------------- | ------------------------------------------------------------ |
| Setup             | Construct `Logger` directly                 | Configure `logger` when constructing `Client`                |
| Logger access     | Keep the constructed instance               | Use `container.logger`                                       |
| `LogLevel` import | `@wolfstar/logger`                          | `@wolfstar/http-framework`                                   |
| Output            | Built-in console output                     | One or more configurable transports                          |
| Colours           | Re-exported helpers from `colorette`        | No colour-helper re-exports                                  |
| Optional backends | Not available                               | Sentry, Consola, Evlog, and Winston                          |
| Custom logger     | Construct and manage it in application code | `logger.instance` remains supported and is never overwritten |

The `ILogger` methods and level ordering are unchanged: existing `trace`, `debug`, `info`, `warn`, `error`, and
`fatal` calls can keep their arguments.

### Swapping the Dependency

<CodeGroup>
  ```bash pnpm theme={"system"}
  pnpm remove @wolfstar/logger
  pnpm add @wolfstar/plugin-logger
  ```

  ```bash npm theme={"system"}
  npm uninstall @wolfstar/logger
  npm install @wolfstar/plugin-logger
  ```

  ```bash yarn theme={"system"}
  yarn remove @wolfstar/logger
  yarn add @wolfstar/plugin-logger
  ```

  ```bash bun theme={"system"}
  bun remove @wolfstar/logger
  bun add @wolfstar/plugin-logger
  ```
</CodeGroup>

Keep the plugin in `dependencies`, not `devDependencies`: it changes the logger at runtime. The backend integrations
are optional peer dependencies, so install only the ones used by your configured transports.

### Moving Logger Setup into `Client`

Replace the manually constructed logger with the `logger` client option, and read the active instance from the
framework container:

```diff theme={"system"}
-import { Logger, LogLevel } from '@wolfstar/logger';
-import { Client } from '@wolfstar/http-framework';
+import '@wolfstar/plugin-logger/register';
+import { Client, LogLevel, container } from '@wolfstar/http-framework';

-const logger = new Logger({ level: LogLevel.Debug });
-const client = new Client();
+const client = new Client({
+  logger: { level: LogLevel.Debug }
+});

-logger.info('Bot starting');
+container.logger.info('Bot starting');
```

The plugin installs its logger during `preGenericsInitialization`, before the rest of the client initializes. An
explicit `logger.instance` is preserved, so applications with their own `ILogger` implementation can keep it:

```typescript theme={"system"}
const client = new Client({
	logger: {
		level: LogLevel.Info,
		instance: myLogger
	}
});
```

### Registering the Plugin

The explicit side-effect import must run before `new Client()` when the application is built with `tsc`, uses
`build.tool: 'none'`, or does not use the Stars CLI:

```typescript theme={"system"}
import '@wolfstar/plugin-logger/register';
```

With `@wolfstar/cli@0.6.0` or newer and a `tsdown` build, installed `@wolfstar/plugin-*` packages are discovered from
runtime dependencies and their `/register` entrypoints are activated automatically. In that setup, remove the manual
import to avoid registering the plugin twice.

### Configuring Transports <Badge type="tip" text="optional" />

Without a `transports` option, the plugin uses `ConsoleTransport`. Pass an array to send every log payload to multiple
destinations:

```typescript theme={"system"}
import * as Sentry from '@sentry/node';
import { Client, LogLevel } from '@wolfstar/http-framework';
import { ConsoleTransport, SentryTransport } from '@wolfstar/plugin-logger';

Sentry.init({ dsn: process.env.SENTRY_DSN });

const client = new Client({
	logger: {
		level: LogLevel.Debug,
		transports: [new ConsoleTransport(), new SentryTransport({ client: Sentry, level: LogLevel.Error })]
	}
});
```

Each transport can have its own minimum `level`; messages below it are skipped for that destination. The Sentry SDK
and alternative logger backends are not installed automatically.

| Backend | Install        | Transport import                  |
| ------- | -------------- | --------------------------------- |
| Sentry  | `@sentry/node` | `@wolfstar/plugin-logger`         |
| Consola | `consola`      | `@wolfstar/plugin-logger/consola` |
| Evlog   | `evlog`        | `@wolfstar/plugin-logger/evlog`   |
| Winston | `winston`      | `@wolfstar/plugin-logger/winston` |

For example, a Winston transport is configured with the Winston logger instance you already own:

```typescript theme={"system"}
import { createLogger, format, transports as winstonTransports } from 'winston';
import { WinstonTransport } from '@wolfstar/plugin-logger/winston';

const winston = createLogger({
	format: format.json(),
	transports: [new winstonTransports.Console()]
});

const client = new Client({
	logger: {
		transports: [new WinstonTransport({ instance: winston })]
	}
});
```

Evlog exposes four levels and Winston's default `npm` levels do not include `fatal`. On those adapters, `trace` is
mapped to `debug` and `fatal` to `error`.

### Updating Colour Helper Imports

The old package re-exported `colorette` helpers. The plugin does not. If the application uses helpers such as `red`,
`bold`, or `stripColor`, depend on `colorette` directly and update the module specifier:

```diff theme={"system"}
-import { bold, red } from '@wolfstar/logger';
+import { bold, red } from 'colorette';
```

### Checklist

* [ ] `@wolfstar/logger` removed from `package.json`
* [ ] `@wolfstar/plugin-logger` added to runtime dependencies when custom transports are needed
* [ ] `@wolfstar/http-framework` on `^3.4.0` or newer and Node.js 20 or newer
* [ ] Manual `new Logger(...)` replaced with the `logger` client option
* [ ] Logger call sites use `container.logger`
* [ ] `/register` imported before `new Client()`, or omitted when the Stars bundler auto-registers plugins
* [ ] Optional backend peer dependencies installed for every configured transport
* [ ] Colour helpers imported directly from `colorette`
