Skip to content

Commit

Permalink
adds JSON files for our hooks' APIs, adds a script to build one large…
Browse files Browse the repository at this point in the history
… JSON file for all hooks (#5554)

adds hooks build to 'build-components-json' in CI
  • Loading branch information
mperrotti authored Jan 16, 2025
1 parent 00e0f87 commit d25d35c
Show file tree
Hide file tree
Showing 25 changed files with 1,138 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ jobs:
- name: Build components.json
run: npx tsx script/components-json/build.ts --storybook-data 'storybook-static/index.json'
working-directory: packages/react
- name: Build hooks.json
run: npx tsx script/hooks-json/build.ts'
working-directory: packages/react

sizes:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"build:docs": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs",
"build:docs:preview": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs preview",
"build:components.json": "npm run build:components.json -w @primer/react",
"build:hooks.json": "npm run build:hooks.json -w @primer/react",
"lint": "eslint '**/*.{js,ts,tsx,md,mdx}' --max-warnings=0",
"lint:css": "stylelint --rd -q '**/*.css'",
"lint:css:fix": "npm run lint:css -- --fix",
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"build:docs": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs",
"build:docs:preview": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs preview",
"build:components.json": "tsx script/components-json/build.ts",
"build:hooks.json": "tsx script/hooks-json/build.ts",
"build:precompile-color-schemes": "tsx script/precompile-color-schemes.ts",
"storybook": "storybook",
"type-check": "tsc --noEmit"
Expand Down
48 changes: 48 additions & 0 deletions packages/react/script/hooks-json/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import glob from 'fast-glob'
import fs from 'fs'
import keyBy from 'lodash.keyby'
import hookSchema from '../hooks-json/hook.schema.json'
import outputSchema from './output.schema.json'
import Ajv from 'ajv'

// Only includes fields we use in this script
type Hook = {
name: string
importPath: '@primer/react' | '@primer/react/experimental'
stories: Array<{id: string}>
}

const ajv = new Ajv()

const hookDocsFiles = glob.sync('src/**/*.hookDocs.json')

const hooks = hookDocsFiles.map(docsFilepath => {
const docs = JSON.parse(fs.readFileSync(docsFilepath, 'utf-8'))

// Create a validator for the hook schema
const validate = ajv.compile<Hook>(hookSchema)

// Validate the hook schema
if (!validate(docs)) {
throw new Error(`Invalid docs file ${docsFilepath}: ${JSON.stringify(validate.errors, null, 2)}}`)
}

return docs
})

const data = {schemaVersion: 2, hooks: keyBy(hooks, 'name')}

// Validate output
const validate = ajv.compile(outputSchema)

if (!validate(data)) {
throw new Error(`Invalid output: ${JSON.stringify(validate.errors, null, 2)}}`)
}

// Create `generated` directory if it doesn't exist
if (!fs.existsSync('generated')) {
fs.mkdirSync('generated')
}

// Write hooks.json file
fs.writeFileSync('generated/hooks.json', JSON.stringify(data, null, 2))
130 changes: 130 additions & 0 deletions packages/react/script/hooks-json/hook.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"$id": "hook.schema.json",
"type": "object",
"required": ["name", "stories", "importPath"],
"additionalProperties": false,
"definitions": {
"parameter": {
"type": "object",
"required": ["name", "type"],
"properties": {
"name": {
"type": "string",
"description": "The name of the parameter."
},
"type": {
"type": "string",
"description": "The type of the parameter in valid TypeScript syntax."
},
"defaultValue": {
"type": "string",
"description": "The default value of the parameter if defined."
},
"required": {
"type": "boolean",
"description": "Indicate whether the parameter is required."
},
"deprecated": {
"type": "boolean",
"description": "Indicate whether the parameter is deprecated."
},
"description": {
"type": "string",
"description": "A concise description of the parameter."
}
}
},
"relatedTypeProperty": {
"type": "object",
"required": ["name", "type"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The name of the property."
},
"type": {
"type": "string",
"description": "The type of the property in valid TypeScript syntax."
},
"required": {
"type": "boolean",
"description": "Indicate whether the property is required."
},
"defaultValue": {
"type": "string",
"description": "The default value of the property if defined."
},
"description": {
"type": "string",
"description": "A concise description of the property."
}
}
},
"story": {
"type": "object",
"required": ["id"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "The Storybook story ID (e.g. \"hooks-usecolorschemevar--default\")."
}
}
}
},
"properties": {
"name": {
"type": "string",
"description": "The name of the hook."
},
"importPath": {
"type": "string",
"description": "The path to import the hook from. i.e. '@primer/react/experimental'"
},
"stories": {
"type": "array",
"description": "An array of Storybook story IDs to embed in the docs.",
"items": {
"$ref": "#/definitions/story"
}
},
"parameters": {
"type": "array",
"description": "An array of parameters the hook accepts.",
"items": {
"$ref": "#/definitions/parameter"
}
},
"returns": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of the returned value in valid TypeScript syntax."
}
}
},
"relatedTypes": {
"type": "array",
"description": "An array of the types or interfaces related to the hook.",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The name of the type or interface."
},
"properties": {
"type": "array",
"description": "An array of the properties in the type or interface.",
"items": {
"$ref": "#/definitions/relatedTypeProperty"
}
}
}
}
}
}
}
21 changes: 21 additions & 0 deletions packages/react/script/hooks-json/output.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$id": "output.schema.json",
"type": "object",
"required": ["schemaVersion", "hooks"],
"properties": {
"schemaVersion": {
"type": "number",
"enum": [2],
"description": "The version of the schema. We increment this when we make breaking changes to the schema."
},
"hooks": {
"type": "object",
"description": "Metadata about exported by @primer/react.",
"patternProperties": {
".*": {
"$ref": "./hook.schema.json#"
}
}
}
}
}
53 changes: 53 additions & 0 deletions packages/react/src/ConfirmationDialog/useConfirm.hookDocs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "useConfirm",
"importPath": "@primer/react",
"stories": [
{
"id": "components-confirmationdialog-features--shorthand-hook"
},
{
"id": "components-confirmationdialog-features--shorthand-hook-from-action-menu"
}
],
"returns": {
"type": "(options: ConfirmOptions) => Promise<boolean>",
"description": "An async function that shows a confirmation dialog and resolves with a boolean indicating whether the confirm button was used."
},
"relatedTypes": [
{
"name": "ConfirmOptions",
"properties": [
{
"name": "title",
"type": "React.ReactNode",
"required": true,
"description": "The title of the ConfirmationDialog. This is usually a brief question."
},
{
"name": "content",
"type": "React.ReactNode",
"required": true,
"description": "ConfirmationDialog body content."
},
{
"name": "cancelButtonContent",
"type": "React.ReactNode",
"defaultValue": "Cancel",
"description": "The text to use for the cancel button."
},
{
"name": "confirmButtonContent",
"type": "React.ReactNode",
"defaultValue": "OK",
"description": "The text to use for the confirm button."
},
{
"name": "confirmButtonType",
"type": "\"normal\" | \"primary\" | \"danger\"",
"defaultValue": "normal",
"description": "The type of button to use for the confirm button."
}
]
}
]
}
17 changes: 17 additions & 0 deletions packages/react/src/FeatureFlags/useFeatureFlag.hookDocs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "useFeatureFlag",
"importPath": "@primer/react",
"stories": [],
"parameters": [
{
"name": "flag",
"type": "string",
"required": true,
"description": "The feature flag ID."
}
],
"returns": {
"type": "boolean",
"description": "Whether the feature flag is enabled."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "useFormControlForwardedProps",
"importPath": "@primer/react",
"stories": [{"id": "hooks-useformcontrolforwardedprops--autowired-custom-input"}],
"parameters": [
{
"name": "externalProps",
"type": "P",
"required": true,
"description": "The external props passed to this component. If provided, these props will be merged with the `FormControl` props, with external props taking priority."
}
],
"returns": {
"type": "P & FormControlForwardedProps"
},
"relatedTypes": [
{
"name": "FormControlForwardedProps",
"properties": [
{
"name": "disabled",
"type": "boolean",
"description": "Indicates if the form control is disabled."
},
{
"name": "id",
"type": "string",
"description": "The id of the form control."
},
{
"name": "required",
"type": "boolean",
"description": "Indicates if the form control is required."
},
{
"name": "aria-describedby",
"type": "string",
"description": "The id of the element that describes the form control."
}
]
}
]
}
Loading

0 comments on commit d25d35c

Please sign in to comment.