From c1e4f2aac2431fc2c5a6a4d8ea3e0e074cacdb59 Mon Sep 17 00:00:00 2001 From: Sindre Gulseth Date: Tue, 12 Mar 2024 11:51:52 +0100 Subject: [PATCH] feat: add cmd to generate a JSON representation of schema (#5919) * feat: add cmd to generate a JSON representation of schema * feat: refactor to use compiled schema * fix: append _type to objects * chore(schema): clean up and describe execution * chore(schema): lock groq-js to minor canary --- packages/@sanity/schema/package.json | 2 + .../@sanity/schema/src/_exports/_internal.ts | 1 + .../schema/src/sanity/extractSchema.ts | 534 ++ .../__snapshots__/extractSchema.test.ts.snap | 4475 +++++++++++++++++ .../test/extractSchema/extractSchema.test.ts | 379 ++ .../extractSchema/fixtures/assetSourceData.ts | 26 + .../test/extractSchema/fixtures/block.ts | 432 ++ .../test/extractSchema/fixtures/fileAsset.ts | 103 + .../test/extractSchema/fixtures/geopoint.ts | 22 + .../test/extractSchema/fixtures/imageAsset.ts | 108 + .../test/extractSchema/fixtures/imageCrop.ts | 23 + .../extractSchema/fixtures/imageDimensions.ts | 10 + .../extractSchema/fixtures/imageHotspot.ts | 23 + .../extractSchema/fixtures/imageMetadata.ts | 56 + .../extractSchema/fixtures/imagePalette.ts | 14 + .../fixtures/imagePaletteSwatch.ts | 11 + .../test/extractSchema/fixtures/slug.ts | 20 + packages/sanity/package.config.ts | 5 + .../cli/actions/schema/extractAction.ts | 74 + .../src/_internal/cli/commands/index.ts | 2 + .../commands/schema/extractSchemaCommand.ts | 33 + .../_internal/cli/threads/extractSchema.ts | 76 + pnpm-lock.yaml | 15 + 23 files changed, 6444 insertions(+) create mode 100644 packages/@sanity/schema/src/sanity/extractSchema.ts create mode 100644 packages/@sanity/schema/test/extractSchema/__snapshots__/extractSchema.test.ts.snap create mode 100644 packages/@sanity/schema/test/extractSchema/extractSchema.test.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/assetSourceData.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/block.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/fileAsset.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/geopoint.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imageAsset.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imageCrop.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imageDimensions.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imageHotspot.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imageMetadata.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imagePalette.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/imagePaletteSwatch.ts create mode 100644 packages/@sanity/schema/test/extractSchema/fixtures/slug.ts create mode 100644 packages/sanity/src/_internal/cli/actions/schema/extractAction.ts create mode 100644 packages/sanity/src/_internal/cli/commands/schema/extractSchemaCommand.ts create mode 100644 packages/sanity/src/_internal/cli/threads/extractSchema.ts diff --git a/packages/@sanity/schema/package.json b/packages/@sanity/schema/package.json index 5606fd81cd5..0aa2a7a4342 100644 --- a/packages/@sanity/schema/package.json +++ b/packages/@sanity/schema/package.json @@ -77,6 +77,7 @@ "@sanity/generate-help-url": "^3.0.0", "@sanity/types": "3.32.0", "arrify": "^1.0.1", + "groq-js": "^1.5.0-canary.1", "humanize-list": "^1.0.1", "leven": "^3.1.0", "lodash": "^4.17.21", @@ -84,6 +85,7 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", + "@sanity/icons": "^2.8.0", "rimraf": "^3.0.2" } } diff --git a/packages/@sanity/schema/src/_exports/_internal.ts b/packages/@sanity/schema/src/_exports/_internal.ts index 2e073fe4d76..1da4b6c341b 100644 --- a/packages/@sanity/schema/src/_exports/_internal.ts +++ b/packages/@sanity/schema/src/_exports/_internal.ts @@ -4,6 +4,7 @@ export { resolveSearchConfig, resolveSearchConfigForBaseFieldPaths, } from '../legacy/searchConfig/resolve' +export {extractSchema} from '../sanity/extractSchema' export {groupProblems} from '../sanity/groupProblems' export { type _FIXME_ as FIXME, diff --git a/packages/@sanity/schema/src/sanity/extractSchema.ts b/packages/@sanity/schema/src/sanity/extractSchema.ts new file mode 100644 index 00000000000..1b7681c734d --- /dev/null +++ b/packages/@sanity/schema/src/sanity/extractSchema.ts @@ -0,0 +1,534 @@ +import { + type ArraySchemaType, + type NumberSchemaType, + type ObjectField, + type ObjectFieldType, + type ObjectSchemaType, + type ReferenceSchemaType, + type Rule, + type Schema as SchemaDef, + type SchemaType as SanitySchemaType, + type StringSchemaType, +} from '@sanity/types' +import { + type ArrayTypeNode, + createReferenceTypeNode, + type DocumentSchemaType, + type InlineTypeNode, + type NullTypeNode, + type NumberTypeNode, + type ObjectAttribute, + type ObjectTypeNode, + type SchemaType, + type StringTypeNode, + type TypeDeclarationSchemaType, + type TypeNode, + type UnionTypeNode, + type UnknownTypeNode, +} from 'groq-js' + +const documentDefaultFields = (typeName: string): Record => ({ + _id: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + _type: { + type: 'objectAttribute', + value: {type: 'string', value: typeName}, + }, + _createdAt: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + _updatedAt: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + _rev: { + type: 'objectAttribute', + value: {type: 'string'}, + }, +}) +const typesMap = new Map([ + ['text', {type: 'string'}], + ['url', {type: 'string'}], + ['datetime', {type: 'string'}], + ['date', {type: 'string'}], + ['boolean', {type: 'boolean'}], + ['email', {type: 'string'}], +]) + +export interface ExtractSchemaOptions { + enforceRequiredFields?: boolean +} + +export function extractSchema( + schemaDef: SchemaDef, + extractOptions: ExtractSchemaOptions = {}, +): SchemaType { + const inlineFields = new Set() + const schema: SchemaType = [] + + // get a list of all the types in the schema, sorted by their dependencies. This ensures that when we check for inline/reference types, we have already processed the type + const sortedSchemaTypeNames = sortByDependencies(schemaDef) + sortedSchemaTypeNames.forEach((typeName) => { + const schemaType = schemaDef.get(typeName) + if (schemaType === undefined) { + return + } + const base = convertBaseType(schemaType) + if (base === null) { + return + } + if (base.type === 'type') { + inlineFields.add(schemaType) + } + + schema.push(base) + }) + + function convertBaseType( + schemaType: SanitySchemaType, + ): DocumentSchemaType | TypeDeclarationSchemaType | null { + let typeName: string | undefined + if (schemaType.type) { + typeName = schemaType.type.name + } else if ('jsonType' in schemaType) { + typeName = schemaType.jsonType + } + + if (typeName === 'document' && isObjectType(schemaType)) { + const defaultAttributes = documentDefaultFields(schemaType.name) + + const object = createObject(schemaType) + if (object.type === 'unknown') { + return null + } + + return { + name: schemaType.name, + type: 'document', + attributes: { + ...defaultAttributes, + ...object.attributes, + }, + } + } + + const value = convertSchemaType(schemaType) + if (value.type === 'unknown') { + return null + } + if (value.type === 'object') { + return { + name: schemaType.name, + type: 'type', + value: { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: schemaType.name, + }, + }, + ...value.attributes, + }, + }, + } + } + + return { + name: schemaType.name, + type: 'type', + value, + } + } + + function convertSchemaType(schemaType: SanitySchemaType): TypeNode { + if (lastType(schemaType)?.name === 'document') { + return createReferenceTypeNode(schemaType.name) + } + + // if we have already seen the base type, we can just reference it + if (inlineFields.has(schemaType.type)) { + return {type: 'inline', name: schemaType.type.name} satisfies InlineTypeNode + } + + // If we have a type that is point to a type, that is pointing to a type, we assume this is a circular reference + // and we return an inline type referencing it instead + if (schemaType.type?.type?.name === 'object') { + return {type: 'inline', name: schemaType.type.name} satisfies InlineTypeNode + } + + if (isStringType(schemaType)) { + return createStringTypeNodeDefintion(schemaType) + } + + if (isNumberType(schemaType)) { + return createNumberTypeNodeDefintion(schemaType) + } + + // map some known types + if (typesMap.has(schemaType.name)) { + return typesMap.get(schemaType.name) + } + + // Cross dataset references are not supported + if (isCrossDatasetReferenceType(schemaType)) { + return {type: 'unknown'} satisfies UnknownTypeNode // we don't support cross-dataset references at the moment + } + + if (isReferenceType(schemaType)) { + return createReferenceTypeNodeDefintion(schemaType) + } + + if (isArrayType(schemaType)) { + return createArray(schemaType) + } + + if (isObjectType(schemaType)) { + return createObject(schemaType) + } + + throw new Error(`Type "${schemaType.name}" not found`) + } + + function createObject( + schemaType: ObjectSchemaType | SanitySchemaType, + ): ObjectTypeNode | UnknownTypeNode { + const attributes: Record = {} + + const fields = gatherFields(schemaType) + for (const field of fields) { + const fieldIsRequired = isFieldRequired(field) + const value = convertSchemaType(field.type) + if (value === null) { + continue + } + attributes[field.name] = { + type: 'objectAttribute', + value, + optional: extractOptions.enforceRequiredFields ? fieldIsRequired : true, + } + } + + // Ignore empty objects + if (Object.keys(attributes).length === 0) { + return {type: 'unknown'} satisfies UnknownTypeNode + } + + if (schemaType.type?.name !== 'document') { + attributes._type = { + type: 'objectAttribute', + value: { + type: 'string', + value: schemaType.name, + }, + } + } + + return { + type: 'object', + attributes, + } + } + + function createArray(arraySchemaType: ArraySchemaType): ArrayTypeNode | NullTypeNode { + const of: TypeNode[] = [] + for (const item of arraySchemaType.of) { + const field = convertSchemaType(item) + if (field.type === 'inline') { + of.push({ + type: 'object', + attributes: { + _key: createKeyField(), + }, + rest: field, + } satisfies ObjectTypeNode) + } else if (field.type === 'object') { + field.rest = { + type: 'object', + attributes: { + _key: createKeyField(), + }, + } + of.push(field) + } else { + of.push(field) + } + } + + if (of.length === 0) { + return {type: 'null'} + } + + return { + type: 'array', + of: + of.length > 1 + ? { + type: 'union', + of, + } + : of[0], + } + } + + return schema +} + +function createKeyField(): ObjectAttribute { + return { + type: 'objectAttribute', + value: { + type: 'string', + }, + } +} + +function isFieldRequired(field: ObjectField): boolean { + const {validation} = field.type + if (!validation) { + return false + } + const rules = Array.isArray(validation) ? validation : [validation] + for (const rule of rules) { + let required = false + + // hack to check if a field is required. We create a proxy that returns itself when a method is called, + // if the method is "required" we set a flag + const proxy = new Proxy( + {}, + { + get: (target, methodName) => () => { + if (methodName === 'required') { + required = true + } + return proxy + }, + }, + ) as Rule + if (typeof rule === 'function') { + rule(proxy) + if (required) { + return true + } + } + } + + return false +} + +function isObjectType(typeDef: SanitySchemaType): typeDef is ObjectSchemaType { + return isType(typeDef, 'object') || typeDef.jsonType === 'object' || 'fields' in typeDef +} +function isArrayType(typeDef: SanitySchemaType): typeDef is ArraySchemaType { + return isType(typeDef, 'array') +} +function isReferenceType(typeDef: SanitySchemaType): typeDef is ReferenceSchemaType { + return isType(typeDef, 'reference') +} +function isCrossDatasetReferenceType(typeDef: SanitySchemaType) { + return isType(typeDef, 'crossDatasetReference') +} +function isStringType(typeDef: SanitySchemaType): typeDef is StringSchemaType { + return isType(typeDef, 'string') +} +function isNumberType(typeDef: SanitySchemaType): typeDef is NumberSchemaType { + return isType(typeDef, 'number') +} +function createStringTypeNodeDefintion( + stringSchemaType: StringSchemaType, +): StringTypeNode | UnionTypeNode { + if (stringSchemaType.options?.list) { + return { + type: 'union', + of: stringSchemaType.options.list.map((v) => ({ + type: 'string', + value: typeof v === 'string' ? v : v.value, + })), + } + } + return { + type: 'string', + } +} + +function createNumberTypeNodeDefintion( + numberSchemaType: NumberSchemaType, +): NumberTypeNode | UnionTypeNode { + if (numberSchemaType.options?.list) { + return { + type: 'union', + of: numberSchemaType.options.list.map((v) => ({ + type: 'number', + value: typeof v === 'number' ? v : v.value, + })), + } + } + return { + type: 'number', + } +} + +function createReferenceTypeNodeDefintion( + reference: ReferenceSchemaType, +): ObjectTypeNode | UnionTypeNode { + const references = gatherReferenceNames(reference) + if (references.length === 1) { + return createReferenceTypeNode(references[0]) + } + + return { + type: 'union', + of: references.map((name) => createReferenceTypeNode(name)), + } +} + +// Traverse the reference type tree and gather all the reference names +function gatherReferenceNames(type: ReferenceSchemaType): string[] { + const allReferences = gatherReferenceTypes(type) + // Remove duplicates + return [...new Set([...allReferences.map((ref) => ref.name)])] +} + +function gatherReferenceTypes(type: ReferenceSchemaType): ObjectSchemaType[] { + const refTo = 'to' in type ? type.to : [] + if ('type' in type && isReferenceType(type.type)) { + return [...gatherReferenceTypes(type.type), ...refTo] + } + + return refTo +} + +// Traverse the type tree and gather all the fields +function gatherFields(type: SanitySchemaType | ObjectSchemaType): ObjectField[] { + if ('fields' in type) { + return type.type ? gatherFields(type.type).concat(type.fields) : type.fields + } + + return [] +} + +// Traverse the type tree and check if the type or any of its subtypes are of the given type +function isType( + typeDef: SanitySchemaType | ObjectField | ObjectFieldType, + typeName: string, +): boolean { + let type: SchemaType | ObjectField | ObjectFieldType | undefined = typeDef + while (type) { + if (type.name === typeName || (type.type && type.type.name === typeName)) { + return true + } + + type = type.type + } + return false +} + +// Traverse the type tree and return the "last" type, ie deepest type in the tree +function lastType(typeDef: SanitySchemaType): SanitySchemaType | undefined { + let type: SchemaType | ObjectField | ObjectFieldType | undefined = typeDef + while (type) { + if (!type.type) { + return type + } + type = type.type + } + + return undefined +} + +// Sorts the types by their dependencies by using a topological sort depth-first algorithm. +function sortByDependencies(compiledSchema: SchemaDef): string[] { + const seen = new Set() + + // Walks the dependencies of a schema type and adds them to the dependencies set + function walkDependencies( + schemaType: SanitySchemaType, + dependencies: Set, + ): void { + if (seen.has(schemaType)) { + return + } + seen.add(schemaType) + + if ('fields' in schemaType) { + for (const field of gatherFields(schemaType)) { + let schemaTypeName: string | undefined + if (schemaType.type.type) { + schemaTypeName = field.type.type.name + } else if ('jsonType' in schemaType.type) { + schemaTypeName = field.type.jsonType + } + + if ( + schemaTypeName === 'document' || + schemaTypeName === 'object' || + schemaTypeName === 'block' + ) { + if (isReferenceType(field.type)) { + field.type.to.forEach((ref) => dependencies.add(ref.type)) + } else { + dependencies.add(field.type) + } + } + walkDependencies(field.type, dependencies) + } + } else if ('of' in schemaType) { + for (const item of schemaType.of) { + walkDependencies(item, dependencies) + } + } + } + const dependencyMap = new Map>() + compiledSchema.getTypeNames().forEach((typeName) => { + const schemaType = compiledSchema.get(typeName) + if (schemaType === undefined || schemaType.type === null) { + return + } + const dependencies = new Set() + + walkDependencies(schemaType, dependencies) + dependencyMap.set(schemaType, dependencies) + }) + + // Sorts the types by their dependencies + const typeNames: string[] = [] + // holds a temporary mark for types that are currently being visited, to detect cyclic dependencies + const currentlyVisiting = new Set() + + // holds a permanent mark for types that have been already visited + const visited = new Set() + + // visit implements a depth-first search + function visit(type: SanitySchemaType) { + if (visited.has(type)) { + return + } + // If we find a type that is already in the temporary mark, we have a cyclic dependency. + if (currentlyVisiting.has(type)) { + return + } + // mark this as a temporary mark, meaning it's being visited + currentlyVisiting.add(type) + const deps = dependencyMap.get(type) + if (deps !== undefined) { + deps.forEach((dep) => visit(dep)) + } + currentlyVisiting.delete(type) + visited.add(type) + + if (!typeNames.includes(type.name)) { + typeNames.unshift(type.name) + } + } + // Visit all types in the dependency map + for (const [type] of dependencyMap) { + visit(type) + } + + return typeNames +} diff --git a/packages/@sanity/schema/test/extractSchema/__snapshots__/extractSchema.test.ts.snap b/packages/@sanity/schema/test/extractSchema/__snapshots__/extractSchema.test.ts.snap new file mode 100644 index 00000000000..4926d91ea69 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/__snapshots__/extractSchema.test.ts.snap @@ -0,0 +1,4475 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Extract schema test Extracts schema general 1`] = ` +Array [ + Object { + "name": "sanity.imagePaletteSwatch", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imagePaletteSwatch", + }, + }, + "background": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "foreground": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "population": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "sanity.imagePalette", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imagePalette", + }, + }, + "darkMuted": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "darkVibrant": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "dominant": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "lightMuted": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "lightVibrant": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "muted": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + "vibrant": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePaletteSwatch", + "type": "inline", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "sanity.imageDimensions", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imageDimensions", + }, + }, + "aspectRatio": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "height": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "width": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "geopoint", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "geopoint", + }, + }, + "alt": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "lat": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "lng": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "slug", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "slug", + }, + }, + "current": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "source": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.fileAsset", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "altText": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "assetId": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "description": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "extension": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "label": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "mimeType": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "originalFilename": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "path": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "sha1hash": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "size": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "source": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.assetSourceData", + "type": "inline", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "uploadId": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "url": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "name": "sanity.fileAsset", + "type": "document", + }, + Object { + "name": "code", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "code", + }, + }, + "thecode": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "customStringType", + "type": "type", + "value": Object { + "type": "string", + }, + }, + Object { + "name": "blocksTest", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "blocksTest", + }, + }, + "arrayOfArticles": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "name": "blocksTest", + "type": "inline", + }, + "type": "object", + }, + "type": "array", + }, + }, + "blockInBlock": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "footnote", + }, + }, + "footnote": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "null", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "blockList": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "blockListEntry", + }, + }, + "blocks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "customized": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "image", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "authors": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "caption": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "test", + }, + }, + "testString": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "deep": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "object", + }, + }, + "blocks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "image", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "something": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + "defaults": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "image", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "book", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "objectWithNestedArray", + }, + }, + "array": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "object", + }, + }, + "author": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "type": "object", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "name": "code", + "type": "inline", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "otherTestObject", + }, + }, + "field1": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "field3": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "object", + }, + }, + "aNumber": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "aString": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "first": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "minimal": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "null", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "readOnlyWithDefaults": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "image", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "book", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "name": "code", + "type": "inline", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "testObject", + }, + }, + "field1": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "otherTestObject", + }, + }, + "field1": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "field3": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "object", + }, + }, + "aNumber": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "aString": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "recursive": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "object", + }, + }, + "blocks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "null", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "name": "blocksTest", + "type": "inline", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + }, + "type": "object", + }, + }, + "reproCH9436": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "imageWithPortableTextCaption", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "caption": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "withGeopoint": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "of": Array [ + Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "name": "geopoint", + "type": "inline", + }, + "type": "object", + }, + ], + "type": "union", + }, + "type": "array", + }, + }, + }, + "type": "object", + }, + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "book", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "name": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "name": "book", + "type": "document", + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "author", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "name": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "profilePicture": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "image", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.imageAsset", + "type": "object", + }, + }, + "attribution": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "caption": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "crop": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageCrop", + "type": "inline", + }, + }, + "hotspot": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageHotspot", + "type": "inline", + }, + }, + }, + "type": "object", + }, + }, + }, + "name": "author", + "type": "document", + }, + Object { + "name": "sanity.imageCrop", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imageCrop", + }, + }, + "bottom": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "left": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "right": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "top": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "sanity.imageHotspot", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imageHotspot", + }, + }, + "height": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "width": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "x": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "y": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + }, + "type": "object", + }, + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imageAsset", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "altText": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "assetId": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "description": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "extension": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "label": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "metadata": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageMetadata", + "type": "inline", + }, + }, + "mimeType": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "originalFilename": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "path": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "sha1hash": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "size": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "source": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.assetSourceData", + "type": "inline", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "uploadId": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "url": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "name": "sanity.imageAsset", + "type": "document", + }, + Object { + "name": "sanity.assetSourceData", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.assetSourceData", + }, + }, + "id": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "name": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "url": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "sanity.imageMetadata", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "sanity.imageMetadata", + }, + }, + "blurHash": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "dimensions": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imageDimensions", + "type": "inline", + }, + }, + "hasAlpha": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + "isOpaque": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + "location": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "geopoint", + "type": "inline", + }, + }, + "lqip": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "palette": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "sanity.imagePalette", + "type": "inline", + }, + }, + }, + "type": "object", + }, + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "validDocument", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "blocks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "block", + }, + }, + "children": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "span", + }, + }, + "marks": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "type": "string", + }, + "type": "array", + }, + }, + "text": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "level": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "listItem": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "bullet", + }, + Object { + "type": "string", + "value": "number", + }, + ], + "type": "union", + }, + }, + "markDefs": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "link", + }, + }, + "href": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "style": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "normal", + }, + Object { + "type": "string", + "value": "h1", + }, + Object { + "type": "string", + "value": "h2", + }, + Object { + "type": "string", + "value": "h3", + }, + Object { + "type": "string", + "value": "h4", + }, + Object { + "type": "string", + "value": "h5", + }, + Object { + "type": "string", + "value": "h6", + }, + Object { + "type": "string", + "value": "blockquote", + }, + ], + "type": "union", + }, + }, + }, + "rest": Object { + "attributes": Object { + "_key": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + "type": "object", + }, + "type": "array", + }, + }, + "customStringType": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "customStringType", + "type": "inline", + }, + }, + "list": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "of": Array [ + Object { + "type": "string", + "value": "a", + }, + Object { + "type": "string", + "value": "b", + }, + Object { + "type": "string", + "value": "c", + }, + ], + "type": "union", + }, + }, + "manuscript": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "manuscript", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.fileAsset", + "type": "object", + }, + }, + "author": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "type": "object", + }, + }, + "description": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + "number": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + "other": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "otherValidDocument", + "type": "object", + }, + }, + "others": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "otherValidDocument", + "type": "object", + }, + }, + "someInlinedObject": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "name": "obj", + "type": "inline", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "name": "validDocument", + "type": "document", + }, + Object { + "attributes": Object { + "_createdAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_id": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_rev": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "otherValidDocument", + }, + }, + "_updatedAt": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "title": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "name": "otherValidDocument", + "type": "document", + }, + Object { + "name": "manuscript", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "manuscript", + }, + }, + "asset": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "sanity.fileAsset", + "type": "object", + }, + }, + "author": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "attributes": Object { + "_ref": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "reference", + }, + }, + "_weak": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "boolean", + }, + }, + }, + "dereferencesTo": "author", + "type": "object", + }, + }, + "description": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + }, + "type": "object", + }, + }, + Object { + "name": "obj", + "type": "type", + "value": Object { + "attributes": Object { + "_type": Object { + "type": "objectAttribute", + "value": Object { + "type": "string", + "value": "obj", + }, + }, + "field1": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "string", + }, + }, + "field2": Object { + "optional": true, + "type": "objectAttribute", + "value": Object { + "type": "number", + }, + }, + }, + "type": "object", + }, + }, +] +`; diff --git a/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts b/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts new file mode 100644 index 00000000000..748461c6056 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts @@ -0,0 +1,379 @@ +import assert from 'node:assert' + +import {describe, expect, test} from '@jest/globals' +import {defineType} from '@sanity/types' + +import {Schema} from '../../src/legacy/Schema' +import {extractSchema} from '../../src/sanity/extractSchema' +import {groupProblems} from '../../src/sanity/groupProblems' +import {validateSchema} from '../../src/sanity/validateSchema' +import schemaFixtures from '../legacy/fixtures/schemas' +// built-in types +import assetSourceData from './fixtures/assetSourceData' +import Block from './fixtures/block' +import fileAsset from './fixtures/fileAsset' +import geopoint from './fixtures/geopoint' +import imageAsset from './fixtures/imageAsset' +import imageCrop from './fixtures/imageCrop' +import imageDimensions from './fixtures/imageDimensions' +import imageHotspot from './fixtures/imageHotspot' +import imageMetadata from './fixtures/imageMetadata' +import imagePalette from './fixtures/imagePalette' +import imagePaletteSwatch from './fixtures/imagePaletteSwatch' +import slug from './fixtures/slug' + +const builtinTypes = [ + assetSourceData, + slug, + geopoint, + imageAsset, + fileAsset, + imageCrop, + imageHotspot, + imageMetadata, + imageDimensions, + imagePalette, + imagePaletteSwatch, +] + +// taken from sanity/src/core/schema/createSchema.ts +function createSchema(schemaDef: {name: string; types: any[]}) { + const validated = validateSchema(schemaDef.types).getTypes() + const validation = groupProblems(validated) + const hasErrors = validation.some((group) => + group.problems.some((problem) => problem.severity === 'error'), + ) + + return Schema.compile({ + name: 'test', + types: hasErrors ? [] : [...schemaDef.types, ...builtinTypes].filter(Boolean), + }) +} + +describe('Extract schema test', () => { + test('Extracts schema general', () => { + const schema = createSchema({ + name: 'test', + types: [ + defineType({ + title: 'Valid document', + name: 'validDocument', + type: 'document', + fields: [ + { + title: 'Title', + name: 'title', + type: 'string', + }, + { + title: 'List', + name: 'list', + type: 'string', + options: { + list: ['a', 'b', 'c'], + }, + validation: (Rule) => Rule.required(), + }, + { + title: 'Number', + name: 'number', + type: 'number', + }, + { + title: 'some other object', + name: 'someInlinedObject', + type: 'obj', + }, + { + title: 'Manuscript', + name: 'manuscript', + type: 'manuscript', + }, + { + title: 'customStringType', + name: 'customStringType', + type: 'customStringType', + }, + { + title: 'Blocks', + name: 'blocks', + type: 'array', + of: [{type: 'block'}], + }, + { + type: 'reference', + name: 'other', + to: { + type: 'otherValidDocument', + }, + }, + { + type: 'reference', + name: 'others', + to: [ + { + type: 'otherValidDocument', + }, + ], + }, + ], + }), + { + title: 'Author', + name: 'author', + type: 'document', + fields: [ + { + title: 'Name', + name: 'name', + type: 'string', + }, + { + title: 'Profile picture', + name: 'profilePicture', + type: 'image', + options: { + hotspot: true, + }, + fields: [ + { + name: 'caption', + type: 'string', + title: 'Caption', + }, + { + name: 'attribution', + type: 'string', + title: 'Attribution', + }, + ], + }, + ], + }, + { + title: 'Book', + name: 'book', + type: 'document', + fields: [ + { + title: 'Name', + name: 'name', + type: 'string', + }, + ], + }, + Block, + { + title: 'Other valid document', + name: 'otherValidDocument', + type: 'document', + fields: [ + { + title: 'Title', + name: 'title', + type: 'string', + }, + ], + }, + { + type: 'object', + name: 'obj', + fields: [ + { + title: 'Field #1', + name: 'field1', + type: 'string', + }, + { + title: 'Field #2', + name: 'field2', + type: 'number', + }, + ], + }, + defineType({ + name: 'customStringType', + title: 'My custom string type', + type: 'string', + }), + { + type: 'object', + name: 'code', + fields: [ + { + title: 'The Code!', + name: 'thecode', + type: 'string', + }, + ], + }, + { + title: 'Manuscript', + name: 'manuscript', + type: 'file', + fields: [ + { + name: 'description', + type: 'string', + title: 'Description', + }, + { + name: 'author', + type: 'reference', + title: 'Author', + to: {type: 'author'}, + }, + ], + }, + ], + }) + + const extracted = extractSchema(schema) + expect(extracted.map((v) => v.name)).toStrictEqual([ + 'sanity.imagePaletteSwatch', + 'sanity.imagePalette', + 'sanity.imageDimensions', + 'geopoint', + 'slug', + 'sanity.fileAsset', + 'code', + 'customStringType', + 'blocksTest', + 'book', + 'author', + 'sanity.imageCrop', + 'sanity.imageHotspot', + 'sanity.imageAsset', + 'sanity.assetSourceData', + 'sanity.imageMetadata', + 'validDocument', + 'otherValidDocument', + 'manuscript', + 'obj', + ]) + const validDocument = extracted.find((type) => type.name === 'validDocument') + expect(validDocument).toBeDefined() + assert(validDocument !== undefined) // this is a workaround for TS, but leave the expect above for clarity in case of failure + + expect(validDocument.name).toEqual('validDocument') + expect(validDocument.type).toEqual('document') + assert(validDocument.type === 'document') // this is a workaround for TS https://github.com/DefinitelyTyped/DefinitelyTyped/issues/41179 + expect(Object.keys(validDocument.attributes)).toStrictEqual([ + '_id', + '_type', + '_createdAt', + '_updatedAt', + '_rev', + 'title', + 'list', + 'number', + 'someInlinedObject', + 'manuscript', + 'customStringType', + 'blocks', + 'other', + 'others', + ]) + + // Check that the block type is extracted correctly, as an array + expect(validDocument.attributes.blocks.type).toEqual('objectAttribute') + expect(validDocument.attributes.blocks.value.type).toEqual('array') + assert(validDocument.attributes.blocks.value.type === 'array') // this is a workaround for TS + expect(validDocument.attributes.blocks.value.of.type).toEqual('object') + assert(validDocument.attributes.blocks.value.of.type === 'object') // this is a workaround for TS + expect(Object.keys(validDocument.attributes.blocks.value.of.attributes)).toStrictEqual([ + 'children', + 'style', + 'listItem', + 'markDefs', + 'level', + '_type', + ]) + + expect(validDocument.attributes.blocks.value.of.attributes.children.value.type).toEqual('array') + assert(validDocument.attributes.blocks.value.of.attributes.children.value.type === 'array') // this is a workaround for TS + expect(validDocument.attributes.blocks.value.of.attributes.children.value.of.type).toEqual( + 'object', + ) + assert(validDocument.attributes.blocks.value.of.attributes.children.value.of.type === 'object') // this is a workaround for TS + expect( + Object.keys(validDocument.attributes.blocks.value.of.attributes.children.value.of.attributes), + ).toStrictEqual(['marks', 'text', '_type']) + + expect(extracted).toMatchSnapshot() + }) + + test('order of types does not matter', () => { + const schema1 = createSchema({ + name: 'test', + types: [ + { + title: 'Author', + name: 'author', + type: 'object', + fields: [ + { + title: 'Name', + name: 'name', + type: 'string', + }, + ], + }, + { + title: 'Book', + name: 'book', + type: 'document', + fields: [ + { + title: 'Name', + name: 'name', + type: 'string', + }, + { + title: 'Author', + name: 'author', + type: 'author', + }, + ], + }, + ], + }) + + expect(extractSchema(schema1).map((v) => v.name)).toStrictEqual([ + 'sanity.imagePaletteSwatch', + 'sanity.imagePalette', + 'sanity.imageDimensions', + 'sanity.imageHotspot', + 'sanity.imageCrop', + 'sanity.fileAsset', + 'sanity.imageAsset', + 'sanity.imageMetadata', + 'geopoint', + 'slug', + 'sanity.assetSourceData', + 'book', + 'author', + ]) + }) + + describe('Can extract sample fixtures', () => { + const cases = Object.keys(schemaFixtures).map((schemaName) => { + const schema = createSchema(schemaFixtures[schemaName]) + if (schema._original.types.length === 0) { + return {schemaName, schema: null} + } + return {schemaName, schema} + }) + const passes = cases.filter((v): v is {schemaName: string; schema: Schema} => v.schema !== null) + + test.each(passes)('extracts schema $schemaName', ({schema}) => { + const extracted = extractSchema(schema) + expect(extracted.length).toBeGreaterThan(0) // we don't really care about the exact number, just that it passes :+1: + }) + + const skips = cases.filter((v): v is {schemaName: string; schema: null} => v.schema === null) + test.skip.each(skips)('extracts schema $schemaName', () => { + // Add a test for the skipped cases so we can track them in the test report + }) + }) +}) diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/assetSourceData.ts b/packages/@sanity/schema/test/extractSchema/fixtures/assetSourceData.ts new file mode 100644 index 00000000000..248ed833a2f --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/assetSourceData.ts @@ -0,0 +1,26 @@ +export default { + name: 'sanity.assetSourceData', + title: 'Asset Source Data', + type: 'object', + fields: [ + { + name: 'name', + title: 'Source name', + description: 'A canonical name for the source this asset is originating from', + type: 'string', + }, + { + name: 'id', + title: 'Asset Source ID', + description: + 'The unique ID for the asset within the originating source so you can programatically find back to it', + type: 'string', + }, + { + name: 'url', + title: 'Asset information URL', + description: 'A URL to find more information about this asset in the originating source', + type: 'string', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/block.ts b/packages/@sanity/schema/test/extractSchema/fixtures/block.ts new file mode 100644 index 00000000000..12390f533c6 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/block.ts @@ -0,0 +1,432 @@ +import {ComposeIcon, DropIcon, ImageIcon} from '@sanity/icons' + +const linkType = { + type: 'object', + name: 'link', + fields: [ + { + type: 'string', + name: 'href', + validation: (Rule) => Rule.uri({scheme: ['http', 'https']}).required(), + }, + ], + options: { + modal: { + type: 'popover', + width: 2, + }, + }, +} + +export default { + name: 'blocksTest', + title: 'Blocks test', + type: 'object', + icon: ComposeIcon, + fields: [ + { + name: 'title', + title: 'Title', + type: 'string', + }, + { + name: 'first', + title: 'Block array as first field', + type: 'array', + of: [ + { + type: 'block', + marks: { + annotations: [linkType], + }, + }, + ], + }, + { + name: 'defaults', + title: 'Content', + description: 'Profound description of what belongs here', + type: 'array', + of: [ + {type: 'image', title: 'Image', icon: ImageIcon}, + { + type: 'reference', + name: 'authorReference', + to: {type: 'author'}, + title: 'Reference to author', + }, + { + type: 'reference', + name: 'bookReference', + to: {type: 'book'}, + title: 'Reference to book', + }, + { + type: 'object', + name: 'objectWithNestedArray', + title: 'An object with nested array', + fields: [ + { + name: 'title', + type: 'string', + }, + { + name: 'array', + type: 'array', + of: [ + { + type: 'object', + fields: [ + {type: 'string', name: 'title'}, + {type: 'reference', name: 'author', to: [{type: 'author'}]}, + ], + }, + ], + }, + ], + }, + {type: 'author', title: 'Embedded author'}, + {type: 'code', title: 'Code'}, + { + type: 'object', + title: 'Other test object', + name: 'otherTestObject', + fields: [ + {name: 'field1', type: 'string'}, + { + name: 'field3', + type: 'array', + of: [ + { + type: 'object', + fields: [ + {name: 'aString', type: 'string'}, + {name: 'aNumber', type: 'number'}, + ], + }, + ], + }, + ], + }, + ], + }, + { + name: 'readOnlyWithDefaults', + title: 'Read only with defaults', + description: 'This is read only', + type: 'array', + readOnly: true, + of: [ + {type: 'image', title: 'Image'}, + { + type: 'reference', + name: 'authorReference', + to: {type: 'author'}, + title: 'Reference to author', + }, + { + type: 'reference', + name: 'bookReference', + to: {type: 'book'}, + title: 'Reference to book', + }, + {type: 'author', title: 'Embedded author'}, + {type: 'code', title: 'Code'}, + { + type: 'object', + title: 'Test object', + name: 'testObject', + fields: [{name: 'field1', type: 'string'}], + }, + { + type: 'object', + title: 'Other test object', + name: 'otherTestObject', + fields: [ + {name: 'field1', type: 'string'}, + { + name: 'field3', + type: 'array', + of: [ + { + type: 'object', + fields: [ + {name: 'aString', type: 'string'}, + {name: 'aNumber', type: 'number'}, + ], + }, + ], + }, + ], + }, + ], + }, + { + name: 'minimal', + title: 'Reset all options', + type: 'array', + of: [ + { + type: 'block', + styles: [], + lists: [], + marks: { + decorators: [], + annotations: [], + }, + }, + ], + }, + { + name: 'reproCH9436', + title: 'Images', + type: 'array', + description: 'Repro case for https://app.clubhouse.io/sanity-io/story/9436/', + of: [ + {type: 'block'}, + { + name: 'imageWithPortableTextCaption', + type: 'image', + fields: [ + { + name: 'caption', + title: 'Caption', + type: 'array', + description: + 'The amount of toolbar buttons here should not affect the width of the PTE input or the width of the dialog which contains it', + options: {isHighlighted: true}, + of: [ + { + title: 'Block', + type: 'block', + marks: { + decorators: [ + // the number of decorators here will currently force the width of the PTE input + // to be wider than the dialog, which again makes the dialog content overflow + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + {title: 'Underline', value: 'underline'}, + {title: 'Strikethrough', value: 'strikethrough'}, + {title: 'Superscript', value: 'superscript'}, + {title: 'Subscript', value: 'subscript'}, + {title: 'Left', value: 'alignleft'}, + {title: 'Center', value: 'aligncenter'}, + {title: 'Right', value: 'alignright'}, + {title: 'Justify', value: 'alignjustify'}, + ], + }, + }, + ], + }, + ], + }, + ], + }, + { + name: 'customized', + title: 'Customized with block types', + type: 'array', + of: [ + {type: 'author', title: 'Author'}, + { + type: 'block', + styles: [ + {title: 'Normal', value: 'normal'}, + {title: 'H1', value: 'h1'}, + {title: 'H2', value: 'h2'}, + {title: 'Quote', value: 'blockquote'}, + ], + lists: [ + {title: 'Bullet', value: 'bullet'}, + {title: 'Numbered', value: 'number'}, + ], + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + {title: 'Decorator with custom icon', value: 'color', icon: DropIcon}, + ], + annotations: [ + { + name: 'Author', + title: 'Author', + type: 'reference', + to: {type: 'author'}, + }, + { + title: 'Annotation with custom icon', + name: 'test', + type: 'object', + icon: DropIcon, + fields: [ + { + name: 'testString', + type: 'string', + }, + ], + }, + ], + }, + of: [ + { + type: 'image', + title: 'Image', + fields: [ + {title: 'Caption', name: 'caption', type: 'string', options: {isHighlighted: true}}, + { + title: 'Authors', + name: 'authors', + type: 'array', + options: {isHighlighted: true}, + of: [{type: 'author', title: 'Author'}], + }, + ], + }, + ], + }, + ], + }, + { + name: 'withGeopoint', + title: 'With geopoints', + type: 'array', + of: [{type: 'block'}, {type: 'geopoint'}], + }, + { + name: 'deep', + title: 'Blocks deep down', + type: 'object', + fields: [ + {name: 'something', title: 'Something', type: 'string'}, + { + name: 'blocks', + type: 'array', + title: 'Blocks', + of: [ + {type: 'image', title: 'Image'}, + {type: 'author', title: 'Author'}, + { + type: 'block', + styles: [ + {title: 'Normal', value: 'normal'}, + {title: 'H1', value: 'h1'}, + {title: 'H2', value: 'h2'}, + {title: 'Quote', value: 'blockquote'}, + ], + lists: [ + {title: 'Bullet', value: 'bullet'}, + {title: 'Numbered', value: 'number'}, + ], + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + ], + annotations: [ + {name: 'Author', title: 'Author', type: 'reference', to: {type: 'author'}}, + ], + }, + }, + ], + }, + ], + }, + { + title: 'Array of articles', + name: 'arrayOfArticles', + type: 'array', + of: [ + { + type: 'blocksTest', + }, + ], + }, + { + title: 'Block in block', + name: 'blockInBlock', + type: 'array', + of: [ + { + type: 'block', + of: [ + { + name: 'footnote', + title: 'Footnote', + type: 'object', + fields: [ + { + title: 'Footnote', + name: 'footnote', + type: 'array', + of: [ + { + type: 'block', + lists: [], + styles: [], + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + ], + annotations: [], + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + name: 'recursive', + type: 'object', + fields: [ + { + name: 'blocks', + type: 'array', + title: 'Blocks', + of: [ + { + type: 'block', + styles: [], + lists: [], + marks: { + decorators: [], + annotations: [], + }, + }, + { + type: 'blocksTest', + title: 'Blocks test!', + }, + ], + }, + ], + }, + { + name: 'blockList', + title: 'Array of blocks', + type: 'array', + of: [ + { + name: 'blockListEntry', + type: 'object', + fields: [ + { + name: 'title', + title: 'Title', + type: 'string', + }, + { + name: 'blocks', + type: 'array', + of: [{type: 'block'}], + }, + ], + }, + ], + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/fileAsset.ts b/packages/@sanity/schema/test/extractSchema/fixtures/fileAsset.ts new file mode 100644 index 00000000000..43e8c9aaa6a --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/fileAsset.ts @@ -0,0 +1,103 @@ +export default { + name: 'sanity.fileAsset', + title: 'File', + type: 'document', + fieldsets: [ + { + name: 'system', + title: 'System fields', + description: 'These fields are managed by the system and not editable', + }, + ], + fields: [ + { + name: 'originalFilename', + type: 'string', + title: 'Original file name', + readOnly: true, + }, + { + name: 'label', + type: 'string', + title: 'Label', + }, + { + name: 'title', + type: 'string', + title: 'Title', + }, + { + name: 'description', + type: 'string', + title: 'Description', + }, + { + name: 'altText', + type: 'string', + title: 'Alternative text', + }, + { + name: 'sha1hash', + type: 'string', + title: 'SHA1 hash', + readOnly: true, + fieldset: 'system', + }, + { + name: 'extension', + type: 'string', + title: 'File extension', + readOnly: true, + fieldset: 'system', + }, + { + name: 'mimeType', + type: 'string', + title: 'Mime type', + readOnly: true, + fieldset: 'system', + }, + { + name: 'size', + type: 'number', + title: 'File size in bytes', + readOnly: true, + fieldset: 'system', + }, + { + name: 'assetId', + type: 'string', + title: 'Asset ID', + readOnly: true, + fieldset: 'system', + }, + { + name: 'uploadId', + type: 'string', + readOnly: true, + hidden: true, + fieldset: 'system', + }, + { + name: 'path', + type: 'string', + title: 'Path', + readOnly: true, + fieldset: 'system', + }, + { + name: 'url', + type: 'string', + title: 'Url', + readOnly: true, + fieldset: 'system', + }, + { + name: 'source', + type: 'sanity.assetSourceData', + title: 'Source', + readOnly: true, + fieldset: 'system', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/geopoint.ts b/packages/@sanity/schema/test/extractSchema/fixtures/geopoint.ts new file mode 100644 index 00000000000..c4a16105b12 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/geopoint.ts @@ -0,0 +1,22 @@ +export default { + title: 'Geographical Point', + name: 'geopoint', + type: 'object', + fields: [ + { + name: 'lat', + type: 'number', + title: 'Latitude', + }, + { + name: 'lng', + type: 'number', + title: 'Longitude', + }, + { + name: 'alt', + type: 'number', + title: 'Altitude', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imageAsset.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imageAsset.ts new file mode 100644 index 00000000000..7becb98dece --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imageAsset.ts @@ -0,0 +1,108 @@ +export default { + name: 'sanity.imageAsset', + title: 'Image', + type: 'document', + fieldsets: [ + { + name: 'system', + title: 'System fields', + description: 'These fields are managed by the system and not editable', + }, + ], + fields: [ + { + name: 'originalFilename', + type: 'string', + title: 'Original file name', + readOnly: true, + }, + { + name: 'label', + type: 'string', + title: 'Label', + }, + { + name: 'title', + type: 'string', + title: 'Title', + }, + { + name: 'description', + type: 'string', + title: 'Description', + }, + { + name: 'altText', + type: 'string', + title: 'Alternative text', + }, + { + name: 'sha1hash', + type: 'string', + title: 'SHA1 hash', + readOnly: true, + fieldset: 'system', + }, + { + name: 'extension', + type: 'string', + readOnly: true, + title: 'File extension', + fieldset: 'system', + }, + { + name: 'mimeType', + type: 'string', + readOnly: true, + title: 'Mime type', + fieldset: 'system', + }, + { + name: 'size', + type: 'number', + title: 'File size in bytes', + readOnly: true, + fieldset: 'system', + }, + { + name: 'assetId', + type: 'string', + title: 'Asset ID', + readOnly: true, + fieldset: 'system', + }, + { + name: 'uploadId', + type: 'string', + readOnly: true, + hidden: true, + fieldset: 'system', + }, + { + name: 'path', + type: 'string', + title: 'Path', + readOnly: true, + fieldset: 'system', + }, + { + name: 'url', + type: 'string', + title: 'Url', + readOnly: true, + fieldset: 'system', + }, + { + name: 'metadata', + type: 'sanity.imageMetadata', + title: 'Metadata', + }, + { + name: 'source', + type: 'sanity.assetSourceData', + title: 'Source', + readOnly: true, + fieldset: 'system', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imageCrop.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imageCrop.ts new file mode 100644 index 00000000000..7389370eac0 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imageCrop.ts @@ -0,0 +1,23 @@ +export default { + name: 'sanity.imageCrop', + title: 'Image crop', + type: 'object', + fields: [ + { + name: 'top', + type: 'number', + }, + { + name: 'bottom', + type: 'number', + }, + { + name: 'left', + type: 'number', + }, + { + name: 'right', + type: 'number', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imageDimensions.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imageDimensions.ts new file mode 100644 index 00000000000..0af193fdb03 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imageDimensions.ts @@ -0,0 +1,10 @@ +export default { + name: 'sanity.imageDimensions', + type: 'object', + title: 'Image dimensions', + fields: [ + {name: 'height', type: 'number', title: 'Height', readOnly: true}, + {name: 'width', type: 'number', title: 'Width', readOnly: true}, + {name: 'aspectRatio', type: 'number', title: 'Aspect ratio', readOnly: true}, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imageHotspot.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imageHotspot.ts new file mode 100644 index 00000000000..bc4de0e43e8 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imageHotspot.ts @@ -0,0 +1,23 @@ +export default { + name: 'sanity.imageHotspot', + title: 'Image hotspot', + type: 'object', + fields: [ + { + name: 'x', + type: 'number', + }, + { + name: 'y', + type: 'number', + }, + { + name: 'height', + type: 'number', + }, + { + name: 'width', + type: 'number', + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imageMetadata.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imageMetadata.ts new file mode 100644 index 00000000000..af5770b9def --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imageMetadata.ts @@ -0,0 +1,56 @@ +export default { + name: 'sanity.imageMetadata', + title: 'Image metadata', + type: 'object', + fieldsets: [ + { + name: 'extra', + title: 'Extra metadata…', + options: { + collapsable: true, + }, + }, + ], + fields: [ + { + name: 'location', + type: 'geopoint', + }, + { + name: 'dimensions', + title: 'Dimensions', + type: 'sanity.imageDimensions', + fieldset: 'extra', + }, + { + name: 'palette', + type: 'sanity.imagePalette', + title: 'Palette', + fieldset: 'extra', + }, + { + name: 'lqip', + title: 'LQIP (Low-Quality Image Placeholder)', + type: 'string', + readOnly: true, + }, + { + name: 'blurHash', + title: 'BlurHash', + type: 'string', + readOnly: true, + }, + { + name: 'hasAlpha', + title: 'Has alpha channel', + type: 'boolean', + readOnly: true, + }, + { + name: 'isOpaque', + title: 'Is opaque', + type: 'boolean', + readOnly: true, + }, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imagePalette.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imagePalette.ts new file mode 100644 index 00000000000..5c8e500453a --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imagePalette.ts @@ -0,0 +1,14 @@ +export default { + name: 'sanity.imagePalette', + title: 'Image palette', + type: 'object', + fields: [ + {name: 'darkMuted', type: 'sanity.imagePaletteSwatch', title: 'Dark Muted'}, + {name: 'lightVibrant', type: 'sanity.imagePaletteSwatch', title: 'Light Vibrant'}, + {name: 'darkVibrant', type: 'sanity.imagePaletteSwatch', title: 'Dark Vibrant'}, + {name: 'vibrant', type: 'sanity.imagePaletteSwatch', title: 'Vibrant'}, + {name: 'dominant', type: 'sanity.imagePaletteSwatch', title: 'Dominant'}, + {name: 'lightMuted', type: 'sanity.imagePaletteSwatch', title: 'Light Muted'}, + {name: 'muted', type: 'sanity.imagePaletteSwatch', title: 'Muted'}, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/imagePaletteSwatch.ts b/packages/@sanity/schema/test/extractSchema/fixtures/imagePaletteSwatch.ts new file mode 100644 index 00000000000..1d43cf9e0e2 --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/imagePaletteSwatch.ts @@ -0,0 +1,11 @@ +export default { + name: 'sanity.imagePaletteSwatch', + title: 'Image palette swatch', + type: 'object', + fields: [ + {name: 'background', type: 'string', title: 'Background', readOnly: true}, + {name: 'foreground', type: 'string', title: 'Foreground', readOnly: true}, + {name: 'population', type: 'number', title: 'Population', readOnly: true}, + {name: 'title', type: 'string', title: 'String', readOnly: true}, + ], +} diff --git a/packages/@sanity/schema/test/extractSchema/fixtures/slug.ts b/packages/@sanity/schema/test/extractSchema/fixtures/slug.ts new file mode 100644 index 00000000000..665665cbb0f --- /dev/null +++ b/packages/@sanity/schema/test/extractSchema/fixtures/slug.ts @@ -0,0 +1,20 @@ +export default { + title: 'Slug', + name: 'slug', + type: 'object', + fields: [ + { + name: 'current', + title: 'Current slug', + type: 'string', + }, + { + // The source field is deprecated/unused, but leaving it included and hidden + // to prevent rendering "Unknown field" warnings on legacy data + name: 'source', + title: 'Source field', + type: 'string', + hidden: true, + }, + ], +} diff --git a/packages/sanity/package.config.ts b/packages/sanity/package.config.ts index 79d59437eaa..9fd86115ca5 100644 --- a/packages/sanity/package.config.ts +++ b/packages/sanity/package.config.ts @@ -39,6 +39,11 @@ export default defineConfig({ require: './lib/_internal/cli/threads/validateSchema.js', default: './lib/_internal/cli/threads/validateSchema.js', }, + './_internal/cli/threads/extractSchema': { + source: './src/_internal/cli/threads/extractSchema.ts', + require: './lib/_internal/cli/threads/extractSchema.js', + default: './lib/_internal/cli/threads/extractSchema.js', + }, }), extract: { diff --git a/packages/sanity/src/_internal/cli/actions/schema/extractAction.ts b/packages/sanity/src/_internal/cli/actions/schema/extractAction.ts new file mode 100644 index 00000000000..3f9dcf83b3c --- /dev/null +++ b/packages/sanity/src/_internal/cli/actions/schema/extractAction.ts @@ -0,0 +1,74 @@ +import {writeFile} from 'node:fs/promises' +import {dirname, join} from 'node:path' +import {Worker} from 'node:worker_threads' + +import {type CliCommandArguments, type CliCommandContext} from '@sanity/cli' +import readPkgUp from 'read-pkg-up' + +import { + type ExtractSchemaWorkerData, + type ExtractSchemaWorkerResult, +} from '../../threads/extractSchema' + +interface ExtractFlags { + workspace?: string + path?: string + 'enforce-required-fields'?: boolean + format?: 'groq-type-nodes' | string +} + +export type SchemaValidationFormatter = (result: ExtractSchemaWorkerResult) => string + +export default async function extractAction( + args: CliCommandArguments, + {workDir, output}: CliCommandContext, +): Promise { + const flags = args.extOptions + const formatFlat = flags.format || 'groq-type-nodes' + + const rootPkgPath = readPkgUp.sync({cwd: __dirname})?.path + if (!rootPkgPath) { + throw new Error('Could not find root directory for `sanity` package') + } + + const workerPath = join( + dirname(rootPkgPath), + 'lib', + '_internal', + 'cli', + 'threads', + 'extractSchema.js', + ) + + const spinner = output + .spinner({}) + .start( + flags['enforce-required-fields'] + ? 'Extracting schema, with enforced required fields' + : 'Extracting schema', + ) + + const worker = new Worker(workerPath, { + workerData: { + workDir, + workspaceName: flags.workspace, + enforceRequiredFields: flags['enforce-required-fields'], + format: formatFlat, + } satisfies ExtractSchemaWorkerData, + // eslint-disable-next-line no-process-env + env: process.env, + }) + + const {schema} = await new Promise((resolve, reject) => { + worker.addListener('message', resolve) + worker.addListener('error', reject) + }) + + const path = flags.path || join(process.cwd(), 'schema.json') + + spinner.text = `Writing schema to ${path}` + + await writeFile(path, JSON.stringify(schema, null, 2)) + + spinner.succeed('Extracted schema') +} diff --git a/packages/sanity/src/_internal/cli/commands/index.ts b/packages/sanity/src/_internal/cli/commands/index.ts index 37463949ef9..e27daee4cce 100644 --- a/packages/sanity/src/_internal/cli/commands/index.ts +++ b/packages/sanity/src/_internal/cli/commands/index.ts @@ -46,6 +46,7 @@ import listMigrationsCommand from './migration/listMigrationsCommand' import migrationGroup from './migration/migrationGroup' import runMigrationCommand from './migration/runMigrationCommand' import previewCommand from './preview/previewCommand' +import extractSchemaCommand from './schema/extractSchemaCommand' import schemaGroup from './schema/schemaGroup' import validateSchemaCommand from './schema/validateSchemaCommand' import startCommand from './start/startCommand' @@ -105,6 +106,7 @@ const commands: (CliCommandDefinition | CliCommandGroupDefinition)[] = [ startCommand, schemaGroup, validateSchemaCommand, + extractSchemaCommand, previewCommand, uninstallCommand, execCommand, diff --git a/packages/sanity/src/_internal/cli/commands/schema/extractSchemaCommand.ts b/packages/sanity/src/_internal/cli/commands/schema/extractSchemaCommand.ts new file mode 100644 index 00000000000..98e1cd2117e --- /dev/null +++ b/packages/sanity/src/_internal/cli/commands/schema/extractSchemaCommand.ts @@ -0,0 +1,33 @@ +import {type CliCommandDefinition} from '@sanity/cli' + +const description = 'Extracts a JSON representation of a Sanity schema within a Studio context.' + +const helpText = ` +**Note**: This command is experimental and subject to change. + +Options + --workspace The name of the workspace to generate a schema for + --path Optional path to specify destination of the schema file + --enforce-required-fields Makes the schema generated treat fields marked as required as non-optional. Defaults to false. + --format=[groq-type-nodes] Format the schema as GROQ type nodes. Only available format at the moment. + +Examples + # Extracts schema types in a Sanity project with more than one workspace + sanity schema extract --workspace default +` + +const extractSchemaCommand: CliCommandDefinition = { + name: 'extract', + group: 'schema', + signature: '', + description, + helpText, + hideFromHelp: true, + action: async (args, context) => { + const mod = await import('../../actions/schema/extractAction') + + return mod.default(args, context) + }, +} satisfies CliCommandDefinition + +export default extractSchemaCommand diff --git a/packages/sanity/src/_internal/cli/threads/extractSchema.ts b/packages/sanity/src/_internal/cli/threads/extractSchema.ts new file mode 100644 index 00000000000..3b66e94c85c --- /dev/null +++ b/packages/sanity/src/_internal/cli/threads/extractSchema.ts @@ -0,0 +1,76 @@ +import {isMainThread, parentPort, workerData as _workerData} from 'node:worker_threads' + +import {extractSchema} from '@sanity/schema/_internal' +import {type Workspace} from 'sanity' + +import {getStudioWorkspaces} from '../util/getStudioWorkspaces' +import {mockBrowserEnvironment} from '../util/mockBrowserEnvironment' + +export interface ExtractSchemaWorkerData { + workDir: string + workspaceName?: string + enforceRequiredFields?: boolean + format: 'groq-type-nodes' | string +} + +export interface ExtractSchemaWorkerResult { + schema: ReturnType +} + +if (isMainThread || !parentPort) { + throw new Error('This module must be run as a worker thread') +} + +const opts = _workerData as ExtractSchemaWorkerData +const cleanup = mockBrowserEnvironment(opts.workDir) + +async function main() { + try { + if (opts.format !== 'groq-type-nodes') { + throw new Error(`Unsupported format: "${opts.format}"`) + } + + const workspaces = await getStudioWorkspaces({basePath: opts.workDir}) + + const workspace = getWorkspace({workspaces, workspaceName: opts.workspaceName}) + + const schema = extractSchema(workspace.schema, { + enforceRequiredFields: opts.enforceRequiredFields, + }) + + parentPort?.postMessage({ + schema, + } satisfies ExtractSchemaWorkerResult) + } finally { + cleanup() + } +} + +main() + +function getWorkspace({ + workspaces, + workspaceName, +}: { + workspaces: Workspace[] + workspaceName?: string +}): Workspace { + if (workspaces.length === 0) { + throw new Error('No studio configuration found') + } + + if (workspaces.length === 1) { + return workspaces[0] + } + + if (workspaceName === undefined) { + throw new Error( + `Multiple workspaces found. Please specify which workspace to use with '--workspace'.`, + ) + } + const workspace = workspaces.find((w) => w.name === workspaceName) + if (!workspace) { + throw new Error(`Could not find workspace "${workspaceName}"`) + } + return workspace +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc30cad2b89..0e7e92dcd86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1175,6 +1175,9 @@ importers: arrify: specifier: ^1.0.1 version: 1.0.1 + groq-js: + specifier: ^1.5.0-canary.1 + version: 1.5.0-canary.1 humanize-list: specifier: ^1.0.1 version: 1.0.1 @@ -1191,6 +1194,9 @@ importers: '@jest/globals': specifier: ^29.7.0 version: 29.7.0 + '@sanity/icons': + specifier: ^2.8.0 + version: 2.11.2(react@18.2.0) rimraf: specifier: ^3.0.2 version: 3.0.2 @@ -11493,6 +11499,15 @@ packages: resolution: {integrity: sha512-h2vFXJ/U5VX9bzlqqZLgx/XS0ibNJza4eMxUjZTqkpe3gKafFIJSkMP0RS/XEQR18gJMjXAJNziWdY7JYYfl7w==} engines: {node: '>= 14'} + /groq-js@1.5.0-canary.1: + resolution: {integrity: sha512-p3eqvL0mYS9bzCgpQT4IGs32MCDyyWOU7ilpr7UR4k7AedXYNtd/ha9UpszP6i2VrAXCfmJ63zvvTut6JCKgSQ==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: false + /groq@3.32.0: resolution: {integrity: sha512-yK3XQapMcXFP4QyVAF/ceRAwfkKbJJFaDt1y4GBxhJ6tDp/9I8bPkDPv/h016CvegOsaUDy68VKGF/LpGX7b+g==} engines: {node: '>=18'}