1
0
mirror of synced 2025-12-19 18:10:59 -05:00
Files
docs/lib/frontmatter.js
2022-06-08 09:07:26 -04:00

295 lines
7.4 KiB
JavaScript

import fs from 'fs'
import path from 'path'
import parse from './read-frontmatter.js'
import semver from 'semver'
import { allVersions } from './all-versions.js'
import { allTools } from './all-tools.js'
const layoutNames = [
'default',
'dev-toc',
'graphql-explorer',
'product-landing',
'product-guides',
'release-notes',
false,
]
const guideTypes = ['overview', 'quick_start', 'tutorial', 'how_to', 'reference']
const featureVersions = fs
.readdirSync(path.posix.join(process.cwd(), 'data/features'))
.map((file) => path.basename(file, '.yml'))
export const schema = {
properties: {
title: {
type: 'string',
required: true,
translatable: true,
},
shortTitle: {
type: 'string',
translatable: true,
},
intro: {
type: 'string',
translatable: true,
},
product: {
type: 'string',
translatable: true,
},
permissions: {
type: 'string',
},
// true by default on articles, false on all other content
showMiniToc: {
type: 'boolean',
},
miniTocMaxHeadingLevel: {
type: 'number',
default: 2,
minimum: 2,
maximum: 4,
},
mapTopic: {
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',
},
layout: {
type: ['string', 'boolean'],
enum: layoutNames,
message: 'must be the filename of an existing layout file, or `false` for no layout',
},
redirect_from: {
type: ['array', 'string'],
},
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' },
},
guides: {
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',
},
videos: {
type: 'array',
items: {
type: 'object',
properties: {
title: 'string',
href: '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' },
},
},
type: {
type: 'string',
enum: guideTypes,
},
topics: {
type: 'array',
},
includeGuides: {
type: 'array',
},
learningTracks: {
type: 'array',
},
// Used in `product-landing.html`
beta_product: {
type: 'boolean',
},
// Show in `product-landing.html`
product_video: {
type: 'string',
},
interactive: {
type: 'boolean',
},
communityRedirect: {
type: 'object',
properties: {
name: 'string',
href: '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),
},
// Documentation contributed by a third party, such as a GitHub Partner
contributor: {
type: 'object',
properties: {
name: { type: 'string' },
URL: { type: 'string' },
},
},
// 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',
properties: {
atom: {
type: 'object',
required: true,
properties: {
id: { type: 'string', required: true },
name: { type: 'string', required: true },
href: { type: 'string', format: 'url', required: true },
external: { type: 'boolean', required: true },
},
},
electron: {
type: 'object',
required: true,
properties: {
id: { type: 'string', required: true },
name: { type: 'string', required: true },
href: { type: 'string', format: 'url', required: true },
external: { type: 'boolean', required: true },
},
},
},
},
// whether or not the page is mirrored by an experimental page
hasExperimentalAlternative: {
type: 'boolean',
},
},
}
const featureVersionsProp = {
feature: {
type: ['string', 'array'],
enum: featureVersions,
items: {
type: 'string',
},
message:
'must be the name (or names) of a feature that matches "filename" in data/features/_filename_.yml',
},
}
const asteriskPattern = /^\*$/
schema.properties.versions = {
type: ['object', 'string'], // allow a '*' string to indicate all versions
required: true,
additionalProperties: false, // don't allow any versions in FM that aren't defined in lib/all-versions
properties: Object.values(allVersions).reduce((acc, versionObj) => {
acc[versionObj.plan] = getValidProps(versionObj)
acc[versionObj.shortName] = getValidProps(versionObj)
return acc
}, featureVersionsProp),
}
function getValidProps(versionObj) {
const valid = { type: 'string' }
// The properties attached to versionObj are defined in lib/all-versions.js.
// If a version has no numbered releases or exception pattern, the only valid value is '*'.
if (!(versionObj.hasNumberedReleases || versionObj.allowedFrontmatterPattern)) {
valid.pattern = asteriskPattern
valid.message = `Must have a value of '*'`
}
// If a version has an exception pattern, both '*' and the exception pattern are valid.
if (versionObj.allowedFrontmatterPattern) {
valid.pattern = new RegExp(`${asteriskPattern.source}|${versionObj.allowedFrontmatterPattern}`)
valid.message = `Must have a value of '*' or 'issue-###', where ### is an integer`
}
// If a version has numbered releases, any semver range is valid. Note '*' is a valid semver range.
if (versionObj.hasNumberedReleases) {
valid.conform = semver.validRange
valid.message = 'Must be a valid SemVer range'
}
return valid
}
function frontmatter(markdown, opts = {}) {
const defaults = {
schema,
validateKeyNames: true,
validateKeyOrder: false, // TODO: enable this once we've sorted all the keys. See issue 9658
}
return parse(markdown, Object.assign({}, defaults, opts))
}
// attach the schema object so it can be `require`d elsewhere.
frontmatter.schema = schema
export default frontmatter