Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions examples/kitchen-sink/docs/references/api/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: Authentication
---

# Authentication

All API requests require a Bearer token in the `Authorization` header.

## Obtaining a Token

Send a `POST` request to `/auth/token` with your client credentials:

```bash
curl -X POST https://api.acme.com/auth/token \
-H "Content-Type: application/json" \
-d '{"client_id": "...", "client_secret": "..."}'
```

## Token Refresh

Tokens expire after 1 hour. Use the refresh token to obtain a new access token without re-authenticating.
23 changes: 23 additions & 0 deletions examples/kitchen-sink/docs/references/api/endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: Endpoints
---

# Endpoints

## Users

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/users` | List all users |
| `GET` | `/users/:id` | Get user by ID |
| `POST` | `/users` | Create a user |
| `PATCH` | `/users/:id` | Update a user |
| `DELETE` | `/users/:id` | Delete a user |

## Projects

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/projects` | List all projects |
| `GET` | `/projects/:id` | Get project by ID |
| `POST` | `/projects` | Create a project |
16 changes: 16 additions & 0 deletions examples/kitchen-sink/docs/references/api/errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: Error Codes
---

# Error Codes

All errors return a JSON body with `code`, `message`, and optional `details`.

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `auth_required` | 401 | Missing or invalid authentication |
| `forbidden` | 403 | Insufficient permissions |
| `not_found` | 404 | Resource does not exist |
| `validation_error` | 422 | Request body failed validation |
| `rate_limited` | 429 | Too many requests |
| `internal_error` | 500 | Unexpected server error |
29 changes: 29 additions & 0 deletions examples/kitchen-sink/docs/references/cli/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Commands
---

# Commands

## `acme init`

Initialize a new Acme project in the current directory.

```bash
acme init [--template <name>]
```

## `acme deploy`

Deploy the current project to production.

```bash
acme deploy [--env <environment>] [--dry-run]
```

## `acme status`

Show the current deployment status.

```bash
acme status [--json]
```
27 changes: 27 additions & 0 deletions examples/kitchen-sink/docs/references/cli/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Configuration
---

# Configuration

The CLI reads configuration from `acme.config.ts` in the project root.

```ts
export default {
org: 'acme',
project: 'web',
region: 'us-east-1',
deploy: {
strategy: 'rolling',
timeout: 300,
},
}
```

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `ACME_TOKEN` | API authentication token | — |
| `ACME_ORG` | Organization slug | from config |
| `ACME_LOG_LEVEL` | Log verbosity (`debug`, `info`, `warn`, `error`) | `info` |
22 changes: 22 additions & 0 deletions examples/kitchen-sink/docs/references/cli/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Installation
---

# Installation

Install the Acme CLI globally:

```bash
npm install -g @acme/cli
```

Verify the installation:

```bash
acme --version
```

## System Requirements

- Node.js 18 or later
- macOS, Linux, or Windows (WSL)
20 changes: 20 additions & 0 deletions examples/kitchen-sink/zpress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ export default defineConfig({
},
],
},
{
title: 'Reference',
icon: 'pixelarticons:book-open',
path: '/references',
root: true,
items: [
{
title: 'API',
path: '/references/api',
include: 'docs/references/api/*.md',
sort: 'alpha',
},
{
title: 'CLI',
path: '/references/cli',
include: 'docs/references/cli/*.md',
sort: 'alpha',
},
],
},
],
sidebar: {
above: [
Expand Down
3 changes: 3 additions & 0 deletions packages/config/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@
},
"standalone": {
"type": "boolean"
},
"root": {
"type": "boolean"
}
},
"required": [
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const entrySchema: z.ZodType<Section> = z.lazy(() =>
icon: iconConfigSchema.optional(),
card: cardConfigSchema.optional(),
standalone: z.boolean().optional(),
root: z.boolean().optional(),
})
.strict()
)
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export interface Section {
readonly icon?: IconConfig
readonly card?: CardConfig
readonly standalone?: boolean
readonly root?: boolean
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ function concatPage(pages: readonly PageData[], page: PageData | undefined): Pag
* @returns Array of standalone scope path strings
*/
function collectStandaloneScopePaths(entries: readonly ResolvedEntry[]): readonly string[] {
return entries.filter((e) => e.standalone && e.link).map((e) => e.link as string)
return entries.filter((e) => (e.standalone || e.root) && e.link).map((e) => e.link as string)
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/sync/resolve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ async function resolveNestedSection(
card: section.card,
landing: section.landing,
standalone: section.standalone,
root: section.root,
autoLink,
items: sorted,
page: sectionPage,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/sync/sidebar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export function generateNav(
// Auto: first 3 non-standalone sections (matching home page features),
// plus all standalone sections (workspace dropdowns).
const visible = resolved.filter((e) => !e.hidden)
const nonStandalone = visible.filter((e) => !e.standalone).slice(0, 3)
const standalone = visible.filter((e) => e.standalone)
const nonStandalone = visible.filter((e) => !e.standalone && !e.root).slice(0, 3)
const standalone = visible.filter((e) => e.standalone || e.root)

return [...nonStandalone, ...standalone]
.map(buildNavEntry)
Expand Down Expand Up @@ -127,7 +127,7 @@ function resolveLink(entry: ResolvedEntry): string | undefined {
* @returns Array of nav items for dropdown, or undefined
*/
function resolveChildren(entry: ResolvedEntry): readonly RspressNavItem[] | undefined {
if (entry.standalone && entry.items && entry.items.length > 0) {
if ((entry.standalone || entry.root) && entry.items && entry.items.length > 0) {
return entry.items
.filter((child) => !child.hidden)
.map(
Expand Down
108 changes: 108 additions & 0 deletions packages/core/src/sync/sidebar/meta.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,36 @@ const packagesRoot: ResolvedEntry = {
],
}

const referenceRoot: ResolvedEntry = {
title: 'Reference',
link: '/references',
root: true,
items: [
{
title: 'API',
link: '/references/api',
items: [
{
title: 'Auth',
link: '/references/api/auth',
page: { outputPath: 'references/api/auth.md', frontmatter: {} },
},
],
},
{
title: 'CLI',
link: '/references/cli',
items: [
{
title: 'Commands',
link: '/references/cli/commands',
page: { outputPath: 'references/cli/commands.md', frontmatter: {} },
},
],
},
],
}

// ---------------------------------------------------------------------------
// buildRootMeta
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -111,6 +141,51 @@ describe(buildRootMeta, () => {
expect(result).toHaveLength(1)
expect(result[0]).toMatchObject({ name: 'visible' })
})

it('should promote root section children to top-level meta items', () => {
const entries: readonly ResolvedEntry[] = [
{ title: 'Getting Started', link: '/getting-started', items: [] },
referenceRoot,
]

const result = buildRootMeta(entries)

expect(result).toEqual([
{ type: 'dir', name: 'getting-started', label: 'Getting Started' },
{ type: 'dir', name: 'api', label: 'API' },
{ type: 'dir', name: 'cli', label: 'CLI' },
])
})

it('should not include root section parent as a meta item', () => {
const entries: readonly ResolvedEntry[] = [referenceRoot]

const result = buildRootMeta(entries)

const names = result
.filter(
(item): item is { readonly type: string; readonly name: string; readonly label: string } =>
'name' in item
)
.map((item) => item.name)
expect(names).not.toContain('references')
})

it('should exclude hidden children from root section promotion', () => {
const rootWithHidden: ResolvedEntry = {
title: 'Reference',
link: '/references',
root: true,
items: [
{ title: 'API', link: '/references/api', items: [] },
{ title: 'Internal', link: '/references/internal', hidden: true, items: [] },
],
}

const result = buildRootMeta([rootWithHidden])

expect(result).toEqual([{ type: 'dir', name: 'api', label: 'API' }])
})
})

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -214,6 +289,39 @@ describe(buildMetaDirectories, () => {
}
})

it('should flatten root section children without emitting parent directory group', () => {
const directories = buildMetaDirectories([referenceRoot])

const dirPaths = directories.map((d) => d.dirPath)
expect(dirPaths).not.toContain('references')
})

it('should emit subdirectories for root section children', () => {
const directories = buildMetaDirectories([referenceRoot])

const apiDir = directories.find((d) => d.dirPath === 'references/api')
expect(apiDir).toBeDefined()
if (apiDir) {
expect(apiDir.items).toContainEqual({ type: 'file', name: 'auth', label: 'Auth' })
}

const cliDir = directories.find((d) => d.dirPath === 'references/cli')
expect(cliDir).toBeDefined()
if (cliDir) {
expect(cliDir.items).toContainEqual({ type: 'file', name: 'commands', label: 'Commands' })
}
})

it('should handle mix of root and non-root sections', () => {
const directories = buildMetaDirectories([packagesRoot, referenceRoot])

const dirPaths = directories.map((d) => d.dirPath)
expect(dirPaths).toContain('packages')
expect(dirPaths).not.toContain('references')
expect(dirPaths).toContain('references/api')
expect(dirPaths).toContain('references/cli')
})

it('should preserve leaf-before-section order when names do not collide', () => {
const mixedSection: ResolvedEntry = {
title: 'Mixed',
Expand Down
Loading
Loading