Skip to content

Commit 7e44be4

Browse files
fix(API): refactor API data compilation to be done at build time
1 parent 55bb455 commit 7e44be4

File tree

24 files changed

+1735
-9
lines changed

24 files changed

+1735
-9
lines changed

.gitignore

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@ pnpm-debug.log*
2828

2929
.eslintcache
3030

31-
## Ignore content.ts
32-
src/content.ts
31+
## Ignore generated files
32+
src/content.ts
33+
src/apiIndex.json
34+
textContent/*.mdx
35+
36+
coverage/

cli/__tests__/convertToMDX.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { convertToMDX } from '../convertToMDX.ts'
55
jest.mock('fs/promises', () => ({
66
readFile: jest.fn(),
77
writeFile: jest.fn(),
8-
access: jest.fn().mockResolvedValue(undefined), // Mock access to always resolve (file exists)
98
}))
109

1110
jest.mock('glob', () => ({
1211
glob: jest.fn(),
1312
}))
1413

14+
jest.mock('../fileExists', () => ({
15+
fileExists: jest.fn().mockResolvedValue(true), // Mock fileExists to always return true (file exists)
16+
}))
17+
1518
beforeEach(() => {
1619
jest.clearAllMocks()
1720
})

cli/__tests__/fileExists.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { access } from 'fs/promises'
2+
import { fileExists } from '../fileExists'
3+
4+
jest.mock('fs/promises', () => ({
5+
access: jest.fn(),
6+
}))
7+
8+
it('returns true when file exists', async () => {
9+
;(access as jest.Mock).mockResolvedValue(undefined)
10+
11+
const result = await fileExists('/path/to/existing/file.txt')
12+
13+
expect(result).toBe(true)
14+
expect(access).toHaveBeenCalledWith('/path/to/existing/file.txt')
15+
})
16+
17+
it('returns false when file does not exist', async () => {
18+
;(access as jest.Mock).mockRejectedValue(
19+
new Error('ENOENT: no such file or directory'),
20+
)
21+
22+
const result = await fileExists('/path/to/nonexistent/file.txt')
23+
24+
expect(result).toBe(false)
25+
expect(access).toHaveBeenCalledWith('/path/to/nonexistent/file.txt')
26+
})
27+
28+
it('returns false when access throws any error', async () => {
29+
;(access as jest.Mock).mockRejectedValue(new Error('Permission denied'))
30+
31+
const result = await fileExists('/path/to/file.txt')
32+
33+
expect(result).toBe(false)
34+
expect(access).toHaveBeenCalledWith('/path/to/file.txt')
35+
})

cli/cli.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { symLinkConfig } from './symLinkConfig.js'
1212
import { buildPropsData } from './buildPropsData.js'
1313
import { hasFile } from './hasFile.js'
1414
import { convertToMDX } from './convertToMDX.js'
15-
import { mkdir } from 'fs/promises'
15+
import { mkdir, copyFile } from 'fs/promises'
16+
import { fileExists } from './fileExists.js'
1617

1718
const currentDir = process.cwd()
1819
const config = await getConfig(`${currentDir}/pf-docs.config.mjs`)
@@ -86,6 +87,26 @@ async function transformMDContentToMDX() {
8687
}
8788
}
8889

90+
async function initializeApiIndex() {
91+
const templateIndexPath = join(astroRoot, 'cli', 'templates', 'apiIndex.json')
92+
const targetIndexPath = join(astroRoot, 'src', 'apiIndex.json')
93+
94+
const indexExists = await fileExists(targetIndexPath)
95+
96+
// early return if the file exists from a previous build
97+
if (indexExists) {
98+
console.log('apiIndex.json already exists, skipping initialization')
99+
return
100+
}
101+
102+
try {
103+
await copyFile(templateIndexPath, targetIndexPath)
104+
console.log('Initialized apiIndex.json')
105+
} catch (e: any) {
106+
console.error('Error copying apiIndex.json template:', e)
107+
}
108+
}
109+
89110
async function buildProject(): Promise<DocsConfig | undefined> {
90111
await updateContent(program)
91112
await generateProps(program, true)
@@ -103,6 +124,7 @@ async function buildProject(): Promise<DocsConfig | undefined> {
103124
return config
104125
}
105126

127+
await initializeApiIndex()
106128
await transformMDContentToMDX()
107129

108130
build({
@@ -172,6 +194,7 @@ program.command('init').action(async () => {
172194

173195
program.command('start').action(async () => {
174196
await updateContent(program)
197+
await initializeApiIndex()
175198

176199
// if a props file hasn't been generated yet, but the consumer has propsData, it will cause a runtime error so to
177200
// prevent that we're just creating a props file regardless of what they say if one doesn't exist yet

cli/convertToMDX.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { readFile, writeFile, access } from 'fs/promises'
1+
import { readFile, writeFile } from 'fs/promises'
22
import { glob } from 'glob'
33
import path from 'path'
4+
import { fileExists } from './fileExists.js'
45

56
function handleTsExamples(content: string): string {
67
//regex link: https://regexr.com/8f0bu
@@ -57,10 +58,6 @@ function convertCommentsToMDX(content: string): string {
5758
)
5859
}
5960

60-
async function fileExists(file: string): Promise<boolean> {
61-
return access(file).then(() => true).catch(() => false)
62-
}
63-
6461
async function processFile(file: string): Promise<void> {
6562
const exists = await fileExists(file)
6663

cli/createCollectionContent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export async function createCollectionContent(
8080
verboseModeLog('repoRootDir: ', repoRootDir, '\n')
8181

8282
const contentWithAbsolutePaths = content.map((contentEntry) => {
83+
const version = contentEntry.version || 'v6'
84+
8385
if (contentEntry.base) {
8486
const absoluteBase = resolve(configDir, contentEntry.base)
8587

@@ -89,6 +91,7 @@ export async function createCollectionContent(
8991
return {
9092
...contentEntry,
9193
base: absoluteBase,
94+
version
9295
}
9396
}
9497

@@ -103,6 +106,7 @@ export async function createCollectionContent(
103106
return {
104107
...contentEntry,
105108
base: null,
109+
version
106110
}
107111
}
108112

@@ -116,6 +120,7 @@ export async function createCollectionContent(
116120
return {
117121
base: packagePath,
118122
...contentEntry,
123+
version
119124
}
120125
})
121126

cli/fileExists.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { access } from 'fs/promises'
2+
3+
export async function fileExists(file: string): Promise<boolean> {
4+
return access(file)
5+
.then(() => true)
6+
.catch(() => false)
7+
}

cli/getConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
export interface CollectionDefinition {
33
base?: string
44
packageName?: string
5+
version?: string
56
pattern: string
67
name: string
78
}

cli/templates/apiIndex.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"versions": [],
3+
"sections": {},
4+
"pages": {},
5+
"tabs": {}
6+
}

src/pages/api/[version].ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { APIRoute } from 'astro'
2+
import { createJsonResponse, createIndexKey } from '../../utils/apiHelpers'
3+
import { sections as sectionsData } from '../../apiIndex.json'
4+
5+
export const prerender = false
6+
7+
export const GET: APIRoute = async ({ params }) => {
8+
const { version } = params
9+
10+
if (!version) {
11+
return createJsonResponse(
12+
{ error: 'Version parameter is required' },
13+
400,
14+
)
15+
}
16+
17+
const key = createIndexKey(version)
18+
const sections = sectionsData[key as keyof typeof sectionsData]
19+
20+
if (!sections) {
21+
return createJsonResponse({ error: `Version '${version}' not found` }, 404)
22+
}
23+
24+
return createJsonResponse(sections)
25+
}

0 commit comments

Comments
 (0)