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.
@@ -7,8 +7,16 @@ var _promises = require('fs/promises'); var _promises2 = _interopRequireDefault(
7
7
  var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
8
8
  var _path = require('path'); var _path2 = _interopRequireDefault(_path);
9
9
  var _os = require('os'); var _os2 = _interopRequireDefault(_os);
10
+ var _consola = require('consola');
10
11
  var _promisepool = require('@supercharge/promise-pool');
11
12
  var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash);
13
+ var consola = _consola.createConsola.call(void 0, {
14
+ throttle: -1,
15
+ // disable throttle
16
+ formatOptions: {
17
+ date: false
18
+ }
19
+ });
12
20
  async function fileExists(path6) {
13
21
  try {
14
22
  await _promises2.default.stat(path6);
@@ -23,6 +31,14 @@ async function fileExists(path6) {
23
31
  async function makeTmpDir(prefix) {
24
32
  return _promises2.default.mkdtemp(_path2.default.join(_os2.default.tmpdir(), _nullishCoalesce(prefix, () => ( "tmp-"))));
25
33
  }
34
+ var logged = /* @__PURE__ */ new Map();
35
+ function warnOnce(key, message) {
36
+ const times = _nullishCoalesce(logged.get(key), () => ( 0));
37
+ if (times <= 0) {
38
+ consola.warn({ message });
39
+ }
40
+ logged.set(key, times + 1);
41
+ }
26
42
  async function atomicCreate(dest, func, opts = {}) {
27
43
  const { replace = true, preserveTmpDir = false } = opts;
28
44
  dest = _path2.default.resolve(dest);
@@ -68,6 +84,14 @@ async function linkOrCp(src, dest) {
68
84
  async function sleep(ms) {
69
85
  return new Promise((resolve) => setTimeout(resolve, ms));
70
86
  }
87
+ async function withTimeout(promise, timeout) {
88
+ let timer;
89
+ const result = Promise.race([
90
+ promise,
91
+ new Promise((resolve, reject) => timer = setTimeout(() => reject(Error("Promise timed out")), timeout))
92
+ ]);
93
+ return result.finally(() => clearTimeout(timer));
94
+ }
71
95
  async function pool(size, items, func) {
72
96
  const { results } = await _promisepool.PromisePool.for(items).withConcurrency(size).handleError(async (error) => {
73
97
  throw error;
@@ -77,6 +101,47 @@ async function pool(size, items, func) {
77
101
  async function maybe(promise) {
78
102
  return promise.then((r) => ({ success: true, result: r, error: void 0 })).catch((e) => ({ success: false, result: void 0, error: e }));
79
103
  }
104
+ async function until(func, opts) {
105
+ const { timeout, poll = 100 } = opts;
106
+ let time = 0;
107
+ let result;
108
+ let error;
109
+ while (!result && time < timeout) {
110
+ try {
111
+ result = await func();
112
+ error = void 0;
113
+ } catch (e) {
114
+ error = e;
115
+ }
116
+ if (!result) {
117
+ await sleep(poll);
118
+ }
119
+ time += poll;
120
+ }
121
+ if (!result) {
122
+ throw new Error("Timed out waiting for condition" + (error ? `: ${error}` : ""));
123
+ }
124
+ return result;
125
+ }
126
+ async function retry(func, opts = {}) {
127
+ const { retries = 4, backoff = 1e3, retryIf = () => true } = opts;
128
+ let attempt = 0;
129
+ let error;
130
+ while (attempt < retries) {
131
+ try {
132
+ return await func(attempt);
133
+ } catch (e) {
134
+ error = e;
135
+ }
136
+ const delay = backoff * Math.random() + backoff * Math.pow(2, attempt);
137
+ attempt += 1;
138
+ if (!retryIf(error) || attempt >= retries) {
139
+ throw error;
140
+ }
141
+ await sleep(delay);
142
+ }
143
+ throw error;
144
+ }
80
145
  function watchFiles(files, func, options) {
81
146
  const debouncedFunc = _lodash2.default.debounce((curr, prev) => {
82
147
  if (curr.mtimeMs > prev.mtimeMs || curr.mtimeMs == 0 && prev.mtimeMs != 0) {
@@ -190,15 +255,17 @@ async function obsidianApiLogin(opts) {
190
255
  let email = _nullishCoalesce(_process.env.OBSIDIAN_EMAIL, () => ( cached.OBSIDIAN_EMAIL));
191
256
  let password = _nullishCoalesce(_process.env.OBSIDIAN_PASSWORD, () => ( cached.OBSIDIAN_PASSWORD));
192
257
  let promptedCredentials = false;
258
+ const predownloadMessage = "pre-download the Obsidian beta with:\n npx obsidian-launcher download app -v <version>";
193
259
  if (!email || !password) {
194
260
  if (interactive) {
195
- console.log("Obsidian Insiders account is required to download Obsidian beta versions.");
261
+ consola.log("Obsidian Insiders account is required to download Obsidian beta versions.");
262
+ consola.log("You can set OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD environment variables (.env file is supported) to avoid this prompt.");
196
263
  email = email || _readlinesync2.default.question("Obsidian email: ");
197
264
  password = password || _readlinesync2.default.question("Obsidian password: ", { hideEchoBack: true });
198
265
  promptedCredentials = true;
199
266
  } else {
200
267
  throw Error(
201
- "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>`"
268
+ "Obsidian Insiders account is required to download Obsidian beta versions. Set the OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD environment variables (.env file is supported)" + (_process.env.CI ? "." : ` or ${predownloadMessage}`)
202
269
  );
203
270
  }
204
271
  }
@@ -231,12 +298,12 @@ async function obsidianApiLogin(opts) {
231
298
  needsMfa = true;
232
299
  if (!interactive) {
233
300
  throw Error(
234
- "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>`"
301
+ "Can't login with 2FA in a non-interactive session. To download Obsidian beta versions disable 2FA on your account" + (_process.env.CI ? "." : ` or ${predownloadMessage}`)
235
302
  );
236
303
  }
237
304
  } else if (["please wait", "try again"].some((m) => _optionalChain([error, 'optionalAccess', _21 => _21.includes, 'call', _22 => _22(m)]))) {
238
- console.warn(`Obsidian login failed: ${signin.error}`);
239
- console.warn("Retrying obsidian login...");
305
+ consola.warn(`Obsidian login failed: ${signin.error}`);
306
+ consola.warn("Retrying obsidian login...");
240
307
  retries++;
241
308
  } else if (!signin.token) {
242
309
  throw Error(`Obsidian login failed: ${_nullishCoalesce(signin.error, () => ( "unknown error"))}`);
@@ -256,7 +323,7 @@ async function obsidianApiLogin(opts) {
256
323
  OBSIDIAN_PASSWORD='${password}'
257
324
  `
258
325
  );
259
- console.log(`Saved Obsidian credentials to ${_path2.default.relative(process.cwd(), savePath)}`);
326
+ consola.log(`Saved Obsidian credentials to ${_path2.default.relative(process.cwd(), savePath)}`);
260
327
  }
261
328
  }
262
329
  return signin.token;
@@ -275,13 +342,21 @@ async function fetchObsidianApi(url, opts) {
275
342
  });
276
343
  return response;
277
344
  }
278
- async function downloadResponse(response, dest) {
279
- if (!response.ok) {
280
- throw Error(`${response.url} failed with ${response.status}`);
281
- }
282
- const fileStream = _fs2.default.createWriteStream(dest, { flags: "w" });
283
- const fetchStream = _stream.Readable.fromWeb(response.body);
284
- await _promises3.finished.call(void 0, fetchStream.pipe(fileStream));
345
+ async function downloadResponse(func, dest, opts = {}) {
346
+ await retry(async () => {
347
+ const response = await func();
348
+ if (!response.ok) {
349
+ const error = Error(`${response.url} failed with ${response.status}`);
350
+ error.status = response.status;
351
+ throw error;
352
+ }
353
+ const fileStream = _fs2.default.createWriteStream(dest, { flags: "w" });
354
+ const fetchStream = _stream.Readable.fromWeb(response.body);
355
+ await _promises3.finished.call(void 0, fetchStream.pipe(fileStream));
356
+ }, {
357
+ retryIf: (e) => !e.status || ![401, 403, 404].includes(e.status),
358
+ ...opts
359
+ });
285
360
  }
286
361
 
287
362
  // src/chromeLocalStorage.ts
@@ -371,6 +446,7 @@ var _util = require('util');
371
446
 
372
447
  var _zlib = require('zlib'); var _zlib2 = _interopRequireDefault(_zlib);
373
448
 
449
+ var _chromeremoteinterface = require('chrome-remote-interface'); var _chromeremoteinterface2 = _interopRequireDefault(_chromeremoteinterface);
374
450
  var execFile = _util.promisify.call(void 0, _child_process2.default.execFile);
375
451
  function normalizeGitHubRepo(repo) {
376
452
  return _nullishCoalesce(_optionalChain([repo, 'access', _26 => _26.match, 'call', _27 => _27(/^(https?:\/\/)?(github.com\/)?(.*?)\/?$/), 'optionalAccess', _28 => _28[3]]), () => ( repo));
@@ -451,61 +527,96 @@ async function extractObsidianDmg(dmg, dest) {
451
527
  }
452
528
  });
453
529
  }
454
- async function extractInstallerInfo(installerKey, url) {
455
- const installerName = url.split("/").at(-1);
456
- console.log(`Extrating installer info for ${installerName}...`);
457
- const tmpDir = await makeTmpDir("obsidian-launcher-");
458
- try {
459
- const installerPath = _path2.default.join(tmpDir, url.split("/").at(-1));
460
- await downloadResponse(await fetch(url), installerPath);
461
- const exractedPath = _path2.default.join(tmpDir, "Obsidian");
462
- let platforms = [];
463
- if (installerKey == "appImage" || installerKey == "appImageArm") {
464
- await extractObsidianAppImage(installerPath, exractedPath);
465
- platforms = ["linux-" + (installerKey == "appImage" ? "x64" : "arm64")];
466
- } else if (installerKey == "tar" || installerKey == "tarArm") {
467
- await extractObsidianTar(installerPath, exractedPath);
468
- platforms = ["linux-" + (installerKey == "tar" ? "x64" : "arm64")];
469
- } else if (installerKey == "exe") {
470
- await extractObsidianExe(installerPath, "x64", exractedPath);
471
- const { stdout } = await sevenZ(["l", "-ba", _path2.default.relative(tmpDir, installerPath)], { cwd: tmpDir });
472
- const lines = stdout.trim().split("\n").map((l) => l.trim());
473
- const files = lines.map((l) => l.split(/\s+/).at(-1).replace(/\\/g, "/"));
474
- if (files.includes("$PLUGINSDIR/app-arm64.7z")) platforms.push("win32-arm64");
475
- if (files.includes("$PLUGINSDIR/app-32.7z")) platforms.push("win32-ia32");
476
- if (files.includes("$PLUGINSDIR/app-64.7z")) platforms.push("win32-x64");
477
- } else if (installerKey == "dmg") {
478
- await extractObsidianDmg(installerPath, exractedPath);
479
- platforms = ["darwin-arm64", "darwin-x64"];
480
- } else {
481
- throw new Error(`Unknown installer key ${installerKey}`);
530
+ async function getCdpSession(launcher, appVersion, installerVersion) {
531
+ [appVersion, installerVersion] = await launcher.resolveVersion(appVersion, installerVersion);
532
+ const cleanup = [];
533
+ const doCleanup = async () => {
534
+ for (const func of [...cleanup].reverse()) {
535
+ await func();
482
536
  }
483
- const matches = [];
484
- const installerFiles = await _promises2.default.readdir(exractedPath, { recursive: true, withFileTypes: true });
485
- for (const file of installerFiles) {
486
- if (file.isFile() && !file.name.endsWith(".asar")) {
487
- const stream = _fs2.default.createReadStream(_path2.default.join(file.parentPath, file.name), { encoding: "utf-8" });
488
- let prev = "";
489
- for await (let chunk of stream) {
490
- const regex = /Chrome\/\d+\.\d+\.\d+\.\d+|Electron\/\d+\.\d+\.\d+/g;
491
- chunk = prev + chunk;
492
- matches.push(...[...(prev + chunk).matchAll(regex)].map((m) => m[0]));
493
- prev = chunk.slice(-64);
537
+ };
538
+ const vault = await makeTmpDir("obsidian-vault-");
539
+ cleanup.push(() => _promises2.default.rm(vault, { recursive: true, force: true }));
540
+ const pluginDir = _path2.default.join(vault, ".obsidian", "plugins", "obsidian-launcher");
541
+ await _promises2.default.mkdir(pluginDir, { recursive: true });
542
+ await _promises2.default.writeFile(_path2.default.join(pluginDir, "manifest.json"), JSON.stringify({
543
+ id: "obsidian-launcher",
544
+ name: "Obsidian Launcher",
545
+ version: "1.0.0",
546
+ minAppVersion: "0.0.1",
547
+ description: "",
548
+ author: "obsidian-launcher",
549
+ isDesktopOnly: false
550
+ }));
551
+ await _promises2.default.writeFile(_path2.default.join(pluginDir, "main.js"), `
552
+ const obsidian = require('obsidian');
553
+ class ObsidianLauncherPlugin extends obsidian.Plugin {
554
+ async onload() { window.obsidianLauncher = {app: this.app, obsidian: obsidian}; };
494
555
  }
556
+ module.exports = ObsidianLauncherPlugin;
557
+ `);
558
+ await _promises2.default.writeFile(_path2.default.join(vault, ".obsidian", "community-plugins.json"), JSON.stringify([
559
+ "obsidian-launcher"
560
+ ]));
561
+ try {
562
+ const { proc, configDir } = await launcher.launch({
563
+ appVersion,
564
+ installerVersion,
565
+ vault,
566
+ copy: false,
567
+ args: [`--remote-debugging-port=0`, "--test-type=webdriver"]
568
+ // will choose a random available port
569
+ });
570
+ cleanup.push(() => _promises2.default.rm(configDir, { recursive: true, force: true }));
571
+ const procExit = new Promise((resolve) => proc.on("close", (code) => resolve(_nullishCoalesce(code, () => ( -1)))));
572
+ cleanup.push(async () => {
573
+ proc.kill("SIGTERM");
574
+ const timeout = await maybe(withTimeout(procExit, 5 * 1e3));
575
+ if (!timeout.success) {
576
+ consola.warn(`Stuck process ${proc.pid}, using SIGKILL`);
577
+ proc.kill("SIGKILL");
495
578
  }
579
+ await procExit;
580
+ });
581
+ const portPromise = new Promise((resolve, reject) => {
582
+ void procExit.then(() => reject(Error("Processed ended without opening a port")));
583
+ proc.stderr.on("data", (data) => {
584
+ const port2 = _optionalChain([data, 'access', _29 => _29.toString, 'call', _30 => _30(), 'access', _31 => _31.match, 'call', _32 => _32(/ws:\/\/[\w.]+?:(\d+)/), 'optionalAccess', _33 => _33[1]]);
585
+ if (port2) {
586
+ resolve(Number(port2));
587
+ }
588
+ });
589
+ });
590
+ const port = await maybe(withTimeout(portPromise, 20 * 1e3));
591
+ if (!port.success) {
592
+ throw new Error("Timed out waiting for Chrome DevTools protocol port");
496
593
  }
497
- const versionSortKey = (v) => v.split(".").map((s) => s.padStart(9, "0")).join(".");
498
- const versions = _lodash2.default.call(void 0, matches).map((m) => m.split("/")).groupBy(0).mapValues((ms) => ms.map((m) => m[1])).mapValues((ms) => _lodash2.default.sortBy(ms, versionSortKey).at(-1)).value();
499
- const electron = versions["Electron"];
500
- const chrome = versions["Chrome"];
501
- if (!electron || !chrome) {
502
- throw new Error(`Failed to extract Electron and Chrome versions from binary ${installerPath}`);
503
- }
504
- console.log(`Extracted installer info for ${installerName}`);
505
- return { electron, chrome, platforms };
506
- } finally {
507
- await _promises2.default.rm(tmpDir, { recursive: true, force: true });
594
+ const client = await _chromeremoteinterface2.default.call(void 0, { port: port.result });
595
+ cleanup.push(() => client.close());
596
+ const expr = _semver2.default.gte(appVersion, "0.12.8") ? "!!window.obsidianLauncher" : "!!window.app.workspace";
597
+ await until(
598
+ () => client.Runtime.evaluate({ expression: expr }).then((r) => r.result.value),
599
+ { timeout: 5e3 }
600
+ );
601
+ return {
602
+ client,
603
+ cleanup: doCleanup,
604
+ proc
605
+ };
606
+ } catch (e) {
607
+ await doCleanup();
608
+ throw e;
609
+ }
610
+ }
611
+ async function cdpEvaluate(client, expression) {
612
+ const response = await client.Runtime.evaluate({ expression, returnByValue: true });
613
+ if (response.exceptionDetails) {
614
+ throw Error(response.exceptionDetails.text);
508
615
  }
616
+ return response.result.value;
617
+ }
618
+ async function cdpEvaluateUntil(client, expression, opts) {
619
+ return await until(() => cdpEvaluate(client, expression), opts);
509
620
  }
510
621
  async function fetchObsidianDesktopReleases(sinceDate, sinceSha) {
511
622
  const repo = "obsidianmd/obsidian-releases";
@@ -522,37 +633,31 @@ async function fetchObsidianDesktopReleases(sinceDate, sinceSha) {
522
633
  commitHistory,
523
634
  (commit) => fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then((r) => r.json())
524
635
  );
525
- const commitDate = _nullishCoalesce(_optionalChain([commitHistory, 'access', _29 => _29.at, 'call', _30 => _30(-1), 'optionalAccess', _31 => _31.commit, 'access', _32 => _32.committer, 'access', _33 => _33.date]), () => ( sinceDate));
526
- const commitSha = _nullishCoalesce(_optionalChain([commitHistory, 'access', _34 => _34.at, 'call', _35 => _35(-1), 'optionalAccess', _36 => _36.sha]), () => ( sinceSha));
636
+ const commitDate = _nullishCoalesce(_optionalChain([commitHistory, 'access', _34 => _34.at, 'call', _35 => _35(-1), 'optionalAccess', _36 => _36.commit, 'access', _37 => _37.committer, 'access', _38 => _38.date]), () => ( sinceDate));
637
+ const commitSha = _nullishCoalesce(_optionalChain([commitHistory, 'access', _39 => _39.at, 'call', _40 => _40(-1), 'optionalAccess', _41 => _41.sha]), () => ( sinceSha));
527
638
  return [fileHistory, { commitDate, commitSha }];
528
639
  }
529
640
  async function fetchObsidianGitHubReleases() {
530
- const gitHubReleases = await fetchGitHubAPIPaginated(`repos/obsidianmd/obsidian-releases/releases`);
531
- return gitHubReleases.reverse();
641
+ const releases = await fetchGitHubAPIPaginated(`repos/obsidianmd/obsidian-releases/releases`);
642
+ return releases.reverse();
532
643
  }
533
- var BROKEN_ASSETS = [
534
- "https://releases.obsidian.md/release/obsidian-0.12.16.asar.gz",
535
- "https://github.com/obsidianmd/obsidian-releases/releases/download/v0.12.16/obsidian-0.12.16.asar.gz",
536
- "https://releases.obsidian.md/release/obsidian-1.4.7.asar.gz",
537
- "https://releases.obsidian.md/release/obsidian-1.4.8.asar.gz"
644
+ var BROKEN_VERSIONS = [
645
+ "0.12.16",
646
+ // broken download link
647
+ "1.4.7",
648
+ // broken download link
649
+ "1.4.8",
650
+ // broken download link
651
+ "1.0.1"
652
+ // won't launch
538
653
  ];
539
654
  function parseObsidianDesktopRelease(fileRelease) {
540
655
  const parse = (r, isBeta) => {
541
- const version = r.latestVersion;
542
- let minInstallerVersion = r.minimumVersion;
543
- if (minInstallerVersion == "0.0.0") {
544
- minInstallerVersion = void 0;
545
- } else if (_semver2.default.satisfies(version, ">=1.3.0 <=1.3.4")) {
546
- minInstallerVersion = "0.14.5";
547
- } else if (_semver2.default.gte(version, "1.5.3") && _semver2.default.lt(minInstallerVersion, "1.1.9")) {
548
- minInstallerVersion = "1.1.9";
549
- }
550
656
  return {
551
657
  version: r.latestVersion,
552
- minInstallerVersion,
553
658
  isBeta,
554
659
  downloads: {
555
- asar: BROKEN_ASSETS.includes(r.downloadUrl) ? void 0 : r.downloadUrl
660
+ asar: r.downloadUrl
556
661
  }
557
662
  };
558
663
  };
@@ -564,11 +669,10 @@ function parseObsidianDesktopRelease(fileRelease) {
564
669
  }
565
670
  function parseObsidianGithubRelease(gitHubRelease) {
566
671
  const version = gitHubRelease.name;
567
- let assets = gitHubRelease.assets.map((a) => ({
672
+ const assets = gitHubRelease.assets.map((a) => ({
568
673
  url: a.browser_download_url,
569
674
  digest: _nullishCoalesce(a.digest, () => ( `id:${a.id}`))
570
675
  }));
571
- assets = assets.filter((a) => !BROKEN_ASSETS.includes(a.url));
572
676
  const asar = assets.find((a) => a.url.match(`${version}.asar.gz$`));
573
677
  const appImage = assets.find((a) => a.url.match(`${version}.AppImage$`));
574
678
  const appImageArm = assets.find((a) => a.url.match(`${version}-arm64.AppImage$`));
@@ -581,14 +685,14 @@ function parseObsidianGithubRelease(gitHubRelease) {
581
685
  version,
582
686
  gitHubRelease: gitHubRelease.html_url,
583
687
  downloads: {
584
- asar: _optionalChain([asar, 'optionalAccess', _37 => _37.url]),
585
- appImage: _optionalChain([appImage, 'optionalAccess', _38 => _38.url]),
586
- appImageArm: _optionalChain([appImageArm, 'optionalAccess', _39 => _39.url]),
587
- tar: _optionalChain([tar, 'optionalAccess', _40 => _40.url]),
588
- tarArm: _optionalChain([tarArm, 'optionalAccess', _41 => _41.url]),
589
- dmg: _optionalChain([dmg, 'optionalAccess', _42 => _42.url]),
590
- exe: _optionalChain([exe, 'optionalAccess', _43 => _43.url]),
591
- apk: _optionalChain([apk, 'optionalAccess', _44 => _44.url])
688
+ asar: _optionalChain([asar, 'optionalAccess', _42 => _42.url]),
689
+ appImage: _optionalChain([appImage, 'optionalAccess', _43 => _43.url]),
690
+ appImageArm: _optionalChain([appImageArm, 'optionalAccess', _44 => _44.url]),
691
+ tar: _optionalChain([tar, 'optionalAccess', _45 => _45.url]),
692
+ tarArm: _optionalChain([tarArm, 'optionalAccess', _46 => _46.url]),
693
+ dmg: _optionalChain([dmg, 'optionalAccess', _47 => _47.url]),
694
+ exe: _optionalChain([exe, 'optionalAccess', _48 => _48.url]),
695
+ apk: _optionalChain([apk, 'optionalAccess', _49 => _49.url])
592
696
  },
593
697
  installers: {
594
698
  appImage: appImage ? { digest: appImage.digest } : void 0,
@@ -608,58 +712,184 @@ var INSTALLER_KEYS = [
608
712
  "dmg",
609
713
  "exe"
610
714
  ];
611
- function updateObsidianVersionList(args) {
612
- const { original = [], destkopReleases = [], gitHubReleases = [], installerInfos = [] } = args;
613
- const oldVersions = _lodash2.default.keyBy(original, (v) => v.version);
614
- const newVersions = _lodash2.default.cloneDeep(oldVersions);
615
- for (const destkopRelease of destkopReleases) {
616
- const { current, beta } = parseObsidianDesktopRelease(destkopRelease);
617
- if (beta) {
618
- newVersions[beta.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[beta.version], () => ( {})), beta);
715
+ async function extractInstallerInfo(version, installerKey, url) {
716
+ const installerName = url.split("/").at(-1);
717
+ consola.log(`Extrating installer info for ${installerName}...`);
718
+ const tmpDir = await makeTmpDir("obsidian-launcher-");
719
+ try {
720
+ const installerPath = _path2.default.join(tmpDir, url.split("/").at(-1));
721
+ await downloadResponse(() => fetch(url), installerPath);
722
+ const exractedPath = _path2.default.join(tmpDir, "Obsidian");
723
+ let platforms = [];
724
+ if (installerKey == "appImage" || installerKey == "appImageArm") {
725
+ await extractObsidianAppImage(installerPath, exractedPath);
726
+ platforms = ["linux-" + (installerKey == "appImage" ? "x64" : "arm64")];
727
+ } else if (installerKey == "tar" || installerKey == "tarArm") {
728
+ await extractObsidianTar(installerPath, exractedPath);
729
+ platforms = ["linux-" + (installerKey == "tar" ? "x64" : "arm64")];
730
+ } else if (installerKey == "exe") {
731
+ await extractObsidianExe(installerPath, "x64", exractedPath);
732
+ const { stdout } = await sevenZ(["l", "-ba", _path2.default.relative(tmpDir, installerPath)], { cwd: tmpDir });
733
+ const lines = stdout.trim().split("\n").map((l) => l.trim());
734
+ const files = lines.map((l) => l.split(/\s+/).at(-1).replace(/\\/g, "/"));
735
+ if (files.includes("$PLUGINSDIR/app-arm64.7z")) platforms.push("win32-arm64");
736
+ if (files.includes("$PLUGINSDIR/app-32.7z")) platforms.push("win32-ia32");
737
+ if (files.includes("$PLUGINSDIR/app-64.7z")) platforms.push("win32-x64");
738
+ } else if (installerKey == "dmg") {
739
+ await extractObsidianDmg(installerPath, exractedPath);
740
+ platforms = ["darwin-arm64", "darwin-x64"];
741
+ } else {
742
+ throw new Error(`Unknown installer key ${installerKey}`);
619
743
  }
620
- newVersions[current.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[current.version], () => ( {})), current);
621
- }
622
- for (const githubRelease of gitHubReleases) {
623
- if (_semver2.default.valid(githubRelease.name) && !_semver2.default.prerelease(githubRelease.name)) {
624
- const parsed = parseObsidianGithubRelease(githubRelease);
625
- const newVersion = _lodash2.default.merge(_nullishCoalesce(newVersions[parsed.version], () => ( {})), parsed);
626
- for (const installerKey of INSTALLER_KEYS) {
627
- const oldDigest = _optionalChain([oldVersions, 'access', _45 => _45[parsed.version], 'optionalAccess', _46 => _46.installers, 'access', _47 => _47[installerKey], 'optionalAccess', _48 => _48.digest]);
628
- const newDigest = _optionalChain([newVersion, 'access', _49 => _49.installers, 'optionalAccess', _50 => _50[installerKey], 'optionalAccess', _51 => _51.digest]);
629
- if (oldDigest && oldDigest != newDigest) {
630
- newVersion.installers[installerKey] = { digest: newDigest };
744
+ const matches = [];
745
+ const installerFiles = await _promises2.default.readdir(exractedPath, { recursive: true, withFileTypes: true });
746
+ for (const file of installerFiles) {
747
+ if (file.isFile() && !file.name.endsWith(".asar")) {
748
+ const stream = _fs2.default.createReadStream(_path2.default.join(file.parentPath, file.name), { encoding: "utf-8" });
749
+ let prev = "";
750
+ for await (let chunk of stream) {
751
+ const regex = /Chrome\/\d+\.\d+\.\d+\.\d+|Electron\/\d+\.\d+\.\d+/g;
752
+ chunk = prev + chunk;
753
+ matches.push(...[...(prev + chunk).matchAll(regex)].map((m) => m[0]));
754
+ prev = chunk.slice(-64);
631
755
  }
632
756
  }
633
- newVersions[parsed.version] = newVersion;
634
757
  }
758
+ const versionSortKey = (v) => v.split(".").map((s) => s.padStart(9, "0")).join(".");
759
+ const versions = _lodash2.default.call(void 0, matches).map((m) => m.split("/")).groupBy(0).mapValues((ms) => ms.map((m) => m[1])).mapValues((ms) => _lodash2.default.sortBy(ms, versionSortKey).at(-1)).value();
760
+ const electron = versions["Electron"];
761
+ const chrome = versions["Chrome"];
762
+ if (!electron || !chrome) {
763
+ throw new Error(`Failed to extract Electron and Chrome versions from binary ${installerPath}`);
764
+ }
765
+ consola.log(`Extracted installer info for ${installerName}`);
766
+ return { electron, chrome, platforms };
767
+ } finally {
768
+ await _promises2.default.rm(tmpDir, { recursive: true, force: true });
769
+ }
770
+ }
771
+ async function checkCompatibility(launcher, appVersion, installerVersion) {
772
+ [appVersion, installerVersion] = await launcher.resolveVersion(appVersion, installerVersion);
773
+ consola.log(`Checking if app ${appVersion} and installer ${installerVersion} are compatible...`);
774
+ await launcher.downloadApp(appVersion);
775
+ await launcher.downloadInstaller(installerVersion);
776
+ const cdpResult = await maybe(retry(
777
+ () => getCdpSession(launcher, appVersion, installerVersion),
778
+ { retries: 3, backoff: 4e3 }
779
+ ));
780
+ if (!cdpResult.success) {
781
+ consola.log(`app ${appVersion} with installer ${installerVersion} failed to launch: ${cdpResult.error}`);
782
+ return false;
635
783
  }
636
- let minInstallerVersion = void 0;
637
- let maxInstallerVersion = void 0;
638
- for (const version of Object.keys(newVersions).sort(_semver2.default.compare)) {
639
- if (newVersions[version].downloads.appImage) {
640
- maxInstallerVersion = version;
641
- if (!minInstallerVersion) {
642
- minInstallerVersion = version;
784
+ const { client, cleanup } = cdpResult.result;
785
+ let result = true;
786
+ try {
787
+ if (_semver2.default.lt(appVersion, "0.7.4")) {
788
+ result = _semver2.default.gte(installerVersion, "0.6.4");
789
+ } else if (_semver2.default.lt(appVersion, "0.13.4")) {
790
+ await cdpEvaluate(client, `window.app.commands.executeCommandById('app:open-settings')`);
791
+ await until(() => cdpEvaluate(client, `
792
+ document.evaluate(
793
+ "//*[contains(@class, 'vertical-tab-nav-item') and contains(text(),'About')]",
794
+ document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
795
+ ).singleNodeValue.click() ?? true;
796
+ `), { timeout: 5e3 });
797
+ const aboutText = await until(() => cdpEvaluate(client, `
798
+ document.evaluate(
799
+ "//*[contains(@class, 'setting-item-name') and contains(text(),'Current version:')]",
800
+ document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
801
+ ).singleNodeValue.parentNode.innerText;
802
+ `), { timeout: 5e3 });
803
+ if (["manual installation", "manually install"].some((t) => aboutText.includes(t))) {
804
+ result = false;
805
+ }
806
+ } else {
807
+ await cdpEvaluate(client, `window.obsidianLauncher.app.commands.executeCommandById('app:show-debug-info')`);
808
+ const debugInfo = await cdpEvaluateUntil(
809
+ client,
810
+ `document.querySelector(".debug-textarea").value.trim()`,
811
+ { timeout: 5e3 }
812
+ );
813
+ if (debugInfo.toLowerCase().match(/installer version too low/)) {
814
+ result = false;
643
815
  }
644
816
  }
645
- newVersions[version] = _lodash2.default.merge(newVersions[version], {
646
- minInstallerVersion: _nullishCoalesce(_optionalChain([newVersions, 'access', _52 => _52[version], 'optionalAccess', _53 => _53.minInstallerVersion]), () => ( minInstallerVersion)),
647
- maxInstallerVersion
648
- // override maxInstallerVersion if it was already set
649
- });
817
+ } finally {
818
+ await cleanup();
650
819
  }
651
- for (const installerInfo of installerInfos) {
652
- newVersions[installerInfo.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[installerInfo.version], () => ( {})), {
653
- version: installerInfo.version,
654
- installers: { [installerInfo.key]: installerInfo.installerInfo }
820
+ consola.log(`app ${appVersion} and installer ${installerVersion} are ${!result ? "in" : ""}compatible`);
821
+ return result;
822
+ }
823
+ async function getCompatibilityInfos(versions, { _checkCompatibility = checkCompatibility } = {}) {
824
+ const tmp = await makeTmpDir("obsidian-installer-compat-");
825
+ try {
826
+ const versionsFile = {
827
+ metadata: {
828
+ schemaVersion: obsidianVersionsSchemaVersion,
829
+ commitDate: "1970-01-01T00:00:00Z",
830
+ commitSha: "0000000000000000000000000000000000000000",
831
+ timestamp: "1970-01-01T00:00:00Z"
832
+ },
833
+ versions: versions.map((v) => ({
834
+ ...v,
835
+ minInstallerVersion: _nullishCoalesce(v.minInstallerVersion, () => ( "0.0.0")),
836
+ maxInstallerVersion: _nullishCoalesce(v.maxInstallerVersion, () => ( "999.9.9"))
837
+ }))
838
+ };
839
+ await _promises2.default.writeFile(_path2.default.join(tmp, "obsidian-versions.json"), JSON.stringify(versionsFile));
840
+ const launcher = new ObsidianLauncher({
841
+ cacheDir: _path2.default.join(tmp, "cache"),
842
+ versionsUrl: _url.pathToFileURL.call(void 0, _path2.default.join(tmp, "obsidian-versions.json")).toString()
655
843
  });
844
+ const versionArr = _lodash2.default.call(void 0, _lodash2.default.cloneDeep(versions)).sort((a, b) => _semver2.default.compare(a.version, b.version)).dropWhile((v) => !v.downloads.appImage).filter((v) => !!v.downloads.asar).value();
845
+ let maxInstallerVersion = void 0;
846
+ for (const version of versionArr) {
847
+ if (version.downloads.appImage) {
848
+ maxInstallerVersion = version.version;
849
+ }
850
+ version.maxInstallerVersion = maxInstallerVersion;
851
+ }
852
+ const installerArr = versionArr.filter((v) => !!v.downloads.appImage);
853
+ const installerIndexMap = _lodash2.default.fromPairs(installerArr.map((v, i) => [v.version, i]));
854
+ for (const [i, version] of versionArr.entries()) {
855
+ if (version.minInstallerVersion) {
856
+ continue;
857
+ }
858
+ const prev = i > 0 ? versionArr[i - 1] : void 0;
859
+ let start = prev ? installerIndexMap[prev.minInstallerVersion] : 0;
860
+ let end = installerIndexMap[version.maxInstallerVersion];
861
+ while (start <= end) {
862
+ const mid = Math.floor((start + end) / 2);
863
+ const compatible = await _checkCompatibility(
864
+ launcher,
865
+ version.version,
866
+ installerArr[mid].version
867
+ );
868
+ if (!compatible) {
869
+ start = mid + 1;
870
+ } else {
871
+ end = mid - 1;
872
+ }
873
+ }
874
+ if (start > installerIndexMap[version.maxInstallerVersion]) {
875
+ throw Error(`${version.version} failed to launch for all installers`);
876
+ }
877
+ version.minInstallerVersion = installerArr[start].version;
878
+ }
879
+ const origVersions = _lodash2.default.call(void 0, versions).map((v) => _lodash2.default.pick(v, ["version", "minInstallerVersion", "maxInstallerVersion"])).keyBy((v) => v.version).value();
880
+ return versionArr.map((v) => ({
881
+ version: v.version,
882
+ minInstallerVersion: v.minInstallerVersion,
883
+ maxInstallerVersion: v.maxInstallerVersion
884
+ })).filter((v) => !_lodash2.default.isEqual(v, origVersions[v.version]));
885
+ } finally {
886
+ await _promises2.default.rm(tmp, { recursive: true, force: true });
656
887
  }
657
- return Object.values(newVersions).map(normalizeObsidianVersionInfo).sort((a, b) => _semver2.default.compare(a.version, b.version));
658
888
  }
659
889
  function normalizeObsidianVersionInfo(versionInfo) {
660
890
  versionInfo = _lodash2.default.cloneDeep(versionInfo);
661
- versionInfo.electronVersion = _optionalChain([versionInfo, 'access', _54 => _54.installers, 'optionalAccess', _55 => _55.appImage, 'optionalAccess', _56 => _56.electron]);
662
- versionInfo.chromeVersion = _optionalChain([versionInfo, 'access', _57 => _57.installers, 'optionalAccess', _58 => _58.appImage, 'optionalAccess', _59 => _59.chrome]);
891
+ versionInfo.electronVersion = _optionalChain([versionInfo, 'access', _50 => _50.installers, 'optionalAccess', _51 => _51.appImage, 'optionalAccess', _52 => _52.electron]);
892
+ versionInfo.chromeVersion = _optionalChain([versionInfo, 'access', _53 => _53.installers, 'optionalAccess', _54 => _54.appImage, 'optionalAccess', _55 => _55.chrome]);
663
893
  versionInfo.downloads = _nullishCoalesce(versionInfo.downloads, () => ( {}));
664
894
  versionInfo.installers = _nullishCoalesce(versionInfo.installers, () => ( {}));
665
895
  const canonicalForm = {
@@ -691,6 +921,74 @@ function normalizeObsidianVersionInfo(versionInfo) {
691
921
  };
692
922
  return normalizeObject(canonicalForm, versionInfo);
693
923
  }
924
+ async function updateObsidianVersionList(original, {
925
+ maxInstances = 1,
926
+ _fetchObsidianDesktopReleases = fetchObsidianDesktopReleases,
927
+ _fetchObsidianGitHubReleases = fetchObsidianGitHubReleases,
928
+ _extractInstallerInfo = extractInstallerInfo,
929
+ _checkCompatibility = checkCompatibility
930
+ } = {}) {
931
+ const oldVersions = _lodash2.default.keyBy(_nullishCoalesce(_optionalChain([original, 'optionalAccess', _56 => _56.versions]), () => ( [])), (v) => v.version);
932
+ let newVersions = _lodash2.default.cloneDeep(oldVersions);
933
+ const [destkopReleases, commitInfo] = await _fetchObsidianDesktopReleases(
934
+ _optionalChain([original, 'optionalAccess', _57 => _57.metadata, 'access', _58 => _58.commitDate]),
935
+ _optionalChain([original, 'optionalAccess', _59 => _59.metadata, 'access', _60 => _60.commitSha])
936
+ );
937
+ for (const destkopRelease of destkopReleases) {
938
+ const { current, beta } = parseObsidianDesktopRelease(destkopRelease);
939
+ if (beta) {
940
+ newVersions[beta.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[beta.version], () => ( {})), beta);
941
+ }
942
+ newVersions[current.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[current.version], () => ( {})), current);
943
+ }
944
+ const gitHubReleases = await _fetchObsidianGitHubReleases();
945
+ for (const githubRelease of gitHubReleases) {
946
+ if (_semver2.default.valid(githubRelease.name) && !_semver2.default.prerelease(githubRelease.name)) {
947
+ const parsed = parseObsidianGithubRelease(githubRelease);
948
+ const newVersion = _lodash2.default.merge(_nullishCoalesce(newVersions[parsed.version], () => ( {})), parsed);
949
+ for (const installerKey of INSTALLER_KEYS) {
950
+ const oldDigest = _optionalChain([oldVersions, 'access', _61 => _61[parsed.version], 'optionalAccess', _62 => _62.installers, 'access', _63 => _63[installerKey], 'optionalAccess', _64 => _64.digest]);
951
+ const newDigest = _optionalChain([newVersion, 'access', _65 => _65.installers, 'optionalAccess', _66 => _66[installerKey], 'optionalAccess', _67 => _67.digest]);
952
+ if (oldDigest && oldDigest != newDigest) {
953
+ newVersion.installers[installerKey] = { digest: newDigest };
954
+ }
955
+ }
956
+ newVersions[parsed.version] = newVersion;
957
+ }
958
+ }
959
+ newVersions = _lodash2.default.omitBy(newVersions, (v) => BROKEN_VERSIONS.includes(v.version));
960
+ const newInstallers = Object.values(newVersions).flatMap((v) => INSTALLER_KEYS.map((k) => [v, k])).filter(([v, key]) => _optionalChain([v, 'access', _68 => _68.downloads, 'optionalAccess', _69 => _69[key]]) && !_optionalChain([v, 'access', _70 => _70.installers, 'optionalAccess', _71 => _71[key], 'optionalAccess', _72 => _72.chrome]));
961
+ const installerInfos = await pool(maxInstances, newInstallers, async ([v, key]) => {
962
+ const installerInfo = await _extractInstallerInfo(v.version, key, v.downloads[key]);
963
+ return { version: v.version, installers: { [key]: installerInfo } };
964
+ });
965
+ for (const installerInfo of installerInfos) {
966
+ newVersions[installerInfo.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[installerInfo.version], () => ( {})), installerInfo);
967
+ }
968
+ const compatInfos = await getCompatibilityInfos(
969
+ Object.values(newVersions),
970
+ { _checkCompatibility }
971
+ );
972
+ for (const compatInfo of compatInfos) {
973
+ newVersions[compatInfo.version] = _lodash2.default.merge(_nullishCoalesce(newVersions[compatInfo.version], () => ( {})), compatInfo);
974
+ }
975
+ const result = {
976
+ metadata: {
977
+ schemaVersion: obsidianVersionsSchemaVersion,
978
+ commitDate: commitInfo.commitDate,
979
+ commitSha: commitInfo.commitSha,
980
+ timestamp: _nullishCoalesce(_optionalChain([original, 'optionalAccess', _73 => _73.metadata, 'access', _74 => _74.timestamp]), () => ( ""))
981
+ // set down below
982
+ },
983
+ versions: Object.values(newVersions).map(normalizeObsidianVersionInfo).sort((a, b) => _semver2.default.compare(a.version, b.version))
984
+ };
985
+ const dayMs = 24 * 60 * 60 * 1e3;
986
+ const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(_nullishCoalesce(_optionalChain([original, 'optionalAccess', _75 => _75.metadata, 'access', _76 => _76.timestamp]), () => ( 0))).getTime();
987
+ if (!_lodash2.default.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {
988
+ result.metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
989
+ }
990
+ return result;
991
+ }
694
992
 
695
993
  // src/launcher.ts
696
994
  var currentPlatform = {
@@ -698,6 +996,7 @@ var currentPlatform = {
698
996
  arch: process.arch
699
997
  };
700
998
  _dotenv2.default.config({ path: [".env"], quiet: true });
999
+ var minSupportedObsidianVersion = "0.12.8";
701
1000
  var ObsidianLauncher = class {
702
1001
  /**
703
1002
  * Construct an ObsidianLauncher.
@@ -731,7 +1030,7 @@ var ObsidianLauncher = class {
731
1030
  if (!(dest in this.metadataCache)) {
732
1031
  let data;
733
1032
  let error;
734
- const cacheMtime = await _asyncOptionalChain([(await _promises2.default.stat(dest).catch(() => void 0)), 'optionalAccess', async _60 => _60.mtime]);
1033
+ const cacheMtime = await _asyncOptionalChain([(await _promises2.default.stat(dest).catch(() => void 0)), 'optionalAccess', async _77 => _77.mtime]);
735
1034
  if (url.startsWith("file:")) {
736
1035
  data = JSON.parse(await _promises2.default.readFile(_url.fileURLToPath.call(void 0, url), "utf-8"));
737
1036
  }
@@ -761,8 +1060,8 @@ var ObsidianLauncher = class {
761
1060
  if (!data && await fileExists(dest)) {
762
1061
  const parsed = JSON.parse(await _promises2.default.readFile(dest, "utf-8"));
763
1062
  if (cacheValid(parsed)) {
764
- console.warn(error);
765
- console.warn(`Unable to download ${url}, using cached file.`);
1063
+ consola.warn(error);
1064
+ consola.warn(`Unable to download ${url}, using cached file.`);
766
1065
  data = parsed;
767
1066
  }
768
1067
  }
@@ -835,8 +1134,11 @@ var ObsidianLauncher = class {
835
1134
  appVersion = appVersionInfo.version;
836
1135
  let installerVersionInfo;
837
1136
  const { platform, arch } = process;
838
- if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {
839
- throw Error(`No installers available for Obsidian ${appVersion}`);
1137
+ if (_semver2.default.lt(appVersion, minSupportedObsidianVersion)) {
1138
+ warnOnce(
1139
+ "unsupported-version",
1140
+ `Obsidian versions before ${minSupportedObsidianVersion} are unsupported, some obsidian-launcher features will not work.`
1141
+ );
840
1142
  }
841
1143
  if (installerVersion == "latest") {
842
1144
  installerVersionInfo = _lodash2.default.findLast(
@@ -845,7 +1147,7 @@ var ObsidianLauncher = class {
845
1147
  );
846
1148
  } else if (installerVersion == "earliest") {
847
1149
  installerVersionInfo = versions.find(
848
- (v) => _semver2.default.gte(v.version, appVersionInfo.minInstallerVersion) && !!this.getInstallerKey(v, { platform, arch })
1150
+ (v) => appVersionInfo.minInstallerVersion && _semver2.default.gte(v.version, appVersionInfo.minInstallerVersion) && _semver2.default.lte(v.version, appVersionInfo.version) && !!this.getInstallerKey(v, { platform, arch })
849
1151
  );
850
1152
  } else {
851
1153
  installerVersion = _nullishCoalesce(_semver2.default.valid(installerVersion), () => ( installerVersion));
@@ -853,14 +1155,15 @@ var ObsidianLauncher = class {
853
1155
  }
854
1156
  if (!installerVersionInfo) {
855
1157
  if (["earliest", "latest"].includes(installerVersion)) {
856
- throw Error(`No compatible installers available for Obsidian ${appVersion}`);
1158
+ throw Error(`No compatible installers available for Obsidian ${appVersion} for ${platform}-${arch}`);
857
1159
  } else {
858
1160
  throw Error(`No Obsidian installer ${installerVersion} found`);
859
1161
  }
860
1162
  }
861
- if (_semver2.default.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) || _semver2.default.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)) {
862
- throw Error(
863
- `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer >=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ${installerVersionInfo.version} specified`
1163
+ if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion || _semver2.default.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) || _semver2.default.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)) {
1164
+ warnOnce(
1165
+ `incompatible-versions-${appVersionInfo.version}-${installerVersionInfo.version}`,
1166
+ `App and installer versions are incompatible: app ${appVersionInfo.version} is compatible with installer >=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ${installerVersionInfo.version} specified.`
864
1167
  );
865
1168
  }
866
1169
  return [appVersionInfo.version, installerVersionInfo.version];
@@ -877,7 +1180,7 @@ var ObsidianLauncher = class {
877
1180
  appVersion = versions.filter((v) => !v.isBeta).at(-1).version;
878
1181
  } else if (appVersion == "earliest") {
879
1182
  const manifest = await this.getRootManifest();
880
- if (!_optionalChain([manifest, 'optionalAccess', _61 => _61.minAppVersion])) {
1183
+ if (!_optionalChain([manifest, 'optionalAccess', _78 => _78.minAppVersion])) {
881
1184
  throw Error('Unable to resolve Obsidian appVersion "earliest", no manifest.json or minAppVersion found.');
882
1185
  }
883
1186
  appVersion = manifest.minAppVersion;
@@ -941,6 +1244,20 @@ var ObsidianLauncher = class {
941
1244
  );
942
1245
  }
943
1246
  }
1247
+ /**
1248
+ * Log into the Obsidian api using your Insider's account so you can download beta versions.
1249
+ *
1250
+ * login will be called automatically when using downloadApp on an Obsidian beta version so you usually won't need
1251
+ * to call this directly.
1252
+ */
1253
+ async login() {
1254
+ if (!this.obsidianApiToken) {
1255
+ this.obsidianApiToken = await obsidianApiLogin({
1256
+ interactive: this.interactive,
1257
+ savePath: _path2.default.join(this.cacheDir, "obsidian-credentials.env")
1258
+ });
1259
+ }
1260
+ }
944
1261
  /**
945
1262
  * Downloads the Obsidian installer for the given version and platform/arch (defaults to host platform/arch).
946
1263
  * Returns the file path.
@@ -969,9 +1286,9 @@ var ObsidianLauncher = class {
969
1286
  throw Error(`Unsupported platform ${platform}`);
970
1287
  }
971
1288
  await atomicCreate(installerDir, async (scratch) => {
972
- console.log(`Downloading Obsidian installer v${installerVersion}...`);
1289
+ consola.log(`Downloading Obsidian installer v${installerVersion}...`);
973
1290
  const installer = _path2.default.join(scratch, "installer");
974
- await downloadResponse(await fetch(installerInfo.url), installer);
1291
+ await downloadResponse(() => fetch(installerInfo.url), installer);
975
1292
  const extracted = _path2.default.join(scratch, "extracted");
976
1293
  await extractor(installer, extracted);
977
1294
  return extracted;
@@ -982,8 +1299,8 @@ var ObsidianLauncher = class {
982
1299
  * Downloads the Obsidian asar for the given version. Returns the file path.
983
1300
  *
984
1301
  * To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the
985
- * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian beta
986
- * with `npx obsidian-launcher download app -v latest-beta`
1302
+ * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` environment variables (`.env` file is supported) or pre-download the
1303
+ * Obsidian beta with `npx obsidian-launcher download app -v latest-beta`
987
1304
  *
988
1305
  * @param appVersion Obsidian version to download
989
1306
  */
@@ -995,23 +1312,18 @@ var ObsidianLauncher = class {
995
1312
  }
996
1313
  const appPath = _path2.default.join(this.cacheDir, "obsidian-app", `obsidian-${versionInfo.version}.asar`);
997
1314
  const isInsiders = new URL(appUrl).hostname.endsWith(".obsidian.md");
998
- if (isInsiders && !this.obsidianApiToken && !await fileExists(appPath)) {
999
- this.obsidianApiToken = await obsidianApiLogin({
1000
- interactive: this.interactive,
1001
- savePath: _path2.default.join(this.cacheDir, "obsidian-credentials.env")
1002
- });
1315
+ if (isInsiders && !await fileExists(appPath)) {
1316
+ await this.login();
1003
1317
  }
1004
1318
  await atomicCreate(appPath, async (scratch) => {
1005
- console.log(`Downloading Obsidian app v${versionInfo.version} ...`);
1006
- let response;
1319
+ consola.log(`Downloading Obsidian app v${versionInfo.version} ...`);
1320
+ const archive = _path2.default.join(scratch, "app.asar.gz");
1321
+ const asar = _path2.default.join(scratch, "app.asar");
1007
1322
  if (isInsiders) {
1008
- response = await fetchObsidianApi(appUrl, { token: this.obsidianApiToken });
1323
+ await downloadResponse(() => fetchObsidianApi(appUrl, { token: this.obsidianApiToken }), archive);
1009
1324
  } else {
1010
- response = await fetch(appUrl);
1325
+ await downloadResponse(() => fetch(appUrl), archive);
1011
1326
  }
1012
- const archive = _path2.default.join(scratch, "app.asar.gz");
1013
- const asar = _path2.default.join(scratch, "app.asar");
1014
- await downloadResponse(response, archive);
1015
1327
  await extractGz(archive, asar);
1016
1328
  return asar;
1017
1329
  }, { replace: false });
@@ -1039,7 +1351,7 @@ var ObsidianLauncher = class {
1039
1351
  chromedriverPath = _path2.default.join(chromedriverDir, `chromedriver`);
1040
1352
  }
1041
1353
  await atomicCreate(chromedriverDir, async (scratch) => {
1042
- console.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
1354
+ consola.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
1043
1355
  const chromedriverZipPath = await _get.downloadArtifact.call(void 0, {
1044
1356
  version: installerInfo.electron,
1045
1357
  artifactName: "chromedriver",
@@ -1064,9 +1376,9 @@ var ObsidianLauncher = class {
1064
1376
  }
1065
1377
  const apkPath = _path2.default.join(this.cacheDir, "obsidian-apk", `obsidian-${versionInfo.version}.apk`);
1066
1378
  await atomicCreate(apkPath, async (scratch) => {
1067
- console.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
1379
+ consola.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
1068
1380
  const dest = _path2.default.join(scratch, "obsidian.apk");
1069
- await downloadResponse(await fetch(apkUrl), dest);
1381
+ await downloadResponse(() => fetch(apkUrl), dest);
1070
1382
  return dest;
1071
1383
  }, { replace: false });
1072
1384
  return apkPath;
@@ -1100,11 +1412,12 @@ var ObsidianLauncher = class {
1100
1412
  await Promise.all(
1101
1413
  Object.entries(assetsToDownload).map(async ([file, required]) => {
1102
1414
  const url = `https://github.com/${repo}/releases/download/${version}/${file}`;
1103
- const response = await fetch(url);
1104
- if (response.ok) {
1105
- await downloadResponse(response, _path2.default.join(scratch, file));
1106
- } else if (required) {
1107
- throw Error(`No ${file} found for ${repo} version ${version}`);
1415
+ try {
1416
+ await downloadResponse(() => fetch(url), _path2.default.join(scratch, file));
1417
+ } catch (e3) {
1418
+ if (required) {
1419
+ throw Error(`No ${file} found for ${repo} version ${version}`);
1420
+ }
1108
1421
  }
1109
1422
  })
1110
1423
  );
@@ -1274,10 +1587,9 @@ var ObsidianLauncher = class {
1274
1587
  assetsToDownload.map(
1275
1588
  async (file) => {
1276
1589
  const url = `${baseUrl}/${file}`;
1277
- const response = await fetch(url);
1278
- if (response.ok) {
1279
- await downloadResponse(response, _path2.default.join(scratch, file));
1280
- } else {
1590
+ try {
1591
+ await downloadResponse(() => fetch(url), _path2.default.join(scratch, file));
1592
+ } catch (e4) {
1281
1593
  throw Error(`No ${file} found for ${repo}`);
1282
1594
  }
1283
1595
  }
@@ -1440,6 +1752,9 @@ var ObsidianLauncher = class {
1440
1752
  }
1441
1753
  }
1442
1754
  });
1755
+ if (_semver2.default.lt(appVersion, "0.10.0")) {
1756
+ obsidianJson.last_open = vaultId;
1757
+ }
1443
1758
  }
1444
1759
  await _promises2.default.writeFile(_path2.default.join(configDir, "obsidian.json"), JSON.stringify(obsidianJson));
1445
1760
  await _promises2.default.writeFile(_path2.default.join(configDir, "Preferences"), JSON.stringify(chromePreferences));
@@ -1527,39 +1842,7 @@ var ObsidianLauncher = class {
1527
1842
  * the internal Electron version.
1528
1843
  */
1529
1844
  async updateVersionList(original, opts = {}) {
1530
- const { maxInstances = 1 } = opts;
1531
- const [destkopReleases, commitInfo] = await fetchObsidianDesktopReleases(
1532
- _optionalChain([original, 'optionalAccess', _62 => _62.metadata, 'access', _63 => _63.commitDate]),
1533
- _optionalChain([original, 'optionalAccess', _64 => _64.metadata, 'access', _65 => _65.commitSha])
1534
- );
1535
- const gitHubReleases = await fetchObsidianGitHubReleases();
1536
- let newVersions = updateObsidianVersionList({
1537
- original: _optionalChain([original, 'optionalAccess', _66 => _66.versions]),
1538
- destkopReleases,
1539
- gitHubReleases
1540
- });
1541
- const newInstallers = newVersions.flatMap((v) => INSTALLER_KEYS.map((k) => [v, k])).filter(([v, key]) => _optionalChain([v, 'access', _67 => _67.downloads, 'optionalAccess', _68 => _68[key]]) && !_optionalChain([v, 'access', _69 => _69.installers, 'optionalAccess', _70 => _70[key], 'optionalAccess', _71 => _71.chrome]));
1542
- const installerInfos = await pool(maxInstances, newInstallers, async ([v, key]) => {
1543
- const installerInfo = await extractInstallerInfo(key, v.downloads[key]);
1544
- return { version: v.version, key, installerInfo };
1545
- });
1546
- newVersions = updateObsidianVersionList({ original: newVersions, installerInfos });
1547
- const result = {
1548
- metadata: {
1549
- schemaVersion: obsidianVersionsSchemaVersion,
1550
- commitDate: commitInfo.commitDate,
1551
- commitSha: commitInfo.commitSha,
1552
- timestamp: _nullishCoalesce(_optionalChain([original, 'optionalAccess', _72 => _72.metadata, 'access', _73 => _73.timestamp]), () => ( ""))
1553
- // set down below
1554
- },
1555
- versions: newVersions
1556
- };
1557
- const dayMs = 24 * 60 * 60 * 1e3;
1558
- const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(_nullishCoalesce(_optionalChain([original, 'optionalAccess', _74 => _74.metadata, 'access', _75 => _75.timestamp]), () => ( 0))).getTime();
1559
- if (!_lodash2.default.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {
1560
- result.metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
1561
- }
1562
- return result;
1845
+ return updateObsidianVersionList(original, { maxInstances: opts.maxInstances });
1563
1846
  }
1564
1847
  /**
1565
1848
  * Returns true if the Obsidian version is already in the cache.
@@ -1588,7 +1871,7 @@ var ObsidianLauncher = class {
1588
1871
  return false;
1589
1872
  }
1590
1873
  if (new URL(versionInfo.downloads.asar).hostname.endsWith(".obsidian.md")) {
1591
- const hasCreds = !!(process.env["OBSIDIAN_EMAIL"] && process.env["OBSIDIAN_PASSWORD"]);
1874
+ const hasCreds = !!(process.env["OBSIDIAN_EMAIL"] && process.env["OBSIDIAN_PASSWORD"]) || await fileExists(_path2.default.join(this.cacheDir, "obsidian-credentials.env"));
1592
1875
  const inCache = await this.isInCache("app", versionInfo.version);
1593
1876
  return hasCreds || inCache;
1594
1877
  } else {
@@ -1600,5 +1883,7 @@ var ObsidianLauncher = class {
1600
1883
 
1601
1884
 
1602
1885
 
1603
- exports.watchFiles = watchFiles; exports.ObsidianLauncher = ObsidianLauncher;
1604
- //# sourceMappingURL=chunk-EPJSKILH.cjs.map
1886
+
1887
+
1888
+ exports.consola = consola; exports.watchFiles = watchFiles; exports.minSupportedObsidianVersion = minSupportedObsidianVersion; exports.ObsidianLauncher = ObsidianLauncher;
1889
+ //# sourceMappingURL=chunk-GVT27UTK.cjs.map