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.
- package/README.md +5 -2
- package/dist/lib/deps.js +2 -2
- package/dist/lib/openapi/types.d.ts +11 -6
- package/dist/lib/openapi/types.d.ts.map +1 -1
- package/dist/lib/path-security.d.ts +3 -0
- package/dist/lib/path-security.d.ts.map +1 -1
- package/dist/lib/path-security.js +14 -1
- package/dist/lib/path-security.js.map +1 -1
- package/package.json +13 -10
- package/vendored/app/[[...slug]]/page.tsx +48 -13
- package/vendored/app/api/assets/[...path]/route.ts +2 -0
- package/vendored/components/layout/LayoutWrapper.tsx +3 -4
- package/vendored/components/mdx/ApiEndpoint.tsx +13 -2
- package/vendored/components/mdx/MDXComponents.tsx +16 -0
- package/vendored/components/mdx/OpenApiEndpoint.tsx +76 -36
- package/vendored/components/mdx/Tabs.tsx +1 -1
- package/vendored/components/mdx/Video.tsx +82 -0
- package/vendored/components/navigation/Header.tsx +3 -3
- package/vendored/components/navigation/Sidebar.tsx +3 -3
- package/vendored/components/ui/CodePanel.tsx +5 -5
- package/vendored/components/ui/CodePanelModal.tsx +3 -3
- package/vendored/components/ui/DevOnlyNotice.tsx +78 -0
- package/vendored/hooks/useChatPanel.tsx +21 -2
- package/vendored/hooks/useMediaQuery.ts +27 -0
- package/vendored/lib/build-endpoint-from-mdx.ts +66 -0
- package/vendored/lib/isr-build-executor.ts +1 -1
- package/vendored/lib/middleware-helpers.ts +6 -1
- package/vendored/lib/openapi/code-examples.ts +479 -99
- package/vendored/lib/openapi/index.ts +9 -1
- package/vendored/lib/openapi/types.ts +29 -5
- package/vendored/lib/preprocess-mdx.ts +103 -36
- package/vendored/lib/process-mdx-with-exports.ts +22 -14
- package/vendored/lib/remark-extract-param-fields.ts +134 -0
- package/vendored/lib/shiki-client.ts +12 -0
- package/vendored/lib/static-artifacts.ts +2 -0
- package/vendored/lib/url-safety.ts +122 -0
- package/vendored/next.config.js +7 -0
- package/vendored/schema/docs-schema.json +17 -4
- package/vendored/scripts/copy-files.cjs +60 -54
- package/vendored/scripts/validate-links.cjs +1 -1
- 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 `
|
|
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
|
-
"
|
|
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",
|
|
@@ -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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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;
|