Skip to content

Commit 795bfad

Browse files
authored
feat: add script runner tool (#878)
1 parent 91a7140 commit 795bfad

15 files changed

Lines changed: 1070 additions & 1 deletion

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
name: test-studio-script-runner
3+
description: Explains the dev/test-studio Script Runner tool. Use when adding, editing, running, or documenting scripts in dev/test-studio/src/script-runner, or when the user mentions the Scripts tool, Studio runner scripts, script variables, or browser-side Sanity Studio scripts.
4+
---
5+
6+
# Test Studio Script Runner
7+
8+
Use this skill when working with the `Scripts` tool in `dev/test-studio`.
9+
10+
## Key Files
11+
12+
- Tool plugin: `dev/test-studio/src/script-runner/index.tsx`
13+
- Runner UI: `dev/test-studio/src/script-runner/ScriptRunnerTool.tsx`
14+
- Script registry: `dev/test-studio/src/script-runner/registry.ts`
15+
- Script contract: `dev/test-studio/src/script-runner/types.ts`
16+
- Script modules: `dev/test-studio/src/script-runner/scripts/*/index.ts`
17+
- Agent-facing docs: `dev/test-studio/src/script-runner/README.md`
18+
19+
Read `README.md` and `types.ts` before changing the runner or adding scripts.
20+
21+
## What The Tool Does
22+
23+
The runner is a Sanity Studio custom tool registered in the `kitchen-sink` workspace.
24+
25+
- Home route: `<studio>/kitchen-sink/scripts`
26+
- Script route: `<studio>/kitchen-sink/scripts/<script-name>`
27+
28+
Scripts are browser-side TypeScript modules discovered at build time with Vite `import.meta.glob`.
29+
They run inside Sanity Studio with the logged-in user's permissions and receive the Studio client.
30+
31+
## Adding A Script
32+
33+
Add a `.ts` file under `dev/test-studio/src/script-runner/scripts/` with a default `StudioScript`
34+
export.
35+
36+
```ts
37+
import type {StudioScript} from '../types'
38+
39+
const script: StudioScript = {
40+
name: 'my-script-name',
41+
title: 'My script name',
42+
description: 'What this script does.',
43+
apiVersion: '2026-03-01',
44+
inputs: [
45+
{
46+
name: 'documentId',
47+
title: 'Document ID',
48+
defaultValue: 'example-id',
49+
required: true,
50+
},
51+
],
52+
async run({client, inputs, log}) {
53+
log.info(`Running for ${inputs.documentId}`)
54+
await client.fetch('*[_type == "post"][0...1]')
55+
log.success('Done')
56+
},
57+
}
58+
59+
export default script
60+
```
61+
62+
Script names must be unique and use lowercase letters, numbers, and hyphens. The script name becomes
63+
the URL segment.
64+
65+
## Runtime Contract
66+
67+
`run()` receives:
68+
69+
- `client`: Sanity Studio client, configured with the script `apiVersion` or the runner default.
70+
- `inputs`: string values from the run screen, keyed by input `name`.
71+
- `log`: `info`, `success`, `warning`, and `error` methods that append output in the UI.
72+
- `signal`: an `AbortSignal` reserved for script code that supports cancellation.
73+
74+
## String Variables
75+
76+
Use `inputs` for string variables. Each input renders as a text field on the script run screen.
77+
Required inputs disable the run button until non-empty.
78+
79+
Available input fields:
80+
81+
- `name`
82+
- `title`
83+
- `description`
84+
- `defaultValue`
85+
- `placeholder`
86+
- `required`
87+
88+
All values passed to scripts are strings. Validate and trim values inside `run()` when needed.
89+
90+
## Browser-Safe Rules
91+
92+
Script runner modules execute in the browser. Do not use:
93+
94+
- `fs`, `path`, or other Node built-ins
95+
- `process.exit`
96+
- direct environment variable access
97+
- `SANITY_AUTH_TOKEN`
98+
99+
Do not create a separate Sanity client from env vars. Use the provided `client`.
100+
101+
If a task needs Node-only APIs or token-based CLI behavior, keep it in `dev/test-studio/scripts/`
102+
instead of the Studio script runner.
103+
104+
## Verification
105+
106+
After changing the runner or adding scripts, run:
107+
108+
```bash
109+
pnpm lint
110+
pnpm --filter test-studio build
111+
```
112+
113+
If `pnpm --filter test-studio build` fails because workspace package `dist` output is missing, build
114+
with dependencies first:
115+
116+
```bash
117+
pnpm --filter test-studio... build
118+
```
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
name: test-studio-script-runner
3+
description: Explains the dev/test-studio Script Runner tool. Use when adding, editing, running, or documenting scripts in dev/test-studio/src/script-runner, or when the user mentions the Scripts tool, Studio runner scripts, script variables, or browser-side Sanity Studio scripts.
4+
---
5+
6+
# Test Studio Script Runner
7+
8+
Use this skill when working with the `Scripts` tool in `dev/test-studio`.
9+
10+
## Key Files
11+
12+
- Tool plugin: `dev/test-studio/src/script-runner/index.tsx`
13+
- Runner UI: `dev/test-studio/src/script-runner/ScriptRunnerTool.tsx`
14+
- Script registry: `dev/test-studio/src/script-runner/registry.ts`
15+
- Script contract: `dev/test-studio/src/script-runner/types.ts`
16+
- Script modules: `dev/test-studio/src/script-runner/scripts/*/index.ts`
17+
- Agent-facing docs: `dev/test-studio/src/script-runner/README.md`
18+
19+
Read `README.md` and `types.ts` before changing the runner or adding scripts.
20+
21+
## What The Tool Does
22+
23+
The runner is a Sanity Studio custom tool registered in the `kitchen-sink` workspace.
24+
25+
- Home route: `<studio>/kitchen-sink/scripts`
26+
- Script route: `<studio>/kitchen-sink/scripts/<script-name>`
27+
28+
Scripts are browser-side TypeScript modules discovered at build time with Vite `import.meta.glob`.
29+
They run inside Sanity Studio with the logged-in user's permissions and receive the Studio client.
30+
31+
## Adding A Script
32+
33+
Add a folder under `dev/test-studio/src/script-runner/scripts/`. The folder name should match the
34+
script name. Put the registered entrypoint in `index.ts`; any helper files can live beside it.
35+
36+
```text
37+
scripts/
38+
my-script-name/
39+
index.ts
40+
helpers.ts
41+
```
42+
43+
Only `scripts/*/index.ts` files are discovered at build time.
44+
45+
```ts
46+
import type {StudioScript} from '../../types'
47+
48+
const script: StudioScript = {
49+
name: 'my-script-name',
50+
title: 'My script name',
51+
description: 'What this script does.',
52+
apiVersion: '2026-03-01',
53+
inputs: [
54+
{
55+
name: 'documentId',
56+
title: 'Document ID',
57+
defaultValue: 'example-id',
58+
required: true,
59+
},
60+
],
61+
async run({client, inputs, log}) {
62+
log.info(`Running for ${inputs.documentId}`)
63+
await client.fetch('*[_type == "post"][0...1]')
64+
log.success('Done')
65+
},
66+
}
67+
68+
export default script
69+
```
70+
71+
Script names must be unique and use lowercase letters, numbers, and hyphens. Keep the folder name
72+
and script `name` aligned. The script name becomes the URL segment.
73+
74+
## Runtime Contract
75+
76+
`run()` receives:
77+
78+
- `client`: Sanity Studio client, configured with the script `apiVersion` or the runner default.
79+
- `inputs`: string values from the run screen, keyed by input `name`.
80+
- `log`: `info`, `success`, `warning`, and `error` methods that append output in the UI.
81+
- `signal`: an `AbortSignal` reserved for script code that supports cancellation.
82+
83+
## String Variables
84+
85+
Use `inputs` for string variables. Each input renders as a text field on the script run screen.
86+
Required inputs disable the run button until non-empty.
87+
88+
Available input fields:
89+
90+
- `name`
91+
- `title`
92+
- `description`
93+
- `defaultValue`
94+
- `placeholder`
95+
- `required`
96+
97+
All values passed to scripts are strings. Validate and trim values inside `run()` when needed.
98+
99+
## Browser-Safe Rules
100+
101+
Script runner modules execute in the browser. Do not use:
102+
103+
- `fs`, `path`, or other Node built-ins
104+
- `process.exit`
105+
- direct environment variable access
106+
- `SANITY_AUTH_TOKEN`
107+
108+
Do not create a separate Sanity client from env vars. Use the provided `client`.
109+
110+
If a task needs Node-only APIs or token-based CLI behavior, keep it in `dev/test-studio/scripts/`
111+
instead of the Studio script runner.
112+
113+
## Verification
114+
115+
After changing the runner or adding scripts, run:
116+
117+
```bash
118+
pnpm lint
119+
pnpm --filter test-studio build
120+
```
121+
122+
If `pnpm --filter test-studio build` fails because workspace package `dist` output is missing, build
123+
with dependencies first:
124+
125+
```bash
126+
pnpm --filter test-studio... build
127+
```

dev/test-studio/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"dependencies": {
1515
"@sanity/assist": "workspace:*",
16+
"@sanity/client": "catalog:",
1617
"@sanity/code-input": "workspace:*",
1718
"@sanity/color-input": "workspace:*",
1819
"@sanity/debug-live-sync-tags": "workspace:*",

dev/test-studio/sanity.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {markdownExample} from '#markdown'
2222
import {presetsWorkspace} from '#presets'
2323
import {richDateInputExample} from '#rich-date-input'
24+
import {scriptRunnerTool} from '#script-runner'
2425
import {sfccExample} from '#sfcc'
2526
import {studioSecretsExample} from '#studio-secrets'
2627
import {unsplashExample} from '#unsplash'
@@ -73,6 +74,7 @@ export default defineConfig([
7374
markdownExample(),
7475
debugSecrets(),
7576
unsplashExample(),
77+
scriptRunnerTool(),
7678
vercelProtectionBypassTool(),
7779
visionTool(),
7880
],

dev/test-studio/src/internationalized-array/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
import {internationalizedArray} from 'sanity-plugin-internationalized-array'
1010
import {structureTool} from 'sanity/structure'
1111

12+
import {issue520Repro} from './issue-520-repro'
13+
1214
const internationalizedPost = defineType({
1315
type: 'document',
1416
name: 'internationalizedPost',
@@ -183,7 +185,15 @@ const table = defineType({
183185

184186
export const internationalizedArrayExample = definePlugin(() => ({
185187
schema: {
186-
types: [internationalizedPost, person, bodyContent, circularSchemaRepro, table, movie],
188+
types: [
189+
internationalizedPost,
190+
person,
191+
bodyContent,
192+
circularSchemaRepro,
193+
table,
194+
movie,
195+
issue520Repro,
196+
],
187197
},
188198
plugins: [
189199
internationalizedArray({
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {defineField, defineType} from 'sanity'
2+
3+
/**
4+
* Document type used to reproduce
5+
* https://github.com/sanity-io/plugins/issues/520
6+
*
7+
* The bug: when the items of an internationalized array are stored in a
8+
* different order than the master `languages` config (e.g. pushed via the
9+
* API in randomized `_key` order), opening the document in a read-only
10+
* Studio perspective (e.g. the *published* version of a doc that was
11+
* created/updated via a release) makes the field crash with
12+
* `"Attempted to patch a read-only document"`.
13+
*
14+
* Seed the misordered document with:
15+
* pnpm --filter test-studio seed:issue-520
16+
*
17+
* Then open it in the `kitchen-sink` workspace and switch the perspective
18+
* to the published version.
19+
*/
20+
export const issue520Repro = defineType({
21+
name: 'issue520Repro',
22+
title: 'Issue #520 reproduction',
23+
type: 'document',
24+
fields: [
25+
defineField({
26+
name: 'title',
27+
title: 'Title',
28+
type: 'string',
29+
}),
30+
defineField({
31+
name: 'localized',
32+
title: 'Localized text (out-of-order key set by seed script)',
33+
type: 'internationalizedArrayString',
34+
}),
35+
],
36+
})

0 commit comments

Comments
 (0)