@xyd-js/uniform 0.1.0-xyd.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/content.ts +1 -0
- package/index.ts +8 -0
- package/markdown.ts +1 -0
- package/package.json +33 -0
- package/src/content/index.ts +124 -0
- package/src/index.ts +87 -0
- package/src/markdown/index.ts +64 -0
- package/src/markdown/utils.ts +235 -0
- package/src/types.ts +134 -0
- package/src/utils.ts +123 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +39 -0
package/README.md
ADDED
package/content.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/content"
|
package/index.ts
ADDED
package/markdown.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/markdown";
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xyd-js/uniform",
|
|
3
|
+
"version": "0.1.0-xyd.2",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./package.json": "./package.json",
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./markdown": "./dist/markdown.js",
|
|
11
|
+
"./content": "./dist/content.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"remark": "^15.0.1",
|
|
15
|
+
"remark-stringify": "^11.0.0",
|
|
16
|
+
"gray-matter": "^4.0.3",
|
|
17
|
+
"unist-builder": "^4.0.0",
|
|
18
|
+
"unist-util-visit": "^5.0.0",
|
|
19
|
+
"codehike": "^1.0.3",
|
|
20
|
+
"@xyd-js/core": "0.1.0-xyd.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.3.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@mdx-js/mdx": "^3.1.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"clean": "rimraf build",
|
|
30
|
+
"prebuild": "pnpm clean",
|
|
31
|
+
"build": "tsup"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import {promises as fs} from "fs";
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
import {parse} from "codehike";
|
|
6
|
+
import {visit} from "unist-util-visit";
|
|
7
|
+
import {recmaCodeHike, remarkCodeHike} from "codehike/mdx";
|
|
8
|
+
import {compile as mdxCompile} from "@mdx-js/mdx";
|
|
9
|
+
|
|
10
|
+
const codeHikeOptions = {
|
|
11
|
+
lineNumbers: true,
|
|
12
|
+
showCopyButton: true,
|
|
13
|
+
autoImport: true,
|
|
14
|
+
components: {code: "Code"},
|
|
15
|
+
// syntaxHighlighting: { // TODO: !!! FROM SETTINGS !!! wait for rr7 rsc ??
|
|
16
|
+
// theme: "github-dark",
|
|
17
|
+
// },
|
|
18
|
+
};
|
|
19
|
+
//
|
|
20
|
+
// // since unist does not support heading level > 6, we need to normalize them
|
|
21
|
+
function normalizeCustomHeadings() {
|
|
22
|
+
return (tree: any) => {
|
|
23
|
+
visit(tree, 'paragraph', (node, index, parent) => {
|
|
24
|
+
const match = node.children[0] && node.children[0].value.match(/^(#+)\s+(.*)/);
|
|
25
|
+
if (match) {
|
|
26
|
+
const level = match[1].length;
|
|
27
|
+
const text = match[2];
|
|
28
|
+
if (level > 6) {
|
|
29
|
+
// Create a new heading node with depth 6
|
|
30
|
+
const headingNode = {
|
|
31
|
+
type: 'heading',
|
|
32
|
+
depth: level,
|
|
33
|
+
children: [{type: 'text', value: text}]
|
|
34
|
+
};
|
|
35
|
+
// Replace the paragraph node with the new heading node
|
|
36
|
+
//@ts-ignore
|
|
37
|
+
parent.children[index] = headingNode;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//
|
|
45
|
+
async function compile(content: string): Promise<string> {
|
|
46
|
+
const compiled = await mdxCompile(content, {
|
|
47
|
+
remarkPlugins: [normalizeCustomHeadings, [remarkCodeHike, codeHikeOptions]],
|
|
48
|
+
recmaPlugins: [
|
|
49
|
+
[recmaCodeHike, codeHikeOptions]
|
|
50
|
+
],
|
|
51
|
+
outputFormat: 'function-body',
|
|
52
|
+
development: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return String(compiled)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function compileBySlug(slug: string) {
|
|
59
|
+
// TODO: cwd ?
|
|
60
|
+
const filePath = path.join(process.cwd(), `${slug}.md`)
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await fs.access(filePath)
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(e)
|
|
66
|
+
return ""
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
70
|
+
|
|
71
|
+
return await compile(content)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getMDXComponent(code: string) {
|
|
75
|
+
const mdxExport = getMDXExport(code)
|
|
76
|
+
return mdxExport.default
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getMDXExport(code: string) {
|
|
80
|
+
const scope = {
|
|
81
|
+
Fragment: React.Fragment,
|
|
82
|
+
jsxs: React.createElement,
|
|
83
|
+
jsx: React.createElement,
|
|
84
|
+
jsxDEV: React.createElement,
|
|
85
|
+
}
|
|
86
|
+
const fn = new Function(...Object.keys(scope), code)
|
|
87
|
+
return fn(scope)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//
|
|
91
|
+
// function MDXContent(code: string) {
|
|
92
|
+
// return React.useMemo(
|
|
93
|
+
// () => code ? getMDXComponent(code) : null,
|
|
94
|
+
// [code]
|
|
95
|
+
// )
|
|
96
|
+
// }
|
|
97
|
+
|
|
98
|
+
// TODO: mod ts
|
|
99
|
+
async function lazyReferences(mod: any) {
|
|
100
|
+
const references: any[] = []
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(mod.default)) {
|
|
103
|
+
for (const chunk of mod.default) {
|
|
104
|
+
try {
|
|
105
|
+
const code = await compile(chunk) // TODO: do we need real path?
|
|
106
|
+
const Content = getMDXComponent(code)
|
|
107
|
+
const content = Content ? parse(Content) : null
|
|
108
|
+
|
|
109
|
+
references.push(...content.references as [])
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error(e)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.warn(`mod.default is not an array, current type is: ${typeof mod.default}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return references
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
compileBySlug,
|
|
123
|
+
lazyReferences
|
|
124
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Define the new PluginV type with a callback function that returns another function
|
|
2
|
+
import {Reference} from "./types";
|
|
3
|
+
|
|
4
|
+
// Define the new PluginV type with a callback function that returns another function
|
|
5
|
+
export type UniformPlugin<T> = (cb: (cb: () => T) => void) => (ref: Reference) => void;
|
|
6
|
+
|
|
7
|
+
// Utility type to infer if a type is an array and avoid wrapping it into an array twice
|
|
8
|
+
type NormalizeArray<T> = T extends Array<infer U> ? U[] : T;
|
|
9
|
+
|
|
10
|
+
// Infer the return type of the plugin callback properly and normalize array types
|
|
11
|
+
type PluginResult<T extends UniformPlugin<any>> = T extends UniformPlugin<infer R> ? R : never;
|
|
12
|
+
|
|
13
|
+
// Merge all plugin return types into a single object and normalize arrays
|
|
14
|
+
type MergePluginResults<T extends UniformPlugin<any>[]> = {
|
|
15
|
+
[K in keyof UnionToIntersection<PluginResult<T[number]>>]: NormalizeArray<UnionToIntersection<PluginResult<T[number]>>[K]>
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Utility type to handle intersection to an object type
|
|
19
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
20
|
+
|
|
21
|
+
// Implement the uniform function
|
|
22
|
+
export default function uniform<T extends UniformPlugin<any>[]>(
|
|
23
|
+
references: Reference[],
|
|
24
|
+
config: { plugins: T }
|
|
25
|
+
) {
|
|
26
|
+
// Infer the merged result type from all plugins
|
|
27
|
+
type ResultType = MergePluginResults<T>;
|
|
28
|
+
|
|
29
|
+
// Initialize the response with a type-safe out object
|
|
30
|
+
const response: {
|
|
31
|
+
references: Reference[]
|
|
32
|
+
out: { [K in keyof ResultType]: ResultType[K] }
|
|
33
|
+
} = {
|
|
34
|
+
references,
|
|
35
|
+
out: {} as { [K in keyof ResultType]: ResultType[K] }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const finishCallbacks = new Set();
|
|
40
|
+
|
|
41
|
+
config.plugins.forEach((plugin) => {
|
|
42
|
+
const call = plugin(cb => {
|
|
43
|
+
finishCallbacks.add(cb);
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
references.forEach((ref) => {
|
|
47
|
+
call(ref)
|
|
48
|
+
});
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
finishCallbacks.forEach(cb => {
|
|
52
|
+
if (typeof cb === "function") {
|
|
53
|
+
const resp = cb()
|
|
54
|
+
if (typeof resp !== "object") {
|
|
55
|
+
throw new Error(`Invalid callback return type: ${typeof resp}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
response.out = {
|
|
59
|
+
...response.out,
|
|
60
|
+
...resp
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error(`Invalid callback type: ${typeof cb}`)
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Example usage
|
|
71
|
+
// const examplePlugin: UniformPlugin<{ value: boolean }> = (cb) => {
|
|
72
|
+
// return (ref: Reference) => {
|
|
73
|
+
// };
|
|
74
|
+
// };
|
|
75
|
+
// function examplePlugin(cb: (cb: () => { value: boolean }) => void) {
|
|
76
|
+
// cb(() => ({
|
|
77
|
+
// value: true,
|
|
78
|
+
// }));
|
|
79
|
+
//
|
|
80
|
+
// return (ref: Reference) => {
|
|
81
|
+
//
|
|
82
|
+
// };
|
|
83
|
+
// }
|
|
84
|
+
// const response = uniform([/* references */], {
|
|
85
|
+
// plugins: [examplePlugin],
|
|
86
|
+
// });
|
|
87
|
+
// response.out
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {remark} from "remark";
|
|
2
|
+
import remarkStringify from "remark-stringify";
|
|
3
|
+
|
|
4
|
+
import {Reference} from "../types";
|
|
5
|
+
|
|
6
|
+
import {definitions, examples, heading, root} from "./utils";
|
|
7
|
+
|
|
8
|
+
// TODO: any
|
|
9
|
+
export function compile(ast: any) {
|
|
10
|
+
return remark()
|
|
11
|
+
// .use(unlimitedHeadings)
|
|
12
|
+
.use(remarkStringify, {
|
|
13
|
+
bullet: '-',
|
|
14
|
+
fences: true,
|
|
15
|
+
listItemIndent: 'one',
|
|
16
|
+
incrementListMarker: false,
|
|
17
|
+
handlers: {
|
|
18
|
+
heading(node) {
|
|
19
|
+
return `${"#".repeat(node.depth)} ${node.children[0].value}`;
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
.stringify(root(ast));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function referenceAST(ref: Reference) {
|
|
27
|
+
const md = []
|
|
28
|
+
|
|
29
|
+
const mdHeading = heading(
|
|
30
|
+
ref.title,
|
|
31
|
+
ref.canonical,
|
|
32
|
+
ref.description,
|
|
33
|
+
ref.category,
|
|
34
|
+
ref.type,
|
|
35
|
+
ref.context
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (mdHeading?.description?.length) {
|
|
39
|
+
md.push(...mdHeading.description)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
md.push(
|
|
43
|
+
mdHeading.title,
|
|
44
|
+
mdHeading.canonical,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (mdHeading.category) {
|
|
48
|
+
md.push(mdHeading.category)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (mdHeading.type) {
|
|
52
|
+
md.push(mdHeading.type)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (mdHeading?.context?.length) {
|
|
56
|
+
md.push(...mdHeading.context)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const mdExamples = examples(ref.examples)
|
|
60
|
+
const mdDefinitions = definitions(ref.definitions)
|
|
61
|
+
md.push(...mdExamples, ...mdDefinitions)
|
|
62
|
+
|
|
63
|
+
return md;
|
|
64
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {u} from "unist-builder";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ExampleRoot,
|
|
5
|
+
Definition,
|
|
6
|
+
ReferenceCategory,
|
|
7
|
+
ReferenceContext,
|
|
8
|
+
ReferenceType,
|
|
9
|
+
DefinitionProperty
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
// START_DEPTH_LEVEL is the start depth level for the markdown AST
|
|
13
|
+
// starts from 2 because 1 is reserved for the title
|
|
14
|
+
const START_DEPTH_LEVEL = 2
|
|
15
|
+
|
|
16
|
+
// TODO: fix any
|
|
17
|
+
export function root(ast: any) {
|
|
18
|
+
return u('root', ast);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function heading(
|
|
22
|
+
title: string,
|
|
23
|
+
canonical: string,
|
|
24
|
+
description: string,
|
|
25
|
+
refCategory?: ReferenceCategory,
|
|
26
|
+
refType?: ReferenceType,
|
|
27
|
+
refContext?: ReferenceContext
|
|
28
|
+
) {
|
|
29
|
+
const uTitle = u(
|
|
30
|
+
'heading',
|
|
31
|
+
{depth: START_DEPTH_LEVEL},
|
|
32
|
+
[u('text', `!!references ${title}`)]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const uCanonical = u(
|
|
36
|
+
'heading',
|
|
37
|
+
{depth: uTitle.depth + 1},
|
|
38
|
+
[u('text', `!canonical ${canonical}`)]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
let uDesc = [
|
|
42
|
+
u(
|
|
43
|
+
'heading',
|
|
44
|
+
{depth: START_DEPTH_LEVEL},
|
|
45
|
+
[u('text', `!description`),]
|
|
46
|
+
),
|
|
47
|
+
u('paragraph', [u('text', description)])
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
let uRefCategory
|
|
52
|
+
if (refCategory) {
|
|
53
|
+
uRefCategory = u(
|
|
54
|
+
'heading',
|
|
55
|
+
{depth: uTitle.depth + 1},
|
|
56
|
+
[u('text', `!category ${refCategory}`)]
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let uRefType
|
|
61
|
+
if (refType) {
|
|
62
|
+
uRefType = u(
|
|
63
|
+
'heading',
|
|
64
|
+
{depth: uTitle.depth + 1},
|
|
65
|
+
[u('text', `!type ${refType}`)]
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let uContext = []
|
|
70
|
+
|
|
71
|
+
if (refContext && Object.keys(refContext)) {
|
|
72
|
+
uContext.push(u(
|
|
73
|
+
'heading',
|
|
74
|
+
{depth: uTitle.depth + 1},
|
|
75
|
+
[
|
|
76
|
+
u('text', `!context`),
|
|
77
|
+
]
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
for (const [key, value] of Object.entries(refContext)) {
|
|
82
|
+
uContext.push(u(
|
|
83
|
+
'heading',
|
|
84
|
+
{depth: uContext[0].depth + 1},
|
|
85
|
+
[u('text', `!${key} ${value}`)]
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
title: uTitle,
|
|
93
|
+
canonical: uCanonical,
|
|
94
|
+
description: uDesc,
|
|
95
|
+
category: uRefCategory || null,
|
|
96
|
+
type: uRefType || null,
|
|
97
|
+
context: uContext || null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function examples(examples: ExampleRoot) {
|
|
102
|
+
const md = []
|
|
103
|
+
|
|
104
|
+
const uExampleRoot = u(
|
|
105
|
+
'heading',
|
|
106
|
+
{depth: START_DEPTH_LEVEL + 1},
|
|
107
|
+
[u('text', `!examples`)]
|
|
108
|
+
)
|
|
109
|
+
md.push(uExampleRoot)
|
|
110
|
+
|
|
111
|
+
examples.groups.forEach(group => {
|
|
112
|
+
const uExampleGroups = u(
|
|
113
|
+
'heading',
|
|
114
|
+
{depth: uExampleRoot.depth + 1},
|
|
115
|
+
[u('text', `!!groups`)]
|
|
116
|
+
)
|
|
117
|
+
md.push(uExampleGroups)
|
|
118
|
+
|
|
119
|
+
const uGroupDescription = u(
|
|
120
|
+
'heading',
|
|
121
|
+
{depth: uExampleGroups.depth + 1},
|
|
122
|
+
[u('text', `!description ${group.description}`)]
|
|
123
|
+
)
|
|
124
|
+
md.push(uGroupDescription)
|
|
125
|
+
|
|
126
|
+
group.examples.forEach(example => {
|
|
127
|
+
const uExamples = u(
|
|
128
|
+
'heading',
|
|
129
|
+
{depth: uExampleGroups.depth + 1},
|
|
130
|
+
[u('text', `!!examples`)]
|
|
131
|
+
)
|
|
132
|
+
md.push(uExamples)
|
|
133
|
+
|
|
134
|
+
const codeBlock = u(
|
|
135
|
+
'heading',
|
|
136
|
+
{depth: uExamples.depth + 1},
|
|
137
|
+
[u('text', `!codeblock`)]
|
|
138
|
+
)
|
|
139
|
+
md.push(codeBlock)
|
|
140
|
+
|
|
141
|
+
const codeBlockTitle = u(
|
|
142
|
+
'heading',
|
|
143
|
+
{depth: codeBlock.depth + 1},
|
|
144
|
+
[u('text', `!title ${example.codeblock.title}`)]
|
|
145
|
+
)
|
|
146
|
+
md.push(codeBlockTitle)
|
|
147
|
+
|
|
148
|
+
const tabs = u(
|
|
149
|
+
'heading',
|
|
150
|
+
{depth: codeBlock.depth + 1},
|
|
151
|
+
[u('text', `!!tabs`)]
|
|
152
|
+
)
|
|
153
|
+
md.push(tabs)
|
|
154
|
+
|
|
155
|
+
example.codeblock.tabs.forEach(tab => {
|
|
156
|
+
const code = u('code', {
|
|
157
|
+
lang: tab.language,
|
|
158
|
+
meta: `!code ${tab.title}`
|
|
159
|
+
}, tab.code);
|
|
160
|
+
|
|
161
|
+
md.push(code)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return md
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function definitions(definitions: Definition[]) {
|
|
171
|
+
const md: any[] = []
|
|
172
|
+
|
|
173
|
+
definitions.forEach(definition => {
|
|
174
|
+
const uDefinition = u(
|
|
175
|
+
'heading',
|
|
176
|
+
{depth: START_DEPTH_LEVEL + 1},
|
|
177
|
+
[u('text', `!!definitions`)]
|
|
178
|
+
)
|
|
179
|
+
md.push(uDefinition)
|
|
180
|
+
|
|
181
|
+
md.push(u(
|
|
182
|
+
'heading',
|
|
183
|
+
{depth: uDefinition.depth + 1},
|
|
184
|
+
[u('text', `!title ${definition.title}`)]
|
|
185
|
+
))
|
|
186
|
+
|
|
187
|
+
definition.properties.forEach(prop => {
|
|
188
|
+
properties(
|
|
189
|
+
uDefinition.depth + 1,
|
|
190
|
+
{
|
|
191
|
+
name: prop.name,
|
|
192
|
+
type: prop.type,
|
|
193
|
+
description: prop.description,
|
|
194
|
+
properties: prop.properties // TODO: fix ts
|
|
195
|
+
},
|
|
196
|
+
md,
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return md
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// TODO: any[]
|
|
205
|
+
export function properties(
|
|
206
|
+
depth: number,
|
|
207
|
+
props: DefinitionProperty,
|
|
208
|
+
output: any[] = []
|
|
209
|
+
) {
|
|
210
|
+
const paramName = props.name;
|
|
211
|
+
|
|
212
|
+
const propTitle = `!!properties ${paramName}`; // TODO: check if `!!properties is enough`
|
|
213
|
+
const uPropTitle = u('heading', {depth}, [u('text', propTitle)]);
|
|
214
|
+
const uPropName = u('paragraph', {depth}, [u('text', `!name ${paramName}`)]);
|
|
215
|
+
const uPropType = u('paragraph', {depth}, [u('text', `!type ${props.type}`)]);
|
|
216
|
+
const uPropDesc = u('paragraph', {depth}, [u('text', props.description || '')]);
|
|
217
|
+
|
|
218
|
+
output.push(
|
|
219
|
+
uPropTitle,
|
|
220
|
+
uPropName,
|
|
221
|
+
uPropType,
|
|
222
|
+
uPropDesc
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (props.properties) {
|
|
226
|
+
const deepDepth = depth + 1
|
|
227
|
+
|
|
228
|
+
for (const prop of props.properties) {
|
|
229
|
+
properties(deepDepth, prop, output)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// TODO: concept only
|
|
2
|
+
export enum ReferenceCategory {
|
|
3
|
+
// for React
|
|
4
|
+
COMPONENTS = "components",
|
|
5
|
+
HOOKS = "hooks",
|
|
6
|
+
// end for React
|
|
7
|
+
|
|
8
|
+
// for API
|
|
9
|
+
REST = "rest",
|
|
10
|
+
GRAPHQL = "graphql",
|
|
11
|
+
// end for API
|
|
12
|
+
|
|
13
|
+
// for code
|
|
14
|
+
FUNCTIONS = "functions",
|
|
15
|
+
//
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: concept only
|
|
19
|
+
export enum ReferenceType {
|
|
20
|
+
// for React
|
|
21
|
+
COMPONENT = "component",
|
|
22
|
+
HOOK = "hook",
|
|
23
|
+
// end for React
|
|
24
|
+
|
|
25
|
+
// for API
|
|
26
|
+
REST_HTTP_GET = "rest_get",
|
|
27
|
+
REST_HTTP_POST = "rest_post",
|
|
28
|
+
REST_HTTP_PUT = "rest_put",
|
|
29
|
+
REST_HTTP_PATCH = "rest_patch",
|
|
30
|
+
REST_HTTP_DELETE = "rest_delete",
|
|
31
|
+
// ---
|
|
32
|
+
GRAPHQL_QUERY = "graphql_query",
|
|
33
|
+
GRAPHQL_MUTATION = "graphql_mutation",
|
|
34
|
+
// end for API
|
|
35
|
+
|
|
36
|
+
// for code
|
|
37
|
+
FUNCTION_JS = "function_js",
|
|
38
|
+
// end for code
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface GraphQLReferenceContext {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TODO: custom value?
|
|
45
|
+
export interface OpenAPIReferenceContext {
|
|
46
|
+
method: string;
|
|
47
|
+
|
|
48
|
+
path: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ReferenceContext = GraphQLReferenceContext | OpenAPIReferenceContext;
|
|
52
|
+
|
|
53
|
+
export interface ExampleRoot {
|
|
54
|
+
groups: ExampleGroup[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ExampleGroup {
|
|
58
|
+
description?: string;
|
|
59
|
+
|
|
60
|
+
examples: Example[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Example {
|
|
64
|
+
description?: string; // TODO: replace with title ?
|
|
65
|
+
|
|
66
|
+
codeblock: CodeBlock;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CodeBlock {
|
|
70
|
+
title?: string;
|
|
71
|
+
|
|
72
|
+
tabs: CodeBlockTab[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface GraphQLExampleContext {
|
|
76
|
+
schema?: any; // TODO:
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface OpenAPIExampleContext {
|
|
80
|
+
status?: number;
|
|
81
|
+
|
|
82
|
+
content?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type ExampleContext = GraphQLExampleContext | OpenAPIExampleContext;
|
|
86
|
+
|
|
87
|
+
export interface CodeBlockTab {
|
|
88
|
+
// title of the tab e.g "JavaScript"
|
|
89
|
+
title: string;
|
|
90
|
+
|
|
91
|
+
// code in the tab e.g "console.log('Hello World')"
|
|
92
|
+
code: string
|
|
93
|
+
|
|
94
|
+
// language of the code e.g "js"
|
|
95
|
+
language: string;
|
|
96
|
+
|
|
97
|
+
// context of the generation method e.g openapi or graphql
|
|
98
|
+
context?: ExampleContext;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface Reference {
|
|
102
|
+
title: string;
|
|
103
|
+
description: string;
|
|
104
|
+
canonical: string;
|
|
105
|
+
|
|
106
|
+
definitions: Definition[]
|
|
107
|
+
examples: ExampleRoot
|
|
108
|
+
|
|
109
|
+
category?: ReferenceCategory; // TODO: do we need that?
|
|
110
|
+
type?: ReferenceType; // TODO: do we need that?
|
|
111
|
+
context?: ReferenceContext;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface Definition {
|
|
115
|
+
title: string;
|
|
116
|
+
|
|
117
|
+
properties: DefinitionProperty[];
|
|
118
|
+
|
|
119
|
+
type?: string;
|
|
120
|
+
|
|
121
|
+
id?: string;
|
|
122
|
+
|
|
123
|
+
description?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface DefinitionProperty {
|
|
127
|
+
name: string;
|
|
128
|
+
|
|
129
|
+
type: string;
|
|
130
|
+
|
|
131
|
+
description: string;
|
|
132
|
+
|
|
133
|
+
properties?: DefinitionProperty[];
|
|
134
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import {Sidebar, FrontMatter, PageFrontMatter} from "@xyd-js/core";
|
|
4
|
+
|
|
5
|
+
import {Reference} from "./types";
|
|
6
|
+
import uniform from "./index";
|
|
7
|
+
|
|
8
|
+
// interface UniformFrontMatter extends FrontMatter { // TODO: it's concept only
|
|
9
|
+
// scopes?: string
|
|
10
|
+
// }
|
|
11
|
+
|
|
12
|
+
type GroupMap = {
|
|
13
|
+
[key: string]: {
|
|
14
|
+
__groups: GroupMap
|
|
15
|
+
pages: Set<string>
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface pluginNavigationOptions {
|
|
20
|
+
urlPrefix: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function pluginNavigation(options: pluginNavigationOptions) {
|
|
24
|
+
if (!options.urlPrefix) {
|
|
25
|
+
throw new Error("urlPrefix is required")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return function pluginNavigationInner(cb: (cb: () => {
|
|
29
|
+
pageFrontMatter: PageFrontMatter
|
|
30
|
+
sidebar: Sidebar[]
|
|
31
|
+
}) => void) {
|
|
32
|
+
const pageFrontMatter: PageFrontMatter = {}
|
|
33
|
+
const groupMaps: GroupMap = {}
|
|
34
|
+
|
|
35
|
+
cb(() => {
|
|
36
|
+
return {
|
|
37
|
+
pageFrontMatter: pageFrontMatter,
|
|
38
|
+
sidebar: convertGroupMapsToNavigations(groupMaps) as Sidebar[]
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return (ref: Reference) => {
|
|
43
|
+
const content = matter(ref.description || "") // TODO: pluginMatter before?
|
|
44
|
+
|
|
45
|
+
if (content.data) {
|
|
46
|
+
const data = content.data as FrontMatter
|
|
47
|
+
|
|
48
|
+
const pagePath = path.join(options.urlPrefix, ref.canonical)
|
|
49
|
+
|
|
50
|
+
if (data.title) {
|
|
51
|
+
pageFrontMatter[pagePath] = {
|
|
52
|
+
title: data.title,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (data.group) {
|
|
57
|
+
if (typeof content?.data?.group === "string") {
|
|
58
|
+
// TODO: seek nested group (it's not always from 0)
|
|
59
|
+
throw new Error("group as string is not supported yet")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
content.data.group.reduce((groups: GroupMap, group: string, i: number) => {
|
|
63
|
+
if (!groups[group]) {
|
|
64
|
+
groups[group] = {
|
|
65
|
+
__groups: {},
|
|
66
|
+
pages: new Set()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (i === content.data.group.length - 1) {
|
|
71
|
+
groups[group].pages.add(pagePath)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return groups[group].__groups
|
|
75
|
+
}, groupMaps)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// back description to original without frontmatter
|
|
79
|
+
ref.description = content.content
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
function convertGroupMapsToNavigations(groupMaps: GroupMap): Sidebar[] {
|
|
87
|
+
const nav: Sidebar[] = []
|
|
88
|
+
|
|
89
|
+
Object.keys(groupMaps).map((groupName) => {
|
|
90
|
+
const current = groupMaps[groupName]
|
|
91
|
+
|
|
92
|
+
const pages: string[] | Sidebar[] = []
|
|
93
|
+
|
|
94
|
+
current.pages.forEach((page: string) => {
|
|
95
|
+
pages.push(page)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (Object.keys(current.__groups).length) {
|
|
99
|
+
const subNav: Sidebar = {
|
|
100
|
+
group: groupName,
|
|
101
|
+
pages: convertGroupMapsToNavigations(current.__groups)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
nav.push(subNav)
|
|
105
|
+
} else {
|
|
106
|
+
nav.push({
|
|
107
|
+
group: groupName,
|
|
108
|
+
pages,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return nav
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// example usage:
|
|
117
|
+
// const response = uniform([/* references */], {
|
|
118
|
+
// plugins: [pluginNavigation({
|
|
119
|
+
// urlPrefix: "/docs"
|
|
120
|
+
// })],
|
|
121
|
+
// });
|
|
122
|
+
|
|
123
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
|
15
|
+
},
|
|
16
|
+
"include": ["examples/basic-example/**/*.ts", "src/**/*.ts"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {defineConfig, Options} from 'tsup';
|
|
2
|
+
|
|
3
|
+
const config: Options = {
|
|
4
|
+
entry: {
|
|
5
|
+
index: 'index.ts',
|
|
6
|
+
markdown: 'markdown.ts',
|
|
7
|
+
content: 'content.ts',
|
|
8
|
+
},
|
|
9
|
+
format: ['esm', 'cjs'], // Output both ESM and CJS formats
|
|
10
|
+
target: 'node16', // Ensure compatibility with Node.js 16
|
|
11
|
+
dts: {
|
|
12
|
+
entry: {
|
|
13
|
+
index: 'index.ts',
|
|
14
|
+
markdown: 'markdown.ts',
|
|
15
|
+
content: 'content.ts',
|
|
16
|
+
},
|
|
17
|
+
resolve: true, // Resolve external types
|
|
18
|
+
},
|
|
19
|
+
splitting: false, // Disable code splitting
|
|
20
|
+
sourcemap: true, // Generate source maps
|
|
21
|
+
clean: true, // Clean the output directory before each build
|
|
22
|
+
esbuildOptions: (options) => {
|
|
23
|
+
options.platform = 'node'; // Ensure the platform is set to Node.js
|
|
24
|
+
options.external = [
|
|
25
|
+
'react',
|
|
26
|
+
'fs',
|
|
27
|
+
'path',
|
|
28
|
+
'node:fs',
|
|
29
|
+
'node:fs/promises',
|
|
30
|
+
|
|
31
|
+
'codehike',
|
|
32
|
+
'unist-util-visit',
|
|
33
|
+
'@mdx-js/mdx'
|
|
34
|
+
]; // Mark these modules as external
|
|
35
|
+
options.loader = { '.js': 'jsx' }; // Ensure proper handling of .js files
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default defineConfig(config);
|