diff --git a/.claude/commands/create-eng-docs.md b/.claude/commands/create-eng-docs.md
index 1de32d519..498187f22 100644
--- a/.claude/commands/create-eng-docs.md
+++ b/.claude/commands/create-eng-docs.md
@@ -215,12 +215,14 @@ Once confirmed, create the documentation file:
> is good practice to add a **persistent**, **unique** id to the
> component:
- **Testing**:
- - **Mandatory**: Start with this exact disclaimer:
+- Testing examples are auto-generated from `.docs.spec.tsx` files
+ - **Mandatory**: Include this text before the injection token:
> These examples demonstrate how to test your implementation when using
> [Component] in your application. The component's internal functionality
> is already tested by Nimbus - these patterns help you verify your
> integration and application-specific logic.
- - Provide realistic test examples (Rendering, Interaction, etc.)
+ - Add injection token: `{{docs-tests: {component-name}.docs.spec.tsx}}`
+ - Create companion `.docs.spec.tsx` file with test sections (see Step 6.1)
- **Resources**:
- Link to Storybook (use "link-tbd" placeholder)
- **Internal component links must start with '/'** (e.g.,
@@ -237,6 +239,82 @@ Once confirmed, create the documentation file:
9. **Write the file** using the Write tool
+### **Step 6.1: Create Documentation Test File**
+
+**IMPORTANT**: After creating the `.dev.mdx` file, create the companion `.docs.spec.tsx` test file.
+
+1. **Create test file**: `{component-name}.docs.spec.tsx` in same directory as component
+
+2. **File structure**:
+ ```typescript
+ import { describe, it, expect, vi } from 'vitest';
+ import { render, screen } from '@testing-library/react';
+ import userEvent from '@testing-library/user-event';
+ import { ComponentName, NimbusProvider } from '@commercetools/nimbus';
+
+ /**
+ * @docs-section basic-rendering
+ * @docs-title Basic Rendering Tests
+ * @docs-description Verify the component renders with expected elements
+ * @docs-order 1
+ */
+ describe('ComponentName - Basic rendering', () => {
+ it('renders component', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('...')).toBeInTheDocument();
+ });
+ });
+
+ /**
+ * @docs-section interactions
+ * @docs-title Interaction Tests
+ * @docs-description Test user interactions with the component
+ * @docs-order 2
+ */
+ describe('ComponentName - Interactions', () => {
+ it('handles user interaction', async () => {
+ const user = userEvent.setup();
+ render(
+
+
+
+ );
+
+ // Test interactions
+ });
+ });
+ ```
+
+3. **JSDoc tags required**:
+ - `@docs-section` - Unique ID for section
+ - `@docs-title` - Display title
+ - `@docs-description` - Brief description
+ - `@docs-order` - Sort order (0 for setup, 1+ for tests)
+
+4. **Test patterns to include** (based on component features):
+ - Basic rendering (always)
+ - Interactions (if interactive)
+ - Controlled mode (if supports value/onChange)
+ - States (disabled, invalid, readonly, required - if component has these props)
+ - Component-specific features
+
+5. **Critical rules**:
+ - ✅ Every `render()` must wrap with ``
+ - ✅ Import NimbusProvider from `@commercetools/nimbus`
+ - ✅ Use `vi.fn()` for mocks (not `jest.fn()`)
+ - ✅ Use `userEvent.setup()` for interactions
+ - ✅ Base test names on component features, not generic patterns
+
+6. **Verify tests**:
+ ```bash
+ pnpm test:unit {component-name}.docs.spec.tsx
+ ```
+
### **Step 7: Validate Documentation**
After generating the file, run validation checks:
@@ -273,7 +351,9 @@ After generating the file, run validation checks:
- [ ] Examples are realistic and functional
- [ ] TypeScript types are correct
- [ ] Accessibility requirements documented
-- [ ] Testing examples provided
+- [ ] Testing section has `{{docs-tests:}}` injection token
+- [ ] Companion `.docs.spec.tsx` file created with test sections
+- [ ] Tests pass when run with `pnpm test:unit`
### Structure Checklist
@@ -286,12 +366,15 @@ After generating the file, run validation checks:
### Code Example Checklist
- [ ] All interactive examples use `jsx-live-dev`
-- [ ] All type/test examples use `tsx`
+- [ ] All type examples use `tsx`
- [ ] Examples follow `const App = () => { }` pattern
- [ ] State declarations use prop type inference pattern
- [ ] Controlled examples include state display with Text component
-- [ ] Test examples use `userEvent.setup()` pattern
-- [ ] Portal/popover tests use `waitFor` and document queries
+- [ ] Test examples in `.docs.spec.tsx` file (not in MDX)
+- [ ] Tests wrap every `render()` with ``
+- [ ] Tests use `vi.fn()` for mocks (not `jest.fn()`)
+- [ ] Tests use `userEvent.setup()` for interactions
+- [ ] Portal/popover tests use `waitFor` with document queries
### Link Checklist
@@ -312,12 +395,19 @@ Provide a final summary:
````markdown
## Documentation Created
-**Component**: {ComponentName} **Type**: [Base Component / Field Pattern]
-**File**: {full-path-to-file} **Size**: {file size in lines}
+**Component**: {ComponentName}
+**Type**: [Base Component / Field Pattern]
+**Files Created**:
+- `.dev.mdx`: {full-path-to-dev-mdx}
+- `.docs.spec.tsx`: {full-path-to-docs-spec}
### Sections Included
-- [List all sections included]
+- [List all sections included in .dev.mdx]
+
+### Test Sections Created
+
+- [List all test sections in .docs.spec.tsx with @docs-section IDs]
### Key Features Documented
@@ -325,20 +415,26 @@ Provide a final summary:
### Code Examples
-- Total interactive examples: X
-- Total test examples: Y
+- Total interactive examples (jsx-live-dev): X
+- Total test sections (in .docs.spec.tsx): Y
+- Total test cases: Z
### Next Steps
1. Review the generated documentation
-2. Test all interactive examples in the docs site
-3. Update the Storybook link once available
-4. Add any component-specific advanced patterns
-5. Review with the team before publishing
+2. Run tests: `pnpm test:unit {component-name}.docs.spec.tsx`
+3. Build docs: `pnpm build:docs`
+4. Test all interactive examples in the docs site
+5. Update the Storybook link once available
+6. Add any component-specific advanced patterns
+7. Review with the team before publishing
### Useful Commands
```bash
+# Run documentation tests
+pnpm test:unit {component-name}.docs.spec.tsx
+
# Start docs site to preview
pnpm start:docs
diff --git a/docs/component-guidelines.md b/docs/component-guidelines.md
index 896800c16..3a079b75a 100644
--- a/docs/component-guidelines.md
+++ b/docs/component-guidelines.md
@@ -36,7 +36,9 @@ detailed guidelines:
### Testing Files
- **[Unit Tests ({utility}.spec.ts)](./file-type-guidelines/unit-testing.md)** -
- Fast, isolated tests for utilities and hooks only (components use Storybook stories)
+ Fast, isolated tests for utilities, hooks, and documentation examples
+- **[Documentation Tests ({component}.docs.spec.tsx)](../engineering-docs-validation.md)** -
+ Consumer-facing test examples automatically injected into `.dev.mdx` documentation
### Styling System Files (When Needed)
@@ -81,9 +83,11 @@ detailed guidelines:
stories with play functions for testing
8. **[Document](./file-type-guidelines/documentation.md)** - Create MDX
documentation
-9. **[Export](./file-type-guidelines/barrel-exports.md)** - Set up public API
+9. **[Add Documentation Tests](../engineering-docs-validation.md)** - Create
+ `.docs.spec.tsx` with consumer test examples (optional but recommended)
+10. **[Export](./file-type-guidelines/barrel-exports.md)** - Set up public API
-**Note**: All component behavior is tested in Storybook stories with play functions. Unit tests are reserved for utilities and hooks only.
+**Note**: All component behavior is tested in Storybook stories with play functions. Documentation tests (`.docs.spec.tsx`) provide consumer-facing examples.
### 🎨 Adding Styling to Components
@@ -178,6 +182,8 @@ component-name/
├── component-name.i18n.ts # i18n messages (if needed)
├── component-name.stories.tsx # Storybook stories (required)
├── component-name.mdx # Documentation (required)
+├── component-name.dev.mdx # Engineering guide (optional)
+├── component-name.docs.spec.tsx # Documentation tests (optional, recommended)
├── components/ # Compound parts (if compound)
│ ├── component-name.root.tsx
│ ├── component-name.part.tsx
@@ -216,6 +222,8 @@ component-name/
| i18n | `{component-name}.i18n.ts` | `button.i18n.ts` |
| Stories | `{component-name}.stories.tsx` | `button.stories.tsx` |
| Documentation | `{component-name}.mdx` | `button.mdx` |
+| Engineering Docs | `{component-name}.dev.mdx` | `button.dev.mdx` |
+| Documentation Tests | `{component-name}.docs.spec.tsx` | `button.docs.spec.tsx` |
### Import Conventions
diff --git a/docs/engineering-docs-validation.md b/docs/engineering-docs-validation.md
new file mode 100644
index 000000000..97657af71
--- /dev/null
+++ b/docs/engineering-docs-validation.md
@@ -0,0 +1,380 @@
+# Engineering Documentation Test Integration
+
+This guide explains how test code examples in engineering documentation (`.dev.mdx` files) are automatically kept up-to-date using real, executable tests.
+
+## Overview
+
+Engineering documentation (`.dev.mdx` files) includes test code examples showing consumers how to test components. These examples are now **automatically generated from real test files**, ensuring they're always valid and up-to-date.
+
+## How It Works
+
+### The Inverted Strategy
+
+Instead of extracting tests from documentation, we **inject tests into documentation** at build time:
+
+1. **Tests live in `.docs.spec.tsx` files** - Real, executable test files colocated with components
+2. **Tests run with normal test suite** - No special CLI workflow needed
+3. **Build extracts test sections** - TypeScript AST parser finds tagged sections
+4. **Documentation generated** - Test code injected into MDX at build time
+
+**Key Benefit:** Tests are the source of truth. Documentation is derived from working tests.
+
+## Writing Documentation Tests
+
+### 1. Create Test File
+
+Create a `.docs.spec.tsx` file alongside your component:
+
+```
+text-input/
+├── text-input.tsx
+├── text-input.types.ts
+├── text-input.dev.mdx # Developer guide
+├── text-input.docs.spec.tsx # NEW: Tests for documentation
+└── text-input.stories.tsx # Storybook tests
+```
+
+### 2. Tag Test Sections with JSDoc
+
+Use JSDoc tags to mark test sections for documentation:
+
+```typescript
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { TextInput, NimbusProvider } from '@commercetools/nimbus';
+
+/**
+ * @docs-section basic-rendering
+ * @docs-title Basic Rendering Tests
+ * @docs-description Verify the component renders with expected elements
+ * @docs-order 1
+ */
+describe('TextInput - Basic rendering', () => {
+ it('renders input element', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ });
+
+ it('renders with placeholder text', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ });
+});
+
+/**
+ * @docs-section interactions
+ * @docs-title Interaction Tests
+ * @docs-description Test user interactions with the component
+ * @docs-order 2
+ */
+describe('TextInput - Interactions', () => {
+ it('updates value when user types', async () => {
+ const user = userEvent.setup();
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole('textbox');
+ await user.type(input, 'Hello World');
+
+ expect(input).toHaveValue('Hello World');
+ });
+});
+```
+
+### 3. Add Injection Token to MDX
+
+In your `.dev.mdx` file, add a single token where tests should appear:
+
+```mdx
+## Testing your implementation
+
+These examples demonstrate how to test your implementation when using ComponentName in your application.
+
+{{docs-tests: component-name.docs.spec.tsx}}
+
+## Additional testing considerations
+
+[Manual content about edge cases, patterns, etc.]
+```
+
+### 4. Build Documentation
+
+When you build docs, test sections are automatically extracted and injected:
+
+```bash
+pnpm build:docs
+```
+
+The build process:
+1. Finds `{{docs-tests:}}` tokens in MDX
+2. Locates companion `.docs.spec.tsx` file
+3. Parses TypeScript AST
+4. Extracts sections with JSDoc tags
+5. Generates markdown sections with code blocks
+6. Cleans code (removes test infrastructure)
+7. Injects into documentation
+
+## JSDoc Tag Reference
+
+### Required Tags
+
+| Tag | Purpose | Example |
+|-----|---------|---------|
+| `@docs-section` | Unique identifier for the section | `@docs-section basic-rendering` |
+| `@docs-title` | Display title in documentation | `@docs-title Basic Rendering Tests` |
+| `@docs-description` | Brief description of what tests demonstrate | `@docs-description Verify component renders correctly` |
+| `@docs-order` | Sort order in documentation (lower = earlier) | `@docs-order 1` |
+
+### Example Usage
+
+```typescript
+/**
+ * @docs-section controlled-mode
+ * @docs-title Testing Controlled Mode
+ * @docs-description Test controlled component behavior
+ * @docs-order 3
+ */
+describe('Component - Controlled mode', () => {
+ // Tests here
+});
+```
+
+## Code Transparency
+
+The build process shows **full, unmodified test code** in documentation. What you write in `.docs.spec.tsx` files is exactly what consumers see.
+
+### What Consumers See
+
+All code is preserved as-is, including:
+
+✅ **Complete test setup:**
+- `renderWithProvider()` helper function
+- `NimbusProvider` imports
+- `ReactNode` type imports
+- All test infrastructure
+
+✅ **Full context:**
+- Exact imports you use
+- Helper functions defined
+- Complete, copy-paste ready examples
+
+### Example
+
+**In test file (what you write):**
+
+```typescript
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { TextInput, NimbusProvider } from '@commercetools/nimbus';
+
+describe('Tests', () => {
+ it('works', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ });
+});
+```
+
+**In documentation (what consumers see):**
+
+```tsx
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { TextInput, NimbusProvider } from '@commercetools/nimbus';
+
+describe('Tests', () => {
+ it('works', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ });
+});
+```
+
+**Result:** Exactly the same! No transformations, no hidden setup. Complete transparency.
+
+## Best Practices
+
+### ✅ DO
+
+- **Keep examples simple and focused** - One concept per test section
+- **Use realistic test patterns** - Show what consumers would actually write
+- **Include common testing scenarios** - Basic rendering, interactions, states
+- **Wrap every render with ``** - Shows consumers the required setup explicitly
+- **Group related tests** - Use describe blocks with clear names
+- **Order logically** - Use `@docs-order` to sequence from simple to complex
+- **Be explicit about boilerplate** - Don't hide setup requirements in helpers
+
+### ❌ DON'T
+
+- **Include overly complex test setups** - Keep examples accessible
+- **Use internal implementation details** - Focus on public API
+- **Add test utilities not available to consumers** - Stick to standard libraries
+- **Skip JSDoc tags** - All required tags must be present
+- **Duplicate test logic** - One source of truth in `.docs.spec.tsx`
+
+## File Naming Convention
+
+| File Type | Purpose | Example |
+|-----------|---------|---------|
+| `.docs.spec.tsx` | Tests for documentation examples | `text-input.docs.spec.tsx` |
+| `.spec.tsx` | Internal unit tests (not shown in docs) | `text-input.spec.tsx` |
+| `.stories.tsx` | Storybook interaction tests | `text-input.stories.tsx` |
+
+**Note:** `.docs.spec.tsx` tests are discovered automatically by Vitest and run with the normal test suite.
+
+## Running Tests
+
+Documentation tests run normally with existing commands:
+
+```bash
+# Run all tests (includes docs tests)
+pnpm test
+
+# Run only unit tests (includes docs tests)
+pnpm test:unit
+
+# Run specific docs test file
+pnpm test:unit text-input.docs.spec.tsx
+
+# Run in watch mode
+pnpm test:unit --watch
+```
+
+**No special CLI workflow needed!**
+
+## Verification Checklist
+
+When creating or updating documentation tests:
+
+- [ ] Test file named `{component}.docs.spec.tsx`
+- [ ] File colocated with component
+- [ ] All describe blocks have JSDoc tags (`@docs-section`, `@docs-title`, `@docs-description`, `@docs-order`)
+- [ ] Every `render()` call wraps component with ``
+- [ ] NimbusProvider imported from `@commercetools/nimbus`
+- [ ] MDX file has `{{docs-tests: filename}}` token
+- [ ] Tests pass when run with `pnpm test:unit`
+- [ ] Documentation builds without errors
+- [ ] Generated docs show full code including provider (no cleaning)
+
+## Troubleshooting
+
+### Tests Fail Locally
+
+**Issue:** Tests fail with "useContext returned undefined" errors
+
+**Solution:** Ensure every `render()` call wraps the component with ``:
+
+```typescript
+// ✅ Correct - Provider wrapper included
+describe('Tests', () => {
+ it('works', () => {
+ render(
+
+
+
+ );
+ });
+});
+
+// ❌ Wrong - Missing provider
+describe('Tests', () => {
+ it('works', () => {
+ render(); // Will fail with context error!
+ });
+});
+```
+
+### Documentation Not Updating
+
+**Issue:** Changes to test file don't appear in docs
+
+**Solution:**
+1. Rebuild nimbus-docs-build package: `pnpm --filter @commercetools/nimbus-docs-build build`
+2. Rebuild documentation: `pnpm build:docs`
+3. Restart dev server if running
+
+### Token Not Replaced
+
+**Issue:** `{{docs-tests:}}` token appears in documentation
+
+**Possible causes:**
+- Test file not found (check filename matches exactly)
+- No sections found (add `@docs-section` JSDoc tags)
+- Syntax error in test file (run tests to verify)
+
+**Debug:**
+```bash
+# Check console output during docs build for warnings
+pnpm build:docs
+
+# Look for messages like:
+# "Test file not found: ..."
+# "No test sections found in ..."
+```
+
+### Code Appears Malformed
+
+**Issue:** Generated code has syntax errors
+
+**Solution:**
+- Verify test file has valid JSX syntax
+- Ensure all imports are valid
+- Run tests to confirm code is executable
+
+## Migration from Old System
+
+If migrating from the HTML comment tag system:
+
+1. **Extract test code** from `` tags in `.dev.mdx`
+2. **Create `.docs.spec.tsx`** file with extracted tests
+3. **Add JSDoc tags** to each describe block
+4. **Wrap all render calls** with `` explicitly
+5. **Add NimbusProvider import** to imports
+6. **Replace test sections** in MDX with `{{docs-tests: filename}}`
+7. **Verify** tests pass and docs build correctly
+
+## Example: Complete TextInput Implementation
+
+See `packages/nimbus/src/components/text-input/` for a complete reference:
+
+- `text-input.docs.spec.tsx` - Test file with 5 sections, 14 tests
+- `text-input.dev.mdx` - Uses `{{docs-tests: text-input.docs.spec.tsx}}`
+- Generated docs at `apps/docs/src/data/routes/components-inputs-textinput.json`
+
+## Benefits
+
+✅ **Always up-to-date** - Tests ARE the examples
+✅ **Type-safe** - TypeScript catches API changes
+✅ **IDE support** - Full autocomplete and type checking
+✅ **No duplication** - Single source of truth
+✅ **Normal workflow** - Tests run with `pnpm test`
+✅ **Build-time validation** - Missing tests fail docs build
+✅ **Clean examples** - Infrastructure removed automatically
+
+---
+
+Last updated: January 2025
diff --git a/docs/file-type-guidelines/unit-testing.md b/docs/file-type-guidelines/unit-testing.md
index 3cbd5c90f..f36fd5796 100644
--- a/docs/file-type-guidelines/unit-testing.md
+++ b/docs/file-type-guidelines/unit-testing.md
@@ -11,28 +11,44 @@ Unit test files (`{utility-name}.spec.ts` or `{hook-name}.spec.ts`) provide fast
## When to Use
-Unit tests are exclusively for non-component code:
+Unit tests are used for:
- **Utility functions and helpers** - Pure functions, formatters, validators, data transformers
- **React hooks** - Custom hooks tested in isolation with `renderHook`
- **Business logic** - Calculations, algorithms, data processing
- **Validation functions** - Input validators, schema validators
- **Helper modules** - String manipulation, date formatting, number formatting
+- **Documentation examples** - Consumer-facing test patterns (in `.docs.spec.tsx` files)
-**For components**: Use Storybook stories with play functions to test all component behavior, interactions, visual states, and accessibility.
+**For component behavior testing**: Use Storybook stories with play functions to test all component interactions, visual states, and accessibility.
-### Unit Tests vs Storybook Tests
+### Documentation Tests (`.docs.spec.tsx`)
-| Aspect | Unit Tests | Storybook Tests |
-|--------|------------|-----------------|
-| **Environment** | JSDOM (fast, simulated) | Real browser (accurate) |
-| **Purpose** | Utility/hook logic verification | Component behavior & interactions |
-| **Speed** | Very fast (~ms per test) | Slower (~seconds per story) |
-| **Focus** | Pure functions, hooks | UI, user flows, visual states, a11y |
-| **Use For** | Utilities, hooks only | ALL components |
-| **Required** | Only for utilities/hooks | For ALL interactive components |
+A special category of unit tests used for engineering documentation:
-**Testing Strategy**: Components are testable in JSDOM (for consumers using JSDOM), but Nimbus tests all component behavior exclusively in Storybook stories with play functions.
+- **Purpose**: Provide real, working test examples for consumers
+- **Location**: Colocated with components (e.g., `text-input.docs.spec.tsx`)
+- **Integration**: Automatically injected into `.dev.mdx` documentation at build time
+- **Workflow**: Write once, used in both test suite AND documentation
+
+See [Engineering Documentation Test Integration](../engineering-docs-validation.md) for complete details.
+
+### Unit Tests vs Storybook Tests vs Documentation Tests
+
+| Aspect | Unit Tests | Storybook Tests | Documentation Tests |
+|--------|------------|-----------------|---------------------|
+| **Environment** | JSDOM (fast, simulated) | Real browser (accurate) | JSDOM (consumer-friendly) |
+| **Purpose** | Utility/hook logic verification | Component behavior & interactions | Consumer test examples |
+| **Speed** | Very fast (~ms per test) | Slower (~seconds per story) | Very fast (~ms per test) |
+| **Focus** | Pure functions, hooks | UI, user flows, visual states, a11y | Integration patterns, public API |
+| **Use For** | Utilities, hooks | ALL components | Documentation examples |
+| **File Pattern** | `*.spec.{ts,tsx}` | `*.stories.tsx` | `*.docs.spec.tsx` |
+| **Audience** | Internal developers | Internal developers | External consumers |
+
+**Testing Strategy**:
+- **Nimbus internal testing**: Storybook stories test all component behavior
+- **Consumer documentation**: `.docs.spec.tsx` tests demonstrate integration patterns
+- **Utility testing**: Standard `.spec.ts` files test helper functions and hooks
## Testing Infrastructure
diff --git a/packages/nimbus-docs-build/package.json b/packages/nimbus-docs-build/package.json
index a71d072c6..f502925d6 100644
--- a/packages/nimbus-docs-build/package.json
+++ b/packages/nimbus-docs-build/package.json
@@ -25,6 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
+ "@typescript-eslint/types": "^8.48.0",
+ "@typescript-eslint/typescript-estree": "^8.48.0",
"gray-matter": "^4.0.3",
"react-docgen-typescript": "^2.4.0",
"remark": "^15.0.1",
diff --git a/packages/nimbus-docs-build/src/parsers/index.ts b/packages/nimbus-docs-build/src/parsers/index.ts
index 6397f7529..eda1729f6 100644
--- a/packages/nimbus-docs-build/src/parsers/index.ts
+++ b/packages/nimbus-docs-build/src/parsers/index.ts
@@ -5,3 +5,4 @@ export * from "./parse-mdx.js";
export * from "./parse-types.js";
export * from "./filter-props.js";
export * from "./process-types.js";
+export * from "./test-extractor.js";
diff --git a/packages/nimbus-docs-build/src/parsers/parse-mdx.ts b/packages/nimbus-docs-build/src/parsers/parse-mdx.ts
index 1722deeaf..ecf0d76f2 100644
--- a/packages/nimbus-docs-build/src/parsers/parse-mdx.ts
+++ b/packages/nimbus-docs-build/src/parsers/parse-mdx.ts
@@ -16,6 +16,125 @@ import { mdxDocumentSchema } from "../schemas/mdx-document.js";
import type { MdxDocument, TocItem } from "../types/mdx.js";
import { menuToPath, getPathFromMonorepoRoot } from "../utils/index.js";
import { validateFilePath } from "../utils/validate-file-path.js";
+import { extractTestSections } from "./test-extractor.js";
+import type { TestSection } from "../types/test-section.js";
+
+/**
+ * Resolve the path to a .docs.spec.ts file from an MDX file path
+ * @param mdxFilePath - Absolute path to the MDX file
+ * @param testFileName - Name of the test file from the {{docs-tests:}} token
+ * @returns Absolute path to the test file
+ */
+const resolveTestFilePath = (
+ mdxFilePath: string,
+ testFileName: string
+): string => {
+ const mdxDir = path.dirname(mdxFilePath);
+ return path.join(mdxDir, testFileName);
+};
+
+/**
+ * Generate markdown section from a test section
+ * @param section - Extracted test section
+ * @returns Markdown string with heading, description, and code block
+ */
+const generateMarkdownSection = (section: TestSection): string => {
+ const lines: string[] = [];
+
+ // Add heading
+ lines.push(`### ${section.title}`);
+ lines.push("");
+
+ // Add description if present
+ if (section.description) {
+ lines.push(section.description);
+ lines.push("");
+ }
+
+ // Add code block with full, unmodified code
+ lines.push("```tsx");
+
+ // Add imports (deduplicated)
+ const uniqueImports = Array.from(new Set(section.imports));
+ if (uniqueImports.length > 0) {
+ lines.push(uniqueImports.join("\n"));
+ lines.push("");
+ }
+
+ // Add test code (unmodified - show exactly what developers write)
+ lines.push(section.code);
+ lines.push("```");
+ lines.push("");
+
+ return lines.join("\n");
+};
+
+/**
+ * Inject test sections into MDX content by replacing {{docs-tests:}} tokens
+ * @param mdxContent - MDX content that may contain {{docs-tests:}} tokens
+ * @param mdxFilePath - Absolute path to the MDX file (for resolving test file paths)
+ * @returns MDX content with test sections injected
+ */
+const injectTestSections = async (
+ mdxContent: string,
+ mdxFilePath: string
+): Promise => {
+ // Pattern: {{docs-tests: filename.docs.spec.ts}}
+ const tokenPattern = /\{\{docs-tests:\s*([^}]+)\}\}/g;
+
+ let result = mdxContent;
+ const matches = mdxContent.matchAll(tokenPattern);
+
+ for (const match of matches) {
+ const [fullMatch, testFileName] = match;
+
+ try {
+ // Resolve test file path
+ const testFilePath = resolveTestFilePath(
+ mdxFilePath,
+ testFileName.trim()
+ );
+
+ // Check if test file exists
+ try {
+ await fs.access(testFilePath);
+ } catch {
+ console.warn(
+ `Test file not found: ${testFilePath} (referenced in ${path.basename(mdxFilePath)})`
+ );
+ // Leave token in place if file doesn't exist
+ continue;
+ }
+
+ // Extract test sections
+ const sections = await extractTestSections(testFilePath);
+
+ if (sections.length === 0) {
+ console.warn(
+ `No test sections found in ${testFileName} (add @docs-section JSDoc tags)`
+ );
+ // Leave token in place if no sections found
+ continue;
+ }
+
+ // Generate markdown for all sections
+ const sectionMarkdown = sections
+ .map((section) => generateMarkdownSection(section))
+ .join("\n");
+
+ // Replace token with generated markdown
+ result = result.replace(fullMatch, sectionMarkdown);
+ } catch (error) {
+ console.error(
+ `Error processing test file ${testFileName}:`,
+ error instanceof Error ? error.message : error
+ );
+ // Leave token in place on error
+ }
+ }
+
+ return result;
+};
/**
* Generate table of contents from MDX content (without frontmatter)
@@ -80,10 +199,18 @@ const parseSingleMdx = async (
} | null> => {
try {
const content = await fs.readFile(filePath, "utf8");
- const { data: frontmatter, content: mdx } = matter(content) as unknown as {
+ const { data: frontmatter, content: mdxContent } = matter(
+ content
+ ) as unknown as {
data: Record;
content: string;
};
+
+ // Inject test sections if {{docs-tests:}} tokens are present
+ const mdx = mdxContent.includes("{{docs-tests:")
+ ? await injectTestSections(mdxContent, filePath)
+ : mdxContent;
+
const toc = await generateToc(mdx);
return { mdx, toc, frontmatter };
} catch {
@@ -117,11 +244,16 @@ export async function parseMdxFile(
// Read main file content
const content = await fs.readFile(filePath, "utf8");
- const { data: meta, content: mdx } = matter(content) as unknown as {
+ const { data: meta, content: mdxContent } = matter(content) as unknown as {
data: Record;
content: string;
};
+ // Inject test sections if {{docs-tests:}} tokens are present
+ const mdx = mdxContent.includes("{{docs-tests:")
+ ? await injectTestSections(mdxContent, filePath)
+ : mdxContent;
+
// Generate TOC for main file
const toc = await generateToc(mdx);
diff --git a/packages/nimbus-docs-build/src/parsers/test-extractor.ts b/packages/nimbus-docs-build/src/parsers/test-extractor.ts
new file mode 100644
index 000000000..2d6648f85
--- /dev/null
+++ b/packages/nimbus-docs-build/src/parsers/test-extractor.ts
@@ -0,0 +1,217 @@
+import { readFile } from "node:fs/promises";
+import { parse } from "@typescript-eslint/typescript-estree";
+import type { TSESTree } from "@typescript-eslint/types";
+import type {
+ TestSection,
+ TestSectionMetadata,
+} from "../types/test-section.js";
+
+/**
+ * Extracts test sections from a .docs.spec.ts file for documentation injection
+ *
+ * Finds describe() blocks with JSDoc @docs-section tags and extracts:
+ * - Metadata from JSDoc tags
+ * - Full source code of the describe block
+ * - Required imports
+ *
+ * @param testFilePath - Absolute path to the .docs.spec.ts file
+ * @returns Array of test sections sorted by order
+ */
+export async function extractTestSections(
+ testFilePath: string
+): Promise {
+ // Read the test file
+ const sourceCode = await readFile(testFilePath, "utf-8");
+ const lines = sourceCode.split("\n");
+
+ // Parse TypeScript to AST (with JSX support)
+ const ast = parse(sourceCode, {
+ loc: true,
+ range: true,
+ comment: true,
+ tokens: false,
+ jsx: true, // Enable JSX parsing for .tsx files
+ });
+
+ // Extract all import statements
+ const imports = extractImports(ast, lines);
+
+ // Find all describe() blocks with @docs-section tags
+ const sections: TestSection[] = [];
+
+ if (ast.comments) {
+ // Visit all nodes in the AST
+ visit(ast, (node) => {
+ // Find describe() call expressions
+ if (
+ node.type === "ExpressionStatement" &&
+ node.expression.type === "CallExpression" &&
+ node.expression.callee.type === "Identifier" &&
+ node.expression.callee.name === "describe"
+ ) {
+ // Look for preceding JSDoc comment
+ const comment = findPrecedingComment(node, ast.comments || []);
+
+ if (comment) {
+ const metadata = parseJSDocMetadata(comment.value);
+
+ // Only include if has @docs-section tag
+ if (metadata.section) {
+ const { code, startLine, endLine } = extractCodeBlock(node, lines);
+
+ sections.push({
+ id: metadata.section,
+ title: metadata.title || metadata.section,
+ description: metadata.description || "",
+ order: metadata.order ?? 999,
+ code,
+ imports,
+ sourceFile: testFilePath,
+ startLine,
+ endLine,
+ });
+ }
+ }
+ }
+ });
+ }
+
+ // Sort by order
+ return sections.sort((a, b) => a.order - b.order);
+}
+
+/**
+ * Extract all import statements from the AST
+ */
+function extractImports(ast: TSESTree.Program, lines: string[]): string[] {
+ const imports: string[] = [];
+
+ for (const node of ast.body) {
+ if (node.type === "ImportDeclaration" && node.loc) {
+ // Extract the full import statement from source
+ const importLines = lines.slice(
+ node.loc.start.line - 1,
+ node.loc.end.line
+ );
+ imports.push(importLines.join("\n"));
+ }
+ }
+
+ return imports;
+}
+
+/**
+ * Find the JSDoc comment immediately preceding a node
+ */
+function findPrecedingComment(
+ node: TSESTree.Node,
+ comments: TSESTree.Comment[]
+): TSESTree.Comment | null {
+ if (!node.loc) return null;
+
+ // Find the last comment that ends before this node starts
+ let precedingComment: TSESTree.Comment | null = null;
+
+ for (const comment of comments) {
+ if (!comment.loc) continue;
+
+ // Comment must end before node starts
+ if (comment.loc.end.line < node.loc.start.line) {
+ // Must be a JSDoc comment (block comment starting with **)
+ if (comment.type === "Block" && comment.value.startsWith("*")) {
+ precedingComment = comment;
+ }
+ }
+ }
+
+ return precedingComment;
+}
+
+/**
+ * Parse JSDoc tags from a comment
+ */
+function parseJSDocMetadata(commentValue: string): TestSectionMetadata {
+ const metadata: TestSectionMetadata = {};
+
+ // Remove leading * from each line
+ const lines = commentValue
+ .split("\n")
+ .map((line) => line.trim().replace(/^\*\s?/, ""));
+
+ for (const line of lines) {
+ // Extract @docs-section
+ const sectionMatch = line.match(/@docs-section\s+(\S+)/);
+ if (sectionMatch) {
+ metadata.section = sectionMatch[1];
+ }
+
+ // Extract @docs-title
+ const titleMatch = line.match(/@docs-title\s+(.+)/);
+ if (titleMatch) {
+ metadata.title = titleMatch[1].trim();
+ }
+
+ // Extract @docs-description
+ const descriptionMatch = line.match(/@docs-description\s+(.+)/);
+ if (descriptionMatch) {
+ metadata.description = descriptionMatch[1].trim();
+ }
+
+ // Extract @docs-order
+ const orderMatch = line.match(/@docs-order\s+(\d+)/);
+ if (orderMatch) {
+ metadata.order = parseInt(orderMatch[1], 10);
+ }
+ }
+
+ return metadata;
+}
+
+/**
+ * Extract the full source code of a describe() block
+ */
+function extractCodeBlock(
+ node: TSESTree.Node,
+ lines: string[]
+): { code: string; startLine: number; endLine: number } {
+ if (!node.loc) {
+ return { code: "", startLine: 0, endLine: 0 };
+ }
+
+ const startLine = node.loc.start.line;
+ const endLine = node.loc.end.line;
+
+ // Extract lines (loc is 1-indexed, array is 0-indexed)
+ const codeLines = lines.slice(startLine - 1, endLine);
+ const code = codeLines.join("\n");
+
+ return { code, startLine, endLine };
+}
+
+/**
+ * Simple AST visitor helper
+ */
+function visit(
+ node: TSESTree.Node | TSESTree.Program,
+ callback: (node: TSESTree.Node) => void
+): void {
+ callback(node as TSESTree.Node);
+
+ // Recursively visit all child nodes
+ for (const key of Object.keys(node)) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const value = (node as any)[key];
+
+ if (value && typeof value === "object") {
+ if (Array.isArray(value)) {
+ for (const item of value) {
+ if (item && typeof item === "object" && "type" in item) {
+ visit(item, callback);
+ }
+ }
+ } else if ("type" in value) {
+ visit(value, callback);
+ }
+ }
+ }
+}
diff --git a/packages/nimbus-docs-build/src/types/index.ts b/packages/nimbus-docs-build/src/types/index.ts
index 49096b914..9145b5d2f 100644
--- a/packages/nimbus-docs-build/src/types/index.ts
+++ b/packages/nimbus-docs-build/src/types/index.ts
@@ -3,4 +3,5 @@
*/
export * from "./mdx.js";
export * from "./config.js";
+export * from "./test-section.js";
export type { LifecycleState } from "../schemas/lifecycle-states.js";
diff --git a/packages/nimbus-docs-build/src/types/test-section.ts b/packages/nimbus-docs-build/src/types/test-section.ts
new file mode 100644
index 000000000..efc818f08
--- /dev/null
+++ b/packages/nimbus-docs-build/src/types/test-section.ts
@@ -0,0 +1,48 @@
+/**
+ * Test section extracted from .docs.spec.ts files for documentation injection
+ */
+export interface TestSection {
+ /** Unique identifier for the section (from @docs-section tag) */
+ id: string;
+
+ /** Display title for the section (from @docs-title tag) */
+ title: string;
+
+ /** Description explaining what these tests demonstrate (from @docs-description tag) */
+ description: string;
+
+ /** Sort order for display in documentation (from @docs-order tag) */
+ order: number;
+
+ /** Full source code of the describe block including tests */
+ code: string;
+
+ /** Import statements required for this test section */
+ imports: string[];
+
+ /** Source file path for debugging and error messages */
+ sourceFile: string;
+
+ /** Line number where this section starts in the source file */
+ startLine: number;
+
+ /** Line number where this section ends in the source file */
+ endLine: number;
+}
+
+/**
+ * Parsed JSDoc tags from test describe blocks
+ */
+export interface TestSectionMetadata {
+ /** @docs-section tag value */
+ section?: string;
+
+ /** @docs-title tag value */
+ title?: string;
+
+ /** @docs-description tag value */
+ description?: string;
+
+ /** @docs-order tag value (parsed to number) */
+ order?: number;
+}
diff --git a/packages/nimbus/src/components/text-input/text-input.dev.mdx b/packages/nimbus/src/components/text-input/text-input.dev.mdx
index 7a21e1789..90add0501 100644
--- a/packages/nimbus/src/components/text-input/text-input.dev.mdx
+++ b/packages/nimbus/src/components/text-input/text-input.dev.mdx
@@ -375,253 +375,7 @@ const App = () => {
These examples demonstrate how to test your implementation when using TextInput in your application. The component's internal functionality is already tested by Nimbus - these patterns help you verify your integration and application-specific logic.
-### Basic rendering tests
-
-Verify the component renders with expected elements:
-
-```tsx
-import { render, screen } from '@testing-library/react';
-import { TextInput } from '@commercetools/nimbus';
-
-describe('TextInput', () => {
- it('renders input element', () => {
- render();
-
- // Verify input is present
- expect(screen.getByRole('textbox')).toBeInTheDocument();
- });
-
- it('renders with placeholder text', () => {
- render();
-
- expect(screen.getByPlaceholderText('Email address')).toBeInTheDocument();
- });
-
- it('renders with aria-label', () => {
- render();
-
- expect(screen.getByRole('textbox', { name: /user email/i })).toBeInTheDocument();
- });
-});
-```
-
-### Interaction tests
-
-Test user interactions with the component:
-
-```tsx
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { TextInput } from '@commercetools/nimbus';
-
-describe('TextInput interactions', () => {
- it('updates value when user types', async () => {
- const user = userEvent.setup();
- render();
-
- const input = screen.getByRole('textbox');
- await user.type(input, 'Hello World');
-
- expect(input).toHaveValue('Hello World');
- });
-
- it('calls onChange callback with string value', async () => {
- const user = userEvent.setup();
- const handleChange = jest.fn();
- render();
-
- const input = screen.getByRole('textbox');
- await user.type(input, 'test');
-
- expect(handleChange).toHaveBeenCalled();
-
- expect(typeof handleChange.mock.calls[0][0]).toBe('string');
- });
-
- it('clears input when clear button is clicked', async () => {
- const user = userEvent.setup();
- const TestComponent = () => {
- const [value, setValue] = React.useState('initial');
- return (
- setValue(value)}
- trailingElement={
-
- }
- />
- );
- };
-
- render();
-
- const input = screen.getByRole('textbox');
- expect(input).toHaveValue('initial');
-
- await user.click(screen.getByText('Clear'));
-
- expect(input).toHaveValue('');
- });
-});
-```
-
-### Testing controlled mode
-
-Test controlled component behavior:
-
-```tsx
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { TextInput } from '@commercetools/nimbus';
-
-describe('TextInput controlled mode', () => {
- it('displays controlled value', () => {
- render( {}} />);
-
- const input = screen.getByRole('textbox');
- expect(input).toHaveValue('controlled value');
- });
-
- it('updates when controlled value changes', () => {
- const { rerender } = render(
- {}} />
- );
-
- expect(screen.getByRole('textbox')).toHaveValue('first value');
-
- rerender( {}} />);
-
- expect(screen.getByRole('textbox')).toHaveValue('second value');
- });
-
- it('validates input in controlled mode', async () => {
- const user = userEvent.setup();
- const TestComponent = () => {
- const [value, setValue] = React.useState('');
- const [isValid, setIsValid] = React.useState(true);
-
- const handleChange = (newValue: string) => {
- setValue(newValue);
- setIsValid(newValue.length >= 3);
- };
-
- return (
- <>
-
- {!isValid && Must be at least 3 characters}
- >
- );
- };
-
- render();
-
- const input = screen.getByRole('textbox');
- await user.type(input, 'ab');
-
- expect(screen.getByText('Must be at least 3 characters')).toBeInTheDocument();
-
- await user.type(input, 'c');
-
- expect(screen.queryByText('Must be at least 3 characters')).not.toBeInTheDocument();
- });
-});
-```
-
-### Testing leading and trailing elements
-
-Test custom elements within the input:
-
-```tsx
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { TextInput } from '@commercetools/nimbus';
-
-describe('TextInput with elements', () => {
- it('renders leading element', () => {
- render(
- 🔍}
- placeholder="Search"
- />
- );
-
- expect(screen.getByTestId('icon')).toBeInTheDocument();
- });
-
- it('renders trailing element', () => {
- render(
- Clear
- }
- placeholder="Input"
- />
- );
-
- expect(screen.getByTestId('clear')).toBeInTheDocument();
- });
-
- it('trailing button is interactive', async () => {
- const user = userEvent.setup();
- const handleClick = jest.fn();
-
- render(
- Action
- }
- />
- );
-
- await user.click(screen.getByText('Action'));
-
- expect(handleClick).toHaveBeenCalledTimes(1);
- });
-});
-```
-
-### Testing validation states
-
-Test different validation states:
-
-```tsx
-import { render, screen } from '@testing-library/react';
-import { TextInput } from '@commercetools/nimbus';
-
-describe('TextInput validation states', () => {
- it('renders disabled state', () => {
- render();
-
- const input = screen.getByRole('textbox');
- expect(input).toBeDisabled();
- });
-
- it('renders invalid state', () => {
- render();
-
- const input = screen.getByRole('textbox');
- expect(input).toHaveAttribute('aria-invalid', 'true');
- });
-
- it('renders read-only state', () => {
- render( {}} />);
-
- const input = screen.getByRole('textbox');
- expect(input).toHaveAttribute('readonly');
- });
-
- it('renders required state', () => {
- render();
-
- const input = screen.getByRole('textbox');
- expect(input).toHaveAttribute('aria-required', 'true');
- });
-});
-```
+{{docs-tests: text-input.docs.spec.tsx}}
## Resources
diff --git a/packages/nimbus/src/components/text-input/text-input.docs.spec.tsx b/packages/nimbus/src/components/text-input/text-input.docs.spec.tsx
new file mode 100644
index 000000000..4c473fe67
--- /dev/null
+++ b/packages/nimbus/src/components/text-input/text-input.docs.spec.tsx
@@ -0,0 +1,223 @@
+import { describe, it, expect, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { TextInput, NimbusProvider } from "@commercetools/nimbus";
+
+/**
+ * @docs-section basic-rendering
+ * @docs-title Basic Rendering Tests
+ * @docs-description Verify the component renders with expected elements
+ * @docs-order 1
+ */
+describe("TextInput - Basic rendering", () => {
+ it("renders input element", () => {
+ render(
+
+
+
+ );
+
+ // Verify input is present
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
+ });
+
+ it("renders with placeholder text", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByPlaceholderText("Email address")).toBeInTheDocument();
+ });
+
+ it("renders with aria-label", () => {
+ render(
+
+
+
+ );
+
+ expect(
+ screen.getByRole("textbox", { name: /user email/i })
+ ).toBeInTheDocument();
+ });
+});
+
+/**
+ * @docs-section interactions
+ * @docs-title Interaction Tests
+ * @docs-description Test user interactions with the component
+ * @docs-order 2
+ */
+describe("TextInput - Interactions", () => {
+ it("updates value when user types", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole("textbox");
+ await user.type(input, "Hello World");
+
+ expect(input).toHaveValue("Hello World");
+ });
+
+ it("calls onChange callback with string value", async () => {
+ const user = userEvent.setup();
+ const handleChange = vi.fn();
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole("textbox");
+ await user.type(input, "test");
+
+ expect(handleChange).toHaveBeenCalled();
+ expect(typeof handleChange.mock.calls[0][0]).toBe("string");
+ });
+});
+
+/**
+ * @docs-section controlled-mode
+ * @docs-title Testing Controlled Mode
+ * @docs-description Test controlled component behavior
+ * @docs-order 3
+ */
+describe("TextInput - Controlled mode", () => {
+ it("displays controlled value", () => {
+ render(
+
+ {}} />
+
+ );
+
+ const input = screen.getByRole("textbox");
+ expect(input).toHaveValue("controlled value");
+ });
+
+ it("updates when controlled value changes", () => {
+ const { rerender } = render(
+
+ {}} />
+
+ );
+
+ expect(screen.getByRole("textbox")).toHaveValue("first value");
+
+ rerender(
+
+ {}} />
+
+ );
+
+ expect(screen.getByRole("textbox")).toHaveValue("second value");
+ });
+});
+
+/**
+ * @docs-section leading-trailing-elements
+ * @docs-title Testing Leading and Trailing Elements
+ * @docs-description Test custom elements within the input
+ * @docs-order 4
+ */
+describe("TextInput - Elements", () => {
+ it("renders leading element", () => {
+ render(
+
+ 🔍}
+ placeholder="Search"
+ />
+
+ );
+
+ expect(screen.getByTestId("icon")).toBeInTheDocument();
+ });
+
+ it("renders trailing element", () => {
+ render(
+
+ Clear}
+ placeholder="Input"
+ />
+
+ );
+
+ expect(screen.getByTestId("clear")).toBeInTheDocument();
+ });
+
+ it("trailing button is interactive", async () => {
+ const user = userEvent.setup();
+ const handleClick = vi.fn();
+
+ render(
+
+ Action}
+ />
+
+ );
+
+ await user.click(screen.getByText("Action"));
+
+ expect(handleClick).toHaveBeenCalled();
+ });
+});
+
+/**
+ * @docs-section validation-states
+ * @docs-title Testing Validation States
+ * @docs-description Test different validation states
+ * @docs-order 5
+ */
+describe("TextInput - Validation states", () => {
+ it("renders disabled state", () => {
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole("textbox");
+ expect(input).toBeDisabled();
+ });
+
+ it("renders invalid state", () => {
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole("textbox");
+ expect(input).toHaveAttribute("aria-invalid", "true");
+ });
+
+ it("renders read-only state", () => {
+ render(
+
+ {}} />
+
+ );
+
+ const input = screen.getByRole("textbox");
+ expect(input).toHaveAttribute("readonly");
+ });
+
+ it("renders required state", () => {
+ render(
+
+
+
+ );
+
+ const input = screen.getByRole("textbox");
+ expect(input).toHaveAttribute("aria-required", "true");
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fc03a0e09..f43702d81 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -655,6 +655,12 @@ importers:
packages/nimbus-docs-build:
dependencies:
+ '@typescript-eslint/types':
+ specifier: ^8.48.0
+ version: 8.48.0
+ '@typescript-eslint/typescript-estree':
+ specifier: ^8.48.0
+ version: 8.48.0(typescript@5.9.3)
gray-matter:
specifier: ^4.0.3
version: 4.0.3
@@ -2657,6 +2663,9 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/lodash@4.17.20':
+ resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
+
'@types/lodash@4.17.21':
resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
@@ -2681,6 +2690,9 @@ packages:
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
+ '@types/node@24.8.0':
+ resolution: {integrity: sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==}
+
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -2752,16 +2764,32 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: ~5.9.3
+ '@typescript-eslint/project-service@8.46.2':
+ resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: ~5.9.3
+
'@typescript-eslint/project-service@8.48.0':
resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: ~5.9.3
+ '@typescript-eslint/scope-manager@8.46.2':
+ resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/scope-manager@8.48.0':
resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/tsconfig-utils@8.46.2':
+ resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: ~5.9.3
+
'@typescript-eslint/tsconfig-utils@8.48.0':
resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2775,16 +2803,33 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: ~5.9.3
+ '@typescript-eslint/types@8.46.2':
+ resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/types@8.48.0':
resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/typescript-estree@8.46.2':
+ resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: ~5.9.3
+
'@typescript-eslint/typescript-estree@8.48.0':
resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: ~5.9.3
+ '@typescript-eslint/utils@8.46.2':
+ resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ~5.9.3
+
'@typescript-eslint/utils@8.48.0':
resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2792,6 +2837,10 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: ~5.9.3
+ '@typescript-eslint/visitor-keys@8.46.2':
+ resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/visitor-keys@8.48.0':
resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -6116,6 +6165,9 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici-types@7.14.0:
+ resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==}
+
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -6602,7 +6654,7 @@ snapshots:
'@babel/helper-annotate-as-pure@7.27.3':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.28.4
'@babel/helper-compilation-targets@7.27.2':
dependencies:
@@ -6661,7 +6713,7 @@ snapshots:
'@babel/helper-optimise-call-expression@7.27.1':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.28.4
'@babel/helper-plugin-utils@7.27.1': {}
@@ -6676,8 +6728,8 @@ snapshots:
'@babel/helper-skip-transparent-expression-wrappers@7.27.1':
dependencies:
- '@babel/traverse': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
transitivePeerDependencies:
- supports-color
@@ -6692,7 +6744,7 @@ snapshots:
'@babel/helpers@7.28.4':
dependencies:
'@babel/template': 7.27.2
- '@babel/types': 7.28.5
+ '@babel/types': 7.28.4
'@babel/parser@7.28.4':
dependencies:
@@ -6757,8 +6809,8 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
'@babel/traverse@7.28.4':
dependencies:
@@ -8890,66 +8942,34 @@ snapshots:
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
- '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
-
'@svgr/babel-preset@8.1.0(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
@@ -8962,18 +8982,6 @@ snapshots:
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.4)
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)':
- dependencies:
- '@babel/core': 7.28.5
- '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5)
- '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5)
-
'@svgr/cli@8.1.0(typescript@5.9.3)':
dependencies:
'@svgr/core': 8.1.0(typescript@5.9.3)
@@ -8992,8 +9000,8 @@ snapshots:
'@svgr/core@8.1.0(typescript@5.9.3)':
dependencies:
- '@babel/core': 7.28.5
- '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5)
+ '@babel/core': 7.28.4
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4)
camelcase: 6.3.0
cosmiconfig: 8.3.6(typescript@5.9.3)
snake-case: 3.0.4
@@ -9142,29 +9150,29 @@ snapshots:
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
'@types/babel__generator': 7.27.0
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.28.0
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.28.4
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
'@types/babel__traverse@7.28.0':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.28.4
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/chai@5.2.2':
dependencies:
@@ -9176,7 +9184,7 @@ snapshots:
'@types/connect@3.4.38':
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/debug@4.1.12':
dependencies:
@@ -9198,7 +9206,7 @@ snapshots:
'@types/express-serve-static-core@5.1.0':
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.0
@@ -9232,12 +9240,14 @@ snapshots:
'@types/jsdom@27.0.0':
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/tough-cookie': 4.0.5
parse5: 7.3.0
'@types/json-schema@7.0.15': {}
+ '@types/lodash@4.17.20': {}
+
'@types/lodash@4.17.21': {}
'@types/mdast@4.0.4':
@@ -9261,6 +9271,10 @@ snapshots:
dependencies:
undici-types: 7.16.0
+ '@types/node@24.8.0':
+ dependencies:
+ undici-types: 7.14.0
+
'@types/parse-json@4.0.2': {}
'@types/prismjs@1.26.5': {}
@@ -9283,23 +9297,23 @@ snapshots:
'@types/resolve@1.17.1':
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/resolve@1.20.6': {}
'@types/send@0.17.5':
dependencies:
'@types/mime': 1.3.5
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/send@1.2.0':
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/serve-static@1.15.9':
dependencies:
'@types/http-errors': 2.0.5
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
'@types/send': 0.17.5
'@types/slug@5.0.9': {}
@@ -9345,6 +9359,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/project-service@8.46.2(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3)
+ '@typescript-eslint/types': 8.46.2
+ debug: 4.4.3
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/project-service@8.48.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3)
@@ -9354,11 +9377,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/scope-manager@8.46.2':
+ dependencies:
+ '@typescript-eslint/types': 8.46.2
+ '@typescript-eslint/visitor-keys': 8.46.2
+
'@typescript-eslint/scope-manager@8.48.0':
dependencies:
'@typescript-eslint/types': 8.48.0
'@typescript-eslint/visitor-keys': 8.48.0
+ '@typescript-eslint/tsconfig-utils@8.46.2(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
'@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
@@ -9375,8 +9407,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/types@8.46.2': {}
+
'@typescript-eslint/types@8.48.0': {}
+ '@typescript-eslint/typescript-estree@8.46.2(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.46.2(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3)
+ '@typescript-eslint/types': 8.46.2
+ '@typescript-eslint/visitor-keys': 8.46.2
+ debug: 4.4.3
+ fast-glob: 3.3.3
+ is-glob: 4.0.3
+ minimatch: 9.0.5
+ semver: 7.7.3
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.48.0(typescript@5.9.3)
@@ -9392,6 +9442,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/utils@8.46.2(eslint@9.39.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
+ '@typescript-eslint/scope-manager': 8.46.2
+ '@typescript-eslint/types': 8.46.2
+ '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3)
+ eslint: 9.39.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
@@ -9403,6 +9464,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/visitor-keys@8.46.2':
+ dependencies:
+ '@typescript-eslint/types': 8.46.2
+ eslint-visitor-keys: 4.2.1
+
'@typescript-eslint/visitor-keys@8.48.0':
dependencies:
'@typescript-eslint/types': 8.48.0
@@ -9528,7 +9594,7 @@ snapshots:
'@vue/compiler-core@3.5.22':
dependencies:
- '@babel/parser': 7.28.5
+ '@babel/parser': 7.28.4
'@vue/shared': 3.5.22
entities: 4.5.0
estree-walker: 2.0.2
@@ -10784,7 +10850,7 @@ snapshots:
eslint-plugin-storybook@9.1.13(eslint@9.39.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.2.4(@types/node@24.10.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)))(typescript@5.9.3):
dependencies:
- '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.46.2(eslint@9.39.1)(typescript@5.9.3)
eslint: 9.39.1
storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@7.2.4(@types/node@24.10.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))
transitivePeerDependencies:
@@ -11582,7 +11648,7 @@ snapshots:
jest-worker@26.6.2:
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
merge-stream: 2.0.0
supports-color: 7.2.0
@@ -11760,8 +11826,8 @@ snapshots:
magicast@0.3.5:
dependencies:
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
source-map-js: 1.2.1
make-dir@4.0.0:
@@ -12771,9 +12837,9 @@ snapshots:
react-docgen@8.0.2:
dependencies:
- '@babel/core': 7.28.5
- '@babel/traverse': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/core': 7.28.4
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
'@types/babel__core': 7.20.5
'@types/babel__traverse': 7.28.0
'@types/doctrine': 0.0.9
@@ -13051,7 +13117,7 @@ snapshots:
rollup-plugin-tree-shakeable@2.0.0:
dependencies:
- '@types/node': 24.10.1
+ '@types/node': 24.8.0
estree-walker: 3.0.3
magic-string: 0.30.19
@@ -13235,7 +13301,7 @@ snapshots:
slate-react@0.75.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(slate@0.75.0):
dependencies:
'@types/is-hotkey': 0.1.10
- '@types/lodash': 4.17.21
+ '@types/lodash': 4.17.20
direction: 1.0.4
is-hotkey: 0.1.8
is-plain-object: 5.0.0
@@ -13601,6 +13667,8 @@ snapshots:
undici-types@6.21.0:
optional: true
+ undici-types@7.14.0: {}
+
undici-types@7.16.0: {}
unicorn-magic@0.1.0: {}