1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Restructure GraphQL automated content scripts (#34308)

This commit is contained in:
Rachael Sewell
2023-02-10 16:38:27 -08:00
committed by GitHub
parent b66f4e731a
commit fe8482408b
38 changed files with 62 additions and 62 deletions

39
src/graphql/README.md Normal file
View File

@@ -0,0 +1,39 @@
# GraphQL
## About this directory
* `src/graphql/lib` and `src/graphql/scripts` are human-editable.
* `src/graphql/data/**` are generated by [scripts](../src/graphql/README.md).
## Editable files
* `src/graphql/lib/validator.json`
- JSON schema used in `tests/graphql.js`.
* `src/graphql/lib/non-schema-scalars.json`
- An array of scalar types that live in [`graphql-ruby`](https://github.com/rmosolgo/graphql-ruby/tree/356d9d369e444423bf06cab3dc767ec75fbc6745/lib/graphql/types) only. These are
not part of the core GraphQL spec.
* `src/graphql/lib/types.json`
- High-level GraphQL types and kinds.
## Data files
Generated by `src/graphql/scripts/update-files.js`:
* `src/graphql/data/schema-VERSION.json` (separate files per version)
* `src/graphql/data/previews.json`
* `src/graphql/data/upcoming-changes.json`
* `src/graphql/data/changelog.json`
## Rendering docs
When the server starts, `middleware/graphql.js` accesses the static JSON files, fetches the data for the current version, and adds it to the `context` object. The added properties are:
* `context.graphql.schemaForCurrentVersion`
* `context.graphql.previewsForCurrentVersion`
* `context.graphql.upcomingChangesForCurrentVersion`
* `context.graphql.changelog`
Markdown files in `content/graphql` use Liquid to loop over these context properties. The Liquid calls HTML files in the `includes` directory to do most of the rendering.
Note that Markdown files exist in `content/graphql` for every URL available in our GraphQL
documentation. Writers can add content to the Markdown files alongside the Liquid.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

90
src/graphql/lib/index.js Normal file
View File

@@ -0,0 +1,90 @@
import {
readCompressedJsonFileFallbackLazily,
readCompressedJsonFileFallback,
} from '../../../lib/read-json-file.js'
import { getAutomatedPageMiniTocItems } from '../../../lib/get-mini-toc-items.js'
import languages from '../../../lib/languages.js'
import { allVersions } from '../../../lib/all-versions.js'
/* ADD LANGUAGE KEY */
let previews
let upcomingChanges
const changelog = new Map()
const graphqlSchema = new Map()
const miniTocs = new Map()
Object.keys(languages).forEach((language) => {
miniTocs.set(language, new Map())
})
export function getGraphqlSchema(version, type) {
const graphqlVersion = getGraphqlVersion(version)
if (!graphqlSchema.has(graphqlVersion)) {
graphqlSchema.set(
graphqlVersion,
readCompressedJsonFileFallback(`src/graphql/data/schema-${graphqlVersion}.json`)
)
}
return graphqlSchema.get(graphqlVersion)[type]
}
export function getGraphqlChangelog() {
if (!changelog.has('schema')) {
changelog.set(
'schema',
readCompressedJsonFileFallbackLazily('./src/graphql/data/changelog.json')()
)
}
return changelog.get('schema')
}
export function getGraphqlBreakingChanges(version) {
const graphqlVersion = getGraphqlVersion(version)
if (!upcomingChanges) {
upcomingChanges = readCompressedJsonFileFallbackLazily(
'./src/graphql/data/upcoming-changes.json'
)()
}
return upcomingChanges[graphqlVersion]
}
export function getPreviews(version) {
const graphqlVersion = getGraphqlVersion(version)
if (!previews) {
previews = readCompressedJsonFileFallbackLazily('./src/graphql/data/previews.json')()
}
return previews[graphqlVersion]
}
export async function getMiniToc(context, type, items, depth = 2, markdownHeading = '') {
const { currentLanguage, currentVersion } = context
const graphqlVersion = getGraphqlVersion(currentVersion)
if (!miniTocs.get(currentLanguage).has(graphqlVersion)) {
miniTocs.get(currentLanguage).set(graphqlVersion, new Map())
}
if (!miniTocs.get(currentLanguage).get(graphqlVersion).has(type)) {
const graphqlMiniTocItems = await getAutomatedPageMiniTocItems(
items,
context,
depth,
markdownHeading
)
miniTocs.get(currentLanguage).get(graphqlVersion).set(type, graphqlMiniTocItems)
}
return miniTocs.get(currentLanguage).get(graphqlVersion).get(type)
}
export async function getChangelogMiniTocs(items, context, depth = 2, markdownHeading = '') {
if (!changelog.has('toc')) {
changelog.set('toc', await getAutomatedPageMiniTocItems(items, context, depth, markdownHeading))
}
return changelog.get('toc')
}
function getGraphqlVersion(version) {
if (!(version in allVersions)) {
throw new Error(`Unrecognized version '${version}'. Not found in ${Object.keys(allVersions)}`)
}
return allVersions[version].miscVersionName
}

View File

@@ -0,0 +1,22 @@
[
{
"name": "Boolean",
"description": "Represents `true` or `false` values."
},
{
"name": "Float",
"description": "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)."
},
{
"name": "ID",
"description": "Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"VXNlci0xMA==\"`) or integer (such as `4`) input value will be accepted as an ID."
},
{
"name": "Int",
"description": "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
},
{
"name": "String",
"description": "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text."
}
]

View File

@@ -0,0 +1,34 @@
[
{
"kind":"queries",
"type":"QueryTypeDefinition"
},
{
"kind":"mutations",
"type":"Mutation"
},
{
"kind":"objects",
"type":"ObjectTypeDefinition"
},
{
"kind":"interfaces",
"type":"InterfaceTypeDefinition"
},
{
"kind":"enums",
"type":"EnumTypeDefinition"
},
{
"kind":"unions",
"type":"UnionTypeDefinition"
},
{
"kind":"inputObjects",
"type":"InputObjectTypeDefinition"
},
{
"kind":"scalars",
"type":"ScalarTypeDefinition"
}
]

View File

@@ -0,0 +1,253 @@
// the tests in tests/graphql.js use this schema to ensure the integrity
// of the data in src/graphql/data/*.json
// PREVIEWS
export const previewsValidator = {
properties: {
title: {
type: 'string',
required: true,
},
description: {
type: 'string',
required: true,
},
toggled_by: {
type: 'string',
required: true,
},
toggled_on: {
type: 'array',
required: true,
},
owning_teams: {
type: 'array',
required: true,
},
accept_header: {
type: 'string',
required: true,
},
href: {
type: 'string',
required: true,
},
},
}
// UPCOMING CHANGES
export const upcomingChangesValidator = {
properties: {
location: {
type: 'string',
required: true,
},
description: {
type: 'string',
required: true,
},
reason: {
type: 'string',
required: true,
},
date: {
type: 'string',
required: true,
pattern: /^\d{4}-\d{2}-\d{2}$/,
},
criticality: {
type: 'string',
required: true,
pattern: '(breaking|dangerous)',
},
owner: {
type: 'string',
required: true,
pattern: /^[\S]*$/,
},
},
}
// SCHEMAS
// many GraphQL schema members have these core properties
const coreProps = {
properties: {
name: {
type: 'string',
required: true,
},
type: {
type: 'string',
required: true,
},
kind: {
type: 'string',
required: true,
},
id: {
type: 'string',
required: true,
},
href: {
type: 'string',
required: true,
},
description: {
type: 'string',
required: true,
},
isDeprecated: {
type: 'boolean',
required: false,
},
preview: {
type: 'object',
required: false,
properties: previewsValidator.properties,
},
},
}
// some GraphQL schema members have the core properties plus an 'args' object
const corePropsPlusArgs = dup(coreProps)
corePropsPlusArgs.properties.args = {
type: 'array',
required: false,
properties: coreProps.properties,
}
// the args object can have defaultValue prop
corePropsPlusArgs.properties.args.properties.defaultValue = {
type: 'boolean',
required: false,
}
const corePropsNoType = dup(coreProps)
delete corePropsNoType.properties.type
const corePropsNoDescription = dup(coreProps)
delete corePropsNoDescription.properties.description
// QUERIES
const queries = dup(corePropsPlusArgs)
// MUTATIONS
const mutations = dup(corePropsNoType)
mutations.properties.inputFields = {
type: 'array',
required: true,
properties: corePropsNoDescription.properties,
}
mutations.properties.returnFields = {
type: 'array',
required: true,
properties: coreProps.properties,
}
// OBJECTS
const objects = dup(corePropsNoType)
objects.properties.fields = {
type: 'array',
required: true,
properties: corePropsPlusArgs.properties,
}
objects.properties.implements = {
type: 'array',
required: false,
properties: {
name: {
type: 'string',
required: true,
},
id: {
type: 'string',
required: true,
},
href: {
type: 'string',
required: true,
},
},
}
// INTERFACES
const interfaces = dup(corePropsNoType)
interfaces.properties.fields = {
type: 'array',
required: true,
properties: corePropsPlusArgs.properties,
}
// ENUMS
const enums = dup(corePropsNoType)
enums.properties.values = {
type: 'array',
required: true,
properties: {
name: {
type: 'string',
required: true,
},
description: {
type: 'string',
required: true,
},
},
}
// UNIONS
const unions = dup(corePropsNoType)
unions.properties.possibleTypes = {
type: 'array',
required: true,
properties: {
name: {
type: 'string',
required: true,
},
id: {
type: 'string',
required: true,
},
href: {
type: 'string',
required: true,
},
},
}
// INPUT OBJECTS
const inputObjects = dup(corePropsNoType)
inputObjects.properties.inputFields = {
type: 'array',
required: true,
properties: coreProps.properties,
}
// SCALARS
const scalars = dup(corePropsNoType)
scalars.properties.kind.required = false
function dup(obj) {
return JSON.parse(JSON.stringify(obj))
}
export const schemaValidator = {
queries,
mutations,
objects,
interfaces,
enums,
unions,
inputObjects,
scalars,
}

View File

@@ -0,0 +1,12 @@
# GraphQL scripts
A [scheduled workflow](../.github/workflows/update-graphql-files.yml) runs the following
scripts on a daily basis:
```
src/graphql/scripts/update-files.js
```
These scripts update the [JSON data files](src/graphql/data) used to
render GraphQL docs. See the [`src/graphql/README`](src/graphql/README.md)
for more info.
**Note**: The changelog script pulls content from the internal-developer repo. It relies on graphql-docs automation running daily to update the changelog files in internal-developer.

View File

@@ -0,0 +1,308 @@
#!/usr/bin/env node
import { diff, ChangeType } from '@graphql-inspector/core'
import { loadSchema } from '@graphql-tools/load'
import fs from 'fs'
import renderContent from '../../../lib/render-content/index.js'
/**
* Tag `changelogEntry` with `date: YYYY-mm-dd`, then prepend it to the JSON
* structure written to `targetPath`. (`changelogEntry` and that file are modified in place.)
* @param {object} changelogEntry
* @param {string} targetPath
* @return {void}
*/
export function prependDatedEntry(changelogEntry, targetPath) {
// Build a `yyyy-mm-dd`-formatted date string
// and tag the changelog entry with it
const todayString = new Date().toISOString().slice(0, 10)
changelogEntry.date = todayString
const previousChangelogString = fs.readFileSync(targetPath)
const previousChangelog = JSON.parse(previousChangelogString)
// add a new entry to the changelog data
previousChangelog.unshift(changelogEntry)
// rewrite the updated changelog
fs.writeFileSync(targetPath, JSON.stringify(previousChangelog, null, 2))
}
/**
* Compare `oldSchemaString` to `newSchemaString`, and if there are any
* changes that warrant a changelog entry, return a changelog entry.
* Based on the parsed `previews`, identify changes that are under a preview.
* Otherwise, return null.
* @param {string} [oldSchemaString]
* @param {string} [newSchemaString]
* @param {Array<object>} [previews]
* @param {Array<object>} [oldUpcomingChanges]
* @param {Array<object>} [newUpcomingChanges]
* @return {object?}
*/
export async function createChangelogEntry(
oldSchemaString,
newSchemaString,
previews,
oldUpcomingChanges,
newUpcomingChanges
) {
// Create schema objects out of the strings
const oldSchema = await loadSchema(oldSchemaString, {})
const newSchema = await loadSchema(newSchemaString, {})
// Generate changes between the two schemas
const changes = await diff(oldSchema, newSchema)
const changesToReport = []
changes.forEach(function (change) {
if (CHANGES_TO_REPORT.includes(change.type)) {
changesToReport.push(change)
} else if (CHANGES_TO_IGNORE.includes(change.type)) {
// Do nothing
} else {
throw new Error(
'This change type should be added to CHANGES_TO_REPORT or CHANGES_TO_IGNORE: ' + change.type
)
}
})
const { schemaChangesToReport, previewChangesToReport } = segmentPreviewChanges(
changesToReport,
previews
)
const addedUpcomingChanges = newUpcomingChanges.filter(function (change) {
// Manually check each of `newUpcomingChanges` for an equivalent entry
// in `oldUpcomingChanges`.
return !oldUpcomingChanges.find(function (oldChange) {
return (
oldChange.location === change.location &&
oldChange.date === change.date &&
oldChange.description === change.description
)
})
})
// If there were any changes, create a changelog entry
if (
schemaChangesToReport.length > 0 ||
previewChangesToReport.length > 0 ||
addedUpcomingChanges.length > 0
) {
const changelogEntry = {
schemaChanges: [],
previewChanges: [],
upcomingChanges: [],
}
const cleanedSchemaChanges = cleanMessagesFromChanges(schemaChangesToReport)
const renderedScheamChanges = await Promise.all(
cleanedSchemaChanges.map(async (change) => {
return await renderContent(change)
})
)
const schemaChange = {
title: 'The GraphQL schema includes these changes:',
// Replace single quotes which wrap field/argument/type names with backticks
changes: renderedScheamChanges,
}
changelogEntry.schemaChanges.push(schemaChange)
for (const previewTitle in previewChangesToReport) {
const previewChanges = previewChangesToReport[previewTitle]
const cleanedPreviewChanges = cleanMessagesFromChanges(previewChanges.changes)
const renderedPreviewChanges = await Promise.all(
cleanedPreviewChanges.map(async (change) => {
return renderContent(change)
})
)
const cleanTitle = cleanPreviewTitle(previewTitle)
const entryTitle =
'The [' +
cleanTitle +
'](/graphql/overview/schema-previews#' +
previewAnchor(cleanTitle) +
') includes these changes:'
changelogEntry.previewChanges.push({
title: entryTitle,
changes: renderedPreviewChanges,
})
}
if (addedUpcomingChanges.length > 0) {
const cleanedUpcomingChanges = addedUpcomingChanges.map((change) => {
const location = change.location
const description = change.description
const date = change.date.split('T')[0]
return 'On member `' + location + '`:' + description + ' **Effective ' + date + '**.'
})
const renderedUpcomingChanges = await Promise.all(
cleanedUpcomingChanges.map(async (change) => {
return await renderContent(change)
})
)
changelogEntry.upcomingChanges.push({
title: 'The following changes will be made to the schema:',
changes: renderedUpcomingChanges,
})
}
return changelogEntry
} else {
return null
}
}
/**
* Prepare the preview title from github/github source for the docs.
* @param {string} title
* @return {string}
*/
export function cleanPreviewTitle(title) {
if (title === 'UpdateRefsPreview') {
title = 'Update refs preview'
} else if (title === 'MergeInfoPreview') {
title = 'Merge info preview'
} else if (!title.endsWith('preview')) {
title = title + ' preview'
}
return title
}
/**
* Turn the given title into an HTML-ready anchor.
* (ported from graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L281)
* @param {string} [previewTitle]
* @return {string}
*/
export function previewAnchor(previewTitle) {
return previewTitle
.toLowerCase()
.replace(/ /g, '-')
.replace(/[^\w-]/g, '')
}
/**
* Turn changes from graphql-inspector into messages for the HTML changelog.
* @param {Array<object>} changes
* @return {Array<string>}
*/
export function cleanMessagesFromChanges(changes) {
return changes.map(function (change) {
// replace single quotes around graphql names with backticks,
// to match previous behavior from graphql-schema-comparator
return change.message.replace(/'([a-zA-Z. :!]+)'/g, '`$1`')
})
}
/**
* Split `changesToReport` into two parts,
* one for changes in the main schema,
* and another for changes that are under preview.
* (Ported from /graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L230)
* @param {Array<object>} changesToReport
* @param {object} previews
* @return {object}
*/
export function segmentPreviewChanges(changesToReport, previews) {
// Build a map of `{ path => previewTitle` }
// for easier lookup of change to preview
const pathToPreview = {}
previews.forEach(function (preview) {
preview.toggled_on.forEach(function (path) {
pathToPreview[path] = preview.title
})
})
const schemaChanges = []
const changesByPreview = {}
changesToReport.forEach(function (change) {
// For each change, see if its path _or_ one of its ancestors
// is covered by a preview. If it is, mark this change as belonging to a preview
const pathParts = change.path.split('.')
let testPath = null
let previewTitle = null
let previewChanges = null
while (pathParts.length > 0 && !previewTitle) {
testPath = pathParts.join('.')
previewTitle = pathToPreview[testPath]
// If that path didn't find a match, then we'll
// check the next ancestor.
pathParts.pop()
}
if (previewTitle) {
previewChanges =
changesByPreview[previewTitle] ||
(changesByPreview[previewTitle] = {
title: previewTitle,
changes: [],
})
previewChanges.changes.push(change)
} else {
schemaChanges.push(change)
}
})
return { schemaChangesToReport: schemaChanges, previewChangesToReport: changesByPreview }
}
// We only want to report changes to schema structure.
// Deprecations are covered by "upcoming changes."
// By listing the changes explicitly here, we can make sure that,
// if the library changes, we don't miss publishing anything that we mean to.
// This was originally ported from graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L35-L103
const CHANGES_TO_REPORT = [
ChangeType.FieldArgumentDefaultChanged,
ChangeType.FieldArgumentTypeChanged,
ChangeType.EnumValueRemoved,
ChangeType.EnumValueAdded,
ChangeType.FieldRemoved,
ChangeType.FieldAdded,
ChangeType.FieldTypeChanged,
ChangeType.FieldArgumentAdded,
ChangeType.FieldArgumentRemoved,
ChangeType.ObjectTypeInterfaceAdded,
ChangeType.ObjectTypeInterfaceRemoved,
ChangeType.InputFieldRemoved,
ChangeType.InputFieldAdded,
ChangeType.InputFieldDefaultValueChanged,
ChangeType.InputFieldTypeChanged,
ChangeType.TypeRemoved,
ChangeType.TypeAdded,
ChangeType.TypeKindChanged,
ChangeType.UnionMemberRemoved,
ChangeType.UnionMemberAdded,
ChangeType.SchemaQueryTypeChanged,
ChangeType.SchemaMutationTypeChanged,
ChangeType.SchemaSubscriptionTypeChanged,
]
const CHANGES_TO_IGNORE = [
ChangeType.FieldArgumentDescriptionChanged,
ChangeType.DirectiveRemoved,
ChangeType.DirectiveAdded,
ChangeType.DirectiveDescriptionChanged,
ChangeType.DirectiveLocationAdded,
ChangeType.DirectiveLocationRemoved,
ChangeType.DirectiveArgumentAdded,
ChangeType.DirectiveArgumentRemoved,
ChangeType.DirectiveArgumentDescriptionChanged,
ChangeType.DirectiveArgumentDefaultValueChanged,
ChangeType.DirectiveArgumentTypeChanged,
ChangeType.EnumValueDescriptionChanged,
ChangeType.EnumValueDeprecationReasonChanged,
ChangeType.EnumValueDeprecationReasonAdded,
ChangeType.EnumValueDeprecationReasonRemoved,
ChangeType.FieldDescriptionChanged,
ChangeType.FieldDescriptionAdded,
ChangeType.FieldDescriptionRemoved,
ChangeType.FieldDeprecationAdded,
ChangeType.FieldDeprecationRemoved,
ChangeType.FieldDeprecationReasonChanged,
ChangeType.FieldDeprecationReasonAdded,
ChangeType.FieldDeprecationReasonRemoved,
ChangeType.InputFieldDescriptionAdded,
ChangeType.InputFieldDescriptionRemoved,
ChangeType.InputFieldDescriptionChanged,
ChangeType.TypeDescriptionChanged,
ChangeType.TypeDescriptionRemoved,
ChangeType.TypeDescriptionAdded,
]
export default { createChangelogEntry, cleanPreviewTitle, previewAnchor, prependDatedEntry }

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env node
import fs from 'fs/promises'
import path from 'path'
import mkdirp from 'mkdirp'
import yaml from 'js-yaml'
import { execSync } from 'child_process'
import { getContents, listMatchingRefs } from '../../../script/helpers/git-utils.js'
import { allVersions } from '../../../lib/all-versions.js'
import processPreviews from './utils/process-previews.js'
import processUpcomingChanges from './utils/process-upcoming-changes.js'
import processSchemas from './utils/process-schemas.js'
import { prependDatedEntry, createChangelogEntry } from './build-changelog.js'
const graphqlDataDir = path.join(process.cwd(), 'data/graphql')
const graphqlStaticDir = path.join(process.cwd(), 'src/graphql/data')
const dataFilenames = JSON.parse(
await fs.readFile(path.join(process.cwd(), './src/graphql/scripts/utils/data-filenames.json'))
)
// check for required PAT
if (!process.env.GITHUB_TOKEN) {
throw new Error('Error! You must have a GITHUB_TOKEN set in an .env file to run this script.')
}
const versionsToBuild = Object.keys(allVersions)
main()
async function main() {
const previewsJson = {}
const upcomingChangesJson = {}
for (const version of versionsToBuild) {
// Get the relevant GraphQL name for the current version
// For example, free-pro-team@latest corresponds to dotcom,
// enterprise-server@2.22 corresponds to ghes-2.22,
// and github-ae@latest corresponds to ghae
const graphqlVersion = allVersions[version].miscVersionName
// 1. UPDATE PREVIEWS
const previewsPath = getDataFilepath('previews', graphqlVersion)
const safeForPublicPreviews = yaml.load(await getRemoteRawContent(previewsPath, graphqlVersion))
await updateFile(previewsPath, yaml.dump(safeForPublicPreviews))
previewsJson[graphqlVersion] = processPreviews(safeForPublicPreviews)
// 2. UPDATE UPCOMING CHANGES
const upcomingChangesPath = getDataFilepath('upcomingChanges', graphqlVersion)
const previousUpcomingChanges = yaml.load(await fs.readFile(upcomingChangesPath, 'utf8'))
const safeForPublicChanges = await getRemoteRawContent(upcomingChangesPath, graphqlVersion)
await updateFile(upcomingChangesPath, safeForPublicChanges)
upcomingChangesJson[graphqlVersion] = await processUpcomingChanges(safeForPublicChanges)
// 3. UPDATE SCHEMAS
// note: schemas live in separate files per version
const schemaPath = getDataFilepath('schemas', graphqlVersion)
const previousSchemaString = await fs.readFile(schemaPath, 'utf8')
const latestSchema = await getRemoteRawContent(schemaPath, graphqlVersion)
await updateFile(schemaPath, latestSchema)
const schemaJsonPerVersion = await processSchemas(latestSchema, safeForPublicPreviews)
await updateStaticFile(
schemaJsonPerVersion,
path.join(graphqlStaticDir, `schema-${graphqlVersion}.json`)
)
// 4. UPDATE CHANGELOG
if (allVersions[version].nonEnterpriseDefault) {
// The Changelog is only build for free-pro-team@latest
const changelogEntry = await createChangelogEntry(
previousSchemaString,
latestSchema,
safeForPublicPreviews,
previousUpcomingChanges.upcoming_changes,
yaml.load(safeForPublicChanges).upcoming_changes
)
if (changelogEntry) {
prependDatedEntry(
changelogEntry,
path.join(process.cwd(), 'src/graphql/data/changelog.json')
)
}
}
}
await updateStaticFile(previewsJson, path.join(graphqlStaticDir, 'previews.json'))
await updateStaticFile(upcomingChangesJson, path.join(graphqlStaticDir, 'upcoming-changes.json'))
// Ensure the YAML linter runs before checkinging in files
execSync('npx prettier -w "**/*.{yml,yaml}"')
}
// get latest from github/github
async function getRemoteRawContent(filepath, graphqlVersion) {
const options = {
owner: 'github',
repo: 'github',
}
// find the relevant branch in github/github and set it as options.ref
await setBranchAsRef(options, graphqlVersion)
// add the filepath to the options so we can get the contents of the file
options.path = `config/${path.basename(filepath)}`
return getContents(...Object.values(options))
}
// find the relevant filepath in src/graphql/scripts/util/data-filenames.json
function getDataFilepath(id, graphqlVersion) {
const versionType = getVersionType(graphqlVersion)
// for example, dataFilenames['schema']['ghes'] = schema.docs-enterprise.graphql
const filename = dataFilenames[id][versionType]
// dotcom files live at the root of data/graphql
// non-dotcom files live in data/graphql/<version_subdir>
const dataSubdir = graphqlVersion === 'dotcom' ? '' : graphqlVersion
return path.join(graphqlDataDir, dataSubdir, filename)
}
async function setBranchAsRef(options, graphqlVersion, branch = false) {
const versionType = getVersionType(graphqlVersion)
const defaultBranch = 'master'
const branches = {
dotcom: defaultBranch,
ghec: defaultBranch,
ghes: `enterprise-${graphqlVersion.replace('ghes-', '')}-release`,
// TODO confirm the below is accurate after the release branch is created
ghae: 'github-ae-release',
}
// the first time this runs, it uses the branch found for the version above
if (!branch) branch = branches[versionType]
// set the branch as the ref
options.ref = `heads/${branch}`
// check whether the branch can be found in github/github
const foundRefs = await listMatchingRefs(...Object.values(options))
// if foundRefs array is empty, the branch cannot be found, so try a fallback
if (!foundRefs.length) {
const fallbackBranch = defaultBranch
await setBranchAsRef(options, graphqlVersion, fallbackBranch)
}
}
// given a GraphQL version like `ghes-2.22`, return `ghes`;
// given a GraphQL version like `ghae` or `dotcom`, return as is
function getVersionType(graphqlVersion) {
return graphqlVersion.split('-')[0]
}
async function updateFile(filepath, content) {
console.log(`fetching latest data to ${filepath}`)
await mkdirp(path.dirname(filepath))
return fs.writeFile(filepath, content, 'utf8')
}
async function updateStaticFile(json, filepath) {
const jsonString = JSON.stringify(json, null, 2)
return updateFile(filepath, jsonString)
}

View File

@@ -0,0 +1,20 @@
{
"schemas": {
"dotcom": "schema.docs.graphql",
"ghec": "schema.docs.graphql",
"ghes": "schema.docs-enterprise.graphql",
"ghae": "schema.docs-ghae.graphql"
},
"previews": {
"dotcom": "graphql_previews.yml",
"ghec": "graphql_previews.yml",
"ghes": "graphql_previews.enterprise.yml",
"ghae": "graphql_previews.ghae.yml"
},
"upcomingChanges": {
"dotcom": "graphql_upcoming_changes.public.yml",
"ghec": "graphql_upcoming_changes.public.yml",
"ghes": "graphql_upcoming_changes.public-enterprise.yml",
"ghae": "graphql_upcoming_changes.public-ghae.yml"
}
}

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env node
import { sentenceCase } from 'change-case'
import GithubSlugger from 'github-slugger'
const slugger = new GithubSlugger()
const inputOrPayload = /(Input|Payload)$/m
export default function processPreviews(previews) {
// clean up raw yml data
previews.forEach((preview) => {
// remove any extra info that follows a hyphen
preview.title = sentenceCase(preview.title.replace(/ -.+/, '')).replace('it hub', 'itHub') // fix overcorrected `git hub` from sentenceCasing
// Add `preview` to the end of titles if needed
preview.title = preview.title.endsWith('preview') ? preview.title : `${preview.title} preview`
// filter out schema members that end in `Input` or `Payload`
preview.toggled_on = preview.toggled_on.filter(
(schemaMember) => !inputOrPayload.test(schemaMember)
)
// remove unnecessary leading colon
preview.toggled_by = preview.toggled_by.replace(':', '')
// add convenience properties
preview.accept_header = `application/vnd.github.${preview.toggled_by}+json`
delete preview.announcement
delete preview.updates
slugger.reset()
preview.href = `/graphql/overview/schema-previews#${slugger.slug(preview.title)}`
})
return previews
}

View File

@@ -0,0 +1,454 @@
#!/usr/bin/env node
import { sortBy } from 'lodash-es'
import { parse, buildASTSchema } from 'graphql'
import helpers from './schema-helpers.js'
import fs from 'fs/promises'
import path from 'path'
const externalScalarsJSON = JSON.parse(
await fs.readFile(path.join(process.cwd(), './src/graphql/lib/non-schema-scalars.json'))
)
const externalScalars = await Promise.all(
externalScalarsJSON.map(async (scalar) => {
scalar.description = await helpers.getDescription(scalar.description)
scalar.id = helpers.getId(scalar.name)
scalar.href = helpers.getFullLink('scalars', scalar.id)
return scalar
})
)
// select and format all the data from the schema that we need for the docs
// used in the build step
export default async function processSchemas(idl, previewsPerVersion) {
const schemaAST = parse(idl.toString())
const schema = buildASTSchema(schemaAST)
// list of objects is used when processing mutations
const objectsInSchema = schemaAST.definitions.filter((def) => def.kind === 'ObjectTypeDefinition')
const data = {}
data.queries = []
data.mutations = []
data.objects = []
data.interfaces = []
data.enums = []
data.unions = []
data.inputObjects = []
data.scalars = []
await Promise.all(
schemaAST.definitions.map(async (def) => {
// QUERIES
if (def.name.value === 'Query') {
await Promise.all(
def.fields.map(async (field) => {
const query = {}
const queryArgs = []
query.name = field.name.value
query.type = helpers.getType(field)
query.kind = helpers.getTypeKind(query.type, schema)
query.id = helpers.getId(query.type)
query.href = helpers.getFullLink(query.kind, query.id)
query.description = await helpers.getDescription(field.description.value)
query.isDeprecated = helpers.getDeprecationStatus(field.directives, query.name)
query.deprecationReason = await helpers.getDeprecationReason(field.directives, query)
query.preview = await helpers.getPreview(field.directives, query, previewsPerVersion)
await Promise.all(
field.arguments.map(async (arg) => {
const queryArg = {}
queryArg.name = arg.name.value
queryArg.defaultValue = arg.defaultValue ? arg.defaultValue.value : undefined
queryArg.type = helpers.getType(arg)
queryArg.id = helpers.getId(queryArg.type)
queryArg.kind = helpers.getTypeKind(queryArg.type, schema)
queryArg.href = helpers.getFullLink(queryArg.kind, queryArg.id)
queryArg.description = await helpers.getDescription(arg.description.value)
queryArg.isDeprecated = helpers.getDeprecationStatus(arg.directives, queryArg.name)
queryArg.deprecationReason = await helpers.getDeprecationReason(
arg.directives,
queryArg
)
queryArg.preview = await helpers.getPreview(
arg.directives,
queryArg,
previewsPerVersion
)
queryArgs.push(queryArg)
})
)
query.args = sortBy(queryArgs, 'name')
data.queries.push(query)
})
)
return
}
// MUTATIONS
if (def.name.value === 'Mutation') {
await Promise.all(
def.fields.map(async (field) => {
const mutation = {}
const inputFields = []
const returnFields = []
mutation.name = field.name.value
mutation.kind = helpers.getKind(def.name.value)
mutation.id = helpers.getId(mutation.name)
mutation.href = helpers.getFullLink('mutations', mutation.id)
mutation.description = await helpers.getDescription(field.description.value)
mutation.isDeprecated = helpers.getDeprecationStatus(field.directives, mutation.name)
mutation.deprecationReason = await helpers.getDeprecationReason(
field.directives,
mutation
)
mutation.preview = await helpers.getPreview(
field.directives,
mutation,
previewsPerVersion
)
// there is only ever one input field argument, but loop anyway
await Promise.all(
field.arguments.map(async (field) => {
const inputField = {}
inputField.name = field.name.value
inputField.type = helpers.getType(field)
inputField.id = helpers.getId(inputField.type)
inputField.kind = helpers.getTypeKind(inputField.type, schema)
inputField.href = helpers.getFullLink(inputField.kind, inputField.id)
inputFields.push(inputField)
})
)
mutation.inputFields = sortBy(inputFields, 'name')
// get return fields
// first get the payload, then find payload object's fields. these are the mutation's return fields.
const returnType = helpers.getType(field)
const mutationReturnFields = objectsInSchema.find(
(obj) => obj.name.value === returnType
)
if (!mutationReturnFields) console.log(`no return fields found for ${returnType}`)
await Promise.all(
mutationReturnFields.fields.map(async (field) => {
const returnField = {}
returnField.name = field.name.value
returnField.type = helpers.getType(field)
returnField.id = helpers.getId(returnField.type)
returnField.kind = helpers.getTypeKind(returnField.type, schema)
returnField.href = helpers.getFullLink(returnField.kind, returnField.id)
returnField.description = await helpers.getDescription(field.description.value)
returnField.isDeprecated = helpers.getDeprecationStatus(
field.directives,
returnField.name
)
returnField.deprecationReason = await helpers.getDeprecationReason(
field.directives,
returnField
)
returnField.preview = await helpers.getPreview(
field.directives,
returnField,
previewsPerVersion
)
returnFields.push(returnField)
})
)
mutation.returnFields = sortBy(returnFields, 'name')
data.mutations.push(mutation)
})
)
return
}
// OBJECTS
if (def.kind === 'ObjectTypeDefinition') {
// objects ending with 'Payload' are only used to derive mutation values
// they are not included in the objects docs
if (def.name.value.endsWith('Payload')) return
const object = {}
const objectImplements = []
const objectFields = []
object.name = def.name.value
object.kind = helpers.getKind(def.kind)
object.id = helpers.getId(object.name)
object.href = helpers.getFullLink('objects', object.id)
object.description = await helpers.getDescription(def.description.value)
object.isDeprecated = helpers.getDeprecationStatus(def.directives, object.name)
object.deprecationReason = await helpers.getDeprecationReason(def.directives, object)
object.preview = await helpers.getPreview(def.directives, object, previewsPerVersion)
// an object's interfaces render in the `Implements` section
// interfaces do not have directives so they cannot be under preview/deprecated
if (def.interfaces.length) {
await Promise.all(
def.interfaces.map(async (graphqlInterface) => {
const objectInterface = {}
objectInterface.name = graphqlInterface.name.value
objectInterface.id = helpers.getId(objectInterface.name)
objectInterface.href = helpers.getFullLink('interfaces', objectInterface.id)
objectImplements.push(objectInterface)
})
)
}
// an object's fields render in the `Fields` section
if (def.fields.length) {
await Promise.all(
def.fields.map(async (field) => {
if (!field.description) return
const objectField = {}
objectField.name = field.name.value
objectField.description = await helpers.getDescription(field.description.value)
objectField.type = helpers.getType(field)
objectField.id = helpers.getId(objectField.type)
objectField.kind = helpers.getTypeKind(objectField.type, schema)
objectField.href = helpers.getFullLink(objectField.kind, objectField.id)
objectField.arguments = await helpers.getArguments(field.arguments, schema)
objectField.isDeprecated = helpers.getDeprecationStatus(field.directives)
objectField.deprecationReason = await helpers.getDeprecationReason(
field.directives,
objectField
)
objectField.preview = await helpers.getPreview(
field.directives,
objectField,
previewsPerVersion
)
objectFields.push(objectField)
})
)
}
if (objectImplements.length) object.implements = sortBy(objectImplements, 'name')
if (objectFields.length) object.fields = sortBy(objectFields, 'name')
data.objects.push(object)
return
}
// INTERFACES
if (def.kind === 'InterfaceTypeDefinition') {
const graphqlInterface = {}
const interfaceFields = []
graphqlInterface.name = def.name.value
graphqlInterface.kind = helpers.getKind(def.kind)
graphqlInterface.id = helpers.getId(graphqlInterface.name)
graphqlInterface.href = helpers.getFullLink('interfaces', graphqlInterface.id)
graphqlInterface.description = await helpers.getDescription(def.description.value)
graphqlInterface.isDeprecated = helpers.getDeprecationStatus(def.directives)
graphqlInterface.deprecationReason = await helpers.getDeprecationReason(
def.directives,
graphqlInterface
)
graphqlInterface.preview = await helpers.getPreview(
def.directives,
graphqlInterface,
previewsPerVersion
)
// an interface's fields render in the "Fields" section
if (def.fields.length) {
await Promise.all(
def.fields.map(async (field) => {
if (!field.description) return
const interfaceField = {}
interfaceField.name = field.name.value
interfaceField.description = await helpers.getDescription(field.description.value)
interfaceField.type = helpers.getType(field)
interfaceField.id = helpers.getId(interfaceField.type)
interfaceField.kind = helpers.getTypeKind(interfaceField.type, schema)
interfaceField.href = helpers.getFullLink(interfaceField.kind, interfaceField.id)
interfaceField.arguments = await helpers.getArguments(field.arguments, schema)
interfaceField.isDeprecated = helpers.getDeprecationStatus(field.directives)
interfaceField.deprecationReason = await helpers.getDeprecationReason(
field.directives,
interfaceField
)
interfaceField.preview = await helpers.getPreview(
field.directives,
interfaceField,
previewsPerVersion
)
interfaceFields.push(interfaceField)
})
)
}
graphqlInterface.fields = sortBy(interfaceFields, 'name')
data.interfaces.push(graphqlInterface)
return
}
// ENUMS
if (def.kind === 'EnumTypeDefinition') {
const graphqlEnum = {}
const enumValues = []
graphqlEnum.name = def.name.value
graphqlEnum.kind = helpers.getKind(def.kind)
graphqlEnum.id = helpers.getId(graphqlEnum.name)
graphqlEnum.href = helpers.getFullLink('enums', graphqlEnum.id)
graphqlEnum.description = await helpers.getDescription(def.description.value)
graphqlEnum.isDeprecated = helpers.getDeprecationStatus(def.directives)
graphqlEnum.deprecationReason = await helpers.getDeprecationReason(
def.directives,
graphqlEnum
)
graphqlEnum.preview = await helpers.getPreview(
def.directives,
graphqlEnum,
previewsPerVersion
)
await Promise.all(
def.values.map(async (value) => {
const enumValue = {}
enumValue.name = value.name.value
enumValue.description = await helpers.getDescription(value.description.value)
enumValues.push(enumValue)
})
)
graphqlEnum.values = sortBy(enumValues, 'name')
data.enums.push(graphqlEnum)
return
}
// UNIONS
if (def.kind === 'UnionTypeDefinition') {
const union = {}
const possibleTypes = []
union.name = def.name.value
union.kind = helpers.getKind(def.kind)
union.id = helpers.getId(union.name)
union.href = helpers.getFullLink('unions', union.id)
union.description = await helpers.getDescription(def.description.value)
union.isDeprecated = helpers.getDeprecationStatus(def.directives)
union.deprecationReason = await helpers.getDeprecationReason(def.directives, union)
union.preview = await helpers.getPreview(def.directives, union, previewsPerVersion)
// union types do not have directives so cannot be under preview/deprecated
await Promise.all(
def.types.map(async (type) => {
const possibleType = {}
possibleType.name = type.name.value
possibleType.id = helpers.getId(possibleType.name)
possibleType.href = helpers.getFullLink('objects', possibleType.id)
possibleTypes.push(possibleType)
})
)
union.possibleTypes = sortBy(possibleTypes, 'name')
data.unions.push(union)
return
}
// INPUT OBJECTS
// NOTE: input objects ending with `Input` are NOT included in the v4 input objects sidebar
// but they are still present in the docs (e.g., https://developer.github.com/v4/input_object/acceptenterpriseadministratorinvitationinput/)
// so we will include them here
if (def.kind === 'InputObjectTypeDefinition') {
const inputObject = {}
const inputFields = []
inputObject.name = def.name.value
inputObject.kind = helpers.getKind(def.kind)
inputObject.id = helpers.getId(inputObject.name)
inputObject.href = helpers.getFullLink('input-objects', inputObject.id)
inputObject.description = await helpers.getDescription(def.description.value)
inputObject.isDeprecated = helpers.getDeprecationStatus(def.directives)
inputObject.deprecationReason = await helpers.getDeprecationReason(
def.directives,
inputObject
)
inputObject.preview = await helpers.getPreview(
def.directives,
inputObject,
previewsPerVersion
)
if (def.fields.length) {
await Promise.all(
def.fields.map(async (field) => {
const inputField = {}
inputField.name = field.name.value
inputField.description = await helpers.getDescription(field.description.value)
inputField.type = helpers.getType(field)
inputField.id = helpers.getId(inputField.type)
inputField.kind = helpers.getTypeKind(inputField.type, schema)
inputField.href = helpers.getFullLink(inputField.kind, inputField.id)
inputField.isDeprecated = helpers.getDeprecationStatus(field.directives)
inputField.deprecationReason = await helpers.getDeprecationReason(
field.directives,
inputField
)
inputField.preview = await helpers.getPreview(
field.directives,
inputField,
previewsPerVersion
)
inputFields.push(inputField)
})
)
}
inputObject.inputFields = sortBy(inputFields, 'name')
data.inputObjects.push(inputObject)
return
}
// SCALARS
if (def.kind === 'ScalarTypeDefinition') {
const scalar = {}
scalar.name = def.name.value
scalar.kind = helpers.getKind(def.kind)
scalar.id = helpers.getId(scalar.name)
scalar.href = helpers.getFullLink('scalars', scalar.id)
scalar.description = await helpers.getDescription(def.description.value)
scalar.isDeprecated = helpers.getDeprecationStatus(def.directives)
scalar.deprecationReason = await helpers.getDeprecationReason(def.directives, scalar)
scalar.preview = await helpers.getPreview(def.directives, scalar, previewsPerVersion)
data.scalars.push(scalar)
}
})
)
// add non-schema scalars and sort all scalars alphabetically
data.scalars = sortBy(data.scalars.concat(externalScalars), 'name')
// sort all the types alphabetically
data.queries = sortBy(data.queries, 'name')
data.mutations = sortBy(data.mutations, 'name')
data.objects = sortBy(data.objects, 'name')
data.interfaces = sortBy(data.interfaces, 'name')
data.enums = sortBy(data.enums, 'name')
data.unions = sortBy(data.unions, 'name')
data.inputObjects = sortBy(data.inputObjects, 'name')
data.scalars = sortBy(data.scalars, 'name')
return data
}

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env node
import yaml from 'js-yaml'
import { groupBy } from 'lodash-es'
import renderContent from '../../../../lib/render-content/index.js'
export default async function processUpcomingChanges(upcomingChangesYml) {
const upcomingChanges = yaml.load(upcomingChangesYml).upcoming_changes
for (const change of upcomingChanges) {
change.date = change.date.slice(0, 10)
change.reason = await renderContent(change.reason)
change.description = await renderContent(change.description)
}
return groupBy(upcomingChanges.reverse(), 'date')
}

View File

@@ -0,0 +1,196 @@
#!/usr/bin/env node
import renderContent from '../../../../lib/render-content/index.js'
import fs from 'fs/promises'
import graphql from 'graphql'
import path from 'path'
const graphqlTypes = JSON.parse(
await fs.readFile(path.join(process.cwd(), './src/graphql/lib/types.json'))
)
const { isScalarType, isObjectType, isInterfaceType, isUnionType, isEnumType, isInputObjectType } =
graphql
const singleQuotesInsteadOfBackticks = / '(\S+?)' /
function addPeriod(string) {
return string.endsWith('.') ? string : string + '.'
}
async function getArguments(args, schema) {
if (!args.length) return
const newArgs = []
for (const arg of args) {
const newArg = {}
const type = {}
newArg.name = arg.name.value
newArg.defaultValue = arg.defaultValue ? arg.defaultValue.value : undefined
newArg.description = await getDescription(arg.description.value)
type.name = getType(arg)
type.id = getId(type.name)
type.kind = getTypeKind(type.name, schema)
type.href = getFullLink(type.kind, type.id)
newArg.type = type
newArgs.push(newArg)
}
return newArgs
}
async function getDeprecationReason(directives, schemaMember) {
if (!schemaMember.isDeprecated) return
// it's possible for a schema member to be deprecated and under preview
const deprecationDirective = directives.filter((dir) => dir.name.value === 'deprecated')
// catch any schema members that have more than one deprecation (none currently)
if (deprecationDirective.length > 1)
console.log(`more than one deprecation found for ${schemaMember.name}`)
return renderContent(deprecationDirective[0].arguments[0].value.value)
}
function getDeprecationStatus(directives) {
if (!directives.length) return
return directives[0].name.value === 'deprecated'
}
async function getDescription(rawDescription) {
rawDescription = rawDescription.replace(singleQuotesInsteadOfBackticks, '`$1`')
return renderContent(addPeriod(rawDescription))
}
function getFullLink(baseType, id) {
return `/graphql/reference/${baseType}#${id}`
}
function getId(path) {
return removeMarkers(path).toLowerCase()
}
// e.g., given `ObjectTypeDefinition`, get `objects`
function getKind(type) {
return graphqlTypes.find((graphqlType) => graphqlType.type === type).kind
}
async function getPreview(directives, schemaMember, previewsPerVersion) {
if (!directives.length) return
// it's possible for a schema member to be deprecated and under preview
const previewDirective = directives.filter((dir) => dir.name.value === 'preview')
if (!previewDirective.length) return
// catch any schema members that are under more than one preview (none currently)
if (previewDirective.length > 1)
console.log(`more than one preview found for ${schemaMember.name}`)
// an input object's input field may have a ListValue directive that is not relevant to previews
if (previewDirective[0].arguments[0].value.kind !== 'StringValue') return
const previewName = previewDirective[0].arguments[0].value.value
const preview = previewsPerVersion.find((p) => p.toggled_by.includes(previewName))
if (!preview) console.error(`cannot find "${previewName}" in graphql_previews.yml`)
return preview
}
// the docs use brackets to denote list types: `[foo]`
// and an exclamation mark to denote non-nullable types: `foo!`
// both single items and lists can be non-nullable
// so the permutations are:
// 1. single items: `foo`, `foo!`
// 2. nullable lists: `[foo]`, `[foo!]`
// 3. non-null lists: `[foo]!`, `[foo!]!`
// see https://github.com/rmosolgo/graphql-ruby/blob/master/guides/type_definitions/lists.md#lists-nullable-lists-and-lists-of-nulls
function getType(field) {
// 1. single items
if (field.type.kind !== 'ListType') {
// nullable item, e.g. `license` query has `License` type
if (field.type.kind === 'NamedType') {
return field.type.name.value
}
// non-null item, e.g. `meta` query has `GitHubMetadata!` type
if (field.type.kind === 'NonNullType' && field.type.type.kind === 'NamedType') {
return `${field.type.type.name.value}!`
}
}
// 2. nullable lists
if (field.type.kind === 'ListType') {
// nullable items, e.g. `codesOfConduct` query has `[CodeOfConduct]` type
if (field.type.type.kind === 'NamedType') {
return `[${field.type.type.name.value}]`
}
// non-null items, e.g. `severities` arg has `[SecurityAdvisorySeverity!]` type
if (field.type.type.kind === 'NonNullType' && field.type.type.type.kind === 'NamedType') {
return `[${field.type.type.type.name.value}!]`
}
}
// 3. non-null lists
if (field.type.kind === 'NonNullType' && field.type.type.kind === 'ListType') {
// nullable items, e.g. `licenses` query has `[License]!` type
if (field.type.type.type.kind === 'NamedType') {
return `[${field.type.type.type.name.value}]!`
}
// non-null items, e.g. `marketplaceCategories` query has `[MarketplaceCategory!]!` type
if (
field.type.type.type.kind === 'NonNullType' &&
field.type.type.type.type.kind === 'NamedType'
) {
return `[${field.type.type.type.type.name.value}!]!`
}
}
console.error(`cannot get type of ${field.name.value}`)
}
function getTypeKind(type, schema) {
type = removeMarkers(type)
const typeFromSchema = schema.getType(type)
if (isScalarType(typeFromSchema)) {
return 'scalars'
}
if (isObjectType(typeFromSchema)) {
return 'objects'
}
if (isInterfaceType(typeFromSchema)) {
return 'interfaces'
}
if (isUnionType(typeFromSchema)) {
return 'unions'
}
if (isEnumType(typeFromSchema)) {
return 'enums'
}
if (isInputObjectType(typeFromSchema)) {
return 'input-objects'
}
console.error(`cannot find type kind of ${type}`)
}
function removeMarkers(str) {
return str.replace('[', '').replace(']', '').replace(/!/g, '')
}
export default {
getArguments,
getDeprecationReason,
getDeprecationStatus,
getDescription,
getFullLink,
getId,
getKind,
getPreview,
getType,
getTypeKind,
}