Repository: sanity-io/sanity (TypeScript)

Issue:
Running sanity schema extract --enforce-required-fields doesn't take validation into account.
**Describe the bug**

Running `sanity schema extract --enforce-required-fields` with `validation: rule => rule.required()` creates a schema.json file where the field still has `optional: true`.

**To Reproduce**

1. Run `npm create sanity@latest`
2. Change add required validation to a field
3. Run `sanity schema extract --enforce-required-fields`
4. See that generated schema file has the field as `optional: true`

Se example repo here: [https://github.com/heggemsnes/sanity-typegen-repro](https://github.com/heggemsnes/sanity-typegen-repro)

**Expected behavior**

If the field is required the type should not be optional.

**Which versions of Sanity are you using?**
3.36.2

**What operating system are you using?**
MacOS 14.3.1

**Which versions of Node.js / npm are you running?**

v18.17.1

**Additional context**

Example document:

```typescript
import {defineField, defineType} from 'sanity'
 
export default defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    }),
  ],
})
 
```

Resulting (partial) schema after export:

```json
{
    "name": "post",
    "type": "document",
    "attributes": {
      "_id": {
        "type": "objectAttribute",
        "value": {
          "type": "string"
        }
      },
      "_type": {
        "type": "objectAttribute",
        "value": {
          "type": "string",
          "value": "post"
        }
      },
      "_createdAt": {
        "type": "objectAttribute",
        "value": {
          "type": "string"
        }
      },
      "_updatedAt": {
        "type": "objectAttribute",
        "value": {
          "type": "string"
        }
      },
      "_rev": {
        "type": "objectAttribute",
        "value": {
          "type": "string"
        }
      },
      "title": {
        "type": "objectAttribute",
        "value": {
          "type": "string"
        },
        "optional": true
      },
      "slug": {
        "type": "objectAttribute",
        "value": {
          "type": "inline",
          "name": "slug"
        },
        "optional": true
      }
    }
  }
```


Discussion:
This thread has been automatically locked because it has not had recent activity.  Please open a new issue for related bugs and link to relevant comments in this thread.



Patch:
diff --git a/packages/@sanity/schema/src/sanity/extractSchema.ts b/packages/@sanity/schema/src/sanity/extractSchema.ts
index 22376c0f88a..e70f909d2ae 100644
--- a/packages/@sanity/schema/src/sanity/extractSchema.ts
+++ b/packages/@sanity/schema/src/sanity/extractSchema.ts
@@ -315,12 +315,19 @@ function isFieldRequired(field: ObjectField): boolean {
         },
       },
     ) as Rule
+
     if (typeof rule === 'function') {
       rule(proxy)
       if (required) {
         return true
       }
     }
+
+    if (typeof rule === 'object' && rule !== null && '_required' in rule) {
+      if (rule._required === 'required') {
+        return true
+      }
+    }
   }
 
   return false


Test patch:
diff --git a/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts b/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts
index d8df385f807..f28f5e126a2 100644
--- a/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts
+++ b/packages/@sanity/schema/test/extractSchema/extractSchema.test.ts
@@ -387,6 +387,12 @@ describe('Extract schema test', () => {
               type: 'string',
               validation: (Rule) => Rule.required(),
             }),
+            {
+              title: 'Another Title',
+              name: 'anotherTitle',
+              type: 'string',
+              validation: {_required: 'required'},
+            },
           ],
         },
       ],
@@ -400,6 +406,7 @@ describe('Extract schema test', () => {
     assert(book.type === 'document') // this is a workaround for TS, but leave the expect above for clarity in case of failure
     expect(book.attributes.title.optional).toBe(true)
     expect(book.attributes.subtitle.optional).toBe(true)
+    expect(book.attributes.anotherTitle.optional).toBe(true)
   })
 
   test('can extract with enforceRequiredFields', () => {
@@ -422,6 +429,18 @@ describe('Extract schema test', () => {
               type: 'string',
               validation: (Rule) => Rule.required(),
             }),
+            {
+              title: 'Another Title',
+              name: 'anotherTitle',
+              type: 'string',
+              validation: {_required: 'required'},
+            },
+            {
+              title: 'Optional Title',
+              name: 'optionalTitle',
+              type: 'string',
+              validation: {_required: 'optional'},
+            },
           ],
         },
       ],
@@ -435,6 +454,8 @@ describe('Extract schema test', () => {
     assert(book.type === 'document') // this is a workaround for TS, but leave the expect above for clarity in case of failure
     expect(book.attributes.title.optional).toBe(true)
     expect(book.attributes.subtitle.optional).toBe(false)
+    expect(book.attributes.anotherTitle.optional).toBe(false)
+    expect(book.attributes.optionalTitle.optional).toBe(true)
   })
 
   describe('can handle `list` option that is not an array', () => {