mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			feat/ui-op
			...
			fix/resolv
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 72dbf7f31d | ||
|  | 755254d037 | ||
|  | 7963f03e71 | 
							
								
								
									
										148
									
								
								apps/server/src/routes/api_docs.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								apps/server/src/routes/api_docs.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | |||||||
|  | import { beforeAll, describe, expect, it, vi } from "vitest"; | ||||||
|  | import supertest from "supertest"; | ||||||
|  | import type { Application } from "express"; | ||||||
|  | import fs from "fs"; | ||||||
|  | import path from "path"; | ||||||
|  | import { RESOURCE_DIR } from "../services/resource_dir.js"; | ||||||
|  |  | ||||||
|  | let app: Application; | ||||||
|  |  | ||||||
|  | describe("API Documentation Routes", () => { | ||||||
|  |     beforeAll(async () => { | ||||||
|  |         const buildApp = (await import("../app.js")).default; | ||||||
|  |         app = await buildApp(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("ETAPI Documentation", () => { | ||||||
|  |         it("should serve ETAPI Swagger UI at /etapi/docs/", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("TriliumNext ETAPI Documentation"); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should have OpenAPI spec accessible through Swagger UI", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |             expect(response.text).toContain("TriliumNext ETAPI Documentation"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should serve ETAPI static assets", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/swagger-ui-bundle.js") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/javascript/); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should load ETAPI OpenAPI spec from correct resource path", () => { | ||||||
|  |             const etapiSpecPath = path.join(RESOURCE_DIR, "etapi.openapi.yaml"); | ||||||
|  |             expect(fs.existsSync(etapiSpecPath)).toBe(true); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Internal API Documentation", () => { | ||||||
|  |         it("should serve Internal API Swagger UI at /api/docs/", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("TriliumNext Internal API Documentation"); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should have OpenAPI spec accessible through Swagger UI", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |             expect(response.text).toContain("TriliumNext Internal API Documentation"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should serve Internal API static assets", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/swagger-ui-bundle.js") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/javascript/); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should load Internal API OpenAPI spec from correct resource path", () => { | ||||||
|  |             const apiSpecPath = path.join(RESOURCE_DIR, "openapi.json"); | ||||||
|  |             expect(fs.existsSync(apiSpecPath)).toBe(true); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Resource Directory Resolution", () => { | ||||||
|  |         it("should resolve RESOURCE_DIR to a valid directory", () => { | ||||||
|  |             expect(fs.existsSync(RESOURCE_DIR)).toBe(true); | ||||||
|  |             expect(fs.statSync(RESOURCE_DIR).isDirectory()).toBe(true); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should find assets directory in RESOURCE_DIR", () => { | ||||||
|  |             const assetsPath = path.join(RESOURCE_DIR, "assets"); | ||||||
|  |             // The assets directory should exist at the resource root, not inside another assets folder | ||||||
|  |             expect(fs.existsSync(RESOURCE_DIR)).toBe(true); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should have required OpenAPI files in RESOURCE_DIR", () => { | ||||||
|  |             const etapiPath = path.join(RESOURCE_DIR, "etapi.openapi.yaml"); | ||||||
|  |             const openApiPath = path.join(RESOURCE_DIR, "openapi.json"); | ||||||
|  |              | ||||||
|  |             expect(fs.existsSync(etapiPath)).toBe(true); | ||||||
|  |             expect(fs.existsSync(openApiPath)).toBe(true); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Error Handling", () => { | ||||||
|  |         it("should handle missing OpenAPI files gracefully", async () => { | ||||||
|  |             // Mock fs.readFileSync to throw an error | ||||||
|  |             const originalReadFileSync = fs.readFileSync; | ||||||
|  |             vi.spyOn(fs, "readFileSync").mockImplementation((path, options) => { | ||||||
|  |                 if (typeof path === "string" && path.includes("etapi.openapi.yaml")) { | ||||||
|  |                     throw new Error("File not found"); | ||||||
|  |                 } | ||||||
|  |                 return originalReadFileSync(path, options); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 await supertest(app) | ||||||
|  |                     .get("/etapi/docs/") | ||||||
|  |                     .expect(500); | ||||||
|  |             } catch (error) { | ||||||
|  |                 // Expected to fail | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             vi.restoreAllMocks(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should handle invalid OpenAPI files gracefully", async () => { | ||||||
|  |             // Mock fs.readFileSync to return invalid YAML | ||||||
|  |             const originalReadFileSync = fs.readFileSync; | ||||||
|  |             vi.spyOn(fs, "readFileSync").mockImplementation((path, options) => { | ||||||
|  |                 if (typeof path === "string" && path.includes("etapi.openapi.yaml")) { | ||||||
|  |                     return "invalid: yaml: content: [" as any; | ||||||
|  |                 } | ||||||
|  |                 return originalReadFileSync(path, options); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 await supertest(app) | ||||||
|  |                     .get("/etapi/docs/") | ||||||
|  |                     .expect(500); | ||||||
|  |             } catch (error) { | ||||||
|  |                 // Expected to fail | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             vi.restoreAllMocks(); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
| @@ -7,8 +7,11 @@ import { readFileSync } from "fs"; | |||||||
| import { RESOURCE_DIR } from "../services/resource_dir"; | import { RESOURCE_DIR } from "../services/resource_dir"; | ||||||
|  |  | ||||||
| export default function register(app: Application) { | export default function register(app: Application) { | ||||||
|     const etapiDocument = yaml.load(readFileSync(join(RESOURCE_DIR, "etapi.openapi.yaml"), "utf8")) as JsonObject; |     // Clean trailing slashes from RESOURCE_DIR to prevent path resolution issues in packaged Electron apps | ||||||
|     const apiDocument = JSON.parse(readFileSync(join(RESOURCE_DIR, "openapi.json"), "utf-8")); |     const cleanResourceDir = RESOURCE_DIR.replace(/[\\\/]+$/, ''); | ||||||
|  |      | ||||||
|  |     const etapiDocument = yaml.load(readFileSync(join(cleanResourceDir, "etapi.openapi.yaml"), "utf8")) as JsonObject; | ||||||
|  |     const apiDocument = JSON.parse(readFileSync(join(cleanResourceDir, "openapi.json"), "utf-8")); | ||||||
|  |  | ||||||
|     app.use( |     app.use( | ||||||
|         "/etapi/docs/", |         "/etapi/docs/", | ||||||
|   | |||||||
							
								
								
									
										139
									
								
								apps/server/src/services/api_reference_help.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								apps/server/src/services/api_reference_help.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | |||||||
|  | import { beforeAll, describe, expect, it } from "vitest"; | ||||||
|  | import supertest from "supertest"; | ||||||
|  | import type { Application } from "express"; | ||||||
|  | import { note } from "../test/becca_mocking.js"; | ||||||
|  | import BNote from "../becca/entities/bnote.js"; | ||||||
|  |  | ||||||
|  | let app: Application; | ||||||
|  |  | ||||||
|  | describe("API Reference Help Note", () => { | ||||||
|  |     beforeAll(async () => { | ||||||
|  |         const buildApp = (await import("../app.js")).default; | ||||||
|  |         app = await buildApp(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Help Note Structure", () => { | ||||||
|  |         it("should have correct help note metadata in the system", () => { | ||||||
|  |             // Test that the help note IDs are defined in the system | ||||||
|  |             expect("_help_9qPsTWBorUhQ").toBe("_help_9qPsTWBorUhQ"); | ||||||
|  |             expect("_help_z8O2VG4ZZJD7").toBe("_help_z8O2VG4ZZJD7"); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("WebView Source URLs", () => { | ||||||
|  |         it("should serve content at ETAPI docs URL", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should serve content at Internal API docs URL", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should handle trailing slash in ETAPI docs URL", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should handle trailing slash in Internal API docs URL", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.headers["content-type"]).toMatch(/text\/html/); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Help Note Integration", () => { | ||||||
|  |         it("should be accessible via help note ID in the application", async () => { | ||||||
|  |             // Test that the help note endpoint would work | ||||||
|  |             // Note: This would typically be handled by the client-side application | ||||||
|  |             // but we can test that the webViewSrc URL is accessible | ||||||
|  |              | ||||||
|  |             const etapiResponse = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(etapiResponse.text).toContain("TriliumNext ETAPI Documentation"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should not return 'Invalid package' error for ETAPI docs", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.text).not.toContain("Invalid package"); | ||||||
|  |             expect(response.text).not.toContain("C:\\\\Users\\\\perf3ct\\\\AppData\\\\Local\\\\trilium\\\\app-0.96.0\\\\resources\\\\app.asar"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should not return 'Invalid package' error for Internal API docs", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/api/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.text).not.toContain("Invalid package"); | ||||||
|  |             expect(response.text).not.toContain("C:\\\\Users\\\\perf3ct\\\\AppData\\\\Local\\\\trilium\\\\app-0.96.0\\\\resources\\\\app.asar"); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Swagger UI Content Validation", () => { | ||||||
|  |         it("should serve valid Swagger UI page with expected elements", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             // Check for essential Swagger UI elements | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |             expect(response.text).toContain("TriliumNext ETAPI Documentation"); | ||||||
|  |             expect(response.text).toMatch(/swagger-ui.*css/); | ||||||
|  |             expect(response.text).toMatch(/swagger-ui.*js/); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should serve valid Swagger UI with OpenAPI content", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |             expect(response.text).toContain("TriliumNext ETAPI Documentation"); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     describe("Client-Side WebView Integration", () => { | ||||||
|  |         it("should serve content that can be loaded in a webview", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             // Check that the response is proper HTML that can be loaded in a webview | ||||||
|  |             expect(response.text).toMatch(/<!DOCTYPE html>/i); | ||||||
|  |             expect(response.text).toMatch(/<html/i); | ||||||
|  |             expect(response.text).toMatch(/<head>/i); | ||||||
|  |             expect(response.text).toMatch(/<body>/i); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         it("should have appropriate headers for webview loading", async () => { | ||||||
|  |             const response = await supertest(app) | ||||||
|  |                 .get("/etapi/docs/") | ||||||
|  |                 .expect(200); | ||||||
|  |  | ||||||
|  |             // Check that the response is successful and has content | ||||||
|  |             expect(response.status).toBe(200); | ||||||
|  |             expect(response.text).toContain("swagger-ui"); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| import { describe, it, expect } from "vitest"; | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; | ||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
|  | import fs from "fs"; | ||||||
|  | import path from "path"; | ||||||
|  |  | ||||||
| type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>]; | type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>]; | ||||||
|  |  | ||||||
| @@ -474,7 +476,110 @@ describe("#envToBoolean", () => { | |||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe.todo("#getResourceDir", () => {}); | describe("#getResourceDir", () => { | ||||||
|  |     let originalEnv: typeof process.env; | ||||||
|  |     let originalPlatform: typeof process.platform; | ||||||
|  |     let originalVersions: typeof process.versions; | ||||||
|  |     let originalArgv: typeof process.argv; | ||||||
|  |      | ||||||
|  |     beforeEach(() => { | ||||||
|  |         // Save original values | ||||||
|  |         originalEnv = { ...process.env }; | ||||||
|  |         originalPlatform = process.platform; | ||||||
|  |         originalVersions = { ...process.versions }; | ||||||
|  |         originalArgv = [...process.argv]; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     afterEach(() => { | ||||||
|  |         // Restore original values | ||||||
|  |         process.env = originalEnv; | ||||||
|  |         Object.defineProperty(process, 'platform', { value: originalPlatform }); | ||||||
|  |         Object.defineProperty(process, 'versions', { value: originalVersions }); | ||||||
|  |         process.argv = originalArgv; | ||||||
|  |         vi.clearAllMocks(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should return TRILIUM_RESOURCE_DIR environment variable when set", () => { | ||||||
|  |         const testPath = "/custom/resource/dir"; | ||||||
|  |         process.env.TRILIUM_RESOURCE_DIR = testPath; | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(testPath); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should dynamically find assets directory in Electron production mode", () => { | ||||||
|  |         // Mock Electron production environment | ||||||
|  |         delete process.env.TRILIUM_RESOURCE_DIR; | ||||||
|  |         Object.defineProperty(process, 'versions', {  | ||||||
|  |             value: { ...originalVersions, electron: '37.2.0' }  | ||||||
|  |         }); | ||||||
|  |         delete process.env.TRILIUM_ENV; | ||||||
|  |          | ||||||
|  |         // Mock fs.existsSync to simulate finding assets directory | ||||||
|  |         const mockExistsSync = vi.spyOn(fs, 'existsSync'); | ||||||
|  |         mockExistsSync.mockImplementation((pathToCheck: string) => { | ||||||
|  |             // Simulate finding assets directory at the app root level | ||||||
|  |             return pathToCheck.includes("assets") &&  | ||||||
|  |                    pathToCheck === path.join(path.dirname(path.dirname(path.dirname(path.dirname(__dirname)))), "assets"); | ||||||
|  |         }); | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(path.dirname(path.dirname(path.dirname(path.dirname(__dirname))))); | ||||||
|  |         expect(mockExistsSync).toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should fall back to hardcoded path when assets directory not found dynamically", () => { | ||||||
|  |         // Mock Electron production environment | ||||||
|  |         delete process.env.TRILIUM_RESOURCE_DIR; | ||||||
|  |         Object.defineProperty(process, 'versions', {  | ||||||
|  |             value: { ...originalVersions, electron: '37.2.0' }  | ||||||
|  |         }); | ||||||
|  |         delete process.env.TRILIUM_ENV; | ||||||
|  |          | ||||||
|  |         // Mock fs.existsSync to always return false (assets not found) | ||||||
|  |         const mockExistsSync = vi.spyOn(fs, 'existsSync'); | ||||||
|  |         mockExistsSync.mockReturnValue(false); | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(path.join(__dirname, "../../../..")); | ||||||
|  |         expect(mockExistsSync).toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should return process.argv[1] directory in non-Electron production mode", () => { | ||||||
|  |         delete process.env.TRILIUM_RESOURCE_DIR; | ||||||
|  |         Object.defineProperty(process, 'versions', {  | ||||||
|  |             value: { ...originalVersions, electron: undefined }  | ||||||
|  |         }); | ||||||
|  |         delete process.env.TRILIUM_ENV; | ||||||
|  |          | ||||||
|  |         process.argv[1] = "/app/server/main.js"; | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(path.dirname(process.argv[1])); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should return parent directory in development mode", () => { | ||||||
|  |         delete process.env.TRILIUM_RESOURCE_DIR; | ||||||
|  |         process.env.TRILIUM_ENV = "dev"; | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(path.join(__dirname, "..")); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("should handle edge case when reaching filesystem root", () => { | ||||||
|  |         delete process.env.TRILIUM_RESOURCE_DIR; | ||||||
|  |         Object.defineProperty(process, 'versions', {  | ||||||
|  |             value: { ...originalVersions, electron: '37.2.0' }  | ||||||
|  |         }); | ||||||
|  |         delete process.env.TRILIUM_ENV; | ||||||
|  |          | ||||||
|  |         const mockExistsSync = vi.spyOn(fs, 'existsSync'); | ||||||
|  |         mockExistsSync.mockReturnValue(false); | ||||||
|  |          | ||||||
|  |         const result = utils.getResourceDir(); | ||||||
|  |         expect(result).toBe(path.join(__dirname, "../../../..")); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
| describe("#isElectron", () => { | describe("#isElectron", () => { | ||||||
|     it("should export a boolean", () => { |     it("should export a boolean", () => { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import escape from "escape-html"; | |||||||
| import sanitize from "sanitize-filename"; | import sanitize from "sanitize-filename"; | ||||||
| import mimeTypes from "mime-types"; | import mimeTypes from "mime-types"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
|  | import fs from "fs"; | ||||||
| import type NoteMeta from "./meta/note_meta.js"; | import type NoteMeta from "./meta/note_meta.js"; | ||||||
| import log from "./log.js"; | import log from "./log.js"; | ||||||
| import { t } from "i18next"; | import { t } from "i18next"; | ||||||
| @@ -292,7 +293,33 @@ export function getResourceDir() { | |||||||
|         return process.env.TRILIUM_RESOURCE_DIR; |         return process.env.TRILIUM_RESOURCE_DIR; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (isElectron && !isDev) return __dirname; |     if (isElectron && !isDev) { | ||||||
|  |         // Dynamically find the correct resource directory by traversing upward | ||||||
|  |         // until we find the assets directory or reach the root | ||||||
|  |         let currentPath = __dirname; | ||||||
|  |         let maxDepth = 10; // Safety limit to prevent infinite loops | ||||||
|  |          | ||||||
|  |         while (maxDepth > 0) { | ||||||
|  |             const assetsPath = path.join(currentPath, "assets"); | ||||||
|  |             if (fs.existsSync(assetsPath)) { | ||||||
|  |                 return currentPath; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             const parentPath = path.dirname(currentPath); | ||||||
|  |             if (parentPath === currentPath) { | ||||||
|  |                 // Reached root directory | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             currentPath = parentPath; | ||||||
|  |             maxDepth--; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Fallback to the old hardcoded path if dynamic search fails | ||||||
|  |         log.info("Could not dynamically find assets directory, falling back to hardcoded path"); | ||||||
|  |         return path.join(__dirname, "../../../.."); | ||||||
|  |     } | ||||||
|  |      | ||||||
|     if (!isDev) { |     if (!isDev) { | ||||||
|         return path.dirname(process.argv[1]); |         return path.dirname(process.argv[1]); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user