@xyd-js/plugin-docs 0.1.0-build.160
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/LICENSE +21 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +4605 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
- package/src/const.ts +7 -0
- package/src/declarations.d.ts +29 -0
- package/src/index.ts +386 -0
- package/src/pages/context.tsx +9 -0
- package/src/pages/layout.tsx +340 -0
- package/src/pages/metatags.ts +96 -0
- package/src/pages/page.tsx +463 -0
- package/src/presets/docs/index.ts +317 -0
- package/src/presets/docs/settings.ts +262 -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 +832 -0
- package/src/types.ts +40 -0
- package/src/utils.ts +19 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { createServer, Plugin as VitePlugin } from "vite";
|
|
6
|
+
import { route } from "@react-router/dev/routes";
|
|
7
|
+
|
|
8
|
+
import { Settings } from "@xyd-js/core";
|
|
9
|
+
|
|
10
|
+
import { Preset, PresetData } from "../../types";
|
|
11
|
+
import { readSettings } from "./settings";
|
|
12
|
+
import { DEFAULT_THEME, THEME_CONFIG_FOLDER } from "../../const";
|
|
13
|
+
import { getDocsPluginBasePath, getHostPath } from "../../utils";
|
|
14
|
+
|
|
15
|
+
interface docsPluginOptions {
|
|
16
|
+
urlPrefix?: string
|
|
17
|
+
onUpdate?: (callback: (settings: Settings) => void) => void
|
|
18
|
+
appInit: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// TODO: find better solution - maybe something what rr7 use?
|
|
22
|
+
async function loadModule(filePath: string) {
|
|
23
|
+
const server = await createServer({
|
|
24
|
+
optimizeDeps: {
|
|
25
|
+
include: ["react/jsx-runtime"],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const module = await server.ssrLoadModule(filePath);
|
|
31
|
+
return module.default;
|
|
32
|
+
} finally {
|
|
33
|
+
await server.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function preinstall() {
|
|
38
|
+
return async function docsPluginInner(_, data: PresetData) {
|
|
39
|
+
// TODO: configurable root?
|
|
40
|
+
const root = process.cwd()
|
|
41
|
+
|
|
42
|
+
const settings = await readSettings()
|
|
43
|
+
if (settings && !settings.theme) {
|
|
44
|
+
settings.theme = {
|
|
45
|
+
name: DEFAULT_THEME
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let themeRoutesExists = false
|
|
50
|
+
try {
|
|
51
|
+
await fs.access(path.join(root, THEME_CONFIG_FOLDER, "./routes.ts"))
|
|
52
|
+
themeRoutesExists = true
|
|
53
|
+
} catch (_) {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (themeRoutesExists) {
|
|
57
|
+
const routeMod = await loadModule(path.join(root, THEME_CONFIG_FOLDER, "./routes.ts"))
|
|
58
|
+
|
|
59
|
+
const routes = routeMod((routePath, routeFile, routeOptions) => {
|
|
60
|
+
return route(routePath, path.join(root, THEME_CONFIG_FOLDER, routeFile), routeOptions)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
data.routes.push(...routes)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
settings
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function vitePluginSettings(options: docsPluginOptions) {
|
|
73
|
+
return function () {
|
|
74
|
+
return async function ({ preinstall }): Promise<VitePlugin> {
|
|
75
|
+
const virtualId = 'virtual:xyd-settings';
|
|
76
|
+
const resolvedId = virtualId + '.jsx';
|
|
77
|
+
|
|
78
|
+
let currentSettings = globalThis.__xydSettings
|
|
79
|
+
let settingsClone = {}
|
|
80
|
+
if (!currentSettings && preinstall?.settings) {
|
|
81
|
+
currentSettings = typeof preinstall?.settings === "string" ? preinstall?.settings : JSON.stringify(preinstall?.settings || {})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let currentUserPreferences = globalThis.__xydUserPreferences
|
|
85
|
+
if (!currentUserPreferences) {
|
|
86
|
+
currentUserPreferences = {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let firstInit = false
|
|
90
|
+
|
|
91
|
+
if (options.onUpdate) {
|
|
92
|
+
options.onUpdate((settings: Settings) => {
|
|
93
|
+
currentSettings = settings
|
|
94
|
+
settingsClone = JSON.parse(JSON.stringify(currentSettings))
|
|
95
|
+
currentUserPreferences = globalThis.__xydUserPreferences
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
name: 'xyd:virtual-settings',
|
|
101
|
+
|
|
102
|
+
resolveId(id) {
|
|
103
|
+
if (id === virtualId) {
|
|
104
|
+
return resolvedId;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async load(id) {
|
|
110
|
+
if (id === 'virtual:xyd-settings.jsx') {
|
|
111
|
+
// console.log("load xyd-settings.jsx")
|
|
112
|
+
|
|
113
|
+
if (!firstInit && globalThis.__xydSettings) {
|
|
114
|
+
currentSettings = globalThis.__xydSettings
|
|
115
|
+
settingsClone = JSON.parse(JSON.stringify(currentSettings))
|
|
116
|
+
currentUserPreferences = globalThis.__xydUserPreferences
|
|
117
|
+
}
|
|
118
|
+
firstInit = true
|
|
119
|
+
|
|
120
|
+
return `
|
|
121
|
+
// Always get the latest settings from globalThis
|
|
122
|
+
const getCurrentSettings = () => {
|
|
123
|
+
return globalThis.__xydSettings || ${typeof currentSettings === "string" ? currentSettings : JSON.stringify(currentSettings)}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getCurrentUserPreferences = () => {
|
|
127
|
+
return globalThis.__xydUserPreferences || ${typeof currentUserPreferences === "string" ? currentUserPreferences : JSON.stringify(currentUserPreferences)}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default {
|
|
131
|
+
get settings() {
|
|
132
|
+
return getCurrentSettings();
|
|
133
|
+
},
|
|
134
|
+
get settingsClone() {
|
|
135
|
+
return ${typeof settingsClone === "string" ? settingsClone : JSON.stringify(settingsClone)}
|
|
136
|
+
},
|
|
137
|
+
get userPreferences() {
|
|
138
|
+
return getCurrentUserPreferences()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
`
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// async hotUpdate(ctx) {
|
|
147
|
+
// console.log("hot update")
|
|
148
|
+
|
|
149
|
+
// const isConfigfileUpdated = ctx.file.includes('react-router.config.ts')
|
|
150
|
+
// if (isConfigfileUpdated) {
|
|
151
|
+
// return
|
|
152
|
+
// }
|
|
153
|
+
|
|
154
|
+
// const newSettings = await readSettings();
|
|
155
|
+
// if (!newSettings) {
|
|
156
|
+
// console.log('⚠️ Failed to read new settings');
|
|
157
|
+
// return
|
|
158
|
+
// }
|
|
159
|
+
|
|
160
|
+
// if (options.appInit) {
|
|
161
|
+
// // TODO: better way to handle that - we need this cuz otherwise its inifiite reloads
|
|
162
|
+
// if (newSettings.engine?.uniform?.store) {
|
|
163
|
+
// await options.appInit({
|
|
164
|
+
// disableFSWrite: true,
|
|
165
|
+
// })
|
|
166
|
+
// } else {
|
|
167
|
+
// await options.appInit() // TODO: !!! IN THE FUTURE MORE EFFICIENT WAY !!!
|
|
168
|
+
// }
|
|
169
|
+
// }
|
|
170
|
+
|
|
171
|
+
// currentSettings = globalThis.__xydSettings
|
|
172
|
+
// settingsClone = JSON.parse(JSON.stringify(currentSettings))
|
|
173
|
+
// currentUserPreferences = globalThis.__xydUserPreferences
|
|
174
|
+
// // globalThis.__xydSettingsClone = JSON.parse(JSON.stringify(globalThis.__xydSettings))
|
|
175
|
+
|
|
176
|
+
// return
|
|
177
|
+
// },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function vitePluginThemeCSS() {
|
|
184
|
+
return async function ({
|
|
185
|
+
preinstall
|
|
186
|
+
}: {
|
|
187
|
+
preinstall: {
|
|
188
|
+
settings: Settings
|
|
189
|
+
}
|
|
190
|
+
}): Promise<VitePlugin> {
|
|
191
|
+
return {
|
|
192
|
+
name: 'virtual:xyd-theme/index.css',
|
|
193
|
+
|
|
194
|
+
resolveId(source) {
|
|
195
|
+
if (source === 'virtual:xyd-theme/index.css') {
|
|
196
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
197
|
+
const __dirname = path.dirname(__filename);
|
|
198
|
+
|
|
199
|
+
const themeName = preinstall.settings.theme?.name || DEFAULT_THEME
|
|
200
|
+
let themePath = ""
|
|
201
|
+
|
|
202
|
+
if (process.env.XYD_CLI) {
|
|
203
|
+
themePath = path.join(getHostPath(), `node_modules/@xyd-js/theme-${themeName}/dist`)
|
|
204
|
+
} else {
|
|
205
|
+
themePath = path.join(path.resolve(__dirname, "../../"), `xyd-theme-${themeName}/dist`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return path.join(themePath, "index.css")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function vitePluginThemeOverrideCSS() {
|
|
218
|
+
return async function ({ preinstall }: { preinstall: { settings: Settings } }): Promise<VitePlugin> {
|
|
219
|
+
return {
|
|
220
|
+
name: 'virtual:xyd-theme-override-css',
|
|
221
|
+
|
|
222
|
+
async resolveId(id) {
|
|
223
|
+
if (id === 'virtual:xyd-theme-override/index.css') {
|
|
224
|
+
const root = process.cwd();
|
|
225
|
+
const filePath = path.join(root, THEME_CONFIG_FOLDER, "./index.css");
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await fs.access(filePath);
|
|
229
|
+
return filePath;
|
|
230
|
+
} catch {
|
|
231
|
+
// File does not exist, omit it
|
|
232
|
+
return 'virtual:xyd-theme-override/empty.css';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async load(id) {
|
|
239
|
+
if (id === 'virtual:xyd-theme-override/empty.css') {
|
|
240
|
+
// Return an empty module
|
|
241
|
+
return '';
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function vitePluginTheme() {
|
|
250
|
+
return async function ({
|
|
251
|
+
preinstall
|
|
252
|
+
}: {
|
|
253
|
+
preinstall: {
|
|
254
|
+
settings: Settings
|
|
255
|
+
}
|
|
256
|
+
}): Promise<VitePlugin> {
|
|
257
|
+
return {
|
|
258
|
+
name: 'virtual:xyd-theme',
|
|
259
|
+
resolveId(id) {
|
|
260
|
+
if (id === 'virtual:xyd-theme') {
|
|
261
|
+
return id;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
},
|
|
265
|
+
async load(id) {
|
|
266
|
+
if (id === 'virtual:xyd-theme') {
|
|
267
|
+
// return ''
|
|
268
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
269
|
+
const __dirname = path.dirname(__filename);
|
|
270
|
+
|
|
271
|
+
const themeName = preinstall.settings.theme?.name || DEFAULT_THEME
|
|
272
|
+
let themePath = ""
|
|
273
|
+
|
|
274
|
+
if (process.env.XYD_CLI) {
|
|
275
|
+
themePath = `@xyd-js/theme-${themeName}`
|
|
276
|
+
} else {
|
|
277
|
+
themePath = path.join(path.resolve(__dirname, "../../"), `xyd-theme-${themeName}/src`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Return a module that imports the theme from the local workspace
|
|
281
|
+
return `
|
|
282
|
+
import Theme from '${themePath}';
|
|
283
|
+
|
|
284
|
+
export default Theme;
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function preset(settings: Settings, options: docsPluginOptions) {
|
|
294
|
+
const basePath = getDocsPluginBasePath()
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
preinstall: [
|
|
298
|
+
preinstall,
|
|
299
|
+
],
|
|
300
|
+
routes: [
|
|
301
|
+
route("", path.join(basePath, "src/pages/docs.tsx")),
|
|
302
|
+
// TODO: custom routes
|
|
303
|
+
route(options.urlPrefix ? `${options.urlPrefix}/*` : "*", path.join(basePath, "src/pages/docs.tsx"), {
|
|
304
|
+
id: "xyd-plugin-docs/docs",
|
|
305
|
+
}),
|
|
306
|
+
],
|
|
307
|
+
vitePlugins: [
|
|
308
|
+
vitePluginSettings(options),
|
|
309
|
+
vitePluginTheme,
|
|
310
|
+
vitePluginThemeCSS,
|
|
311
|
+
vitePluginThemeOverrideCSS
|
|
312
|
+
],
|
|
313
|
+
basePath
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const docsPreset = preset as Preset<unknown>
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { createServer } from 'vite';
|
|
6
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
7
|
+
|
|
8
|
+
import { Settings } from "@xyd-js/core";
|
|
9
|
+
import { getThemeColors } from '@code-hike/lighter';
|
|
10
|
+
|
|
11
|
+
const extensions = ['tsx', 'ts', 'json'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reads `xyd` settings from the current working directory.
|
|
15
|
+
*
|
|
16
|
+
* This function searches for a file named 'xyd' with one of the supported extensions
|
|
17
|
+
* (tsx, jsx, js, ts, json) in the current working directory. If found, it loads the
|
|
18
|
+
* settings from that file.
|
|
19
|
+
*
|
|
20
|
+
* For React-based settings files (tsx, jsx, js, ts), it uses Vite's SSR module loading
|
|
21
|
+
* to evaluate the file and extract the default export. For JSON files, it simply
|
|
22
|
+
* parses the JSON content.
|
|
23
|
+
*
|
|
24
|
+
* Environment variables in the format $ENV_VAR will be replaced with actual environment
|
|
25
|
+
* variable values. Environment variables are loaded from .env files before processing.
|
|
26
|
+
*
|
|
27
|
+
* @returns A Promise that resolves to:
|
|
28
|
+
* - The Settings object if a valid settings file was found and loaded
|
|
29
|
+
* - A string if the settings file contains a string value
|
|
30
|
+
* - null if no settings file was found or an error occurred
|
|
31
|
+
*
|
|
32
|
+
* @throws May throw errors if file reading or parsing fails
|
|
33
|
+
*/
|
|
34
|
+
export async function readSettings() {
|
|
35
|
+
const dirPath = process.cwd();
|
|
36
|
+
const baseFileName = 'docs';
|
|
37
|
+
|
|
38
|
+
// Load environment variables from .env files first
|
|
39
|
+
await loadEnvFiles(dirPath);
|
|
40
|
+
|
|
41
|
+
let settingsFilePath = '';
|
|
42
|
+
let reactSettings = false;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const files = await fs.readdir(dirPath);
|
|
46
|
+
const settingsFile = files.find(file => {
|
|
47
|
+
const ext = path.extname(file).slice(1);
|
|
48
|
+
return file.startsWith(baseFileName) && extensions.includes(ext);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (settingsFile) {
|
|
52
|
+
settingsFilePath = path.join(dirPath, settingsFile);
|
|
53
|
+
reactSettings = path.extname(settingsFile) !== '.json';
|
|
54
|
+
} else {
|
|
55
|
+
console.error(`No settings file found.\nFile must be named 'docs' with one of the following extensions: ${extensions.join(', ')}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(error);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (reactSettings) {
|
|
64
|
+
const settingsPreview = await createServer({
|
|
65
|
+
optimizeDeps: {
|
|
66
|
+
include: ["react/jsx-runtime"],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const config = await settingsPreview.ssrLoadModule(settingsFilePath);
|
|
70
|
+
const mod = config.default as Settings;
|
|
71
|
+
|
|
72
|
+
// Replace environment variables in the settings
|
|
73
|
+
const processedSettings = replaceEnvVars(mod);
|
|
74
|
+
presets(processedSettings)
|
|
75
|
+
|
|
76
|
+
return processedSettings
|
|
77
|
+
} else {
|
|
78
|
+
const rawJsonSettings = await fs.readFile(settingsFilePath, 'utf-8');
|
|
79
|
+
try {
|
|
80
|
+
let json = JSON.parse(rawJsonSettings) as Settings
|
|
81
|
+
|
|
82
|
+
// Replace environment variables in the settings
|
|
83
|
+
const processedSettings = replaceEnvVars(json);
|
|
84
|
+
presets(processedSettings)
|
|
85
|
+
|
|
86
|
+
return processedSettings
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error("⚠️ Error parsing settings file")
|
|
89
|
+
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// if (settings?.theme?.coder?.syntaxHighlight) {
|
|
96
|
+
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
function presets(settings: Settings) {
|
|
100
|
+
if (settings?.theme?.coder?.syntaxHighlight && typeof settings.theme.coder.syntaxHighlight === 'string') {
|
|
101
|
+
handleSyntaxHighlight(settings.theme.coder.syntaxHighlight, settings);
|
|
102
|
+
}
|
|
103
|
+
ensureNavigation(settings)
|
|
104
|
+
|
|
105
|
+
if (settings?.theme && !settings?.theme?.head?.length) {
|
|
106
|
+
settings.theme.head = []
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function handleSyntaxHighlight(syntaxHighlight: string, settings: Settings) {
|
|
111
|
+
try {
|
|
112
|
+
// Ensure theme.coder exists
|
|
113
|
+
if (!settings.theme) {
|
|
114
|
+
settings.theme = { name: 'default' } as any;
|
|
115
|
+
}
|
|
116
|
+
if (!settings.theme!.coder) {
|
|
117
|
+
settings.theme!.coder = {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if it's a URL
|
|
121
|
+
if (isUrl(syntaxHighlight)) {
|
|
122
|
+
// Fetch from remote URL
|
|
123
|
+
const response = await fetch(syntaxHighlight);
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
console.error(`⚠️ Failed to fetch syntax highlight from URL: ${syntaxHighlight}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const json = await response.json();
|
|
129
|
+
settings.theme!.coder!.syntaxHighlight = json;
|
|
130
|
+
} else {
|
|
131
|
+
// Handle local path - but first check if ita's actually a path
|
|
132
|
+
const localPath = path.resolve(process.cwd(), syntaxHighlight);
|
|
133
|
+
try {
|
|
134
|
+
// Check if the file exists before trying to read it
|
|
135
|
+
await fs.access(localPath);
|
|
136
|
+
const fileContent = await fs.readFile(localPath, 'utf-8');
|
|
137
|
+
const json = JSON.parse(fileContent);
|
|
138
|
+
settings.theme!.coder!.syntaxHighlight = json;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const syntaxHighlightTheme = settings.theme?.coder?.syntaxHighlight
|
|
144
|
+
if (syntaxHighlightTheme) {
|
|
145
|
+
try {
|
|
146
|
+
const themeColors = await getThemeColors(syntaxHighlightTheme);
|
|
147
|
+
|
|
148
|
+
if (themeColors) {
|
|
149
|
+
globalThis.__xydUserPreferences = {
|
|
150
|
+
themeColors
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`⚠️ Error processing syntax highlight theme colors.`, error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`⚠️ Error processing syntax highlight: ${syntaxHighlight}`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Loads environment variables from .env files
|
|
165
|
+
* @param dirPath - The directory path to search for .env files
|
|
166
|
+
*/
|
|
167
|
+
async function loadEnvFiles(dirPath: string) {
|
|
168
|
+
try {
|
|
169
|
+
// Define the order of .env files to load (later files override earlier ones)
|
|
170
|
+
const envFiles = [
|
|
171
|
+
'.env',
|
|
172
|
+
'.env.local',
|
|
173
|
+
'.env.development',
|
|
174
|
+
'.env.production'
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
for (const envFile of envFiles) {
|
|
178
|
+
const envPath = path.join(dirPath, envFile);
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
await fs.access(envPath);
|
|
182
|
+
const result = dotenvConfig({
|
|
183
|
+
path: envPath,
|
|
184
|
+
override: true // Ensure variables are overridden
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (result.parsed && Object.keys(result.parsed).length > 0) {
|
|
188
|
+
console.debug(`📄 Loaded environment variables.`);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// File doesn't exist, which is fine - continue to next file
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.warn('⚠️ Error loading .env files:', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Recursively replaces environment variable placeholders in an object
|
|
201
|
+
* @param obj - The object to process
|
|
202
|
+
* @returns The object with environment variables replaced
|
|
203
|
+
*/
|
|
204
|
+
function replaceEnvVars(obj: any): any {
|
|
205
|
+
if (obj === null || obj === undefined) {
|
|
206
|
+
return obj;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof obj === 'string') {
|
|
210
|
+
// Check if the string contains environment variable placeholders
|
|
211
|
+
if (obj.includes('$')) {
|
|
212
|
+
return obj.replace(/\$([A-Z_][A-Z0-9_]*)/g, (match, varName) => {
|
|
213
|
+
const envValue = process.env[varName];
|
|
214
|
+
if (envValue === undefined) {
|
|
215
|
+
console.warn(`\n⚠️ Environment variable "${varName}" is not set, keeping placeholder: ${match}`);
|
|
216
|
+
return match;
|
|
217
|
+
}
|
|
218
|
+
return envValue;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return obj;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (Array.isArray(obj)) {
|
|
225
|
+
return obj.map(item => replaceEnvVars(item));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (typeof obj === 'object') {
|
|
229
|
+
const result: any = {};
|
|
230
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
231
|
+
result[key] = replaceEnvVars(value);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return obj;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isUrl(str: string): boolean {
|
|
240
|
+
try {
|
|
241
|
+
new URL(str);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function ensureNavigation(json: Settings) {
|
|
249
|
+
if (!json?.webeditor) {
|
|
250
|
+
json.webeditor = {}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!json?.navigation) {
|
|
254
|
+
json.navigation = {
|
|
255
|
+
sidebar: []
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!json?.navigation?.sidebar) {
|
|
260
|
+
json.navigation.sidebar = []
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Settings } from "@xyd-js/core";
|
|
2
|
+
import { gqlSchemaToReferences } from "@xyd-js/gql";
|
|
3
|
+
import type { Reference } from "@xyd-js/uniform";
|
|
4
|
+
|
|
5
|
+
import { Preset } from "../../types"
|
|
6
|
+
import { UniformPreset } from "../uniform"
|
|
7
|
+
|
|
8
|
+
interface graphqlPluginOptions {
|
|
9
|
+
urlPrefix?: string
|
|
10
|
+
root?: string
|
|
11
|
+
disableFSWrite?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function preset(
|
|
15
|
+
settings: Settings,
|
|
16
|
+
options: graphqlPluginOptions
|
|
17
|
+
) {
|
|
18
|
+
return GraphQLUniformPreset.new(settings, options)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const graphqlPreset = preset satisfies Preset<unknown>
|
|
22
|
+
|
|
23
|
+
class GraphQLUniformPreset extends UniformPreset {
|
|
24
|
+
private constructor(
|
|
25
|
+
settings: Settings,
|
|
26
|
+
options: {
|
|
27
|
+
disableFSWrite?: boolean
|
|
28
|
+
}
|
|
29
|
+
) {
|
|
30
|
+
super(
|
|
31
|
+
"graphql",
|
|
32
|
+
settings.api?.graphql || "",
|
|
33
|
+
settings?.navigation?.sidebar || [],
|
|
34
|
+
options.disableFSWrite
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
this.uniformRefResolver = this.uniformRefResolver.bind(this)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static new(
|
|
41
|
+
settings: Settings,
|
|
42
|
+
options: graphqlPluginOptions
|
|
43
|
+
) {
|
|
44
|
+
return new GraphQLUniformPreset(settings, {
|
|
45
|
+
disableFSWrite: options.disableFSWrite
|
|
46
|
+
})
|
|
47
|
+
.urlPrefix(options.urlPrefix || "")
|
|
48
|
+
.newUniformPreset()(settings, "graphql")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected override async uniformRefResolver(filePath: string): Promise<Reference[]> {
|
|
52
|
+
if (!filePath) {
|
|
53
|
+
return []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resp = await gqlSchemaToReferences(filePath)
|
|
57
|
+
|
|
58
|
+
if ("__UNSAFE_route" in resp && typeof resp.__UNSAFE_route === "function") {
|
|
59
|
+
// If the route is a function, we need to call it to get the actual route
|
|
60
|
+
const route = resp.__UNSAFE_route();
|
|
61
|
+
if (route) {
|
|
62
|
+
this.fileRouting(filePath, route);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return resp
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|