Skip to content

Commit deb617b

Browse files
authored
Merge pull request #18551 from github/move-product-list-to-frontmatter
Move products list to frontmatter
2 parents 51d451c + 1937a4d commit deb617b

15 files changed

Lines changed: 148 additions & 131 deletions

File tree

.github/workflows/send-prs-to-how-how-we-work-boards.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ jobs:
3535
3636
var column_id = 13445681;
3737
try {
38-
github.projects.createCard({
38+
await github.projects.createCard({
3939
column_id: column_id,
4040
content_id: context.payload.pull_request.id,
4141
content_type: "PullRequest"
4242
});
4343
} catch (error) {
44-
console.log(error);
44+
if (error.includes('Project already has the associated issue')) {
45+
return
46+
} else {
47+
console.log(error);
48+
}
4549
}

content/index.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,42 @@ featuredLinks:
1313
- /github/getting-started-with-github/managing-remote-repositories
1414
- /github/working-with-github-pages
1515
versions: '*'
16+
children:
17+
- github
18+
- admin
19+
- discussions
20+
- code-security
21+
- actions
22+
- packages
23+
- developers
24+
- rest
25+
- graphql
26+
- insights
27+
- communities
28+
- pages
29+
- education
30+
- desktop
31+
- early-access
32+
externalProducts:
33+
cli:
34+
id: cli
35+
name: GitHub CLI
36+
href: 'https://cli.github.com/manual'
37+
external: true
38+
atom:
39+
id: atom
40+
name: Atom
41+
href: 'https://atom.io/docs'
42+
external: true
43+
electron:
44+
id: electron
45+
name: Electron
46+
href: 'https://electronjs.org/docs'
47+
external: true
48+
codeql:
49+
id: codeql
50+
name: 'CodeQL'
51+
href: 'https://codeql.github.com/docs'
52+
external: true
1653
---
1754

data/products.yml

Lines changed: 0 additions & 18 deletions
This file was deleted.

includes/header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ <h4 class="text-mono f5 text-normal text-gray d-md-none">{% data ui.homepage.exp
2626
<div id="current-product" class="d-flex flex-items-center flex-justify-between" style="padding-top: 2px;">
2727
<!-- Product switcher - GitHub.com, Enterprise Server, etc -->
2828
<!-- 404 and 500 error layouts are not real pages so we need to hardcode the name for those -->
29-
{{ allProducts[currentProduct].name }}
29+
{{ productMap[currentProduct].name }}
3030
<svg class="arrow ml-md-1" width="14px" height="8px" viewBox="0 0 14 8" xml:space="preserve" fill="none" stroke="#1277eb"><path d="M1,1l6.2,6L13,1"></path></svg>
3131
</div>
3232
</summary>

lib/all-products.js

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,28 @@
11
const fs = require('fs')
22
const path = require('path')
3-
const slash = require('slash')
4-
const assert = require('assert')
5-
const { difference } = require('lodash')
6-
const yaml = require('js-yaml')
7-
const contentDir = path.join(process.cwd(), 'content')
83
const frontmatter = require('./read-frontmatter')
94
const getApplicableVersions = require('./get-applicable-versions')
105
const removeFPTFromPath = require('./remove-fpt-from-path')
116

12-
// the product order is determined by data/products.yml
13-
const productsFile = path.join(process.cwd(), 'data/products.yml')
14-
const productsYml = yaml.load(fs.readFileSync(productsFile, 'utf8'))
15-
const sortedProductIds = productsYml.productsInOrder
16-
17-
const contentProductIds = fs.readdirSync(contentDir, { withFileTypes: true })
18-
.map(entry => {
19-
// `fs.readdir` provides file entries based on `fs.lstat`, which doesn't
20-
// resolve symbolic links to their target file/directory. We need to take
21-
// an extra step here to resolve the Early Access symlinked directory.
22-
const { name } = entry
23-
if (entry.isSymbolicLink()) {
24-
entry = fs.statSync(path.join(contentDir, entry.name))
25-
entry.name = name
26-
}
27-
return entry
28-
})
29-
.filter(entry => entry.isDirectory())
30-
.map(entry => entry.name)
31-
32-
// require the content/<subdir> list to match the list in data/products.yml,
33-
// with the exception of content/early-access, which lives in a separate private repo
34-
const publicContentProductIds = contentProductIds.filter(id => id !== 'early-access')
35-
assert(difference(sortedProductIds, publicContentProductIds).length === 0)
36-
assert(difference(publicContentProductIds, sortedProductIds).length === 0)
7+
// Both internal and external products are specified in content/index.md
8+
const homepage = path.posix.join(process.cwd(), 'content/index.md')
9+
const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
10+
const productIds = data.children
11+
const externalProducts = data.externalProducts
3712

3813
const internalProducts = {}
3914

40-
// add optional early access content dir to sorted products list if present
41-
const earlyAccessId = contentProductIds.find(id => id === 'early-access')
42-
if (earlyAccessId) sortedProductIds.push(earlyAccessId)
43-
44-
sortedProductIds.forEach(productId => {
15+
productIds.forEach(productId => {
4516
const relPath = productId
46-
const dir = slash(path.join('content', relPath))
47-
const toc = slash(path.join(dir, 'index.md'))
17+
const dir = path.posix.join('content', relPath)
18+
19+
// Early Access may not exist in the current checkout
20+
if (!fs.existsSync(dir)) return
21+
22+
const toc = path.posix.join(dir, 'index.md')
4823
const { data } = frontmatter(fs.readFileSync(toc, 'utf8'))
4924
const applicableVersions = getApplicableVersions(data.versions, toc)
50-
const href = removeFPTFromPath(slash(path.join('/', applicableVersions[0], productId)))
25+
const href = removeFPTFromPath(path.posix.join('/', applicableVersions[0], productId))
5126

5227
internalProducts[productId] = {
5328
id: productId,
@@ -62,33 +37,9 @@ sortedProductIds.forEach(productId => {
6237
internalProducts[productId].versions = applicableVersions
6338
})
6439

65-
const externalProducts = {
66-
cli: {
67-
id: 'cli',
68-
name: 'GitHub CLI',
69-
href: 'https://cli.github.com/manual',
70-
external: true
71-
},
72-
atom: {
73-
id: 'atom',
74-
name: 'Atom',
75-
href: 'https://atom.io/docs',
76-
external: true
77-
},
78-
electron: {
79-
id: 'electron',
80-
name: 'Electron',
81-
href: 'https://electronjs.org/docs',
82-
external: true
83-
},
84-
codeql: {
85-
id: 'codeql',
86-
name: 'CodeQL',
87-
href: 'https://codeql.github.com/docs',
88-
external: true
89-
}
90-
}
40+
const productMap = Object.assign({}, internalProducts, externalProducts)
9141

92-
const allProducts = Object.assign({}, internalProducts, externalProducts)
93-
94-
module.exports = allProducts
42+
module.exports = {
43+
productIds,
44+
productMap
45+
}

lib/frontmatter.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,46 @@ const schema = {
126126
name: { type: 'string' },
127127
URL: { type: 'string' }
128128
}
129+
},
130+
// Child links specified on any TOC page
131+
children: {
132+
type: 'array'
133+
},
134+
// External products specified on the homepage
135+
externalProducts: {
136+
type: 'object',
137+
properties: {
138+
cli: {
139+
type: 'object',
140+
required: true,
141+
properties: {
142+
id: { type: 'string', required: true },
143+
name: { type: 'string', required: true },
144+
href: { type: 'string', format: 'url', required: true },
145+
external: { type: 'boolean', required: true }
146+
}
147+
},
148+
atom: {
149+
type: 'object',
150+
required: true,
151+
properties: {
152+
id: { type: 'string', required: true },
153+
name: { type: 'string', required: true },
154+
href: { type: 'string', format: 'url', required: true },
155+
external: { type: 'boolean', required: true }
156+
}
157+
},
158+
electron: {
159+
type: 'object',
160+
required: true,
161+
properties: {
162+
id: { type: 'string', required: true },
163+
name: { type: 'string', required: true },
164+
href: { type: 'string', format: 'url', required: true },
165+
external: { type: 'boolean', required: true }
166+
}
167+
}
168+
}
129169
}
130170
}
131171
}

lib/get-toc-items.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
const productTOCs = Object.values(require('./all-products'))
1+
const { productMap } = require('./all-products')
2+
const productTOCs = Object.values(productMap)
23
.filter(product => !product.external)
34
.map(product => product.toc.replace('content/', ''))
45

lib/page.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const Permalink = require('./permalink')
1212
const languages = require('./languages')
1313
const renderContent = require('./render-content')
1414
const { renderReact } = require('./react/engine')
15-
const products = require('./all-products')
15+
const { productMap } = require('./all-products')
1616
const slash = require('slash')
1717
const statsd = require('./statsd')
1818
const readFileContents = require('./read-file-contents')
@@ -76,8 +76,11 @@ class Page {
7676
this.introLinks.rawOverview = this.introLinks.overview
7777
}
7878

79+
// Get array of versions that the page is available in for fast lookup
80+
this.applicableVersions = getApplicableVersions(this.versions, this.fullPath)
81+
7982
// a page should only be available in versions that its parent product is available in
80-
const versionsParentProductIsNotAvailableIn = getApplicableVersions(this.versions, this.fullPath)
83+
const versionsParentProductIsNotAvailableIn = this.applicableVersions
8184
// only the homepage will not have this.parentProduct
8285
.filter(availableVersion => this.parentProduct && !this.parentProduct.versions.includes(availableVersion))
8386

@@ -124,7 +127,7 @@ class Page {
124127
// make sure the ID is valid
125128
if (process.env.NODE_ENV !== 'test') {
126129
assert(
127-
Object.keys(products).includes(id),
130+
Object.keys(productMap).includes(id),
128131
`page ${this.fullPath} has an invalid product ID: ${id}`
129132
)
130133
}
@@ -133,7 +136,7 @@ class Page {
133136
}
134137

135138
get parentProduct () {
136-
return products[this.parentProductId]
139+
return productMap[this.parentProductId]
137140
}
138141

139142
async renderTitle (context, opts = {}) {

lib/path-utils.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const slash = require('slash')
22
const path = require('path')
33
const patterns = require('./patterns')
44
const { latest } = require('./enterprise-server-releases')
5-
const allProducts = require('./all-products')
5+
const { productIds } = require('./all-products')
66
const allVersions = require('./all-versions')
77
const supportedVersions = new Set(Object.keys(allVersions))
88
const nonEnterpriseDefaultVersion = require('./non-enterprise-default-version')
@@ -43,7 +43,7 @@ function getVersionStringFromPath (href) {
4343
const versionFromPath = href.split('/')[1]
4444

4545
// If the first segment is a supported product, assume this is FPT
46-
if (allProducts[versionFromPath]) {
46+
if (productIds.includes(versionFromPath)) {
4747
return nonEnterpriseDefaultVersion
4848
}
4949

@@ -85,7 +85,7 @@ function getProductStringFromPath (href) {
8585

8686
if (pathParts.includes('early-access')) return 'early-access'
8787

88-
return allProducts[pathParts[2]]
88+
return productIds.includes(pathParts[2])
8989
? pathParts[2]
9090
: pathParts[1]
9191
}
@@ -99,27 +99,19 @@ function getCategoryStringFromPath (href) {
9999

100100
if (pathParts.includes('early-access')) return null
101101

102-
const productIndex = allProducts[pathParts[2]]
102+
const productIndex = productIds.includes(pathParts[2])
103103
? 2
104104
: 1
105105

106106
return pathParts[productIndex + 1]
107107
}
108108

109-
// Return the corresponding object for the product segment in a path
110-
function getProductObjectFromPath (href) {
111-
const productFromPath = getProductStringFromPath(href)
112-
113-
return allProducts[productFromPath]
114-
}
115-
116109
module.exports = {
117110
getPathWithLanguage,
118111
getPathWithoutLanguage,
119112
getPathWithoutVersion,
120113
getVersionStringFromPath,
121114
getVersionObjectFromPath,
122115
getProductStringFromPath,
123-
getCategoryStringFromPath,
124-
getProductObjectFromPath
116+
getCategoryStringFromPath
125117
}

lib/site-tree.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const path = require('path')
2-
const products = Object.values(require('./all-products'))
2+
const { productMap } = require('./all-products')
33
const languageCodes = Object.keys(require('./languages'))
44
const addTitlesToTree = require('./site-tree-titles')
55
const allVersions = Object.keys(require('./all-versions'))
@@ -26,7 +26,7 @@ module.exports = async function buildSiteTree (pageMap, site, redirects) {
2626
siteTree[languageCode][version] = {}
2727
const productTree = {}
2828

29-
products.forEach(item => {
29+
Object.values(productMap).forEach(item => {
3030
const product = { title: item.name }
3131

3232
// return early if the product has external docs, like Atom

0 commit comments

Comments
 (0)