diff --git a/package.json b/package.json index a5951063..90064a69 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,9 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.3", + "jsdom": "^21.1.0", "lint-staged": "^13.2.1", + "markdown-it": "^13.0.1", "minimist": "^1.2.8", "prettier": "^2.8.4", "release-it": "^15.10.3", diff --git a/packages/agora-rtc-react/package.json b/packages/agora-rtc-react/package.json index 540c0440..61540504 100644 --- a/packages/agora-rtc-react/package.json +++ b/packages/agora-rtc-react/package.json @@ -43,7 +43,8 @@ "test:watch": "vitest --ui", "gene-stories": "esbuild-dev ../../scripts/generate-storybook-mdx.ts", "gene-md": "esbuild-dev ../../scripts/generate-docs.ts", - "gene-docs": "pnpm run gene-md && pnpm run gene-stories" + "gene-comment": "esbuild-dev ../../scripts/api.ts && esbuild-dev ../../scripts/component.ts", + "gene-docs": "pnpm run gene-md && pnpm run gene-stories && pnpm run gene-comment" }, "peerDependencies": { "agora-rtc-sdk-ng": ">=4.18.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1aef07d7..b8f720ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,9 +69,15 @@ importers: husky: specifier: ^8.0.3 version: 8.0.3 + jsdom: + specifier: ^21.1.0 + version: 21.1.0 lint-staged: specifier: ^13.2.1 version: 13.2.1 + markdown-it: + specifier: ^13.0.1 + version: 13.0.1 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -6956,6 +6962,11 @@ packages: tapable: 2.2.1 dev: true + /entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + dev: true + /entities@4.4.0: resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} engines: {node: '>=0.12'} @@ -9333,6 +9344,12 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /linkify-it@4.0.1: + resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} + dependencies: + uc.micro: 1.0.6 + dev: true + /lint-staged@13.2.1: resolution: {integrity: sha512-8gfzinVXoPfga5Dz/ZOn8I2GOhf81Wvs+KwbEXQn/oWZAvCVS2PivrXfVbFJc93zD16uC0neS47RXHIjXKYZQw==} engines: {node: ^14.13.1 || >=16.0.0} @@ -9619,6 +9636,17 @@ packages: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true + /markdown-it@13.0.1: + resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 3.0.1 + linkify-it: 4.0.1 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + /markdown-to-jsx@7.1.9(react@18.2.0): resolution: {integrity: sha512-x4STVIKIJR0mGgZIZ5RyAeQD7FEZd5tS8m/htbcVGlex32J+hlSLj+ExrHCxP6nRKF1EKbcO7i6WhC1GtOpBlA==} engines: {node: '>= 10'} @@ -9657,6 +9685,10 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: true + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -12979,6 +13011,10 @@ packages: hasBin: true dev: true + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: true + /ufo@1.1.1: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} dev: true diff --git a/scripts/const.ts b/scripts/const.ts index 06d5a5bb..78206726 100644 --- a/scripts/const.ts +++ b/scripts/const.ts @@ -7,3 +7,5 @@ export const languagesFormat = [".zh-CN", ".en-US"]; export const packagePath = path.join(__dirname, "..", "packages", "agora-rtc-react"); export const docsPath = path.join(packagePath, "docs"); export const storiesPath = path.join(packagePath, "src", "stories"); +export const hooksPath = path.join(packagePath, "src", "hooks"); +export const componentsPath = path.join(packagePath, "src", "components"); diff --git a/scripts/docs/api.ts b/scripts/docs/api.ts new file mode 100644 index 00000000..27cfac2b --- /dev/null +++ b/scripts/docs/api.ts @@ -0,0 +1,129 @@ +import fs from "node:fs"; +import path from "node:path"; + +import jsdom from "jsdom"; +import MarkdownIt from "markdown-it"; + +import { docsPath, hooksPath, languagesFormat } from "../const"; + +import { readDirRecursively, tableToJson } from "./utils"; + +const md = new MarkdownIt(); + +async function writeComment(markdownPath) { + const markdown = fs.readFileSync(markdownPath, "utf-8"); + const result = md.render(markdown, "utf-8"); + const dom: HTMLElement = new jsdom.JSDOM(result).window.document; + + const target = dom.querySelector("h3")?.textContent; + const targetDescription = dom.querySelectorAll("p")[0]?.textContent; + const targetRequireParameterList = tableToJson(dom.querySelectorAll("table")[0]); + const targetRequireParameterInsertList: string[] = []; + for (const row of targetRequireParameterList) { + let targetRequireParameterContent = " * @param"; + for (let i = 0; i < row.length; i++) { + if ((i == 0 || i == 3) && row[i]?.textContent) { + const replacedStr = row[i].innerHTML + .replace(/(.*)<\/a>/, "[$2]($1)") + .replace(/(.*?)<\/code>/g, "`$1`"); + targetRequireParameterContent = targetRequireParameterContent + " " + replacedStr; + } + if (i == 1 && row[i]?.textContent) { + targetRequireParameterContent = + targetRequireParameterContent + " " + "{" + row[i].textContent + "}"; + } + } + targetRequireParameterInsertList.push(targetRequireParameterContent); + } + const targetReturnParameterList = tableToJson(dom.querySelectorAll("table")[1]); + const targetReturnParameterInsertList: string[] = []; + for (const row of targetReturnParameterList) { + let targetReturnParameterContent = " * @return"; + for (let i = 0; i < row.length; i++) { + if ((i == 0 || i == 1) && row[i]?.textContent) { + targetReturnParameterContent = targetReturnParameterContent + " " + row[i].textContent; + } + } + targetReturnParameterInsertList.push(targetReturnParameterContent); + } + + const files = fs.readdirSync(hooksPath); + files.forEach(file => { + const filePath = path.join(hooksPath, file); + + let content = fs.readFileSync(filePath, "utf-8"); + + if (content.includes(`export function ${target}`)) { + let comment = ` +/** + * ${targetDescription} + * +`; + if (targetRequireParameterInsertList.length > 0) { + comment = comment.concat(targetRequireParameterInsertList.join("\n")); + } + if (targetReturnParameterInsertList.length > 0) { + comment = comment.concat(`\n`); + comment = comment.concat(targetReturnParameterInsertList.join("\n")); + } + comment = comment.concat(`\n */`); + const position = content.indexOf(`export function ${target}`); + content = content.slice(0, position - 1) + comment + content.slice(position - 1); + fs.writeFileSync(filePath, content); + } + }); +} + +async function cleanComment(filePath) { + let content = fs.readFileSync(filePath, "utf-8"); + const regex = /\/\*(.*?)\*\//gs; + + content = content.replace(regex, (match, group) => { + if (group.includes("@ignore")) { + return match; + } else if (group.includes("@__PURE__")) { + return match; + } else { + return ""; + } + }); + fs.writeFileSync(filePath, content); +} + +//hooks clean +await readDirRecursively(`${hooksPath}`, async (filePath: string) => { + if ( + filePath.includes("client.ts") || + filePath.includes("context.ts") || + filePath.includes("tracks.ts") || + filePath.includes("users.ts") + ) { + await cleanComment(filePath); + } +}); + +//hooks inject +await readDirRecursively(`${docsPath}/hooks`, async (filePath: string) => { + if (filePath.includes(languagesFormat[1])) { + await writeComment(filePath); + } +}); + +//interfaces clean +await readDirRecursively(`${hooksPath}`, async (filePath: string) => { + if ( + filePath.includes("client.ts") || + filePath.includes("context.ts") || + filePath.includes("tracks.ts") || + filePath.includes("users.ts") + ) { + await cleanComment(filePath); + } +}); + +//interfaces inject +await readDirRecursively(`${docsPath}/hooks`, async (filePath: string) => { + if (filePath.includes(languagesFormat[1])) { + await writeComment(filePath); + } +}); diff --git a/scripts/docs/component.ts b/scripts/docs/component.ts new file mode 100644 index 00000000..b5ff287d --- /dev/null +++ b/scripts/docs/component.ts @@ -0,0 +1,99 @@ +import fs from "node:fs"; +import path from "node:path"; + +import jsdom from "jsdom"; +import MarkdownIt from "markdown-it"; + +import { componentsPath, docsPath, languagesFormat } from "../const"; + +import { readDirRecursively, tableToJson } from "./utils"; + +const md = new MarkdownIt(); + +async function writeComment(markdownPath) { + const markdown = fs.readFileSync(markdownPath, "utf-8"); + const result = md.render(markdown, "utf-8"); + const dom: HTMLElement = new jsdom.JSDOM(result).window.document; + + const target = dom.querySelector("h3")?.textContent; + const targetDescription = dom.querySelectorAll("p")[0]?.textContent; + const targetRequireParameterList = tableToJson(dom.querySelectorAll("table")[0]); + + const files = fs.readdirSync(componentsPath); + files.forEach(file => { + const filePath = path.join(componentsPath, file); + + let content = fs.readFileSync(filePath, "utf-8"); + + if (content.includes(`export function ${target}`)) { + let comment = ` +/** + * ${targetDescription} +`; + comment = comment.concat(`\n */`); + const position = content.indexOf(`export function ${target}`); + content = content.slice(0, position - 1) + comment + content.slice(position - 1); + + for (const row of targetRequireParameterList) { + let interfaceName = ""; + let replacedStr = ""; + if (row[0]?.textContent) { + interfaceName = row[0].textContent; + } + if (row[3]?.innerHTML) { + replacedStr = row[3]?.innerHTML + .replace(/(.*)<\/a>/, "[$2]($1)") + .replace(/<li>(.*?)<\/li>/g, "$1") + .replace(/(.*?)<\/code>/g, "`$1`"); + } + const interfaceComment = ` +/** + * ${replacedStr} + */\n`; + if (content.includes(`readonly ${interfaceName}`)) { + const interfacePosition = content.indexOf(`readonly ${interfaceName}`); + content = + content.slice(0, interfacePosition - 1) + + interfaceComment + + content.slice(interfacePosition - 1); + } + } + fs.writeFileSync(filePath, content); + } + }); +} + +async function cleanComment(filePath) { + let content = fs.readFileSync(filePath, "utf-8"); + const regex = /\/\*(.*?)\*\//gs; + + content = content.replace(regex, (match, group) => { + if (group.includes("@ignore")) { + return match; + } else if (group.includes("@__PURE__")) { + return match; + } else { + return ""; + } + }); + fs.writeFileSync(filePath, content); +} + +//components clean +await readDirRecursively(`${componentsPath}`, async (filePath: string) => { + if ( + !filePath.includes(".stories.tsx") && + !filePath.includes("styles.ts") && + !filePath.includes("TrackBoundary.tsx") && + !filePath.includes("UserCover.tsx") + ) { + await cleanComment(filePath); + } +}); + +// components inject +await readDirRecursively(`${docsPath}/components`, async (filePath: string) => { + if (filePath.includes(languagesFormat[1])) { + await writeComment(filePath); + } +}); diff --git a/scripts/docs/utils.ts b/scripts/docs/utils.ts new file mode 100644 index 00000000..569a9f44 --- /dev/null +++ b/scripts/docs/utils.ts @@ -0,0 +1,32 @@ +import fs from "node:fs"; +import path from "node:path"; + +export function tableToJson(table) { + if (!table) { + return []; + } + const data: HTMLTableCellElement[][] = []; + for (let i = 1; i < table.rows.length; i++) { + const tableRow: HTMLTableRowElement = table.rows[i]; + const rowData: HTMLTableCellElement[] = []; + for (let j = 0; j < tableRow.cells.length; j++) { + rowData.push(tableRow.cells[j]); + } + data.push(rowData); + } + return data; +} + +export async function readDirRecursively(dir, handler) { + const files = fs.readdirSync(dir); + files.forEach(async file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + readDirRecursively(filePath, handler); + } else { + handler && handler(filePath); + } + }); +}