Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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,762 changes: 1,908 additions & 854 deletions schema/dereferenced/repos.json

Large diffs are not rendered by default.

832 changes: 480 additions & 352 deletions schema/dereferenced/settings.json

Large diffs are not rendered by default.

2,762 changes: 1,908 additions & 854 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": [
{
"type": "null",
"description": "Set to `null` to delete 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": [
{
"type": "null",
"description": "Set to `null` to delete 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": [
{
"type": "null",
"description": "Set to `null` to delete 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
69 changes: 69 additions & 0 deletions test/unit/schema.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* 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)))
Comment thread
tdabasinskas marked this conversation as resolved.
Outdated

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')))
Comment thread
tdabasinskas marked this conversation as resolved.
Outdated
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)))
Comment thread
tdabasinskas marked this conversation as resolved.
Outdated
const ruleset = schema.properties.rulesets.items
expect(ruleset.required).toEqual(['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')
})
})
Comment thread
tdabasinskas marked this conversation as resolved.

// `protection: null` is safe-settings' own delete-branch-protection semantic
// (see isEmpty in lib/plugins/branches.js); the GitHub API's PUT body it is
// validated against does not model that, so the schema allows null explicitly.
it('allows `protection: null` in every schema that has branches', () => {
files.forEach(file => {
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../../schema/dereferenced', file)))
Comment thread
tdabasinskas marked this conversation as resolved.
Outdated
const protection = schema.properties.branches.items.properties.protection
expect(protection.anyOf).toEqual(expect.arrayContaining([expect.objectContaining({ type: 'null' })]))
})
})
Comment thread
tdabasinskas marked this conversation as resolved.
})