Skip to content

Latest commit

 

History

History
477 lines (311 loc) · 15.4 KB

File metadata and controls

477 lines (311 loc) · 15.4 KB

Agent Guide for Sanity Plugins Monorepo

This guide is for AI agents working on this codebase. Follow these instructions to ensure successful contributions.

Self-Improvement: If you discover undocumented requirements, commands, or workflows during your work (e.g., a reviewer asks you to run something not covered here), update this file on the same PR. Keep this guide accurate and helpful for future agents.

Quick Reference

Task Command
Install dependencies pnpm install
Format code pnpm format
Run linters pnpm lint
Build all packages pnpm build
Run tests pnpm test
Add changeset pnpm changeset add
Start dev server pnpm dev

Environment Setup

Node.js Version

Use the latest LTS release of Node.js.

pnpm Version

The exact pnpm version is managed via the packageManager field in root package.json. You only need pnpm v10 or later installed globally—corepack or pnpm itself will auto-install the exact version specified.

# Enable corepack to automatically use the correct pnpm version
corepack enable

# Install all dependencies
pnpm install

Before Submitting a PR

Run these commands in order. All must pass or CI will fail:

# 1. Format code (oxfmt)
pnpm format

# 2. Run linters (oxlint)
pnpm lint

# 3. Build all packages
pnpm build

# 4. Run tests
pnpm test

Required: Add Changesets

Every PR that changes published packages must include changesets. Important: Create a separate changeset file for each plugin you modify.

Why Separate Changesets?

Each plugin has its own changelog that consumers read. A combined changeset would pollute individual plugin changelogs with irrelevant information. For example, @sanity/code-input's changelog should not mention workflow-specific changes.

How to Add Changesets

Option 1: Manual Creation (Recommended for Multiple Plugins)

Create individual .md files in .changeset/ directory:

---
'plugin-name': patch
---

Brief description of the change specific to this plugin

Example: If you modified 3 plugins, create 3 files:

# .changeset/code-input-fix.md
---
"@sanity/code-input": patch
---

Fix input border styling with v2 theme API

# .changeset/workflow-hooks.md
---
"sanity-plugin-workflow": patch
---

Replace deprecated useTimeAgo hook with useRelativeTime

# .changeset/markdown-theme.md
---
"sanity-plugin-markdown": patch
---

Migrate to v2 theme API for color properties

Option 2: Interactive CLI (For Single Plugin)

pnpm changeset add

Follow the prompts to:

  1. Select one package that changed
  2. Choose the version bump type (patch/minor/major)
  3. Write a summary of changes specific to that package
  4. Repeat for each modified package

Changeset Guidelines

  • Scope: Each changeset should only mention changes relevant to that specific plugin
  • Clarity: Write from the consumer's perspective—what do they need to know?
  • Version bumps:
    • patch: Bug fixes, dependency updates, internal refactors
    • minor: New features, non-breaking API additions
    • major: Breaking changes
  • Format: Use clear, concise language without technical jargon when possible

Bad vs Good Examples

Bad (combined changeset):

---
'@sanity/code-input': patch
'sanity-plugin-workflow': patch
---

- Migrated to v2 theme APIs
- Replaced useTimeAgo with useRelativeTime
- Fixed input styling

Problem: Workflow's changelog will say "Fixed input styling" which is irrelevant

Good (separate changesets):

# .changeset/code-input-theme.md

---

## "@sanity/code-input": patch

Migrate to v2 theme API for input styling

# .changeset/workflow-hooks.md

---

## "sanity-plugin-workflow": patch

Replace deprecated useTimeAgo hook with useRelativeTime

Each plugin's changelog only shows relevant changes

CI Checks

The CI pipeline runs on every PR:

Job What it checks
build pnpm build - All packages compile successfully
lint pnpm lint --format github - Code passes oxlint
test pnpm test - All tests pass (runs after build + lint)

Lint Specifics

  • oxlint: Type-aware linting with --deny-warnings (warnings are errors). React Compiler rules run via the react-hooks-js plugin.
  • TypeScript type checking is included in pnpm lint via oxlint — no separate tsc needed
  • Run pnpm lint:fix to auto-fix issues when possible

Testing

The monorepo uses Vitest v4 for testing.

Running Tests

# Run all tests (non-watch mode)
pnpm test run

# Run tests in watch mode (default vitest behavior)
pnpm test

# Update snapshots
pnpm test -u

Writing Tests

Tests are co-located with source code in the src/ directory:

  • Test files use .test.ts or .spec.ts extensions
  • Each plugin has a minimal vitest.config.ts that inlines vitest-package-exports
  • All plugins include a package exports test using vitest-package-exports to verify all exports are valid

When creating a new plugin with pnpm generate, test files are automatically created. The root vitest.config.ts uses glob patterns to automatically discover all plugins, so new plugins are included without manual configuration.

Example test file (src/index.test.ts):

import {fileURLToPath} from 'node:url'
import {expect, test} from 'vitest'
import {getPackageExportsManifest} from 'vitest-package-exports'

test('package exports', {timeout: 30_000}, async () => {
  const manifest = await getPackageExportsManifest({
    importMode: 'dist',
    cwd: fileURLToPath(import.meta.url),
  })

  expect(manifest.exports).toMatchInlineSnapshot()
})

Test Timeouts

Always use the options object syntax for timeouts, placing it as the second argument:

// Correct - options object as second argument
test('my test', {timeout: 30_000}, async () => {
  // ... test code
})

// Wrong - timeout as third argument (deprecated style)
test('my test', async () => {
  // ...
}, 30000)

Use numeric separators (30_000 instead of 30000) for readability.

Test Configuration

  • Root vitest.config.ts uses glob patterns ['plugins/@sanity/*', 'plugins/sanity-plugin-*'] to automatically find all plugin projects
  • Individual plugins have minimal vitest.config.ts starting with just inline deps configuration for vitest-package-exports
  • Plugins can expand their vitest configs and test suites over time as needed (unit tests, integration tests, etc.)
  • Tests run against built dist/ output after pnpm build
  • Snapshots are generated with pnpm test -u

Pull Request Workflow

1. Create as Draft PR First

Always open PRs as draft initially. The prompter (person who requested the work) reviews first before marking ready for team review.

2. Apply the "🤖 bot" Label

All PRs created by AI agents must be labeled with "🤖 bot". This label helps the team identify bot-created PRs and ensures proper tracking and review workflows.

When creating or updating a PR, always ensure the 🤖 bot label is applied.

3. Move Out of Draft

Once the prompter approves, convert from draft to ready-for-review so the team can review.

4. Merge Process

After approval, PRs merge to main. The release workflow automatically:

  1. Creates a "Version Packages" PR that bumps versions
  2. When that PR merges, packages publish to npm with provenance

Development Server

Starting the Test Studio

pnpm dev

This starts the Sanity Studio at http://localhost:3333.

⚠️ Authentication Required

The test studio is a real Sanity Studio that connects to Sanity APIs. You must:

  1. Have a Sanity account
  2. Log in via the browser when prompted
  3. Have access to the configured project (ppsg7ml5 by default)

Simply accessing http://localhost:3333 is not enough—the studio requires browser-based Sanity authentication to function.

Inspecting Production Builds with Vite DevTools

The test studio can run with Vite DevTools enabled, which lets you inspect the output of sanity build runs (module graph, chunks, plugin timings, bundle treemaps, session diffing) from inside a long-running sanity dev server—no restart needed.

# Builds the test studio with devtools enabled, then starts the dev server
# (so there's a build session to inspect right away)
pnpm devtools:test-studio

Open http://localhost:3333 and use the Vite DevTools dock to explore the recorded Rolldown build session, or open the full-page DevTools UI directly at http://localhost:3333/__devtools/. See the DevTools for Rolldown features guide for how to use the module graph, chunk, asset, and plugin panels.

The first time a browser connects, the dock asks for a one-time permission grant (a prompt in the sanity dev terminal, or a manual auth URL printed there). Trusted clients are remembered in ~/.vite/devtools/auth.json.

To inspect a new build after making changes—while pnpm devtools:test-studio is still running—run in a second terminal:

# Creates a fresh build session that shows up in the running DevTools dock
pnpm devtools:test-studio:build

Builds are not hooked into HMR; sanity build must be invoked manually (via the command above) each time you want a new session to inspect. Sessions can be compared against each other in the DevTools UI to diff bundle changes.

How it works:

  • Both commands set ENABLE_VITE_DEVTOOLS=true, which makes dev/test-studio/sanity.cli.ts add the DevTools() Vite plugin and enable build.rolldownOptions.devtools
  • Build sessions are written to dev/test-studio/node_modules/.rolldown (gitignored)
  • The flag is declared in dev/test-studio/turbo.jsonc so turbo-cached builds are invalidated when it changes (and so turbo's strict env mode passes it through to pnpm dev)
  • Enabling devtools makes sanity build noticeably slower; that's why it's opt-in via the env flag

Creating a New Plugin

Use the generator:

pnpm generate "new plugin"

When adding a new plugin, also update the root README.md ## Current Plugins table so the monorepo plugin list stays current.

Or to migrate an existing plugin:

pnpm generate "copy plugin"

For agent-specific transfer guidance, use the plugin-transfer skill in .agents/skills/plugin-transfer/SKILL.md.

When migrating a plugin, agents should ensure:

  • README.md from the original repository is preserved
  • A test-studio example is present and wired in dev/test-studio/sanity.config.ts
  • .github/CODEOWNERS is not updated unless explicitly requested
  • The transfer includes a major changeset
  • Only monorepo-required plugin config files are maintained (package.json, package.config.ts, tsconfig.json, tsconfig.build.json, vitest.config.ts)

See CONTRIBUTING.md for detailed instructions on:

  • Setting up npm trusted publishing (different process for new vs existing packages)
  • Creating initial release changesets
  • Migrating existing plugins

Trusted Publishing Quick Reference

For brand new packages (not yet on npm):

  • Use the "Setup a new npm package with Trusted Publishing" GitHub Actions workflow
  • Then run locally: npm trust github <package-name> --file=release.yml --repository=sanity-io/plugins (requires npm >= 11.10.0)

For existing packages (already on npm):

  • ⚠️ DO NOT use the setup workflow
  • Run: npm trust github <package-name> --file=release.yml --repository=sanity-io/plugins (requires npm >= 11.10.0)

Code Style

Dependencies

Always use lodash-es instead of lodash

When working with lodash utility functions, always use the lodash-es package instead of lodash. The lodash-es package is the ES module version that supports tree-shaking and works correctly with modern build tools.

# Correct
pnpm add lodash-es

# Wrong
pnpm add lodash

Formatting

We use oxfmt:

pnpm format

Linting

We use oxlint for all linting (type-aware, includes TypeScript type checking and React Compiler rules via the react-hooks-js plugin):

pnpm lint        # Run the linter (includes type checking)
pnpm lint:fix    # Auto-fix what's possible

Project Structure

plugins/
├── dev/test-studio/      # Test Sanity Studio (localhost:3333)
├── packages/@repo/       # Internal shared packages
├── plugins/              # Published plugins
│   ├── @sanity/          # @sanity/* scoped packages
│   └── sanity-plugin-*   # Community-style packages
├── turbo/generators/     # Plugin scaffolding templates
└── .changeset/           # Changeset files for releases

Common Issues

Lint Errors About Missing Types

Run pnpm build first—some packages need to be built for type information to be available.

"Command not found: pnpm"

Ensure you have pnpm v10+ installed, then run:

corepack enable

Test Studio Won't Load

  1. Check you're logged into Sanity in the browser
  2. Verify you have access to the project
  3. Check the browser console for specific errors

Cursor Cloud specific instructions

Services

Service Port Purpose
Test Studio (pnpm dev) 3333 Local Sanity Studio for manual plugin testing

No Docker, databases, or other local services are required. CI-style verification (pnpm lint, pnpm build, pnpm test run) runs entirely in-process.

Starting the dev server

The test studio dev script uses sanity dev --no-auto-updates, so pnpm dev starts non-interactively (no version-upgrade prompt). The studio serves at http://localhost:3333.

Sanity authentication

The test studio connects to Sanity Cloud (project ppsg7ml5, dataset plugins by default). For cloud agents, use the injected STUDIO_AUTH_TOKEN secret instead of interactive login.

Navigate to the studio with the token in the URL hash (Sanity consumes it on load and removes it from the address bar):

http://localhost:3333/kitchen-sink#token=<STUDIO_AUTH_TOKEN>

Build the URL from the secret:

node -e "const t=process.env.STUDIO_AUTH_TOKEN; console.log('http://localhost:3333/kitchen-sink#token=' + encodeURIComponent(t))"

Open that URL in the browser to authenticate and land directly in the Kitchen Sink workspace. Without a token, workspaces show as "Signed out".

Node.js version notes

dev/test-studio declares engines.node: "24"; the monorepo otherwise targets latest LTS. Node 22 works for build/lint/test with engine warnings. Node 24 is preferred when available.

Lint / build / test

Standard commands from the Quick Reference table apply. Run pnpm build before pnpm lint if type errors reference missing dist/ output. pretest automatically builds all packages except dev/* before Vitest runs.

Related Documentation