From ae5fac1642d9d1e31c7ba274539bbfd7d679e6da Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Wed, 29 Oct 2025 01:39:52 +0800 Subject: [PATCH] fix: potential symlinks problem of subwiki --- features/stepDefinitions/wiki.ts | 19 +++++++++++++++---- .../FileSystemAdaptor.ts | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/features/stepDefinitions/wiki.ts b/features/stepDefinitions/wiki.ts index 68b92ca8..39dbb6d9 100644 --- a/features/stepDefinitions/wiki.ts +++ b/features/stepDefinitions/wiki.ts @@ -68,7 +68,18 @@ When('I cleanup test wiki so it could create a new one on start', async function */ Then('file {string} should exist in {string}', { timeout: 15000 }, async function(this: ApplicationWorld, fileName: string, directoryPath: string) { // Replace {tmpDir} with wiki test root (not wiki subfolder) - const actualPath = directoryPath.replace('{tmpDir}', wikiTestRootPath); + let actualPath = directoryPath.replace('{tmpDir}', wikiTestRootPath); + + // Resolve symlinks on all platforms to handle sub-wikis correctly + // On Linux, symlinks might point to the real path, so we need to follow them + if (await fs.pathExists(actualPath)) { + try { + actualPath = fs.realpathSync(actualPath); + } catch { + // If realpathSync fails, continue with the original path + } + } + const filePath = path.join(actualPath, fileName); let exists = false; @@ -169,20 +180,20 @@ When('I modify file {string} to contain {string}', async function(this: Applicat // We need to preserve headers and only modify the text part // Split by both \n and \r\n to handle different line endings const lines = fileContent.split(/\r?\n/); - + const blankLineIndex = lines.findIndex(line => line.trim() === ''); if (blankLineIndex >= 0) { // File has headers and content separated by blank line // Keep headers, replace text after blank line const headers = lines.slice(0, blankLineIndex + 1); - + // Note: We intentionally do NOT update the modified field here // This simulates a real user editing the file in an external editor, // where the modified field would not be automatically updated // The echo prevention mechanism should detect this as a real external change // because the content changed but the modified timestamp stayed the same - + fileContent = [...headers, content].join('\n'); } else { // File has only headers, no content yet (no blank line separator) diff --git a/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts b/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts index 0dc3f627..b5177c64 100644 --- a/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts +++ b/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts @@ -2,6 +2,7 @@ import type { Logger } from '$:/core/modules/utils/logger.js'; import { workspace } from '@services/wiki/wikiWorker/services'; import type { IWikiWorkspace, IWorkspace } from '@services/workspaces/interface'; import { backOff } from 'exponential-backoff'; +import fs from 'fs'; import path from 'path'; import type { FileInfo } from 'tiddlywiki'; import type { Tiddler, Wiki } from 'tiddlywiki'; @@ -148,9 +149,22 @@ export class FileSystemAdaptor { /** * Generate file info for sub-wiki directory + * Handles symlinks correctly across platforms (Windows junctions and Linux symlinks) */ protected generateSubWikiFileInfo(tiddler: Tiddler, subWiki: IWikiWorkspace, fileInfo: FileInfo | undefined): FileInfo { - const targetDirectory = subWiki.wikiFolderLocation; + let targetDirectory = subWiki.wikiFolderLocation; + + // Resolve symlinks to ensure consistent path handling across platforms + // On Windows, this resolves junctions; on Linux, this resolves symbolic links + // This prevents path inconsistencies when the same symlinked directory is referenced differently + // (e.g., via the symlink path vs the real path) + try { + targetDirectory = fs.realpathSync(targetDirectory); + } catch { + // If realpath fails, use the original path + // This can happen if the directory doesn't exist yet + } + $tw.utils.createDirectory(targetDirectory); return $tw.utils.generateTiddlerFileInfo(tiddler, {