Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4a4a915
MDX-19: Fix ESLint warnings in mdxe.js
devin-ai-integration[bot] May 14, 2025
f7f41ec
MDX-19: Restore tempConfigInfo initialization while fixing ESLint war…
devin-ai-integration[bot] May 14, 2025
cbb1948
MDX-19: Fix duplicate entries in tsconfig.json
devin-ai-integration[bot] May 14, 2025
b2f636e
Fix build issues in minimal example by focusing on Pages Router
devin-ai-integration[bot] May 14, 2025
c085be7
MDX-19: Update tsconfig.json to fix ESLint warnings
devin-ai-integration[bot] May 14, 2025
5093c46
Add ESLint configuration to minimal example
devin-ai-integration[bot] May 14, 2025
46c30a4
Fix lint command to prevent ESLint from waiting for input
devin-ai-integration[bot] May 14, 2025
c221e58
MDX-19: Fix lint command to prevent ESLint config prompts
devin-ai-integration[bot] May 14, 2025
7a8f744
Fix ESLint configuration to prevent prompts during CI
devin-ai-integration[bot] May 14, 2025
42eb745
Add Next.js and Vercel configuration for minimal example
devin-ai-integration[bot] May 14, 2025
fd22ff6
Add Pages Router files for minimal example
devin-ai-integration[bot] May 14, 2025
62bfc1d
Update mdxe dependency to use specific version instead of workspace
devin-ai-integration[bot] May 14, 2025
d67bad0
Add content directory with test markdown file
devin-ai-integration[bot] May 14, 2025
16fd172
Update package.json and next.config.js to use @next/mdx for MDX confi…
devin-ai-integration[bot] May 14, 2025
90ec3f3
Update pnpm-lock.yaml with new dependencies
devin-ai-integration[bot] May 14, 2025
b9e4375
Update date-fns to latest version to fix mdxdb build issue
devin-ai-integration[bot] May 14, 2025
b15d72e
Fix date-fns version to 2.30.0 to resolve compatibility issues with @…
devin-ai-integration[bot] May 14, 2025
becf764
Update minimal example: use specific mdxe version instead of workspac…
devin-ai-integration[bot] May 14, 2025
5c4ee26
Update mdxe version to 1.2.0 in minimal example
devin-ai-integration[bot] May 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/minimal/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
1 change: 1 addition & 0 deletions examples/minimal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules\n.next\n.vercel
1 change: 1 addition & 0 deletions examples/minimal/content/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test Markdown\n\nThis is a test markdown file.
18 changes: 18 additions & 0 deletions examples/minimal/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const createMDX = require('@next/mdx')

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
transpilePackages: ['mdxe']
}

const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [],
providerImportSource: "@mdx-js/react"
}
})

module.exports = withMDX(nextConfig)
6 changes: 4 additions & 2 deletions examples/minimal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"lint": "mdxe lint"
},
"dependencies": {
"mdxe": "workspace:*",
"next": "^15.3.0"
"mdxe": "^1.2.0",
"next": "^15.3.0",
"@next/mdx": "^15.3.0",
"@mdx-js/react": "^3.0.0"
}
}
7 changes: 7 additions & 0 deletions examples/minimal/pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
15 changes: 15 additions & 0 deletions examples/minimal/pages/_document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
10 changes: 10 additions & 0 deletions examples/minimal/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'

export default function Home() {
return (
<div>
<h1>MDXE Minimal Example</h1>
<p>This is a minimal example of using MDXE to serve Markdown and MDX files.</p>
</div>
)
}
5 changes: 5 additions & 0 deletions examples/minimal/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"buildCommand": "pnpm build",
"installCommand": "pnpm install",
"framework": "nextjs"
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
"printWidth": 160,
"tabWidth": 2,
"semi": false
},
"dependencies": {
"date-fns": "^2.30.0"
}
}
17 changes: 8 additions & 9 deletions packages/mdxe/bin/mdxe.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,21 @@ import { join, resolve } from 'path'
import { spawn } from 'child_process'
import { fileURLToPath } from 'url'
import { dirname } from 'path'
import { isDirectory, isMarkdownFile, findIndexFile, resolvePath, getAllMarkdownFiles, filePathToRoutePath } from '../src/utils/file-resolution.js'
import { resolvePath } from '../src/utils/file-resolution.js'
import { createTempNextConfig } from '../src/utils/temp-config.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

const packageJsonPath = join(__dirname, '..', 'package.json')
const packageJson = JSON.parse(await import('fs').then((fs) => fs.readFileSync(packageJsonPath, 'utf8')))
const fs = await import('fs')
const packageJson = JSON.parse(await fs.readFileSync(packageJsonPath, 'utf8'))
const version = packageJson.version

const program = new Command()

program.name('mdxe').description('Zero-config CLI for serving Markdown and MDX files').version(version)

const findConfigFile = (dir, filename) => {
const configPath = join(dir, filename)
return existsSync(configPath) ? configPath : null
}

let activeProcess = null
let tempConfigInfo = null

Expand All @@ -48,6 +44,8 @@ const runNextCommand = async (command, args = []) => {
const embeddedAppPath = resolve(mdxeRoot, 'src')

try {
tempConfigInfo = await createTempNextConfig(userCwd)

const readmePath = resolve(userCwd, 'README.md')
const hasReadme = existsSync(readmePath)

Expand All @@ -71,8 +69,8 @@ const runNextCommand = async (command, args = []) => {

activeProcess = spawn(cmd, cmdArgs, {
stdio: 'inherit',
shell: true,
cwd: embeddedAppPath,
shell: process.platform === 'win32', // Only use shell when necessary
cwd: tempConfigInfo.tempDir, // Use the temp directory as the working directory
env: {
...process.env,
PAYLOAD_DB_PATH: resolve(userCwd, 'mdx.db'),
Expand Down Expand Up @@ -125,6 +123,7 @@ program
.command('lint')
.description('Run linting on the application')
.action(async () => {
// No flags needed as we're creating an ESLint config file in the temp directory
await runNextCommand('lint')
})

Expand Down
1 change: 1 addition & 0 deletions packages/mdxe/src/config/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const nextConfig = {
images: {
domains: (process.env.NEXT_IMAGE_DOMAINS || '').split(',').filter(Boolean),
},
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
}

module.exports = nextConfig
3 changes: 2 additions & 1 deletion packages/mdxe/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
"**/*.ts",
"**/*.tsx",
"/Users/nathanclevenger/Projects/mdx/examples/minimal/.next/types/**/*.ts",
"/Users/nathanclevenger/Projects/mdx/packages/mdxe/.next/types/**/*.ts",
"next-env.d.ts",
"/Users/nathanclevenger/Projects/mdx/packages/mdxe/.next/types/**/*.ts"
"/home/ubuntu/repos/mdx/examples/minimal/.next/types/**/*.ts"
],
"exclude": [
"node_modules"
Expand Down
150 changes: 65 additions & 85 deletions packages/mdxe/src/utils/temp-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,8 @@ export async function createTempNextConfig(contentDir) {
mkdirSync(tempDir, { recursive: true })
}

const appDir = join(tempDir, 'app')
mkdirSync(appDir, { recursive: true })

// Create API directory for server components
const apiDir = join(appDir, 'api')
mkdirSync(apiDir, { recursive: true })

const contentApiDir = join(apiDir, 'content')
mkdirSync(contentApiDir, { recursive: true })
const pagesDir = join(tempDir, 'pages')
mkdirSync(pagesDir, { recursive: true })

const mdxeDir = resolve(__dirname, '..', '..')
const configFiles = [
Expand All @@ -37,30 +30,81 @@ export async function createTempNextConfig(contentDir) {
{ src: join(mdxeDir, 'src', 'config', 'mdx-components.js'), dest: join(tempDir, 'mdx-components.js') }
]

// Create ESLint configuration to prevent prompts during lint
await fs.writeFile(
join(tempDir, '.eslintrc.json'),
JSON.stringify({
extends: "next/core-web-vitals"
}, null, 2)
)

for (const { src, dest } of configFiles) {
await fs.copyFile(src, dest)
}

// Create _document.js in pages directory to properly handle Html component
await fs.writeFile(
join(pagesDir, '_document.js'),
`import Document, { Html, Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<body className="prose prose-slate max-w-7xl mx-auto p-4">
<Main />
<NextScript />
</body>
</Html>
)
}
}
`
)

// Create _app.js in pages directory to handle global styles and layouts
await fs.writeFile(
join(appDir, 'layout.tsx'),
`
export default function RootLayout({ children }) {
join(pagesDir, '_app.js'),
`import React from 'react'

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
`
)

// Create index.js in pages directory to serve as the main page
await fs.writeFile(
join(pagesDir, 'index.js'),
`import React from 'react'

export default function Home() {
return (
<html lang="en">
<body>{children}</body>
</html>
<div>
<h1>MDXE Content</h1>
<p>Welcome to your MDX content site.</p>
</div>
)
}
`
)

// Create the API route for content listing
// Create a pages API route for content listing instead of App Router API route
const pagesApiDir = join(pagesDir, 'api')
mkdirSync(pagesApiDir, { recursive: true })

const contentApiPageDir = join(pagesApiDir, 'content')
mkdirSync(contentApiPageDir, { recursive: true })

await fs.writeFile(
join(contentApiDir, 'route.js'),
join(contentApiPageDir, 'index.js'),
`import { readdir } from 'fs/promises'
import { join } from 'path'

export async function GET() {
export default async function handler(req, res) {
try {
const contentDir = process.env.MDXE_CONTENT_DIR || '.'
const filePaths = []
Expand All @@ -81,79 +125,15 @@ export async function GET() {

await findMarkdownFiles(contentDir)

return Response.json({ files: filePaths })
res.status(200).json({ files: filePaths })
} catch (error) {
console.error('Error loading content:', error)
return Response.json({ files: [], error: error.message }, { status: 500 })
res.status(500).json({ files: [], error: error.message })
}
}
`
)

// Create the client page component
await fs.writeFile(
join(appDir, 'page.tsx'),
`"use client"

import { useEffect, useState } from 'react'

export default function Page() {
const [contentPaths, setContentPaths] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)

useEffect(() => {
async function fetchContent() {
try {
setIsLoading(true)
const response = await fetch('/api/content')

if (!response.ok) {
throw new Error('Failed to fetch content')
}

const data = await response.json()
setContentPaths(data.files)
} catch (err) {
console.error('Error fetching content:', err)
setError(err.message)
} finally {
setIsLoading(false)
}
}

fetchContent()
}, [])

if (isLoading) {
return <div className="p-4">Loading content...</div>
}

if (error) {
return <div className="p-4 text-red-500">Error: {error}</div>
}

return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">MDXE Content</h1>
{contentPaths.length === 0 ? (
<p>No markdown content found.</p>
) : (
<ul className="list-disc pl-6">
{contentPaths.map((path, index) => (
<li key={index} className="mb-2">
<a href={'/content/' + path.replace(process.env.MDXE_CONTENT_DIR || '.', '')} className="text-blue-500 hover:underline">
{path.split('/').pop()}
</a>
</li>
))}
</ul>
)}
</div>
)
}`
)

const cleanup = async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true })
Expand All @@ -164,7 +144,7 @@ export default function Page() {

return {
tempDir,
appDir,
pagesDir,
cleanup
}
}
Loading
Loading