jamdesk 1.1.7 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +5 -2
  2. package/dist/lib/deps.js +2 -2
  3. package/dist/lib/openapi/types.d.ts +11 -6
  4. package/dist/lib/openapi/types.d.ts.map +1 -1
  5. package/dist/lib/path-security.d.ts +3 -0
  6. package/dist/lib/path-security.d.ts.map +1 -1
  7. package/dist/lib/path-security.js +14 -1
  8. package/dist/lib/path-security.js.map +1 -1
  9. package/package.json +13 -10
  10. package/vendored/app/[[...slug]]/page.tsx +48 -13
  11. package/vendored/app/api/assets/[...path]/route.ts +2 -0
  12. package/vendored/components/layout/LayoutWrapper.tsx +3 -4
  13. package/vendored/components/mdx/ApiEndpoint.tsx +13 -2
  14. package/vendored/components/mdx/MDXComponents.tsx +16 -0
  15. package/vendored/components/mdx/OpenApiEndpoint.tsx +76 -36
  16. package/vendored/components/mdx/Tabs.tsx +1 -1
  17. package/vendored/components/mdx/Video.tsx +82 -0
  18. package/vendored/components/navigation/Header.tsx +3 -3
  19. package/vendored/components/navigation/Sidebar.tsx +3 -3
  20. package/vendored/components/ui/CodePanel.tsx +5 -5
  21. package/vendored/components/ui/CodePanelModal.tsx +3 -3
  22. package/vendored/components/ui/DevOnlyNotice.tsx +78 -0
  23. package/vendored/hooks/useChatPanel.tsx +21 -2
  24. package/vendored/hooks/useMediaQuery.ts +27 -0
  25. package/vendored/lib/build-endpoint-from-mdx.ts +66 -0
  26. package/vendored/lib/isr-build-executor.ts +1 -1
  27. package/vendored/lib/middleware-helpers.ts +6 -1
  28. package/vendored/lib/openapi/code-examples.ts +479 -99
  29. package/vendored/lib/openapi/index.ts +9 -1
  30. package/vendored/lib/openapi/types.ts +29 -5
  31. package/vendored/lib/preprocess-mdx.ts +103 -36
  32. package/vendored/lib/process-mdx-with-exports.ts +22 -14
  33. package/vendored/lib/remark-extract-param-fields.ts +134 -0
  34. package/vendored/lib/shiki-client.ts +12 -0
  35. package/vendored/lib/static-artifacts.ts +2 -0
  36. package/vendored/lib/url-safety.ts +122 -0
  37. package/vendored/next.config.js +7 -0
  38. package/vendored/schema/docs-schema.json +17 -4
  39. package/vendored/scripts/copy-files.cjs +60 -54
  40. package/vendored/scripts/validate-links.cjs +1 -1
  41. package/vendored/shared/path-security.ts +17 -1
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Jamdesk CLI
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/jamdesk)](https://www.npmjs.com/package/jamdesk)
4
+ [![npm downloads](https://img.shields.io/npm/dm/jamdesk)](https://www.npmjs.com/package/jamdesk)
4
5
  [![Node.js](https://img.shields.io/node/v/jamdesk)](https://nodejs.org)
5
6
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE)
7
+ [![Claude Code](https://img.shields.io/badge/Claude_Code-skills-blueviolet?logo=anthropic)](https://www.jamdesk.com/docs/claude-code)
6
8
 
7
9
  CLI for [Jamdesk](https://www.jamdesk.com) — build, preview, and deploy documentation sites from MDX.
8
10
 
@@ -32,7 +34,7 @@ Your docs are at **http://localhost:3000**. That's it.
32
34
 
33
35
  ## Installation
34
36
 
35
- ### npm (recommended)
37
+ ### [npm](https://www.npmjs.com/package/jamdesk) (recommended)
36
38
 
37
39
  ```bash
38
40
  npm install -g jamdesk
@@ -222,7 +224,7 @@ Found 3 misspellings across 24 pages.
222
224
  Tip: Run "jamdesk spellcheck --fix" to interactively fix or ignore words.
223
225
  ```
224
226
 
225
- Uses an English dictionary with 150+ built-in tech terms (API, GraphQL, Kubernetes, etc.) so common jargon doesn't flag. Add project-specific words to `docs.json`:
227
+ Uses an English dictionary with 150+ built-in tech terms (API, GraphQL, Kubernetes, etc.) so common jargon doesn't flag. Currently English only — multi-language support is planned. Add project-specific words to `docs.json`:
226
228
 
227
229
  ```json
228
230
  {
@@ -429,6 +431,7 @@ See the [docs.json reference](https://www.jamdesk.com/docs/config/docs-json-refe
429
431
  - [Config Reference](https://www.jamdesk.com/docs/config/docs-json-reference)
430
432
  - [OpenAPI](https://www.jamdesk.com/docs/api-reference/openapi-setup)
431
433
  - [Deployment](https://www.jamdesk.com/docs/deploy)
434
+ - [npm Package](https://www.npmjs.com/package/jamdesk)
432
435
  - [Homepage](https://www.jamdesk.com)
433
436
  - [Pricing](https://www.jamdesk.com/pricing)
434
437
 
package/dist/lib/deps.js CHANGED
@@ -54,7 +54,7 @@ const REQUIRED_DEPS = {
54
54
  'remark-math': '^6.0.0',
55
55
  'remark-smartypants': '^3.0.2',
56
56
  // Math/LaTeX rendering
57
- 'katex': '^0.16.34',
57
+ 'katex': '^0.16.44',
58
58
  // Diagrams
59
59
  'mermaid': '^11.12.2',
60
60
  // YAML parsing (for OpenAPI specs)
@@ -81,7 +81,7 @@ const REQUIRED_DEPS = {
81
81
  'json5': '^2.2.3',
82
82
  'glob': '^13.0.6',
83
83
  // TypeScript (needed for Next.js to avoid auto-install breaking symlink)
84
- 'typescript': '^5.3.3',
84
+ 'typescript': '^6.0.2',
85
85
  '@types/node': '^25.5.0',
86
86
  '@types/react': '^19.2.14',
87
87
  '@types/react-dom': '^19.0.0',
@@ -168,12 +168,17 @@ export interface ValidationResult {
168
168
  error?: OpenApiValidationError;
169
169
  }
170
170
  /**
171
- * Generated code examples
172
- */
173
- export interface CodeExamples {
174
- curl: string;
175
- python: string;
176
- javascript: string;
171
+ * Single code example for a specific language
172
+ */
173
+ export interface CodeExample {
174
+ /** Language identifier (e.g., 'curl', 'python', 'go') */
175
+ id: string;
176
+ /** Display label for the tab (e.g., 'cURL', 'Python', 'Go') */
177
+ label: string;
178
+ /** Shiki language ID for syntax highlighting (e.g., 'bash', 'python', 'go') */
179
+ language: string;
180
+ /** Generated code string */
181
+ code: string;
177
182
  }
178
183
  /**
179
184
  * Generated page from auto-generation
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/openapi/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/openapi/types.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,9 @@
9
9
  /**
10
10
  * Check if a path stays within the project directory.
11
11
  * Returns true if path is within project, false otherwise.
12
+ *
13
+ * Rejects null bytes, URL-encoded sequences, and normalizes
14
+ * backslashes to prevent traversal via encoding tricks.
12
15
  */
13
16
  export declare function isPathWithinProject(filePath: string, projectDir: string): boolean;
14
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAKjF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAIpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGtF"}
1
+ {"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAkBjF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAIpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGtF"}
@@ -10,9 +10,22 @@ import path from 'path';
10
10
  /**
11
11
  * Check if a path stays within the project directory.
12
12
  * Returns true if path is within project, false otherwise.
13
+ *
14
+ * Rejects null bytes, URL-encoded sequences, and normalizes
15
+ * backslashes to prevent traversal via encoding tricks.
13
16
  */
14
17
  export function isPathWithinProject(filePath, projectDir) {
15
- const absolutePath = path.resolve(projectDir, filePath);
18
+ // Reject null bytes (real or URL-encoded)
19
+ if (filePath.includes('\0') || filePath.includes('%00')) {
20
+ return false;
21
+ }
22
+ // Reject URL-encoded path separators and traversal sequences
23
+ if (/%2f/i.test(filePath) || /%5c/i.test(filePath)) {
24
+ return false;
25
+ }
26
+ // Normalize backslashes to forward slashes to prevent Windows-style traversal
27
+ const normalized = filePath.replace(/\\/g, '/');
28
+ const absolutePath = path.resolve(projectDir, normalized);
16
29
  const normalizedProject = path.resolve(projectDir);
17
30
  return absolutePath.startsWith(normalizedProject + path.sep) || absolutePath === normalizedProject;
18
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"path-security.js","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,UAAkB;IACtE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACxD,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEnD,OAAO,YAAY,CAAC,UAAU,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,iBAAiB,CAAC;AACrG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,UAAkB;IAC5E,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,EAAU,EAAE,UAAkB;IAC9E,yBAAyB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5C,yBAAyB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"path-security.js","sourceRoot":"","sources":["../../src/lib/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,UAAkB;IACtE,0CAA0C;IAC1C,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,6DAA6D;IAC7D,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8EAA8E;IAC9E,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEhD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAC1D,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEnD,OAAO,YAAY,CAAC,UAAU,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,iBAAiB,CAAC;AACrG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,UAAkB;IAC5E,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,EAAU,EAAE,UAAkB;IAC9E,yBAAyB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5C,yBAAyB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC"}
package/package.json CHANGED
@@ -1,36 +1,39 @@
1
1
  {
2
2
  "name": "jamdesk",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
5
5
  "keywords": [
6
6
  "jamdesk",
7
7
  "documentation",
8
8
  "documentation-generator",
9
9
  "documentation-platform",
10
+ "documentation-tool",
10
11
  "docs",
11
12
  "docs-as-code",
13
+ "docs-generator",
12
14
  "mdx",
13
15
  "markdown",
14
16
  "cli",
15
- "dev-server",
16
- "hot-reload",
17
- "turbopack",
18
17
  "developer-tools",
19
18
  "api-documentation",
20
19
  "openapi",
21
20
  "api-reference",
22
- "static-site",
21
+ "static-site-generator",
23
22
  "nextjs",
23
+ "react",
24
+ "typescript",
24
25
  "technical-writing",
25
26
  "mintlify",
26
27
  "mintlify-alternative",
28
+ "docusaurus-alternative",
29
+ "gitbook-alternative",
27
30
  "documentation-hosting",
28
31
  "documentation-site",
29
32
  "developer-documentation",
30
33
  "api-docs",
31
- "react-components",
32
34
  "docs-site-generator",
33
- "ai-search"
35
+ "ai-search",
36
+ "llms-txt"
34
37
  ],
35
38
  "homepage": "https://www.jamdesk.com",
36
39
  "bugs": {
@@ -44,7 +47,7 @@
44
47
  "license": "Apache-2.0",
45
48
  "author": {
46
49
  "name": "Jamdesk",
47
- "email": "hello@jamdesk.com",
50
+ "email": "support@jamdesk.com",
48
51
  "url": "https://www.jamdesk.com"
49
52
  },
50
53
  "type": "module",
@@ -113,8 +116,8 @@
113
116
  "@mdx-js/mdx": "^3.1.1",
114
117
  "@types/fs-extra": "^11.0.0",
115
118
  "@types/node": "^25.5.0",
116
- "typescript": "^5.3.0",
117
- "vitest": "^4.1.0"
119
+ "typescript": "^6.0.2",
120
+ "vitest": "^4.1.2"
118
121
  },
119
122
  "engines": {
120
123
  "node": ">=20.0.0"
@@ -42,6 +42,7 @@ import { getLatexRemarkPlugins, getLatexRehypePlugins } from '@/lib/latex-config
42
42
  import { getTypographyRemarkPlugins } from '@/lib/typography-config';
43
43
  import { recmaCompoundComponents } from '@/lib/recma-compound-components';
44
44
  import { extractInlineComponents } from '@/lib/process-mdx-with-exports';
45
+ import { buildEndpointFromMdx } from '@/lib/build-endpoint-from-mdx';
45
46
  import { mdxSecurityOptions } from '@/lib/mdx-security-options';
46
47
  import fs from 'fs';
47
48
  import path from 'path';
@@ -67,7 +68,7 @@ import {
67
68
  generateCodeExamples,
68
69
  formatOpenApiWarning,
69
70
  type OpenApiEndpointData,
70
- type CodeExamples,
71
+ type CodeExample,
71
72
  type AuthMethod,
72
73
  } from '@/lib/openapi';
73
74
  import { ApiEndpoint } from '@/components/mdx/ApiEndpoint';
@@ -134,6 +135,7 @@ interface FrontmatterData {
134
135
  description?: string;
135
136
  api?: string;
136
137
  openapi?: string;
138
+ playground?: string;
137
139
  mode?: string;
138
140
  hideFooter?: boolean;
139
141
  rss?: boolean;
@@ -505,7 +507,7 @@ export default async function DocPage({ params }: PageProps) {
505
507
 
506
508
  // Extract and compile inline component exports from MDX
507
509
  // Only pass MDXComponents (server-compatible) to inline extraction
508
- const { inlineComponents } = await extractInlineComponents(content, MDXComponents);
510
+ const { inlineComponents, paramFields } = await extractInlineComponents(content, MDXComponents);
509
511
 
510
512
  // Check for component name collisions and warn
511
513
  const overriddenComponents = Object.keys(inlineComponents).filter(
@@ -560,7 +562,7 @@ export default async function DocPage({ params }: PageProps) {
560
562
 
561
563
  // Parse OpenAPI endpoint data if openapi frontmatter is present
562
564
  let openApiEndpointData: OpenApiEndpointData | null = null;
563
- let openApiCodeExamples: CodeExamples | null = null;
565
+ let openApiCodeExamples: CodeExample[] | null = null;
564
566
 
565
567
  // OpenAPI spec parsing - supports both static and ISR modes
566
568
  if (data.openapi && typeof data.openapi === 'string') {
@@ -591,7 +593,8 @@ export default async function DocPage({ params }: PageProps) {
591
593
 
592
594
  // Generate code examples
593
595
  const authMethod = config.api?.mdx?.auth?.method as AuthMethod | undefined;
594
- openApiCodeExamples = generateCodeExamples(openApiEndpointData, { authMethod });
596
+ const languages = config.api?.examples?.languages;
597
+ openApiCodeExamples = generateCodeExamples(openApiEndpointData, { authMethod, languages });
595
598
  } catch (err) {
596
599
  // Log formatted warning to console (appears in CLI and build logs)
597
600
  // Check if it's an OpenAPI validation error with our format
@@ -607,16 +610,12 @@ export default async function DocPage({ params }: PageProps) {
607
610
  // Parse MDX api field for pages with api: frontmatter (but not openapi:)
608
611
  let mdxApiMethod: HttpMethod | null = null;
609
612
  let mdxApiPath: string | null = null;
610
- let mdxApiBaseUrl: string | undefined;
611
613
 
612
614
  if (data.api && typeof data.api === 'string' && !data.openapi) {
613
615
  const parsed = parseMdxApiField(data.api);
614
616
  if (parsed) {
615
617
  mdxApiMethod = parsed.method;
616
618
  mdxApiPath = parsed.path;
617
- // Get server URL from config, with fallback
618
- const serverConfig = config.api?.mdx?.server;
619
- mdxApiBaseUrl = Array.isArray(serverConfig) ? serverConfig[0] : serverConfig;
620
619
  }
621
620
  }
622
621
 
@@ -644,6 +643,23 @@ export default async function DocPage({ params }: PageProps) {
644
643
  // Prose class for MDX content styling (defined in base.css)
645
644
  const proseClasses = 'prose max-w-none';
646
645
 
646
+ // Playground configuration — covers both openapi: and api: pages
647
+ const hasApiEndpoint = openApiEndpointData || (mdxApiMethod && mdxApiPath);
648
+ const playgroundDisplay = hasApiEndpoint
649
+ ? ((data.playground as 'interactive' | 'simple' | 'none' | undefined)
650
+ || config.api?.playground?.display || 'none') as 'interactive' | 'simple' | 'none'
651
+ : 'none';
652
+ const mdxServerConfig = config.api?.mdx?.server;
653
+ const fallbackServerUrl = Array.isArray(mdxServerConfig) ? mdxServerConfig[0] : mdxServerConfig;
654
+ const proxyEnabled = config.api?.playground?.proxy
655
+ ?? (playgroundDisplay === 'interactive');
656
+
657
+ // Build endpoint data for api: pages (for playground)
658
+ let mdxEndpointData: OpenApiEndpointData | undefined;
659
+ if (!openApiEndpointData && mdxApiMethod && mdxApiPath && playgroundDisplay !== 'none') {
660
+ mdxEndpointData = buildEndpointFromMdx(mdxApiMethod, mdxApiPath, paramFields, fallbackServerUrl);
661
+ }
662
+
647
663
  // For API pages, wrap the entire content area with ApiPageWrapper
648
664
  // so code panels can be positioned as siblings at the page level
649
665
  if (isApiPage) {
@@ -676,11 +692,24 @@ export default async function DocPage({ params }: PageProps) {
676
692
  <div className={proseClasses}>
677
693
  {/* MDX API endpoint badge (for pages with api: frontmatter) */}
678
694
  {mdxApiMethod && mdxApiPath && (
679
- <ApiEndpoint
680
- method={mdxApiMethod}
681
- path={mdxApiPath}
682
- baseUrl={mdxApiBaseUrl}
683
- />
695
+ mdxEndpointData && playgroundDisplay !== 'none' ? (
696
+ <OpenApiEndpoint
697
+ endpoint={mdxEndpointData}
698
+ playgroundOnly
699
+ playgroundDisplay={playgroundDisplay}
700
+ authMethod={config.api?.mdx?.auth?.method as AuthMethod | undefined}
701
+ authHeaderName={config.api?.mdx?.auth?.name}
702
+ serverUrl={fallbackServerUrl}
703
+ proxyEnabled={proxyEnabled}
704
+ languages={config.api?.examples?.languages}
705
+ />
706
+ ) : (
707
+ <ApiEndpoint
708
+ method={mdxApiMethod}
709
+ path={mdxApiPath}
710
+ baseUrl={fallbackServerUrl}
711
+ />
712
+ )
684
713
  )}
685
714
 
686
715
  {/* OpenAPI endpoint documentation (auto-generated from spec) */}
@@ -688,6 +717,12 @@ export default async function DocPage({ params }: PageProps) {
688
717
  <OpenApiEndpoint
689
718
  endpoint={openApiEndpointData}
690
719
  codeExamples={openApiCodeExamples || undefined}
720
+ playgroundDisplay={playgroundDisplay}
721
+ authMethod={config.api?.mdx?.auth?.method as AuthMethod | undefined}
722
+ authHeaderName={config.api?.mdx?.auth?.name}
723
+ serverUrl={fallbackServerUrl}
724
+ proxyEnabled={proxyEnabled}
725
+ languages={config.api?.examples?.languages}
691
726
  />
692
727
  )}
693
728
 
@@ -18,6 +18,8 @@ const CACHE_DURATIONS: Record<string, number> = {
18
18
  'image/svg+xml': 86400, // 1 day (may change)
19
19
  'image/x-icon': 31536000,
20
20
  'application/pdf': 86400,
21
+ 'video/mp4': 31536000, // 1 year (busted by ?v= query param on new builds)
22
+ 'video/webm': 31536000,
21
23
  default: 3600, // 1 hour
22
24
  };
23
25
 
@@ -37,10 +37,9 @@ function useMediaQuery(query: string): boolean {
37
37
 
38
38
  export function LayoutWrapper({ config, children }: LayoutWrapperProps) {
39
39
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
40
- // Chat is on by default in production; projects can opt out with chat.enabled: false.
41
- // Hidden in local dev the chat API route needs Upstash Vector + Anthropic API.
42
- const chatEnabled = process.env.NODE_ENV === 'production'
43
- && (config.chat?.enabled !== false);
40
+ // Chat button is shown in both dev and production so the layout matches.
41
+ // In dev mode, clicking it shows a "production only" notice instead of opening the panel.
42
+ const chatEnabled = config.chat?.enabled !== false;
44
43
 
45
44
  // xl breakpoint (1280px) — chat renders inline on desktop, overlay on mobile
46
45
  const isDesktop = useMediaQuery('(min-width: 1280px)');
@@ -6,6 +6,7 @@ interface ApiEndpointProps {
6
6
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
7
7
  path: string;
8
8
  baseUrl?: string;
9
+ onTryIt?: () => void;
9
10
  }
10
11
 
11
12
  const methodColors: Record<string, { bg: string; text: string; border: string }> = {
@@ -51,7 +52,7 @@ const methodColors: Record<string, { bg: string; text: string; border: string }>
51
52
  },
52
53
  };
53
54
 
54
- export function ApiEndpoint({ method, path, baseUrl = 'https://api.jamdesk.com/api' }: ApiEndpointProps) {
55
+ export function ApiEndpoint({ method, path, baseUrl = 'https://api.jamdesk.com/api', onTryIt }: ApiEndpointProps) {
55
56
  const [copied, setCopied] = useState(false);
56
57
  const colors = methodColors[method] || methodColors.GET;
57
58
  const fullUrl = `${baseUrl}${path}`;
@@ -73,10 +74,20 @@ export function ApiEndpoint({ method, path, baseUrl = 'https://api.jamdesk.com/a
73
74
  <span className="text-[var(--color-text-muted)]">{baseUrl}</span>
74
75
  <span className="text-[var(--color-text-primary)]">{path}</span>
75
76
  </span>
77
+ {/* Try it button */}
78
+ {onTryIt && (
79
+ <button
80
+ onClick={onTryIt}
81
+ className="px-2.5 py-1 text-xs font-medium rounded-md bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-hover,var(--color-accent))] text-white hover:opacity-90 transition-opacity cursor-pointer"
82
+ aria-label="Open API playground"
83
+ >
84
+ Try it
85
+ </button>
86
+ )}
76
87
  {/* Copy button */}
77
88
  <button
78
89
  onClick={handleCopy}
79
- className="p-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-md transition-colors"
90
+ className="p-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-md transition-colors cursor-pointer"
80
91
  title="Copy endpoint URL"
81
92
  aria-label="Copy endpoint URL"
82
93
  >
@@ -30,6 +30,7 @@ import { Tree, TreeFolder, TreeFile } from './Tree';
30
30
  import { Columns } from './Columns';
31
31
  import { View, ViewProvider, ViewSelector, ViewWrapper } from './View';
32
32
  import { YouTube } from './YouTube';
33
+ import { Video } from './Video';
33
34
 
34
35
  /**
35
36
  * Extract language from a pre element for tab label
@@ -148,6 +149,14 @@ function HttpCodeBlock({ method, url }: { method: string; url: string }) {
148
149
  );
149
150
  }
150
151
 
152
+ /** Fallback for raw <img src="video.mp4"> in MDX (preprocessor handles markdown syntax) */
153
+ const VIDEO_EXTENSIONS_IMG = ['.mp4', '.webm'];
154
+ function isVideoUrl(src: string | undefined): boolean {
155
+ if (!src) return false;
156
+ const pathOnly = src.split('?')[0];
157
+ return VIDEO_EXTENSIONS_IMG.some(ext => pathOnly.toLowerCase().endsWith(ext));
158
+ }
159
+
151
160
  export const MDXComponents = {
152
161
  Card,
153
162
  // Callout components
@@ -218,6 +227,8 @@ export const MDXComponents = {
218
227
  ViewWrapper,
219
228
  // Media embeds
220
229
  YouTube,
230
+ // Video player for local video files
231
+ Video,
221
232
  // Sized images from preprocess-mdx (![alt](url =WIDTHx) syntax).
222
233
  // These are output as <SizedImage> JSX so they go through component mapping
223
234
  // (raw <img> JSX in MDX bypasses the components provider).
@@ -253,6 +264,11 @@ export const MDXComponents = {
253
264
  // Destructure and ignore any other props to prevent them from being passed to DOM
254
265
  ...rest
255
266
  }: any) => {
267
+ // Check if this is a video file — render <Video> instead of <ZoomableImage>
268
+ if (isVideoUrl(src)) {
269
+ return <Video src={src} title={alt || undefined} />;
270
+ }
271
+
256
272
  // Check for data-no-zoom attribute or noZoom prop (handle both camelCase and lowercase)
257
273
  const disableZoom = dataNoZoom === 'true' || dataNoZoom === true ||
258
274
  noZoom === true || noZoom === 'true' ||
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { useState, useMemo } from 'react';
3
+ import { useDevOnlyNotice, DEV_FEATURE } from '../ui/DevOnlyNotice';
4
+
5
+ import { useState, useEffect, useMemo } from 'react';
4
6
  import { ApiEndpoint } from './ApiEndpoint';
5
7
  import { ParamField } from './ParamField';
6
8
  import { CodeGroup } from './CodeGroup';
@@ -14,9 +16,10 @@ import type {
14
16
  OpenApiEndpointData,
15
17
  ParsedParameter,
16
18
  ParsedResponse,
17
- CodeExamples,
19
+ CodeExample,
18
20
  JsonSchema,
19
21
  SecurityRequirement,
22
+ AuthMethod,
20
23
  } from '@/lib/openapi/types';
21
24
 
22
25
  // Preload Shiki highlighter on module load
@@ -28,9 +31,25 @@ interface OpenApiEndpointProps {
28
31
  /** Endpoint data from OpenAPI spec */
29
32
  endpoint: OpenApiEndpointData;
30
33
  /** Generated code examples */
31
- codeExamples?: CodeExamples;
34
+ codeExamples?: CodeExample[];
32
35
  /** Additional content to render after endpoint info */
33
36
  children?: React.ReactNode;
37
+ /** Playground display mode */
38
+ playgroundDisplay?: 'interactive' | 'simple' | 'none';
39
+ /** Auth method for playground */
40
+ authMethod?: AuthMethod;
41
+ /** Custom auth header name */
42
+ authHeaderName?: string;
43
+ /** Fallback server URL when endpoint.servers is empty */
44
+ serverUrl?: string;
45
+ /** Whether CORS proxy is enabled */
46
+ proxyEnabled?: boolean;
47
+ /** Languages for code generation */
48
+ languages?: string[];
49
+ /** When true, render only the ApiEndpoint badge (with Try it) + PlaygroundModal.
50
+ * Skips all auto-generated param docs, response panels, and code examples.
51
+ * Used for api: pages where the MDX content provides the documentation. */
52
+ playgroundOnly?: boolean;
34
53
  }
35
54
 
36
55
  /**
@@ -834,35 +853,23 @@ function ResponseFieldItem({
834
853
  /**
835
854
  * Code examples section - renders in right panel using CodePanel
836
855
  */
837
- function CodeExamplesSection({ examples }: { examples: CodeExamples }) {
856
+ function CodeExamplesSection({ examples }: { examples: CodeExample[] }) {
838
857
  const codeItems = useMemo(
839
- () => [
840
- { code: examples.curl, language: 'bash' },
841
- { code: examples.python, language: 'python' },
842
- { code: examples.javascript, language: 'javascript' },
843
- ],
844
- [examples.curl, examples.python, examples.javascript]
858
+ () => examples.map(ex => ({ code: ex.code, language: ex.language })),
859
+ [examples]
845
860
  );
846
861
 
847
862
  const { results: highlightedResults, isLoading } = useShikiHighlightMultiple(codeItems);
848
863
 
849
- // Render full Shiki HTML for theme background support
864
+ // Shiki generates HTML from trusted code strings (not user input)
850
865
  const tabs: CodePanelTab[] = useMemo(
851
- () => [
852
- {
853
- label: 'cURL',
854
- content: <div dangerouslySetInnerHTML={{ __html: highlightedResults[0] || '' }} />,
855
- },
856
- {
857
- label: 'Python',
858
- content: <div dangerouslySetInnerHTML={{ __html: highlightedResults[1] || '' }} />,
859
- },
860
- {
861
- label: 'JavaScript',
862
- content: <div dangerouslySetInnerHTML={{ __html: highlightedResults[2] || '' }} />,
863
- },
864
- ],
865
- [highlightedResults]
866
+ () => examples.map((ex, i) => ({
867
+ label: ex.label,
868
+ content: highlightedResults[i]
869
+ ? <div dangerouslySetInnerHTML={{ __html: highlightedResults[i] }} />
870
+ : null,
871
+ })),
872
+ [examples, highlightedResults]
866
873
  );
867
874
 
868
875
  // Don't render until highlighting is complete to prevent flash of unformatted content
@@ -880,13 +887,20 @@ function CodeExamplesSection({ examples }: { examples: CodeExamples }) {
880
887
  export function OpenApiEndpoint({
881
888
  endpoint,
882
889
  codeExamples,
883
- children
890
+ children,
891
+ playgroundDisplay = 'none',
892
+ authMethod,
893
+ authHeaderName,
894
+ serverUrl,
895
+ proxyEnabled,
896
+ languages,
897
+ playgroundOnly,
884
898
  }: OpenApiEndpointProps) {
885
- const {
886
- method,
887
- path,
888
- summary,
889
- description,
899
+ const {
900
+ method,
901
+ path,
902
+ summary,
903
+ description,
890
904
  deprecated,
891
905
  parameters,
892
906
  requestBody,
@@ -895,15 +909,33 @@ export function OpenApiEndpoint({
895
909
  externalDocs,
896
910
  servers,
897
911
  } = endpoint;
898
-
912
+
899
913
  // Group parameters by location
900
914
  const pathParams = parameters.filter(p => p.in === 'path');
901
915
  const queryParams = parameters.filter(p => p.in === 'query');
902
916
  const headerParams = parameters.filter(p => p.in === 'header');
903
917
  const cookieParams = parameters.filter(p => p.in === 'cookie');
904
-
918
+
905
919
  const baseUrl = servers[0]?.url;
906
-
920
+
921
+ // Playground state
922
+ const showPlayground = playgroundDisplay !== 'none';
923
+ const { showNotice: showDevNotice, notice: devNotice } = useDevOnlyNotice();
924
+
925
+ if (playgroundOnly) {
926
+ return (
927
+ <div className="openapi-endpoint">
928
+ <ApiEndpoint
929
+ method={method}
930
+ path={path}
931
+ baseUrl={baseUrl}
932
+ onTryIt={showPlayground ? () => { showDevNotice(DEV_FEATURE.API_PLAYGROUND); } : undefined}
933
+ />
934
+ {devNotice}
935
+ </div>
936
+ );
937
+ }
938
+
907
939
  return (
908
940
  <div className="openapi-endpoint">
909
941
  {/* Deprecation Warning */}
@@ -917,7 +949,12 @@ export function OpenApiEndpoint({
917
949
  )}
918
950
 
919
951
  {/* Endpoint Badge */}
920
- <ApiEndpoint method={method} path={path} baseUrl={baseUrl} />
952
+ <ApiEndpoint
953
+ method={method}
954
+ path={path}
955
+ baseUrl={baseUrl}
956
+ onTryIt={showPlayground ? () => { showDevNotice(DEV_FEATURE.API_PLAYGROUND); } : undefined}
957
+ />
921
958
 
922
959
  {/* External Docs */}
923
960
  {externalDocs && (
@@ -961,9 +998,12 @@ export function OpenApiEndpoint({
961
998
 
962
999
  {/* Responses - field documentation in main content */}
963
1000
  <ResponseSection responses={responses} />
1001
+ {devNotice}
964
1002
 
965
1003
  {/* Additional Content */}
966
1004
  {children}
1005
+
1006
+ {/* Playground Modal (lazy-loaded) */}
967
1007
  </div>
968
1008
  );
969
1009
  }
@@ -115,7 +115,7 @@ export const Tabs = memo(function Tabs({ children, borderBottom }: TabsProps) {
115
115
  <button
116
116
  key={index}
117
117
  onClick={() => handleTabClick(index)}
118
- className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
118
+ className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors whitespace-nowrap cursor-pointer ${
119
119
  isActive
120
120
  ? 'border-[var(--color-accent)] text-[var(--color-accent-text)]'
121
121
  : 'border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:border-[var(--color-border)]'