Skip to content

Commit e17b966

Browse files
Merge branch 'main' into customize-nav-ordering
2 parents 396e479 + fb6a78e commit e17b966

31 files changed

+1878
-100
lines changed

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ To define the markdown schema this project uses a typescript based schema known
3737

3838
All commands are run from the root of the project, from a terminal:
3939

40-
| Command | Action |
41-
| :------------------------ | :----------------------------------------------- |
42-
| `npm install` | Installs dependencies |
43-
| `npm run dev` | Starts local dev server at `localhost:4321` |
44-
| `npm run build` | Build your production site to `./dist/` |
45-
| `npm run preview` | Preview your build locally, before deploying |
46-
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
47-
| `npm run astro -- --help` | Get help using the Astro CLI |
48-
| `npm run build:cli` | Create a JS build of the documentation core CLI |
49-
| `npm run build:cli:watch` | Run the CLI builder in watch mode |
40+
| Command | Action |
41+
| :------------------------ | :-----------------------------------------------------------------|
42+
| `npm install` | Installs dependencies |
43+
| `npm run dev` | Starts local dev server at `localhost:4321` |
44+
| `npm run build` | Build your production site to `./dist/` |
45+
| `npm run preview` | Preview your build locally, before deploying |
46+
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
47+
| `npm run astro -- --help` | Get help using the Astro CLI |
48+
| `npm run build:cli` | Create a JS build of the documentation core CLI |
49+
| `npm run build:cli:watch` | Run the CLI builder in watch mode |
50+
| `npm run build:props` | Create a json file of your TsDoc compatible in code documentation |

astro.config.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
import { defineConfig } from 'astro/config';
33
import react from '@astrojs/react';
44

5+
import node from '@astrojs/node';
6+
57
// https://astro.build/config
68
export default defineConfig({
79
integrations: [react()],
10+
811
vite: {
912
ssr: {
1013
noExternal: ["@patternfly/*", "react-dropzone"],
@@ -14,5 +17,9 @@ export default defineConfig({
1417
allow: ['./']
1518
}
1619
}
17-
}
20+
},
21+
22+
adapter: node({
23+
mode: 'standalone'
24+
})
1825
});
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { writeFile } from 'fs/promises'
2+
import { glob } from 'glob'
3+
import { buildPropsData } from '../buildPropsData'
4+
import { getConfig } from '../getConfig'
5+
import { tsDocgen } from '../tsDocGen'
6+
7+
jest.mock('fs/promises')
8+
jest.mock('glob')
9+
jest.mock('../getConfig')
10+
jest.mock('../tsDocGen')
11+
12+
// suppress console logs so that it doesn't clutter the test output
13+
jest.spyOn(console, 'log').mockImplementation(() => {})
14+
15+
const validConfigResponse = {
16+
propsGlobs: [
17+
{
18+
include: ['**/include/files/*', '**/include/other/files/*'],
19+
exclude: ['**/exclude/files/*'],
20+
},
21+
{ include: ['**/one/more/include/*'], exclude: [] },
22+
],
23+
}
24+
25+
const sharedPropData = [
26+
{
27+
name: 'firstProp',
28+
type: 'firstPropType',
29+
description: 'this is the first prop',
30+
required: false,
31+
defaultValue: 'firstDefaultValue',
32+
hide: false,
33+
},
34+
{
35+
name: 'secondProp',
36+
type: 'secondPropType',
37+
description: 'this is the second prop',
38+
required: false,
39+
defaultValue: 'secondDefaultValue',
40+
hide: false,
41+
},
42+
]
43+
44+
const validTsDocGenResponseOne = [
45+
{
46+
name: 'ComponentOne',
47+
description: 'This is the first component',
48+
props: sharedPropData,
49+
},
50+
{
51+
name: 'ComponentTwo',
52+
description: 'This is the second component',
53+
props: sharedPropData,
54+
},
55+
]
56+
57+
const validTsDocGenResponseTwo = [
58+
{
59+
name: 'ComponentThree',
60+
description: 'This is the third component',
61+
props: sharedPropData,
62+
},
63+
]
64+
65+
const propsData = {
66+
ComponentOne: {
67+
name: 'ComponentOne',
68+
description: 'This is the first component',
69+
props: sharedPropData,
70+
},
71+
ComponentTwo: {
72+
name: 'ComponentTwo',
73+
description: 'This is the second component',
74+
props: sharedPropData,
75+
},
76+
ComponentThree: {
77+
name: 'ComponentThree',
78+
description: 'This is the third component',
79+
props: sharedPropData,
80+
},
81+
}
82+
83+
it('should call getConfig with the passed config file location', async () => {
84+
await buildPropsData('/root/', '/astro/', '/config', false)
85+
86+
expect(getConfig).toHaveBeenCalledWith('/config')
87+
})
88+
89+
it('should not proceed if config is not found', async () => {
90+
;(getConfig as jest.Mock).mockResolvedValue(undefined)
91+
92+
await buildPropsData('/root/', '/astro/', '/config', false)
93+
94+
expect(writeFile).not.toHaveBeenCalled()
95+
})
96+
97+
it('should send an error to the console if the config file does not have a propsGlobs entry', async () => {
98+
;(getConfig as jest.Mock).mockResolvedValue({ foo: 'bar' })
99+
100+
const mockConsoleError = jest.fn()
101+
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)
102+
103+
await buildPropsData('/root/', '/astro/', '/config', false)
104+
105+
expect(mockConsoleError).toHaveBeenCalledWith('No props data found in config')
106+
expect(writeFile).not.toHaveBeenCalled()
107+
})
108+
109+
it('should call glob with the propGlobs in the config file and the cwd set to the root dir', async () => {
110+
;(getConfig as jest.Mock).mockResolvedValue(validConfigResponse)
111+
;(glob as unknown as jest.Mock).mockResolvedValue(['files/one', 'files/two'])
112+
;(tsDocgen as jest.Mock).mockResolvedValue(validTsDocGenResponseOne)
113+
114+
await buildPropsData('/root/', '/astro/', '/config', false)
115+
116+
expect(glob).toHaveBeenNthCalledWith(
117+
1,
118+
['**/include/files/*', '**/include/other/files/*'],
119+
{ cwd: '/root/', ignore: ['**/exclude/files/*'] },
120+
)
121+
expect(glob).toHaveBeenNthCalledWith(2, ['**/one/more/include/*'], {
122+
cwd: '/root/',
123+
ignore: [],
124+
})
125+
expect(glob).toHaveBeenCalledTimes(2)
126+
})
127+
128+
it('should call tsDocGen with each file that glob returns', async () => {
129+
;(getConfig as jest.Mock).mockResolvedValue(validConfigResponse)
130+
;(glob as unknown as jest.Mock).mockResolvedValueOnce([
131+
'files/one',
132+
'files/two',
133+
])
134+
;(glob as unknown as jest.Mock).mockResolvedValueOnce([
135+
'files/three',
136+
'files/four',
137+
])
138+
;(tsDocgen as jest.Mock).mockReset()
139+
;(tsDocgen as jest.Mock).mockResolvedValue(validTsDocGenResponseOne)
140+
141+
await buildPropsData('/root/', '/astro/', '/config', false)
142+
143+
expect(tsDocgen).toHaveBeenNthCalledWith(1, 'files/one')
144+
expect(tsDocgen).toHaveBeenNthCalledWith(2, 'files/two')
145+
expect(tsDocgen).toHaveBeenNthCalledWith(3, 'files/three')
146+
expect(tsDocgen).toHaveBeenNthCalledWith(4, 'files/four')
147+
expect(tsDocgen).toHaveBeenCalledTimes(4)
148+
})
149+
150+
it('should call writeFile with the returned prop data in JSON form', async () => {
151+
;(getConfig as jest.Mock).mockResolvedValue(validConfigResponse)
152+
;(glob as unknown as jest.Mock).mockResolvedValueOnce(['files/one'])
153+
;(glob as unknown as jest.Mock).mockResolvedValueOnce(['files/two'])
154+
;(tsDocgen as jest.Mock).mockResolvedValueOnce(validTsDocGenResponseOne)
155+
;(tsDocgen as jest.Mock).mockResolvedValueOnce(validTsDocGenResponseTwo)
156+
;(writeFile as jest.Mock).mockReset()
157+
158+
await buildPropsData('/root/', '/astro/', '/config', false)
159+
160+
expect(writeFile).toHaveBeenCalledWith(
161+
'/astro/dist/props.json',
162+
JSON.stringify(propsData),
163+
)
164+
})

cli/buildPropsData.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* eslint-disable no-console */
2+
3+
import { glob } from 'glob'
4+
import { writeFile } from 'fs/promises'
5+
import { join } from 'path'
6+
7+
import { tsDocgen } from './tsDocGen.js'
8+
import { getConfig, PropsGlobs } from './getConfig.js'
9+
10+
interface Prop {
11+
name: string
12+
type: string
13+
description?: string
14+
required?: boolean
15+
defaultValue?: string
16+
hide?: boolean
17+
}
18+
interface TsDoc {
19+
name: string
20+
description: string
21+
props: Prop[]
22+
}
23+
interface PropsData {
24+
[key: string]: TsDoc
25+
}
26+
27+
// Build unique names for components with a "variant" extension
28+
type TsDocVariants = 'next' | 'deprecated' | undefined
29+
function getTsDocName(name: string, variant: TsDocVariants) {
30+
return `${name}${variant ? `-${variant}` : ''}`
31+
}
32+
33+
function getTsDocNameVariant(source: string) {
34+
if (source.includes('next')) {
35+
return 'next'
36+
}
37+
38+
if (source.includes('deprecated')) {
39+
return 'deprecated'
40+
}
41+
}
42+
43+
async function getFiles(root: string, globs: PropsGlobs[]) {
44+
const files = await Promise.all(
45+
globs.map(async ({ include, exclude }) => {
46+
const files = await glob(include, { cwd: root, ignore: exclude })
47+
return files
48+
}),
49+
)
50+
return files.flat()
51+
}
52+
53+
async function getPropsData(files: string[], verbose: boolean) {
54+
const perFilePropsData = await Promise.all(
55+
files.map(async (file) => {
56+
if (verbose) {
57+
console.log(`Parsing props from ${file}`)
58+
}
59+
60+
const props = (await tsDocgen(file)) as TsDoc[]
61+
62+
const tsDocs = props.reduce((acc, { name, description, props }) => {
63+
const key = getTsDocName(name, getTsDocNameVariant(file))
64+
return { ...acc, [key]: { name, description, props } }
65+
}, {} as PropsData)
66+
67+
return tsDocs
68+
}),
69+
)
70+
71+
const combinedPropsData = perFilePropsData.reduce((acc, props) => {
72+
Object.keys(props).forEach((key) => {
73+
const propsData = props[key]
74+
if (acc[key]) {
75+
acc[key].props = [...acc[key].props, ...propsData.props]
76+
} else {
77+
acc[key] = propsData
78+
}
79+
})
80+
return acc
81+
}, {})
82+
83+
return combinedPropsData
84+
}
85+
86+
export async function buildPropsData(
87+
rootDir: string,
88+
astroRoot: string,
89+
configFile: string,
90+
verbose: boolean,
91+
) {
92+
const config = await getConfig(configFile)
93+
if (!config) {
94+
return
95+
}
96+
97+
const { propsGlobs } = config
98+
if (!propsGlobs) {
99+
console.error('No props data found in config')
100+
return
101+
}
102+
103+
const files = await getFiles(rootDir, propsGlobs)
104+
if (verbose) {
105+
console.log(`Found ${files.length} files to parse`)
106+
}
107+
108+
const propsData = await getPropsData(files, verbose)
109+
110+
const propsFile = join(astroRoot, 'dist', 'props.json')
111+
112+
await writeFile(propsFile, JSON.stringify(propsData))
113+
}

0 commit comments

Comments
 (0)