Skip to content

Commit e3a02c4

Browse files
authored
chore: add docgen script (#7387)
* chore: add docgen script * fixup! chore: add docgen script * fixup! chore: add docgen script
1 parent 5c75a38 commit e3a02c4

File tree

10 files changed

+285
-1
lines changed

10 files changed

+285
-1
lines changed

dev/test-studio/schema/debug/validation.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ export default defineType({
368368
title: 'A geopoint',
369369
description: 'Required, must be in Norway somewhere',
370370
validation: (Rule) =>
371-
Rule.required().custom((geoPoint) => {
371+
Rule.custom((geoPoint) => {
372372
if (!geoPoint) {
373373
return true
374374
}

dev/test-studio/schema/species.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default {
1818
title: 'Species',
1919
type: 'string',
2020
},
21+
{name: 'description', type: 'text', title: 'Description'},
2122
{
2223
name: 'image',
2324
title: 'Image',

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"deploy:movies": "pnpm build && cd examples/movies-studio && sanity deploy",
3030
"deploy:test": "pnpm build && cd dev/test-studio && sanity deploy",
3131
"dev": "pnpm dev:test-studio",
32+
"generate:docs": "tsx --env-file=.env.local ./scripts/generate-documents/cli.ts",
3233
"dev:cli": "pnpm watch",
3334
"dev:design-studio": "pnpm --filter design-studio dev",
3435
"dev:embedded-studio": "pnpm --filter embedded-studio dev",

scripts/generate-documents/cli.ts

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import path from 'node:path'
2+
import {parseArgs} from 'node:util'
3+
4+
import {createClient} from '@sanity/client'
5+
import {tap} from 'rxjs'
6+
7+
import {readEnv} from '../utils/envVars'
8+
import {run} from './run'
9+
import {book} from './templates/book'
10+
import {species} from './templates/species'
11+
import {validation} from './templates/validation'
12+
13+
const {values: args} = parseArgs({
14+
args: process.argv.slice(2),
15+
options: {
16+
number: {
17+
type: 'string',
18+
short: 'n',
19+
default: '1',
20+
},
21+
dataset: {
22+
type: 'string',
23+
},
24+
bundle: {
25+
type: 'string',
26+
},
27+
draft: {
28+
type: 'boolean',
29+
},
30+
published: {
31+
type: 'boolean',
32+
},
33+
size: {
34+
type: 'string',
35+
},
36+
concurrency: {
37+
type: 'string',
38+
short: 'c',
39+
},
40+
template: {
41+
type: 'string',
42+
short: 't',
43+
},
44+
help: {
45+
type: 'boolean',
46+
short: 'h',
47+
},
48+
},
49+
})
50+
51+
const templates = {
52+
validation: validation,
53+
book: book,
54+
species: species,
55+
}
56+
57+
const HELP_TEXT = `Usage: tsx --env-file=.env.local ./${path.relative(process.cwd(), process.argv[1])} --template <template> [arguments]
58+
59+
Arguments:
60+
--template, -t <template>: Template to use (required). Possible values: ${Object.keys(templates).join(', ')}
61+
62+
--dataset: Dataset to generate documents in (defaults to 'test')
63+
--amount, -n <int>: Number of documents to generate
64+
--draft: Generate draft documents
65+
--published: Generate published documents
66+
--bundle <string>: Bundle to generate documents in
67+
--size <bytes>: Size (in bytes) of the generated document (will be approximated)
68+
--concurrency, -c <int>: Number of concurrent requests
69+
--help, -h: Show this help message
70+
71+
Add more templates by adding them to the "./${path.relative(process.cwd(), path.join(__dirname, './templates'))}" directory.
72+
`
73+
74+
if (args.help) {
75+
// eslint-disable-next-line no-console
76+
console.log(HELP_TEXT)
77+
process.exit(0)
78+
}
79+
80+
if (!args.template) {
81+
console.error('Error: Missing required `--template` argument\n')
82+
console.error(HELP_TEXT)
83+
process.exit(1)
84+
}
85+
if (!(args.template in templates)) {
86+
console.error(`Error: Template "${args.template}" does not exist. Available templates: ${Object.keys(templates).join(', ')}
87+
`)
88+
console.error(HELP_TEXT)
89+
process.exit(1)
90+
}
91+
92+
const template = templates[args.template as keyof typeof templates]
93+
94+
type KnownEnvVar = 'TEST_STUDIO_WRITE_TOKEN'
95+
96+
const client = createClient({
97+
projectId: 'ppsg7ml5',
98+
dataset: args.dataset || 'test',
99+
token: readEnv<KnownEnvVar>('TEST_STUDIO_WRITE_TOKEN'),
100+
apiVersion: '2024-07-31',
101+
useCdn: false,
102+
})
103+
104+
run({
105+
bundle: args.bundle,
106+
draft: args.draft || true,
107+
published: args.published,
108+
concurrency: args.concurrency ? Number(args.concurrency) : undefined,
109+
number: args.number ? Number(args.number) : undefined,
110+
size: args.size ? Number(args.size) : undefined,
111+
template,
112+
client,
113+
})
114+
.pipe(
115+
tap({
116+
next: (doc) => {
117+
// eslint-disable-next-line no-console
118+
console.log('Created', doc._id)
119+
},
120+
error: console.error,
121+
}),
122+
)
123+
.subscribe()

scripts/generate-documents/run.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {type SanityClient, type SanityDocument} from '@sanity/client'
2+
import {mergeMap, range} from 'rxjs'
3+
4+
import {type DocGenTemplate} from './types'
5+
6+
export type ProgramArgs = {
7+
client: SanityClient
8+
number?: number
9+
published?: boolean
10+
size?: number
11+
draft?: boolean
12+
bundle?: string
13+
template: DocGenTemplate
14+
concurrency?: number
15+
}
16+
17+
export function run(_args: ProgramArgs) {
18+
const {client, bundle, draft, concurrency, published, template, number, size} = _args
19+
const runId = Date.now()
20+
21+
return range(0, number || 1).pipe(
22+
mergeMap((i) => {
23+
const id = `${runId}-autogenerated-${i}`
24+
25+
const templateOptions = {
26+
size: size ?? 256,
27+
}
28+
29+
const title = `Generated #${runId.toString(32).slice(2)}/${i}`
30+
31+
const publishedDocument = published
32+
? {...template({...templateOptions, title: `${title} - Published`}), _id: id}
33+
: undefined
34+
35+
const draftDocument = draft
36+
? {
37+
...template({...templateOptions, title: `${title} - Published`}),
38+
_id: `drafts.${id}`,
39+
title: `${title} - Draft`,
40+
}
41+
: undefined
42+
43+
const bundleDocument = bundle
44+
? {
45+
...template({...templateOptions, title: `${title} - Published`}),
46+
_id: `${bundle}.${id}`,
47+
_version: {},
48+
title: `${title} - Bundle: ${bundle}`,
49+
}
50+
: undefined
51+
52+
return [publishedDocument, draftDocument, bundleDocument].flatMap((d) => (d ? [d] : []))
53+
}),
54+
mergeMap((doc) => {
55+
console.log('Creating', doc._id)
56+
return client.observable.create(doc as SanityDocument, {autoGenerateArrayKeys: true})
57+
}, concurrency ?? 2),
58+
)
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {type DocGenTemplate} from '../types'
2+
3+
export const book: DocGenTemplate = () => ({
4+
_type: 'book',
5+
title: 'Generated book',
6+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {type DocGenTemplate} from '../types'
2+
import {loremString} from '../utils/lorem'
3+
4+
export const species: DocGenTemplate = (options) => ({
5+
_type: 'species',
6+
name: options.title,
7+
foo: 'bar',
8+
genus: 'Foo',
9+
species: 'Bar',
10+
description: loremString(options.size),
11+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {type DocGenTemplate} from '../types'
2+
import {loremString} from '../utils/lorem'
3+
4+
export const validation: DocGenTemplate = (options) => ({
5+
_type: 'validationTest',
6+
arrayOfSlugs: [
7+
{
8+
_type: 'slugEmbed',
9+
sku: {
10+
_type: 'slug',
11+
current: 'foo',
12+
},
13+
},
14+
],
15+
body: [
16+
{
17+
_type: 'block',
18+
children: [
19+
{
20+
_type: 'span',
21+
marks: [],
22+
text: loremString(options.size / 2),
23+
},
24+
],
25+
markDefs: [],
26+
style: 'normal',
27+
},
28+
],
29+
bymonth: [11],
30+
checkbox: true,
31+
dropdown: 'one',
32+
infoValidation: 'moop',
33+
intro: loremString(options.size / 2),
34+
lowestTemperature: 50,
35+
myFancyUrlField: 'http://www.sanity.io',
36+
myUrlField: 'https://www.sanity.io',
37+
radio: 'one',
38+
relativeUrl: '/foo/bar',
39+
slug: {
40+
_type: 'slug',
41+
current: 'slug',
42+
},
43+
switch: false,
44+
title: options.title,
45+
titleCase: 'bar',
46+
translations: {
47+
no: 'asdasdsad',
48+
},
49+
})

scripts/generate-documents/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
interface DocGenTemplateOptions {
2+
size: number
3+
title: string
4+
}
5+
6+
type TypedDocument = {_type: string} & Record<string, unknown>
7+
8+
export type DocGenTemplate = (options: DocGenTemplateOptions) => TypedDocument
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const encoder = new TextEncoder()
2+
const decoder = new TextDecoder()
3+
const TEXT = encoder.encode(
4+
`Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. `,
5+
)
6+
7+
/**
8+
* Generate a Uint8Array of lorem ipsum text of the given byte size
9+
* @param byteSize - The size in bytes of the uint array to generate
10+
* @internal
11+
*/
12+
export function loremBytes(byteSize: number): Uint8Array {
13+
const result = new Uint8Array(byteSize)
14+
for (let i = 0; i < byteSize; i++) {
15+
result[i] = TEXT[i % TEXT.byteLength]
16+
}
17+
return result
18+
}
19+
20+
/**
21+
* Generate a lorem ipsum string of the given byte size
22+
* @param byteSize - The size in bytes of the string to generate
23+
*/
24+
export function loremString(byteSize: number): string {
25+
return decoder.decode(loremBytes(byteSize))
26+
}

0 commit comments

Comments
 (0)