docs-util: add examples generator for workflows and steps (#10914)

* initial changes

* docs-util: generate examples for workflows and steps
This commit is contained in:
Shahed Nasser
2025-01-10 19:16:43 +02:00
committed by GitHub
parent e82645ba55
commit c5915451b8
17 changed files with 810 additions and 142 deletions
+2
View File
@@ -23,7 +23,9 @@
"typescript": "^5.6.2"
},
"dependencies": {
"@faker-js/faker": "^9.3.0",
"octokit": "^3.1.2",
"prettier": "^3.4.2",
"rimraf": "^5.0.5"
}
}
@@ -0,0 +1,319 @@
import { faker } from "@faker-js/faker"
import {
DeclarationReflection,
LiteralType,
ReflectionFlags,
SomeType,
} from "typedoc"
import * as prettier from "prettier"
import { getTypeChildren } from "./get-type-children.js"
const MAX_LEVEL = 7
export function isReflectionTypeOptional(
reflection: DeclarationReflection
): boolean {
return "flags" in reflection
? (reflection.flags as ReflectionFlags).isOptional
: false
}
function getReflectionTypeFakeValue({
reflectionType,
name,
level = 1,
}: {
reflectionType: SomeType
name: string
level?: number
}): unknown {
if (reflectionType.type === "literal") {
return getFakeStrValue({
name,
type: typeof reflectionType.value,
})
}
if (reflectionType.type === "intrinsic") {
return getFakeStrValue({
name,
type: reflectionType.name,
})
}
if (level > MAX_LEVEL) {
return reflectionType.type === "array" ? [] : {}
}
if (reflectionType.type === "array") {
return new Array(
getReflectionTypeFakeValue({
reflectionType: reflectionType.elementType,
name,
level: level + 1,
})
)
}
if (reflectionType.type === "reflection") {
if (reflectionType.declaration.type) {
return getReflectionTypeFakeValue({
reflectionType: reflectionType.declaration.type,
name,
level: level + 1,
})
}
if (reflectionType.declaration.children) {
const obj: Record<string, unknown> = {}
reflectionType.declaration.children.forEach((child) => {
if (!child.type || isReflectionTypeOptional(child)) {
return
}
obj[child.name] = getReflectionTypeFakeValue({
reflectionType: child.type,
name: child.name,
level: level + 1,
})
})
return obj
}
return {}
}
if (
reflectionType.type === "reference" &&
reflectionType.reflection instanceof DeclarationReflection
) {
if (reflectionType.reflection.name === "BigNumberInput") {
return getFakeStrValue({
name,
type: "number",
})
} else if (reflectionType.reflection.name === "Record") {
return getFakeStrValue({
name,
type: "object",
})
}
const obj: Record<string, unknown> = {}
const children = getTypeChildren({
reflectionType: reflectionType,
project: reflectionType.reflection.project,
})
if (!children.length) {
// check whether type is enum
if (reflectionType.reflection.type?.type === "union") {
const isEnum = reflectionType.reflection.type.types.every((type) => {
return type.type === "literal" && typeof type.value === "string"
})
if (isEnum) {
return getFakeStrValue({
name,
type: "enum",
data: {
enum: (
reflectionType.reflection.type.types as LiteralType[]
).reduce(
(acc, type) => {
acc[type.value as string] = type.value as string
return acc
},
{} as Record<string, string>
),
},
})
}
}
}
children.forEach((child) => {
if (!child.type || isReflectionTypeOptional(child)) {
return
}
obj[child.name] = getReflectionTypeFakeValue({
reflectionType: child.type,
name: child.name,
level: level + 1,
})
})
return obj
}
if (reflectionType.type === "intersection") {
const obj: Record<string, unknown> = {}
reflectionType.types?.forEach((type) => {
const value = getReflectionTypeFakeValue({
reflectionType: type,
name,
level: level + 1,
})
if (typeof value === "object") {
Object.assign(obj, value)
} else {
obj[name] = value
}
})
return obj
}
if (reflectionType.type === "union" && reflectionType.types.length) {
// check if it's type Record<>
const isRecord =
reflectionType.types[0].type === "reference" &&
reflectionType.types[0].name === "Record"
if (isRecord) {
return getFakeStrValue({
name,
type: "object",
})
}
return getReflectionTypeFakeValue({
reflectionType: reflectionType.types[0],
name,
level: level + 1,
})
}
// TODO: handle more types
return "{value}"
}
export function getReflectionTypeFakeValueStr({
reflectionType,
name,
}: {
reflectionType: SomeType
name: string
}): string {
const value = getReflectionTypeFakeValue({
reflectionType,
name,
})
return JSON.stringify(value, null, 2)
}
export function getFakeStrValue({
name,
type,
format,
data,
}: {
/**
* The name of the property. It can help when generating the fake value.
* For example, if the name is `id`, the fake value generated will be of the format `id_<randomstring>`.
*/
name: string
/**
* The type of the property, such as `string` or `boolean`.
*/
type: string
/**
* The format of the property, useful for OAS. For example, `date-time`.
*/
format?: string
/**
* Additional data to help generate the fake value.
*/
data?: {
enum: Record<string, string>
}
}): unknown {
let value: unknown
if (!format && name.endsWith("_at")) {
format = "date-time"
}
switch (true) {
case type === "string" && format === "date-time":
value = faker.date.future().toISOString()
break
case type === "boolean":
value = faker.datatype.boolean()
break
case type === "integer" || type === "number":
value = faker.number.int({ min: 0, max: 50 })
break
case type === "array":
value = []
break
case type === "enum" && data?.enum !== undefined:
value = faker.helpers.enumValue(data.enum)
break
case type === "object":
value = {}
break
case type === "string":
value = faker.helpers.mustache(`{{${name}}}`, {
id: () =>
`id_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
ids: () =>
`id_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
name: () => faker.person.firstName(),
email: () => faker.internet.email(),
password: () => faker.internet.password({ length: 8 }),
currency: () => faker.finance.currencyCode(),
title: () => faker.lorem.word(),
description: () => faker.lorem.sentence(),
url: () => faker.internet.url(),
phone: () => faker.phone.number(),
created_by: () =>
`user_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
updated_by: () =>
`user_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
rejected_by: () =>
`user_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
canceled_by: () =>
`user_${faker.string.alphanumeric({
length: { min: 10, max: 20 },
})}`,
price: () => faker.commerce.price(),
quantity: () => `${faker.number.int({ min: 1, max: 10 })}`,
})
try {
value = faker.helpers.fake(value as string)
} catch (e) {
// Ignore error
}
value = (value as string).replace(`{{${name}}}`, "{value}")
}
return value !== undefined ? value : "{value}"
}
export async function formatWithPrettier(
content: string,
fileName: string
): Promise<string> {
// load config of the file
const prettierConfig = (await prettier.resolveConfig(fileName)) || undefined
if (prettierConfig && !prettierConfig.parser) {
prettierConfig.parser = "babel-ts"
}
return await prettier.format(content, prettierConfig)
}
+1
View File
@@ -1,4 +1,5 @@
export * from "./dml-utils.js"
export * from "./examples-utils.js"
export * from "./general-utils.js"
export * from "./get-type-children.js"
export * from "./get-project-child.js"