obsidian-launcher 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,7 +82,7 @@ Options:
82
82
  - `-p, --plugin <plugin>`: Plugin(s) to install
83
83
  - `-t, --theme <plugin>`: Theme(s) to install
84
84
  - `--copy`: Copy the vault first
85
- - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ".obsidian-cache")
85
+ - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ~/.obsidian-cache)
86
86
 
87
87
  ### watch
88
88
  Downloads Obsidian and opens a vault, then watches for changes to plugins and themes.
@@ -98,7 +98,7 @@ Options:
98
98
  - `-p, --plugin <plugin>`: Plugin(s) to install
99
99
  - `-t, --theme <plugin>`: Theme to install
100
100
  - `--copy`: Copy the vault first
101
- - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ".obsidian-cache")
101
+ - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ~/.obsidian-cache)
102
102
 
103
103
  ### install
104
104
  Install plugins and themes into an Obsidian vault.
@@ -109,7 +109,7 @@ Arguments:
109
109
  Options:
110
110
  - `-p, --plugin <plugin>`: Plugin(s) to install
111
111
  - `-t, --theme <plugin>`: Theme(s) to install.
112
- - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ".obsidian-cache")
112
+ - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ~/.obsidian-cache)
113
113
 
114
114
  ### download
115
115
  Download Obsidian to the cache.
@@ -128,5 +128,5 @@ Options:
128
128
  - `-i, --installer <version>`: Obsidian installer version (default: "earliest")
129
129
  - `--platform <platform>`: Platform of the installer, one of linux, win32, darwin. (default: system platform)
130
130
  - `--arch <arch>`: Architecture of the installer, one of arm64, ia32, x64. (default: system arch)
131
- - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ".obsidian-cache")
131
+ - `-c, --cache <cache>`: Directory to use as the download cache (default: OBSIDIAN_CACHE env var or ~/.obsidian-cache)
132
132
 
@@ -77,6 +77,10 @@ async function linkOrCp(src, dest) {
77
77
  await fsAsync.copyFile(src, dest);
78
78
  }
79
79
  }
80
+ function pathIsUnder(parent, child) {
81
+ const rel = path.relative(path.resolve(parent), path.resolve(child));
82
+ return rel != "" && rel.split(path.sep)[0] != ".." && !path.isAbsolute(rel);
83
+ }
80
84
  async function sleep(ms) {
81
85
  return new Promise((resolve) => setTimeout(resolve, ms));
82
86
  }
@@ -176,6 +180,7 @@ import crypto from "crypto";
176
180
  import extractZip from "extract-zip";
177
181
  import { downloadArtifact } from "@electron/get";
178
182
  import child_process2 from "child_process";
183
+ import os3 from "os";
179
184
  import semver2 from "semver";
180
185
  import { fileURLToPath as fileURLToPath2 } from "url";
181
186
  import _4 from "lodash";
@@ -434,6 +439,7 @@ var ChromeLocalStorage = class {
434
439
  // src/launcherUtils.ts
435
440
  import fsAsync3 from "fs/promises";
436
441
  import fs3 from "fs";
442
+ import os2 from "os";
437
443
  import path4 from "path";
438
444
  import { promisify } from "util";
439
445
  import child_process from "child_process";
@@ -523,17 +529,18 @@ async function extractObsidianDmg(dmg, dest) {
523
529
  }
524
530
  });
525
531
  }
526
- async function getCdpSession(launcher, appVersion, installerVersion) {
527
- [appVersion, installerVersion] = await launcher.resolveVersion(appVersion, installerVersion);
532
+ async function getCdpSession(launcher, params) {
533
+ const [appVersion, installerVersion] = await launcher.resolveVersion(
534
+ params.appVersion ?? "latest",
535
+ params.installerVersion ?? "latest"
536
+ );
528
537
  const cleanup = [];
529
538
  const doCleanup = async () => {
530
539
  for (const func of [...cleanup].reverse()) {
531
540
  await func();
532
541
  }
533
542
  };
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");
543
+ const pluginDir = path4.join(params.vault, ".obsidian", "plugins", "obsidian-launcher");
537
544
  await fsAsync3.mkdir(pluginDir, { recursive: true });
538
545
  await fsAsync3.writeFile(path4.join(pluginDir, "manifest.json"), JSON.stringify({
539
546
  id: "obsidian-launcher",
@@ -551,19 +558,23 @@ async function getCdpSession(launcher, appVersion, installerVersion) {
551
558
  }
552
559
  module.exports = ObsidianLauncherPlugin;
553
560
  `);
554
- await fsAsync3.writeFile(path4.join(vault, ".obsidian", "community-plugins.json"), JSON.stringify([
555
- "obsidian-launcher"
556
- ]));
561
+ const communityPluginsPath = path4.join(params.vault, ".obsidian", "community-plugins.json");
562
+ let communityPlugins = ["obsidian-launcher"];
563
+ if (await fileExists(communityPluginsPath)) {
564
+ communityPlugins = [...JSON.parse(await fsAsync3.readFile(communityPluginsPath, "utf-8")), ...communityPlugins];
565
+ }
566
+ await fsAsync3.writeFile(communityPluginsPath, JSON.stringify(communityPlugins));
557
567
  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"]
568
+ const launchResult = await launcher.launch({
569
+ ...params,
564
570
  // will choose a random available port
571
+ args: [`--remote-debugging-port=0`, "--test-type=webdriver", ...params.args ?? []]
565
572
  });
566
- cleanup.push(() => fsAsync3.rm(configDir, { recursive: true, force: true }));
573
+ if (params.copy) {
574
+ cleanup.push(() => fsAsync3.rm(launchResult.vault, { recursive: true, force: true }));
575
+ }
576
+ const { proc } = launchResult;
577
+ cleanup.push(() => fsAsync3.rm(launchResult.configDir, { recursive: true, force: true }));
567
578
  const procExit = new Promise((resolve) => proc.on("close", (code) => resolve(code ?? -1)));
568
579
  cleanup.push(async () => {
569
580
  proc.kill("SIGTERM");
@@ -595,9 +606,10 @@ async function getCdpSession(launcher, appVersion, installerVersion) {
595
606
  { timeout: 5e3 }
596
607
  );
597
608
  return {
609
+ ...launchResult,
598
610
  client,
599
611
  cleanup: doCleanup,
600
- proc
612
+ vault: launchResult.vault
601
613
  };
602
614
  } catch (e) {
603
615
  await doCleanup();
@@ -614,6 +626,85 @@ async function cdpEvaluate(client, expression) {
614
626
  async function cdpEvaluateUntil(client, expression, opts) {
615
627
  return await until(() => cdpEvaluate(client, expression), opts);
616
628
  }
629
+ async function getProcesses() {
630
+ if (process.platform === "win32") {
631
+ const { stdout } = await execFile("powershell.exe", [
632
+ "-NoProfile",
633
+ "-ExecutionPolicy",
634
+ "Bypass",
635
+ "-Command",
636
+ "Get-CimInstance Win32_Process | Sort-Object -Property CreationDate | Select-Object ProcessId,CommandLine | ConvertTo-Json"
637
+ ]);
638
+ const data = JSON.parse(stdout);
639
+ const list = Array.isArray(data) ? data : [data];
640
+ return list.map((p) => ({ pid: p.ProcessId, command: p.CommandLine || "" }));
641
+ } else {
642
+ const { stdout } = await execFile("ps", ["-xww", "-o", "lstart=,pid=,command="]);
643
+ const processes = stdout.split("\n").map((l) => l.trim()).filter((line) => line).map((line) => {
644
+ const [_5, startTime, pid, command] = line.match(/^(\w+ \w+ \d+ \d\d:\d\d:\d\d \d\d\d\d)\s+(\d+)\s+(.*)$/);
645
+ return {
646
+ pid: Number(pid),
647
+ startTime: new Date(startTime).getTime(),
648
+ command
649
+ };
650
+ });
651
+ return _3.sortBy(processes, (i) => i.startTime).map((i) => _3.omit(i, "startTime"));
652
+ }
653
+ }
654
+ async function getObsidianCli(args) {
655
+ const clean = (s) => {
656
+ return s.trim().match(/^["']?(.*?)["']?$/)[1];
657
+ };
658
+ let processes = await getProcesses();
659
+ processes = processes.filter((p) => p.command.includes("--tag=obsidian-launcher"));
660
+ let obsidianInstances = [];
661
+ for (const proc of processes) {
662
+ try {
663
+ const match2 = proc.command.match(/(.*?) --user-data-dir=(.*?obsidian-launcher-config-.+?)( |$)/);
664
+ const [_5, exe2, configDir] = match2.map(clean);
665
+ const obsidianJson = JSON.parse(await fsAsync3.readFile(path4.join(configDir, "obsidian.json"), "utf-8"));
666
+ for (const [vaultId, vaultInfo] of Object.entries(obsidianJson.vaults)) {
667
+ const vaultPath = await fsAsync3.realpath(vaultInfo.path);
668
+ obsidianInstances.push({ pid: proc.pid, exe: exe2, configDir, vaultId, vaultPath });
669
+ }
670
+ } catch (e) {
671
+ console.warn(`Failed to connect to obsidian-launcher instance ${proc.pid}: ${e}`);
672
+ }
673
+ }
674
+ obsidianInstances = obsidianInstances.reverse();
675
+ let match;
676
+ let newArgs = [...args];
677
+ if (args.length > 0 && args[0].startsWith("vault=")) {
678
+ const vault = args[0].slice(6);
679
+ match = obsidianInstances.find((i) => i.vaultId == vault || path4.basename(i.vaultPath).toUpperCase() == vault.toUpperCase());
680
+ if (!match) {
681
+ const systemTmpDir = await fsAsync3.realpath(os2.tmpdir());
682
+ match = obsidianInstances.find((i) => pathIsUnder(systemTmpDir, i.vaultPath) && path4.basename(i.vaultPath).toUpperCase().match(/(.*)-.{6}/)?.[1] == vault.toUpperCase());
683
+ }
684
+ if (!match) {
685
+ throw Error(`No running Obsidian instance for ${vault}`);
686
+ }
687
+ newArgs = args.slice(1);
688
+ }
689
+ if (!match) {
690
+ const cwd = await fsAsync3.realpath(process.cwd()).catch(() => process.cwd());
691
+ match = obsidianInstances.find((i) => cwd == i.vaultPath || pathIsUnder(i.vaultPath, cwd));
692
+ }
693
+ if (!match) {
694
+ match = obsidianInstances.at(0);
695
+ }
696
+ if (!match) {
697
+ throw Error(`No running Obsidian instance`);
698
+ }
699
+ const exe = match.exe.replace(/.exe$/, ".com");
700
+ newArgs = [
701
+ `vault=${match.vaultId}`,
702
+ ...newArgs,
703
+ `--user-data-dir=${match.configDir}`,
704
+ ...process.platform == "linux" ? ["--no-sandbox"] : []
705
+ ];
706
+ return [exe, newArgs];
707
+ }
617
708
  async function fetchObsidianDesktopReleases(sinceDate, sinceSha) {
618
709
  const repo = "obsidianmd/obsidian-releases";
619
710
  let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {
@@ -769,8 +860,9 @@ async function checkCompatibility(launcher, appVersion, installerVersion) {
769
860
  consola.log(`Checking if app ${appVersion} and installer ${installerVersion} are compatible...`);
770
861
  await launcher.downloadApp(appVersion);
771
862
  await launcher.downloadInstaller(installerVersion);
863
+ const vault = await makeTmpDir("obsidian-launcher-");
772
864
  const cdpResult = await maybe(retry(
773
- () => getCdpSession(launcher, appVersion, installerVersion),
865
+ () => getCdpSession(launcher, { appVersion, installerVersion, vault }),
774
866
  { retries: 3, backoff: 4e3 }
775
867
  ));
776
868
  if (!cdpResult.success) {
@@ -812,6 +904,7 @@ async function checkCompatibility(launcher, appVersion, installerVersion) {
812
904
  }
813
905
  } finally {
814
906
  await cleanup();
907
+ await fsAsync3.rm(vault, { recursive: true, force: true });
815
908
  }
816
909
  consola.log(`app ${appVersion} and installer ${installerVersion} are ${!result ? "in" : ""}compatible`);
817
910
  return result;
@@ -996,7 +1089,7 @@ var minSupportedObsidianVersion = "0.12.8";
996
1089
  var ObsidianLauncher = class {
997
1090
  /**
998
1091
  * Construct an ObsidianLauncher.
999
- * @param opts.cacheDir Path to the cache directory. Defaults to "OBSIDIAN_CACHE" env var or ".obsidian-cache".
1092
+ * @param opts.cacheDir Path to the cache directory. Defaults to "OBSIDIAN_CACHE" env var or ~/.obsidian-cache.
1000
1093
  * @param opts.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.
1001
1094
  * @param opts.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.
1002
1095
  * @param opts.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.
@@ -1005,7 +1098,9 @@ var ObsidianLauncher = class {
1005
1098
  */
1006
1099
  constructor(opts = {}) {
1007
1100
  this.interactive = false;
1008
- this.cacheDir = path5.resolve(opts.cacheDir ?? process.env.OBSIDIAN_CACHE ?? "./.obsidian-cache");
1101
+ this.cacheDir = path5.resolve(
1102
+ opts.cacheDir ?? process.env.OBSIDIAN_CACHE ?? path5.join(os3.homedir(), ".obsidian-cache")
1103
+ );
1009
1104
  const defaultVersionsUrl = "https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json";
1010
1105
  this.versionsUrl = opts.versionsUrl ?? defaultVersionsUrl;
1011
1106
  const defaultCommunityPluginsUrl = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json";
@@ -1728,12 +1823,18 @@ var ObsidianLauncher = class {
1728
1823
  };
1729
1824
  const chromePreferences = _4.merge(
1730
1825
  // disables the "allow pasting" bit in the dev tools console
1731
- { "electron": { "devtools": { "preferences": { "disable-self-xss-warning": "true" } } } },
1826
+ { "electron": { "devtools": { "preferences": {
1827
+ // chrome switched between using kebab-case and CamelCase sometime between 114.0.5735.289 and 120.0.6099.283
1828
+ "disable-self-xss-warning": "true",
1829
+ "disableSelfXssWarning": "true"
1830
+ } } } },
1732
1831
  params.chromePreferences ?? {}
1733
1832
  );
1734
1833
  const obsidianJson = {
1735
- updateDisabled: true
1834
+ updateDisabled: true,
1736
1835
  // prevents Obsidian trying to auto-update on boot.
1836
+ cli: true
1837
+ // enable the CLI
1737
1838
  };
1738
1839
  if (params.vault !== void 0) {
1739
1840
  if (!await fileExists(params.vault)) {
@@ -1826,7 +1927,9 @@ var ObsidianLauncher = class {
1826
1927
  `--user-data-dir=${configDir}`,
1827
1928
  // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510
1828
1929
  ...process.platform == "linux" ? ["--no-sandbox"] : [],
1829
- ...params.args ?? []
1930
+ ...params.args ?? [],
1931
+ "--tag=obsidian-launcher"
1932
+ // hack so we can identify obsidian-launcher processes when connecting the CLI
1830
1933
  ], {
1831
1934
  ...params.spawnOptions
1832
1935
  });
@@ -1874,6 +1977,35 @@ var ObsidianLauncher = class {
1874
1977
  return true;
1875
1978
  }
1876
1979
  }
1980
+ /**
1981
+ * Return the command needed to run the Obsidian CLI.
1982
+ *
1983
+ * As obsidian-launcher sandboxes the config dir for each Obsidian instance, the Obsidian CLI won't connect to the
1984
+ * launched instances by default. This method takes Obsidian CLI args, and then returns an [executable, args] tuple
1985
+ * that can be used to launch the Obsidian CLI against the sandboxed instances.
1986
+ *
1987
+ * Like the the regular Obsidian CLI, it will connect to the instance matching the `vault=` argument if present, or
1988
+ * the cwd.
1989
+ *
1990
+ * Just pass the result to child_process.spawn or child_process.execFile to run the command.
1991
+ *
1992
+ * Example:
1993
+ * ```js
1994
+ * import child_process from "child_process";
1995
+ * import util from "util";
1996
+ * const execFile = util.promisify(child_process.execFile);
1997
+ * const [executable, args] = await launcher.getObsidianCli(["file", "file=Dashboard"]);
1998
+ * const {stdout, stderr} = await execFile(executable, args);
1999
+ * ```
2000
+ *
2001
+ * The Obsidian CLI only works on Obsidian >=1.12.0 with installer >=1.11.7.
2002
+ * See https://help.obsidian.md/cli
2003
+ *
2004
+ * @returns [executable, args] tuple
2005
+ */
2006
+ async getObsidianCli(args) {
2007
+ return await getObsidianCli(args);
2008
+ }
1877
2009
  };
1878
2010
 
1879
2011
  export {
@@ -1882,4 +2014,4 @@ export {
1882
2014
  minSupportedObsidianVersion,
1883
2015
  ObsidianLauncher
1884
2016
  };
1885
- //# sourceMappingURL=chunk-LBBOWJGG.js.map
2017
+ //# sourceMappingURL=chunk-4A3EFE5K.js.map