Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ jobs:
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm run build:cli
- run: npm test
- run: npm run lint
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ jobs:
registry-url: https://registry.npmjs.org/
- name: Build dist
run: npm build
- name: Build CLI
run: npm run build:cli
- name: Release to NPM
run: npx semantic-release
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__tests__
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,38 @@

Patternfly documentation core contains the base packages needed to build and release the PatternFly org website.

## Development
## Consuming this repo as a package

### Setup

Using this package for your documentation is accomplished in just a few simple steps:

1. Run `npx patternfly-doc-core@latest setup` from the root of your repo. This will:
- add the documentation core as a dependency in your package
- add the relevant scripts for using the documentation core to your package scripts
- create the configuration file for customizing the documentation core
1. Install the documentation core using your projects dependency manager, e.g. `npm install` or `yarn install`
1. Run the initialization script using your script runner, e.g. `npm run init:docs` or `yarn init:docs`
- this will update a Vite config in the documentation so that it can access the files in your repo when running the development server
1. Edit the `pf-docs.config.mjs` file in your project root to point the documentation core to your documentation files

### Use

Once setup is complete you can start the dev server with the `start` script, and create production builds using the `build:docs` script!

## Running this repo directly

### Development

The website is built using [Astro](https://astro.build). Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.

The `src/components/` folder contains Astro and React components that can be used to build the websites pages.
The `src/components/` folder contains Astro and React components that can be used to build the websites pages.

Any static assets, like images, can be placed in the `public/` directory.

To define the markdown schema this project uses a typescript based schema known as [Zod](https://zod.dev). Details of how this is integratred into Astro can be found in Astros documentation on [content creation using Zod](https://docs.astro.build/en/guides/content-collections/#defining-datatypes-with-zod).

## 🧞 Commands
### 🧞 Commands

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

Expand All @@ -24,3 +45,5 @@ All commands are run from the root of the project, from a terminal:
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
| `npm run build:cli` | Create a JS build of the documentation core CLI |
| `npm run build:cli:watch` | Run the CLI builder in watch mode |
5 changes: 5 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export default defineConfig({
vite: {
ssr: {
noExternal: ["@patternfly/*", "react-dropzone"],
},
server: {
fs: {
allow: ['./']
}
}
}
});
85 changes: 85 additions & 0 deletions cli/__tests__/createCollectionContent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { createCollectionContent } from '../createCollectionContent'
import { getConfig } from '../getConfig'
import { writeFile } from 'fs/promises'

jest.mock('../getConfig')
jest.mock('fs/promises')

// suppress console.log so that it doesn't clutter the test output
jest.spyOn(console, 'log').mockImplementation(() => {})

it('should call getConfig with the passed config file location', async () => {
await createCollectionContent('/foo/', 'bar', false)

expect(getConfig).toHaveBeenCalledWith('bar')
})

it('should not proceed if config is not found', async () => {
;(getConfig as jest.Mock).mockResolvedValue(undefined)

await createCollectionContent('/foo/', 'bar', false)

expect(writeFile).not.toHaveBeenCalled()
})

it('should log error if content is not found in config', async () => {
;(getConfig as jest.Mock).mockResolvedValue({ foo: 'bar' })

const mockConsoleError = jest.fn()
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)

await createCollectionContent('/foo/', 'bar', false)

expect(mockConsoleError).toHaveBeenCalledWith('No content found in config')
expect(writeFile).not.toHaveBeenCalled()
})

it('should call writeFile with the expected file location and content without throwing any errors', async () => {
;(getConfig as jest.Mock).mockResolvedValue({ content: { key: 'value' } })

const mockConsoleError = jest.fn()
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)

await createCollectionContent('/foo/', 'bar', false)

expect(writeFile).toHaveBeenCalledWith(
'/foo/src/content.ts',
`export const content = ${JSON.stringify({ key: 'value' })}`,
)
expect(mockConsoleError).not.toHaveBeenCalled()
})

it('should log error if writeFile throws an error', async () => {
;(getConfig as jest.Mock).mockResolvedValue({ content: { key: 'value' } })

const mockConsoleError = jest.fn()
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)

const error = new Error('error')
;(writeFile as jest.Mock).mockRejectedValue(error)

await createCollectionContent('/foo/', 'bar', false)

expect(mockConsoleError).toHaveBeenCalledWith(
'Error writing content file',
error,
)
})

it('should log that content file was created when run in verbose mode', async () => {
const mockConsoleLog = jest.fn()
jest.spyOn(console, 'log').mockImplementation(mockConsoleLog)

await createCollectionContent('/foo/', 'bar', true)

expect(mockConsoleLog).toHaveBeenCalledWith('Content file created')
})

it('should not log that content file was created when not run in verbose mode', async () => {
const mockConsoleLog = jest.fn()
jest.spyOn(console, 'log').mockImplementation(mockConsoleLog)

await createCollectionContent('/foo/', 'bar', false)

expect(mockConsoleLog).not.toHaveBeenCalled()
})
52 changes: 52 additions & 0 deletions cli/__tests__/createConfigFile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-disable no-console */

import { createConfigFile } from '../createConfigFile.ts'
import { access, copyFile } from 'fs/promises'

jest.mock('fs/promises')

afterEach(() => {
jest.clearAllMocks()
})

// suppress console calls so that it doesn't clutter the test output
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(console, 'error').mockImplementation(() => {})

it('should log a message and not call copyFile if the config file already exists', async () => {
;(access as jest.Mock).mockResolvedValue(true)

await createConfigFile('/astro', '/consumer')

expect(copyFile).not.toHaveBeenCalled()
expect(console.log).toHaveBeenCalledWith(
'pf-docs.config.mjs already exists, proceeding to next setup step',
)
})

it('should copy the template file if the config file does not exist', async () => {
;(access as jest.Mock).mockRejectedValue(new Error())
;(copyFile as jest.Mock).mockResolvedValue(undefined)

const from = '/astro/cli/templates/pf-docs.config.mjs'
const to = '/consumer/pf-docs.config.mjs'

await createConfigFile('/astro', '/consumer')

expect(copyFile).toHaveBeenCalledWith(from, to)
expect(console.log).toHaveBeenCalledWith(
'pf-docs.config.mjs has been created in /consumer',
)
})

it('should log an error if copyFile fails', async () => {
;(access as jest.Mock).mockRejectedValue(new Error())
;(copyFile as jest.Mock).mockRejectedValue(new Error('copy failed'))

await createConfigFile('/astro', '/consumer')

expect(console.error).toHaveBeenCalledWith(
'Error creating pf-docs.config.mjs in /consumer.',
)
expect(console.error).toHaveBeenCalledWith(new Error('copy failed'))
})
30 changes: 30 additions & 0 deletions cli/__tests__/getConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getConfig } from '../getConfig'
import { resolve } from 'path'

it('should return the config when pf-docs.config.mjs exists', async () => {
const config = await getConfig(resolve('./cli/testData/good.config.js'))
expect(config).toEqual({
config: {
content: [
{
base: 'base-path',
packageName: 'package-name',
pattern: 'pattern',
name: 'name',
},
],
},
})
})

it('should return undefined and log error when pf-docs.config.mjs does not exist', async () => {
const consoleErrorMock = jest.fn()

jest.spyOn(console, 'error').mockImplementation(consoleErrorMock)

const config = await getConfig('foo')
expect(config).toBeUndefined()
expect(consoleErrorMock).toHaveBeenCalledWith(
'pf-docs.config.mjs not found, have you created it at the root of your package?',
)
})
58 changes: 58 additions & 0 deletions cli/__tests__/setFsRootDir.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { readFile, writeFile } from 'fs/promises'
import { setFsRootDir } from '../setFsRootDir'

jest.mock('fs/promises')

// suppress console.log so that it doesn't clutter the test output
jest.spyOn(console, 'log').mockImplementation(() => {})

it('should attempt to read the astro config file', async () => {
;(readFile as jest.Mock).mockResolvedValue("{ fs: { allow: ['/bar/'] } }")

await setFsRootDir('/foo/', '/bar')

expect(readFile).toHaveBeenCalledWith('/foo/astro.config.mjs', 'utf8')
})

it('should not modify the file if the default allow list is not present', async () => {
;(readFile as jest.Mock).mockResolvedValue("{ fs: { allow: ['/bar/'] } }")

await setFsRootDir('/foo/', '/bar')

expect(writeFile).not.toHaveBeenCalled()
})

it('should modify the file if the default allow list is present', async () => {
;(readFile as jest.Mock).mockResolvedValue("{ fs: { allow: ['./'] } }")

await setFsRootDir('/foo/', '/bar')

expect(writeFile).toHaveBeenCalledWith(
'/foo/astro.config.mjs',
"{ fs: { allow: ['/bar/'] } }",
)
})

it('should log an error if writing the file fails', async () => {
;(readFile as jest.Mock).mockResolvedValue("{ fs: { allow: ['./'] } }")
;(writeFile as jest.Mock).mockRejectedValue(new Error('write error'))
const consoleErrorSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {})

await setFsRootDir('/foo/', '/bar')

expect(consoleErrorSpy).toHaveBeenCalledWith(
`Error setting the server allow list in /foo/`,
expect.any(Error),
)
})

it('should log a success message after attempting to write the file', async () => {
;(readFile as jest.Mock).mockResolvedValue("{ fs: { allow: ['./'] } }")
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})

await setFsRootDir('/foo/', '/bar')

expect(consoleLogSpy).toHaveBeenCalledWith('fs value set created')
})
Loading
Loading