Skip to content

feat(rstack): add plugin SPI - #336

Open
ScriptedAlchemy wants to merge 11 commits into
rstackjs:mainfrom
ScriptedAlchemy:codex/rstack-plugin-spi
Open

feat(rstack): add plugin SPI#336
ScriptedAlchemy wants to merge 11 commits into
rstackjs:mainfrom
ScriptedAlchemy:codex/rstack-plugin-spi

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Contributor

Summary

  • add a small public Rstack CLI plugin SPI with explicit define.plugins registration, sequential setup, custom commands, and typed config modifiers
  • compose plugin contributions into Rsbuild, Rslib, Rstest, Rslint, Rspress, fmt, and staged config loading while preserving native-tool lifecycle ownership and CLI precedence
  • validate plugin and command definitions, isolate config-loading sessions, and cover command dispatch, modifiers, reloads, Rstest inheritance, and type exports
  • document the lifecycle, native extension boundary, compatibility policy, and non-goals in aligned English and Chinese guides

Testing

  • pnpm format
  • pnpm check
  • pnpm check:spell
  • pnpm build
  • pnpm --filter rstack build:native
  • pnpm test
  • pnpm --dir website build

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 623c4cba77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/rstack/src/rsbuildConfig.ts
@ScriptedAlchemy

Copy link
Copy Markdown
Contributor Author

RFC: Rstack CLI plugin SPI

Status: Proposed in this PR

Decision

Add a small, general Rstack CLI plugin SPI. Rstack owns orchestration only; plugins register commands and transform native tool configurations. Domain-specific lifecycle and result processing remain in Rsbuild, Rslib, Rstest, Rslint, Rspress, Prettier, and lint-staged.

This fills the missing Rstack-wide extension point. Native plugins and reporters can extend one tool, but they cannot register an rs command or coordinate configuration contributions across the Rstack toolchain.

Contract

type RstackPlugin = {
  name: string;
  setup(api: RstackPluginAPI): void | Promise<void>;
};

type RstackPluginAPI = {
  readonly context: {
    cwd: string;
    command: string;
    args: readonly string[];
    configFilePath: string | null;
  };
  readonly logger: RstackLogger;
  addCommand(command: {
    name: string;
    handler(args: readonly string[]): void | Promise<void>;
  }): void;
  modifyConfig<K extends keyof RstackConfigMap>(
    kind: K,
    handler: ConfigModifier<RstackConfigMap[K]>,
  ): void;
};

RstackConfigMap covers app, lib, doc, test, lint, fmt, and staged using each tool native config type. Plugins are registered explicitly:

import { define } from "rstack";
import { pluginAcme } from "@acme/rstack-plugin";

define.plugins([pluginAcme()]);

Nested, asynchronous, conditional, and falsy entries are supported in declaration order. Rstack performs no package scanning.

Lifecycle and precedence

  1. Load one rstack.config.* and capture configs and plugins.
  2. Flatten and validate every plugin.
  3. Run plugin setup functions sequentially.
  4. Resolve the selected native config factory.
  5. Run matching modifiers sequentially; each may mutate, replace, or resolve asynchronously.
  6. Apply Rstack internal invariants and start the native tool.
  7. Existing child CLI overlays run afterward and retain precedence.

Rstest first applies needed app or lib modifiers for automatic extends, constructs inheritance, then applies test modifiers. Explicit Rstest extends continues to opt out. A missing staged config remains an error unless a staged modifier contributes one. Root help and version remain config-free.

Validation and compatibility

Rstack rejects invalid or duplicate plugin names, invalid or duplicate commands, and attempts to replace built-ins or aliases. Plugin initialization is isolated per config load and repeats on reload. Existing projects without plugins retain current behavior.

The exported SPI types follow Rstack semantic versioning. Native config values follow the versions of their respective tools, so plugin packages should declare compatible peer ranges.

Non-goals

  • automatic discovery or dependency scanning;
  • built-in command replacement;
  • global before/after hooks or middleware;
  • plugin ordering descriptors or a service registry;
  • cross-tool event or result abstractions.

Build hooks, reporters, lint execution, and documentation lifecycle remain in native tool extension APIs.

Verification

The PR includes runtime validation, command dispatch, all seven config kinds, async ordering, reload isolation, Rstest inheritance, CLI precedence, TypeScript export resolution, and aligned English/Chinese documentation. Required formatting, type, lint, spelling, build, native build, and test checks pass locally.

@ScriptedAlchemy

Copy link
Copy Markdown
Contributor Author

@chenjiahan @fi3ework, could you review this Rstack CLI plugin SPI when you have a chance? It adds a deliberately small public surface for plugin commands and typed native-tool config transforms. The short RFC is attached above, the branch is current with main, and local verification is green. The forked Lint/Test workflows also need maintainer approval to start.

@chenjiahan chenjiahan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin interface seems very useful. I haven’t thought deeply about a plugin design for Rstack CLI yet.

Given that we already have Rspack plugins, Rsbuild plugins and Rspress plugins, I’d like to be especially careful when introducing another plugin system to avoid confusing our users. I’ll take a closer look at the design proposed in this PR

# Conflicts:
#	packages/rstack/src/rslibConfig.ts
#	packages/rstack/src/rspressConfig.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants