Skip to content

Commit 7c0b947

Browse files
authored
Merge pull request #49 from LonoxX/develop
fix: resolve vault cache synchronization issues with file and folder operations
2 parents 047c8b4 + 9f9d0e7 commit 7c0b947

7 files changed

Lines changed: 181 additions & 30 deletions

File tree

esbuild.config.mjs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ if you want to view the source, please visit the github repository of this plugi
1111
`;
1212

1313
const prod = process.argv[2] === "production";
14+
const devVaultPath =
15+
"/home/lonoxx/Nextcloud/Obsidian/.obsidian/plugins/github-issues/";
1416

1517
// Function to copy files to dist folder
1618
function copyToDist(files) {
@@ -31,6 +33,33 @@ function copyToDist(files) {
3133
});
3234
}
3335

36+
// Function to copy files from dist to dev vault
37+
function copyToDevVault() {
38+
if (!fs.existsSync(devVaultPath)) {
39+
fs.mkdirSync(devVaultPath, { recursive: true });
40+
}
41+
42+
const distFiles = [
43+
"dist/main.js",
44+
"manifest.json",
45+
"styles.css",
46+
"versions.json",
47+
];
48+
49+
distFiles.forEach((file) => {
50+
if (fs.existsSync(file)) {
51+
const fileName = path.basename(file);
52+
const destPath = path.join(devVaultPath, fileName);
53+
fs.copyFileSync(file, destPath);
54+
console.log(`Copied ${file} to ${destPath}`);
55+
} else {
56+
console.warn(
57+
`Warning: ${file} not found, skipping copy to dev vault`,
58+
);
59+
}
60+
});
61+
}
62+
3463
const context = await esbuild.context({
3564
banner: {
3665
js: banner,
@@ -60,11 +89,36 @@ const context = await esbuild.context({
6089
treeShaking: true,
6190
outfile: "dist/main.js",
6291
minify: prod,
92+
plugins: prod
93+
? []
94+
: [
95+
{
96+
name: "copy-to-dev-vault",
97+
setup(build) {
98+
build.onEnd((result) => {
99+
if (result.errors.length === 0) {
100+
console.log("Copying to dev vault...");
101+
const mainJsPath = path.join(
102+
devVaultPath,
103+
"main.js",
104+
);
105+
if (fs.existsSync("dist/main.js")) {
106+
fs.copyFileSync("dist/main.js", mainJsPath);
107+
console.log(
108+
`Copied dist/main.js → ${mainJsPath}`,
109+
);
110+
}
111+
}
112+
});
113+
},
114+
},
115+
],
63116
});
64117

65118
if (prod) {
66119
await context.rebuild();
67120
copyToDist(["versions.json", "manifest.json", "styles.css"]);
121+
copyToDevVault();
68122
process.exit(0);
69123
} else {
70124
await context.watch();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"obsidian": "^1.12.3",
2828
"prettier": "^3.8.1",
2929
"tslib": "^2.8.1",
30-
"typescript": "^5.9.3"
30+
"typescript": "^6.0.2"
3131
},
3232
"dependencies": {
3333
"date-fns": "^4.1.0",

src/file-manager.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ export class FileManager {
295295
this.settings.dateFormat,
296296
);
297297
const fileName = `${baseFileName}.md`;
298-
const filePath = `${folderPath}/${fileName}`;
298+
// Normalize folder path to use forward slashes for consistent vault lookups
299+
const normalizedFolderPath = folderPath.replace(/\\/g, "/");
300+
const filePath = `${normalizedFolderPath}/${fileName}`;
299301

300302
const existingFile = this.app.vault.getAbstractFileByPath(filePath);
301303
let fileContent = await this.generateProjectItemContent(
@@ -323,7 +325,25 @@ export class FileManager {
323325
}
324326
await this.app.vault.modify(existingFile, fileContent);
325327
} else {
326-
await this.app.vault.create(filePath, fileContent);
328+
try {
329+
await this.app.vault.create(filePath, fileContent);
330+
} catch (fileCreateError: unknown) {
331+
const errorMsg = fileCreateError instanceof Error ? fileCreateError.message : String(fileCreateError);
332+
333+
// Check if file exists due to stale cache
334+
const fileCheck = this.app.vault.getAbstractFileByPath(filePath);
335+
336+
if (fileCheck instanceof TFile) {
337+
338+
// File exists but wasn't detected before - update it
339+
const existingContent = await this.app.vault.read(fileCheck);
340+
await this.app.vault.modify(fileCheck, fileContent);
341+
this.noticeManager.debug(`Updated existing project item file for #${content.number} (file existed but cache was stale)`);
342+
} else {
343+
// File creation genuinely failed - rethrow
344+
throw fileCreateError;
345+
}
346+
}
327347
}
328348
createdCount++;
329349
}

src/issue-file-manager.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ export class IssueFileManager {
110110
);
111111
}
112112

113+
// Normalize folder path to use forward slashes for consistent vault lookups
114+
const normalizedIssueFolderPath = issueFolderPath.replace(/\\/g, "/");
113115
const file = this.app.vault.getAbstractFileByPath(
114-
`${issueFolderPath}/${fileName}`,
116+
`${normalizedIssueFolderPath}/${fileName}`,
115117
);
116118

117119
const [owner, repoName] = repo.repository.split("/");
@@ -288,11 +290,30 @@ export class IssueFileManager {
288290
}
289291
}
290292
} else {
291-
await this.app.vault.create(
292-
`${issueFolderPath}/${fileName}`,
293-
content,
294-
);
295-
this.noticeManager.debug(`Created issue file for ${issue.number}`);
293+
// Normalize path to use forward slashes consistently
294+
const normalizedFolderPath = issueFolderPath.replace(/\\/g, "/");
295+
const filePathToCreate = `${normalizedFolderPath}/${fileName}`;
296+
297+
try {
298+
await this.app.vault.create(filePathToCreate, content);
299+
this.noticeManager.debug(`Created issue file for ${issue.number}`);
300+
} catch (fileCreateError: unknown) {
301+
const errorMsg = fileCreateError instanceof Error ? fileCreateError.message : String(fileCreateError);
302+
303+
// Check if file exists due to stale cache
304+
const fileCheck = this.app.vault.getAbstractFileByPath(filePathToCreate);
305+
306+
if (fileCheck instanceof TFile) {
307+
// File exists but wasn't detected before - update it
308+
const existingContent = await this.app.vault.read(fileCheck);
309+
await this.app.vault.modify(fileCheck, content);
310+
this.noticeManager.debug(`Updated existing issue file for ${issue.number} (file existed but cache was stale)`);
311+
return;
312+
}
313+
314+
// File creation genuinely failed - rethrow
315+
throw fileCreateError;
316+
}
296317
}
297318
}
298319

src/pr-file-manager.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ export class PullRequestFileManager {
113113
);
114114
}
115115

116+
// Normalize folder path to use forward slashes for consistent vault lookups
117+
const normalizedPullRequestFolderPath = pullRequestFolderPath.replace(/\\/g, "/");
116118
const file = this.app.vault.getAbstractFileByPath(
117-
`${pullRequestFolderPath}/${fileName}`,
119+
`${normalizedPullRequestFolderPath}/${fileName}`,
118120
);
119121

120122
const [owner, repoName] = repo.repository.split("/");
@@ -246,11 +248,30 @@ export class PullRequestFileManager {
246248
}
247249
}
248250
} else {
249-
await this.app.vault.create(
250-
`${pullRequestFolderPath}/${fileName}`,
251-
content,
252-
);
253-
this.noticeManager.debug(`Created PR file for ${pr.number}`);
251+
// Normalize path to use forward slashes consistently
252+
const normalizedFolderPath = pullRequestFolderPath.replace(/\\/g, "/");
253+
const filePathToCreate = `${normalizedFolderPath}/${fileName}`;
254+
255+
try {
256+
await this.app.vault.create(filePathToCreate, content);
257+
this.noticeManager.debug(`Created PR file for ${pr.number}`);
258+
} catch (fileCreateError: unknown) {
259+
const errorMsg = fileCreateError instanceof Error ? fileCreateError.message : String(fileCreateError);
260+
261+
// Check if file exists due to stale cache
262+
const fileCheck = this.app.vault.getAbstractFileByPath(filePathToCreate);
263+
264+
if (fileCheck instanceof TFile) {
265+
// File exists but wasn't detected before - update it
266+
const existingContent = await this.app.vault.read(fileCheck);
267+
await this.app.vault.modify(fileCheck, content);
268+
this.noticeManager.debug(`Updated existing PR file for ${pr.number} (file existed but cache was stale)`);
269+
return;
270+
}
271+
272+
// File creation genuinely failed - rethrow
273+
throw fileCreateError;
274+
}
254275
}
255276
}
256277

src/util/file-helpers.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, TFile } from "obsidian";
1+
import { App, TFile, TFolder } from "obsidian";
22
import { format } from "date-fns";
33
import { escapeBody } from "./escapeUtils";
44
import { NoticeManager } from "../notice-manager";
@@ -50,19 +50,54 @@ export class FileHelpers {
5050
return;
5151
}
5252

53-
const folder = this.app.vault.getAbstractFileByPath(path);
54-
if (!folder) {
55-
try {
56-
await this.app.vault.createFolder(path);
57-
this.noticeManager.debug(`Created folder: ${path}`);
58-
} catch (error) {
59-
// Folder may have been created concurrently or vault cache was stale - verify it exists now
60-
const existsNow = this.app.vault.getAbstractFileByPath(path);
61-
if (!existsNow) {
62-
// Folder truly doesn't exist and creation failed - rethrow
63-
throw error;
53+
// Normalize path separators to forward slashes for consistency
54+
const normalizedPath = path.replace(/\\/g, "/");
55+
let existing = this.app.vault.getAbstractFileByPath(normalizedPath);
56+
57+
// Check if folder already exists
58+
if (existing instanceof TFolder) {
59+
return;
60+
}
61+
62+
63+
64+
try {
65+
await this.app.vault.createFolder(normalizedPath);
66+
this.noticeManager.debug(`Created folder: ${normalizedPath}`);
67+
} catch (error: unknown) {
68+
const errorMsg = error instanceof Error ? error.message : String(error);
69+
70+
// Handle "Folder already exists" or other folder creation errors
71+
// Retry vault check with slight delay to allow cache to update
72+
const existsNow = this.app.vault.getAbstractFileByPath(normalizedPath);
73+
74+
if (existsNow instanceof TFolder) {
75+
// Folder exists now, which is fine (concurrent creation or cache stale)
76+
return;
77+
}
78+
79+
if (
80+
error instanceof Error &&
81+
error.message.includes("Folder already exists")
82+
) {
83+
// Expected case - folder was created successfully but Obsidian threw error anyway
84+
// This commonly happens when the vault cache is out of sync
85+
// Try one more time with a tiny delay to let cache update
86+
await new Promise(resolve => setTimeout(resolve, 10));
87+
const retryCheck = this.app.vault.getAbstractFileByPath(normalizedPath);
88+
if (retryCheck instanceof TFolder) {
89+
this.noticeManager.debug(
90+
`Folder created successfully: ${normalizedPath}`,
91+
);
92+
return;
6493
}
94+
// Even though cache says it doesn't exist, the folder was likely created
95+
// Continue anyway - subsequent operations will work since the folder actually exists
96+
return;
6597
}
98+
99+
// Folder creation genuinely failed - rethrow
100+
throw error;
66101
}
67102
}
68103

src/util/templateUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ function buildReplacements(
217217
.map((a) => `- ${a}`)
218218
.join("\n");
219219
replacements["{assignees_yaml}"] =
220-
`[${data.assignees.map((a) => `'${a}'`).join(", ")}]`;
220+
`[${data.assignees.map((a) => `"${escapeYamlString(a)}"`).join(", ")}]`;
221221
} else {
222222
replacements["{assignees}"] = "";
223223
replacements["{assignees_list}"] = "";
@@ -233,7 +233,7 @@ function buildReplacements(
233233
.map((l) => `#${l.replace(/\s/g, "_")}`)
234234
.join(" ");
235235
replacements["{labels_yaml}"] =
236-
`[${data.labels.map((l) => `'${l}'`).join(", ")}]`;
236+
`[${data.labels.map((l) => `"${escapeYamlString(l)}"`).join(", ")}]`;
237237
} else {
238238
replacements["{labels}"] = "";
239239
replacements["{labels_list}"] = "";
@@ -269,7 +269,7 @@ function buildReplacements(
269269
.map((p) => p.projectTitle)
270270
.join(", ");
271271
replacements["{projects_yaml}"] =
272-
`[${data.projectData.map((p) => `'${p.projectTitle}'`).join(", ")}]`;
272+
`[${data.projectData.map((p) => `"${escapeYamlString(p.projectTitle)}"`).join(", ")}]`;
273273

274274
const customFieldsYaml = Object.entries(firstProject.customFields)
275275
.map(([name, field]) => ` ${name}: "${field.value}"`)

0 commit comments

Comments
 (0)