jamdesk 1.1.6 → 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 (79) hide show
  1. package/README.md +46 -1
  2. package/dist/__tests__/unit/docs-json-writer.test.js +59 -1
  3. package/dist/__tests__/unit/docs-json-writer.test.js.map +1 -1
  4. package/dist/__tests__/unit/spellcheck-fix.test.d.ts +2 -0
  5. package/dist/__tests__/unit/spellcheck-fix.test.d.ts.map +1 -0
  6. package/dist/__tests__/unit/spellcheck-fix.test.js +82 -0
  7. package/dist/__tests__/unit/spellcheck-fix.test.js.map +1 -0
  8. package/dist/__tests__/unit/spellcheck-utils.test.d.ts +2 -0
  9. package/dist/__tests__/unit/spellcheck-utils.test.d.ts.map +1 -0
  10. package/dist/__tests__/unit/spellcheck-utils.test.js +234 -0
  11. package/dist/__tests__/unit/spellcheck-utils.test.js.map +1 -0
  12. package/dist/__tests__/unit/tech-words.test.d.ts +2 -0
  13. package/dist/__tests__/unit/tech-words.test.d.ts.map +1 -0
  14. package/dist/__tests__/unit/tech-words.test.js +31 -0
  15. package/dist/__tests__/unit/tech-words.test.js.map +1 -0
  16. package/dist/commands/spellcheck.d.ts +13 -0
  17. package/dist/commands/spellcheck.d.ts.map +1 -0
  18. package/dist/commands/spellcheck.js +144 -0
  19. package/dist/commands/spellcheck.js.map +1 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +28 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib/deps.js +2 -2
  24. package/dist/lib/docs-json-writer.d.ts +6 -0
  25. package/dist/lib/docs-json-writer.d.ts.map +1 -1
  26. package/dist/lib/docs-json-writer.js +29 -0
  27. package/dist/lib/docs-json-writer.js.map +1 -1
  28. package/dist/lib/openapi/types.d.ts +11 -6
  29. package/dist/lib/openapi/types.d.ts.map +1 -1
  30. package/dist/lib/path-security.d.ts +3 -0
  31. package/dist/lib/path-security.d.ts.map +1 -1
  32. package/dist/lib/path-security.js +14 -1
  33. package/dist/lib/path-security.js.map +1 -1
  34. package/dist/lib/spellcheck-fix.d.ts +37 -0
  35. package/dist/lib/spellcheck-fix.d.ts.map +1 -0
  36. package/dist/lib/spellcheck-fix.js +292 -0
  37. package/dist/lib/spellcheck-fix.js.map +1 -0
  38. package/dist/lib/spellcheck-utils.d.ts +36 -0
  39. package/dist/lib/spellcheck-utils.d.ts.map +1 -0
  40. package/dist/lib/spellcheck-utils.js +138 -0
  41. package/dist/lib/spellcheck-utils.js.map +1 -0
  42. package/dist/lib/tech-words.d.ts +9 -0
  43. package/dist/lib/tech-words.d.ts.map +1 -0
  44. package/dist/lib/tech-words.js +118 -0
  45. package/dist/lib/tech-words.js.map +1 -0
  46. package/package.json +15 -10
  47. package/vendored/app/[[...slug]]/page.tsx +48 -13
  48. package/vendored/app/api/assets/[...path]/route.ts +2 -0
  49. package/vendored/components/layout/LayoutWrapper.tsx +3 -4
  50. package/vendored/components/mdx/ApiEndpoint.tsx +13 -2
  51. package/vendored/components/mdx/MDXComponents.tsx +16 -0
  52. package/vendored/components/mdx/OpenApiEndpoint.tsx +76 -36
  53. package/vendored/components/mdx/Tabs.tsx +1 -1
  54. package/vendored/components/mdx/Video.tsx +82 -0
  55. package/vendored/components/navigation/Header.tsx +3 -3
  56. package/vendored/components/navigation/Sidebar.tsx +3 -3
  57. package/vendored/components/ui/CodePanel.tsx +5 -5
  58. package/vendored/components/ui/CodePanelModal.tsx +3 -3
  59. package/vendored/components/ui/DevOnlyNotice.tsx +78 -0
  60. package/vendored/hooks/useChatPanel.tsx +21 -2
  61. package/vendored/hooks/useMediaQuery.ts +27 -0
  62. package/vendored/lib/build-endpoint-from-mdx.ts +66 -0
  63. package/vendored/lib/isr-build-executor.ts +1 -1
  64. package/vendored/lib/middleware-helpers.ts +6 -1
  65. package/vendored/lib/openapi/code-examples.ts +479 -99
  66. package/vendored/lib/openapi/index.ts +9 -1
  67. package/vendored/lib/openapi/types.ts +29 -5
  68. package/vendored/lib/preprocess-mdx.ts +103 -36
  69. package/vendored/lib/process-mdx-with-exports.ts +22 -14
  70. package/vendored/lib/remark-extract-param-fields.ts +134 -0
  71. package/vendored/lib/shiki-client.ts +12 -0
  72. package/vendored/lib/static-artifacts.ts +2 -0
  73. package/vendored/lib/static-file-route.ts +1 -1
  74. package/vendored/lib/url-safety.ts +122 -0
  75. package/vendored/next.config.js +7 -0
  76. package/vendored/schema/docs-schema.json +35 -4
  77. package/vendored/scripts/copy-files.cjs +60 -54
  78. package/vendored/scripts/validate-links.cjs +1 -1
  79. package/vendored/shared/path-security.ts +17 -1
@@ -123,11 +123,11 @@
123
123
  "simple",
124
124
  "none"
125
125
  ],
126
- "description": "Set the API playground display style. Options: `interactive` (full playground), `simple` (simplified view), or `none` (hidden). Defaults to `interactive`"
126
+ "description": "Set the API playground display style. Options: `interactive` (full playground), `simple` (simplified view), or `none` (hidden). Defaults to `none`"
127
127
  },
128
128
  "proxy": {
129
129
  "type": "boolean",
130
- "description": "Enable proxy server for API requests to handle CORS and authentication. Defaults to `true`"
130
+ "description": "Enable proxy server for API requests to handle CORS and authentication. Defaults to `true` when display is `interactive`, `false` otherwise"
131
131
  }
132
132
  },
133
133
  "additionalProperties": false,
@@ -147,9 +147,22 @@
147
147
  "languages": {
148
148
  "type": "array",
149
149
  "items": {
150
- "type": "string"
150
+ "type": "string",
151
+ "enum": [
152
+ "curl",
153
+ "bash",
154
+ "python",
155
+ "javascript",
156
+ "go",
157
+ "ruby",
158
+ "csharp",
159
+ "java",
160
+ "rust",
161
+ "php"
162
+ ]
151
163
  },
152
- "description": "Programming languages to include in auto-generated API code examples"
164
+ "uniqueItems": true,
165
+ "description": "Programming languages to include in auto-generated API code examples. Order determines tab display order. Defaults to [\"curl\", \"python\", \"javascript\"]"
153
166
  },
154
167
  "prefill": {
155
168
  "type": "boolean",
@@ -1473,6 +1486,18 @@
1473
1486
  "type": "string",
1474
1487
  "enum": ["sidenav", "topnav"],
1475
1488
  "description": "Deprecated: Ignored by Jamdesk. Theme determines layout automatically"
1489
+ },
1490
+ "spellcheck": {
1491
+ "type": "object",
1492
+ "properties": {
1493
+ "ignore": {
1494
+ "type": "array",
1495
+ "items": { "type": "string" },
1496
+ "description": "Words to ignore during spellcheck (product names, technical terms, etc.)"
1497
+ }
1498
+ },
1499
+ "additionalProperties": false,
1500
+ "description": "Configuration for the jamdesk spellcheck command"
1476
1501
  }
1477
1502
  },
1478
1503
  "required": [
@@ -1585,6 +1610,9 @@
1585
1610
  },
1586
1611
  "layout": {
1587
1612
  "$ref": "#/anyOf/0/properties/layout"
1613
+ },
1614
+ "spellcheck": {
1615
+ "$ref": "#/anyOf/0/properties/spellcheck"
1588
1616
  }
1589
1617
  },
1590
1618
  "required": [
@@ -1697,6 +1725,9 @@
1697
1725
  },
1698
1726
  "layout": {
1699
1727
  "$ref": "#/anyOf/0/properties/layout"
1728
+ },
1729
+ "spellcheck": {
1730
+ "$ref": "#/anyOf/0/properties/spellcheck"
1700
1731
  }
1701
1732
  },
1702
1733
  "required": [
@@ -49,10 +49,19 @@ function getContentImagesDir() {
49
49
  return path.join(__dirname, '../content/images');
50
50
  }
51
51
 
52
+ function getContentVideosDir() {
53
+ if (projectName) {
54
+ return path.join(projectsDir, projectName, 'videos');
55
+ }
56
+ return path.join(__dirname, '../content/videos');
57
+ }
58
+
52
59
  const projectDir = getProjectDir();
53
60
  const contentImagesDir = getContentImagesDir();
54
61
  const publicDir = path.join(__dirname, '../public');
55
62
  const publicImagesDir = path.join(publicDir, 'images');
63
+ const contentVideosDir = getContentVideosDir();
64
+ const publicVideosDir = path.join(publicDir, 'videos');
56
65
 
57
66
  // File type limits (in bytes)
58
67
  const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
@@ -234,66 +243,63 @@ async function copyAssets() {
234
243
  // Copy logo folder and favicon
235
244
  await copyLogoAndFavicon();
236
245
 
237
- if (!fs.existsSync(contentImagesDir)) {
238
- console.log(' No images directory found, skipping images...');
239
- return;
240
- }
241
-
242
- console.log('📁 Copying static assets to public directory...');
243
- await fs.ensureDir(publicImagesDir);
244
-
245
- const files = await fs.readdir(contentImagesDir, { withFileTypes: true });
246
- let copied = 0;
247
- let skipped = 0;
248
- let tooLarge = 0;
249
-
250
- for (const file of files) {
251
- const sourcePath = path.join(contentImagesDir, file.name);
252
- const destPath = path.join(publicImagesDir, file.name);
253
-
254
- if (file.isFile()) {
255
- const fileInfo = getFileCategory(sourcePath);
256
-
257
- if (!fileInfo) {
258
- console.log(` ⏭️ Skipping unsupported file type: ${file.name}`);
259
- skipped++;
260
- continue;
261
- }
262
-
263
- const stats = await fs.stat(sourcePath);
264
-
265
- if (stats.size > fileInfo.maxSize) {
266
- const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
267
- const maxMB = (fileInfo.maxSize / 1024 / 1024).toFixed(0);
268
- console.log(` ⚠️ File too large (${sizeMB}MB > ${maxMB}MB): ${file.name}`);
269
- tooLarge++;
270
- continue;
271
- }
272
-
273
- await fs.copy(sourcePath, destPath, { overwrite: true });
274
- copied++;
275
- } else if (file.isDirectory()) {
276
- // Recursively copy subdirectories
277
- const subFiles = await copySubDirectory(sourcePath, path.join(publicImagesDir, file.name));
278
- copied += subFiles.copied;
279
- skipped += subFiles.skipped;
280
- tooLarge += subFiles.tooLarge;
281
- }
282
- }
283
-
284
- console.log(`✓ Copied ${copied} file(s)`);
285
- if (skipped > 0) {
286
- console.log(` Skipped ${skipped} unsupported file(s)`);
287
- }
288
- if (tooLarge > 0) {
289
- console.log(` Skipped ${tooLarge} file(s) exceeding size limit`);
290
- }
246
+ await copyAssetDirectory(contentImagesDir, publicImagesDir, '📁');
247
+ await copyAssetDirectory(contentVideosDir, publicVideosDir, '🎬');
291
248
  } catch (error) {
292
249
  console.error('❌ Failed to copy assets:', error.message);
293
250
  process.exit(1);
294
251
  }
295
252
  }
296
253
 
254
+ async function copyAssetDirectory(srcDir, destDir, emoji) {
255
+ if (!fs.existsSync(srcDir)) return;
256
+
257
+ console.log(`${emoji} Copying assets to public directory...`);
258
+ await fs.ensureDir(destDir);
259
+
260
+ const files = await fs.readdir(srcDir, { withFileTypes: true });
261
+ let copied = 0;
262
+ let skipped = 0;
263
+ let tooLarge = 0;
264
+
265
+ for (const file of files) {
266
+ const sourcePath = path.join(srcDir, file.name);
267
+ const destPath = path.join(destDir, file.name);
268
+
269
+ if (file.isFile()) {
270
+ const fileInfo = getFileCategory(sourcePath);
271
+
272
+ if (!fileInfo) {
273
+ console.log(` ⏭️ Skipping unsupported file type: ${file.name}`);
274
+ skipped++;
275
+ continue;
276
+ }
277
+
278
+ const stats = await fs.stat(sourcePath);
279
+
280
+ if (stats.size > fileInfo.maxSize) {
281
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
282
+ const maxMB = (fileInfo.maxSize / 1024 / 1024).toFixed(0);
283
+ console.log(` ⚠️ File too large (${sizeMB}MB > ${maxMB}MB): ${file.name}`);
284
+ tooLarge++;
285
+ continue;
286
+ }
287
+
288
+ await fs.copy(sourcePath, destPath, { overwrite: true });
289
+ copied++;
290
+ } else if (file.isDirectory()) {
291
+ const subFiles = await copySubDirectory(sourcePath, path.join(destDir, file.name));
292
+ copied += subFiles.copied;
293
+ skipped += subFiles.skipped;
294
+ tooLarge += subFiles.tooLarge;
295
+ }
296
+ }
297
+
298
+ console.log(`✓ Copied ${copied} file(s)`);
299
+ if (skipped > 0) console.log(` Skipped ${skipped} unsupported file(s)`);
300
+ if (tooLarge > 0) console.log(` Skipped ${tooLarge} file(s) exceeding size limit`);
301
+ }
302
+
297
303
  async function copySubDirectory(sourceDir, destDir) {
298
304
  await fs.ensureDir(destDir);
299
305
 
@@ -89,7 +89,7 @@ function shouldSkipLink(href) {
89
89
  // Strips any trailing dimension suffix before checking extension
90
90
  // Formats: =WIDTHxHEIGHT, =WIDTHx, =xHEIGHT, =WIDTH
91
91
  const pathWithoutDimensions = href.replace(/\s*=(?:\d+x\d*|x\d+|\d+)$/i, '');
92
- if (/\.(png|jpg|jpeg|gif|svg|webp|ico|pdf|zip)$/i.test(pathWithoutDimensions)) return true;
92
+ if (/\.(png|jpg|jpeg|gif|svg|webp|ico|pdf|zip|mp4|webm)$/i.test(pathWithoutDimensions)) return true;
93
93
 
94
94
  // Root paths like /introduction are valid doc links — don't skip them
95
95
 
@@ -12,9 +12,25 @@ import path from 'path';
12
12
  /**
13
13
  * Check if a path stays within the project directory.
14
14
  * Returns true if path is within project, false otherwise.
15
+ *
16
+ * Rejects null bytes, URL-encoded sequences, and normalizes
17
+ * backslashes to prevent traversal via encoding tricks.
15
18
  */
16
19
  export function isPathWithinProject(filePath: string, projectDir: string): boolean {
17
- const absolutePath = path.resolve(projectDir, filePath);
20
+ // Reject null bytes (real or URL-encoded)
21
+ if (filePath.includes('\0') || filePath.includes('%00')) {
22
+ return false;
23
+ }
24
+
25
+ // Reject URL-encoded path separators and traversal sequences
26
+ if (/%2f/i.test(filePath) || /%5c/i.test(filePath)) {
27
+ return false;
28
+ }
29
+
30
+ // Normalize backslashes to forward slashes to prevent Windows-style traversal
31
+ const normalized = filePath.replace(/\\/g, '/');
32
+
33
+ const absolutePath = path.resolve(projectDir, normalized);
18
34
  const normalizedProject = path.resolve(projectDir);
19
35
 
20
36
  return absolutePath.startsWith(normalizedProject + path.sep) || absolutePath === normalizedProject;