mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			v0.97.1
			...
			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";
 | 
			
		||||
 | 
			
		||||
export default function register(app: Application) {
 | 
			
		||||
    const etapiDocument = yaml.load(readFileSync(join(RESOURCE_DIR, "etapi.openapi.yaml"), "utf8")) as JsonObject;
 | 
			
		||||
    const apiDocument = JSON.parse(readFileSync(join(RESOURCE_DIR, "openapi.json"), "utf-8"));
 | 
			
		||||
    // Clean trailing slashes from RESOURCE_DIR to prevent path resolution issues in packaged Electron apps
 | 
			
		||||
    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(
 | 
			
		||||
        "/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 fs from "fs";
 | 
			
		||||
import path from "path";
 | 
			
		||||
 | 
			
		||||
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", () => {
 | 
			
		||||
    it("should export a boolean", () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import escape from "escape-html";
 | 
			
		||||
import sanitize from "sanitize-filename";
 | 
			
		||||
import mimeTypes from "mime-types";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import type NoteMeta from "./meta/note_meta.js";
 | 
			
		||||
import log from "./log.js";
 | 
			
		||||
import { t } from "i18next";
 | 
			
		||||
@@ -292,7 +293,33 @@ export function getResourceDir() {
 | 
			
		||||
        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) {
 | 
			
		||||
        return path.dirname(process.argv[1]);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user