Skip to main content

npm

Source

Description

@wolfstar/kit provides defineModule and the runtime that installs Stars modules, the counterpart of @nuxt/kit. A module is a package that a project lists in modules in stars.config. While the stars CLI loads the project, it installs each module. A module can register runtime plugins in the bot, add auto import presets, add directories of pieces and locales, run code at the client’s lifecycle points, and hook into the CLI. @wolfstar/kit depends on @wolfstar/schema only. A module declares it as a peer dependency without pulling in the framework or the CLI.

When to use it

  • Use modules in stars.config to activate a module, such as the @wolfstar/plugin-* packages. You do not import @wolfstar/kit yourself for this, but the plugin packages list it as an optional peer, so install it in the project.
  • Depend on @wolfstar/kit when you write a package that should be installable from stars.config.

Activating modules

List each module as a package specifier, or as a [specifier, options] tuple to pass options. Falsy entries are dropped, so condition && 'some-module' works:
The options are merged over the module’s defaults: plain objects merge deeply, and arrays and other values replace the default. A module that declares a meta.configKey also reads its options from a top-level stars.config key of that name. A package listed in modules is no longer activated through the /register import the CLI otherwise injects for @wolfstar/plugin-* packages. With future.compatibilityVersion: 7, the CLI injects no /register import at all, so only modules register.

Writing a module

Export the module as the package’s default export with defineModule. Like definePlugin, it is an identity helper that types the definition.
The runtime plugin is a regular definePlugin export. When the export is a function, the module runtime calls it with the options given to addPlugin:

Module definition

meta.compatibility.framework is a semver range checked against the project’s installed @wolfstar/http-framework, and meta.compatibility.stars against the running @wolfstar/cli. A mismatch fails the install early instead of leaving a plugin that never runs. A range is only checked when the CLI finds the version.

Reading options from a config key

Set meta.configKey to let users configure the module from a top-level key of stars.config, instead of only through the [specifier, options] tuple in modules. The options setup receives are merged in this order, each layer over the previous one:
  1. The module’s defaults.
  2. The object under configKey in stars.config.
  3. The inline options: the [specifier, options] tuple, or the options passed to ctx.installModule.
To type the key in defineConfig, augment the StarsConfig interface of @wolfstar/schema:
The augmentation only applies once TypeScript loads the module’s types. stars prepare does not reference installed modules yet, so import the package in stars.config.ts. A configKey must follow these rules, or the install fails with MODULE_INVALID:
  • It is a non-empty string.
  • It is not a built-in stars.config key: root, entry, build, dev, codegen, imports, env, hooks, modules, experimental, future, vite, or tsdown.
  • No other installed module claims it.
  • The value under it in stars.config, when set, is a plain object.
Modules installed through dependencies or ctx.installModule read their key too. Once every module is set up, the CLI reports any top-level key that no installed module claimed as UNKNOWN_OPTION, so a misspelled key is still an error. A module is installed once, by meta.name. When two paths install the same module, the first installation wins, whatever options the second one passes.

The setup context

The plugin runs in the bot’s process, while setup runs in the CLI’s. That is why addPlugin takes a plugin source instead of a plugin value: a string or URL, or { from, export?, options? }.
  • from is a package specifier resolved from the project, an absolute path, or a file: URL such as new URL('./plugin.js', import.meta.url). A relative path is rejected, because it would resolve differently in each build tool.
  • export names the export to use and defaults to 'default'. When the export is a function, the runtime calls it as a factory with options. A legacy Plugin class is passed to Client.use as it is, never called.
  • options must be JSON-serialisable, because the CLI writes it into the built entry.

Adding pieces

ctx.addPiecesDir(directory) registers a directory of pieces that the bot loads next to its own. Use it to ship commands, listeners, interaction handlers, or preconditions in a module. The directory holds one folder per store (commands, listeners, interaction-handlers, preconditions), like the bot’s own src:
  • Give an absolute path or a file: URL. A relative path fails the install with MODULE_PIECES_INVALID.
  • The pieces are read from files at runtime, so the directory has to exist in the built bot. A build that bundles everything (Vite, Nitro) has no such directory, so addPiecesDir is not useful there.
  • Only the stores that exist when the module runtime runs are covered: the framework’s four, and any registered by an earlier import. A folder for a store that a plugin registers later is ignored.

Adding locales

ctx.addLocales(directory) registers a directory of translations next to the project’s own languages directory. Use it when the pieces or plugins of a module need their own strings. The directory is laid out as <language>/<namespace>.json, and nested folders make namespaces such as commands/shared:
  • Give an absolute path or a file: URL to a directory that exists when the module is set up. setup reads its namespaces then.
  • A runtime plugin generated by the CLI merges the directory into the client’s i18n option before @wolfstar/plugin-i18next builds its handler. The project’s own languages directory is kept.
  • It needs @wolfstar/plugin-i18next, and a framework that re-exports getRootData from @wolfstar/http-framework.
  • Like addPiecesDir, the absolute path is written into the generated source and read at runtime. A build that bundles everything (Vite, Nitro), or one moved to another machine, does not find the directory.
  • A relative path, or a directory that cannot be read, fails the install with MODULE_LOCALES_INVALID.

Runtime client hooks

A module can run code in the bot at the client’s lifecycle points, without writing a full runtime plugin. Declare the hook in hooks with a client: name, or call ctx.addClientHook(name, source) from setup. The supported names are:
  • client:preGenericsInitialization
  • client:preInitialization
  • client:postInitialization
  • client:preLoad
  • client:postListen
The hook runs in the bot’s process, while setup runs in the CLI’s, so it is given by source like a plugin: a package specifier, an absolute path or a file: URL, or { from, export? }. The export (default 'default') is a function called with (client, clientOptions), the same arguments a definePlugin hook gets.
  • Only client:preLoad and client:postListen hooks may be async. The other three run inside the synchronous Client constructor.
  • A module’s hooks share one runtime plugin named after the module, so a failing hook is reported with the module’s name.
  • A module declares one hook per name. A second hook with the same name, an unknown client: name, or a hook that is not given by source (such as a function, or a relative path) fails the install with MODULE_HOOK_INVALID.

Production with tsc or none

The tsdown, vite, and Nitro builds register the runtime plugins through the bot’s entry. The tsc and none build tools cannot transform the entry, so the plugins, pieces directories, locales, and client hooks are preloaded with node --import instead:
  • stars dev preloads them for you.
  • stars prepare and stars build write .stars/modules.mjs. Start the production bot with it preloaded:
When env is enabled, .stars/modules.mjs imports .stars/env.mjs first, so the environment is loaded before any plugin is evaluated. stars build, stars prepare, and stars info report a MODULES_PRELOAD_REQUIRED warning for such projects.

Errors

When a module cannot be installed, @wolfstar/kit throws a ModuleError. Its moduleName is the module’s meta.name, or the specifier it was listed by, and its code is one of: The stars CLI reports a failing module as a MODULE_FAILED diagnostic and exits with code 2.

Programmatic usage

setupModules({ config, hooks, load, versions }) is the function the CLI calls. load imports a specifier from the project, and hooks is a hookable-shaped registry. The result, a ModulesRuntime, lists the installed modules and the runtime plugins, pieces directories (piecesDirs), locales, client hooks (clientHooks), import presets, and configKeys they contributed. mergeOptions(defaults, options) exposes the merge used for module options.