├── app ├── public │ ├── robots.txt │ ├── icon.svg │ └── unjs.svg ├── tsconfig.json ├── modules │ ├── og-image │ │ ├── runtime │ │ │ ├── assets │ │ │ │ ├── fonts │ │ │ │ │ ├── PublicSans-Bold.woff2 │ │ │ │ │ ├── PublicSans-Thin.woff2 │ │ │ │ │ ├── PublicSans-Black.woff2 │ │ │ │ │ ├── PublicSans-Light.woff2 │ │ │ │ │ ├── PublicSans-Medium.woff2 │ │ │ │ │ ├── PublicSans-Regular.woff2 │ │ │ │ │ └── PublicSans-ExtraLight.woff2 │ │ │ │ ├── template.svg │ │ │ │ └── unjs.svg │ │ │ └── handler.ts │ │ └── index.ts │ ├── content │ │ ├── icons.ts │ │ ├── index.ts │ │ └── hooks.ts │ ├── css.ts │ └── theme.ts ├── layouts │ ├── blog.vue │ └── docs.vue ├── components │ ├── global │ │ ├── Important.vue │ │ ├── Mermaid.vue │ │ ├── Pm-x.vue │ │ ├── Pm-Run.vue │ │ ├── Pm-Install.vue │ │ └── ReadMore.vue │ ├── OrgLogoBase.vue │ ├── AppFooterNotesBase.vue │ ├── AppFooter.vue │ ├── AppFooterNotes.vue │ ├── color-picker │ │ ├── ColorPickerPill.vue │ │ └── ColorPicker.vue │ ├── page │ │ ├── PageContributors.vue │ │ └── PageSponsors.vue │ ├── AppHeaderVersionsMenu.vue │ ├── DocsCollapsable.vue │ ├── SocialButtons.vue │ ├── LandingBackground.vue │ ├── IconMenuToggle.vue │ ├── AppHeader.vue │ └── OrgLogo.vue ├── utils │ ├── title.ts │ ├── pm.ts │ └── numbers.ts ├── assets │ ├── icons │ │ ├── crossws.svg │ │ ├── rou3.svg │ │ ├── h3.svg │ │ ├── srvx.svg │ │ ├── hono.svg │ │ ├── nitro.svg │ │ └── elysia.svg │ └── main.css ├── content.config.ts ├── composables │ ├── useSponsors.ts │ ├── useContributors.ts │ ├── useMermaid.ts │ ├── useDocsNav.ts │ └── usePageSEO.ts ├── error.vue ├── pages │ ├── blog │ │ ├── index.vue │ │ └── [...slug].vue │ ├── [...slug].vue │ └── index.vue ├── app.vue ├── nuxt.config.ts └── app.config.ts ├── docs ├── .foo │ ├── bar.md │ └── baz.md ├── blog │ ├── 0.index.md │ ├── 1.initial-release.md │ ├── 2.second-release.md │ └── 3.new-blog.md ├── 1.guide │ ├── components │ │ ├── .navigation.yml │ │ ├── components.md │ │ └── content-transformation.md │ └── 1.index.md ├── package.json ├── .partials │ └── warn.md ├── .config │ ├── automd.ts │ └── docs.yaml ├── .docs │ └── public │ │ └── icon.svg └── 2.config │ └── 1.index.md ├── template ├── .gitignore ├── 1.guide │ └── 1.index.md ├── package.json ├── .config │ └── docs.yaml └── .docs │ └── public │ └── icon.svg ├── .npmrc ├── renovate.json ├── assets └── ellipse.png ├── .prettierrc ├── schema ├── config.schema.ts ├── config.d.ts └── config.json ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── tsconfig.json ├── .editorconfig ├── cli ├── main.mjs ├── cli.mjs └── setup.mjs ├── .github └── workflows │ ├── checks.yml │ ├── publish.yml │ └── autofix.yml ├── .gitignore ├── eslint.config.mjs ├── LICENSE ├── README.md ├── package.json └── CHANGELOG.md /app/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /docs/.foo/bar.md: -------------------------------------------------------------------------------- 1 | # Foo bar 2 | 3 | foobar... 4 | -------------------------------------------------------------------------------- /docs/.foo/baz.md: -------------------------------------------------------------------------------- 1 | # Foo baz 2 | 3 | foobaz... 4 | -------------------------------------------------------------------------------- /docs/blog/0.index.md: -------------------------------------------------------------------------------- 1 | # Blog 2 | 3 | > Latest undocs updates! 4 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nuxt 3 | .output 4 | dist 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | ignore-workspace-root-check=true 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>unjs/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../docs/.docs/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /assets/ellipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/assets/ellipse.png -------------------------------------------------------------------------------- /docs/1.guide/components/.navigation.yml: -------------------------------------------------------------------------------- 1 | # icon: material-symbols:markdown-outline 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "vueIndentScriptAndStyle": false, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /schema/config.schema.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error 2 | import $schema from './config.json' 3 | 4 | export default { 5 | $schema, 6 | } 7 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.18.2 2 | 3 | RUN apt update && apt install -y sudo linux-perf 4 | 5 | RUN curl -fsSL https://bun.sh/install | bash 6 | 7 | -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Bold.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Thin.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Black.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Light.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Medium.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-Regular.woff2 -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/fonts/PublicSans-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/undocs/HEAD/app/modules/og-image/runtime/assets/fonts/PublicSans-ExtraLight.woff2 -------------------------------------------------------------------------------- /app/layouts/blog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /docs/blog/1.initial-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: release 3 | date: 2000-01-01 4 | --- 5 | 6 | # Initial release 7 | 8 | > Undocs is out! 9 | 10 | [release notes and other content] 11 | -------------------------------------------------------------------------------- /template/1.guide/1.index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | install packageName from npm 4 | 5 | You can install packageName from [npm](https://npmjs.com/packageName): 6 | 7 | :pm-install{name="packageName"} 8 | -------------------------------------------------------------------------------- /app/components/global/Important.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/blog/2.second-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: release 3 | date: 2001-01-01 4 | --- 5 | 6 | # Second release 7 | 8 | > Undocs is out with improvements! 9 | 10 | [release notes and other content] 11 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "docs", 4 | "scripts": { 5 | "dev": "undocs dev", 6 | "build": "undocs build" 7 | }, 8 | "devDependencies": { 9 | "undocs": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "docs", 4 | "scripts": { 5 | "dev": "undocs dev", 6 | "build": "undocs build" 7 | }, 8 | "devDependencies": { 9 | "undocs": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/utils/title.ts: -------------------------------------------------------------------------------- 1 | import { titleCase as _titleCase } from 'scule' 2 | 3 | export function titleCase(key: string) { 4 | let label = _titleCase(key) 5 | if (label === 'Api') { 6 | label = 'API' 7 | } 8 | return label 9 | } 10 | -------------------------------------------------------------------------------- /app/components/OrgLogoBase.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "skipLibCheck": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "types": ["node"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [{package.json,*.yml,*.cjson}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /app/components/AppFooterNotesBase.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /cli/main.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { createCLI } from './cli.mjs' 4 | 5 | const cli = createCLI({ 6 | name: 'undocs', 7 | description: 'UnJS Docs Tool', 8 | setup: { 9 | defaults: { 10 | github: 'unjs', 11 | themeColor: 'amber', 12 | }, 13 | }, 14 | }) 15 | 16 | cli.runMain() 17 | -------------------------------------------------------------------------------- /docs/.partials/warn.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Undocs is currently intended for UnJS docs only and is not fully customizable yet.
3 | > Contributions are more than welcome but please consider that this project is not ready yet to be used.
4 | > We don't guarantee stability yet and it is expected that it doesn't work time to time. 5 | -------------------------------------------------------------------------------- /docs/.config/automd.ts: -------------------------------------------------------------------------------- 1 | import { defineGenerator, type Config } from 'automd' 2 | 3 | export default { 4 | generators: { 5 | test: defineGenerator({ 6 | name: 'test', 7 | async generate() { 8 | return { 9 | contents: `automd works!`, 10 | } 11 | }, 12 | }), 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /docs/blog/3.new-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: release 3 | date: 2025-06-01 4 | authors: 5 | - name: Pooya Parsa 6 | github: pi0 7 | --- 8 | 9 | # New blog section 10 | 11 | > Undocs now allows easily creating a blog section! 12 | 13 | ## Section 14 | 15 | [release notes and other content] 16 | 17 | ## Section 18 | 19 | [another section] 20 | -------------------------------------------------------------------------------- /app/assets/icons/crossws.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/icons/rou3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | on: { push: {}, pull_request: {} } 3 | jobs: 4 | checks: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v6 8 | - run: npm i -fg corepack && corepack enable 9 | - uses: actions/setup-node@v6 10 | with: { node-version: 24, cache: 'pnpm' } 11 | - run: pnpm install 12 | - run: pnpm run lint 13 | -------------------------------------------------------------------------------- /app/assets/icons/h3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/icons/srvx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/content.config.ts: -------------------------------------------------------------------------------- 1 | import { defineContentConfig, defineCollection } from '@nuxt/content' 2 | 3 | export default defineContentConfig({ 4 | collections: { 5 | content: defineCollection({ 6 | type: 'page', 7 | source: { 8 | cwd: globalThis.__DOCS_CWD__, 9 | include: '**/*.{md,yml}', 10 | exclude: ['**/.**/**', '**/node_modules/**', '**/dist/**', '**/.docs/**'], 11 | }, 12 | }), 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | .eslintcache 21 | 22 | # Local env files 23 | .env 24 | .env.* 25 | !.env.example 26 | 27 | # Template 28 | template/pnpm-lock.yaml 29 | 30 | # npm pack 31 | *.tgz 32 | 33 | # Temp files 34 | .tmp 35 | .profile 36 | *.0x 37 | -------------------------------------------------------------------------------- /app/assets/icons/hono.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { createConfigForNuxt } from '@nuxt/eslint-config/flat' 2 | 3 | export default createConfigForNuxt( 4 | {}, 5 | { 6 | rules: { 7 | 'vue/multi-word-component-names': 'off', 8 | '@typescript-eslint/no-explicit-any': 'off', 9 | 'unicorn/prefer-module': 'off', 10 | '@typescript-eslint/ban-ts-comment': 'off', 11 | 'vue/html-self-closing': 'off', 12 | 'vue/first-attribute-linebreak': 'off', 13 | }, 14 | }, 15 | ) 16 | -------------------------------------------------------------------------------- /app/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /app/composables/useSponsors.ts: -------------------------------------------------------------------------------- 1 | export interface Sponsors { 2 | username: string 3 | sponsors: { 4 | name: string 5 | image: string 6 | inactive?: boolean 7 | website: string 8 | }[][] 9 | } 10 | 11 | export async function useSponsors(): Promise { 12 | const appConfig = useAppConfig() 13 | const sponsorsAPI = appConfig.docs.sponsors?.api 14 | if (sponsorsAPI) { 15 | return (await $fetch(sponsorsAPI)) || undefined 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/main.css: -------------------------------------------------------------------------------- 1 | @theme static { 2 | --font-sans: 'Inter', sans-serif; 3 | } 4 | 5 | :root { 6 | --ui-container: 90rem; 7 | } 8 | 9 | .fade-enter-active, 10 | .fade-leave-active { 11 | transition: opacity 0.2s ease-in-out; 12 | } 13 | 14 | .fade-enter-from, 15 | .fade-leave-to { 16 | opacity: 0; 17 | } 18 | 19 | .md a { 20 | color: var(--ui-primary); 21 | } 22 | 23 | pre .shiki span.line { 24 | display: inline; 25 | } 26 | 27 | main { 28 | min-height: calc(100vh - 150px); 29 | } 30 | -------------------------------------------------------------------------------- /app/components/global/Mermaid.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | -------------------------------------------------------------------------------- /app/utils/pm.ts: -------------------------------------------------------------------------------- 1 | export const packageManagers = [ 2 | { name: 'npm', command: 'npm', install: 'i ', run: 'run ', x: 'npx ' }, 3 | { name: 'yarn', command: 'yarn', install: 'add ', run: '', x: 'yarn dlx ' }, 4 | { name: 'pnpm', command: 'pnpm', install: 'i ', run: '', x: 'pnpm dlx ' }, 5 | { name: 'bun', command: 'bun', install: 'i ', run: 'run ', x: 'bunx ' }, 6 | { name: 'deno', command: 'deno', install: 'i npm:', run: 'run ', x: 'deno run -A npm:' }, 7 | // { name: 'auto', command: 'npx nypm', install: 'i ', run: 'run ', x: 'npx ' }, 8 | ] as const 9 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: { push: { branches: [main] } } 3 | permissions: { id-token: write, contents: read } 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v6 9 | with: { fetch-depth: 0 } 10 | - run: npm i -fg corepack && corepack enable 11 | - uses: actions/setup-node@v6 12 | with: { node-version: 24, cache: 'pnpm' } 13 | - run: pnpm install 14 | - run: pnpm changelogen --bump --canary nightly 15 | - run: npm publish --tag latest 16 | -------------------------------------------------------------------------------- /template/.config/docs.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json 2 | 3 | name: 'packageName' 4 | shortDescription: '' 5 | description: '' 6 | github: 'unjs/packageName' 7 | # url: 'https://packageName.unjs.io' 8 | # redirects: 9 | # "/from": "/to" 10 | # automd: true 11 | landing: 12 | heroLinks: 13 | stackblitz: 14 | icon: 'i-heroicons-play' 15 | to: 'https://stackblitz.com/github/unjs/packageName/tree/main/playground' 16 | # contributors: true 17 | # features: 18 | # - title: 19 | # description: 20 | -------------------------------------------------------------------------------- /app/utils/numbers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Format number using k, m, M to have the shortest possible string. 3 | */ 4 | export function formatNumber(value: number, fractionDigits = 1): string { 5 | if (value < 1e3) { 6 | return value.toString() 7 | } 8 | 9 | if (value < 1e6) { 10 | return `${(value / 1e3).toFixed(fractionDigits)}k` 11 | } 12 | 13 | if (value < 1e9) { 14 | return `${(value / 1e6).toFixed(fractionDigits)}m` 15 | } 16 | 17 | if (value < 1e12) { 18 | return `${(value / 1e9).toFixed(fractionDigits)}M` 19 | } 20 | 21 | return value.toString() 22 | } 23 | -------------------------------------------------------------------------------- /app/components/AppFooterNotes.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci 2 | on: { push: {}, pull_request: {} } 3 | permissions: { contents: read } 4 | jobs: 5 | autofix: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v6 9 | - run: npm i -fg corepack && corepack enable 10 | - uses: actions/setup-node@v6 11 | with: { node-version: 24, cache: 'pnpm' } 12 | - run: pnpm install 13 | - run: pnpm lint:fix 14 | - run: pnpm automd 15 | - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 16 | with: { commit-message: 'chore: apply automated updates' } 17 | -------------------------------------------------------------------------------- /app/modules/content/icons.ts: -------------------------------------------------------------------------------- 1 | export const CommonIcons = [ 2 | { 3 | pattern: 'guide', 4 | icon: 'i-lucide-book-open', 5 | }, 6 | { 7 | pattern: 'components', 8 | icon: 'i-lucide-component', 9 | }, 10 | { 11 | pattern: 'config', 12 | icon: 'i-lucide-settings', 13 | }, 14 | { 15 | pattern: 'configuration', 16 | icon: 'i-lucide-settings', 17 | }, 18 | { 19 | pattern: 'examples', 20 | icon: 'i-lucide-code', 21 | }, 22 | { 23 | pattern: 'utils', 24 | icon: 'i-lucide-square-function', 25 | }, 26 | { 27 | pattern: 'blog', 28 | icon: 'i-lucide-file-text', 29 | }, 30 | ] 31 | -------------------------------------------------------------------------------- /app/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/layouts/docs.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // https://code.visualstudio.com/docs/devcontainers/containers 2 | // https://containers.dev/implementors/json_reference/ 3 | { 4 | "build": { 5 | "dockerfile": "Dockerfile" 6 | }, 7 | "features": {}, 8 | "customizations": { 9 | "vscode": { 10 | "settings": {}, 11 | "extensions": [ 12 | "ms-azuretools.vscode-docker", 13 | "dbaeumer.vscode-eslint", 14 | "github.vscode-github-actions", 15 | "esbenp.prettier-vscode" 16 | ] 17 | } 18 | }, 19 | "runArgs": ["--privileged"], 20 | "postStartCommand": "corepack enable && pnpm install", 21 | "mounts": ["type=volume,target=${containerWorkspaceFolder}/node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /app/components/global/Pm-x.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/components/global/Pm-Run.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/components/global/Pm-Install.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/composables/useContributors.ts: -------------------------------------------------------------------------------- 1 | export interface Contributor { 2 | name: string 3 | username: string 4 | profile: string 5 | avatar: string 6 | } 7 | 8 | export async function useContributors(): Promise { 9 | const { docs: docsConfig } = useAppConfig() 10 | if (!docsConfig.github) { 11 | return 12 | } 13 | const { contributors = [] } = await await $fetch<{ contributors: { username: string }[] }>( 14 | `https://ungh.cc/repos/${docsConfig.github}/contributors`, 15 | ) 16 | return contributors 17 | .filter((c) => !c.username.includes('bot')) 18 | .map((c) => ({ 19 | name: `@${c.username}`, 20 | username: c.username, 21 | profile: `https://github.com/${c.username}`, 22 | avatar: `https://github.com/${c.username}.png`, 23 | })) 24 | } 25 | -------------------------------------------------------------------------------- /app/modules/content/index.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule } from 'nuxt/kit' 2 | import type { DocsConfig } from '../../../schema/config' 3 | import { setupContentHooks } from './hooks' 4 | 5 | export default defineNuxtModule({ 6 | async setup(_, nuxt) { 7 | if (nuxt.options._prepare) { 8 | return 9 | } 10 | 11 | const docsConfig = (nuxt.options as any).docs as DocsConfig 12 | 13 | await setupContentHooks(nuxt, docsConfig) 14 | 15 | if (docsConfig.landing === false) { 16 | nuxt.hooks.hook('pages:extend', (pages) => { 17 | const index = pages.findIndex((page) => page.path === '/') 18 | if (index !== -1) { 19 | pages.splice(index, 1) 20 | } 21 | }) 22 | } 23 | 24 | // @ts-ignore 25 | globalThis.__undocs__ = { docsConfig } 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /app/components/color-picker/ColorPickerPill.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /app/components/page/PageContributors.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /app/error.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 46 | -------------------------------------------------------------------------------- /app/modules/og-image/index.ts: -------------------------------------------------------------------------------- 1 | import { addServerHandler, createResolver, defineNuxtModule } from 'nuxt/kit' 2 | 3 | export default defineNuxtModule({ 4 | setup(_, nuxt) { 5 | if (nuxt.options._prepare) { 6 | return 7 | } 8 | const resolver = createResolver(import.meta.url) 9 | 10 | addServerHandler({ 11 | route: '/_og/**', 12 | handler: resolver.resolve('./runtime/handler'), 13 | }) 14 | 15 | nuxt.options.nitro.serverAssets = [ 16 | ...(nuxt.options.nitro.serverAssets || []).filter((asset) => asset.baseName !== 'og-image'), 17 | { 18 | baseName: 'og-image', 19 | dir: resolver.resolve('./runtime/assets'), 20 | }, 21 | ] 22 | 23 | nuxt.hook('nitro:init', (nitro) => { 24 | nitro.hooks.hook('prerender:generate', (route) => { 25 | if (route.route.startsWith('/_og/')) { 26 | // temp patch for nitro prerenderer 27 | route.route = route.route.split('?')[0] 28 | route.fileName = route.fileName.split('?')[0] 29 | } 30 | }) 31 | }) 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /app/composables/useMermaid.ts: -------------------------------------------------------------------------------- 1 | import { watch, type WatchSource } from 'vue' 2 | import mermaid from 'mermaid/dist/mermaid.esm.min.mjs' 3 | 4 | const mermaidCache: Record> = Object.create(null) 5 | 6 | export function useMermaid(source: WatchSource): Ref { 7 | const svg = ref(null) 8 | const id = Math.random().toString(36).substring(2, 15) 9 | const cache = mermaidCache[id] || (mermaidCache[id] = {}) 10 | watch( 11 | source, 12 | async (value) => { 13 | if (!source || import.meta.server) { 14 | svg.value = null 15 | return 16 | } 17 | if (cache[value]) { 18 | svg.value = cache[value] 19 | return 20 | } 21 | try { 22 | const res = await mermaid.render(`mermaid-${id}`, value) 23 | cache[value] = res.svg 24 | svg.value = res.svg 25 | } catch (error) { 26 | console.error('Error rendering mermaid diagram:', error) 27 | svg.value = `` 28 | } 29 | }, 30 | { immediate: true }, 31 | ) 32 | return svg 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Pooya Parsa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/1.guide/1.index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | > Highlights information that users should take into account, even when skimming. 4 | 5 | 6 | 7 | > [!IMPORTANT] 8 | > Undocs is currently intended for UnJS docs only and is not fully customizable yet.
9 | > Contributions are more than welcome but please consider that this project is not ready yet to be used.
10 | > We don't guarantee stability yet and it is expected that it doesn't work time to time. 11 | 12 | 13 | 14 | ## Overview 15 | 16 | UnJS Docs is a minimal Documentation Theme and CLI for shared usage across UnJS projects. 17 | 18 | It is made with [Nuxt](https://nuxt.com/), [Nuxt Content](https://content.nuxt.com), and [Nuxt UI](https://ui.nuxt.com) with a zero config and elegant CLI wrapper. 19 | 20 | ## Quick Start 21 | 22 | Create `docs/` project with starter template: 23 | 24 | :pm-x{command="giget gh:unjs/undocs/template docs --install"} 25 | 26 | Go to the docs dir `cd docs/` 27 | 28 | Start development server: 29 | 30 | :pm-run{script="dev"} 31 | 32 | Build for production: 33 | 34 | :pm-run{script="build"} 35 | -------------------------------------------------------------------------------- /app/modules/css.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule, addTemplate, createResolver } from '@nuxt/kit' 2 | import { joinURL } from 'ufo' 3 | import { resolveModulePath } from 'exsolve' 4 | 5 | export default defineNuxtModule({ 6 | meta: { 7 | name: 'css', 8 | }, 9 | async setup(_options, nuxt) { 10 | const dir = nuxt.options.rootDir 11 | const resolver = createResolver(import.meta.url) 12 | 13 | const contentDir = joinURL(dir, '../') 14 | const uiPath = resolveModulePath('@nuxt/ui', { from: import.meta.url, conditions: ['style'] }) 15 | const tailwindPath = resolveModulePath('tailwindcss', { from: import.meta.url, conditions: ['style'] }) 16 | const layerDir = resolver.resolve('../') 17 | 18 | const cssTemplate = addTemplate({ 19 | filename: 'undocs.css', 20 | getContents: () => { 21 | return `@import ${JSON.stringify(tailwindPath)}; 22 | @import ${JSON.stringify(uiPath)}; 23 | 24 | @source "${contentDir.replace(/\\/g, '/')}/**/*.md"; 25 | @source "${layerDir.replace(/\\/g, '/')}/**/*"; 26 | @source "../../app.config.ts";` 27 | }, 28 | }) 29 | 30 | nuxt.options.css.unshift(cssTemplate.dst) 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /schema/config.d.ts: -------------------------------------------------------------------------------- 1 | import type { BannerProps } from '@nuxt/ui' 2 | 3 | export interface DocsConfig { 4 | dir?: string 5 | name?: string 6 | description?: string 7 | shortDescription?: string 8 | url?: string 9 | github?: string 10 | socials?: Record 11 | branch?: string 12 | banner?: BannerProps 13 | versions?: { label: string; to: string; active?: boolean }[] 14 | themeColor?: string 15 | redirects?: Record 16 | automd?: unknown 17 | buildCache?: boolean 18 | sponsors?: { api: string } 19 | landing?: 20 | | false 21 | | { 22 | title?: string 23 | description?: string 24 | _heroMdTitle?: string 25 | heroTitle?: string 26 | heroSubtitle?: string 27 | heroDescription?: string 28 | heroLinks?: Record< 29 | string, 30 | string | { label?: string; icon?: string; to?: string; size?: string; order?: number } 31 | > 32 | heroCode?: string | { content: string; title?: string; lang?: string } 33 | featuresTitle?: string 34 | featuresLayout?: 'default' | 'hero' 35 | features?: { title: string; description?: string; icon?: string }[] 36 | contributors?: boolean 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/modules/theme.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule } from 'nuxt/kit' 2 | import type { DocsConfig } from '../../schema/config' 3 | 4 | export default defineNuxtModule({ 5 | setup(_, nuxt) { 6 | if (nuxt.options._prepare) { 7 | return 8 | } 9 | 10 | const docsConfig = (nuxt.options as any).docs as DocsConfig 11 | 12 | const uiConfig = { 13 | primary: docsConfig.themeColor || 'amber', 14 | gray: 'neutral', 15 | } 16 | 17 | // if (docsConfig.themeColor) { 18 | // const { getColors } = await import('theme-colors') 19 | // const colors = getColors(docsConfig.themeColor) 20 | // // UI 21 | // // uiConfig.primary = colors['500'] 22 | // // Tailwind 23 | // nuxt.options.tailwindcss ||= {} as any 24 | // nuxt.options.tailwindcss.config ||= {} 25 | // nuxt.options.tailwindcss.config.theme ||= {} 26 | // nuxt.options.tailwindcss.config.theme.extend ||= {} 27 | // nuxt.options.tailwindcss.config.theme.extend.colors = { 28 | // ...colors, 29 | // ...nuxt.options.tailwindcss.config.theme.extend.colors, 30 | // } 31 | // } 32 | 33 | nuxt.hook('ready', () => { 34 | nuxt.options.appConfig.ui = { 35 | ...nuxt.options.appConfig.ui, 36 | ...uiConfig, 37 | } 38 | }) 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /docs/.config/docs.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json 2 | 3 | name: 'UnDocs' 4 | shortDescription: 'Docs, made easy.' 5 | description: 'Elegant documentation tooling for UnJS ecosystem.' 6 | github: 'unjs/undocs' 7 | themeColor: blue 8 | socials: 9 | x: 'https://x.com/unjsio' 10 | bluesky: 'https://bsky.app/profile/unjs.io' 11 | discord: '#' 12 | sponsors: 13 | api: https://sponsors.pi0.io/sponsors.json 14 | url: 'https://undocs.pages.dev' 15 | redirects: 16 | '/docs': '/docs/getting-started' 17 | '/old': '/docs' 18 | automd: true 19 | landing: 20 | heroLinks: 21 | stackblitz: 22 | icon: 'i-heroicons-play' 23 | to: 'https://stackblitz.com/github/unjs/undocs/tree/main/template' 24 | heroCode: 'npx giget gh:unjs/undocs/template docs --install' 25 | contributors: true 26 | features: 27 | - title: 'Easy to use' 28 | icon: '⚡' 29 | description: 'Focus on writing your *documentation* with Markdown, not tooling.' 30 | - title: 'Nuxt powered' 31 | icon: 'logos:nuxt-icon' 32 | description: 'Made with [Nuxt](https://nuxt.com), [Nuxt Content](https://content.nuxt.com), and [Nuxt UI](https://ui.nuxt.com).' 33 | - title: 'Deploy anywhere' 34 | icon: '✨' 35 | description: 'The documentation can be hosted on any static hosting.' 36 | -------------------------------------------------------------------------------- /app/components/global/ReadMore.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 48 | -------------------------------------------------------------------------------- /app/components/AppHeaderVersionsMenu.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /app/components/DocsCollapsable.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 44 | -------------------------------------------------------------------------------- /app/composables/useDocsNav.ts: -------------------------------------------------------------------------------- 1 | import type { ContentNavigationItem } from '@nuxt/content' 2 | 3 | export function useDocsNav() { 4 | const navigation = inject>('navigation') 5 | const route = useRoute() 6 | const isActive = (path: string) => route.path.startsWith(path) 7 | 8 | const links = computed(() => { 9 | return navigation.value.map((item) => { 10 | // Flaten single child 11 | if (item.children?.length === 1) { 12 | item = { 13 | ...item, 14 | ...item.children[0], 15 | children: undefined, 16 | } 17 | } 18 | 19 | // Check if group index is not exists and default to first child 20 | const originalPath = item.path 21 | if (item.children?.length && !item.children.some((c) => c.path === originalPath)) { 22 | item.path = item.children[0].path 23 | } 24 | 25 | return { 26 | ...item, 27 | to: item.path, 28 | originalPath, 29 | hasIndex: item.path === originalPath, 30 | label: item.title || titleCase(originalPath), 31 | active: isActive(originalPath), 32 | } 33 | }) 34 | }) 35 | 36 | const activeSection = computed(() => links.value.find((l) => route.path.startsWith(l.originalPath))) 37 | const activeLinks = computed(() => (activeSection.value?.children || []).filter(Boolean)) 38 | 39 | return reactive({ 40 | links, 41 | activeSection, 42 | activeLinks, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /app/components/SocialButtons.vue: -------------------------------------------------------------------------------- 1 | 38 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnJS Docs 2 | 3 | Minimal Documentation Theme and CLI for shared usage across UnJS projects. 4 | 5 | 6 | 7 | > [!IMPORTANT] 8 | > Undocs is currently intended for UnJS docs only and is not fully customizable yet.
9 | > Contributions are more than welcome but please consider that this project is not ready yet to be used.
10 | > We don't guarantee stability yet and it is expected that it doesn't work time to time. 11 | 12 | 13 | 14 | ## Contribution 15 | 16 |
17 | Local development 18 | 19 | - Clone this repository 20 | - Install the latest LTS version of [Node.js](https://nodejs.org/en/) 21 | - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` 22 | - Install dependencies using `pnpm install` 23 | - Run tests using `pnpm dev` 24 | 25 |
26 | 27 | 28 | 29 | ## License 30 | 31 | 32 | 33 | Published under the [MIT](https://github.com/unjs/undocs/blob/main/LICENSE) license. 34 | Made by [@pi0](https://github.com/pi0), [@atinux](https://github.com/atinux) and [community](https://github.com/unjs/undocs/graphs/contributors) 💛 35 |

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | --- 45 | 46 | _🤖 auto updated with [automd](https://automd.unjs.io)_ 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/components/LandingBackground.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 58 | -------------------------------------------------------------------------------- /app/composables/usePageSEO.ts: -------------------------------------------------------------------------------- 1 | export interface PageMeta { 2 | title: string 3 | ogTitle: string 4 | description: string 5 | } 6 | 7 | export function usePageSEO(page: PageMeta) { 8 | const route = useRoute() 9 | const appConfig = useAppConfig() 10 | 11 | useSeoMeta({ 12 | title: page.title, 13 | description: page.description, 14 | }) 15 | 16 | if (!(import.meta.server || import.meta.dev || import.meta.prerender)) { 17 | return 18 | } 19 | 20 | const path = route.path === '/' ? '/_index' : route.path 21 | const canonicalURL = import.meta.dev ? useRequestURL() : appConfig.site.url 22 | const ogURL = new URL(`/_og${path}.png`, canonicalURL) 23 | 24 | ogURL.searchParams.set('name', appConfig.site.name) 25 | ogURL.searchParams.set('title', page.ogTitle || page.title || appConfig.site.name) 26 | ogURL.searchParams.set('description', page.description || appConfig.site.description || '') 27 | 28 | useSeoMeta({ 29 | ogTitle: page.title, 30 | ogDescription: page.description, 31 | ogImage: { 32 | url: ogURL.href, 33 | width: 1200, 34 | height: 600, 35 | type: 'image/png', 36 | alt: page.description || appConfig.site.description, 37 | }, 38 | twitterCard: 'summary_large_image', 39 | twitterImage: { 40 | url: ogURL.href, 41 | width: 1200, 42 | height: 600, 43 | alt: page.description || appConfig.site.description, 44 | }, 45 | }) 46 | 47 | useHead({ 48 | link: [ 49 | { 50 | rel: 'icon', 51 | type: 'image/svg+xml', 52 | href: '/icon.svg', 53 | }, 54 | ], 55 | }) 56 | 57 | if (import.meta.prerender) { 58 | prerenderRoutes(ogURL.pathname + ogURL.search) 59 | ogURL.searchParams.delete('name') 60 | ogURL.searchParams.delete('title') 61 | ogURL.searchParams.delete('description') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/pages/blog/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 58 | -------------------------------------------------------------------------------- /app/app.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 73 | -------------------------------------------------------------------------------- /docs/.docs/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/components/IconMenuToggle.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 86 | -------------------------------------------------------------------------------- /app/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { createResolver } from 'nuxt/kit' 2 | import { defineNuxtConfig } from 'nuxt/config' 3 | import { eventHandler } from 'h3' 4 | 5 | const { resolve } = createResolver(import.meta.url) 6 | 7 | // Flag enabled when developing docs theme 8 | const dev = !!process.env.NUXT_DOCS_DEV 9 | 10 | // SSR enabled only for production build to save life (at least until our stack will be a little bit lighter) 11 | const isProd = process.env.NODE_ENV === 'production' 12 | const ssr = Boolean(isProd || process.env.NUXT_DOCS_SSR) 13 | 14 | export default defineNuxtConfig({ 15 | $meta: { 16 | name: 'undocs', 17 | }, 18 | ssr, 19 | modules: ['@nuxt/ui', '@nuxt/content', isProd && '@nuxtjs/plausible'], 20 | css: [resolve('./assets/main.css')], 21 | ui: { 22 | theme: { 23 | colors: ['primary', 'secondary', 'info', 'success', 'warning', 'error', 'important'], 24 | }, 25 | }, 26 | app: { 27 | head: { 28 | htmlAttrs: { 29 | dir: 'ltr', 30 | }, 31 | templateParams: { 32 | separator: '·', 33 | }, 34 | }, 35 | }, 36 | content: { 37 | experimental: { 38 | sqliteConnector: 'native', 39 | }, 40 | build: { 41 | markdown: { 42 | highlight: { 43 | theme: { 44 | default: 'github-dark', 45 | dark: 'github-dark', 46 | light: 'github-light', 47 | }, 48 | // prettier-ignore 49 | langs: ['json', 'json5', 'jsonc', 'toml', 'yaml', 'html', 'sh', 'shell', 'bash', 'mdc', 'markdown', 'md', 'vue', 'js', 'ts', 'javascript', 'typescript', 'ini', 'diff'], 50 | }, 51 | }, 52 | }, 53 | }, 54 | nitro: { 55 | devHandlers: [ 56 | { 57 | route: '/.well-known/appspecific/com.chrome.devtools.json', 58 | handler: eventHandler(() => ({})), 59 | }, 60 | ], 61 | prerender: { 62 | autoSubfolderIndex: false, 63 | failOnError: false, 64 | }, 65 | }, 66 | devtools: { 67 | enabled: dev, 68 | }, 69 | typescript: { 70 | strict: false, 71 | includeWorkspace: true, 72 | }, 73 | colorMode: { 74 | preference: 'dark', 75 | fallback: 'dark', 76 | }, 77 | }) 78 | -------------------------------------------------------------------------------- /app/components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 77 | -------------------------------------------------------------------------------- /app/components/page/PageSponsors.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "undocs", 3 | "version": "0.4.11", 4 | "repository": "unjs/undocs", 5 | "license": "MIT", 6 | "type": "module", 7 | "bin": { 8 | "undocs": "./cli/main.mjs" 9 | }, 10 | "files": [ 11 | "app", 12 | "cli", 13 | "schema", 14 | "!app/**/node_modules", 15 | "!app/**/tsconfig.json" 16 | ], 17 | "scripts": { 18 | "build": "true", 19 | "dev": "pnpm run docs:dev", 20 | "docs:build": "pnpm run undocs build docs", 21 | "docs:dev": "NUXT_DOCS_DEV=1 pnpm run undocs dev docs", 22 | "lint": "eslint --cache . && prettier -c app cli schema", 23 | "lint:fix": "eslint --cache . --fix && prettier -c -w app cli schema", 24 | "prepack": "pnpm run build", 25 | "profile:0x": "pnpx 0x -o cli/main.mjs dev docs", 26 | "profile:0x-kernel": "pnpm run profile:0x --kernel-tracing", 27 | "profile:cpu": "rm -rf .profile && node --cpu-prof --diagnostic-dir ../.profile bin/cli.mjs dev docs", 28 | "release": "pnpm run lint && changelogen --release && npm publish && git push --follow-tags", 29 | "template:dev": "pnpm run undocs dev template", 30 | "undocs": "./cli/main.mjs" 31 | }, 32 | "dependencies": { 33 | "@headlessui/vue": "^1.7.23", 34 | "@iconify-json/logos": "^1.2.9", 35 | "@iconify-json/simple-icons": "^1.2.52", 36 | "@nuxt/content": "^3.7.1", 37 | "@nuxt/fonts": "^0.11.4", 38 | "@nuxt/ui": "4.0.0-alpha.2", 39 | "@nuxtjs/plausible": "^2.0.1", 40 | "@resvg/resvg-wasm": "^2.6.2", 41 | "automd": "^0.4.2", 42 | "c12": "^3.3.0", 43 | "citty": "^0.1.6", 44 | "consola": "^3.4.2", 45 | "defu": "^6.1.4", 46 | "is-buffer": "^2.0.5", 47 | "md4w": "^0.2.7", 48 | "mermaid": "^11.12.0", 49 | "motion-v": "^1.7.1", 50 | "nitropack": "^2.12.6", 51 | "nuxi": "^3.28.0", 52 | "nuxt": "^4.1.2", 53 | "nuxt-build-cache": "^0.1.1", 54 | "nuxt-llms": "^0.1.3", 55 | "pkg-types": "^2.3.0", 56 | "scule": "^1.3.0", 57 | "shiki": "^3.12.2", 58 | "tailwindcss": "^4.1.13", 59 | "unctx": "^2.4.1", 60 | "unstorage": "^1.17.1", 61 | "vue": "^3.5.21", 62 | "vue-router": "^4.5.1" 63 | }, 64 | "devDependencies": { 65 | "@nuxt/eslint-config": "^1.9.0", 66 | "@nuxt/image": "^1.11.0", 67 | "@types/node": "^24.5.2", 68 | "changelogen": "^0.6.2", 69 | "eslint": "^9.35.0", 70 | "eslint-config-unjs": "^0.5.0", 71 | "jiti": "^2.5.1", 72 | "prettier": "^3.6.2", 73 | "typescript": "^5.9.2", 74 | "vue-tsc": "^3.0.7" 75 | }, 76 | "packageManager": "pnpm@10.17.0" 77 | } 78 | -------------------------------------------------------------------------------- /app/pages/blog/[...slug].vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 80 | -------------------------------------------------------------------------------- /docs/2.config/1.index.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | > Learn how to configure the documentation site made with UnDocs. 4 | 5 | ## Example 6 | 7 | ```yaml 8 | # 9 | 10 | 11 | # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json 12 | 13 | name: 'packageName' 14 | shortDescription: '' 15 | description: '' 16 | github: 'unjs/packageName' 17 | # url: 'https://packageName.unjs.io' 18 | # redirects: 19 | # "/from": "/to" 20 | # automd: true 21 | landing: 22 | heroLinks: 23 | stackblitz: 24 | icon: 'i-heroicons-play' 25 | to: 'https://stackblitz.com/github/unjs/packageName/tree/main/playground' 26 | # contributors: true 27 | # features: 28 | # - title: 29 | # description: 30 | 31 | 32 | ``` 33 | 34 | ## Reference 35 | 36 | 37 | 38 | ### `$schema` 39 | 40 | - **Type**: `string` 41 | 42 | ### `automd` 43 | 44 | - **Type**: `boolean` 45 | 46 | Enable integration with https://automd.unjs.io 47 | 48 | ### `branch` 49 | 50 | - **Type**: `string` 51 | 52 | The branch of the GitHub repository for the documentation site. 53 | 54 | ### `buildCache` 55 | 56 | - **Type**: `boolean` 57 | 58 | Enable build cache (experimental) 59 | 60 | ### `description` 61 | 62 | - **Type**: `string` 63 | 64 | The description of the documentation site. 65 | 66 | ### `dir` 67 | 68 | - **Type**: `string` 69 | 70 | Documentation directory 71 | 72 | Note: This option will be automatically set 73 | 74 | ### `github` 75 | 76 | - **Type**: `string` 77 | 78 | The GitHub repository for the documentation site. 79 | 80 | ### `landing` 81 | 82 | - **Type**: `undefined` 83 | 84 | ### `name` 85 | 86 | - **Type**: `string` 87 | 88 | The name of the documentation site. 89 | 90 | ### `redirects` 91 | 92 | Redirects for the documentation site. 93 | 94 | ### `shortDescription` 95 | 96 | - **Type**: `string` 97 | 98 | The description of the documentation site. 99 | 100 | ### `socials` 101 | 102 | Social media links for the documentation site. 103 | 104 | ### `sponsors` 105 | 106 | #### `api` 107 | 108 | - **Type**: `string` 109 | 110 | The URL to the sponsors JSON API. 111 | 112 | ### `themeColor` 113 | 114 | - **Type**: `string` 115 | 116 | The theme color of the documentation site. 117 | 118 | It will be used as the `theme-color` meta tag and a full palette of colors will be generated from it. 119 | 120 | ### `url` 121 | 122 | - **Type**: `string` 123 | 124 | The URL of the documentation site. 125 | 126 | 127 | -------------------------------------------------------------------------------- /app/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | docs: { 3 | socialBackground: 'https://github.com/unjs/undocs/blob/main/assets/ellipse.png?raw=true', 4 | logo: '/icon.svg', 5 | github: undefined, 6 | socials: {}, 7 | banner: {}, 8 | versions: [], 9 | }, 10 | ui: { 11 | colors: { 12 | // primary: 'amber', // set in setup.mjs 13 | important: 'violet', 14 | neutral: 'neutral', 15 | }, 16 | prose: { 17 | codeIcon: { 18 | '.config': 'vscode-icons:file-type-config', 19 | // '.plugin': 'vscode-icons:file-type-plugin', 20 | 'package.json': 'vscode-icons:file-type-node', 21 | 'tsconfig.json': 'vscode-icons:file-type-tsconfig', 22 | '.npmrc': 'vscode-icons:file-type-npm', 23 | '.editorconfig': 'vscode-icons:file-type-editorconfig', 24 | '.eslintrc': 'vscode-icons:file-type-eslint', 25 | '.eslintrc.cjs': 'vscode-icons:file-type-eslint', 26 | '.eslintignore': 'vscode-icons:file-type-eslint', 27 | '.gitignore': 'vscode-icons:file-type-git', 28 | 'yarn.lock': 'vscode-icons:file-type-yarn', 29 | '.env': 'vscode-icons:file-type-dotenv', 30 | '.env.example': 'vscode-icons:file-type-dotenv', 31 | '.vscode/settings.json': 'vscode-icons:file-type-vscode', 32 | '.nuxtrc': 'vscode-icons:file-type-nuxt', 33 | '.nuxtignore': 'vscode-icons:file-type-nuxt', 34 | 'nuxt.config.ts': 'vscode-icons:file-type-nuxt', 35 | 'nuxt.schema.ts': 'vscode-icons:file-type-nuxt', 36 | 'nitro.config.ts': 'i-undocs-nitro', 37 | 'vite.config.js': 'i-logos-vitejs', 38 | 'vite.config.mjs': 'i-logos-vitejs', 39 | 'vite.config.ts': 'i-logos-vitejs', 40 | 'tailwind.config.js': 'vscode-icons:file-type-tailwind', 41 | 'tailwind.config.ts': 'vscode-icons:file-type-tailwind', 42 | ts: 'vscode-icons:file-type-typescript', 43 | tsx: 'vscode-icons:file-type-typescript', 44 | mjs: 'vscode-icons:file-type-js', 45 | cjs: 'vscode-icons:file-type-js', 46 | js: 'vscode-icons:file-type-js', 47 | jsx: 'vscode-icons:file-type-js', 48 | md: 'vscode-icons:file-type-markdown', 49 | py: 'vscode-icons:file-type-python', 50 | ico: 'vscode-icons:file-type-favicon', 51 | npm: 'vscode-icons:file-type-npm', 52 | pnpm: 'vscode-icons:file-type-pnpm', 53 | npx: 'vscode-icons:file-type-npm', 54 | yarn: 'vscode-icons:file-type-yarn', 55 | bun: 'vscode-icons:file-type-bun', 56 | node: 'vscode-icons:file-type-node', 57 | deno: 'vscode-icons:file-type-deno', 58 | yml: 'vscode-icons:file-type-yaml', 59 | terminal: 'i-heroicons-command-line', 60 | }, 61 | }, 62 | }, 63 | }) 64 | -------------------------------------------------------------------------------- /cli/cli.mjs: -------------------------------------------------------------------------------- 1 | import { defineCommand, runMain } from 'citty' 2 | import consola from 'consola' 3 | // import { relative } from "pathe" 4 | import { getContext } from 'unctx' 5 | import { setupDocs } from './setup.mjs' 6 | 7 | const HMRKeys = new Set(['description', 'shortDescription', 'landing']) 8 | 9 | export function createCLI(opts) { 10 | const sharedArgs = { 11 | dir: { 12 | type: 'positional', 13 | description: 'Docs directory', 14 | required: true, 15 | default: '.', 16 | }, 17 | } 18 | 19 | const dev = defineCommand({ 20 | meta: { 21 | name: 'dev', 22 | description: 'Start docs in development mode', 23 | }, 24 | args: { ...sharedArgs }, 25 | async setup({ args }) { 26 | // const cwd = process.cwd() 27 | const logger = consola.withTag('undocs') 28 | const { tryUse: tryUseNuxt } = getContext('nuxt') 29 | 30 | const { appDir, nuxtConfig, unwatch } = await setupDocs(args.dir, { 31 | ...opts.setup, 32 | dev: true, 33 | watch: { 34 | // onWatch: (event) => { 35 | // logger.info(`Config file ${event.type} \`${relative(cwd, event.path)}\``) 36 | // }, 37 | acceptHMR({ getDiff }) { 38 | const diff = getDiff().filter((entry) => entry.key !== 'dir') 39 | if (diff.length === 0) { 40 | return true 41 | } 42 | }, 43 | onUpdate({ getDiff, newConfig: { config } }) { 44 | const diff = getDiff().filter((entry) => entry.key !== 'dir') 45 | logger.info('Config updated:\n' + diff.map((i) => ' - ' + i.toJSON()).join('\n')) 46 | Object.assign(nuxtConfig.docs, config) 47 | if (diff.some((entry) => !HMRKeys.has(entry.key.split('.')[0]))) { 48 | logger.info('Full reloading...') 49 | tryUseNuxt()?.callHook('restart') 50 | } else { 51 | logger.info('Fast reloading...') 52 | tryUseNuxt()?.callHook('undocs:config', config) 53 | } 54 | }, 55 | }, 56 | }) 57 | 58 | process.chdir(appDir) 59 | process.on('exit', () => unwatch()) 60 | 61 | const { runCommand } = await import('nuxi') 62 | await runCommand('dev', [appDir, '--no-fork', '--port', process.env.PORT || '4000'], { overrides: nuxtConfig }) 63 | }, 64 | }) 65 | 66 | const build = defineCommand({ 67 | meta: { 68 | name: 'build', 69 | description: 'Build static docs for production', 70 | }, 71 | args: { ...sharedArgs }, 72 | async setup({ args }) { 73 | const { appDir, nuxtConfig } = await setupDocs(args.dir, opts.setup) 74 | 75 | process.chdir(appDir) 76 | 77 | const { runCommand } = await import('nuxi') 78 | await runCommand('generate', [appDir], { overrides: nuxtConfig }) 79 | }, 80 | }) 81 | 82 | const main = defineCommand({ 83 | meta: { 84 | name: opts.name, 85 | description: opts.description, 86 | }, 87 | subCommands: { 88 | dev, 89 | build, 90 | }, 91 | }) 92 | 93 | return { 94 | runMain: () => runMain(main), 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/components/color-picker/ColorPicker.vue: -------------------------------------------------------------------------------- 1 | 2 | 47 | 48 | 101 | -------------------------------------------------------------------------------- /app/pages/[...slug].vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 96 | -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/template.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 34 | 36 | 42 | 43 | 56 | 60 | 64 | 65 | 66 | 72 | {icon} {name} 84 | 87 | {title} 91 | 92 | {description1}{description2}{description3}{description4} 106 | 107 | -------------------------------------------------------------------------------- /app/assets/icons/nitro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/modules/og-image/runtime/handler.ts: -------------------------------------------------------------------------------- 1 | import { defineLazyEventHandler, setHeader, getQuery } from 'h3' 2 | 3 | const themeColorMap = { 4 | red: '#ff6467', 5 | orange: '#ff8904', 6 | amber: '#ffb900', 7 | yellow: '#fdc700', 8 | lime: '#9ae600', 9 | green: '#05df72', 10 | emerald: '#00d492', 11 | teal: '#00d5be', 12 | cyan: '#00d3f2', 13 | sky: '#00bcff', 14 | blue: '#50a2ff', 15 | indigo: '#7c86ff', 16 | violet: '#a684ff', 17 | purple: '#c27aff', 18 | fuchsia: '#ed6aff', 19 | pink: '#fb64b6', 20 | rose: '#ff637e', 21 | } 22 | 23 | export default defineLazyEventHandler(async () => { 24 | // const { Resvg } = await import('@resvg/resvg-js') 25 | const { default: ResvgWasm } = await import('@resvg/resvg-wasm/index_bg.wasm?module' as any) 26 | const { Resvg, initWasm } = await import('@resvg/resvg-wasm') 27 | await initWasm(ResvgWasm) 28 | 29 | // Read server assets 30 | const storage = useStorage() 31 | // https://github.com/unjs/unstorage/issues/477 32 | // const fontNames = await storage.getKeys('assets:og-image:fonts:') 33 | const fontNames = ['Black', 'Bold', 'ExtraLight', 'Light', 'Medium', 'Regular', 'Thin'].flatMap((v) => [ 34 | `assets:og-image:fonts:PublicSans-${v}.woff2`, 35 | ]) 36 | const fontBuffers = await Promise.all(fontNames.map((name) => storage.getItemRaw(name))) 37 | 38 | // Load icon 39 | const iconSvg: string = 40 | (await storage.getItem('assets:public:icon.svg')) || (await storage.getItem('assets:og-image:unjs.svg')) 41 | 42 | let svgTemplate = (await storage.getItem('assets:og-image:template.svg')) as string 43 | 44 | return defineEventHandler(async (event) => { 45 | if (import.meta.dev) { 46 | svgTemplate = (await useStorage().getItem('assets:og-image:template.svg')) as string 47 | } 48 | 49 | const { name = '', title = '', description = '' } = getQuery(event) as Record 50 | 51 | const docsConfig = useAppConfig().docs 52 | const themeColor = docsConfig.themeColor || 'yellow' 53 | const themeColorValue = themeColorMap[themeColor] || themeColor 54 | 55 | const descriptionLines = _wrapLine(decodeURIComponent(description), 55) 56 | const titleDecoded = decodeURIComponent(title) 57 | const nameDecoded = decodeURIComponent(name) 58 | const svg = svgTemplate 59 | .replace('{name}', nameDecoded) 60 | .replace('{title}', titleDecoded) 61 | .replace('{titleSize}', String(titleDecoded.length > 30 ? 4 : 5)) 62 | .replace('{description1}', descriptionLines[0] || '') 63 | .replace('{description2}', descriptionLines[1] || '') 64 | .replace('{description3}', descriptionLines[2] || '') 65 | .replace('{description4}', descriptionLines[3] || '') 66 | .replace(/yellow/g, themeColorValue) 67 | .replace('{icon}', updateSvg(iconSvg, { x: 1000, y: 450, width: 120, height: 120 })) 68 | 69 | // https://github.com/yisibl/resvg-js 70 | const resvg = new Resvg(svg, { font: { fontBuffers } }) 71 | const pngData = resvg.render() 72 | const pngBuffer = pngData.asPng() 73 | 74 | setHeader(event, 'Content-Type', 'image/png') 75 | return pngBuffer 76 | }) 77 | }) 78 | 79 | function updateSvg(svg: string, { x, y, width, height }: { x: number; y: number; width: number; height: number }) { 80 | const match = svg.match(/]*>/) 81 | if (!match) return svg 82 | svg = svg.replace(/width="[^"]*"/, `width="${width}"`) 83 | svg = svg.replace(/height="[^"]*"/, `height="${height}"`) 84 | svg = svg.replace('= width) { 93 | lines.push(line) 94 | line = '' 95 | } 96 | line += word + ' ' 97 | } 98 | lines.push(line) 99 | return lines 100 | } 101 | -------------------------------------------------------------------------------- /docs/1.guide/components/components.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | > Discover the components you can use in your markdown files. 4 | 5 | 6 | 7 | > [!IMPORTANT] 8 | > Undocs is currently intended for UnJS docs only and is not fully customizable yet.
9 | > Contributions are more than welcome but please consider that this project is not ready yet to be used.
10 | > We don't guarantee stability yet and it is expected that it doesn't work time to time. 11 | 12 | 13 | 14 | ## Alerts 15 | 16 | 17 | ::tabs 18 | ::div 19 | --- 20 | label: Preview 21 | icon: i-heroicons-magnifying-glass-circle 22 | --- 23 | ::note 24 | Highlights information that users should take into account, even when skimming. 25 | :: 26 | ::tip 27 | Optional information to help a user be more successful. 28 | :: 29 | ::important 30 | Crucial information necessary for users to succeed. 31 | :: 32 | ::warning{to="/"} 33 | Critical content demanding immediate user attention due to potential risks. 34 | :: 35 | ::caution{to="/"} 36 | Negative potential consequences of an action. 37 | :: 38 | :: 39 | ::div 40 | --- 41 | label: Code 42 | icon: i-heroicons-code-bracket-square 43 | --- 44 | ```mdc 45 | ::note 46 | Highlights information that users should take into account, even when skimming. 47 | :: 48 | ::tip 49 | Optional information to help a user be more successful. 50 | :: 51 | ::important 52 | Crucial information necessary for users to succeed. 53 | :: 54 | ::warning{to="/"} 55 | Critical content demanding immediate user attention due to potential risks. 56 | :: 57 | ::caution{to="/"} 58 | Negative potential consequences of an action. 59 | :: 60 | ``` 61 | :: 62 | :: 63 | 64 | 65 | ## Package Manager 66 | 67 | Components to generate cross package manager comments 68 | 69 | 70 | ::tabs 71 | ::div 72 | --- 73 | label: Preview 74 | icon: i-heroicons-magnifying-glass-circle 75 | --- 76 | :pm-install{name="defu"} 77 | 78 | :pm-run{script="dev"} 79 | 80 | :pm-x{command="giget unjs new-lib"} 81 | 82 | :: 83 | ::div 84 | --- 85 | label: Code 86 | icon: i-heroicons-code-bracket-square 87 | --- 88 | ```mdc 89 | :pm-install{name="defu"} 90 | 91 | :pm-run{script="dev"} 92 | 93 | :pm-x{command="giget unjs new-lib"} 94 | ``` 95 | :: 96 | :: 97 | 98 | 99 | ## Read More 100 | 101 | The component is used to create a link to another page. 102 | 103 | 104 | ::tabs 105 | ::div 106 | --- 107 | label: Preview 108 | icon: i-heroicons-magnifying-glass-circle 109 | --- 110 | :read-more{to="/guide"} 111 | :read-more{to="https://unjs.io" title="UnJS Website"} 112 | :: 113 | ::div 114 | --- 115 | label: Code 116 | icon: i-heroicons-code-bracket-square 117 | --- 118 | ```mdc 119 | :read-more{to="/guide"} 120 | :read-more{to="https://unjs.io" title="UnJS Website"} 121 | ``` 122 | :: 123 | :: 124 | 125 | 126 | ## Mermaid Graphs 127 | 128 | ```` 129 | ```mermaid 130 | graph TD 131 | A[Getting Started] --> B[Components] 132 | B --> C[Content Transform] 133 | 134 | click A "/guide" 135 | click B "/guide/components/components" 136 | click C "/guide/components/content-transformation" 137 | ``` 138 | ```` 139 | 140 | ```mermaid 141 | %%{init: {'theme':'neutral'}}%% 142 | graph TD 143 | A[Getting Started] --> B[Components] 144 | B --> C[Content Transform] 145 | 146 | click A "/guide" 147 | click B "/guide/components/components" 148 | click C "/guide/components/content-transformation" 149 | ``` 150 | 151 | ## Nuxt UI 152 | 153 | You can use all the Prose components from Nuxt UI in your markdown files. 154 | 155 | Read more on https://ui4.nuxt.com/docs/typography 156 | -------------------------------------------------------------------------------- /app/components/OrgLogo.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /app/public/unjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/modules/og-image/runtime/assets/unjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /template/.docs/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/1.guide/components/content-transformation.md: -------------------------------------------------------------------------------- 1 | # Content Transform 2 | 3 | > Discover ways that undocs transforms content to make it more easier to just write documentation. 4 | 5 | 6 | 7 | > [!IMPORTANT] 8 | > Undocs is currently intended for UnJS docs only and is not fully customizable yet.
9 | > Contributions are more than welcome but please consider that this project is not ready yet to be used.
10 | > We don't guarantee stability yet and it is expected that it doesn't work time to time. 11 | 12 | 13 | 14 | ## Github Notes 15 | 16 | https://github.com/orgs/community/discussions/16925 17 | 18 | ```md 19 | > [!NOTE] 20 | > Highlights information that users should take into account, even when skimming. 21 | ``` 22 | 23 | > [!NOTE] 24 | > Highlights information that users should take into account, even when skimming. 25 | 26 | ```md 27 | > [!TIP] 28 | > Optional information to help a user be more successful. 29 | ``` 30 | 31 | > [!TIP] 32 | > Optional information to help a user be more successful. 33 | 34 | ```md 35 | > [!IMPORTANT] 36 | > Crucial information necessary for users to succeed. 37 | ``` 38 | 39 | > [!IMPORTANT] 40 | > Crucial information necessary for users to succeed. 41 | 42 | ```md 43 | > [!WARNING] 44 | > Critical content demanding immediate user attention due to potential risks. 45 | ``` 46 | 47 | > [!WARNING] 48 | > Critical content demanding immediate user attention due to potential risks. 49 | 50 | ```md 51 | > [!CAUTION] 52 | > Negative potential consequences of an action. 53 | ``` 54 | 55 | > [!CAUTION] 56 | > Negative potential consequences of an action. 57 | 58 | ## Auto Code Groups 59 | 60 | If you have code blocks right after each other, they will be grouped together using [`code-group`](https://ui.nuxt.com/pro/prose/code-group). 61 | 62 | ````md 63 | ```json [package.json] 64 | { 65 | "scripts": { 66 | "dev": "undocs dev" 67 | } 68 | } 69 | ``` 70 | 71 | ```ts [server/api/hello.get.ts] 72 | export default defineEventHandler(() => { 73 | return { 74 | hello: 'world', 75 | } 76 | }) 77 | ``` 78 | 79 | ```html [index.html] 80 | 85 | ``` 86 | ```` 87 | 88 | ```json [package.json] 89 | { 90 | "scripts": { 91 | "dev": "undocs dev" 92 | } 93 | } 94 | ``` 95 | 96 | ```ts [server/api/hello.get.ts] 97 | export default defineEventHandler(() => { 98 | return { 99 | hello: 'world', 100 | } 101 | }) 102 | ``` 103 | 104 | ```html [index.html] 105 | 110 | ``` 111 | 112 | ## Steps 113 | 114 | Generate steps by using standard markdown numbered lists! 115 | 116 | > [!IMPORTANT] 117 | > In order to generate this component, you need to have content inside at least one of the lists. The list can't be a child of another component & also any content within a Markdown list will need at least 2 tabs to be considered as a child of the list. 118 | 119 | ```md 120 | 1. Install Package 121 | 122 | ::note 123 | Please note that steps only work with numbered lists and is not within children. 124 | :: 125 | 126 | :pm-install{name="undocs"} 127 | 128 | 2. Run development server 129 | 130 | :pm-run{script="undocs"} 131 | 132 | 3. Done ✅ 133 | ``` 134 | 135 | 1. Install Package 136 | 137 | ::note 138 | Please note that steps only work with numbered lists and is not within children. 139 | :: 140 | 141 | :pm-install{name="undocs"} 142 | 143 | 2. Run development server 144 | 145 | :pm-run{script="undocs"} 146 | 147 | 3. Done ✅ 148 | 149 | ## Config References 150 | 151 | Generate beautiful references for your configuration files by just using markdown! 152 | 153 | > [!TIP] 154 | > If you use [automd:jsdocs](https://automd.unjs.io/generators/jsdocs), you can reference the schema file directly! Check out this example from the [config](/config) page. 155 | 156 | ```md 157 | ### `$schema` 158 | 159 | - **Type**: `string` 160 | 161 | ### `automd` 162 | 163 | - **Type**: `boolean` 164 | 165 | Enable integration with https://automd.unjs.io 166 | 167 | ### `buildCache` 168 | 169 | - **Type**: `boolean` 170 | 171 | Enable build cache (experimental) 172 | 173 | ### `description` 174 | 175 | - **Type**: `string` 176 | 177 | The description of the documentation site. 178 | ``` 179 | 180 | ### `$schema` 181 | 182 | - **Type**: `string` 183 | 184 | ### `automd` 185 | 186 | - **Type**: `boolean` 187 | 188 | Enable integration with https://automd.unjs.io 189 | 190 | ### `buildCache` 191 | 192 | - **Type**: `boolean` 193 | 194 | Enable build cache (experimental) 195 | 196 | ### `description` 197 | 198 | - **Type**: `string` 199 | 200 | The description of the documentation site. 201 | 202 | > [!TIP] 203 | > Do you have an idea for a new content transformation, feel free to [open an issue](https://github.com/unjs/undocs/issues/new?assignees=&labels=pending+triage&projects=&template=feature-request.yml)! 204 | -------------------------------------------------------------------------------- /cli/setup.mjs: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { resolve } from 'node:path' 3 | import { existsSync } from 'node:fs' 4 | import { execSync } from 'node:child_process' 5 | import { loadConfig, watchConfig } from 'c12' 6 | 7 | const appDir = fileURLToPath(new URL('../app', import.meta.url)) 8 | 9 | const pkgDir = fileURLToPath(new URL('..', import.meta.url)) 10 | 11 | export async function setupDocs(docsDir, opts = {}) { 12 | // Load config 13 | const { unwatch, config: docsconfig } = await (opts.watch ? watchConfig : loadConfig)({ 14 | name: 'docs', 15 | cwd: docsDir, 16 | defaults: { 17 | url: inferSiteURL(), 18 | ...opts.defaults, 19 | }, 20 | ...opts.watch, 21 | }) 22 | 23 | // Normalize dir 24 | docsconfig.dir = docsDir = resolve(docsconfig.dir || docsDir) 25 | 26 | globalThis.__DOCS_CWD__ = docsconfig.dir 27 | 28 | // URL is required for production build (SEO) 29 | if (!docsconfig.url && !opts.dev) { 30 | throw new Error('`url` config is required for production build!') 31 | } 32 | 33 | // Guess branch 34 | docsconfig.branch = docsconfig.branch || getGitBranch() || 'main' 35 | 36 | // Convert markdown to HTML for landing items 37 | if (docsconfig.landing?.features) { 38 | const md4w = await import('md4w') 39 | await md4w.init() 40 | for (const item of docsconfig.landing.features) { 41 | if (item.description) { 42 | item.description = md4w.mdToHtml(item.description) 43 | } 44 | } 45 | } 46 | 47 | // Normalize and format hero code 48 | if (docsconfig.landing?.heroCode) { 49 | if (typeof docsconfig.landing.heroCode === 'string') { 50 | docsconfig.landing.heroCode = { 51 | content: docsconfig.landing.heroCode, 52 | } 53 | } 54 | const shiki = await import('shiki') 55 | docsconfig.landing.heroCode.contentHighlighted = ( 56 | await shiki.codeToHtml(docsconfig.landing.heroCode.content, { 57 | lang: docsconfig.landing.heroCode.lang || 'sh', 58 | defaultColor: 'dark', 59 | themes: { 60 | default: 'github-dark', 61 | dark: 'github-dark', 62 | light: 'github-light', 63 | }, 64 | }) 65 | ) 66 | .replace(/background-color:#[0-9a-fA-F]{6};/g, '') 67 | .replaceAll(``, '') 68 | } 69 | 70 | // Module to fix layers (force add .docs as first) 71 | const docsSrcDir = resolve(docsDir, '.docs') 72 | const fixLayers = (_, nuxt) => { 73 | nuxt.options._layers.unshift({ 74 | cwd: docsSrcDir, 75 | config: { 76 | rootDir: docsSrcDir, 77 | srcDir: docsSrcDir, 78 | }, 79 | }) 80 | } 81 | 82 | // Prepare loadNuxt overrides 83 | const nuxtConfig = { 84 | compatibilityDate: 'latest', 85 | rootDir: docsSrcDir, 86 | srcDir: docsSrcDir, 87 | extends: [...(opts.extends || []), appDir], 88 | modulesDir: [resolve(pkgDir, 'node_modules'), resolve(docsDir, 'node_modules')], 89 | modules: ['@nuxt/ui', fixLayers, docsconfig.buildCache ? 'nuxt-build-cache' : undefined].filter(Boolean), 90 | // @ts-ignore 91 | docs: docsconfig, 92 | // @ts-ignore 93 | site: { 94 | name: docsconfig.name || '', 95 | description: docsconfig.description || '', 96 | url: docsconfig.url, 97 | }, 98 | appConfig: { 99 | site: { 100 | name: docsconfig.name || '', 101 | description: docsconfig.description || '', 102 | url: docsconfig.url, 103 | }, 104 | docs: { 105 | ...docsconfig, 106 | dir: undefined, 107 | }, 108 | ui: { 109 | colors: { 110 | primary: docsconfig.themeColor || 'amber', 111 | }, 112 | }, 113 | }, 114 | nitro: { 115 | static: true, 116 | publicAssets: [{ baseURL: '/', dir: resolve(docsDir, '.docs/public'), maxAge: 0 }], 117 | serverAssets: [ 118 | { 119 | baseName: 'public', 120 | dir: resolve(docsDir, '.docs/public'), 121 | }, 122 | ], 123 | }, 124 | routeRules: { 125 | ...Object.fromEntries(Object.entries(docsconfig.redirects || {}).map(([from, to]) => [from, { redirect: to }])), 126 | }, 127 | icon: { 128 | customCollections: [ 129 | { 130 | prefix: 'undocs', 131 | dir: resolve(appDir, 'assets/icons'), 132 | }, 133 | ...(existsSync(resolve(docsDir, '.docs/icons')) 134 | ? [ 135 | { 136 | prefix: 'custom', 137 | dir: resolve(docsDir, '.docs/icons'), 138 | }, 139 | ] 140 | : []), 141 | ], 142 | }, 143 | } 144 | 145 | return { 146 | docsDir, 147 | appDir, 148 | nuxtConfig, 149 | unwatch, 150 | } 151 | } 152 | 153 | function inferSiteURL() { 154 | // https://github.com/unjs/std-env/issues/59 155 | return ( 156 | process.env.NUXT_PUBLIC_SITE_URL || 157 | (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`) || // Vercel 158 | process.env.URL || // Netlify 159 | process.env.CI_PAGES_URL || // Gitlab Pages 160 | process.env.CF_PAGES_URL // Cloudflare Pages 161 | ) 162 | } 163 | 164 | function getGitBranch() { 165 | const envName = 166 | process.env.CF_PAGES_BRANCH || 167 | process.env.CI_COMMIT_BRANCH || 168 | process.env.VERCEL_BRANCH_URL || 169 | process.env.BRANCH || 170 | process.env.GITHUB_REF_NAME 171 | if (envName && envName !== 'HEAD') { 172 | return envName 173 | } 174 | try { 175 | const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim() 176 | if (branch && branch !== 'HEAD') { 177 | return branch 178 | } 179 | } catch { 180 | // Ignore 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 181 | -------------------------------------------------------------------------------- /app/assets/icons/elysia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/modules/content/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { MarkdownRoot, MinimarkNode, MinimarkElement } from '@nuxt/content' 2 | import type { Nuxt } from 'nuxt/schema' 3 | import { CommonIcons } from './icons' 4 | import type { DocsConfig } from '../../../schema/config' 5 | import { pathToFileURL } from 'node:url' 6 | import { writeFile } from 'node:fs/promises' 7 | 8 | interface ParsedContentFile extends Record { 9 | path?: string 10 | navigation?: { 11 | icon?: string 12 | } 13 | meta?: { 14 | icon?: string 15 | } 16 | body?: { 17 | icon?: string 18 | } 19 | } 20 | 21 | export async function setupContentHooks(nuxt: Nuxt, docsConfig: DocsConfig) { 22 | let automdTransform: (content: string, path: string) => string | Promise | undefined 23 | if (docsConfig.automd) { 24 | const automd = await import('automd') 25 | const config = await automd.loadConfig(docsConfig.dir, docsConfig.automd) 26 | automdTransform = async (content: string, path: string) => { 27 | const res = await automd.transform(content, config, pathToFileURL(path).href) 28 | if (!res.hasIssues) { 29 | if (res.hasChanged) { 30 | console.info(`[undocs] [automd] Updated \`${path}\` with automd transform, saving changes...`) 31 | await writeFile(path, res.contents, 'utf-8') 32 | } 33 | return res.contents 34 | } 35 | console.warn( 36 | `[undocs] [automd] Issues for updating \`${path}\`:`, 37 | res.updates 38 | .flatMap((u) => u.result.issues) 39 | .map((i) => `\n - ${i}`) 40 | .join('\n'), 41 | ) 42 | return content 43 | } 44 | } 45 | 46 | nuxt.hooks.hook('content:file:beforeParse', async ({ file }) => { 47 | if (typeof file.body !== 'string') { 48 | return // can be json meta 49 | } 50 | if (file.body.includes('