Skip to content

Commit 48c71d9

Browse files
author
Peter Bengtsson
authored
Port product-groups.js to TypeScript (#51364)
1 parent b894f1f commit 48c71d9

File tree

5 files changed

+118
-16
lines changed

5 files changed

+118
-16
lines changed

src/frame/middleware/context/product-groups.js renamed to src/frame/middleware/context/product-groups.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { getProductGroups } from '#src/products/lib/get-product-groups.js'
2-
import warmServer from '#src/frame/lib/warm-server.js'
3-
import { languageKeys } from '#src/languages/lib/languages.js'
4-
import { allVersionKeys } from '#src/versions/lib/all-versions.js'
1+
import type { Response, NextFunction } from 'express'
52

6-
const isHomepage = (path) => {
3+
import type { ExtendedRequest } from '@/types'
4+
import { getProductGroups } from '@/products/lib/get-product-groups'
5+
import warmServer from '@/frame/lib/warm-server.js'
6+
import { languageKeys } from '@/languages/lib/languages.js'
7+
import { allVersionKeys } from '@/versions/lib/all-versions.js'
8+
9+
const isHomepage = (path: string) => {
710
const split = path.split('/')
811
// E.g. `/foo` but not `foo/bar` or `foo/`
912
if (split.length === 2 && split[1] && !split[0]) {
@@ -17,7 +20,14 @@ const isHomepage = (path) => {
1720
return false
1821
}
1922

20-
export default async function productGroups(req, res, next) {
23+
export default async function productGroups(
24+
req: ExtendedRequest,
25+
res: Response,
26+
next: NextFunction,
27+
) {
28+
if (!req.context) throw new Error('request is not contextualized')
29+
if (!req.pagePath) throw new Error('pagePath is not set on request')
30+
if (!req.language) throw new Error('language is not set on request')
2131
// It's important to use `req.pathPage` instead of `req.path` because
2232
// the request could be the client-side routing from Next where the URL
2333
// might be something like `/_next/data/foo/bar.json` which is translated,
@@ -31,7 +41,7 @@ export default async function productGroups(req, res, next) {
3141
// known versions. Because if it's not valid, any possible
3242
// use of `{% ifversion ... %}` in Liquid, will throw an error.
3343
if (isHomepage(req.pagePath) && req.context.currentVersionObj) {
34-
const { pages } = await warmServer()
44+
const { pages } = await warmServer([])
3545
req.context.productGroups = await getProductGroups(pages, req.language, req.context)
3646
}
3747

src/frame/middleware/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import glossaries from './context/glossaries'
4747
import renderProductName from './context/render-product-name'
4848
import features from '@/versions/middleware/features.js'
4949
import productExamples from './context/product-examples'
50-
import productGroups from './context/product-groups.js'
50+
import productGroups from './context/product-groups'
5151
import featuredLinks from '@/landings/middleware/featured-links.js'
5252
import learningTrack from '@/learning-track/middleware/learning-track.js'
5353
import next from './next.js'

src/products/lib/all-products.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { Product } from '@/types'
1+
import type { PageFrontmatter, Product } from '@/types'
22

3-
export const { data }: Record<string, any>
3+
export const data: PageFrontmatter
44

55
export const productIds: string[]
66

src/products/lib/get-product-groups.js renamed to src/products/lib/get-product-groups.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import path from 'path'
22

3+
import type { Page, ProductGroup, ProductGroupChild, Context } from '@/types'
34
import { productMap, data } from './all-products.js'
4-
import { renderContentWithFallback } from '#src/languages/lib/render-with-fallback.js'
5-
import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js'
5+
import { renderContentWithFallback } from '@/languages/lib/render-with-fallback.js'
6+
import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path.js'
67

7-
async function getPage(id, lang, pageMap, context) {
8+
type PageMap = Record<string, Page>
9+
10+
async function getPage(
11+
id: string,
12+
lang: string,
13+
pageMap: PageMap,
14+
context: Context,
15+
): Promise<ProductGroupChild | undefined> {
816
const productId = id.split('/')[0]
917
const product = productMap[productId]
18+
1019
const external = product.external || false // undefined becomes false
1120

1221
// The href depends. Initially all we have is an `id` which might be
@@ -26,6 +35,8 @@ async function getPage(id, lang, pageMap, context) {
2635

2736
let name = product.name
2837

38+
if (!context.currentVersion) throw new Error('context.currentVersion is not set')
39+
2940
if (!external) {
3041
// First we have to find it as a page object based on its ID.
3142
href = removeFPTFromPath(path.posix.join('/', lang, context.currentVersion, id))
@@ -34,6 +45,7 @@ async function getPage(id, lang, pageMap, context) {
3445
// fall back it its default version, which is `product.versions[0]`.
3546
// For example, you're on `/en/enterprise-server@3.1` and you're
3647
// but a `/foo/bar` is only available in `enterprise-cloud@latest`.
48+
if (!product.versions) throw new Error(`Product ${productId} has no versions`)
3749
href = removeFPTFromPath(path.posix.join('/', lang, product.versions[0], id))
3850
}
3951
const page = pageMap[href]
@@ -74,17 +86,21 @@ async function getPage(id, lang, pageMap, context) {
7486
}
7587
}
7688

77-
export async function getProductGroups(pageMap, lang, context) {
89+
export async function getProductGroups(
90+
pageMap: PageMap,
91+
lang: string,
92+
context: Context,
93+
): Promise<ProductGroup[]> {
7894
return await Promise.all(
79-
data.childGroups.map(async (group) => {
95+
data.childGroups!.map(async (group) => {
8096
return {
8197
name: group.name,
8298
icon: group.icon || null,
8399
octicon: group.octicon || null,
84100
// Typically the children are product IDs, but we support deeper page paths too
85101
children: (
86102
await Promise.all(group.children.map((id) => getPage(id, lang, pageMap, context)))
87-
).filter(Boolean),
103+
).filter(Boolean) as ProductGroupChild[],
88104
}
89105
}),
90106
)

src/types.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,66 @@ export type ExtendedRequest = Request & {
1414
// Add more properties here as needed
1515
}
1616

17+
// TODO: Make this type from inference using AJV based on the schema.
18+
// For now, it's based on `schema` in frame/lib/frontmatter.js
19+
export type PageFrontmatter = {
20+
title: string
21+
versions: FrontmatterVersions
22+
shortTitle?: string
23+
intro?: string
24+
product?: string
25+
permissions?: string
26+
showMiniToc?: boolean
27+
miniTocMaxHeadingLevel?: number
28+
mapTopic?: boolean
29+
hidden?: boolean
30+
noEarlyAccessBanner?: boolean
31+
earlyAccessToc?: string
32+
layout?: string | boolean
33+
redirect_from?: string[]
34+
allowTitleToDifferFromFilename?: boolean
35+
introLinks?: object
36+
authors?: string[]
37+
examples_source?: string
38+
effectiveDate?: string
39+
40+
featuredLinks?: {
41+
gettingStarted?: string[]
42+
startHere?: string[]
43+
guideCards?: string[]
44+
popular?: string[]
45+
popularHeading?: string
46+
videos?: {
47+
title: string
48+
href: string
49+
}[]
50+
videoHeadings?: string
51+
}[]
52+
changelog?: ChangeLog
53+
type?: string
54+
topics?: string[]
55+
includeGuides?: string[]
56+
learningTracks?: string[]
57+
beta_product?: boolean
58+
product_video?: boolean
59+
product_video_transcript?: string
60+
interactive?: boolean
61+
communityRedirect?: {
62+
name: string
63+
href: string
64+
}
65+
defaultPlatform?: 'mac' | 'windows' | 'linux'
66+
defaultTool?: string
67+
childGroups?: ChildGroup[]
68+
}
69+
70+
export type ChildGroup = {
71+
name: string
72+
octicon: string
73+
children: string[]
74+
icon?: string
75+
}
76+
1777
export type Product = {
1878
id: string
1979
name: string
@@ -23,6 +83,7 @@ export type Product = {
2383
wip?: boolean
2484
hidden?: boolean
2585
versions?: string[]
86+
external?: boolean
2687
}
2788

2889
type ProductMap = {
@@ -95,6 +156,21 @@ export type Context = {
95156
currentProductName?: string
96157
productCommunityExamples?: ProductExample[]
97158
productUserExamples?: ProductExample[]
159+
productGroups?: ProductGroup[]
160+
}
161+
162+
export type ProductGroup = {
163+
name: string
164+
icon: string | null
165+
octicon: string | null
166+
children: ProductGroupChild[]
167+
}
168+
169+
export type ProductGroupChild = {
170+
id: string
171+
name: string
172+
href: string
173+
external: boolean
98174
}
99175

100176
export type Glossary = {

0 commit comments

Comments
 (0)