obsidian-launcher 2.1.1 → 2.1.2

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.
package/README.md CHANGED
@@ -7,7 +7,7 @@ The primary use case for this package is to allow [wdio-obsidian-service](../wdi
7
7
  ## Example Usage
8
8
  You can run the CLI via `npx`, e.g.:
9
9
  ```bash
10
- npx obsidian-launcher watch --version 1.8.10 --copy -p . test/vault
10
+ npx obsidian-launcher watch --version 1.8.10 --copy --plugin . test/vault
11
11
  ```
12
12
  This will download and launch Obsidian 1.8.10 with a sandboxed configuration directory so you don't need to worry about it interfering with your system Obsidian installation. You can even launch multiple different versions of Obsidian side-by-side. See [below](#cli) for CLI docs.
13
13
 
@@ -32,7 +32,7 @@ Obsidian Desktop is distributed in two parts, the "installer" which is the execu
32
32
  `appVersion` can be set to one of:
33
33
  - a specific version string like "1.7.7"
34
34
  - "latest": run the latest non-beta Obsidian version
35
- - "latest-beta": run the latest beta Obsidian version (or latest is there is no current beta)
35
+ - "latest-beta": run the latest beta Obsidian version (or latest if there is no current beta)
36
36
  - To download Obsidian beta versions you'll need to have an Obsidian Insiders account
37
37
  - "earliest": run the `minAppVersion` set in your plugin's `manifest.json`
38
38
 
@@ -61,7 +61,7 @@ Several commands can take a list of plugins and themes to install. You can speci
61
61
  - `id:<community-id>`: For plugins, id of a community plugin, e.g. `id:templater-obsidian`
62
62
  - `name:<community-name>`: For themes, name of a community theme, e.g. `name:Minimal`
63
63
 
64
- You can install a specific version of a plugin or theme with `-p id:myplugin=1.2.3`.
64
+ You can install a specific version of a plugin or theme with `-p id:myplugin@1.2.3`.
65
65
 
66
66
  ### launch
67
67
  Download and launch Obsidian, opening the specified vault.
@@ -7,38 +7,50 @@ import { PromisePool } from "@supercharge/promise-pool";
7
7
  import _ from "lodash";
8
8
  async function fileExists(path6) {
9
9
  try {
10
- await fsAsync.access(path6);
10
+ await fsAsync.stat(path6);
11
11
  return true;
12
- } catch {
13
- return false;
12
+ } catch (e) {
13
+ if (e?.code == "ENOENT") {
14
+ return false;
15
+ }
16
+ throw e;
14
17
  }
15
18
  }
16
19
  async function makeTmpDir(prefix) {
17
20
  return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? "tmp-"));
18
21
  }
19
22
  async function atomicCreate(dest, func, opts = {}) {
23
+ const { replace = true, preserveTmpDir = false } = opts;
20
24
  dest = path.resolve(dest);
21
- const createdParentDir = await fsAsync.mkdir(path.dirname(dest), { recursive: true });
22
- const tmpDir = await fsAsync.mkdtemp(path.join(path.dirname(dest), `.${path.basename(dest)}.tmp.`));
23
- let success = false;
25
+ const parentDir = path.dirname(dest);
26
+ if (!replace && await fileExists(dest)) return;
27
+ await fsAsync.mkdir(parentDir, { recursive: true });
28
+ const scratch = await fsAsync.mkdtemp(path.join(parentDir, `.${path.basename(dest)}.tmp.`));
24
29
  try {
25
- let result = await func(tmpDir) ?? tmpDir;
26
- result = path.resolve(tmpDir, result);
27
- if (!result.startsWith(tmpDir)) {
28
- throw new Error(`Returned path ${result} not under tmpDir`);
30
+ let result = await func(scratch) ?? scratch;
31
+ result = path.resolve(scratch, result);
32
+ if (!result.startsWith(scratch)) {
33
+ throw new Error(`Returned path ${result} not under scratch`);
29
34
  }
30
- if (await fileExists(dest) && (await fsAsync.stat(dest)).isDirectory()) {
31
- await fsAsync.rename(dest, tmpDir + ".old");
35
+ if (replace) {
36
+ if ((await fsAsync.stat(dest).catch(() => null))?.isDirectory()) {
37
+ await fsAsync.rename(dest, `${scratch}.old`);
38
+ }
39
+ await fsAsync.rename(result, dest);
40
+ } else {
41
+ if (!await fileExists(dest)) {
42
+ await fsAsync.rename(result, dest).catch((e) => {
43
+ if (e?.code != "ENOTEMPTY") throw e;
44
+ });
45
+ }
32
46
  }
33
- await fsAsync.rename(result, dest);
34
- success = true;
35
- } finally {
36
- if (success) {
37
- await fsAsync.rm(tmpDir + ".old", { recursive: true, force: true });
38
- await fsAsync.rm(tmpDir, { recursive: true, force: true });
39
- } else if (!opts.preserveTmpDir) {
40
- await fsAsync.rm(createdParentDir ?? tmpDir, { recursive: true, force: true });
47
+ await fsAsync.rm(scratch, { recursive: true, force: true });
48
+ await fsAsync.rm(`${scratch}.old`, { recursive: true, force: true });
49
+ } catch (e) {
50
+ if (!preserveTmpDir) {
51
+ await fsAsync.rm(scratch, { recursive: true, force: true });
41
52
  }
53
+ throw e;
42
54
  }
43
55
  }
44
56
  async function linkOrCp(src, dest) {
@@ -170,32 +182,37 @@ async function fetchGitHubAPIPaginated(url, params = {}) {
170
182
  }
171
183
  async function obsidianApiLogin(opts) {
172
184
  const { interactive = false, savePath } = opts;
173
- if (savePath) dotenv.config({ path: [savePath], quiet: true });
174
- let email = env.OBSIDIAN_EMAIL;
175
- let password = env.OBSIDIAN_PASSWORD;
185
+ const cached = savePath ? dotenv.parse(await fsAsync2.readFile(savePath).catch(() => "")) : {};
186
+ let email = env.OBSIDIAN_EMAIL ?? cached.OBSIDIAN_EMAIL;
187
+ let password = env.OBSIDIAN_PASSWORD ?? cached.OBSIDIAN_PASSWORD;
188
+ let promptedCredentials = false;
176
189
  if (!email || !password) {
177
190
  if (interactive) {
178
191
  console.log("Obsidian Insiders account is required to download Obsidian beta versions.");
179
192
  email = email || readlineSync.question("Obsidian email: ");
180
193
  password = password || readlineSync.question("Obsidian password: ", { hideEchoBack: true });
194
+ promptedCredentials = true;
181
195
  } else {
182
196
  throw Error(
183
197
  "Obsidian Insiders account is required to download Obsidian beta versions. Either set the OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD env vars (.env file is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download app -v <version>`"
184
198
  );
185
199
  }
186
200
  }
201
+ function parseSignin(r) {
202
+ return { token: r.token ? "token" : void 0, error: r.error?.toString(), license: r.license };
203
+ }
187
204
  let needsMfa = false;
188
205
  let retries = 0;
189
206
  let signin = void 0;
190
207
  while (!signin?.token && retries < 3) {
191
208
  if (retries > 0 || env.CI) {
192
- await sleep(2 * Math.random() + retries * retries * 2);
209
+ await sleep(2 * Math.random() + retries * retries * 3);
193
210
  }
194
211
  let mfa = "";
195
212
  if (needsMfa && interactive) {
196
213
  mfa = readlineSync.question("Obsidian 2FA: ");
197
214
  }
198
- signin = await fetch("https://api.obsidian.md/user/signin", {
215
+ const response = await fetch("https://api.obsidian.md/user/signin", {
199
216
  method: "post",
200
217
  headers: {
201
218
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36",
@@ -204,6 +221,7 @@ async function obsidianApiLogin(opts) {
204
221
  },
205
222
  body: JSON.stringify({ email, password, mfa })
206
223
  }).then((r) => r.json());
224
+ signin = parseSignin(response);
207
225
  const error = signin.error?.toLowerCase();
208
226
  if (error?.includes("2fa") && !needsMfa) {
209
227
  needsMfa = true;
@@ -214,6 +232,7 @@ async function obsidianApiLogin(opts) {
214
232
  }
215
233
  } else if (["please wait", "try again"].some((m) => error?.includes(m))) {
216
234
  console.warn(`Obsidian login failed: ${signin.error}`);
235
+ console.warn("Retrying obsidian login...");
217
236
  retries++;
218
237
  } else if (!signin.token) {
219
238
  throw Error(`Obsidian login failed: ${signin.error ?? "unknown error"}`);
@@ -224,7 +243,7 @@ async function obsidianApiLogin(opts) {
224
243
  } else if (!signin?.license) {
225
244
  throw Error("Obsidian Insiders account is required to download Obsidian beta versions");
226
245
  }
227
- if (interactive && savePath && (!env.OBSIDIAN_EMAIL || !env.OBSIDIAN_PASSWORD)) {
246
+ if (savePath && promptedCredentials) {
228
247
  const save = readlineSync.question("Cache credentails to disk? [y/n]: ");
229
248
  if (["y", "yes"].includes(save.toLowerCase())) {
230
249
  await fsAsync2.writeFile(
@@ -239,6 +258,9 @@ OBSIDIAN_PASSWORD='${password}'
239
258
  return signin.token;
240
259
  }
241
260
  async function fetchObsidianApi(url, opts) {
261
+ if (!opts.token) {
262
+ throw Error("Obsidian credentials required to download Obsidian beta release");
263
+ }
242
264
  url = createURL(url, "https://releases.obsidian.md");
243
265
  const response = await fetch(url, {
244
266
  headers: {
@@ -370,16 +392,16 @@ ${stderr}`);
370
392
  return result;
371
393
  }
372
394
  async function extractObsidianAppImage(appImage, dest) {
373
- await atomicCreate(dest, async (tmpDir) => {
374
- await sevenZ(["x", "-o.", path4.relative(tmpDir, appImage)], { cwd: tmpDir });
375
- return tmpDir;
395
+ await atomicCreate(dest, async (scratch) => {
396
+ await sevenZ(["x", "-o.", path4.relative(scratch, appImage)], { cwd: scratch });
397
+ return scratch;
376
398
  });
377
399
  }
378
400
  async function extractObsidianTar(tar, dest) {
379
- await atomicCreate(dest, async (tmpDir) => {
380
- await extractGz(tar, path4.join(tmpDir, "inflated.tar"));
381
- await sevenZ(["x", "-o.", "inflated.tar"], { cwd: tmpDir });
382
- return (await fsAsync3.readdir(tmpDir)).find((p) => p.match("obsidian-"));
401
+ await atomicCreate(dest, async (scratch) => {
402
+ await extractGz(tar, path4.join(scratch, "inflated.tar"));
403
+ await sevenZ(["x", "-o.", "inflated.tar"], { cwd: scratch });
404
+ return (await fsAsync3.readdir(scratch)).find((p) => p.match("obsidian-"));
383
405
  });
384
406
  }
385
407
  async function extractObsidianExe(exe, arch, dest) {
@@ -393,17 +415,17 @@ async function extractObsidianExe(exe, arch, dest) {
393
415
  } else {
394
416
  throw Error(`No Obsidian installer found for ${process.platform} ${process.arch}`);
395
417
  }
396
- await atomicCreate(dest, async (tmpDir) => {
397
- await sevenZ(["x", "-oinstaller", path4.relative(tmpDir, exe), subArchive], { cwd: tmpDir });
398
- await sevenZ(["x", "-oobsidian", path4.join("installer", subArchive)], { cwd: tmpDir });
418
+ await atomicCreate(dest, async (scratch) => {
419
+ await sevenZ(["x", "-oinstaller", path4.relative(scratch, exe), subArchive], { cwd: scratch });
420
+ await sevenZ(["x", "-oobsidian", path4.join("installer", subArchive)], { cwd: scratch });
399
421
  return "obsidian";
400
422
  });
401
423
  }
402
424
  async function extractObsidianDmg(dmg, dest) {
403
425
  dest = path4.resolve(dest);
404
- await atomicCreate(dest, async (tmpDir) => {
405
- await sevenZ(["x", "-o.", path4.relative(tmpDir, dmg), "*/Obsidian.app", "Obsidian.app"], { cwd: tmpDir });
406
- const files = await fsAsync3.readdir(tmpDir);
426
+ await atomicCreate(dest, async (scratch) => {
427
+ await sevenZ(["x", "-o.", path4.relative(scratch, dmg), "*/Obsidian.app", "Obsidian.app"], { cwd: scratch });
428
+ const files = await fsAsync3.readdir(scratch);
407
429
  if (files.includes("Obsidian.app")) {
408
430
  return "Obsidian.app";
409
431
  } else {
@@ -546,7 +568,11 @@ function updateObsidianVersionList(args) {
546
568
  minInstallerVersion = version;
547
569
  }
548
570
  }
549
- newVersions[version] = _3.merge({ minInstallerVersion, maxInstallerVersion }, newVersions[version]);
571
+ newVersions[version] = _3.merge(newVersions[version], {
572
+ minInstallerVersion: newVersions[version]?.minInstallerVersion ?? minInstallerVersion,
573
+ maxInstallerVersion
574
+ // override maxInstallerVersion if it was already set
575
+ });
550
576
  }
551
577
  for (const installerInfo of installerInfos) {
552
578
  newVersions[installerInfo.version] = _3.merge(newVersions[installerInfo.version] ?? {}, {
@@ -705,10 +731,10 @@ var ObsidianLauncher = class {
705
731
  return d;
706
732
  }));
707
733
  if (response.success) {
708
- await atomicCreate(dest, async (tmpDir) => {
709
- await fsAsync4.writeFile(path5.join(tmpDir, "download.json"), response.result);
710
- return path5.join(tmpDir, "download.json");
711
- });
734
+ await atomicCreate(dest, async (scratch) => {
735
+ await fsAsync4.writeFile(path5.join(scratch, "download.json"), response.result);
736
+ return path5.join(scratch, "download.json");
737
+ }, { replace: true });
712
738
  data = JSON.parse(response.result);
713
739
  } else {
714
740
  error = response.error;
@@ -775,7 +801,7 @@ var ObsidianLauncher = class {
775
801
  * Resolves Obsidian app and installer version strings to absolute versions.
776
802
  * @param appVersion specific version or one of
777
803
  * - "latest": Get the current latest non-beta Obsidian version
778
- * - "latest-beta": Get the current latest beta Obsidian version (or latest is there is no current beta)
804
+ * - "latest-beta": Get the current latest beta Obsidian version (or latest if there is no current beta)
779
805
  * - "earliest": Get the `minAppVersion` set in your `manifest.json`
780
806
  * @param installerVersion specific version or one of
781
807
  * - "latest": Get the latest Obsidian installer compatible with `appVersion`
@@ -909,31 +935,29 @@ var ObsidianLauncher = class {
909
935
  const versionInfo = await this.getVersionInfo(installerVersion);
910
936
  installerVersion = versionInfo.version;
911
937
  const installerInfo = await this.getInstallerInfo(installerVersion, { platform, arch });
912
- const cacheDir = path5.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);
938
+ const installerDir = path5.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);
913
939
  let binaryPath;
914
940
  let extractor;
915
941
  if (platform == "linux") {
916
- binaryPath = path5.join(cacheDir, "obsidian");
942
+ binaryPath = path5.join(installerDir, "obsidian");
917
943
  extractor = (installer, dest) => extractObsidianAppImage(installer, dest);
918
944
  } else if (platform == "win32") {
919
- binaryPath = path5.join(cacheDir, "Obsidian.exe");
945
+ binaryPath = path5.join(installerDir, "Obsidian.exe");
920
946
  extractor = (installer, dest) => extractObsidianExe(installer, arch, dest);
921
947
  } else if (platform == "darwin") {
922
- binaryPath = path5.join(cacheDir, "Contents/MacOS/Obsidian");
948
+ binaryPath = path5.join(installerDir, "Contents/MacOS/Obsidian");
923
949
  extractor = (installer, dest) => extractObsidianDmg(installer, dest);
924
950
  } else {
925
951
  throw Error(`Unsupported platform ${platform}`);
926
952
  }
927
- if (!await fileExists(binaryPath)) {
953
+ await atomicCreate(installerDir, async (scratch) => {
928
954
  console.log(`Downloading Obsidian installer v${installerVersion}...`);
929
- await atomicCreate(cacheDir, async (tmpDir) => {
930
- const installer = path5.join(tmpDir, "installer");
931
- await downloadResponse(await fetch(installerInfo.url), installer);
932
- const extracted = path5.join(tmpDir, "extracted");
933
- await extractor(installer, extracted);
934
- return extracted;
935
- });
936
- }
955
+ const installer = path5.join(scratch, "installer");
956
+ await downloadResponse(await fetch(installerInfo.url), installer);
957
+ const extracted = path5.join(scratch, "extracted");
958
+ await extractor(installer, extracted);
959
+ return extracted;
960
+ }, { replace: false });
937
961
  return binaryPath;
938
962
  }
939
963
  /**
@@ -952,29 +976,27 @@ var ObsidianLauncher = class {
952
976
  throw Error(`No asar found for Obsidian version ${appVersion}`);
953
977
  }
954
978
  const appPath = path5.join(this.cacheDir, "obsidian-app", `obsidian-${versionInfo.version}.asar`);
955
- if (!await fileExists(appPath)) {
956
- console.log(`Downloading Obsidian app v${versionInfo.version} ...`);
957
- await atomicCreate(appPath, async (tmpDir) => {
958
- const isInsiders = new URL(appUrl).hostname.endsWith(".obsidian.md");
959
- let response;
960
- if (isInsiders) {
961
- if (!this.obsidianApiToken) {
962
- this.obsidianApiToken = await obsidianApiLogin({
963
- interactive: this.interactive,
964
- savePath: path5.join(this.cacheDir, "obsidian-credentials.env")
965
- });
966
- }
967
- response = await fetchObsidianApi(appUrl, { token: this.obsidianApiToken });
968
- } else {
969
- response = await fetch(appUrl);
970
- }
971
- const archive = path5.join(tmpDir, "app.asar.gz");
972
- const asar = path5.join(tmpDir, "app.asar");
973
- await downloadResponse(response, archive);
974
- await extractGz(archive, asar);
975
- return asar;
979
+ const isInsiders = new URL(appUrl).hostname.endsWith(".obsidian.md");
980
+ if (isInsiders && !this.obsidianApiToken && !await fileExists(appPath)) {
981
+ this.obsidianApiToken = await obsidianApiLogin({
982
+ interactive: this.interactive,
983
+ savePath: path5.join(this.cacheDir, "obsidian-credentials.env")
976
984
  });
977
985
  }
986
+ await atomicCreate(appPath, async (scratch) => {
987
+ console.log(`Downloading Obsidian app v${versionInfo.version} ...`);
988
+ let response;
989
+ if (isInsiders) {
990
+ response = await fetchObsidianApi(appUrl, { token: this.obsidianApiToken });
991
+ } else {
992
+ response = await fetch(appUrl);
993
+ }
994
+ const archive = path5.join(scratch, "app.asar.gz");
995
+ const asar = path5.join(scratch, "app.asar");
996
+ await downloadResponse(response, archive);
997
+ await extractGz(archive, asar);
998
+ return asar;
999
+ }, { replace: false });
978
1000
  return appPath;
979
1001
  }
980
1002
  /**
@@ -991,26 +1013,24 @@ var ObsidianLauncher = class {
991
1013
  async downloadChromedriver(installerVersion, opts = {}) {
992
1014
  const { platform, arch } = _4.defaults({}, opts, currentPlatform);
993
1015
  const installerInfo = await this.getInstallerInfo(installerVersion, { platform, arch });
994
- const cacheDir = path5.join(this.cacheDir, `electron-chromedriver/${platform}-${arch}/${installerInfo.electron}`);
1016
+ const chromedriverDir = path5.join(this.cacheDir, `electron-chromedriver/${platform}-${arch}/${installerInfo.electron}`);
995
1017
  let chromedriverPath;
996
1018
  if (process.platform == "win32") {
997
- chromedriverPath = path5.join(cacheDir, `chromedriver.exe`);
1019
+ chromedriverPath = path5.join(chromedriverDir, `chromedriver.exe`);
998
1020
  } else {
999
- chromedriverPath = path5.join(cacheDir, `chromedriver`);
1021
+ chromedriverPath = path5.join(chromedriverDir, `chromedriver`);
1000
1022
  }
1001
- if (!await fileExists(chromedriverPath)) {
1023
+ await atomicCreate(chromedriverDir, async (scratch) => {
1002
1024
  console.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
1003
- await atomicCreate(cacheDir, async (tmpDir) => {
1004
- const chromedriverZipPath = await downloadArtifact({
1005
- version: installerInfo.electron,
1006
- artifactName: "chromedriver",
1007
- cacheRoot: path5.join(tmpDir, "download")
1008
- });
1009
- const extracted = path5.join(tmpDir, "extracted");
1010
- await extractZip(chromedriverZipPath, { dir: extracted });
1011
- return extracted;
1025
+ const chromedriverZipPath = await downloadArtifact({
1026
+ version: installerInfo.electron,
1027
+ artifactName: "chromedriver",
1028
+ cacheRoot: path5.join(scratch, "download")
1012
1029
  });
1013
- }
1030
+ const extracted = path5.join(scratch, "extracted");
1031
+ await extractZip(chromedriverZipPath, { dir: extracted });
1032
+ return extracted;
1033
+ }, { replace: false });
1014
1034
  return chromedriverPath;
1015
1035
  }
1016
1036
  /**
@@ -1025,14 +1045,12 @@ var ObsidianLauncher = class {
1025
1045
  );
1026
1046
  }
1027
1047
  const apkPath = path5.join(this.cacheDir, "obsidian-apk", `obsidian-${versionInfo.version}.apk`);
1028
- if (!await fileExists(apkPath)) {
1048
+ await atomicCreate(apkPath, async (scratch) => {
1029
1049
  console.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
1030
- await atomicCreate(apkPath, async (tmpDir) => {
1031
- const dest = path5.join(tmpDir, "obsidian.apk");
1032
- await downloadResponse(await fetch(apkUrl), dest);
1033
- return dest;
1034
- });
1035
- }
1050
+ const dest = path5.join(scratch, "obsidian.apk");
1051
+ await downloadResponse(await fetch(apkUrl), dest);
1052
+ return dest;
1053
+ }, { replace: false });
1036
1054
  return apkPath;
1037
1055
  }
1038
1056
  /** Gets the latest version of a plugin. */
@@ -1059,23 +1077,21 @@ var ObsidianLauncher = class {
1059
1077
  }
1060
1078
  version = semver2.valid(version);
1061
1079
  const pluginDir = path5.join(this.cacheDir, "obsidian-plugins", repo, version);
1062
- if (!await fileExists(pluginDir)) {
1063
- await atomicCreate(pluginDir, async (tmpDir) => {
1064
- const assetsToDownload = { "manifest.json": true, "main.js": true, "styles.css": false };
1065
- await Promise.all(
1066
- Object.entries(assetsToDownload).map(async ([file, required]) => {
1067
- const url = `https://github.com/${repo}/releases/download/${version}/${file}`;
1068
- const response = await fetch(url);
1069
- if (response.ok) {
1070
- await downloadResponse(response, path5.join(tmpDir, file));
1071
- } else if (required) {
1072
- throw Error(`No ${file} found for ${repo} version ${version}`);
1073
- }
1074
- })
1075
- );
1076
- return tmpDir;
1077
- });
1078
- }
1080
+ await atomicCreate(pluginDir, async (scratch) => {
1081
+ const assetsToDownload = { "manifest.json": true, "main.js": true, "styles.css": false };
1082
+ await Promise.all(
1083
+ Object.entries(assetsToDownload).map(async ([file, required]) => {
1084
+ const url = `https://github.com/${repo}/releases/download/${version}/${file}`;
1085
+ const response = await fetch(url);
1086
+ if (response.ok) {
1087
+ await downloadResponse(response, path5.join(scratch, file));
1088
+ } else if (required) {
1089
+ throw Error(`No ${file} found for ${repo} version ${version}`);
1090
+ }
1091
+ })
1092
+ );
1093
+ return scratch;
1094
+ }, { replace: false });
1079
1095
  return pluginDir;
1080
1096
  }
1081
1097
  /**
@@ -1227,31 +1243,29 @@ var ObsidianLauncher = class {
1227
1243
  }
1228
1244
  version = semver2.valid(version);
1229
1245
  const themeDir = path5.join(this.cacheDir, "obsidian-themes", repo, version);
1230
- if (!await fileExists(themeDir)) {
1231
- await atomicCreate(themeDir, async (tmpDir) => {
1232
- const assetsToDownload = ["manifest.json", "theme.css"];
1233
- let baseUrl = `https://github.com/${repo}/releases/download/${version}`;
1234
- if (!(await fetch(`${baseUrl}/manifest.json`)).ok) {
1235
- if (version != latest) {
1236
- throw Error(`No theme version "${version}" found`);
1237
- }
1238
- baseUrl = `https://raw.githubusercontent.com/${repo}/HEAD`;
1246
+ await atomicCreate(themeDir, async (scratch) => {
1247
+ const assetsToDownload = ["manifest.json", "theme.css"];
1248
+ let baseUrl = `https://github.com/${repo}/releases/download/${version}`;
1249
+ if (!(await fetch(`${baseUrl}/manifest.json`)).ok) {
1250
+ if (version != latest) {
1251
+ throw Error(`No theme version "${version}" found`);
1239
1252
  }
1240
- await Promise.all(
1241
- assetsToDownload.map(
1242
- async (file) => {
1243
- const url = `${baseUrl}/${file}`;
1244
- const response = await fetch(url);
1245
- if (response.ok) {
1246
- await downloadResponse(response, path5.join(tmpDir, file));
1247
- } else {
1248
- throw Error(`No ${file} found for ${repo}`);
1249
- }
1253
+ baseUrl = `https://raw.githubusercontent.com/${repo}/HEAD`;
1254
+ }
1255
+ await Promise.all(
1256
+ assetsToDownload.map(
1257
+ async (file) => {
1258
+ const url = `${baseUrl}/${file}`;
1259
+ const response = await fetch(url);
1260
+ if (response.ok) {
1261
+ await downloadResponse(response, path5.join(scratch, file));
1262
+ } else {
1263
+ throw Error(`No ${file} found for ${repo}`);
1250
1264
  }
1251
- )
1252
- );
1253
- });
1254
- }
1265
+ }
1266
+ )
1267
+ );
1268
+ }, { replace: false });
1255
1269
  return themeDir;
1256
1270
  }
1257
1271
  /**
@@ -1569,4 +1583,4 @@ export {
1569
1583
  watchFiles,
1570
1584
  ObsidianLauncher
1571
1585
  };
1572
- //# sourceMappingURL=chunk-RDGKX2LW.js.map
1586
+ //# sourceMappingURL=chunk-HMKNUWAY.js.map