@assistant-ui/mcp-docs-server 0.1.17 → 0.1.18

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 (95) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +146 -152
  2. package/.docs/organized/code-examples/with-ai-sdk-v5.md +96 -101
  3. package/.docs/organized/code-examples/with-assistant-transport.md +132 -220
  4. package/.docs/organized/code-examples/with-cloud.md +124 -131
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +26 -46
  6. package/.docs/organized/code-examples/with-external-store.md +146 -151
  7. package/.docs/organized/code-examples/with-ffmpeg.md +129 -139
  8. package/.docs/organized/code-examples/with-langgraph.md +231 -225
  9. package/.docs/organized/code-examples/with-parent-id-grouping.md +146 -151
  10. package/.docs/organized/code-examples/with-react-hook-form.md +146 -152
  11. package/.docs/organized/code-examples/{store-example.md → with-store.md} +16 -20
  12. package/.docs/organized/code-examples/with-tanstack.md +23 -41
  13. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +36 -0
  14. package/.docs/raw/docs/runtimes/custom/local.mdx +31 -8
  15. package/.docs/raw/docs/ui/Scrollbar.mdx +0 -6
  16. package/dist/constants.d.ts +10 -0
  17. package/dist/constants.d.ts.map +1 -0
  18. package/dist/constants.js +14 -0
  19. package/dist/constants.js.map +1 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +33 -1
  23. package/dist/index.js.map +1 -0
  24. package/dist/prepare-docs/code-examples.d.ts +2 -0
  25. package/dist/prepare-docs/code-examples.d.ts.map +1 -0
  26. package/dist/prepare-docs/code-examples.js +129 -0
  27. package/dist/prepare-docs/code-examples.js.map +1 -0
  28. package/dist/prepare-docs/copy-raw.d.ts +2 -0
  29. package/dist/prepare-docs/copy-raw.d.ts.map +1 -0
  30. package/dist/prepare-docs/copy-raw.js +50 -0
  31. package/dist/prepare-docs/copy-raw.js.map +1 -0
  32. package/dist/prepare-docs/prepare.d.ts +2 -0
  33. package/dist/prepare-docs/prepare.d.ts.map +1 -0
  34. package/dist/prepare-docs/prepare.js +18 -195
  35. package/dist/prepare-docs/prepare.js.map +1 -0
  36. package/dist/stdio.d.ts +3 -0
  37. package/dist/stdio.d.ts.map +1 -0
  38. package/dist/stdio.js +4 -5
  39. package/dist/stdio.js.map +1 -0
  40. package/dist/tools/docs.d.ts +23 -0
  41. package/dist/tools/docs.d.ts.map +1 -0
  42. package/dist/tools/docs.js +168 -0
  43. package/dist/tools/docs.js.map +1 -0
  44. package/dist/tools/examples.d.ts +23 -0
  45. package/dist/tools/examples.d.ts.map +1 -0
  46. package/dist/tools/examples.js +95 -0
  47. package/dist/tools/examples.js.map +1 -0
  48. package/dist/tools/tests/test-setup.d.ts +4 -0
  49. package/dist/tools/tests/test-setup.d.ts.map +1 -0
  50. package/dist/tools/tests/test-setup.js +36 -0
  51. package/dist/tools/tests/test-setup.js.map +1 -0
  52. package/dist/utils/logger.d.ts +7 -0
  53. package/dist/utils/logger.d.ts.map +1 -0
  54. package/dist/utils/logger.js +20 -0
  55. package/dist/utils/logger.js.map +1 -0
  56. package/dist/utils/mcp-format.d.ts +7 -0
  57. package/dist/utils/mcp-format.d.ts.map +1 -0
  58. package/dist/utils/mcp-format.js +11 -0
  59. package/dist/utils/mcp-format.js.map +1 -0
  60. package/dist/utils/mdx.d.ts +9 -0
  61. package/dist/utils/mdx.d.ts.map +1 -0
  62. package/dist/utils/mdx.js +27 -0
  63. package/dist/utils/mdx.js.map +1 -0
  64. package/dist/utils/paths.d.ts +8 -0
  65. package/dist/utils/paths.d.ts.map +1 -0
  66. package/dist/utils/paths.js +84 -0
  67. package/dist/utils/paths.js.map +1 -0
  68. package/dist/utils/security.d.ts +2 -0
  69. package/dist/utils/security.d.ts.map +1 -0
  70. package/dist/utils/security.js +43 -0
  71. package/dist/utils/security.js.map +1 -0
  72. package/package.json +37 -19
  73. package/src/constants.ts +22 -0
  74. package/src/index.ts +51 -0
  75. package/src/prepare-docs/code-examples.ts +158 -0
  76. package/src/prepare-docs/copy-raw.ts +55 -0
  77. package/src/prepare-docs/prepare.ts +24 -0
  78. package/src/stdio.ts +7 -0
  79. package/src/tools/docs.ts +207 -0
  80. package/src/tools/examples.ts +107 -0
  81. package/src/tools/tests/docs.test.ts +122 -0
  82. package/src/tools/tests/examples.test.ts +94 -0
  83. package/src/tools/tests/integration.test.ts +46 -0
  84. package/src/tools/tests/json-parsing.test.ts +23 -0
  85. package/src/tools/tests/mcp-protocol.test.ts +133 -0
  86. package/src/tools/tests/path-traversal.test.ts +81 -0
  87. package/src/tools/tests/test-setup.ts +40 -0
  88. package/src/utils/logger.ts +20 -0
  89. package/src/utils/mcp-format.ts +12 -0
  90. package/src/utils/mdx.ts +39 -0
  91. package/src/utils/paths.ts +114 -0
  92. package/src/utils/security.ts +52 -0
  93. package/src/utils/tests/security.test.ts +119 -0
  94. package/dist/chunk-M2RKUM66.js +0 -38
  95. package/dist/chunk-NVNFQ5ZO.js +0 -423
@@ -0,0 +1,133 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { server } from "../../index.js";
3
+ import {
4
+ InitializeRequestSchema,
5
+ ListToolsRequestSchema,
6
+ CallToolRequestSchema,
7
+ type InitializeRequest,
8
+ type ListToolsRequest,
9
+ type CallToolRequest,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+
12
+ describe("MCP Protocol Integration", () => {
13
+ // These tests verify the MCP protocol layer handles requests correctly
14
+ // and that parameter schemas are properly converted to JSON schemas
15
+ it("should handle Initialize request", async () => {
16
+ const request: InitializeRequest = {
17
+ method: "initialize",
18
+ params: {
19
+ protocolVersion: "2024-11-05",
20
+ capabilities: {
21
+ tools: {},
22
+ },
23
+ clientInfo: {
24
+ name: "test-client",
25
+ version: "1.0.0",
26
+ },
27
+ },
28
+ };
29
+
30
+ // Parse and validate the request
31
+ const parsed = InitializeRequestSchema.parse(request);
32
+ expect(parsed).toBeDefined();
33
+
34
+ // The server should have an initialize handler set up
35
+ const handlers = (server as any).server._requestHandlers;
36
+ expect(handlers).toBeDefined();
37
+ expect(handlers.get("initialize")).toBeDefined();
38
+ });
39
+
40
+ it("should handle ListTools request", async () => {
41
+ const request: ListToolsRequest = {
42
+ method: "tools/list",
43
+ params: {},
44
+ };
45
+
46
+ // Parse and validate the request
47
+ const parsed = ListToolsRequestSchema.parse(request);
48
+ expect(parsed).toBeDefined();
49
+
50
+ // The server should have a tools/list handler
51
+ const handlers = (server as any).server._requestHandlers;
52
+ expect(handlers.get("tools/list")).toBeDefined();
53
+
54
+ // Call the handler
55
+ const handler = handlers.get("tools/list");
56
+ const result = await handler(parsed, {});
57
+
58
+ expect(result).toBeDefined();
59
+ expect(result.tools).toBeInstanceOf(Array);
60
+ expect(result.tools).toHaveLength(2);
61
+
62
+ // Check the tools have proper JSON schemas
63
+ const docsTool = result.tools.find(
64
+ (t: any) => t.name === "assistantUIDocs",
65
+ );
66
+ expect(docsTool).toBeDefined();
67
+ expect(docsTool.inputSchema).toBeDefined();
68
+ expect(docsTool.inputSchema.type).toBe("object");
69
+ expect(docsTool.inputSchema.properties).toBeDefined();
70
+
71
+ const examplesTool = result.tools.find(
72
+ (t: any) => t.name === "assistantUIExamples",
73
+ );
74
+ expect(examplesTool).toBeDefined();
75
+ expect(examplesTool.inputSchema).toBeDefined();
76
+ expect(examplesTool.inputSchema.type).toBe("object");
77
+ expect(examplesTool.inputSchema.properties).toBeDefined();
78
+ });
79
+
80
+ it("should handle CallTool request for assistantUIDocs", async () => {
81
+ const request: CallToolRequest = {
82
+ method: "tools/call",
83
+ params: {
84
+ name: "assistantUIDocs",
85
+ arguments: {
86
+ paths: ["/"],
87
+ },
88
+ },
89
+ };
90
+
91
+ // Parse and validate the request
92
+ const parsed = CallToolRequestSchema.parse(request);
93
+ expect(parsed).toBeDefined();
94
+
95
+ // The server should have a tools/call handler
96
+ const handlers = (server as any).server._requestHandlers;
97
+ expect(handlers.get("tools/call")).toBeDefined();
98
+
99
+ // Call the handler through the MCP protocol layer
100
+ const handler = handlers.get("tools/call");
101
+ const result = await handler(parsed, {});
102
+
103
+ expect(result).toBeDefined();
104
+ expect(result.content).toBeDefined();
105
+ expect(result.content[0].type).toBe("text");
106
+ });
107
+
108
+ it("should handle CallTool request for assistantUIExamples with no arguments", async () => {
109
+ const request: CallToolRequest = {
110
+ method: "tools/call",
111
+ params: {
112
+ name: "assistantUIExamples",
113
+ arguments: {},
114
+ },
115
+ };
116
+
117
+ // Parse and validate the request
118
+ const parsed = CallToolRequestSchema.parse(request);
119
+ expect(parsed).toBeDefined();
120
+
121
+ // The server should have a tools/call handler
122
+ const handlers = (server as any).server._requestHandlers;
123
+ expect(handlers.get("tools/call")).toBeDefined();
124
+
125
+ // Call the handler
126
+ const handler = handlers.get("tools/call");
127
+ const result = await handler(parsed, {});
128
+
129
+ expect(result).toBeDefined();
130
+ expect(result.content).toBeDefined();
131
+ expect(result.content[0].type).toBe("text");
132
+ });
133
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { testContext } from "./test-setup.js";
3
+
4
+ describe("Path Traversal Security", () => {
5
+ describe("assistantUIDocs tool", () => {
6
+ const maliciousPaths = [
7
+ "../../../../etc/passwd",
8
+ "../../../package.json",
9
+ "..\\..\\..\\windows\\system32",
10
+ "/etc/passwd",
11
+ "docs/../../../sensitive-file",
12
+ "./../../private/keys",
13
+ ];
14
+
15
+ maliciousPaths.forEach((path) => {
16
+ it(`should block path traversal attempt: ${path}`, async () => {
17
+ const result = await testContext.callTool("assistantUIDocs", {
18
+ paths: [path],
19
+ });
20
+
21
+ expect(result.error).toBeDefined();
22
+ expect(result.error).toContain("Invalid path");
23
+ expect(result.content).toBeUndefined();
24
+ });
25
+ });
26
+
27
+ it("should handle multiple paths with one malicious", async () => {
28
+ const result = await testContext.callTool("assistantUIDocs", {
29
+ paths: ["getting-started", "../../../../etc/passwd", "guides"],
30
+ });
31
+
32
+ expect(result.results).toBeDefined();
33
+ expect(result.results).toHaveLength(3);
34
+
35
+ expect(result.results[0].found).toBe(true);
36
+ expect(result.results[1].error).toContain("Invalid path");
37
+ expect(result.results[2].found).toBe(true);
38
+ });
39
+ });
40
+
41
+ describe("assistantUIExamples tool", () => {
42
+ const maliciousExamples = [
43
+ "../../../../etc/passwd",
44
+ "../../../src/index",
45
+ "..\\..\\..\\config",
46
+ "/root/.ssh/id_rsa",
47
+ "examples/../../../private",
48
+ ];
49
+
50
+ maliciousExamples.forEach((example) => {
51
+ it(`should block path traversal attempt: ${example}`, async () => {
52
+ const result = await testContext.callTool("assistantUIExamples", {
53
+ example,
54
+ });
55
+
56
+ expect(result.error).toBeDefined();
57
+ expect(result.error).toContain("Example not found");
58
+ expect(result.content).toBeUndefined();
59
+ });
60
+ });
61
+ });
62
+
63
+ describe("Valid paths should still work", () => {
64
+ it("should allow valid documentation paths", async () => {
65
+ const result = await testContext.callTool("assistantUIDocs", {
66
+ paths: ["getting-started", "api-reference/primitives/Thread"],
67
+ });
68
+
69
+ expect(result.results).toBeDefined();
70
+ expect(result.results).toHaveLength(2);
71
+ expect(result.results.every((r: any) => r.found || r.error)).toBe(true);
72
+ });
73
+
74
+ it("should allow valid example names", async () => {
75
+ const result = await testContext.callTool("assistantUIExamples", {});
76
+
77
+ expect(result.examples).toBeDefined();
78
+ expect(Array.isArray(result.examples)).toBe(true);
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,40 @@
1
+ import { beforeAll } from "vitest";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { PACKAGE_DIR } from "../../constants.js";
5
+ import { docsTools } from "../docs.js";
6
+ import { examplesTools } from "../examples.js";
7
+
8
+ const tools = {
9
+ assistantUIDocs: docsTools,
10
+ assistantUIExamples: examplesTools,
11
+ };
12
+
13
+ export const testContext = {
14
+ callTool: async (name: string, args: any) => {
15
+ const tool = tools[name as keyof typeof tools];
16
+ if (!tool) {
17
+ throw new Error(`Tool ${name} not found`);
18
+ }
19
+ const result = await tool.execute(args);
20
+
21
+ const text = result.content?.[0]?.text;
22
+ if (text === undefined) {
23
+ throw new Error(`Tool ${name} returned no content`);
24
+ }
25
+ try {
26
+ return JSON.parse(text);
27
+ } catch (error) {
28
+ throw new Error(
29
+ `Tool ${name} returned invalid JSON. Output: ${text}\nParse error: ${error instanceof Error ? error.message : String(error)}`,
30
+ );
31
+ }
32
+ },
33
+ };
34
+
35
+ beforeAll(() => {
36
+ const docsPath = join(PACKAGE_DIR, ".docs");
37
+ if (!existsSync(docsPath)) {
38
+ throw new Error("Documentation not prepared. Run: pnpm build");
39
+ }
40
+ });
@@ -0,0 +1,20 @@
1
+ import { IS_PREPARE_MODE } from "../constants.js";
2
+
3
+ export const logger = {
4
+ debug: (message: string, ...args: any[]) => {
5
+ if (process.env["DEBUG"]) {
6
+ console.debug(`[DEBUG] ${message}`, ...args);
7
+ }
8
+ },
9
+ info: (message: string, ...args: any[]) => {
10
+ if (IS_PREPARE_MODE) {
11
+ console.log(`[INFO] ${message}`, ...args);
12
+ }
13
+ },
14
+ error: (message: string, ...args: any[]) => {
15
+ console.error(`[ERROR] ${message}`, ...args);
16
+ },
17
+ warn: (message: string, ...args: any[]) => {
18
+ console.warn(`[WARN] ${message}`, ...args);
19
+ },
20
+ };
@@ -0,0 +1,12 @@
1
+ export function formatMCPResponse(data: any): {
2
+ content: Array<{ type: "text"; text: string }>;
3
+ } {
4
+ return {
5
+ content: [
6
+ {
7
+ type: "text",
8
+ text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
9
+ },
10
+ ],
11
+ };
12
+ }
@@ -0,0 +1,39 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import matter from "gray-matter";
3
+ import { logger } from "./logger.js";
4
+
5
+ interface MDXContent {
6
+ content: string;
7
+ frontmatter: Record<string, any>;
8
+ excerpt?: string;
9
+ }
10
+
11
+ export async function readMDXFile(
12
+ filePath: string,
13
+ ): Promise<MDXContent | null> {
14
+ try {
15
+ const fileContent = await readFile(filePath, "utf-8");
16
+ const { content, data } = matter(fileContent);
17
+
18
+ const excerptMatch = content.match(/^(.+?)(?:\n\n|$)/);
19
+ const excerpt =
20
+ excerptMatch?.[1] !== undefined
21
+ ? excerptMatch[1].replace(/^#+ /, "")
22
+ : undefined;
23
+
24
+ return {
25
+ content,
26
+ frontmatter: data,
27
+ ...(excerpt !== undefined && { excerpt }),
28
+ };
29
+ } catch (error) {
30
+ logger.error(`Failed to read MDX file: ${filePath}`, error);
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export function formatMDXContent(mdxContent: MDXContent): string {
36
+ const { content, frontmatter } = mdxContent;
37
+
38
+ return matter.stringify(content, frontmatter);
39
+ }
@@ -0,0 +1,114 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join, extname } from "node:path";
3
+ import { DOCS_PATH, MDX_EXTENSION, MD_EXTENSION } from "../constants.js";
4
+ import { logger } from "./logger.js";
5
+
6
+ const SIMILARITY_THRESHOLDS = {
7
+ EXACT_MATCH: 1,
8
+ CONTAINS_MATCH: 0.8,
9
+ PARTIAL_MATCH: 0.5,
10
+ MIN_SUGGESTION: 0.3,
11
+ } as const;
12
+
13
+ export async function listDirContents(dirPath: string): Promise<{
14
+ directories: string[];
15
+ files: string[];
16
+ }> {
17
+ try {
18
+ const items = await readdir(dirPath, { withFileTypes: true });
19
+
20
+ const directories = items
21
+ .filter((item) => item.isDirectory())
22
+ .map((item) => item.name)
23
+ .filter((name) => !name.startsWith("."))
24
+ .sort();
25
+
26
+ const files = items
27
+ .filter(
28
+ (item) =>
29
+ item.isFile() &&
30
+ (extname(item.name) === MDX_EXTENSION ||
31
+ extname(item.name) === MD_EXTENSION),
32
+ )
33
+ .map((item) => item.name)
34
+ .sort();
35
+
36
+ return { directories, files };
37
+ } catch (error) {
38
+ logger.error(`Failed to list directory contents: ${dirPath}`, error);
39
+ return { directories: [], files: [] };
40
+ }
41
+ }
42
+
43
+ export async function getAvailablePaths(): Promise<string[]> {
44
+ const paths: string[] = [];
45
+
46
+ async function scanDirectory(
47
+ dir: string,
48
+ prefix: string = "",
49
+ ): Promise<void> {
50
+ const { directories, files } = await listDirContents(dir);
51
+
52
+ for (const file of files) {
53
+ const name = file.replace(MDX_EXTENSION, "").replace(MD_EXTENSION, "");
54
+ paths.push(prefix ? `${prefix}/${name}` : name);
55
+ }
56
+
57
+ for (const subdir of directories) {
58
+ const subdirPath = join(dir, subdir);
59
+ const newPrefix = prefix ? `${prefix}/${subdir}` : subdir;
60
+ paths.push(newPrefix);
61
+ await scanDirectory(subdirPath, newPrefix);
62
+ }
63
+ }
64
+
65
+ await scanDirectory(DOCS_PATH);
66
+ return paths.sort();
67
+ }
68
+
69
+ export function findNearestPaths(
70
+ requestedPath: string,
71
+ availablePaths: string[],
72
+ ): string[] {
73
+ const normalizedRequest = requestedPath
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9]/g, "");
76
+
77
+ const scored = availablePaths.map((path) => {
78
+ const normalizedPath = path.toLowerCase().replace(/[^a-z0-9]/g, "");
79
+
80
+ if (normalizedPath.includes(normalizedRequest)) {
81
+ return { path, score: SIMILARITY_THRESHOLDS.EXACT_MATCH };
82
+ }
83
+
84
+ if (normalizedRequest.includes(normalizedPath)) {
85
+ return { path, score: SIMILARITY_THRESHOLDS.CONTAINS_MATCH };
86
+ }
87
+
88
+ const overlap = [...normalizedRequest].filter((char) =>
89
+ normalizedPath.includes(char),
90
+ ).length;
91
+
92
+ return {
93
+ path,
94
+ score:
95
+ (overlap / normalizedRequest.length) *
96
+ SIMILARITY_THRESHOLDS.PARTIAL_MATCH,
97
+ };
98
+ });
99
+
100
+ return scored
101
+ .filter((item) => item.score > SIMILARITY_THRESHOLDS.MIN_SUGGESTION)
102
+ .sort((a, b) => b.score - a.score)
103
+ .slice(0, 3)
104
+ .map((item) => item.path);
105
+ }
106
+
107
+ export async function pathExists(path: string): Promise<boolean> {
108
+ try {
109
+ await stat(path);
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
@@ -0,0 +1,52 @@
1
+ import path from "node:path";
2
+
3
+ export function sanitizePath(userPath: string): string {
4
+ if (!userPath || typeof userPath !== "string") {
5
+ throw new Error("Invalid path: Path must be a non-empty string");
6
+ }
7
+
8
+ // Check for traversal patterns before normalization - defense in depth
9
+ if (userPath.includes("../") || userPath.includes("..\\")) {
10
+ throw new Error("Invalid path: Directory traversal attempt detected");
11
+ }
12
+
13
+ const normalized = path.normalize(userPath);
14
+
15
+ if (path.isAbsolute(normalized)) {
16
+ throw new Error("Invalid path: Absolute paths are not allowed");
17
+ }
18
+
19
+ const relativePath = path.relative("", normalized);
20
+
21
+ if (relativePath.startsWith("..")) {
22
+ throw new Error("Invalid path: Directory traversal attempt detected");
23
+ }
24
+
25
+ if (relativePath.includes("..")) {
26
+ throw new Error("Invalid path: Path contains invalid traversal sequences");
27
+ }
28
+
29
+ if (relativePath.includes("\0")) {
30
+ throw new Error("Invalid path: Path contains null bytes");
31
+ }
32
+
33
+ if (process.platform !== "win32") {
34
+ if (normalized.includes("\\")) {
35
+ throw new Error("Invalid path: Backslashes not allowed");
36
+ }
37
+ } else {
38
+ if (normalized.includes(":") || normalized.startsWith("\\\\")) {
39
+ throw new Error("Invalid path: Path contains invalid Windows characters");
40
+ }
41
+ }
42
+
43
+ const segments = relativePath.split(path.sep);
44
+ for (const segment of segments) {
45
+ if (segment.startsWith(".") && segment !== ".") {
46
+ throw new Error("Invalid path: Hidden files are not allowed");
47
+ }
48
+ }
49
+
50
+ // Always return Unix-style paths for consistency across platforms
51
+ return relativePath.replace(/\\/g, "/");
52
+ }
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { sanitizePath } from "../security.js";
3
+
4
+ describe("sanitizePath", () => {
5
+ describe("should reject directory traversal attempts", () => {
6
+ const maliciousPaths = [
7
+ "../../../etc/passwd",
8
+ "..\\..\\windows\\system32",
9
+ "/etc/passwd",
10
+ "/absolute/path",
11
+ "docs/../../../etc/passwd",
12
+ "valid/../../../../../../etc/passwd",
13
+ "../",
14
+ "..",
15
+ ".../.../",
16
+ "foo/../../bar",
17
+ "./../../etc/passwd",
18
+ "path/with/../../../traversal",
19
+ "path/to/\0/null",
20
+ "path\\to\\..\\..\\file",
21
+ ];
22
+
23
+ if (process.platform === "win32") {
24
+ maliciousPaths.push("C:\\Windows\\System32", "\\\\server\\share");
25
+ } else {
26
+ maliciousPaths.push("C:\\Windows\\System32", "\\\\server\\share");
27
+ }
28
+
29
+ maliciousPaths.forEach((path) => {
30
+ it(`should reject: ${path}`, () => {
31
+ expect(() => sanitizePath(path)).toThrow();
32
+ });
33
+ });
34
+ });
35
+
36
+ describe("should reject invalid inputs", () => {
37
+ it("should reject empty string", () => {
38
+ expect(() => sanitizePath("")).toThrow(
39
+ "Invalid path: Path must be a non-empty string",
40
+ );
41
+ });
42
+
43
+ it("should reject null", () => {
44
+ expect(() => sanitizePath(null as any)).toThrow(
45
+ "Invalid path: Path must be a non-empty string",
46
+ );
47
+ });
48
+
49
+ it("should reject undefined", () => {
50
+ expect(() => sanitizePath(undefined as any)).toThrow(
51
+ "Invalid path: Path must be a non-empty string",
52
+ );
53
+ });
54
+
55
+ it("should reject numbers", () => {
56
+ expect(() => sanitizePath(123 as any)).toThrow(
57
+ "Invalid path: Path must be a non-empty string",
58
+ );
59
+ });
60
+ });
61
+
62
+ describe("should reject hidden files", () => {
63
+ const hiddenPaths = [
64
+ ".hidden",
65
+ ".git/config",
66
+ "docs/.secret",
67
+ "path/to/.env",
68
+ ];
69
+
70
+ hiddenPaths.forEach((path) => {
71
+ it(`should reject: ${path}`, () => {
72
+ expect(() => sanitizePath(path)).toThrow(
73
+ "Invalid path: Hidden files are not allowed",
74
+ );
75
+ });
76
+ });
77
+ });
78
+
79
+ describe("should allow valid paths", () => {
80
+ const validPaths = [
81
+ { input: "getting-started", expected: "getting-started" },
82
+ {
83
+ input: "api-reference/primitives/Thread",
84
+ expected: "api-reference/primitives/Thread",
85
+ },
86
+ { input: "guides/tools", expected: "guides/tools" },
87
+ { input: "docs/index", expected: "docs/index" },
88
+ { input: "examples/with-ai-sdk", expected: "examples/with-ai-sdk" },
89
+ { input: "./current-dir-file", expected: "current-dir-file" },
90
+ {
91
+ input: "deeply/nested/path/to/file",
92
+ expected: "deeply/nested/path/to/file",
93
+ },
94
+ ];
95
+
96
+ validPaths.forEach(({ input, expected }) => {
97
+ it(`should allow: ${input} => ${expected}`, () => {
98
+ expect(sanitizePath(input)).toBe(expected);
99
+ });
100
+ });
101
+ });
102
+
103
+ if (process.platform === "win32") {
104
+ describe("Windows-specific tests", () => {
105
+ it("should reject Windows drive letters", () => {
106
+ expect(() => sanitizePath("C:")).toThrow(
107
+ "Invalid path: Path contains invalid Windows characters",
108
+ );
109
+ expect(() => sanitizePath("D:\\file")).toThrow();
110
+ });
111
+
112
+ it("should reject UNC paths", () => {
113
+ expect(() => sanitizePath("\\\\server\\share")).toThrow(
114
+ "Invalid path: Absolute paths are not allowed",
115
+ );
116
+ });
117
+ });
118
+ }
119
+ });
@@ -1,38 +0,0 @@
1
- import { fileURLToPath } from 'url';
2
- import { dirname, join } from 'path';
3
-
4
- // src/constants.ts
5
- var __dirname$1 = dirname(fileURLToPath(import.meta.url));
6
- var ROOT_DIR = join(__dirname$1, "../../../");
7
- var PACKAGE_DIR = join(__dirname$1, "../");
8
- var EXAMPLES_PATH = join(ROOT_DIR, "examples");
9
- var DOCS_BASE = join(PACKAGE_DIR, ".docs");
10
- var DOCS_PATH = join(DOCS_BASE, "raw/docs");
11
- join(DOCS_BASE, "raw/blog");
12
- var CODE_EXAMPLES_PATH = join(DOCS_BASE, "organized/code-examples");
13
- var MDX_EXTENSION = ".mdx";
14
- var MD_EXTENSION = ".md";
15
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
16
- var IS_PREPARE_MODE = process.env.PREPARE === "true";
17
-
18
- // src/utils/logger.ts
19
- var logger = {
20
- debug: (message, ...args) => {
21
- if (process.env.DEBUG) {
22
- console.debug(`[DEBUG] ${message}`, ...args);
23
- }
24
- },
25
- info: (message, ...args) => {
26
- if (process.env.PREPARE === "true") {
27
- console.log(`[INFO] ${message}`, ...args);
28
- }
29
- },
30
- error: (message, ...args) => {
31
- console.error(`[ERROR] ${message}`, ...args);
32
- },
33
- warn: (message, ...args) => {
34
- console.warn(`[WARN] ${message}`, ...args);
35
- }
36
- };
37
-
38
- export { CODE_EXAMPLES_PATH, DOCS_PATH, EXAMPLES_PATH, IS_PREPARE_MODE, MAX_FILE_SIZE, MDX_EXTENSION, MD_EXTENSION, ROOT_DIR, logger };