@xyd-js/plugin-docs 0.1.0-xyd.2 → 0.1.0-xyd.4
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/CHANGELOG.md +47 -0
- package/TODO.md +1 -0
- package/package.json +7 -17
- package/src/const.ts +7 -0
- package/src/declarations.d.ts +23 -0
- package/src/index.ts +305 -0
- package/src/pages/context.tsx +9 -0
- package/src/pages/layout.tsx +263 -0
- package/src/pages/metatags.ts +96 -0
- package/src/pages/page.tsx +363 -0
- package/src/presets/docs/index.ts +232 -0
- package/src/presets/docs/settings.ts +75 -0
- package/src/presets/graphql/index.ts +69 -0
- package/src/presets/openapi/index.ts +66 -0
- package/src/presets/sources/index.ts +74 -0
- package/src/presets/uniform/index.ts +751 -0
- package/src/types.ts +38 -0
- package/src/utils.ts +19 -0
- package/tsconfig.json +51 -0
- package/tsup.config.ts +74 -0
- package/dist/index.d.ts +0 -42
- package/dist/index.js +0 -4307
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import matterStringify from "gray-matter/lib/stringify";
|
|
6
|
+
import { Plugin as VitePlugin } from "vite"
|
|
7
|
+
import { route } from "@react-router/dev/routes";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Settings,
|
|
11
|
+
APIFile,
|
|
12
|
+
Sidebar,
|
|
13
|
+
SidebarRoute,
|
|
14
|
+
Metadata
|
|
15
|
+
} from "@xyd-js/core";
|
|
16
|
+
import uniform, {
|
|
17
|
+
pluginNavigation,
|
|
18
|
+
Reference,
|
|
19
|
+
ReferenceType,
|
|
20
|
+
OpenAPIReferenceContext,
|
|
21
|
+
GraphQLReferenceContext
|
|
22
|
+
} from "@xyd-js/uniform";
|
|
23
|
+
import { uniformPluginXDocsSidebar } from "@xyd-js/openapi";
|
|
24
|
+
|
|
25
|
+
import { Preset, PresetData } from "../../types";
|
|
26
|
+
|
|
27
|
+
import { createRequire } from 'module';
|
|
28
|
+
import { VIRTUAL_CONTENT_FOLDER } from "../../const";
|
|
29
|
+
import { getHostPath } from "../../utils";
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const matter = require('gray-matter'); // TODO: !!! BETTER SOLUTION !!!
|
|
33
|
+
|
|
34
|
+
export async function ensureAndCleanupVirtualFolder() {
|
|
35
|
+
try {
|
|
36
|
+
// Create directory recursively if it doesn't exist
|
|
37
|
+
await fs.mkdir(VIRTUAL_CONTENT_FOLDER, { recursive: true });
|
|
38
|
+
|
|
39
|
+
// Read all files and directories in the folder
|
|
40
|
+
const entries = await fs.readdir(VIRTUAL_CONTENT_FOLDER, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
// Delete each entry recursively
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const fullPath = path.join(VIRTUAL_CONTENT_FOLDER, entry.name);
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
await fs.rm(fullPath, { recursive: true, force: true });
|
|
47
|
+
} else {
|
|
48
|
+
await fs.unlink(fullPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error managing virtual folder:', error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// TODO: !!!!! REFACTOR PLUGIN-ZERO AND ITS DEPS FOR MORE READABLE CODE AND BETTER API !!!!
|
|
57
|
+
|
|
58
|
+
export interface uniformPresetOptions {
|
|
59
|
+
urlPrefix?: string
|
|
60
|
+
sourceTheme?: boolean
|
|
61
|
+
disableFSWrite?: boolean
|
|
62
|
+
fileRouting?: { [key: string]: string }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function flatPages(
|
|
66
|
+
sidebar: (SidebarRoute | Sidebar)[],
|
|
67
|
+
groups: { [key: string]: string },
|
|
68
|
+
resp: string[] = [],
|
|
69
|
+
) {
|
|
70
|
+
sidebar.map(async side => {
|
|
71
|
+
if ("route" in side) {
|
|
72
|
+
side?.pages.map(item => {
|
|
73
|
+
return flatPages([item], groups, resp)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (groups[side.group || ""]) {
|
|
80
|
+
const link = groups[side.group || ""]
|
|
81
|
+
|
|
82
|
+
resp.push(link)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
side?.pages?.map(async page => {
|
|
86
|
+
if (typeof page === "string") {
|
|
87
|
+
resp.push(page)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ("virtual" in page) {
|
|
92
|
+
resp.push(page.virtual)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return flatPages([page], groups, resp)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return resp
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function flatGroups(
|
|
104
|
+
sidebar: (SidebarRoute | Sidebar)[],
|
|
105
|
+
resp: { [key: string]: string } = {}
|
|
106
|
+
) {
|
|
107
|
+
sidebar.map(async side => {
|
|
108
|
+
if ("route" in side) {
|
|
109
|
+
side?.pages.map(item => {
|
|
110
|
+
return flatGroups([item], resp)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (side.group) {
|
|
117
|
+
if (resp[side.group]) {
|
|
118
|
+
console.error('group already exists', side.group)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const first = side?.pages?.[0]
|
|
122
|
+
if (first && typeof first === "string") {
|
|
123
|
+
const chunks = first.split("/")
|
|
124
|
+
chunks[chunks.length - 1] = side.group || ""
|
|
125
|
+
const groupLink = chunks.join("/")
|
|
126
|
+
|
|
127
|
+
resp[side.group] = groupLink
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
side?.pages?.map(async page => {
|
|
132
|
+
if (typeof page === "string") {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ("virtual" in page) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return flatGroups([page], resp)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return resp
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function uniformSidebarLevelMap(pages: string[]) {
|
|
148
|
+
const out = {};
|
|
149
|
+
let level = 0;
|
|
150
|
+
|
|
151
|
+
function recursive(items: string[]) {
|
|
152
|
+
for (const item of items) {
|
|
153
|
+
out[item] = level++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
recursive(pages);
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Helper function to read markdown files with support for both .mdx and .md extensions
|
|
162
|
+
async function readMarkdownFile(root: string, page: string): Promise<string> {
|
|
163
|
+
try {
|
|
164
|
+
// Try .mdx first
|
|
165
|
+
return await fs.readFile(path.join(root, page + '.mdx'), "utf-8");
|
|
166
|
+
} catch (e) {
|
|
167
|
+
// If .mdx fails, try .md
|
|
168
|
+
try {
|
|
169
|
+
return await fs.readFile(path.join(root, page + '.md'), "utf-8");
|
|
170
|
+
} catch (e) {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return ""
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function uniformResolver(
|
|
178
|
+
settings: Settings,
|
|
179
|
+
root: string,
|
|
180
|
+
matchRoute: string,
|
|
181
|
+
apiFile: string,
|
|
182
|
+
uniformApiResolver: (filePath: string) => Promise<Reference[]>,
|
|
183
|
+
sidebar?: (SidebarRoute | Sidebar)[],
|
|
184
|
+
options?: uniformPresetOptions,
|
|
185
|
+
uniformType?: UniformType,
|
|
186
|
+
disableFSWrite?: boolean
|
|
187
|
+
) {
|
|
188
|
+
let urlPrefix = ""
|
|
189
|
+
|
|
190
|
+
if (matchRoute && sidebar) {
|
|
191
|
+
sidebar.forEach((sidebar) => {
|
|
192
|
+
if ("route" in sidebar) {
|
|
193
|
+
if (sidebar.route === matchRoute) {
|
|
194
|
+
if (urlPrefix) {
|
|
195
|
+
throw new Error('multiple sidebars found for apiFile match')
|
|
196
|
+
}
|
|
197
|
+
urlPrefix = sidebar.route
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resolvedApiFile = path.relative(process.cwd(), path.resolve(process.cwd(), apiFile))
|
|
204
|
+
const uniformRefs = await uniformApiResolver(resolvedApiFile)
|
|
205
|
+
const plugins = globalThis.__xydUserUniformVitePlugins || []
|
|
206
|
+
|
|
207
|
+
if (!urlPrefix && options?.fileRouting?.[resolvedApiFile]) {
|
|
208
|
+
matchRoute = options.fileRouting[resolvedApiFile]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!urlPrefix && matchRoute) {
|
|
212
|
+
sidebar?.push({
|
|
213
|
+
route: matchRoute,
|
|
214
|
+
pages: []
|
|
215
|
+
})
|
|
216
|
+
urlPrefix = matchRoute
|
|
217
|
+
}
|
|
218
|
+
if (!urlPrefix && options?.urlPrefix) {
|
|
219
|
+
urlPrefix = options.urlPrefix
|
|
220
|
+
}
|
|
221
|
+
if (!urlPrefix) {
|
|
222
|
+
throw new Error('(uniformResolver): urlPrefix not found')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (uniformType === "openapi") {
|
|
226
|
+
plugins.push(uniformPluginXDocsSidebar)
|
|
227
|
+
}
|
|
228
|
+
const uniformWithNavigation = uniform(uniformRefs, {
|
|
229
|
+
plugins: [
|
|
230
|
+
...plugins,
|
|
231
|
+
pluginNavigation(settings, {
|
|
232
|
+
urlPrefix,
|
|
233
|
+
}),
|
|
234
|
+
]
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
let pageLevels = {}
|
|
238
|
+
|
|
239
|
+
const uniformData = {
|
|
240
|
+
slugs: {},
|
|
241
|
+
data: [] as any[], // TODO: fix any
|
|
242
|
+
i: 0,
|
|
243
|
+
set: (slug, content: string, options = {}) => {
|
|
244
|
+
if (uniformData.slugs[slug]) {
|
|
245
|
+
console.error('slug already exists', slug)
|
|
246
|
+
}
|
|
247
|
+
// TODO: in the future custom sort
|
|
248
|
+
const level = pageLevels[slug]
|
|
249
|
+
|
|
250
|
+
uniformData.data[level] = {
|
|
251
|
+
slug,
|
|
252
|
+
content,
|
|
253
|
+
options
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const uniformSidebars: SidebarRoute[] = []
|
|
259
|
+
|
|
260
|
+
if (sidebar && matchRoute) {
|
|
261
|
+
// TODO: DRY
|
|
262
|
+
sidebar.forEach((sidebar) => {
|
|
263
|
+
if ("route" in sidebar) {
|
|
264
|
+
if (sidebar.route === matchRoute) {
|
|
265
|
+
uniformSidebars.push(sidebar)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
if (uniformSidebars.length > 1) {
|
|
271
|
+
throw new Error('multiple sidebars found for uniform match')
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
{
|
|
276
|
+
const otherUniformPages = flatPages(uniformSidebars, {})
|
|
277
|
+
const groups = flatGroups(uniformWithNavigation.out.sidebar)
|
|
278
|
+
const flatUniformPages = [
|
|
279
|
+
...otherUniformPages,
|
|
280
|
+
...flatPages(
|
|
281
|
+
uniformWithNavigation.out.sidebar,
|
|
282
|
+
groups // TODO: we dont need groups - because it comes to structured page levels
|
|
283
|
+
),
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
pageLevels = uniformSidebarLevelMap(flatUniformPages)
|
|
287
|
+
|
|
288
|
+
{
|
|
289
|
+
// TODO: below should be inside uniform?
|
|
290
|
+
// TODO: custom `fn` logic?
|
|
291
|
+
await Promise.all(otherUniformPages.map(async (page) => {
|
|
292
|
+
const content = await readMarkdownFile(root, page);
|
|
293
|
+
uniformData.set(page, content + "\n");
|
|
294
|
+
}))
|
|
295
|
+
|
|
296
|
+
await Promise.all(Object.keys(groups).map(async (group) => {
|
|
297
|
+
try {
|
|
298
|
+
// TODO: only if `group_index`
|
|
299
|
+
const page = groups[group]
|
|
300
|
+
const content = await readMarkdownFile(root, page);
|
|
301
|
+
uniformData.set(page, content + "\n");
|
|
302
|
+
} catch (e) {
|
|
303
|
+
// Silently continue if file not found
|
|
304
|
+
}
|
|
305
|
+
}))
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
{
|
|
310
|
+
const routeFolder = path.join(root, matchRoute)
|
|
311
|
+
try {
|
|
312
|
+
await fs.access(routeFolder);
|
|
313
|
+
} catch {
|
|
314
|
+
await fs.mkdir(routeFolder, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let composedFileMap: Record<string, string> = {}
|
|
319
|
+
if (!settings.engine?.uniform?.store) {
|
|
320
|
+
composedFileMap = await composeFileMap(root, matchRoute)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const basePath = settings.engine?.uniform?.store
|
|
324
|
+
? root
|
|
325
|
+
: path.join(root, VIRTUAL_CONTENT_FOLDER)
|
|
326
|
+
|
|
327
|
+
await Promise.all(
|
|
328
|
+
uniformWithNavigation.references.map(async (ref) => {
|
|
329
|
+
const byCanonical = path.join(urlPrefix, ref.canonical)
|
|
330
|
+
const mdPath = path.join(basePath, byCanonical + '.md')
|
|
331
|
+
|
|
332
|
+
const frontmatter = uniformWithNavigation.out.pageFrontMatter[byCanonical]
|
|
333
|
+
|
|
334
|
+
if (!frontmatter) {
|
|
335
|
+
console.error('frontmatter not found', byCanonical)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let meta: Metadata = {
|
|
340
|
+
title: frontmatter.title,
|
|
341
|
+
layout: "wide"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
// const mdFilePath = path.join(basePath, byCanonical)
|
|
346
|
+
const absoluteApiFile = path.join(
|
|
347
|
+
process.cwd(),
|
|
348
|
+
apiFile,
|
|
349
|
+
)
|
|
350
|
+
// const relativeApiFile = path.relative(
|
|
351
|
+
// mdFilePath,
|
|
352
|
+
// absoluteApiFile
|
|
353
|
+
// )
|
|
354
|
+
const resolvedApiFile = absoluteApiFile // TODO: leave absolute or relative?
|
|
355
|
+
let region = ""
|
|
356
|
+
// TODO: in the future more advanced composition? - not only like `GET /users/{id}`
|
|
357
|
+
switch (uniformType) {
|
|
358
|
+
case "graphql": {
|
|
359
|
+
const ctx = ref.context as GraphQLReferenceContext;
|
|
360
|
+
region = `${ctx.graphqlTypeShort}.${ctx?.graphqlName}`
|
|
361
|
+
|
|
362
|
+
meta.graphql = `${resolvedApiFile}#${region}`
|
|
363
|
+
|
|
364
|
+
break
|
|
365
|
+
}
|
|
366
|
+
case "openapi": {
|
|
367
|
+
const ctx = ref.context as OpenAPIReferenceContext;
|
|
368
|
+
const method = (ctx?.method || "").toUpperCase()
|
|
369
|
+
if (method && ctx?.path) {
|
|
370
|
+
region = `${method} ${ctx?.path}`
|
|
371
|
+
} else if (ctx.componentSchema) {
|
|
372
|
+
region = "/components/schemas/" + ctx.componentSchema
|
|
373
|
+
}
|
|
374
|
+
meta.openapi = `${resolvedApiFile}#${region}`
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let composedContent = ""
|
|
380
|
+
if (region && composedFileMap[region]) {
|
|
381
|
+
const content = await fs.readFile(composedFileMap[region], 'utf-8');
|
|
382
|
+
const resp = matter(content);
|
|
383
|
+
|
|
384
|
+
meta = {
|
|
385
|
+
...meta,
|
|
386
|
+
...composyingMetaProps(resp.data, "title", "description", "layout")
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
composedContent = resp.content
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const content = matterStringify({ content: composedContent }, meta);
|
|
393
|
+
|
|
394
|
+
if (!disableFSWrite) {
|
|
395
|
+
try {
|
|
396
|
+
await fs.access(path.dirname(mdPath));
|
|
397
|
+
} catch {
|
|
398
|
+
await fs.mkdir(path.dirname(mdPath), { recursive: true });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
await fs.writeFile(mdPath, content)
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if (!sidebar) {
|
|
407
|
+
return {
|
|
408
|
+
sidebar: [
|
|
409
|
+
{
|
|
410
|
+
route: matchRoute,
|
|
411
|
+
pages: uniformWithNavigation.out.sidebar
|
|
412
|
+
}
|
|
413
|
+
] as SidebarRoute[],
|
|
414
|
+
data: uniformData.data
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (matchRoute) {
|
|
419
|
+
// TODO: in the future custom position - before / after
|
|
420
|
+
uniformSidebars[0].pages.unshift(...uniformWithNavigation.out.sidebar)
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
data: uniformData.data,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
sidebar.unshift({
|
|
428
|
+
route: matchRoute,
|
|
429
|
+
pages: uniformWithNavigation.out.sidebar
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
data: uniformData.data,
|
|
434
|
+
composedFileMap
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const allowedMetaProps = ['title', 'description', 'layout'] as const;
|
|
439
|
+
|
|
440
|
+
type AllowedMetaProps = Pick<Metadata, typeof allowedMetaProps[number]>;
|
|
441
|
+
|
|
442
|
+
function composyingMetaProps(meta: Metadata, ...props: (keyof AllowedMetaProps)[]) {
|
|
443
|
+
let newProps = {}
|
|
444
|
+
props.forEach(prop => {
|
|
445
|
+
if (allowedMetaProps.includes(prop as typeof allowedMetaProps[number]) && typeof meta[prop] === "string") {
|
|
446
|
+
newProps[prop] = meta[prop] as string;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
return newProps;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function composeFileMap(basePath: string, matchRoute: string) {
|
|
454
|
+
const routeMap: Record<string, string> = {};
|
|
455
|
+
|
|
456
|
+
async function processDirectory(dirPath: string) {
|
|
457
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
458
|
+
|
|
459
|
+
for (const entry of entries) {
|
|
460
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
461
|
+
|
|
462
|
+
if (entry.isDirectory()) {
|
|
463
|
+
await processDirectory(fullPath);
|
|
464
|
+
} else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
|
|
465
|
+
try {
|
|
466
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
467
|
+
const { data: frontmatter } = matter(content);
|
|
468
|
+
|
|
469
|
+
if (frontmatter && frontmatter.openapi) {
|
|
470
|
+
const route = frontmatter.openapi;
|
|
471
|
+
routeMap[route] = path.join(matchRoute, entry.name);
|
|
472
|
+
}
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error(`Error processing file ${fullPath}:`, error);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await processDirectory(path.join(basePath, matchRoute));
|
|
481
|
+
return routeMap;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// preinstall adds uniform navigation to settings
|
|
485
|
+
function preinstall(
|
|
486
|
+
id: string,
|
|
487
|
+
uniformApiResolver: (filePath: string) => Promise<Reference[]>,
|
|
488
|
+
apiFile: APIFile,
|
|
489
|
+
uniformType: UniformType,
|
|
490
|
+
disableFSWrite?: boolean,
|
|
491
|
+
options?: uniformPresetOptions,
|
|
492
|
+
) {
|
|
493
|
+
return function preinstallInner(innerOptions: any) {
|
|
494
|
+
return async function uniformPluginInner(settings: Settings, data: PresetData) {
|
|
495
|
+
const root = process.cwd()
|
|
496
|
+
|
|
497
|
+
if (!apiFile) {
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const resp: any[] = []
|
|
502
|
+
|
|
503
|
+
// TODO: support NOT ROUTE MATCH
|
|
504
|
+
|
|
505
|
+
if (typeof apiFile === "string") {
|
|
506
|
+
const routeMatch = id
|
|
507
|
+
|
|
508
|
+
const resolved = await uniformResolver(
|
|
509
|
+
settings,
|
|
510
|
+
root,
|
|
511
|
+
routeMatch,
|
|
512
|
+
apiFile,
|
|
513
|
+
uniformApiResolver,
|
|
514
|
+
settings?.navigation?.sidebar,
|
|
515
|
+
{
|
|
516
|
+
...options,
|
|
517
|
+
...innerOptions,
|
|
518
|
+
},
|
|
519
|
+
uniformType,
|
|
520
|
+
disableFSWrite
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
if (resolved.sidebar) {
|
|
524
|
+
settings.navigation = {
|
|
525
|
+
...settings?.navigation,
|
|
526
|
+
sidebar: !settings.navigation?.sidebar
|
|
527
|
+
? resolved.sidebar
|
|
528
|
+
: [
|
|
529
|
+
...resolved.sidebar,
|
|
530
|
+
...!settings.navigation?.sidebar || [],
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
resp.push({
|
|
536
|
+
urlPrefix: routeMatch.startsWith("/") ? routeMatch : `/${routeMatch}`,
|
|
537
|
+
data: resolved.data,
|
|
538
|
+
})
|
|
539
|
+
} else {
|
|
540
|
+
async function resolve(
|
|
541
|
+
routeMatch: string,
|
|
542
|
+
uniform: string,
|
|
543
|
+
) {
|
|
544
|
+
const resolved = await uniformResolver(
|
|
545
|
+
settings,
|
|
546
|
+
root,
|
|
547
|
+
routeMatch,
|
|
548
|
+
uniform,
|
|
549
|
+
uniformApiResolver,
|
|
550
|
+
settings?.navigation?.sidebar,
|
|
551
|
+
{
|
|
552
|
+
...options,
|
|
553
|
+
...innerOptions,
|
|
554
|
+
},
|
|
555
|
+
uniformType,
|
|
556
|
+
disableFSWrite
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if (resolved.sidebar) {
|
|
560
|
+
settings.navigation = {
|
|
561
|
+
...settings?.navigation,
|
|
562
|
+
sidebar: !settings.navigation?.sidebar
|
|
563
|
+
? resolved.sidebar
|
|
564
|
+
: [
|
|
565
|
+
...resolved.sidebar,
|
|
566
|
+
...!settings.navigation?.sidebar || [],
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
resp.push({
|
|
572
|
+
urlPrefix: routeMatch.startsWith("/") ? routeMatch : `/${routeMatch}`,
|
|
573
|
+
data: resolved.data,
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (apiFile["source"]) {
|
|
578
|
+
await resolve(apiFile["route"], apiFile["source"])
|
|
579
|
+
} else {
|
|
580
|
+
for (const apiKey in apiFile) {
|
|
581
|
+
const uniform = apiFile?.[apiKey]?.source || apiFile?.[apiKey] || ""
|
|
582
|
+
const routeMatch = settings.api?.[id]?.[apiKey]?.route || ""
|
|
583
|
+
|
|
584
|
+
if (!uniform) {
|
|
585
|
+
throw new Error(`uniform not found for ${apiKey}`)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
await resolve(routeMatch, uniform)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return resp
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function vitePluginUniformContent(pluginId: string) {
|
|
599
|
+
return function vitePluginUniformContentInner() {
|
|
600
|
+
return async function ({
|
|
601
|
+
preinstall
|
|
602
|
+
}): Promise<VitePlugin> {
|
|
603
|
+
return {
|
|
604
|
+
name: `virtual:xyd-plugin-docs/${pluginId}`, // TODO: unique name per plugin ?
|
|
605
|
+
resolveId(id) {
|
|
606
|
+
if (id == `virtual:xyd-plugin-docs/${pluginId}`) {
|
|
607
|
+
return id;
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
async load(id) {
|
|
611
|
+
if (id === `virtual:xyd-plugin-docs/${pluginId}`) {
|
|
612
|
+
if (!preinstall.data) {
|
|
613
|
+
return `export default ${JSON.stringify(preinstall)}`;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return `export default ${JSON.stringify(preinstall.data)}`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
type UniformType = "graphql" | "openapi" | "sources"
|
|
625
|
+
|
|
626
|
+
function uniformPreset(
|
|
627
|
+
id: string,
|
|
628
|
+
apiFile: APIFile,
|
|
629
|
+
sidebar: (SidebarRoute | Sidebar)[],
|
|
630
|
+
options: uniformPresetOptions,
|
|
631
|
+
uniformApiResolver: (filePath: string) => Promise<Reference[]>,
|
|
632
|
+
disableFSWrite?: boolean
|
|
633
|
+
) {
|
|
634
|
+
return function (settings: Settings, uniformType: UniformType) {
|
|
635
|
+
const routeMatches: string[] = []
|
|
636
|
+
|
|
637
|
+
if (apiFile) {
|
|
638
|
+
sidebar.forEach((sidebar) => {
|
|
639
|
+
if ("route" in sidebar) {
|
|
640
|
+
if (typeof apiFile === "string") {
|
|
641
|
+
const routeMatch = id
|
|
642
|
+
|
|
643
|
+
if (sidebar.route === routeMatch) {
|
|
644
|
+
routeMatches.push(routeMatch)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (typeof apiFile === "object" && !Array.isArray(apiFile)) {
|
|
651
|
+
for (const routeMatchKey in apiFile) {
|
|
652
|
+
// TODO: is 'id' a good idea here?
|
|
653
|
+
const routeMatch = settings?.api?.[id]?.[routeMatchKey]?.route || ""
|
|
654
|
+
if (sidebar.route === routeMatch) {
|
|
655
|
+
routeMatches.push(routeMatch)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
// TODO: support NOT match sidebar
|
|
663
|
+
})
|
|
664
|
+
} else {
|
|
665
|
+
if (!options.urlPrefix) {
|
|
666
|
+
throw new Error('(uniformPreset): urlPrefix not found')
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
routeMatches.push(`${options.urlPrefix}/*`)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const basePath = path.join(getHostPath(), "./plugins/xyd-plugin-docs")
|
|
673
|
+
const pageTheme = "src/pages/docs.tsx"
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
preinstall: [
|
|
677
|
+
preinstall(
|
|
678
|
+
id,
|
|
679
|
+
uniformApiResolver,
|
|
680
|
+
apiFile,
|
|
681
|
+
uniformType,
|
|
682
|
+
disableFSWrite,
|
|
683
|
+
options
|
|
684
|
+
)
|
|
685
|
+
],
|
|
686
|
+
routes: routeMatches.map((routeMatch, i) => route(
|
|
687
|
+
`${routeMatch}/*`,
|
|
688
|
+
path.join(basePath, pageTheme), {
|
|
689
|
+
id: `xyd-plugin-docs/${id}-${i}`,
|
|
690
|
+
}
|
|
691
|
+
),
|
|
692
|
+
),
|
|
693
|
+
vitePlugins: [
|
|
694
|
+
vitePluginUniformContent(id),
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
} satisfies Preset<unknown>
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
// TODO: refactor to use class methods + separate functions if needed?
|
|
702
|
+
export abstract class UniformPreset {
|
|
703
|
+
private _urlPrefix: string;
|
|
704
|
+
private _sourceTheme: boolean;
|
|
705
|
+
private _fileRouting: { [key: string]: string } = {};
|
|
706
|
+
|
|
707
|
+
protected constructor(
|
|
708
|
+
private presetId: string,
|
|
709
|
+
private apiFile: APIFile,
|
|
710
|
+
private sidebar: (SidebarRoute | Sidebar)[],
|
|
711
|
+
private disableFSWrite?: boolean
|
|
712
|
+
) {
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
protected abstract uniformRefResolver(filePath: string): Promise<Reference[]>
|
|
716
|
+
|
|
717
|
+
protected urlPrefix(urlPrefix: string): this {
|
|
718
|
+
this._urlPrefix = urlPrefix
|
|
719
|
+
|
|
720
|
+
return this
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
protected sourceTheme(v: boolean): this {
|
|
724
|
+
this._sourceTheme = v
|
|
725
|
+
|
|
726
|
+
return this
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
protected fileRouting(file: string, route: string): this {
|
|
730
|
+
this._fileRouting[file] = route
|
|
731
|
+
|
|
732
|
+
return this
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
protected newUniformPreset() {
|
|
736
|
+
return uniformPreset(
|
|
737
|
+
this.presetId,
|
|
738
|
+
this.apiFile,
|
|
739
|
+
this.sidebar,
|
|
740
|
+
{
|
|
741
|
+
urlPrefix: this._urlPrefix,
|
|
742
|
+
sourceTheme: this._sourceTheme,
|
|
743
|
+
fileRouting: this._fileRouting,
|
|
744
|
+
},
|
|
745
|
+
this.uniformRefResolver,
|
|
746
|
+
this.disableFSWrite
|
|
747
|
+
)
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|