Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,768 changes: 1,916 additions & 852 deletions schema/dereferenced/repos.json

Large diffs are not rendered by default.

842 changes: 490 additions & 352 deletions schema/dereferenced/settings.json

Large diffs are not rendered by default.

2,768 changes: 1,916 additions & 852 deletions schema/dereferenced/suborgs.json

Large diffs are not rendered by default.

29 changes: 22 additions & 7 deletions schema/repos.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
"validator": {
"$ref": "#/$defs/ValidatorSettings"
},
"rulesets": {
"description": "Repository rulesets applied to this repository",
"type": "array",
"items": {
"$ref": "#/$defs/RulesetSettings"
}
},
"environments": {
"description": "Deployment environments",
"type": "array",
Expand Down Expand Up @@ -75,7 +82,7 @@
"description": "Repository settings",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -162,7 +169,7 @@
"description": "A collaborator entry giving a specific user access to a repository.",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -190,7 +197,7 @@
},
"TeamSettings": {
"description": "A team entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
},
"MilestoneSettings": {
"description": "A milestone entry",
Expand All @@ -217,13 +224,21 @@
"type": "string"
},
"protection": {
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
"anyOf": [
{
"enum": [null, {}, [], false],
"description": "An empty value deletes the existing branch protection"
},
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
}
]
}
Comment thread
tdabasinskas marked this conversation as resolved.
}
},
"AutolinkSettings": {
"description": "An autolink reference entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
},
"ValidatorSettings": {
"description": "Repository name validation",
Expand All @@ -235,8 +250,8 @@
}
},
"RulesetSettings": {
"description": "A ruleset entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1rulesets/post/requestBody/content/application~1json/schema"
"description": "A repository ruleset entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1rulesets/post/requestBody/content/application~1json/schema"
},
"EnvironmentsSettings": {
"description": "A deployment environment configuration entry",
Expand Down
20 changes: 14 additions & 6 deletions schema/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"description": "Repository settings",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -169,7 +169,7 @@
"description": "A collaborator entry giving a specific user access to a repository.",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -197,7 +197,7 @@
},
"TeamSettings": {
"description": "A team entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
},
"MilestoneSettings": {
"description": "A milestone entry",
Expand All @@ -224,13 +224,21 @@
"type": "string"
},
"protection": {
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
"anyOf": [
{
"enum": [null, {}, [], false],
"description": "An empty value deletes the existing branch protection"
},
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
}
]
}
Comment thread
tdabasinskas marked this conversation as resolved.
}
},
"AutolinkSettings": {
"description": "An autolink reference entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
},
"ValidatorSettings": {
"description": "Repository name validation",
Expand All @@ -243,7 +251,7 @@
},
"RulesetSettings": {
"description": "A ruleset entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1rulesets/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1rulesets/post/requestBody/content/application~1json/schema"
},
"EnvironmentsSettings": {
"description": "A deployment environment configuration entry",
Expand Down
29 changes: 22 additions & 7 deletions schema/suborgs.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@
"validator": {
"$ref": "#/$defs/ValidatorSettings"
},
"rulesets": {
"description": "Repository rulesets applied to every repository in the suborg",
"type": "array",
"items": {
"$ref": "#/$defs/RulesetSettings"
}
},
"environments": {
"description": "Deployment environments",
"type": "array",
Expand Down Expand Up @@ -109,7 +116,7 @@
"description": "Repository settings. Use force_create to create the repository if it does not exist, and template to specify a template repository to use when creating it.",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}/patch/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -196,7 +203,7 @@
"description": "A collaborator entry giving a specific user access to a repository.",
"allOf": [
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1collaborators~1{username}/put/requestBody/content/application~1json/schema"
},
{
"type": "object",
Expand Down Expand Up @@ -224,7 +231,7 @@
},
"TeamSettings": {
"description": "A team entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1teams/post/requestBody/content/application~1json/schema"
},
"MilestoneSettings": {
"description": "A milestone entry",
Expand All @@ -251,13 +258,21 @@
"type": "string"
},
"protection": {
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
"anyOf": [
{
"enum": [null, {}, [], false],
"description": "An empty value deletes the existing branch protection"
},
{
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1branches~1{branch}~1protection/put/requestBody/content/application~1json/schema"
}
]
}
Comment thread
tdabasinskas marked this conversation as resolved.
}
},
"AutolinkSettings": {
"description": "An autolink reference entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1autolinks/post/requestBody/content/application~1json/schema"
},
"ValidatorSettings": {
"description": "Repository name validation",
Expand All @@ -269,8 +284,8 @@
}
},
"RulesetSettings": {
"description": "A ruleset entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json#/paths/~1orgs~1{org}~1rulesets/post/requestBody/content/application~1json/schema"
"description": "A repository ruleset entry",
"$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.2026-03-10.json#/paths/~1repos~1{owner}~1{repo}~1rulesets/post/requestBody/content/application~1json/schema"
},
"EnvironmentsSettings": {
"description": "A deployment environment configuration entry",
Expand Down
8 changes: 5 additions & 3 deletions script/build-schema
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const path = require('node:path')

const schemas = [
{ src: 'schema/settings.json', dest: 'schema/dereferenced/settings.json' },
{ src: 'schema/suborgs.json', dest: 'schema/dereferenced/suborgs.json' },
{ src: 'schema/repos.json', dest: 'schema/dereferenced/repos.json' }
{ src: 'schema/suborgs.json', dest: 'schema/dereferenced/suborgs.json' },
{ src: 'schema/repos.json', dest: 'schema/dereferenced/repos.json' }
]

;(async () => {
Expand All @@ -32,9 +32,11 @@ const schemas = [
console.log(`Fetching ${specUrl} ...`)
const resolvedSpec = await $RefParser.dereference(specUrl)

// Serve the pre-dereferenced spec for the exact URL the schema files pin,
// so the resolver can never match a different document.
const githubApiResolver = {
order: 1,
canRead: /\/descriptions\/api\.github\.com\/api\.github\.com/,
canRead: file => file.url === specUrl,
read: () => resolvedSpec
}
Comment thread
tdabasinskas marked this conversation as resolved.
Comment thread
tdabasinskas marked this conversation as resolved.

Expand Down
76 changes: 76 additions & 0 deletions test/unit/schema.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-undef */

const fs = require('fs')
const path = require('path')

// The generated schemas declare JSON Schema draft 2020-12, where OpenAPI 3.0's
// `nullable` keyword does not exist. The build consumes GitHub's OpenAPI 3.1
// description, which models null-ability as `type: [X, 'null']` unions instead,
// so no `nullable` keyword may survive in the output.
describe('dereferenced schemas', () => {
const files = ['settings.json', 'suborgs.json', 'repos.json']

files.forEach(file => {
describe(file, () => {
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../../schema/dereferenced', file), 'utf8'))

it('contains no OpenAPI `nullable` keywords', () => {
const found = []
const walk = (node, at) => {
if (Array.isArray(node)) {
node.forEach((v, i) => walk(v, `${at}/${i}`))
} else if (node && typeof node === 'object') {
if ('nullable' in node) {
found.push(at)
}
Object.entries(node).forEach(([k, v]) => walk(v, `${at}/${k}`))
}
}
walk(schema, '')
expect(found).toEqual([])
})
})
})

it('allows null for required-but-nullable branch protection fields', () => {
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../../schema/dereferenced/settings.json'), 'utf8'))
const protection = schema.properties.branches.items.properties.protection
const protectionObject = protection.anyOf.find(variant => variant.type === 'object')
expect(protectionObject.properties.required_status_checks.type).toContain('null')
expect(protectionObject.properties.enforce_admins.type).toContain('null')
expect(protectionObject.properties.restrictions.type).toContain('null')
})

// Suborg and repo override files feed the rulesets plugin at repo scope
// (childPluginsList in lib/settings.js), so their schemas validate rulesets
// against the repo-level API shape, not the org-level one.
it('validates per-repo rulesets in the suborg and repo schemas', () => {
;['suborgs.json', 'repos.json'].forEach(file => {
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../../schema/dereferenced', file), 'utf8'))
const ruleset = schema.properties.rulesets.items
// `required` is an unordered set in JSON Schema
expect(ruleset.required).toHaveLength(2)
expect(ruleset.required).toEqual(expect.arrayContaining(['name', 'enforcement']))
// org-only condition targeting must not leak into the repo-level shape
expect(JSON.stringify(ruleset.properties.conditions)).not.toContain('repository_name')
const actorTypes = ruleset.properties.bypass_actors.items.properties.actor_type.enum
expect(actorTypes).toContain('User')
})
})

// An empty `protection` value is safe-settings' own delete-branch-protection
// semantic (see isEmpty in lib/plugins/branches.js, and the plugin tests for
// null/{}/[]/false); the GitHub API's PUT body it is validated against does
// not model that, so the schema allows the empty values explicitly.
it('allows the empty protection values that delete branch protection', () => {
files.forEach(file => {
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../../schema/dereferenced', file), 'utf8'))
const protection = schema.properties.branches.items.properties.protection
// enum ordering is not semantically meaningful, so assert membership
const emptyVariant = protection.anyOf.find(variant => Array.isArray(variant.enum))
expect(emptyVariant).toBeDefined()
expect(emptyVariant.enum).toHaveLength(4)
expect(emptyVariant.enum).toEqual(expect.arrayContaining([null, {}, [], false]))
})
})
})