469 lines
12 KiB
TypeScript
469 lines
12 KiB
TypeScript
// when updating to typescript,
|
|
// update links in content/contributing as well
|
|
|
|
import parse from '@/frame/lib/read-frontmatter'
|
|
import { allVersions } from '@/versions/lib/all-versions'
|
|
import { allTools } from '@/tools/lib/all-tools'
|
|
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data'
|
|
|
|
interface SchemaProperty {
|
|
type?: string | string[]
|
|
translatable?: boolean
|
|
deprecated?: boolean
|
|
default?: any
|
|
minimum?: number
|
|
maximum?: number
|
|
enum?: any[]
|
|
errorMessage?: string
|
|
items?: any
|
|
properties?: Record<string, any>
|
|
required?: string[]
|
|
additionalProperties?: boolean
|
|
format?: string
|
|
description?: string
|
|
minItems?: number
|
|
maxItems?: number
|
|
}
|
|
|
|
interface Schema {
|
|
type: string
|
|
required: string[]
|
|
additionalProperties: boolean
|
|
properties: Record<string, SchemaProperty>
|
|
}
|
|
|
|
const layoutNames = [
|
|
'default',
|
|
'graphql-explorer',
|
|
'product-landing',
|
|
'product-guides',
|
|
'release-notes',
|
|
'inline',
|
|
'category-landing',
|
|
'bespoke-landing',
|
|
'discovery-landing',
|
|
'journey-landing',
|
|
false,
|
|
]
|
|
|
|
// DEPRECATED: Use 'contentType' instead of 'type' for new content.
|
|
// 'type' exists on ~40% of files but is used only for internal analytics.
|
|
// Migration tool: src/content-render/scripts/add-content-type.ts
|
|
const guideTypes = ['overview', 'quick_start', 'tutorial', 'how_to', 'reference', 'rai']
|
|
|
|
// As of July 2025, use 'contentType' rather than 'type'.
|
|
export const contentTypesEnum = [
|
|
'get-started',
|
|
'concepts',
|
|
'how-tos',
|
|
'reference',
|
|
'tutorials',
|
|
'homepage', // Only applies to the sole 'content/index.md' file (the homepage).
|
|
'landing', // Only applies to 'content/<product>/index.md' files (product landings).
|
|
'rai', // Only applies to files that live in directories with 'responsible-use' in the name.
|
|
'other', // Everything else.
|
|
]
|
|
|
|
export const schema: Schema = {
|
|
type: 'object',
|
|
required: ['title', 'versions'],
|
|
additionalProperties: false,
|
|
properties: {
|
|
title: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
shortTitle: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
intro: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
product: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
permissions: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
// true by default on articles, false on all other content
|
|
showMiniToc: {
|
|
type: 'boolean',
|
|
},
|
|
// This frontmatter property is deprecated. Despite what `miniTocMaxHeadingLevel` says, the max level of mini TOC is *always* 2. See github/docs-engineering#2701.
|
|
miniTocMaxHeadingLevel: {
|
|
deprecated: true,
|
|
type: 'number',
|
|
default: 2,
|
|
minimum: 2,
|
|
maximum: 4,
|
|
},
|
|
subcategory: {
|
|
type: 'boolean',
|
|
},
|
|
// allow hidden articles under `early-access`
|
|
hidden: {
|
|
type: 'boolean',
|
|
},
|
|
// specify whether an Early Access article should not have a header notice
|
|
noEarlyAccessBanner: {
|
|
type: 'boolean',
|
|
},
|
|
// specify whether an Early Access product should have a table of contents
|
|
// (EA categories and subcategories have them by default, but products don't)
|
|
earlyAccessToc: {
|
|
type: 'boolean',
|
|
},
|
|
layout: {
|
|
type: ['string', 'boolean'],
|
|
enum: layoutNames,
|
|
errorMessage: 'must be the filename of an existing layout file, or `false` for no layout',
|
|
},
|
|
redirect_from: {
|
|
type: 'array',
|
|
},
|
|
allowTitleToDifferFromFilename: {
|
|
type: 'boolean',
|
|
},
|
|
introLinks: {
|
|
type: 'object',
|
|
},
|
|
authors: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
examples_source: {
|
|
type: 'string',
|
|
},
|
|
effectiveDate: {
|
|
type: 'string',
|
|
},
|
|
featuredLinks: {
|
|
type: 'object',
|
|
properties: {
|
|
gettingStarted: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
},
|
|
startHere: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
},
|
|
guideCards: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
},
|
|
popular: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
},
|
|
// allows you to use an alternate heading for the popular column
|
|
popularHeading: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
videos: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
title: {
|
|
type: 'string',
|
|
},
|
|
href: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// allows you to use an alternate heading for the videos column
|
|
videosHeading: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
// Shown in `product-landing.html` "What's new" section
|
|
changelog: {
|
|
type: 'object',
|
|
properties: {
|
|
label: { type: 'string' },
|
|
prefix: { type: 'string' },
|
|
},
|
|
},
|
|
audience: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
enum: ['builder', 'driver'],
|
|
},
|
|
},
|
|
// DEPRECATED: Use 'contentType' instead of 'type' for new content.
|
|
// 'type' exists on ~40% of files but is used only for internal analytics.
|
|
// Migration tool: src/content-render/scripts/add-content-type.ts
|
|
type: {
|
|
type: 'string',
|
|
enum: guideTypes,
|
|
},
|
|
// As of July 2025, use 'contentType' rather than 'type'.
|
|
contentType: {
|
|
type: 'string',
|
|
enum: contentTypesEnum,
|
|
},
|
|
topics: {
|
|
type: 'array',
|
|
},
|
|
includeGuides: {
|
|
type: 'array',
|
|
},
|
|
learningTracks: {
|
|
type: 'array',
|
|
},
|
|
// Journey tracks for journey landing pages
|
|
journeyTracks: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['id', 'title', 'guides'],
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
description: 'Unique identifier for the journey track',
|
|
},
|
|
title: {
|
|
type: 'string',
|
|
translatable: true,
|
|
description: 'Display title for the journey track',
|
|
},
|
|
description: {
|
|
type: 'string',
|
|
translatable: true,
|
|
description: 'Optional description for the journey track',
|
|
},
|
|
guides: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
description: 'Array of article paths that make up this journey track',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
description: 'Array of journey tracks for journey landing pages',
|
|
},
|
|
// Used in `product-landing.html`
|
|
beta_product: {
|
|
type: 'boolean',
|
|
},
|
|
// Show in `product-landing.html`
|
|
product_video: {
|
|
type: 'string',
|
|
},
|
|
// Show in `product-landing.html`
|
|
product_video_transcript: {
|
|
type: 'string',
|
|
},
|
|
// Hero image for landing pages
|
|
heroImage: {
|
|
type: 'string',
|
|
},
|
|
interactive: {
|
|
type: 'boolean',
|
|
},
|
|
communityRedirect: {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
},
|
|
href: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
// Platform-specific content preference
|
|
defaultPlatform: {
|
|
type: 'string',
|
|
enum: ['mac', 'windows', 'linux'],
|
|
},
|
|
// Tool-specific content preference, the list of tools are kept in
|
|
// make it easier to update in a single place
|
|
defaultTool: {
|
|
type: 'string',
|
|
enum: Object.keys(allTools),
|
|
},
|
|
// Child groups specified on top-level TOC
|
|
childGroups: {
|
|
type: 'array',
|
|
},
|
|
// Child links specified on any TOC page
|
|
children: {
|
|
type: 'array',
|
|
},
|
|
// External products specified on the homepage
|
|
externalProducts: {
|
|
type: 'object',
|
|
required: ['electron'],
|
|
properties: {
|
|
electron: {
|
|
type: 'object',
|
|
required: ['id', 'name', 'href', 'external'],
|
|
properties: {
|
|
id: { type: 'string' },
|
|
name: { type: 'string' },
|
|
href: { type: 'string', format: 'url' },
|
|
external: { type: 'boolean' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// whether or not the page is mirrored by an experimental page
|
|
hasExperimentalAlternative: {
|
|
type: 'boolean',
|
|
},
|
|
// Translation metadata properties added during the translation process,
|
|
// we don't use these properties ourselves.
|
|
'ms.openlocfilehash': {
|
|
type: 'string',
|
|
},
|
|
'ms.sourcegitcommit': {
|
|
type: 'string',
|
|
},
|
|
'ms.translationtype': {
|
|
type: 'string',
|
|
},
|
|
'ms.contentlocale': {
|
|
type: 'string',
|
|
},
|
|
'ms.lasthandoff': {
|
|
type: 'string',
|
|
},
|
|
'ms.locfileid': {
|
|
type: 'string',
|
|
},
|
|
autogenerated: {
|
|
type: 'string',
|
|
enum: ['audit-logs', 'codeql-cli', 'github-apps', 'graphql', 'rest', 'webhooks'],
|
|
},
|
|
// START category-landing tags
|
|
category: {
|
|
type: 'array',
|
|
errorMessage: `must be an array, which is written in frontmatter like:
|
|
category:
|
|
- Category Name`,
|
|
},
|
|
complexity: {
|
|
type: 'array',
|
|
},
|
|
industry: {
|
|
type: 'array',
|
|
},
|
|
octicon: {
|
|
type: 'string',
|
|
},
|
|
// END category landing tags
|
|
// Custom sidebar link for category pages
|
|
sidebarLink: {
|
|
type: 'object',
|
|
required: ['text', 'href'],
|
|
properties: {
|
|
text: {
|
|
type: 'string',
|
|
translatable: true,
|
|
},
|
|
href: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
// Spotlight configuration for category landing pages
|
|
spotlight: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['article', 'image'],
|
|
properties: {
|
|
article: {
|
|
type: 'string',
|
|
description: 'Path to the article to spotlight',
|
|
},
|
|
image: {
|
|
type: 'string',
|
|
description: 'Path to image for the spotlight card',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
description: 'Array of articles to feature in the spotlight section',
|
|
},
|
|
// Recommended configuration for category landing pages
|
|
recommended: {
|
|
type: 'array',
|
|
minItems: 3,
|
|
maxItems: 9,
|
|
description: 'Array of articles to feature in the carousel section',
|
|
},
|
|
// Included categories for article grid filtering
|
|
includedCategories: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
description: 'Array of category names to include in the article grid dropdown filter',
|
|
},
|
|
},
|
|
}
|
|
|
|
// returns a list of deprecated properties
|
|
export const deprecatedProperties = Object.keys(schema.properties).filter((prop: string) => {
|
|
return (schema.properties as Record<string, SchemaProperty>)[prop].deprecated
|
|
})
|
|
|
|
const featureVersionsProp = {
|
|
feature: {
|
|
type: ['string', 'array'],
|
|
enum: Object.keys(getDeepDataByLanguage('features', 'en')),
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
errorMessage:
|
|
'must be the name (or names) of a feature that matches "filename" in data/features/_filename_.yml',
|
|
},
|
|
}
|
|
|
|
const semverRange = {
|
|
type: 'string',
|
|
format: 'semver',
|
|
// This is JSON pointer syntax with ajv so we can specify the bad version
|
|
// in the error message.
|
|
errorMessage: 'Must be a valid SemVer range: ${0}',
|
|
}
|
|
|
|
;(schema.properties as Record<string, any>).versions = {
|
|
type: ['object', 'string'], // allow a '*' string to indicate all versions
|
|
additionalProperties: false, // don't allow any versions in FM that aren't defined in lib/all-versions
|
|
properties: Object.values(allVersions).reduce((acc: any, versionObj) => {
|
|
acc[versionObj.plan] = semverRange
|
|
acc[versionObj.shortName] = semverRange
|
|
return acc
|
|
}, featureVersionsProp),
|
|
}
|
|
|
|
export function frontmatter(markdown: string, opts: any = {}) {
|
|
const defaults = {
|
|
schema,
|
|
}
|
|
|
|
return parse(markdown, Object.assign({}, defaults, opts))
|
|
}
|
|
|
|
// attach the schema object so it can be `require`d elsewhere.
|
|
frontmatter.schema = schema
|
|
|
|
export default frontmatter
|