chore: reorganize docs apps (#7228)

* reorganize docs apps

* add README

* fix directory

* add condition for old docs
This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-05-03 17:36:38 +03:00
1 parent 224ebb2154
commit 4fe28f5a95
6187 files changed
+601447 -598226

No files matched your search

@@ -0,0 +1,31 @@
import { existsSync } from "fs"
import path from "path"
import type { Transformer } from "unified"
import type { UnistNode, UnistTree } from "./types/index.js"
export function brokenLinkCheckerPlugin(): Transformer {
return async (tree, file) => {
const { visit } = await import("unist-util-visit")
const currentPageFilePath = file.history[0].replace(
`/${path.basename(file.history[0])}`,
""
)
visit(tree as UnistTree, "element", (node: UnistNode) => {
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
return
}
// get absolute path of the URL
const linkedFilePath = path
.resolve(currentPageFilePath, node.properties.href)
.replace(/#.*$/, "")
// check if the file exists
if (!existsSync(linkedFilePath)) {
throw new Error(
`Broken link found! ${node.properties.href} linked in ${file.history[0]}`
)
}
})
}
}
@@ -0,0 +1,112 @@
import type { Transformer } from "unified"
import type { CloudinaryConfig, UnistNode, UnistTree } from "./types/index.js"
const cloudinaryImageRegex =
/^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?<imageId>.*)$/
export function cloudinaryImgRehypePlugin({
cloudinaryConfig,
}: {
cloudinaryConfig: CloudinaryConfig
}): Transformer {
return async (tree) => {
if (!cloudinaryConfig) {
return
}
const { visit } = await import("unist-util-visit")
const { Cloudinary } = await import("@cloudinary/url-gen")
const { byRadius } = await import(
"@cloudinary/url-gen/actions/roundCorners"
)
const {
pad,
imaggaScale,
imaggaCrop,
crop,
fit,
minimumPad,
fill,
scale,
limitFit,
thumbnail,
limitFill,
minimumFit,
limitPad,
fillPad,
} = await import("@cloudinary/url-gen/actions/resize")
const resizeActions = {
pad,
imaggaScale,
imaggaCrop,
crop,
fit,
minimumPad,
fill,
scale,
limitFit,
thumbnail,
limitFill,
minimumFit,
limitPad,
fillPad,
}
visit(tree as UnistTree, (node: UnistNode) => {
const matchingRegex = node.properties?.src?.match(cloudinaryImageRegex)
if (node.tagName !== "img" || !matchingRegex?.groups?.imageId) {
return
}
const cloudinary = new Cloudinary({
cloud: {
cloudName: cloudinaryConfig.cloudName,
},
})
const image = cloudinary.image(
matchingRegex.groups.imageId.replaceAll("%20", " ")
)
cloudinaryConfig.flags?.forEach((flag) => image.addTransformation(flag))
if (cloudinaryConfig.roundCorners) {
image.roundCorners(byRadius(cloudinaryConfig.roundCorners))
}
if (cloudinaryConfig.resize) {
const action =
resizeActions[
cloudinaryConfig.resize.action as keyof typeof resizeActions
]
let resizeAction = action()
if (node.properties?.width || node.properties?.height) {
if (node.properties?.width) {
resizeAction = resizeAction.width(node.properties.width)
}
if (node.properties?.height) {
resizeAction = resizeAction.height(node.properties.height)
}
} else if (cloudinaryConfig.resize.aspectRatio) {
resizeAction = resizeAction.aspectRatio(
cloudinaryConfig.resize.aspectRatio
)
} else {
if (cloudinaryConfig.resize.width) {
resizeAction = resizeAction.width(cloudinaryConfig.resize.width)
}
if (cloudinaryConfig.resize.height) {
resizeAction = resizeAction.height(cloudinaryConfig.resize.height)
}
}
image.resize(resizeAction)
}
node.properties!.src = image.toURL()
})
}
}
@@ -0,0 +1,45 @@
import type { Transformer } from "unified"
import type {
CrossProjectLinksOptions,
UnistNode,
UnistTree,
} from "./types/index.js"
const PROJECT_REGEX = /^!(?<area>[\w-]+)!/
export function crossProjectLinksPlugin({
baseUrl,
projectUrls,
}: CrossProjectLinksOptions): Transformer {
return async (tree) => {
const { visit } = await import("unist-util-visit")
visit(tree as UnistTree, "element", (node: UnistNode) => {
if (node.tagName !== "a" || !node.properties?.href) {
return
}
const projectArea = PROJECT_REGEX.exec(node.properties.href)
if (!projectArea?.groups?.area) {
return
}
const actualUrl = node.properties.href.replace(PROJECT_REGEX, "")
const base =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.url
? projectUrls[projectArea.groups.area].url
: baseUrl
const path =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.path
? projectUrls[projectArea.groups.area].path
: projectArea.groups.area
node.properties.href = `${base}/${path}${actualUrl}`
})
}
}
@@ -0,0 +1,11 @@
export * from "./broken-link-checker.js"
export * from "./cloudinary-img.js"
export * from "./cross-project-links.js"
export * from "./local-links.js"
export * from "./page-number.js"
export * from "./resolve-admonitions.js"
export * from "./type-list-link-fixer.js"
export * from "./utils/fix-link.js"
export * from "./utils/get-file-slug.js"
export * from "./utils/get-front-matter.js"
@@ -0,0 +1,40 @@
import path from "path"
import type { Transformer } from "unified"
import type { LocalLinkOptions, UnistNode, UnistTree } from "./types/index.js"
import { fixLinkUtil } from "./index.js"
export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
const { filePath, basePath } = options || {}
return async (tree, file) => {
if (!file.cwd) {
return
}
if (!file.history.length) {
if (!filePath) {
return
}
file.history.push(filePath)
}
const { visit } = await import("unist-util-visit")
const currentPageFilePath = file.history[0].replace(
`/${path.basename(file.history[0])}`,
""
)
const appsPath = basePath || path.join(file.cwd, "app")
visit(tree as UnistTree, "element", (node: UnistNode) => {
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
return
}
node.properties.href = fixLinkUtil({
currentPageFilePath,
linkedPath: node.properties.href,
appsPath,
})
})
}
}
@@ -0,0 +1,86 @@
import path from "path"
import type { Transformer } from "unified"
import type { RawSidebarItemType } from "types"
import type { UnistTree } from "./types/index.js"
export function pageNumberRehypePlugin({
sidebar,
}: {
sidebar: RawSidebarItemType[]
}): Transformer {
return async (tree, file) => {
const { valueToEstree } = await import("estree-util-value-to-estree")
let number = ""
if (file.history.length) {
// get page path
const pageFilePath = file.history[0].replace(
path.join(file.cwd, "app"),
""
)
let pagePath = pageFilePath.replace(`/${path.basename(pageFilePath)}`, "")
if (!pagePath.length) {
pagePath = "/"
}
const pageSidebarItem = findSidebarItem(sidebar, pagePath)
if (pageSidebarItem?.number) {
number = pageSidebarItem?.number
}
}
;(tree as UnistTree).children.unshift({
type: "mdxjsEsm",
value: `export const pageNumber = "${number}"`,
data: {
estree: {
type: "Program",
body: [
{
type: "ExportNamedDeclaration",
declaration: {
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "pageNumber",
},
init: valueToEstree(number),
},
],
kind: "const",
},
specifiers: [],
source: null,
},
],
sourceType: "module",
},
},
})
}
}
function findSidebarItem(
sidebar: RawSidebarItemType[],
path: string
): RawSidebarItemType | undefined {
let foundItem = undefined
for (const item of sidebar) {
if (path === item.path) {
foundItem = item
} else if (item.children) {
foundItem = findSidebarItem(item.children, path)
}
if (foundItem) {
break
}
}
return foundItem
}
@@ -0,0 +1,47 @@
import { Transformer } from "unified"
import { UnistNode } from "./types/index.js"
const ALLOWED_NODE_NAMES = ["note", "tip", "info", "warning"]
export function resolveAdmonitionsPlugin(): Transformer {
return async (tree) => {
const { visit } = await import("unist-util-visit")
visit(tree as UnistNode, (node: UnistNode) => {
if (
(node.type !== "containerDirective" &&
node.type !== "leafDirective" &&
node.type !== "textDirective") ||
!node.name ||
!ALLOWED_NODE_NAMES.includes(node.name)
) {
return
}
let title: string | undefined
node.children?.some((childNode, childIndex) => {
if (childNode.data?.directiveLabel && childNode.children?.length) {
title = childNode.children.find((item) => item.type === "text")?.value
if (title) {
node.children?.splice(childIndex, 1)
}
}
return title !== undefined
})
if (!node.data) {
node.data = {}
}
node.data.hName = "Note"
node.data.hProperties = {
title:
title ||
(node.name !== "note"
? `${node.name.charAt(0).toUpperCase()}${node.name.substring(1)}`
: "Note"),
}
})
}
}
@@ -0,0 +1,156 @@
import path from "path"
import { Transformer } from "unified"
import {
TypeListLinkFixerOptions,
UnistNode,
UnistNodeWithData,
UnistTree,
} from "./types/index.js"
import { FixLinkOptions, fixLinkUtil } from "./index.js"
const LINK_REGEX = /\[(.*?)\]\((?<link>.*?)\)/gm
function matchLinks(
str: string,
linkOptions: Omit<FixLinkOptions, "linkedPath">
) {
let linkMatches
while ((linkMatches = LINK_REGEX.exec(str)) !== null) {
if (!linkMatches.groups?.link) {
return
}
const newUrl = fixLinkUtil({
...linkOptions,
linkedPath: linkMatches.groups.link,
})
str = str.replace(linkMatches.groups.link, newUrl)
}
return str
}
function traverseTypes(
types: Record<string, unknown>[],
linkOptions: Omit<FixLinkOptions, "linkedPath">
) {
return types.map((typeItem) => {
typeItem.type = matchLinks(typeItem.type as string, linkOptions)
typeItem.description = matchLinks(
typeItem.description as string,
linkOptions
)
if (typeItem.children) {
typeItem.children = traverseTypes(
typeItem.children as Record<string, unknown>[],
linkOptions
)
}
return typeItem
})
}
export function typeListLinkFixerPlugin(
options?: TypeListLinkFixerOptions
): Transformer {
const { filePath, basePath } = options || {}
return async (tree, file) => {
if (!file.cwd) {
return
}
if (!file.history.length) {
if (!filePath) {
return
}
file.history.push(filePath)
}
const { visit } = await import("unist-util-visit")
const currentPageFilePath = file.history[0].replace(
`/${path.basename(file.history[0])}`,
""
)
const appsPath = basePath || path.join(file.cwd, "app")
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNode) => {
if (node.name !== "TypeList") {
return
}
const typesAttributeIndex = node.attributes?.findIndex(
(attribute) => attribute.name === "types"
)
if (typesAttributeIndex === undefined || typesAttributeIndex === -1) {
return
}
const typesAttribute = node.attributes![typesAttributeIndex]
if (
!typesAttribute ||
!(typesAttribute.value as Record<string, unknown>)?.value
) {
return
}
const linkOptions = {
currentPageFilePath,
appsPath,
}
let newItems: Record<string, unknown>[]
try {
newItems = traverseTypes(
JSON.parse(
(typesAttribute.value as Record<string, unknown>).value as string
) as Record<string, unknown>[],
linkOptions
)
} catch (e) {
// eslint-disable-next-line no-console
console.log(
`[type-list-link-fixer-plugin]: An error occurred while parsing items for page ${file.history[0]}: ${e}`
)
return
}
;(
node.attributes![typesAttributeIndex].value as Record<string, unknown>
).value = JSON.stringify(newItems)
if (
(node as UnistNodeWithData).attributes![typesAttributeIndex].value?.data
?.estree?.body?.length
) {
const oldItems = (node as UnistNodeWithData).attributes[
typesAttributeIndex
].value.data!.estree!.body![0].expression!.elements!
;(node as UnistNodeWithData).attributes[
typesAttributeIndex
].value.data!.estree!.body![0].expression!.elements = newItems.map(
(newItem, index) => {
oldItems[index].properties = oldItems[index].properties.map(
(property) => {
if (Object.hasOwn(newItem, property.key.value)) {
property.value.value = newItem[property.key.value]
property.value.raw = JSON.stringify(
newItem[property.key.value]
)
}
return property
}
)
return oldItems[index]
}
)
}
})
}
}
@@ -0,0 +1,96 @@
import { Node } from "unist-builder"
export interface UnistNode extends Node {
type: string
name?: string
tagName?: string
value?: string
properties?: {
[key: string]: string
}
attributes?: {
name: string
value: unknown
type?: string
}[]
children?: UnistNode[]
}
export interface UnistNodeWithData extends UnistNode {
attributes: {
name: string
value: {
data?: {
estree?: {
body?: {
type?: string
expression?: {
type?: string
elements?: {
properties: AttributeProperty[]
}[]
}
}[]
}
}
value?: string
}
type?: string
}[]
}
export interface AttributeProperty {
key: {
value: string
raw: string
}
value: {
value: unknown
raw: string
}
}
export interface UnistTree extends Node {
children: UnistNode[]
}
export declare type CloudinaryConfig = {
cloudName?: string
flags?: string[]
resize?: {
action: string
width?: number
height?: number
aspectRatio?: string
}
roundCorners?: number
}
export declare type FrontMatter = {
slug?: string
sidebar_label?: string
sidebar_group?: string
sidebar_group_main?: boolean
sidebar_position?: number
sidebar_autogenerate_exclude?: boolean
}
export declare type CrossProjectLinksOptions = {
baseUrl: string
projectUrls?: {
[k: string]: {
url: string
path?: string
}
}
}
export declare type TypeListLinkFixerOptions = {
filePath?: string
basePath?: string
}
export declare type LocalLinkOptions = {
filePath?: string
basePath?: string
}
@@ -0,0 +1,27 @@
import path from "path"
export type FixLinkOptions = {
currentPageFilePath: string
linkedPath: string
appsPath: string
}
export function fixLinkUtil({
currentPageFilePath,
linkedPath,
appsPath: basePath,
}: FixLinkOptions) {
// get absolute path of the URL
const linkedFilePath = path
.resolve(currentPageFilePath, linkedPath)
.replace(basePath, "")
// persist hash in new URL
const hash = linkedFilePath.includes("#")
? linkedFilePath.substring(linkedFilePath.indexOf("#"))
: ""
return `${linkedFilePath.substring(
0,
linkedFilePath.indexOf(`/${path.basename(linkedFilePath)}`)
)}${hash}`
}
@@ -0,0 +1,12 @@
import { getFrontMatterUtil } from "./get-front-matter.js"
export async function getFileSlugUtil(
filePath: string
): Promise<string | undefined> {
const fileFrontmatter = await getFrontMatterUtil(filePath)
if (fileFrontmatter.slug) {
// add to slugs array
return fileFrontmatter.slug
}
}
@@ -0,0 +1,24 @@
import remarkFrontmatter from "remark-frontmatter"
import remarkParse from "remark-parse"
import remarkStringify from "remark-stringify"
import { unified } from "unified"
import { read } from "to-vfile"
import { matter } from "vfile-matter"
import { FrontMatter } from "../types/index.js"
export async function getFrontMatterUtil(
filePath: string
): Promise<FrontMatter> {
return (
await unified()
.use(remarkParse)
.use(remarkStringify)
.use(remarkFrontmatter, ["yaml"])
.use(() => {
return (tree, file) => {
matter(file)
}
})
.process(await read(filePath))
).data.matter as FrontMatter
}