1
0
mirror of synced 2025-12-20 18:36:31 -05:00
Files
docs/script/rest/update-files.js
Grace Park b98c83e924 REST operations sidebar (#26225)
* testing out a rest operations sidebar

* cleanup

* renamed 5 files

* renamed 5 files

* set redirect_from on 5 files

* renamed 1 files

* renamed 1 files

* renamed 3 files

* renamed 1 files

* renamed 1 files

* renamed 3 files

* renamed 1 files

* renamed 2 files

* renamed 1 files

* renamed 4 files

* renamed 15 files

* renamed 2 files

* renamed 6 files

* renamed 1 files

* renamed 4 files

* renamed 7 files

* renamed 1 files

* renamed 3 files

* renamed 3 files

* renamed 1 files

* renamed 5 files

* renamed 1 files

* renamed 1 files

* renamed 3 files

* renamed 4 files

* renamed 1 files

* renamed 1 files

* renamed 2 files

* renamed 4 files

* renamed 1 files

* renamed 1 files

* renamed 1 files

* renamed 6 files

* renamed 6 files

* renamed 4 files

* move files

* adding more

* updating to add restcontext and start of removing data/reusables/rest-reference

* removed data/reusables

* add a RestMiniTocItem and updating the filtering to add a subcategory so all manually added H3s are in mini tocs in addition to operations

* remove console log

* [WIP]: REST New Proposal Sidebar (#26471)

* saving

* update sidebar

* remove console log

* update guides and overview

* import Category for category level rest pages

* update undefined restOperations

* update restOperationData category and subcategory levels"

* minor updates

* update get mini toc items function

* updating REST context for sidebar

* updating rest data

* remove console logs

* WIP: mini-toc-ing the sidebar

Co-authored-by: Robert Sese <rsese@github.com>

* A little cleanup

* Fix first subcategory link and add some comments

* updating anchor links in sidebar

* adding updates

* remove standalone

* update product and maptopic pages using article context

* add conditional link wrapper

* fix sidebar toggle and versions for enterprise admin

* update versions per subcategory

* Highlight sidebar link for current page

* Update miniToc hash links and hash change tracking

* fix unique key in CollapsibleSection

* Fix list markup

* remove title

* update permissions

* Hide minitocs on landing (#26594)

* hide minitocs on landing page

* simplify page components and remove minitoc from sidebar for guides/overview

* fix carats and category fix

* remove id

Co-authored-by: Grace Park <gracepark@github.com>

* updating content based on versions script check with the OpenAPI

* update script and content files

* update script and content/rest files

* update to add TocLanding

* update script

* update index files

* add codespaces repository-secrets

* remove openapi schema check script

* remove minitocs at the top

* add h2 about the {title} api

* fix tests/unit/openapi-schema.js

* Fix linting tests

* fix search/topics test

* fix tests/unit/pages test

* update rest/reference links in components

* run prettier

* Update components/rest/RestReferencePage.tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update components/rest/RestReferencePage.tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update pages/[versionId]/rest/[category]/[subcategory].tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update pages/[versionId]/rest/[category]/[subcategory].tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update pages/[versionId]/rest/[category]/[subcategory].tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update pages/[versionId]/rest/[category]/[subcategory].tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Update tests/unit/openapi-schema.js

Co-authored-by: Rachael Sewell <rachmari@github.com>

* updating comment location

* remove dependabot override

* remove path-utils current product update for rest

* run linter

* remove dependabot.md and remove h2 heading on restreference

* update the correct product to rest for rest pages

* adding comments for updates to path-utils

* remove console log

* REST sidebar: handle legacy v3 redirects (#26686)

* Add script to handle legacy v3 REST redirects

* Run the script

* Handle a redirect to a redirect

* Update REST test URLs

* 'await' and test runs subcategory of checks

* Update REST URLs for routing/developer-site-redirects tests

* Update developer-redirects fixture with new REST URLs

* Resolve merge conflicts

* Update rest-redirects fixture with new REST URLs

* Fix broken links with REST pages re-org

* redirectTo could be undefined

* Fix script for posterity, can't redirect paths with hashes

* Remove invalid hash redirects

* Typically don't need to save one-off scripts

* Undo redirect changes (not necessary for handling v3 redirects)

* Remove script-added redirects

* Update old v3 redirects with new REST URLs

* No more GHES search indexing page

* 'org' not 'organization'

* Update fixture data for new REST URLs

* revert any content directory changes

Co-authored-by: Grace Park <gracepark@github.com>
Co-authored-by: Rachael Sewell <rachmari@github.com>

* Adding test rest (#26750)

* add test to check openapi schema versions and content rest frontmatter versions

* update lib/redirects

* fix test and add error messages

* adding repository secrets

* adding repository-secrets.md

* Revert "update lib/redirects"

This reverts commit 3aafe28265764d5bc09c0c478c8e0ca099c8fbcf.

* remove lib/redirects changes and console logs

* Update lib/rest/index.js

Co-authored-by: Rachael Sewell <rachmari@github.com>

* update unique key

* Rest client side redirects (#26754)

* adding tags subcategory for the rest content repos category

* run prettier

* bug fix for anchor scrolls" (#26892)

* updating width size for rest reference page

* Rest sidebar consolidation (#26862)

* refactor sidebar

* fix articlecontext provider issue on rest product landing page for all versions

* fix a bug, create new component

* revert change to create new component and fix bug

Co-authored-by: Rachael Sewell <rachmari@github.com>

* Set currentAnchor with a hashchange handler (#26923)

* Rest sidebar design tweaks (#26807)

* Rest sidebar design tweaks

* tweak color to subtle

* use muted color and margin for line

* update to design feedback

Co-authored-by: Grace Park <gracepark@github.com>

* Remove cheerio from rest-collapsible (#26948)

* remove cheerio from rest-collapsible

* update type

* adding endswith instead

* use productId instead

* one off edge case for secret-scanning

* Reorganize subcategory and category, Update pre -> div, Add RestContext (#26950)

* reorganize subcategory and category

* add RestContext

* update comment

* update for endpoints page

* add comment

* move object to restcontext

* remove effectiveDate in restcontext

* remove width calculation for rest reference page

* fix adding manual writer's minitocs to sidebar

* update with feedback

* update comment

* update isRestReferencePage

* remove page component and fix bug

* adding back rest/index.tsx

Co-authored-by: Rachael Sewell <rachmari@github.com>

* update content/rest"

* add back design tweak

* update to div

* update margins on rest api reference

* remove page component

* adding tests

* separate product from rest sidebar (#27065)

* separate product from rest sidebar

* Use ProductCollapsibleSections for product pages

* fix tests

Co-authored-by: Robert Sese <rsese@github.com>
Co-authored-by: Grace Park <gracepark@github.com>

* Rest sidebar translations (#27052)

* update translations

* remove general test

Co-authored-by: Robert Sese <rsese@github.com>
Co-authored-by: Rachael Sewell <rachmari@github.com>
2022-04-20 10:19:48 -07:00

425 lines
16 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// [start-readme]
//
// Run this script to pull openAPI files from github/github, dereference them, and decorate them.
//
// [end-readme]
import { stat, readFile, writeFile, readdir } from 'fs/promises'
import path from 'path'
import program from 'commander'
import { execSync } from 'child_process'
import mkdirp from 'mkdirp'
import rimraf from 'rimraf'
import getOperations from './utils/get-operations.js'
import yaml from 'js-yaml'
const tempDocsDir = path.join(process.cwd(), 'openapiTmp')
const githubRepoDir = path.join(process.cwd(), '../github')
const dereferencedPath = path.join(process.cwd(), 'lib/rest/static/dereferenced')
const appsStaticPath = path.join(process.cwd(), 'lib/rest/static/apps')
const decoratedPath = path.join(process.cwd(), 'lib/rest/static/decorated')
const openApiReleasesDir = `${githubRepoDir}/app/api/description/config/releases`
program
.description('Generate dereferenced OpenAPI and decorated schema files.')
.option(
'--decorate-only',
'⚠️ Only used by a 🤖 to generate decorated schema files from existing dereferenced schema files.'
)
.option(
'-v --versions <VERSIONS...>',
'A list of undeprecated, published versions to build, separated by a space. Example "ghes-3.1" or "api.github.com github.ae"'
)
.option('-d --include-deprecated', 'Includes schemas that are marked as `deprecated: true`')
.option('-u --include-unpublished', 'Includes schemas that are marked as `published: false`')
.option('--redirects-only', 'Only generate the redirects file')
.parse(process.argv)
const { decorateOnly, versions, includeUnpublished, includeDeprecated, redirectsOnly } =
program.opts()
// Check that the github/github repo exists. If the files are only being
// decorated, the github/github repo isn't needed.
if (!decorateOnly && !redirectsOnly) {
try {
await stat(githubRepoDir)
} catch (error) {
console.log(
`🛑 The ${githubRepoDir} does not exist. Make sure you have a local, bootstrapped checkout of github/github at the same level as your github/docs-internal repo before running this script.`
)
process.exit(1)
}
}
// When the input parameter type is decorate-only, use the local
// `github/docs-internal` repo to generate a list of schema files.
// Otherwise, use the `github/github` list of config files
const referenceSchemaDirectory = decorateOnly ? dereferencedPath : openApiReleasesDir
// A full list of unpublished, deprecated, and active schemas
const allSchemas = await getOpenApiSchemas(referenceSchemaDirectory)
await validateInputParameters(allSchemas)
// Format the command supplied to the bundle script in `github/github`
const commandParameters = await getCommandParameters()
// Get the list of schemas for this bundle, depending on options
const schemas = await getSchemas(allSchemas)
main()
async function main() {
// Generate the dereferenced OpenAPI schema files
if (!decorateOnly && !redirectsOnly) {
await getDereferencedFiles()
}
// Decorate the dereferenced files in a format ingestible by docs.github.com
if (!redirectsOnly) {
await decorate()
}
console.log(
'\n🏁 The static REST API files are now up-to-date with your local `github/github` checkout. To revert uncommitted changes, run `git checkout lib/rest/static/*.\n\n'
)
}
async function getDereferencedFiles() {
// Get the github/github repo branch name and pull latest
const githubBranch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: githubRepoDir })
.toString()
.trim()
// Only pull master branch because development mode branches are assumed
// to be up-to-date during active work.
if (githubBranch === 'master') {
execSync('git pull', { cwd: githubRepoDir })
}
// Create a tmp directory to store schema files generated from github/github
rimraf.sync(tempDocsDir)
await mkdirp(tempDocsDir)
console.log(
`\n🏃🏃🏃Running \`bin/openapi bundle\` in branch '${githubBranch}' of your github/github checkout to generate the dereferenced OpenAPI schema files.\n`
)
try {
console.log(`bundle -o ${tempDocsDir} ${commandParameters}`)
execSync(
`${path.join(githubRepoDir, 'bin/openapi')} bundle -o ${tempDocsDir} ${commandParameters}`,
{ stdio: 'inherit' }
)
} catch (error) {
console.error(error)
console.log(
'🛑 Whoops! It looks like the `bin/openapi bundle` command failed to run in your `github/github` repository checkout. To troubleshoot, ensure that your OpenAPI schema YAML is formatted correctly. A CI test runs on your `github/github` PR that flags malformed YAML. You can check the PR diff view for comments left by the openapi CI test to find and fix any formatting errors.'
)
process.exit(1)
}
execSync(`find ${tempDocsDir} -type f -name "*deref.json" -exec mv '{}' ${dereferencedPath} ';'`)
rimraf.sync(tempDocsDir)
// When running in development mode (locally), the the info.version
// property in the dereferenced schema is replaced with the branch
// name of the `github/github` checkout. A CI test
// checks the version and fails if it's not a semantic version.
for (const filename of schemas) {
const schema = JSON.parse(await readFile(path.join(dereferencedPath, filename)))
schema.info.version = `${githubBranch} !!DEVELOPMENT MODE - DO NOT MERGE!!`
await writeFile(path.join(dereferencedPath, filename), JSON.stringify(schema, null, 2))
}
}
async function getCategoryOverrideRedirects() {
const { operationUrls, sectionUrls } = JSON.parse(
await readFile('script/rest/utils/rest-api-overrides.json', 'utf8')
)
const operationRedirects = {}
console.log('\n➡ Updating REST API redirect exception list.\n')
Object.values(operationUrls).forEach((value) => {
const oldUrl = value.originalUrl.replace('/rest/reference', '/rest')
const anchor = oldUrl.split('#')[1]
const subcategory = value.subcategory
const redirectTo = subcategory
? `/rest/${value.category}/${subcategory}#${anchor}`
: `/rest/${value.category}#${anchor}`
operationRedirects[oldUrl] = redirectTo
})
const redirects = {
...operationRedirects,
...sectionUrls,
}
return redirects
}
async function decorate() {
console.log('\n🎄 Decorating the OpenAPI schema files in lib/rest/static/dereferenced.\n')
const dereferencedSchemas = {}
for (const filename of schemas) {
const schema = JSON.parse(await readFile(path.join(dereferencedPath, filename)))
const key = filename.replace('.deref.json', '')
dereferencedSchemas[key] = schema
}
const operationsEnabledForGitHubApps = {}
const clientSideRedirects = await getCategoryOverrideRedirects()
const skipCategory = [
'billing',
'code-scanning',
'codes-of-conduct',
'deploy-keys',
'emojis',
'gitignore',
'licenses',
'markdown',
'meta',
'oauth-authorizations',
'packages',
'pages',
'rate-limit',
'reactions',
'scim',
'search',
'secret-scanning',
]
for (const [schemaName, schema] of Object.entries(dereferencedSchemas)) {
try {
// get all of the operations for a particular version of the openapi
const operations = await getOperations(schema)
// process each operation, asynchronously rendering markdown and stuff
await Promise.all(operations.map((operation) => operation.process()))
// For each rest operation that doesn't have an override defined
// in script/rest/utils/rest-api-overrides.json,
// add a client-side redirect
operations.forEach((operation) => {
// A handful of operations don't have external docs properties
const externalDocs = operation.getExternalDocs()
if (!externalDocs) {
return
}
const oldUrl = `/rest${
externalDocs.url.replace('/rest/reference', '/rest').split('/rest')[1]
}`
if (!(oldUrl in clientSideRedirects)) {
// There are some operations that aren't nested in the sidebar
// For these, don't need to add a client-side redirect, the
// frontmatter redirect will handle it for us.
if (skipCategory.includes(operation.category)) {
return
}
const anchor = oldUrl.split('#')[1]
const subcategory = operation.subcategory
// If there is no subcategory, a new page with the same name as the
// category was created. That page name may change going forward.
const redirectTo = subcategory
? `/rest/${operation.category}/${subcategory}#${anchor}`
: `/rest/${operation.category}/${operation.category}#${anchor}`
clientSideRedirects[oldUrl] = redirectTo
}
// There are a lot of section headings that we'll want to redirect too,
// now that subcategories are on their own page. For example,
// /rest/reference/actions#artifacts should redirect to
// /rest/actions/artifacts
if (operation.subcategory) {
const sectionRedirectFrom = `/rest/${operation.category}#${operation.subcategory}`
const sectionRedirectTo = `/rest/${operation.category}/${operation.subcategory}`
if (!(sectionRedirectFrom in clientSideRedirects)) {
clientSideRedirects[sectionRedirectFrom] = sectionRedirectTo
}
}
})
const categories = [...new Set(operations.map((operation) => operation.category))].sort()
// Orders the operations by their category and subcategories.
// All operations must have a category, but operations don't need
// a subcategory. When no subcategory is present, the subcategory
// property is an empty string ('').
/*
Example:
{
[category]: {
'': {
"description": "",
"operations": []
},
[subcategory sorted alphabetically]: {
"description": "",
"operations": []
}
}
}
*/
const operationsByCategory = {}
categories.forEach((category) => {
operationsByCategory[category] = {}
const categoryOperations = operations.filter((operation) => operation.category === category)
categoryOperations
.filter((operation) => !operation.subcategory)
.map((operation) => (operation.subcategory = operation.category))
const subcategories = [
...new Set(categoryOperations.map((operation) => operation.subcategory)),
].sort()
// the first item should be the item that has no subcategory
// e.g., when the subcategory = category
const firstItemIndex = subcategories.indexOf(category)
if (firstItemIndex > -1) {
const firstItem = subcategories.splice(firstItemIndex, 1)[0]
subcategories.unshift(firstItem)
}
subcategories.forEach((subcategory) => {
operationsByCategory[category][subcategory] = {}
const subcategoryOperations = categoryOperations.filter(
(operation) => operation.subcategory === subcategory
)
operationsByCategory[category][subcategory] = subcategoryOperations
})
})
const filename = path.join(decoratedPath, `${schemaName}.json`).replace('.deref', '')
// write processed operations to disk
await writeFile(filename, JSON.stringify(operationsByCategory, null, 2))
console.log('Wrote', path.relative(process.cwd(), filename))
// Create the enabled-for-apps.json file used for
// https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps
operationsEnabledForGitHubApps[schemaName] = {}
for (const category of categories) {
const categoryOperations = operations.filter((operation) => operation.category === category)
// This is a collection of operations that have `enabledForGitHubApps = true`
// It's grouped by resource title to make rendering easier
operationsEnabledForGitHubApps[schemaName][category] = categoryOperations
.filter((operation) => operation.enabledForGitHubApps)
.map((operation) => ({
slug: operation.slug,
verb: operation.verb,
requestPath: operation.requestPath,
}))
}
} catch (error) {
console.error(error)
console.log(
"🐛 Whoops! It looks like the decorator script wasn't able to parse the dereferenced schema. A recent change may not yet be supported by the decorator. Please reach out in the #docs-engineering slack channel for help."
)
process.exit(1)
}
}
await writeFile(
path.join(appsStaticPath, 'enabled-for-apps.json'),
JSON.stringify(operationsEnabledForGitHubApps, null, 2)
)
console.log('Wrote', path.relative(process.cwd(), `${appsStaticPath}/enabled-for-apps.json`))
await writeFile(
'lib/redirects/static/client-side-rest-api-redirects.json',
JSON.stringify(clientSideRedirects, null, 2),
'utf8'
)
console.log(
'Wrote',
path.relative(process.cwd(), `lib/redirects/static/client-side-rest-api-redirects.json`)
)
}
async function validateInputParameters(schemas) {
// The `--versions` and `--decorate-only` options cannot be used
// with the `--include-deprecated` or `--include-unpublished` options
const numberOfOptions = Object.keys(program.opts()).length
if (numberOfOptions > 1 && (decorateOnly || versions)) {
console.log(
`🛑 You cannot use the versions and decorate-only options with any other options.\nThe decorate-only switch will decorate all dereferenced schemas files in the docs-internal repo.\nThis script doesn't support generating individual deprecated or unpublished schemas.\nPlease reach out to #docs-engineering if this is a use case that you need.`
)
process.exit(1)
}
// Validate individual versions provided
if (versions) {
versions.forEach((version) => {
if (
schemas.deprecated.includes(`${version}.deref.json`) ||
schemas.unpublished.includes(`${version}.deref.json`)
) {
console.log(
`🛑 This script doesn't support generating individual deprecated or unpublished schemas. Please reach out to #docs-engineering if this is a use case that you need.`
)
process.exit(1)
} else if (!schemas.currentReleases.includes(`${version}.deref.json`)) {
console.log(`🛑 The version (${version}) you specified is not valid.`)
process.exit(1)
}
})
}
}
async function getOpenApiSchemas(directory) {
const openAPIConfigs = await readdir(directory)
const unpublished = []
const deprecated = []
const currentReleases = []
for (const file of openAPIConfigs) {
const newFileName = `${path.basename(file, 'yaml')}deref.json`
const content = await readFile(path.join(directory, file), 'utf8')
const yamlContent = yaml.load(content)
if (!yamlContent.published) {
unpublished.push(newFileName)
}
if (yamlContent.deprecated) {
deprecated.push(newFileName)
}
if (!yamlContent.deprecated && yamlContent.published) {
currentReleases.push(newFileName)
}
}
return { currentReleases, unpublished, deprecated }
}
async function getCommandParameters() {
let includeParams = []
if (versions) {
includeParams = versions
}
if (includeUnpublished) {
includeParams.push('--include_unpublished')
}
if (includeDeprecated) {
includeParams.push('--include_deprecated')
}
return includeParams.join(' ')
}
async function getSchemas(allSchemas) {
if (decorateOnly) {
const files = await readdir(dereferencedPath)
return files
} else if (versions) {
return versions.map((elem) => `${elem}.deref.json`)
} else {
const schemas = allSchemas.currentReleases
if (includeUnpublished) {
schemas.push(...allSchemas.unpublished)
}
if (includeDeprecated) {
schemas.push(...allSchemas.deprecated)
}
return schemas
}
}