mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-04-04 15:56:16 +02:00
fix(Docs): fix doc rendering
This commit is contained in:
parent
97655ef75d
commit
44b7bfee16
|
|
@ -28,5 +28,6 @@ ENV NODE_ENV=production
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=production-deps /app/node_modules /app/node_modules
|
COPY --from=production-deps /app/node_modules /app/node_modules
|
||||||
COPY --from=build /app/build /app
|
COPY --from=build /app/build /app
|
||||||
|
COPY ./docs /app/docs
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["node", "./bin/server.js"]
|
CMD ["node", "./bin/server.js"]
|
||||||
|
|
@ -9,15 +9,13 @@ export default class DocsController {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async list({ }: HttpContext) {
|
async list({ }: HttpContext) {
|
||||||
const docs = await this.docsService.getDocs();
|
return await this.docsService.getDocs();
|
||||||
return { articles: docs };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async show({ params, inertia }: HttpContext) {
|
async show({ params, inertia }: HttpContext) {
|
||||||
const content = await this.docsService.parseFile(`${params.slug}.md`);
|
const content = await this.docsService.parseFile(params.slug);
|
||||||
return inertia.render('docs/show', {
|
return inertia.render('docs/show', {
|
||||||
content,
|
content,
|
||||||
title: "Documentation"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,28 @@
|
||||||
|
import drive from '@adonisjs/drive/services/main';
|
||||||
import Markdoc from '@markdoc/markdoc';
|
import Markdoc from '@markdoc/markdoc';
|
||||||
import { readdir, readFile } from 'node:fs/promises';
|
import { streamToString } from '../../util/docs.js';
|
||||||
import { join } from 'node:path';
|
|
||||||
|
|
||||||
export class DocsService {
|
export class DocsService {
|
||||||
async getDocs() {
|
async getDocs() {
|
||||||
const docsPath = join(process.cwd(), '/docs');
|
const disk = drive.use('docs');
|
||||||
console.log(`Resolving docs path: ${docsPath}`);
|
if (!disk) {
|
||||||
|
throw new Error('Docs disk not configured');
|
||||||
|
}
|
||||||
|
|
||||||
const files = await readdir(docsPath, { withFileTypes: true });
|
const contents = await disk.listAll('/');
|
||||||
const docs = files
|
const files: Array<{ title: string; slug: string }> = [];
|
||||||
.filter(file => file.isFile() && file.name.endsWith('.md'))
|
|
||||||
.map(file => file.name);
|
for (const item of contents.objects) {
|
||||||
return docs;
|
if (item.isFile && item.name.endsWith('.md')) {
|
||||||
|
const cleaned = this.prettify(item.name);
|
||||||
|
files.push({
|
||||||
|
title: cleaned,
|
||||||
|
slug: item.name.replace(/\.md$/, '')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(content: string) {
|
parse(content: string) {
|
||||||
|
|
@ -26,13 +37,36 @@ export class DocsService {
|
||||||
return Markdoc.transform(ast, config);
|
return Markdoc.transform(ast, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseFile(filename: string) {
|
async parseFile(_filename: string) {
|
||||||
const fullPath = join(process.cwd(), '/docs', filename);
|
const disk = drive.use('docs');
|
||||||
console.log(`Resolving file path: ${fullPath}`);
|
if (!disk) {
|
||||||
const content = await readFile(fullPath, 'utf-8')
|
throw new Error('Docs disk not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_filename) {
|
||||||
|
throw new Error('Filename is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md`;
|
||||||
|
|
||||||
|
const fileExists = await disk.exists(filename);
|
||||||
|
if (!fileExists) {
|
||||||
|
throw new Error(`File not found: ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileStream = await disk.getStream(filename);
|
||||||
|
if (!fileStream) {
|
||||||
|
throw new Error(`Failed to read file stream: ${filename}`);
|
||||||
|
}
|
||||||
|
const content = await streamToString(fileStream);
|
||||||
return this.parse(content);
|
return this.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private prettify(filename: string) {
|
||||||
|
const cleaned = filename.replace(/_/g, ' ').replace(/\.md$/, '');
|
||||||
|
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
private getConfig() {
|
private getConfig() {
|
||||||
return {
|
return {
|
||||||
tags: {
|
tags: {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ const driveConfig = defineConfig({
|
||||||
routeBasePath: '/storage',
|
routeBasePath: '/storage',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
}),
|
}),
|
||||||
|
docs: services.fs({
|
||||||
|
location: app.makePath('docs'),
|
||||||
|
serveFiles: false, // Don't serve files directly - we handle this via routes/Inertia
|
||||||
|
visibility: 'public',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,71 @@
|
||||||
# This is a markdown file!
|
# Lorem Ipsum Markdown Showcase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This document serves as a comprehensive example of **Markdown's various formatting possibilities**, using the classic *Lorem Ipsum* text as its content. From basic text styling to lists, code blocks, and tables, you'll find a demonstration of common Markdown features here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Basic Text Formatting
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
|
||||||
|
* This text is **bold**.
|
||||||
|
* This text is *italic*.
|
||||||
|
* This text is ***bold and italic***.
|
||||||
|
* This text is ~~struck through~~.
|
||||||
|
* You can also use `backticks` for `inline code`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Headers
|
||||||
|
|
||||||
|
Markdown supports up to six levels of headers.
|
||||||
|
|
||||||
|
# Header 1
|
||||||
|
## Header 2
|
||||||
|
### Header 3
|
||||||
|
#### Header 4
|
||||||
|
##### Header 5
|
||||||
|
###### Header 6
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lists
|
||||||
|
|
||||||
|
### Unordered List
|
||||||
|
|
||||||
|
* Lorem ipsum dolor sit amet.
|
||||||
|
* Consectetur adipiscing elit.
|
||||||
|
* Sed do eiusmod tempor.
|
||||||
|
* Incididunt ut labore et dolore magna.
|
||||||
|
* Aliqua ut enim ad minim veniam.
|
||||||
|
|
||||||
|
### Ordered List
|
||||||
|
|
||||||
|
1. Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
|
2. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||||
|
3. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blockquotes
|
||||||
|
|
||||||
|
> "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||||
|
>
|
||||||
|
> — John Doe, *Lorem Ipsum Anthology*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Blocks
|
||||||
|
|
||||||
|
```python
|
||||||
|
def fibonacci(n):
|
||||||
|
a, b = 0, 1
|
||||||
|
for i in range(n):
|
||||||
|
print(a, end=" ")
|
||||||
|
a, b = b, a + b
|
||||||
|
|
||||||
|
fibonacci(10)
|
||||||
|
|
@ -9,6 +9,7 @@ import ModalsProvider from '~/providers/ModalProvider'
|
||||||
import { TransmitProvider } from 'react-adonis-transmit'
|
import { TransmitProvider } from 'react-adonis-transmit'
|
||||||
import { generateUUID } from '~/lib/util'
|
import { generateUUID } from '~/lib/util'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||||
|
|
||||||
const appName = import.meta.env.VITE_APP_NAME || 'Project N.O.M.A.D.'
|
const appName = import.meta.env.VITE_APP_NAME || 'Project N.O.M.A.D.'
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
|
@ -36,6 +37,7 @@ createInertiaApp({
|
||||||
<TransmitProvider baseUrl={window.location.origin} enableLogging={true}>
|
<TransmitProvider baseUrl={window.location.origin} enableLogging={true}>
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<App {...props} />
|
<App {...props} />
|
||||||
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</TransmitProvider>
|
</TransmitProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ interface StyledSidebarProps {
|
||||||
items: Array<{
|
items: Array<{
|
||||||
name: string
|
name: string
|
||||||
href: string
|
href: string
|
||||||
icon: React.ElementType
|
icon?: React.ElementType
|
||||||
current: boolean
|
current: boolean
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
|
||||||
const ListItem = (item: {
|
const ListItem = (item: {
|
||||||
name: string
|
name: string
|
||||||
href: string
|
href: string
|
||||||
icon: React.ElementType
|
icon?: React.ElementType
|
||||||
current: boolean
|
current: boolean
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -39,7 +39,7 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
|
||||||
'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold'
|
'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon aria-hidden="true" className="size-6 shrink-0" />
|
{item.icon && <item.icon aria-hidden="true" className="size-6 shrink-0" />}
|
||||||
{item.name}
|
{item.name}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,29 @@
|
||||||
import { Cog6ToothIcon, CommandLineIcon, FolderIcon } from '@heroicons/react/24/outline'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import StyledSidebar from '~/components/StyledSidebar'
|
import StyledSidebar from '~/components/StyledSidebar'
|
||||||
import api from '~/lib/api'
|
import api from '~/lib/api'
|
||||||
|
|
||||||
const navigation = [
|
|
||||||
{ name: 'Apps', href: '/settings/apps', icon: CommandLineIcon, current: false },
|
|
||||||
{ name: 'ZIM Explorer', href: '/settings/zim', icon: FolderIcon, current: false },
|
|
||||||
{ name: 'System', href: '/settings/system', icon: Cog6ToothIcon, current: true },
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||||
const [docs, setDocs] = useState<Array<{ title: string; slug: string }>>([])
|
const { data, isLoading } = useQuery<Array<{ title: string; slug: string }>>({
|
||||||
|
queryKey: ['docs'],
|
||||||
|
queryFn: () => api.listDocs(),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
|
||||||
// Fetch docs when the component mounts
|
const items = useMemo(() => {
|
||||||
useEffect(() => {
|
if (isLoading || !data) return []
|
||||||
fetchDocs()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
async function fetchDocs() {
|
return data.map((doc) => ({
|
||||||
try {
|
name: doc.title,
|
||||||
const data = await api.listDocs()
|
href: `/docs/${doc.slug}`,
|
||||||
console.log('Fetched docs:', data)
|
current: false,
|
||||||
setDocs(data)
|
}))
|
||||||
} catch (error) {
|
}, [data, isLoading])
|
||||||
console.error('Error fetching docs:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-row bg-stone-50/90">
|
<div className="min-h-screen flex flex-row bg-stone-50/90">
|
||||||
<StyledSidebar title="Documentation" items={navigation} />
|
<StyledSidebar title="Documentation" items={items} />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ class API {
|
||||||
|
|
||||||
async listDocs() {
|
async listDocs() {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get<{ articles: Array<{ title: string; slug: string }> }>("/docs/list");
|
const response = await this.client.get<Array<{ title: string; slug: string }>>("/docs/list");
|
||||||
return response.data.articles;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error listing docs:", error);
|
console.error("Error listing docs:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import { Head } from '@inertiajs/react'
|
||||||
import MarkdocRenderer from '~/components/MarkdocRenderer'
|
import MarkdocRenderer from '~/components/MarkdocRenderer'
|
||||||
import DocsLayout from '~/layouts/DocsLayout'
|
import DocsLayout from '~/layouts/DocsLayout'
|
||||||
|
|
||||||
export default function Show({ content, title }: { content: any; title: string }) {
|
export default function Show({ content }: { content: any; }) {
|
||||||
return (
|
return (
|
||||||
<DocsLayout>
|
<DocsLayout>
|
||||||
<Head title={`${title} | Documentation | Project N.O.M.A.D.`} />
|
<Head title={'Documentation | Project N.O.M.A.D.'} />
|
||||||
<div className="xl:pl-80 py-6">
|
<div className="xl:pl-80 py-6">
|
||||||
<h1 className='font-semibold text-xl'>{title}</h1>
|
|
||||||
<MarkdocRenderer content={content} />
|
<MarkdocRenderer content={content} />
|
||||||
</div>
|
</div>
|
||||||
</DocsLayout>
|
</DocsLayout>
|
||||||
|
|
|
||||||
42
admin/package-lock.json
generated
42
admin/package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
||||||
"@tabler/icons-react": "^3.34.0",
|
"@tabler/icons-react": "^3.34.0",
|
||||||
"@tailwindcss/vite": "^4.1.10",
|
"@tailwindcss/vite": "^4.1.10",
|
||||||
"@tanstack/react-query": "^5.81.5",
|
"@tanstack/react-query": "^5.81.5",
|
||||||
|
"@tanstack/react-query-devtools": "^5.83.0",
|
||||||
"@tanstack/react-virtual": "^3.13.12",
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"@vinejs/vine": "^3.0.1",
|
"@vinejs/vine": "^3.0.1",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
|
@ -3668,9 +3669,19 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.81.5",
|
"version": "5.83.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.5.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz",
|
||||||
"integrity": "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==",
|
"integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/query-devtools": {
|
||||||
|
"version": "5.81.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz",
|
||||||
|
"integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -3678,12 +3689,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-query": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "5.81.5",
|
"version": "5.83.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.5.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz",
|
||||||
"integrity": "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==",
|
"integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.81.5"
|
"@tanstack/query-core": "5.83.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -3693,6 +3704,23 @@
|
||||||
"react": "^18 || ^19"
|
"react": "^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-query-devtools": {
|
||||||
|
"version": "5.83.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.83.0.tgz",
|
||||||
|
"integrity": "sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-devtools": "5.81.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tanstack/react-query": "^5.83.0",
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/react-virtual": {
|
"node_modules/@tanstack/react-virtual": {
|
||||||
"version": "3.13.12",
|
"version": "3.13.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
"@tabler/icons-react": "^3.34.0",
|
"@tabler/icons-react": "^3.34.0",
|
||||||
"@tailwindcss/vite": "^4.1.10",
|
"@tailwindcss/vite": "^4.1.10",
|
||||||
"@tanstack/react-query": "^5.81.5",
|
"@tanstack/react-query": "^5.81.5",
|
||||||
|
"@tanstack/react-query-devtools": "^5.83.0",
|
||||||
"@tanstack/react-virtual": "^3.13.12",
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"@vinejs/vine": "^3.0.1",
|
"@vinejs/vine": "^3.0.1",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ server.errorHandler(() => import('#exceptions/handler'))
|
||||||
*/
|
*/
|
||||||
server.use([
|
server.use([
|
||||||
() => import('#middleware/container_bindings_middleware'),
|
() => import('#middleware/container_bindings_middleware'),
|
||||||
() => import('@adonisjs/static/static_middleware'),
|
|
||||||
() => import('@adonisjs/cors/cors_middleware'),
|
() => import('@adonisjs/cors/cors_middleware'),
|
||||||
() => import('@adonisjs/vite/vite_middleware'),
|
() => import('@adonisjs/vite/vite_middleware'),
|
||||||
() => import('@adonisjs/inertia/inertia_middleware')
|
() => import('@adonisjs/inertia/inertia_middleware'),
|
||||||
|
() => import('@adonisjs/static/static_middleware')
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
9
admin/util/docs.ts
Normal file
9
admin/util/docs.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
|
export const streamToString = async (stream: Readable): Promise<string> => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
chunks.push(Buffer.from(chunk));
|
||||||
|
}
|
||||||
|
return Buffer.concat(chunks).toString('utf-8');
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user