obsidian-launcher 2.1.6 → 2.2.0

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.
@@ -3,8 +3,16 @@ import fsAsync from "fs/promises";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
5
  import os from "os";
6
+ import { createConsola } from "consola";
6
7
  import { PromisePool } from "@supercharge/promise-pool";
7
8
  import _ from "lodash";
9
+ var consola = createConsola({
10
+ throttle: -1,
11
+ // disable throttle
12
+ formatOptions: {
13
+ date: false
14
+ }
15
+ });
8
16
  async function fileExists(path6) {
9
17
  try {
10
18
  await fsAsync.stat(path6);
@@ -19,6 +27,14 @@ async function fileExists(path6) {
19
27
  async function makeTmpDir(prefix) {
20
28
  return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? "tmp-"));
21
29
  }
30
+ var logged = /* @__PURE__ */ new Map();
31
+ function warnOnce(key, message) {
32
+ const times = logged.get(key) ?? 0;
33
+ if (times <= 0) {
34
+ consola.warn({ message });
35
+ }
36
+ logged.set(key, times + 1);
37
+ }
22
38
  async function atomicCreate(dest, func, opts = {}) {
23
39
  const { replace = true, preserveTmpDir = false } = opts;
24
40
  dest = path.resolve(dest);
@@ -64,6 +80,14 @@ async function linkOrCp(src, dest) {
64
80
  async function sleep(ms) {
65
81
  return new Promise((resolve) => setTimeout(resolve, ms));
66
82
  }
83
+ async function withTimeout(promise, timeout) {
84
+ let timer;
85
+ const result = Promise.race([
86
+ promise,
87
+ new Promise((resolve, reject) => timer = setTimeout(() => reject(Error("Promise timed out")), timeout))
88
+ ]);
89
+ return result.finally(() => clearTimeout(timer));
90
+ }
67
91
  async function pool(size, items, func) {
68
92
  const { results } = await PromisePool.for(items).withConcurrency(size).handleError(async (error) => {
69
93
  throw error;
@@ -73,6 +97,47 @@ async function pool(size, items, func) {
73
97
  async function maybe(promise) {
74
98
  return promise.then((r) => ({ success: true, result: r, error: void 0 })).catch((e) => ({ success: false, result: void 0, error: e }));
75
99
  }
100
+ async function until(func, opts) {
101
+ const { timeout, poll = 100 } = opts;
102
+ let time = 0;
103
+ let result;
104
+ let error;
105
+ while (!result && time < timeout) {
106
+ try {
107
+ result = await func();
108
+ error = void 0;
109
+ } catch (e) {
110
+ error = e;
111
+ }
112
+ if (!result) {
113
+ await sleep(poll);
114
+ }
115
+ time += poll;
116
+ }
117
+ if (!result) {
118
+ throw new Error("Timed out waiting for condition" + (error ? `: ${error}` : ""));
119
+ }
120
+ return result;
121
+ }
122
+ async function retry(func, opts = {}) {
123
+ const { retries = 4, backoff = 1e3, retryIf = () => true } = opts;
124
+ let attempt = 0;
125
+ let error;
126
+ while (attempt < retries) {
127
+ try {
128
+ return await func(attempt);
129
+ } catch (e) {
130
+ error = e;
131
+ }
132
+ const delay = backoff * Math.random() + backoff * Math.pow(2, attempt);
133
+ attempt += 1;
134
+ if (!retryIf(error) || attempt >= retries) {
135
+ throw error;
136
+ }
137
+ await sleep(delay);
138
+ }
139
+ throw error;
140
+ }
76
141
  function watchFiles(files, func, options) {
77
142
  const debouncedFunc = _.debounce((curr, prev) => {
78
143
  if (curr.mtimeMs > prev.mtimeMs || curr.mtimeMs == 0 && prev.mtimeMs != 0) {
@@ -186,15 +251,17 @@ async function obsidianApiLogin(opts) {
186
251
  let email = env.OBSIDIAN_EMAIL ?? cached.OBSIDIAN_EMAIL;
187
252
  let password = env.OBSIDIAN_PASSWORD ?? cached.OBSIDIAN_PASSWORD;
188
253
  let promptedCredentials = false;
254
+ const predownloadMessage = "pre-download the Obsidian beta with:\n npx obsidian-launcher download app -v <version>";
189
255
  if (!email || !password) {
190
256
  if (interactive) {
191
- console.log("Obsidian Insiders account is required to download Obsidian beta versions.");
257
+ consola.log("Obsidian Insiders account is required to download Obsidian beta versions.");
258
+ consola.log("You can set OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD environment variables (.env file is supported) to avoid this prompt.");
192
259
  email = email || readlineSync.question("Obsidian email: ");
193
260
  password = password || readlineSync.question("Obsidian password: ", { hideEchoBack: true });
194
261
  promptedCredentials = true;
195
262
  } else {
196
263
  throw Error(
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>`"
264
+ "Obsidian Insiders account is required to download Obsidian beta versions. Set the OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD environment variables (.env file is supported)" + (env.CI ? "." : ` or ${predownloadMessage}`)
198
265
  );
199
266
  }
200
267
  }
@@ -227,12 +294,12 @@ async function obsidianApiLogin(opts) {
227
294
  needsMfa = true;
228
295
  if (!interactive) {
229
296
  throw Error(
230
- "Can't login with 2FA in a non-interactive session. To download Obsidian beta versions, either disable 2FA on your account or pre-download the Obsidian beta with `npx obsidian-launcher download app -v <version>`"
297
+ "Can't login with 2FA in a non-interactive session. To download Obsidian beta versions disable 2FA on your account" + (env.CI ? "." : ` or ${predownloadMessage}`)
231
298
  );
232
299
  }
233
300
  } else if (["please wait", "try again"].some((m) => error?.includes(m))) {
234
- console.warn(`Obsidian login failed: ${signin.error}`);
235
- console.warn("Retrying obsidian login...");
301
+ consola.warn(`Obsidian login failed: ${signin.error}`);
302
+ consola.warn("Retrying obsidian login...");
236
303
  retries++;
237
304
  } else if (!signin.token) {
238
305
  throw Error(`Obsidian login failed: ${signin.error ?? "unknown error"}`);
@@ -252,7 +319,7 @@ async function obsidianApiLogin(opts) {
252
319
  OBSIDIAN_PASSWORD='${password}'
253
320
  `
254
321
  );
255
- console.log(`Saved Obsidian credentials to ${path2.relative(process.cwd(), savePath)}`);
322
+ consola.log(`Saved Obsidian credentials to ${path2.relative(process.cwd(), savePath)}`);
256
323
  }
257
324
  }
258
325
  return signin.token;
@@ -271,13 +338,21 @@ async function fetchObsidianApi(url, opts) {
271
338
  });
272
339
  return response;
273
340
  }
274
- async function downloadResponse(response, dest) {
275
- if (!response.ok) {
276
- throw Error(`${response.url} failed with ${response.status}`);
277
- }
278
- const fileStream = fs2.createWriteStream(dest, { flags: "w" });
279
- const fetchStream = Readable.fromWeb(response.body);
280
- await finished(fetchStream.pipe(fileStream));
341
+ async function downloadResponse(func, dest, opts = {}) {
342
+ await retry(async () => {
343
+ const response = await func();
344
+ if (!response.ok) {
345
+ const error = Error(`${response.url} failed with ${response.status}`);
346
+ error.status = response.status;
347
+ throw error;
348
+ }
349
+ const fileStream = fs2.createWriteStream(dest, { flags: "w" });
350
+ const fetchStream = Readable.fromWeb(response.body);
351
+ await finished(fetchStream.pipe(fileStream));
352
+ }, {
353
+ retryIf: (e) => !e.status || ![401, 403, 404].includes(e.status),
354
+ ...opts
355
+ });
281
356
  }
282
357
 
283
358
  // src/chromeLocalStorage.ts
@@ -366,7 +441,8 @@ import semver from "semver";
366
441
  import _3 from "lodash";
367
442
  import { pipeline } from "stream/promises";
368
443
  import zlib from "zlib";
369
- import { fileURLToPath } from "url";
444
+ import { fileURLToPath, pathToFileURL } from "url";
445
+ import CDP from "chrome-remote-interface";
370
446
  var execFile = promisify(child_process.execFile);
371
447
  function normalizeGitHubRepo(repo) {
372
448
  return repo.match(/^(https?:\/\/)?(github.com\/)?(.*?)\/?$/)?.[3] ?? repo;
@@ -447,61 +523,96 @@ async function extractObsidianDmg(dmg, dest) {
447
523
  }
448
524
  });
449
525
  }
450
- async function extractInstallerInfo(installerKey, url) {
451
- const installerName = url.split("/").at(-1);
452
- console.log(`Extrating installer info for ${installerName}...`);
453
- const tmpDir = await makeTmpDir("obsidian-launcher-");
454
- try {
455
- const installerPath = path4.join(tmpDir, url.split("/").at(-1));
456
- await downloadResponse(await fetch(url), installerPath);
457
- const exractedPath = path4.join(tmpDir, "Obsidian");
458
- let platforms = [];
459
- if (installerKey == "appImage" || installerKey == "appImageArm") {
460
- await extractObsidianAppImage(installerPath, exractedPath);
461
- platforms = ["linux-" + (installerKey == "appImage" ? "x64" : "arm64")];
462
- } else if (installerKey == "tar" || installerKey == "tarArm") {
463
- await extractObsidianTar(installerPath, exractedPath);
464
- platforms = ["linux-" + (installerKey == "tar" ? "x64" : "arm64")];
465
- } else if (installerKey == "exe") {
466
- await extractObsidianExe(installerPath, "x64", exractedPath);
467
- const { stdout } = await sevenZ(["l", "-ba", path4.relative(tmpDir, installerPath)], { cwd: tmpDir });
468
- const lines = stdout.trim().split("\n").map((l) => l.trim());
469
- const files = lines.map((l) => l.split(/\s+/).at(-1).replace(/\\/g, "/"));
470
- if (files.includes("$PLUGINSDIR/app-arm64.7z")) platforms.push("win32-arm64");
471
- if (files.includes("$PLUGINSDIR/app-32.7z")) platforms.push("win32-ia32");
472
- if (files.includes("$PLUGINSDIR/app-64.7z")) platforms.push("win32-x64");
473
- } else if (installerKey == "dmg") {
474
- await extractObsidianDmg(installerPath, exractedPath);
475
- platforms = ["darwin-arm64", "darwin-x64"];
476
- } else {
477
- throw new Error(`Unknown installer key ${installerKey}`);
526
+ async function getCdpSession(launcher, appVersion, installerVersion) {
527
+ [appVersion, installerVersion] = await launcher.resolveVersion(appVersion, installerVersion);
528
+ const cleanup = [];
529
+ const doCleanup = async () => {
530
+ for (const func of [...cleanup].reverse()) {
531
+ await func();
478
532
  }
479
- const matches = [];
480
- const installerFiles = await fsAsync3.readdir(exractedPath, { recursive: true, withFileTypes: true });
481
- for (const file of installerFiles) {
482
- if (file.isFile() && !file.name.endsWith(".asar")) {
483
- const stream = fs3.createReadStream(path4.join(file.parentPath, file.name), { encoding: "utf-8" });
484
- let prev = "";
485
- for await (let chunk of stream) {
486
- const regex = /Chrome\/\d+\.\d+\.\d+\.\d+|Electron\/\d+\.\d+\.\d+/g;
487
- chunk = prev + chunk;
488
- matches.push(...[...(prev + chunk).matchAll(regex)].map((m) => m[0]));
489
- prev = chunk.slice(-64);
533
+ };
534
+ const vault = await makeTmpDir("obsidian-vault-");
535
+ cleanup.push(() => fsAsync3.rm(vault, { recursive: true, force: true }));
536
+ const pluginDir = path4.join(vault, ".obsidian", "plugins", "obsidian-launcher");
537
+ await fsAsync3.mkdir(pluginDir, { recursive: true });
538
+ await fsAsync3.writeFile(path4.join(pluginDir, "manifest.json"), JSON.stringify({
539
+ id: "obsidian-launcher",
540
+ name: "Obsidian Launcher",
541
+ version: "1.0.0",
542
+ minAppVersion: "0.0.1",
543
+ description: "",
544
+ author: "obsidian-launcher",
545
+ isDesktopOnly: false
546
+ }));
547
+ await fsAsync3.writeFile(path4.join(pluginDir, "main.js"), `
548
+ const obsidian = require('obsidian');
549
+ class ObsidianLauncherPlugin extends obsidian.Plugin {
550
+ async onload() { window.obsidianLauncher = {app: this.app, obsidian: obsidian}; };
490
551
  }
552
+ module.exports = ObsidianLauncherPlugin;
553
+ `);
554
+ await fsAsync3.writeFile(path4.join(vault, ".obsidian", "community-plugins.json"), JSON.stringify([
555
+ "obsidian-launcher"
556
+ ]));
557
+ try {
558
+ const { proc, configDir } = await launcher.launch({
559
+ appVersion,
560
+ installerVersion,
561
+ vault,
562
+ copy: false,
563
+ args: [`--remote-debugging-port=0`, "--test-type=webdriver"]
564
+ // will choose a random available port
565
+ });
566
+ cleanup.push(() => fsAsync3.rm(configDir, { recursive: true, force: true }));
567
+ const procExit = new Promise((resolve) => proc.on("close", (code) => resolve(code ?? -1)));
568
+ cleanup.push(async () => {
569
+ proc.kill("SIGTERM");
570
+ const timeout = await maybe(withTimeout(procExit, 5 * 1e3));
571
+ if (!timeout.success) {
572
+ consola.warn(`Stuck process ${proc.pid}, using SIGKILL`);
573
+ proc.kill("SIGKILL");
491
574
  }
575
+ await procExit;
576
+ });
577
+ const portPromise = new Promise((resolve, reject) => {
578
+ void procExit.then(() => reject(Error("Processed ended without opening a port")));
579
+ proc.stderr.on("data", (data) => {
580
+ const port2 = data.toString().match(/ws:\/\/[\w.]+?:(\d+)/)?.[1];
581
+ if (port2) {
582
+ resolve(Number(port2));
583
+ }
584
+ });
585
+ });
586
+ const port = await maybe(withTimeout(portPromise, 20 * 1e3));
587
+ if (!port.success) {
588
+ throw new Error("Timed out waiting for Chrome DevTools protocol port");
492
589
  }
493
- const versionSortKey = (v) => v.split(".").map((s) => s.padStart(9, "0")).join(".");
494
- const versions = _3(matches).map((m) => m.split("/")).groupBy(0).mapValues((ms) => ms.map((m) => m[1])).mapValues((ms) => _3.sortBy(ms, versionSortKey).at(-1)).value();
495
- const electron = versions["Electron"];
496
- const chrome = versions["Chrome"];
497
- if (!electron || !chrome) {
498
- throw new Error(`Failed to extract Electron and Chrome versions from binary ${installerPath}`);
499
- }
500
- console.log(`Extracted installer info for ${installerName}`);
501
- return { electron, chrome, platforms };
502
- } finally {
503
- await fsAsync3.rm(tmpDir, { recursive: true, force: true });
590
+ const client = await CDP({ port: port.result });
591
+ cleanup.push(() => client.close());
592
+ const expr = semver.gte(appVersion, "0.12.8") ? "!!window.obsidianLauncher" : "!!window.app.workspace";
593
+ await until(
594
+ () => client.Runtime.evaluate({ expression: expr }).then((r) => r.result.value),
595
+ { timeout: 5e3 }
596
+ );
597
+ return {
598
+ client,
599
+ cleanup: doCleanup,
600
+ proc
601
+ };
602
+ } catch (e) {
603
+ await doCleanup();
604
+ throw e;
605
+ }
606
+ }
607
+ async function cdpEvaluate(client, expression) {
608
+ const response = await client.Runtime.evaluate({ expression, returnByValue: true });
609
+ if (response.exceptionDetails) {
610
+ throw Error(response.exceptionDetails.text);
504
611
  }
612
+ return response.result.value;
613
+ }
614
+ async function cdpEvaluateUntil(client, expression, opts) {
615
+ return await until(() => cdpEvaluate(client, expression), opts);
505
616
  }
506
617
  async function fetchObsidianDesktopReleases(sinceDate, sinceSha) {
507
618
  const repo = "obsidianmd/obsidian-releases";
@@ -523,32 +634,26 @@ async function fetchObsidianDesktopReleases(sinceDate, sinceSha) {
523
634
  return [fileHistory, { commitDate, commitSha }];
524
635
  }
525
636
  async function fetchObsidianGitHubReleases() {
526
- const gitHubReleases = await fetchGitHubAPIPaginated(`repos/obsidianmd/obsidian-releases/releases`);
527
- return gitHubReleases.reverse();
637
+ const releases = await fetchGitHubAPIPaginated(`repos/obsidianmd/obsidian-releases/releases`);
638
+ return releases.reverse();
528
639
  }
529
- var BROKEN_ASSETS = [
530
- "https://releases.obsidian.md/release/obsidian-0.12.16.asar.gz",
531
- "https://github.com/obsidianmd/obsidian-releases/releases/download/v0.12.16/obsidian-0.12.16.asar.gz",
532
- "https://releases.obsidian.md/release/obsidian-1.4.7.asar.gz",
533
- "https://releases.obsidian.md/release/obsidian-1.4.8.asar.gz"
640
+ var BROKEN_VERSIONS = [
641
+ "0.12.16",
642
+ // broken download link
643
+ "1.4.7",
644
+ // broken download link
645
+ "1.4.8",
646
+ // broken download link
647
+ "1.0.1"
648
+ // won't launch
534
649
  ];
535
650
  function parseObsidianDesktopRelease(fileRelease) {
536
651
  const parse = (r, isBeta) => {
537
- const version = r.latestVersion;
538
- let minInstallerVersion = r.minimumVersion;
539
- if (minInstallerVersion == "0.0.0") {
540
- minInstallerVersion = void 0;
541
- } else if (semver.satisfies(version, ">=1.3.0 <=1.3.4")) {
542
- minInstallerVersion = "0.14.5";
543
- } else if (semver.gte(version, "1.5.3") && semver.lt(minInstallerVersion, "1.1.9")) {
544
- minInstallerVersion = "1.1.9";
545
- }
546
652
  return {
547
653
  version: r.latestVersion,
548
- minInstallerVersion,
549
654
  isBeta,
550
655
  downloads: {
551
- asar: BROKEN_ASSETS.includes(r.downloadUrl) ? void 0 : r.downloadUrl
656
+ asar: r.downloadUrl
552
657
  }
553
658
  };
554
659
  };
@@ -560,11 +665,10 @@ function parseObsidianDesktopRelease(fileRelease) {
560
665
  }
561
666
  function parseObsidianGithubRelease(gitHubRelease) {
562
667
  const version = gitHubRelease.name;
563
- let assets = gitHubRelease.assets.map((a) => ({
668
+ const assets = gitHubRelease.assets.map((a) => ({
564
669
  url: a.browser_download_url,
565
670
  digest: a.digest ?? `id:${a.id}`
566
671
  }));
567
- assets = assets.filter((a) => !BROKEN_ASSETS.includes(a.url));
568
672
  const asar = assets.find((a) => a.url.match(`${version}.asar.gz$`));
569
673
  const appImage = assets.find((a) => a.url.match(`${version}.AppImage$`));
570
674
  const appImageArm = assets.find((a) => a.url.match(`${version}-arm64.AppImage$`));
@@ -604,53 +708,179 @@ var INSTALLER_KEYS = [
604
708
  "dmg",
605
709
  "exe"
606
710
  ];
607
- function updateObsidianVersionList(args) {
608
- const { original = [], destkopReleases = [], gitHubReleases = [], installerInfos = [] } = args;
609
- const oldVersions = _3.keyBy(original, (v) => v.version);
610
- const newVersions = _3.cloneDeep(oldVersions);
611
- for (const destkopRelease of destkopReleases) {
612
- const { current, beta } = parseObsidianDesktopRelease(destkopRelease);
613
- if (beta) {
614
- newVersions[beta.version] = _3.merge(newVersions[beta.version] ?? {}, beta);
711
+ async function extractInstallerInfo(version, installerKey, url) {
712
+ const installerName = url.split("/").at(-1);
713
+ consola.log(`Extrating installer info for ${installerName}...`);
714
+ const tmpDir = await makeTmpDir("obsidian-launcher-");
715
+ try {
716
+ const installerPath = path4.join(tmpDir, url.split("/").at(-1));
717
+ await downloadResponse(() => fetch(url), installerPath);
718
+ const exractedPath = path4.join(tmpDir, "Obsidian");
719
+ let platforms = [];
720
+ if (installerKey == "appImage" || installerKey == "appImageArm") {
721
+ await extractObsidianAppImage(installerPath, exractedPath);
722
+ platforms = ["linux-" + (installerKey == "appImage" ? "x64" : "arm64")];
723
+ } else if (installerKey == "tar" || installerKey == "tarArm") {
724
+ await extractObsidianTar(installerPath, exractedPath);
725
+ platforms = ["linux-" + (installerKey == "tar" ? "x64" : "arm64")];
726
+ } else if (installerKey == "exe") {
727
+ await extractObsidianExe(installerPath, "x64", exractedPath);
728
+ const { stdout } = await sevenZ(["l", "-ba", path4.relative(tmpDir, installerPath)], { cwd: tmpDir });
729
+ const lines = stdout.trim().split("\n").map((l) => l.trim());
730
+ const files = lines.map((l) => l.split(/\s+/).at(-1).replace(/\\/g, "/"));
731
+ if (files.includes("$PLUGINSDIR/app-arm64.7z")) platforms.push("win32-arm64");
732
+ if (files.includes("$PLUGINSDIR/app-32.7z")) platforms.push("win32-ia32");
733
+ if (files.includes("$PLUGINSDIR/app-64.7z")) platforms.push("win32-x64");
734
+ } else if (installerKey == "dmg") {
735
+ await extractObsidianDmg(installerPath, exractedPath);
736
+ platforms = ["darwin-arm64", "darwin-x64"];
737
+ } else {
738
+ throw new Error(`Unknown installer key ${installerKey}`);
615
739
  }
616
- newVersions[current.version] = _3.merge(newVersions[current.version] ?? {}, current);
617
- }
618
- for (const githubRelease of gitHubReleases) {
619
- if (semver.valid(githubRelease.name) && !semver.prerelease(githubRelease.name)) {
620
- const parsed = parseObsidianGithubRelease(githubRelease);
621
- const newVersion = _3.merge(newVersions[parsed.version] ?? {}, parsed);
622
- for (const installerKey of INSTALLER_KEYS) {
623
- const oldDigest = oldVersions[parsed.version]?.installers[installerKey]?.digest;
624
- const newDigest = newVersion.installers?.[installerKey]?.digest;
625
- if (oldDigest && oldDigest != newDigest) {
626
- newVersion.installers[installerKey] = { digest: newDigest };
740
+ const matches = [];
741
+ const installerFiles = await fsAsync3.readdir(exractedPath, { recursive: true, withFileTypes: true });
742
+ for (const file of installerFiles) {
743
+ if (file.isFile() && !file.name.endsWith(".asar")) {
744
+ const stream = fs3.createReadStream(path4.join(file.parentPath, file.name), { encoding: "utf-8" });
745
+ let prev = "";
746
+ for await (let chunk of stream) {
747
+ const regex = /Chrome\/\d+\.\d+\.\d+\.\d+|Electron\/\d+\.\d+\.\d+/g;
748
+ chunk = prev + chunk;
749
+ matches.push(...[...(prev + chunk).matchAll(regex)].map((m) => m[0]));
750
+ prev = chunk.slice(-64);
627
751
  }
628
752
  }
629
- newVersions[parsed.version] = newVersion;
630
753
  }
754
+ const versionSortKey = (v) => v.split(".").map((s) => s.padStart(9, "0")).join(".");
755
+ const versions = _3(matches).map((m) => m.split("/")).groupBy(0).mapValues((ms) => ms.map((m) => m[1])).mapValues((ms) => _3.sortBy(ms, versionSortKey).at(-1)).value();
756
+ const electron = versions["Electron"];
757
+ const chrome = versions["Chrome"];
758
+ if (!electron || !chrome) {
759
+ throw new Error(`Failed to extract Electron and Chrome versions from binary ${installerPath}`);
760
+ }
761
+ consola.log(`Extracted installer info for ${installerName}`);
762
+ return { electron, chrome, platforms };
763
+ } finally {
764
+ await fsAsync3.rm(tmpDir, { recursive: true, force: true });
765
+ }
766
+ }
767
+ async function checkCompatibility(launcher, appVersion, installerVersion) {
768
+ [appVersion, installerVersion] = await launcher.resolveVersion(appVersion, installerVersion);
769
+ consola.log(`Checking if app ${appVersion} and installer ${installerVersion} are compatible...`);
770
+ await launcher.downloadApp(appVersion);
771
+ await launcher.downloadInstaller(installerVersion);
772
+ const cdpResult = await maybe(retry(
773
+ () => getCdpSession(launcher, appVersion, installerVersion),
774
+ { retries: 3, backoff: 4e3 }
775
+ ));
776
+ if (!cdpResult.success) {
777
+ consola.log(`app ${appVersion} with installer ${installerVersion} failed to launch: ${cdpResult.error}`);
778
+ return false;
631
779
  }
632
- let minInstallerVersion = void 0;
633
- let maxInstallerVersion = void 0;
634
- for (const version of Object.keys(newVersions).sort(semver.compare)) {
635
- if (newVersions[version].downloads.appImage) {
636
- maxInstallerVersion = version;
637
- if (!minInstallerVersion) {
638
- minInstallerVersion = version;
780
+ const { client, cleanup } = cdpResult.result;
781
+ let result = true;
782
+ try {
783
+ if (semver.lt(appVersion, "0.7.4")) {
784
+ result = semver.gte(installerVersion, "0.6.4");
785
+ } else if (semver.lt(appVersion, "0.13.4")) {
786
+ await cdpEvaluate(client, `window.app.commands.executeCommandById('app:open-settings')`);
787
+ await until(() => cdpEvaluate(client, `
788
+ document.evaluate(
789
+ "//*[contains(@class, 'vertical-tab-nav-item') and contains(text(),'About')]",
790
+ document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
791
+ ).singleNodeValue.click() ?? true;
792
+ `), { timeout: 5e3 });
793
+ const aboutText = await until(() => cdpEvaluate(client, `
794
+ document.evaluate(
795
+ "//*[contains(@class, 'setting-item-name') and contains(text(),'Current version:')]",
796
+ document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
797
+ ).singleNodeValue.parentNode.innerText;
798
+ `), { timeout: 5e3 });
799
+ if (["manual installation", "manually install"].some((t) => aboutText.includes(t))) {
800
+ result = false;
801
+ }
802
+ } else {
803
+ await cdpEvaluate(client, `window.obsidianLauncher.app.commands.executeCommandById('app:show-debug-info')`);
804
+ const debugInfo = await cdpEvaluateUntil(
805
+ client,
806
+ `document.querySelector(".debug-textarea").value.trim()`,
807
+ { timeout: 5e3 }
808
+ );
809
+ if (debugInfo.toLowerCase().match(/installer version too low/)) {
810
+ result = false;
639
811
  }
640
812
  }
641
- newVersions[version] = _3.merge(newVersions[version], {
642
- minInstallerVersion: newVersions[version]?.minInstallerVersion ?? minInstallerVersion,
643
- maxInstallerVersion
644
- // override maxInstallerVersion if it was already set
645
- });
813
+ } finally {
814
+ await cleanup();
646
815
  }
647
- for (const installerInfo of installerInfos) {
648
- newVersions[installerInfo.version] = _3.merge(newVersions[installerInfo.version] ?? {}, {
649
- version: installerInfo.version,
650
- installers: { [installerInfo.key]: installerInfo.installerInfo }
816
+ consola.log(`app ${appVersion} and installer ${installerVersion} are ${!result ? "in" : ""}compatible`);
817
+ return result;
818
+ }
819
+ async function getCompatibilityInfos(versions, { _checkCompatibility = checkCompatibility } = {}) {
820
+ const tmp = await makeTmpDir("obsidian-installer-compat-");
821
+ try {
822
+ const versionsFile = {
823
+ metadata: {
824
+ schemaVersion: obsidianVersionsSchemaVersion,
825
+ commitDate: "1970-01-01T00:00:00Z",
826
+ commitSha: "0000000000000000000000000000000000000000",
827
+ timestamp: "1970-01-01T00:00:00Z"
828
+ },
829
+ versions: versions.map((v) => ({
830
+ ...v,
831
+ minInstallerVersion: v.minInstallerVersion ?? "0.0.0",
832
+ maxInstallerVersion: v.maxInstallerVersion ?? "999.9.9"
833
+ }))
834
+ };
835
+ await fsAsync3.writeFile(path4.join(tmp, "obsidian-versions.json"), JSON.stringify(versionsFile));
836
+ const launcher = new ObsidianLauncher({
837
+ cacheDir: path4.join(tmp, "cache"),
838
+ versionsUrl: pathToFileURL(path4.join(tmp, "obsidian-versions.json")).toString()
651
839
  });
840
+ const versionArr = _3(_3.cloneDeep(versions)).sort((a, b) => semver.compare(a.version, b.version)).dropWhile((v) => !v.downloads.appImage).filter((v) => !!v.downloads.asar).value();
841
+ let maxInstallerVersion = void 0;
842
+ for (const version of versionArr) {
843
+ if (version.downloads.appImage) {
844
+ maxInstallerVersion = version.version;
845
+ }
846
+ version.maxInstallerVersion = maxInstallerVersion;
847
+ }
848
+ const installerArr = versionArr.filter((v) => !!v.downloads.appImage);
849
+ const installerIndexMap = _3.fromPairs(installerArr.map((v, i) => [v.version, i]));
850
+ for (const [i, version] of versionArr.entries()) {
851
+ if (version.minInstallerVersion) {
852
+ continue;
853
+ }
854
+ const prev = i > 0 ? versionArr[i - 1] : void 0;
855
+ let start = prev ? installerIndexMap[prev.minInstallerVersion] : 0;
856
+ let end = installerIndexMap[version.maxInstallerVersion];
857
+ while (start <= end) {
858
+ const mid = Math.floor((start + end) / 2);
859
+ const compatible = await _checkCompatibility(
860
+ launcher,
861
+ version.version,
862
+ installerArr[mid].version
863
+ );
864
+ if (!compatible) {
865
+ start = mid + 1;
866
+ } else {
867
+ end = mid - 1;
868
+ }
869
+ }
870
+ if (start > installerIndexMap[version.maxInstallerVersion]) {
871
+ throw Error(`${version.version} failed to launch for all installers`);
872
+ }
873
+ version.minInstallerVersion = installerArr[start].version;
874
+ }
875
+ const origVersions = _3(versions).map((v) => _3.pick(v, ["version", "minInstallerVersion", "maxInstallerVersion"])).keyBy((v) => v.version).value();
876
+ return versionArr.map((v) => ({
877
+ version: v.version,
878
+ minInstallerVersion: v.minInstallerVersion,
879
+ maxInstallerVersion: v.maxInstallerVersion
880
+ })).filter((v) => !_3.isEqual(v, origVersions[v.version]));
881
+ } finally {
882
+ await fsAsync3.rm(tmp, { recursive: true, force: true });
652
883
  }
653
- return Object.values(newVersions).map(normalizeObsidianVersionInfo).sort((a, b) => semver.compare(a.version, b.version));
654
884
  }
655
885
  function normalizeObsidianVersionInfo(versionInfo) {
656
886
  versionInfo = _3.cloneDeep(versionInfo);
@@ -687,6 +917,74 @@ function normalizeObsidianVersionInfo(versionInfo) {
687
917
  };
688
918
  return normalizeObject(canonicalForm, versionInfo);
689
919
  }
920
+ async function updateObsidianVersionList(original, {
921
+ maxInstances = 1,
922
+ _fetchObsidianDesktopReleases = fetchObsidianDesktopReleases,
923
+ _fetchObsidianGitHubReleases = fetchObsidianGitHubReleases,
924
+ _extractInstallerInfo = extractInstallerInfo,
925
+ _checkCompatibility = checkCompatibility
926
+ } = {}) {
927
+ const oldVersions = _3.keyBy(original?.versions ?? [], (v) => v.version);
928
+ let newVersions = _3.cloneDeep(oldVersions);
929
+ const [destkopReleases, commitInfo] = await _fetchObsidianDesktopReleases(
930
+ original?.metadata.commitDate,
931
+ original?.metadata.commitSha
932
+ );
933
+ for (const destkopRelease of destkopReleases) {
934
+ const { current, beta } = parseObsidianDesktopRelease(destkopRelease);
935
+ if (beta) {
936
+ newVersions[beta.version] = _3.merge(newVersions[beta.version] ?? {}, beta);
937
+ }
938
+ newVersions[current.version] = _3.merge(newVersions[current.version] ?? {}, current);
939
+ }
940
+ const gitHubReleases = await _fetchObsidianGitHubReleases();
941
+ for (const githubRelease of gitHubReleases) {
942
+ if (semver.valid(githubRelease.name) && !semver.prerelease(githubRelease.name)) {
943
+ const parsed = parseObsidianGithubRelease(githubRelease);
944
+ const newVersion = _3.merge(newVersions[parsed.version] ?? {}, parsed);
945
+ for (const installerKey of INSTALLER_KEYS) {
946
+ const oldDigest = oldVersions[parsed.version]?.installers[installerKey]?.digest;
947
+ const newDigest = newVersion.installers?.[installerKey]?.digest;
948
+ if (oldDigest && oldDigest != newDigest) {
949
+ newVersion.installers[installerKey] = { digest: newDigest };
950
+ }
951
+ }
952
+ newVersions[parsed.version] = newVersion;
953
+ }
954
+ }
955
+ newVersions = _3.omitBy(newVersions, (v) => BROKEN_VERSIONS.includes(v.version));
956
+ const newInstallers = Object.values(newVersions).flatMap((v) => INSTALLER_KEYS.map((k) => [v, k])).filter(([v, key]) => v.downloads?.[key] && !v.installers?.[key]?.chrome);
957
+ const installerInfos = await pool(maxInstances, newInstallers, async ([v, key]) => {
958
+ const installerInfo = await _extractInstallerInfo(v.version, key, v.downloads[key]);
959
+ return { version: v.version, installers: { [key]: installerInfo } };
960
+ });
961
+ for (const installerInfo of installerInfos) {
962
+ newVersions[installerInfo.version] = _3.merge(newVersions[installerInfo.version] ?? {}, installerInfo);
963
+ }
964
+ const compatInfos = await getCompatibilityInfos(
965
+ Object.values(newVersions),
966
+ { _checkCompatibility }
967
+ );
968
+ for (const compatInfo of compatInfos) {
969
+ newVersions[compatInfo.version] = _3.merge(newVersions[compatInfo.version] ?? {}, compatInfo);
970
+ }
971
+ const result = {
972
+ metadata: {
973
+ schemaVersion: obsidianVersionsSchemaVersion,
974
+ commitDate: commitInfo.commitDate,
975
+ commitSha: commitInfo.commitSha,
976
+ timestamp: original?.metadata.timestamp ?? ""
977
+ // set down below
978
+ },
979
+ versions: Object.values(newVersions).map(normalizeObsidianVersionInfo).sort((a, b) => semver.compare(a.version, b.version))
980
+ };
981
+ const dayMs = 24 * 60 * 60 * 1e3;
982
+ const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();
983
+ if (!_3.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {
984
+ result.metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
985
+ }
986
+ return result;
987
+ }
690
988
 
691
989
  // src/launcher.ts
692
990
  var currentPlatform = {
@@ -694,6 +992,7 @@ var currentPlatform = {
694
992
  arch: process.arch
695
993
  };
696
994
  dotenv2.config({ path: [".env"], quiet: true });
995
+ var minSupportedObsidianVersion = "0.12.8";
697
996
  var ObsidianLauncher = class {
698
997
  /**
699
998
  * Construct an ObsidianLauncher.
@@ -757,8 +1056,8 @@ var ObsidianLauncher = class {
757
1056
  if (!data && await fileExists(dest)) {
758
1057
  const parsed = JSON.parse(await fsAsync4.readFile(dest, "utf-8"));
759
1058
  if (cacheValid(parsed)) {
760
- console.warn(error);
761
- console.warn(`Unable to download ${url}, using cached file.`);
1059
+ consola.warn(error);
1060
+ consola.warn(`Unable to download ${url}, using cached file.`);
762
1061
  data = parsed;
763
1062
  }
764
1063
  }
@@ -831,8 +1130,11 @@ var ObsidianLauncher = class {
831
1130
  appVersion = appVersionInfo.version;
832
1131
  let installerVersionInfo;
833
1132
  const { platform, arch } = process;
834
- if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {
835
- throw Error(`No installers available for Obsidian ${appVersion}`);
1133
+ if (semver2.lt(appVersion, minSupportedObsidianVersion)) {
1134
+ warnOnce(
1135
+ "unsupported-version",
1136
+ `Obsidian versions before ${minSupportedObsidianVersion} are unsupported, some obsidian-launcher features will not work.`
1137
+ );
836
1138
  }
837
1139
  if (installerVersion == "latest") {
838
1140
  installerVersionInfo = _4.findLast(
@@ -841,7 +1143,7 @@ var ObsidianLauncher = class {
841
1143
  );
842
1144
  } else if (installerVersion == "earliest") {
843
1145
  installerVersionInfo = versions.find(
844
- (v) => semver2.gte(v.version, appVersionInfo.minInstallerVersion) && !!this.getInstallerKey(v, { platform, arch })
1146
+ (v) => appVersionInfo.minInstallerVersion && semver2.gte(v.version, appVersionInfo.minInstallerVersion) && semver2.lte(v.version, appVersionInfo.version) && !!this.getInstallerKey(v, { platform, arch })
845
1147
  );
846
1148
  } else {
847
1149
  installerVersion = semver2.valid(installerVersion) ?? installerVersion;
@@ -849,14 +1151,15 @@ var ObsidianLauncher = class {
849
1151
  }
850
1152
  if (!installerVersionInfo) {
851
1153
  if (["earliest", "latest"].includes(installerVersion)) {
852
- throw Error(`No compatible installers available for Obsidian ${appVersion}`);
1154
+ throw Error(`No compatible installers available for Obsidian ${appVersion} for ${platform}-${arch}`);
853
1155
  } else {
854
1156
  throw Error(`No Obsidian installer ${installerVersion} found`);
855
1157
  }
856
1158
  }
857
- if (semver2.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) || semver2.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)) {
858
- throw Error(
859
- `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer >=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ${installerVersionInfo.version} specified`
1159
+ if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion || semver2.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) || semver2.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)) {
1160
+ warnOnce(
1161
+ `incompatible-versions-${appVersionInfo.version}-${installerVersionInfo.version}`,
1162
+ `App and installer versions are incompatible: app ${appVersionInfo.version} is compatible with installer >=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ${installerVersionInfo.version} specified.`
860
1163
  );
861
1164
  }
862
1165
  return [appVersionInfo.version, installerVersionInfo.version];
@@ -937,6 +1240,20 @@ var ObsidianLauncher = class {
937
1240
  );
938
1241
  }
939
1242
  }
1243
+ /**
1244
+ * Log into the Obsidian api using your Insider's account so you can download beta versions.
1245
+ *
1246
+ * login will be called automatically when using downloadApp on an Obsidian beta version so you usually won't need
1247
+ * to call this directly.
1248
+ */
1249
+ async login() {
1250
+ if (!this.obsidianApiToken) {
1251
+ this.obsidianApiToken = await obsidianApiLogin({
1252
+ interactive: this.interactive,
1253
+ savePath: path5.join(this.cacheDir, "obsidian-credentials.env")
1254
+ });
1255
+ }
1256
+ }
940
1257
  /**
941
1258
  * Downloads the Obsidian installer for the given version and platform/arch (defaults to host platform/arch).
942
1259
  * Returns the file path.
@@ -965,9 +1282,9 @@ var ObsidianLauncher = class {
965
1282
  throw Error(`Unsupported platform ${platform}`);
966
1283
  }
967
1284
  await atomicCreate(installerDir, async (scratch) => {
968
- console.log(`Downloading Obsidian installer v${installerVersion}...`);
1285
+ consola.log(`Downloading Obsidian installer v${installerVersion}...`);
969
1286
  const installer = path5.join(scratch, "installer");
970
- await downloadResponse(await fetch(installerInfo.url), installer);
1287
+ await downloadResponse(() => fetch(installerInfo.url), installer);
971
1288
  const extracted = path5.join(scratch, "extracted");
972
1289
  await extractor(installer, extracted);
973
1290
  return extracted;
@@ -978,8 +1295,8 @@ var ObsidianLauncher = class {
978
1295
  * Downloads the Obsidian asar for the given version. Returns the file path.
979
1296
  *
980
1297
  * To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the
981
- * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian beta
982
- * with `npx obsidian-launcher download app -v latest-beta`
1298
+ * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` environment variables (`.env` file is supported) or pre-download the
1299
+ * Obsidian beta with `npx obsidian-launcher download app -v latest-beta`
983
1300
  *
984
1301
  * @param appVersion Obsidian version to download
985
1302
  */
@@ -991,23 +1308,18 @@ var ObsidianLauncher = class {
991
1308
  }
992
1309
  const appPath = path5.join(this.cacheDir, "obsidian-app", `obsidian-${versionInfo.version}.asar`);
993
1310
  const isInsiders = new URL(appUrl).hostname.endsWith(".obsidian.md");
994
- if (isInsiders && !this.obsidianApiToken && !await fileExists(appPath)) {
995
- this.obsidianApiToken = await obsidianApiLogin({
996
- interactive: this.interactive,
997
- savePath: path5.join(this.cacheDir, "obsidian-credentials.env")
998
- });
1311
+ if (isInsiders && !await fileExists(appPath)) {
1312
+ await this.login();
999
1313
  }
1000
1314
  await atomicCreate(appPath, async (scratch) => {
1001
- console.log(`Downloading Obsidian app v${versionInfo.version} ...`);
1002
- let response;
1315
+ consola.log(`Downloading Obsidian app v${versionInfo.version} ...`);
1316
+ const archive = path5.join(scratch, "app.asar.gz");
1317
+ const asar = path5.join(scratch, "app.asar");
1003
1318
  if (isInsiders) {
1004
- response = await fetchObsidianApi(appUrl, { token: this.obsidianApiToken });
1319
+ await downloadResponse(() => fetchObsidianApi(appUrl, { token: this.obsidianApiToken }), archive);
1005
1320
  } else {
1006
- response = await fetch(appUrl);
1321
+ await downloadResponse(() => fetch(appUrl), archive);
1007
1322
  }
1008
- const archive = path5.join(scratch, "app.asar.gz");
1009
- const asar = path5.join(scratch, "app.asar");
1010
- await downloadResponse(response, archive);
1011
1323
  await extractGz(archive, asar);
1012
1324
  return asar;
1013
1325
  }, { replace: false });
@@ -1035,7 +1347,7 @@ var ObsidianLauncher = class {
1035
1347
  chromedriverPath = path5.join(chromedriverDir, `chromedriver`);
1036
1348
  }
1037
1349
  await atomicCreate(chromedriverDir, async (scratch) => {
1038
- console.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
1350
+ consola.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
1039
1351
  const chromedriverZipPath = await downloadArtifact({
1040
1352
  version: installerInfo.electron,
1041
1353
  artifactName: "chromedriver",
@@ -1060,9 +1372,9 @@ var ObsidianLauncher = class {
1060
1372
  }
1061
1373
  const apkPath = path5.join(this.cacheDir, "obsidian-apk", `obsidian-${versionInfo.version}.apk`);
1062
1374
  await atomicCreate(apkPath, async (scratch) => {
1063
- console.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
1375
+ consola.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
1064
1376
  const dest = path5.join(scratch, "obsidian.apk");
1065
- await downloadResponse(await fetch(apkUrl), dest);
1377
+ await downloadResponse(() => fetch(apkUrl), dest);
1066
1378
  return dest;
1067
1379
  }, { replace: false });
1068
1380
  return apkPath;
@@ -1096,11 +1408,12 @@ var ObsidianLauncher = class {
1096
1408
  await Promise.all(
1097
1409
  Object.entries(assetsToDownload).map(async ([file, required]) => {
1098
1410
  const url = `https://github.com/${repo}/releases/download/${version}/${file}`;
1099
- const response = await fetch(url);
1100
- if (response.ok) {
1101
- await downloadResponse(response, path5.join(scratch, file));
1102
- } else if (required) {
1103
- throw Error(`No ${file} found for ${repo} version ${version}`);
1411
+ try {
1412
+ await downloadResponse(() => fetch(url), path5.join(scratch, file));
1413
+ } catch {
1414
+ if (required) {
1415
+ throw Error(`No ${file} found for ${repo} version ${version}`);
1416
+ }
1104
1417
  }
1105
1418
  })
1106
1419
  );
@@ -1270,10 +1583,9 @@ var ObsidianLauncher = class {
1270
1583
  assetsToDownload.map(
1271
1584
  async (file) => {
1272
1585
  const url = `${baseUrl}/${file}`;
1273
- const response = await fetch(url);
1274
- if (response.ok) {
1275
- await downloadResponse(response, path5.join(scratch, file));
1276
- } else {
1586
+ try {
1587
+ await downloadResponse(() => fetch(url), path5.join(scratch, file));
1588
+ } catch {
1277
1589
  throw Error(`No ${file} found for ${repo}`);
1278
1590
  }
1279
1591
  }
@@ -1436,6 +1748,9 @@ var ObsidianLauncher = class {
1436
1748
  }
1437
1749
  }
1438
1750
  });
1751
+ if (semver2.lt(appVersion, "0.10.0")) {
1752
+ obsidianJson.last_open = vaultId;
1753
+ }
1439
1754
  }
1440
1755
  await fsAsync4.writeFile(path5.join(configDir, "obsidian.json"), JSON.stringify(obsidianJson));
1441
1756
  await fsAsync4.writeFile(path5.join(configDir, "Preferences"), JSON.stringify(chromePreferences));
@@ -1523,39 +1838,7 @@ var ObsidianLauncher = class {
1523
1838
  * the internal Electron version.
1524
1839
  */
1525
1840
  async updateVersionList(original, opts = {}) {
1526
- const { maxInstances = 1 } = opts;
1527
- const [destkopReleases, commitInfo] = await fetchObsidianDesktopReleases(
1528
- original?.metadata.commitDate,
1529
- original?.metadata.commitSha
1530
- );
1531
- const gitHubReleases = await fetchObsidianGitHubReleases();
1532
- let newVersions = updateObsidianVersionList({
1533
- original: original?.versions,
1534
- destkopReleases,
1535
- gitHubReleases
1536
- });
1537
- const newInstallers = newVersions.flatMap((v) => INSTALLER_KEYS.map((k) => [v, k])).filter(([v, key]) => v.downloads?.[key] && !v.installers?.[key]?.chrome);
1538
- const installerInfos = await pool(maxInstances, newInstallers, async ([v, key]) => {
1539
- const installerInfo = await extractInstallerInfo(key, v.downloads[key]);
1540
- return { version: v.version, key, installerInfo };
1541
- });
1542
- newVersions = updateObsidianVersionList({ original: newVersions, installerInfos });
1543
- const result = {
1544
- metadata: {
1545
- schemaVersion: obsidianVersionsSchemaVersion,
1546
- commitDate: commitInfo.commitDate,
1547
- commitSha: commitInfo.commitSha,
1548
- timestamp: original?.metadata.timestamp ?? ""
1549
- // set down below
1550
- },
1551
- versions: newVersions
1552
- };
1553
- const dayMs = 24 * 60 * 60 * 1e3;
1554
- const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();
1555
- if (!_4.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {
1556
- result.metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
1557
- }
1558
- return result;
1841
+ return updateObsidianVersionList(original, { maxInstances: opts.maxInstances });
1559
1842
  }
1560
1843
  /**
1561
1844
  * Returns true if the Obsidian version is already in the cache.
@@ -1584,7 +1867,7 @@ var ObsidianLauncher = class {
1584
1867
  return false;
1585
1868
  }
1586
1869
  if (new URL(versionInfo.downloads.asar).hostname.endsWith(".obsidian.md")) {
1587
- const hasCreds = !!(process.env["OBSIDIAN_EMAIL"] && process.env["OBSIDIAN_PASSWORD"]);
1870
+ const hasCreds = !!(process.env["OBSIDIAN_EMAIL"] && process.env["OBSIDIAN_PASSWORD"]) || await fileExists(path5.join(this.cacheDir, "obsidian-credentials.env"));
1588
1871
  const inCache = await this.isInCache("app", versionInfo.version);
1589
1872
  return hasCreds || inCache;
1590
1873
  } else {
@@ -1594,7 +1877,9 @@ var ObsidianLauncher = class {
1594
1877
  };
1595
1878
 
1596
1879
  export {
1880
+ consola,
1597
1881
  watchFiles,
1882
+ minSupportedObsidianVersion,
1598
1883
  ObsidianLauncher
1599
1884
  };
1600
- //# sourceMappingURL=chunk-O3OTELNK.js.map
1885
+ //# sourceMappingURL=chunk-LBBOWJGG.js.map