obsidian-launcher 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -817,9 +817,12 @@ var ObsidianLauncher = class {
817
817
  if (!pluginId) {
818
818
  pluginId = JSON.parse(await fsAsync4.readFile(manifestPath, "utf8").catch(() => "{}")).id;
819
819
  if (!pluginId) {
820
- throw Error(`${pluginPath}/manifest.json malformed.`);
820
+ throw Error(`${manifestPath} malformed.`);
821
821
  }
822
822
  }
823
+ if (!await fileExists(path4.join(pluginPath, "main.js"))) {
824
+ throw Error(`No main.js found under ${pluginPath}`);
825
+ }
823
826
  let enabled;
824
827
  if (typeof plugin == "string") {
825
828
  enabled = true;
@@ -925,6 +928,9 @@ var ObsidianLauncher = class {
925
928
  throw Error(`${themePath}/manifest.json malformed.`);
926
929
  }
927
930
  }
931
+ if (!await fileExists(path4.join(themePath, "theme.css"))) {
932
+ throw Error(`No theme.css found under ${themePath}`);
933
+ }
928
934
  let enabled;
929
935
  if (typeof theme == "string") {
930
936
  enabled = true;
@@ -1269,4 +1275,4 @@ var ObsidianLauncher = class {
1269
1275
  export {
1270
1276
  ObsidianLauncher
1271
1277
  };
1272
- //# sourceMappingURL=chunk-PEIKWKCF.js.map
1278
+ //# sourceMappingURL=chunk-22ZW6ZDG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/launcher.ts","../src/utils.ts","../src/apis.ts","../src/chromeLocalStorage.ts","../src/launcherUtils.ts"],"sourcesContent":["import fsAsync from \"fs/promises\"\nimport fs from \"fs\"\nimport zlib from \"zlib\"\nimport path from \"path\"\nimport crypto from \"crypto\";\nimport extractZip from \"extract-zip\"\nimport { pipeline } from \"stream/promises\";\nimport { downloadArtifact } from '@electron/get';\nimport child_process from \"child_process\"\nimport semver from \"semver\"\nimport { fileExists, makeTmpDir, withTmpDir, linkOrCp, maybe, pool, mergeKeepUndefined } from \"./utils.js\";\nimport {\n ObsidianVersionInfo, ObsidianCommunityPlugin, ObsidianCommunityTheme,\n PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry,\n ObsidianVersionInfos,\n} from \"./types.js\";\nimport { fetchObsidianAPI, fetchGitHubAPIPaginated, fetchWithFileUrl } from \"./apis.js\";\nimport ChromeLocalStorage from \"./chromeLocalStorage.js\";\nimport {\n normalizeGitHubRepo, extractObsidianAppImage, extractObsidianExe, extractObsidianDmg,\n parseObsidianDesktopRelease, parseObsidianGithubRelease, correctObsidianVersionInfo,\n getElectronVersionInfo, normalizeObsidianVersionInfo,\n} from \"./launcherUtils.js\";\nimport _ from \"lodash\"\n\n\n/**\n * The `ObsidianLauncher` class.\n * \n * Helper class that handles downloading and installing Obsidian versions, plugins, and themes and launching Obsidian\n * with sandboxed configuration directories.\n */\nexport class ObsidianLauncher {\n readonly cacheDir: string\n\n readonly versionsUrl: string\n readonly communityPluginsUrl: string\n readonly communityThemesUrl: string\n readonly cacheDuration: number\n\n /** Cached metadata files and requests */\n private metadataCache: Record<string, any>\n\n /**\n * Construct an ObsidianLauncher.\n * @param options.cacheDir Path to the cache directory. Defaults to \"OBSIDIAN_CACHE\" env var or \".obsidian-cache\".\n * @param options.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.\n * @param options.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.\n * @param options.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.\n * @param options.cacheDuration If the cached version list is older than this (in ms), refetch it. Defaults to 30 minutes.\n */\n constructor(options: {\n cacheDir?: string,\n versionsUrl?: string,\n communityPluginsUrl?: string,\n communityThemesUrl?: string,\n cacheDuration?: number,\n } = {}) {\n this.cacheDir = path.resolve(options.cacheDir ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n \n const defaultVersionsUrl = 'https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json'\n this.versionsUrl = options.versionsUrl ?? defaultVersionsUrl;\n \n const defaultCommunityPluginsUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\";\n this.communityPluginsUrl = options.communityPluginsUrl ?? defaultCommunityPluginsUrl;\n\n const defaultCommunityThemesUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\";\n this.communityThemesUrl = options.communityThemesUrl ?? defaultCommunityThemesUrl;\n\n this.cacheDuration = options.cacheDuration ?? (30 * 60 * 1000);\n\n this.metadataCache = {};\n }\n\n /**\n * Returns file content fetched from url as JSON. Caches content to dest and uses that cache if its more recent than\n * cacheDuration ms or if there are network errors.\n */\n private async cachedFetch(url: string, dest: string): Promise<any> {\n dest = path.resolve(dest);\n if (!(dest in this.metadataCache)) {\n let fileContent: string|undefined;\n const mtime = await fileExists(dest) ? (await fsAsync.stat(dest)).mtime : undefined;\n\n if (mtime && new Date().getTime() - mtime.getTime() < this.cacheDuration) { // read from cache if its recent\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else { // otherwise try to fetch the url\n const request = await maybe(fetchWithFileUrl(url));\n if (request.success) {\n await fsAsync.mkdir(path.dirname(dest), { recursive: true });\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.writeFile(path.join(tmpDir, 'download.json'), request.result);\n return path.join(tmpDir, 'download.json');\n })\n fileContent = request.result;\n } else if (await fileExists(dest)) { // use cache on network error\n console.warn(request.error)\n console.warn(`Unable to download ${dest}, using cached file.`);\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else {\n throw request.error;\n }\n }\n\n this.metadataCache[dest] = JSON.parse(fileContent);\n }\n return this.metadataCache[dest];\n }\n\n /**\n * Get parsed content of the current project's manifest.json\n */\n private async getRootManifest(): Promise<any> {\n if (!('manifest.json' in this.metadataCache)) {\n const root = path.parse(process.cwd()).root;\n let dir = process.cwd();\n while (dir != root && !(await fileExists(path.join(dir, 'manifest.json')))) {\n dir = path.dirname(dir);\n }\n const manifestPath = path.join(dir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n this.metadataCache['manifest.json'] = JSON.parse(await fsAsync.readFile(manifestPath, 'utf-8'));\n } else {\n this.metadataCache['manifest.json'] = null;\n }\n }\n return this.metadataCache['manifest.json'];\n }\n\n /**\n * Get information about all available Obsidian versions.\n */\n async getVersions(): Promise<ObsidianVersionInfo[]> {\n const dest = path.join(this.cacheDir, \"obsidian-versions.json\");\n return (await this.cachedFetch(this.versionsUrl, dest)).versions;\n }\n\n /**\n * Get information about all available community plugins.\n */\n async getCommunityPlugins(): Promise<ObsidianCommunityPlugin[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-plugins.json\");\n return await this.cachedFetch(this.communityPluginsUrl, dest);\n }\n\n /**\n * Get information about all available community themes.\n */\n async getCommunityThemes(): Promise<ObsidianCommunityTheme[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-css-themes.json\");\n return await this.cachedFetch(this.communityThemesUrl, dest);\n }\n\n /**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * @param appVersion Obsidian version string or one of \n * - \"latest\": Get the current latest non-beta Obsidian version\n * - \"latest-beta\": Get the current latest beta Obsidian version (or latest is there is no current beta)\n * - \"earliest\": Get the `minAppVersion` set in set in your `manifest.json`\n * @param installerVersion Obsidian version string or one of \n * - \"latest\": Get the latest Obsidian installer\n * - \"earliest\": Get the oldest Obsidian installer compatible with the specified Obsidian app version\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\n async resolveVersions(appVersion: string, installerVersion = \"latest\"): Promise<[string, string]> {\n const versions = await this.getVersions();\n const appVersionInfo = await this.getVersionInfo(appVersion);\n\n if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {\n throw Error(`No compatible installers available for app version ${appVersion}`);\n }\n if (installerVersion == \"latest\") {\n installerVersion = appVersionInfo.maxInstallerVersion;\n } else if (installerVersion == \"earliest\") {\n installerVersion = appVersionInfo.minInstallerVersion;\n } else {\n installerVersion = semver.valid(installerVersion) ?? installerVersion;\n }\n const installerVersionInfo = versions.find(v => v.version == installerVersion);\n if (!installerVersionInfo || !installerVersionInfo.chromeVersion) {\n throw Error(`No Obsidian installer for version ${installerVersion} found`);\n }\n if (\n semver.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) ||\n semver.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)\n ) {\n throw Error(\n `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer ` +\n `>=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ` +\n `${installerVersionInfo.version} specified`\n )\n }\n\n return [appVersionInfo.version, installerVersionInfo.version];\n }\n\n /**\n * Gets details about an Obsidian version.\n * @param appVersion Obsidian app version (see {@link resolveVersions} for format)\n */\n async getVersionInfo(appVersion: string): Promise<ObsidianVersionInfo> {\n const versions = await this.getVersions();\n if (appVersion == \"latest-beta\") {\n appVersion = versions.at(-1)!.version;\n } else if (appVersion == \"latest\") {\n appVersion = versions.filter(v => !v.isBeta).at(-1)!.version;\n } else if (appVersion == \"earliest\") {\n appVersion = (await this.getRootManifest())?.minAppVersion;\n if (!appVersion) {\n throw Error('Unable to resolve Obsidian app appVersion \"earliest\", no manifest.json or minAppVersion found.')\n }\n } else {\n // if invalid match won't be found and we'll throw error below\n appVersion = semver.valid(appVersion) ?? appVersion;\n }\n const versionInfo = versions.find(v => v.version == appVersion);\n if (!versionInfo) {\n throw Error(`No Obsidian app version ${appVersion} found`);\n }\n\n return versionInfo;\n }\n\n /**\n * Downloads the Obsidian installer for the given version and platform. Returns the file path.\n * @param installerVersion Obsidian installer version to download. (see {@link resolveVersions} for format)\n */\n async downloadInstaller(installerVersion: string): Promise<string> {\n const installerVersionInfo = await this.getVersionInfo(installerVersion);\n return await this.downloadInstallerFromVersionInfo(installerVersionInfo);\n }\n\n /**\n * Helper for downloadInstaller that doesn't require the obsidian-versions.json file so it can be used in\n * updateObsidianVersionInfos\n */\n private async downloadInstallerFromVersionInfo(versionInfo: ObsidianVersionInfo): Promise<string> {\n const installerVersion = versionInfo.version;\n const {platform, arch} = process;\n const cacheDir = path.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);\n \n let installerPath: string\n let downloader: ((tmpDir: string) => Promise<string>)|undefined\n \n if (platform == \"linux\") {\n installerPath = path.join(cacheDir, \"obsidian\");\n let installerUrl: string|undefined\n if (arch.startsWith(\"arm\")) {\n installerUrl = versionInfo.downloads.appImageArm;\n } else {\n installerUrl = versionInfo.downloads.appImage;\n }\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const appImage = path.join(tmpDir, \"Obsidian.AppImage\");\n await fsAsync.writeFile(appImage, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianAppImage(appImage, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"win32\") {\n installerPath = path.join(cacheDir, \"Obsidian.exe\")\n const installerUrl = versionInfo.downloads.exe;\n let appArch: string|undefined\n if (arch == \"x64\") {\n appArch = \"app-64\"\n } else if (arch == \"ia32\") {\n appArch = \"app-32\"\n } else if (arch.startsWith(\"arm\")) {\n appArch = \"app-arm64\"\n }\n if (installerUrl && appArch) {\n downloader = async (tmpDir) => {\n const installerExecutable = path.join(tmpDir, \"Obsidian.exe\");\n await fsAsync.writeFile(installerExecutable, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianExe(installerExecutable, appArch, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"darwin\") {\n installerPath = path.join(cacheDir, \"Contents/MacOS/Obsidian\");\n const installerUrl = versionInfo.downloads.dmg;\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const dmg = path.join(tmpDir, \"Obsidian.dmg\");\n await fsAsync.writeFile(dmg, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianDmg(dmg, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else {\n throw Error(`Unsupported platform ${platform}`);\n }\n if (!downloader) {\n throw Error(`No Obsidian installer download available for v${installerVersion} ${platform} ${arch}`);\n }\n\n if (!(await fileExists(installerPath))) {\n console.log(`Downloading Obsidian installer v${installerVersion}...`)\n await fsAsync.mkdir(path.dirname(cacheDir), { recursive: true });\n await withTmpDir(cacheDir, downloader);\n }\n\n return installerPath;\n }\n\n /**\n * Downloads the Obsidian asar for the given version and platform. Returns the file path.\n * \n * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`\n * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * \n * @param appVersion Obsidian version to download (see {@link resolveVersions} for format)\n */\n async downloadApp(appVersion: string): Promise<string> {\n const appVersionInfo = await this.getVersionInfo(appVersion);\n const appUrl = appVersionInfo.downloads.asar;\n if (!appUrl) {\n throw Error(`No asar found for Obsidian version ${appVersion}`);\n }\n const appPath = path.join(this.cacheDir, 'obsidian-app', `obsidian-${appVersionInfo.version}.asar`);\n\n if (!(await fileExists(appPath))) {\n console.log(`Downloading Obsidian app v${appVersion} ...`)\n await fsAsync.mkdir(path.dirname(appPath), { recursive: true });\n\n await withTmpDir(appPath, async (tmpDir) => {\n const isInsidersBuild = new URL(appUrl).hostname.endsWith('.obsidian.md');\n const response = isInsidersBuild ? await fetchObsidianAPI(appUrl) : await fetch(appUrl);\n const archive = path.join(tmpDir, 'app.asar.gz');\n const asar = path.join(tmpDir, 'app.asar')\n await fsAsync.writeFile(archive, response.body as any);\n await pipeline(fs.createReadStream(archive), zlib.createGunzip(), fs.createWriteStream(asar));\n return asar;\n })\n }\n\n return appPath;\n }\n\n /**\n * Downloads chromedriver for the given Obsidian version.\n * \n * wdio will download chromedriver from the Chrome for Testing API automatically (see\n * https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints). However, Google has only put\n * chromedriver since v115.0.5763.0 in that API, so wdio can't automatically download older versions of chromedriver\n * for old Electron versions. Here we download chromedriver for older versions ourselves using the @electron/get\n * package which fetches chromedriver from https://github.com/electron/electron/releases.\n * \n * @param installerVersion Obsidian installer version (see {@link resolveVersions} for format)\n */\n async downloadChromedriver(installerVersion: string): Promise<string> {\n const versionInfo = await this.getVersionInfo(installerVersion);\n const electronVersion = versionInfo.electronVersion;\n if (!electronVersion) {\n throw Error(`${installerVersion} is not an Obsidian installer version.`)\n }\n\n const chromedriverZipPath = await downloadArtifact({\n version: electronVersion,\n artifactName: 'chromedriver',\n cacheRoot: path.join(this.cacheDir, \"chromedriver-legacy\"),\n unsafelyDisableChecksums: true, // the checksums are slow and run even on cache hit.\n });\n\n let chromedriverPath: string\n if (process.platform == \"win32\") {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver.exe\");\n } else {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver\");\n }\n\n if (!(await fileExists(chromedriverPath))) {\n console.log(`Downloading legacy chromedriver for electron ${electronVersion} ...`)\n await withTmpDir(chromedriverPath, async (tmpDir) => {\n await extractZip(chromedriverZipPath, { dir: tmpDir });\n return path.join(tmpDir, path.basename(chromedriverPath));\n })\n }\n\n return chromedriverPath;\n }\n\n /** Gets the latest version of a plugin. */\n private async getLatestPluginVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-plugins\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a plugin from a GitHub repo to the cache.\n * @param repo Repo\n * @param version Version of the plugin to install or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadGitHubPlugin(repo: string, version = \"latest\"): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n if (version == \"latest\") {\n version = await this.getLatestPluginVersion(repo);\n }\n if (!semver.valid(version)) {\n throw Error(`Invalid version \"${version}\"`);\n }\n version = semver.valid(version)!;\n\n const pluginDir = path.join(this.cacheDir, \"obsidian-plugins\", repo, version);\n if (!(await fileExists(pluginDir))) {\n await fsAsync.mkdir(path.dirname(pluginDir), { recursive: true });\n await withTmpDir(pluginDir, async (tmpDir) => {\n const assetsToDownload = {'manifest.json': true, 'main.js': true, 'styles.css': false};\n await Promise.all(\n Object.entries(assetsToDownload).map(async ([file, required]) => {\n const url = `https://github.com/${repo}/releases/download/${version}/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else if (required) {\n throw Error(`No ${file} found for ${repo} version ${version}`)\n }\n })\n )\n return tmpDir;\n });\n }\n\n return pluginDir;\n }\n\n /**\n * Downloads a community plugin to the cache.\n * @param id Id of the plugin\n * @param version Version of the plugin to install, or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadCommunityPlugin(id: string, version = \"latest\"): Promise<string> {\n const communityPlugins = await this.getCommunityPlugins();\n const pluginInfo = communityPlugins.find(p => p.id == id);\n if (!pluginInfo) {\n throw Error(`No plugin with id ${id} found.`);\n }\n return await this.downloadGitHubPlugin(pluginInfo.repo, version);\n }\n\n /**\n * Downloads a list of plugins to the cache and returns a list of `DownloadedPluginEntry` with the downloaded paths.\n * Also adds the `id` property to the plugins based on the manifest.\n * \n * You can download plugins from GitHub using `{repo: \"org/repo\"}` and community plugins using `{id: 'plugin-id'}`.\n * Local plugins will just be passed through.\n * \n * @param plugins List of plugins to download.\n */\n async downloadPlugins(plugins: PluginEntry[]): Promise<DownloadedPluginEntry[]> {\n return await Promise.all(\n plugins.map(async (plugin) => {\n if (typeof plugin == \"object\" && \"originalType\" in plugin) {\n return {...plugin as DownloadedPluginEntry}\n }\n let pluginPath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof plugin == \"string\") {\n pluginPath = path.resolve(plugin);\n originalType = \"local\";\n } else if (\"path\" in plugin) {;\n pluginPath = path.resolve(plugin.path);\n originalType = \"local\";\n } else if (\"repo\" in plugin) {\n pluginPath = await this.downloadGitHubPlugin(plugin.repo, plugin.version);\n originalType = \"github\";\n } else if (\"id\" in plugin) {\n pluginPath = await this.downloadCommunityPlugin(plugin.id, plugin.version);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of plugin path, repo, or id\")\n }\n\n const manifestPath = path.join(pluginPath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No plugin found at ${pluginPath}`)\n }\n let pluginId = (typeof plugin == \"object\" && (\"id\" in plugin)) ? plugin.id : undefined;\n if (!pluginId) {\n pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} malformed.`);\n }\n }\n if (!(await fileExists(path.join(pluginPath, \"main.js\")))) {\n throw Error(`No main.js found under ${pluginPath}`)\n }\n\n let enabled: boolean\n if (typeof plugin == \"string\") {\n enabled = true\n } else {\n enabled = plugin.enabled ?? true;\n }\n return {path: pluginPath, id: pluginId, enabled, originalType}\n })\n );\n }\n\n /** Gets the latest version of a theme. */\n private async getLatestThemeVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-themes\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a theme from a GitHub repo to the cache.\n * @param repo Repo\n * @returns path to the downloaded theme\n */\n private async downloadGitHubTheme(repo: string): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n // Obsidian theme's are just pulled from the repo HEAD, not releases, so we can't really choose a specific \n // version of a theme.\n // We use the manifest.json version to check if the theme has changed.\n const version = await this.getLatestThemeVersion(repo);\n const themeDir = path.join(this.cacheDir, \"obsidian-themes\", repo, version);\n\n if (!(await fileExists(themeDir))) {\n await fsAsync.mkdir(path.dirname(themeDir), { recursive: true });\n await withTmpDir(themeDir, async (tmpDir) => {\n const assetsToDownload = ['manifest.json', 'theme.css'];\n await Promise.all(\n assetsToDownload.map(async (file) => {\n const url = `https://raw.githubusercontent.com/${repo}/HEAD/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else {\n throw Error(`No ${file} found for ${repo}`);\n }\n })\n )\n return tmpDir;\n });\n }\n\n return themeDir;\n }\n\n /**\n * Downloads a community theme to the cache.\n * @param name name of the theme\n * @returns path to the downloaded theme\n */\n private async downloadCommunityTheme(name: string): Promise<string> {\n const communityThemes = await this.getCommunityThemes();\n const themeInfo = communityThemes.find(p => p.name == name);\n if (!themeInfo) {\n throw Error(`No theme with name ${name} found.`);\n }\n return await this.downloadGitHubTheme(themeInfo.repo);\n }\n\n /**\n * Downloads a list of themes to the cache and returns a list of `DownloadedThemeEntry` with the downloaded paths.\n * Also adds the `name` property to the plugins based on the manifest.\n * \n * You can download themes from GitHub using `{repo: \"org/repo\"}` and community themes using `{name: 'theme-name'}`.\n * Local themes will just be passed through.\n * \n * @param themes List of themes to download\n */\n async downloadThemes(themes: ThemeEntry[]): Promise<DownloadedThemeEntry[]> {\n return await Promise.all(\n themes.map(async (theme) => {\n if (typeof theme == \"object\" && \"originalType\" in theme) {\n return {...theme as DownloadedThemeEntry}\n }\n let themePath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof theme == \"string\") {\n themePath = path.resolve(theme);\n originalType = \"local\";\n } else if (\"path\" in theme) {;\n themePath = path.resolve(theme.path);\n originalType = \"local\";\n } else if (\"repo\" in theme) {\n themePath = await this.downloadGitHubTheme(theme.repo);\n originalType = \"github\";\n } else if (\"name\" in theme) {\n themePath = await this.downloadCommunityTheme(theme.name);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of theme path, repo, or name\")\n }\n\n const manifestPath = path.join(themePath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No theme found at ${themePath}`)\n }\n let themeName = (typeof theme == \"object\" && (\"name\" in theme)) ? theme.name : undefined;\n if (!themeName) {\n const manifestPath = path.join(themePath, \"manifest.json\");\n themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${themePath}/manifest.json malformed.`);\n }\n }\n if (!(await fileExists(path.join(themePath, \"theme.css\")))) {\n throw Error(`No theme.css found under ${themePath}`)\n }\n\n let enabled: boolean\n if (typeof theme == \"string\") {\n enabled = true\n } else {\n enabled = theme.enabled ?? true;\n }\n return {path: themePath, name: themeName, enabled: enabled, originalType};\n })\n );\n }\n\n /**\n * Installs plugins into an Obsidian vault\n * @param vault Path to the vault to install the plugins in\n * @param plugins List plugins to install\n */\n async installPlugins(vault: string, plugins: PluginEntry[]) {\n const downloadedPlugins = await this.downloadPlugins(plugins);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n const enabledPluginsPath = path.join(obsidianDir, 'community-plugins.json');\n let originalEnabledPlugins: string[] = [];\n if (await fileExists(enabledPluginsPath)) {\n originalEnabledPlugins = JSON.parse(await fsAsync.readFile(enabledPluginsPath, 'utf-8'));\n }\n let enabledPlugins = [...originalEnabledPlugins];\n\n for (const {path: pluginPath, enabled = true, originalType} of downloadedPlugins) {\n const manifestPath = path.join(pluginPath, 'manifest.json');\n const pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n\n const pluginDest = path.join(obsidianDir, 'plugins', pluginId);\n await fsAsync.mkdir(pluginDest, { recursive: true });\n\n const files: Record<string, [boolean, boolean]> = {\n \"manifest.json\": [true, true],\n \"main.js\": [true, true],\n \"styles.css\": [false, true],\n \"data.json\": [false, false],\n }\n for (const [file, [required, deleteIfMissing]] of Object.entries(files)) {\n if (await fileExists(path.join(pluginPath, file))) {\n await linkOrCp(path.join(pluginPath, file), path.join(pluginDest, file));\n } else if (required) {\n throw Error(`${pluginPath}/${file} missing.`);\n } else if (deleteIfMissing) {\n await fsAsync.rm(path.join(pluginDest, file), {force: true});\n }\n }\n\n const pluginAlreadyListed = enabledPlugins.includes(pluginId);\n if (enabled && !pluginAlreadyListed) {\n enabledPlugins.push(pluginId)\n } else if (!enabled && pluginAlreadyListed) {\n enabledPlugins = enabledPlugins.filter(p => p != pluginId);\n }\n\n if (originalType == \"local\") {\n // Add a .hotreload file for the https://github.com/pjeby/hot-reload plugin\n await fsAsync.writeFile(path.join(pluginDest, '.hotreload'), '');\n }\n }\n\n if (!_.isEqual(enabledPlugins, originalEnabledPlugins)) {\n await fsAsync.writeFile(enabledPluginsPath, JSON.stringify(enabledPlugins, undefined, 2));\n }\n }\n\n /** \n * Installs themes into an Obsidian vault\n * @param vault Path to the theme to install the themes in\n * @param themes List of themes to install\n */\n async installThemes(vault: string, themes: ThemeEntry[]) {\n const downloadedThemes = await this.downloadThemes(themes);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n let enabledTheme: string|undefined = undefined;\n\n for (const {path: themePath, enabled = true} of downloadedThemes) {\n const manifestPath = path.join(themePath, 'manifest.json');\n const cssPath = path.join(themePath, 'theme.css');\n\n const themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n if (!(await fileExists(cssPath))) {\n throw Error(`${cssPath} missing.`);\n }\n\n const themeDest = path.join(obsidianDir, 'themes', themeName);\n await fsAsync.mkdir(themeDest, { recursive: true });\n\n await linkOrCp(manifestPath, path.join(themeDest, \"manifest.json\"));\n await linkOrCp(cssPath, path.join(themeDest, \"theme.css\"));\n\n if (enabledTheme && enabled) {\n throw Error(\"You can only have one enabled theme.\")\n } else if (enabled) {\n enabledTheme = themeName;\n }\n }\n\n if (themes.length > 0) { // Only update appearance.json if we set the themes\n const appearancePath = path.join(obsidianDir, 'appearance.json');\n let appearance: any = {}\n if (await fileExists(appearancePath)) {\n appearance = JSON.parse(await fsAsync.readFile(appearancePath, 'utf-8'));\n }\n appearance.cssTheme = enabledTheme ?? \"\";\n await fsAsync.writeFile(appearancePath, JSON.stringify(appearance, undefined, 2));\n }\n }\n\n /**\n * Sets up the config dir to use for the `--user-data-dir` in obsidian. Returns the path to the created config dir.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format)\n * @param params.installerVersion Obsidian version string.\n * @param params.appPath Path to the asar file to install. Will download if omitted.\n * @param params.vault Path to the vault to open in Obsidian.\n * @param params.dest Destination path for the config dir. If omitted it will create a temporary directory.\n */\n async setupConfigDir(params: {\n appVersion: string, installerVersion: string,\n appPath?: string,\n vault?: string,\n dest?: string,\n }): Promise<string> {\n const [appVersion, installerVersion] = await this.resolveVersions(params.appVersion, params.installerVersion);\n // configDir will be passed to --user-data-dir, so Obsidian is somewhat sandboxed. We set up \"obsidian.json\" so\n // that Obsidian opens the vault by default and doesn't check for updates.\n const configDir = params.dest ?? await makeTmpDir('obs-launcher-config-');\n \n let obsidianJson: any = {\n updateDisabled: true, // Prevents Obsidian trying to auto-update on boot.\n }\n let localStorageData: Record<string, string> = {\n \"most-recently-installed-version\": appVersion, // prevents the changelog page on boot\n }\n\n if (params.vault !== undefined) {\n if (!await fileExists(params.vault)) {\n throw Error(`Vault path ${params.vault} doesn't exist.`)\n }\n const vaultId = crypto.randomBytes(8).toString(\"hex\");\n obsidianJson = {\n ...obsidianJson,\n vaults: {\n [vaultId]: {\n path: path.resolve(params.vault),\n ts: new Date().getTime(),\n open: true,\n },\n },\n };\n localStorageData = {\n ...localStorageData,\n [`enable-plugin-${vaultId}`]: \"true\", // Disable \"safe mode\" and enable plugins\n }\n }\n\n await fsAsync.writeFile(path.join(configDir, 'obsidian.json'), JSON.stringify(obsidianJson));\n\n let appPath = params.appPath;\n if (!appPath) {\n appPath = await this.downloadApp(appVersion);\n }\n await linkOrCp(appPath, path.join(configDir, path.basename(appPath)));\n\n const localStorage = new ChromeLocalStorage(configDir);\n await localStorage.setItems(\"app://obsidian.md\", localStorageData)\n await localStorage.close();\n\n return configDir;\n }\n\n /**\n * Sets up a vault for Obsidian, installing plugins and themes and optionally copying the vault to a temporary\n * directory first.\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @returns Path to the copied vault (or just the path to the vault if copy is false)\n */\n async setupVault(params: {\n vault: string,\n copy?: boolean,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n }): Promise<string> {\n let vault = params.vault;\n if (params.copy) {\n const dest = await makeTmpDir('obs-launcher-vault-');\n await fsAsync.cp(vault, dest, { recursive: true, preserveTimestamps: true });\n vault = dest;\n }\n await this.installPlugins(vault, params.plugins ?? []);\n await this.installThemes(vault, params.themes ?? []);\n\n return vault;\n }\n\n /**\n * Downloads and launches Obsidian with a sandboxed config dir and a specifc vault open. Optionally install plugins\n * and themes first.\n * \n * This is just a shortcut for calling `downloadApp`, `downloadInstaller`, `setupVault` and `setupConfDir`.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format). Default \"latest\"\n * @param params.installerVersion Obsidian installer version (see {@link resolveVersions} for format).\n * Default \"latest\"\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @param params.args CLI args to pass to Obsidian\n * @param params.spawnOptions Options to pass to `spawn`\n * @returns The launched child process and the created tmpdirs\n */\n async launch(params: {\n appVersion?: string, installerVersion?: string,\n copy?: boolean,\n vault?: string,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n args?: string[],\n spawnOptions?: child_process.SpawnOptions,\n }): Promise<{proc: child_process.ChildProcess, configDir: string, vault?: string}> {\n const [appVersion, installerVersion] = await this.resolveVersions(\n params.appVersion ?? \"latest\",\n params.installerVersion ?? \"latest\",\n );\n const appPath = await this.downloadApp(appVersion);\n const installerPath = await this.downloadInstaller(installerVersion);\n\n let vault = params.vault;\n if (vault) {\n vault = await this.setupVault({\n vault,\n copy: params.copy ?? false,\n plugins: params.plugins, themes: params.themes,\n })\n }\n\n const configDir = await this.setupConfigDir({ appVersion, installerVersion, appPath, vault });\n\n // Spawn child.\n const proc = child_process.spawn(installerPath, [\n `--user-data-dir=${configDir}`,\n ...(params.args ?? []),\n ], {\n ...params.spawnOptions,\n });\n\n return {proc, configDir, vault};\n }\n\n /** \n * Updates the info obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands\n * and in wdio-obsidian-service to get metadata about Obsidian versions in one place such as minInstallerVersion and\n * the internal electron version.\n */\n async updateObsidianVersionInfos(\n original?: ObsidianVersionInfos, { maxInstances = 1 } = {},\n ): Promise<ObsidianVersionInfos> {\n const repo = 'obsidianmd/obsidian-releases';\n\n let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {\n path: \"desktop-releases.json\",\n since: original?.metadata.commit_date,\n });\n commitHistory.reverse();\n if (original) {\n commitHistory = _.takeRightWhile(commitHistory, c => c.sha != original.metadata.commit_sha);\n }\n \n const fileHistory: any[] = await pool(8, commitHistory, commit =>\n fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then(r => r.json())\n );\n \n const githubReleases = await fetchGitHubAPIPaginated(`repos/${repo}/releases`);\n \n const versionMap: _.Dictionary<Partial<ObsidianVersionInfo>> = _.keyBy(\n original?.versions ?? [],\n v => v.version,\n );\n \n for (const {beta, ...current} of fileHistory) {\n if (beta && (!versionMap[beta.latestVersion] || versionMap[beta.latestVersion].isBeta)) {\n versionMap[beta.latestVersion] = _.merge({}, versionMap[beta.latestVersion],\n parseObsidianDesktopRelease(beta, true),\n );\n }\n versionMap[current.latestVersion] = _.merge({}, versionMap[current.latestVersion],\n parseObsidianDesktopRelease(current, false),\n )\n }\n \n for (const release of githubReleases) {\n if (versionMap.hasOwnProperty(release.name)) {\n versionMap[release.name] = _.merge({}, versionMap[release.name], parseObsidianGithubRelease(release));\n }\n }\n \n const dependencyVersions = await pool(maxInstances,\n Object.values(versionMap).filter(v => v.downloads?.appImage && !v.chromeVersion),\n async (v) => {\n const binaryPath = await this.downloadInstallerFromVersionInfo(v as ObsidianVersionInfo);\n const electronVersionInfo = await getElectronVersionInfo(v.version!, binaryPath);\n return {...v, ...electronVersionInfo};\n },\n )\n for (const deps of dependencyVersions) {\n versionMap[deps.version!] = _.merge({}, versionMap[deps.version!], deps);\n }\n \n // populate minInstallerVersion and maxInstallerVersion and add corrections\n let minInstallerVersion: string|undefined = undefined;\n let maxInstallerVersion: string|undefined = undefined;\n for (const version of Object.keys(versionMap).sort(semver.compare)) {\n // older versions have 0.0.0 as there min version, which doesn't exist anywhere we can download.\n // we'll set those to the first available installer version.\n if (!minInstallerVersion && versionMap[version].chromeVersion) {\n minInstallerVersion = version;\n }\n if (versionMap[version].downloads!.appImage) {\n maxInstallerVersion = version;\n }\n versionMap[version] = mergeKeepUndefined({}, versionMap[version],\n {\n minInstallerVersion: versionMap[version].minInstallerVersion ?? minInstallerVersion,\n maxInstallerVersion: maxInstallerVersion,\n },\n correctObsidianVersionInfo(versionMap[version]),\n );\n }\n \n const result: ObsidianVersionInfos = {\n metadata: {\n commit_date: commitHistory.at(-1)?.commit.committer.date ?? original?.metadata.commit_date,\n commit_sha: commitHistory.at(-1)?.sha ?? original?.metadata.commit_sha,\n timestamp: original?.metadata.timestamp ?? \"\", // set down below\n },\n versions: Object.values(versionMap)\n .map(normalizeObsidianVersionInfo)\n .sort((a, b) => semver.compare(a.version, b.version)),\n }\n\n // Update timestamp if anything has changed. Also, GitHub will cancel scheduled workflows if the repository is\n // \"inactive\" for 60 days. So we'll update the timestamp every once in a while even if there are no Obsidian\n // updates to make sure there's commit activity in the repo.\n const dayMs = 24 * 60 * 60 * 1000;\n const timeSinceLastUpdate = new Date().getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();\n if (!_.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {\n result.metadata.timestamp = new Date().toISOString();\n }\n\n return result;\n }\n\n /**\n * Returns true if the Obsidian version is already in the cache.\n * @param type on of \"app\" or \"installer\"\n * @param version Obsidian app/installer version (see {@link resolveVersions} for format)\n */\n async isInCache(type: \"app\"|\"installer\", version: string) {\n version = (await this.getVersionInfo(version)).version;\n\n let dest: string\n if (type == \"app\") {\n dest = `obsidian-app/obsidian-${version}.asar`;\n } else { // type == \"installer\"\n const {platform, arch} = process;\n dest =`obsidian-installer/${platform}-${arch}/Obsidian-${version}`;\n }\n\n return (await fileExists(path.join(this.cacheDir, dest)));\n }\n\n /**\n * Returns true if we either have the credentials to download the version or it's already in cache.\n * This is only relevant for Obsidian beta versions, as they require Obsidian insider credentials to download.\n * @param version Obsidian version (see {@link resolveVersions} for format)\n */\n async isAvailable(version: string): Promise<boolean> {\n const versionInfo = await this.getVersionInfo(version);\n const versionExists = !!(versionInfo.downloads.asar && versionInfo.minInstallerVersion)\n\n if (versionInfo.isBeta) {\n const hasCreds = !!(process.env['OBSIDIAN_USERNAME'] && process.env['OBSIDIAN_PASSWORD']);\n const inCache = await this.isInCache('app', versionInfo.version);\n return versionExists && (hasCreds || inCache);\n } else {\n return versionExists;\n }\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport os from \"os\"\nimport { PromisePool } from '@supercharge/promise-pool'\nimport _ from \"lodash\"\n\n/// Files ///\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create tmpdir under the system temporary directory.\n * @param prefix \n * @returns \n */\nexport async function makeTmpDir(prefix?: string) {\n return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? 'tmp-'));\n}\n\n/**\n * Handles creating a file \"atomically\" by creating a tmpDir, then downloading or otherwise creating the file under it,\n * then renaming it to the final location when done.\n * @param dest Path the file should end up at.\n * @param func Function takes path to a temporary directory it can use as scratch space. The path it returns will be\n * moved to `dest`. If no path is returned, it will move the whole tmpDir to dest.\n */\nexport async function withTmpDir(dest: string, func: (tmpDir: string) => Promise<string|void>): Promise<void> {\n dest = path.resolve(dest);\n const tmpDir = await fsAsync.mkdtemp(path.join(path.dirname(dest), `.${path.basename(dest)}.tmp.`));\n try {\n let result = await func(tmpDir) ?? tmpDir;\n if (!path.isAbsolute(result)) {\n result = path.join(tmpDir, result);\n } else if (!path.resolve(result).startsWith(tmpDir)) {\n throw new Error(`Returned path ${result} not under tmpDir`)\n }\n // rename will overwrite files but not directories\n if (await fileExists(dest) && (await fsAsync.stat(dest)).isDirectory()) {\n await fsAsync.rename(dest, tmpDir + \".old\")\n }\n \n await fsAsync.rename(result, dest);\n await fsAsync.rm(tmpDir + \".old\", { recursive: true, force: true });\n } finally {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Tries to hardlink a file, falls back to copy if it fails\n */\nexport async function linkOrCp(src: string, dest: string) {\n try {\n await fsAsync.link(src, dest);\n } catch {\n await fsAsync.copyFile(src, dest);\n }\n}\n\n\n/// Promises ///\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Await a promise or reject if it takes longer than timeout.\n */\nexport async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n let timer: NodeJS.Timeout;\n const result = Promise.race([\n promise,\n new Promise<T>((resolve, reject) => timer = setTimeout(() => reject(Error(\"Promise timed out\")), timeout))\n ])\n return result.finally(() => clearTimeout(timer));\n}\n\n/**\n * Wrapper around PromisePool that throws on any error.\n */\nexport async function pool<T, U>(size: number, items: T[], func: (item: T) => Promise<U>): Promise<U[]> {\n const { results } = await PromisePool\n .for(items)\n .withConcurrency(size)\n .handleError(async (error) => { throw error; })\n .useCorrespondingResults()\n .process(func);\n return results as U[];\n}\n\nexport type SuccessResult<T> = {success: true, result: T, error: undefined};\nexport type ErrorResult = {success: false, result: undefined, error: any};\nexport type Maybe<T> = SuccessResult<T>|ErrorResult;\n\n/**\n * Helper for handling asynchronous errors with less hassle.\n */\nexport async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {\n return promise\n .then(r => ({success: true, result: r, error: undefined} as const))\n .catch(e => ({success: false, result: undefined, error: e} as const));\n}\n\n\n/**\n * Lodash _.merge but overwrites values with undefined if present, instead of ignoring undefined.\n */\nexport function mergeKeepUndefined(object: any, ...sources: any[]) {\n return _.mergeWith(object, ...sources,\n (objValue: any, srcValue: any, key: any, obj: any) => {\n if (_.isPlainObject(obj) && objValue !== srcValue && srcValue === undefined) {\n obj[key] = srcValue\n }\n }\n );\n}","import _ from \"lodash\"\nimport fsAsync from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\n\n\n/**\n * GitHub API stores pagination information in the \"Link\" header. The header looks like this:\n * ```\n * <https://api.github.com/repositories/1300192/issues?page=2>; rel=\"prev\", <https://api.github.com/repositories/1300192/issues?page=4>; rel=\"next\"\n * ```\n */\nexport function parseLinkHeader(linkHeader: string): Record<string, Record<string, string>> {\n function parseLinkData(linkData: string) {\n return Object.fromEntries(\n linkData.split(\";\").flatMap(x => {\n const partMatch = x.trim().match(/^([^=]+?)\\s*=\\s*\"?([^\"]+)\"?$/);\n return partMatch ? [[partMatch[1], partMatch[2]]] : [];\n })\n )\n }\n\n const linkDatas = linkHeader\n .split(/,\\s*(?=<)/)\n .flatMap(link => {\n const linkMatch = link.trim().match(/^<([^>]*)>(.*)$/);\n if (linkMatch) {\n return [{\n url: linkMatch[1],\n ...parseLinkData(linkMatch[2]),\n } as Record<string, string>];\n } else {\n return [];\n }\n })\n .filter(l => l.rel)\n return Object.fromEntries(linkDatas.map(l => [l.rel, l]));\n};\n\n\nfunction createURL(url: string, base: string, params: Record<string, any> = {}) {\n params =_.pickBy(params, x => x !== undefined);\n const urlObj = new URL(url, base);\n const searchParams = new URLSearchParams({...Object.fromEntries(urlObj.searchParams), ...params});\n if ([...searchParams].length > 0) {\n urlObj.search = '?' + searchParams;\n }\n return urlObj.toString();\n}\n\n\n/**\n * Fetch from the GitHub API. Uses GITHUB_TOKEN if available. You can access the API without a token, but will hit\n * the usage caps very quickly.\n */\nexport async function fetchGitHubAPI(url: string, params: Record<string, any> = {}) {\n url = createURL(url, \"https://api.github.com\", params)\n const token = process.env.GITHUB_TOKEN;\n const headers: Record<string, string> = token ? {Authorization: \"Bearer \" + token} : {};\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`GitHub API error: ${await response.text()}`);\n }\n return await response;\n}\n\n\n/**\n * Fetch all data from a paginated GitHub API request.\n */\nexport async function fetchGitHubAPIPaginated(url: string, params: Record<string, any> = {}) {\n const results = [];\n let next: string|undefined = createURL(url, \"https://api.github.com\", { per_page: 100, ...params });\n while (next) {\n const response = await fetchGitHubAPI(next);\n results.push(...await response.json() as any);\n next = parseLinkHeader(response.headers.get('link') ?? '').next?.url;\n }\n return results;\n}\n\n/**\n * Fetch from the Obsidian API to download insider versions. Uses OBSIDIAN_USERNAME and\n * OBSIDIAN_PASSWORD environment variables.\n */\nexport async function fetchObsidianAPI(url: string) {\n url = createURL(url, \"https://releases.obsidian.md\");\n\n const username = process.env.OBSIDIAN_USERNAME;\n const password = process.env.OBSIDIAN_PASSWORD;\n if (!username || !password) {\n throw Error(\"OBSIDIAN_USERNAME or OBSIDIAN_PASSWORD environment variables are required to access the Obsidian API for beta versions.\")\n }\n\n const response = await fetch(url, {\n headers: {\n // For some reason you have to set the User-Agent or it won't let you download\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',\n \"Origin\": \"app://obsidian.md\",\n 'Authorization': 'Basic ' + btoa(username + \":\" + password),\n },\n })\n return response;\n}\n\n\n/**\n * Fetches a URL returning its content as a string. Throws if response is not OK.\n * URL can be a file url.\n */\nexport async function fetchWithFileUrl(url: string) {\n if (url.startsWith(\"file:\")) {\n return await fsAsync.readFile(fileURLToPath(url), 'utf-8');\n } else {\n const response = await fetch(url);\n if (response.ok) {\n return response.text()\n } else {\n throw Error(`Request failed with ${response.status}: ${response.text()}`)\n }\n }\n}\n","import path from \"path\"\nimport { ClassicLevel } from \"classic-level\"\n\n\n/**\n * Class to directly manipulate chrome/electron local storage.\n *\n * Normally you'd just manipulate `localStorage` directly during the webdriver tests. However, there's not a built in\n * way to set up localStorage values *before* the app boots. We need to set `enable-plugins` and some other keys for\n * Obsidian to read during the boot process. This class lets us setup the local storage before launching Obsidian.\n */\nexport default class ChromeLocalStorage {\n private db: ClassicLevel;\n\n /** Pass the path to the user data dir for Chrome/Electron. If it doesn't exist it will be created. */\n constructor(public readonly userDataDir: string) {\n this.db = new ClassicLevel(path.join(userDataDir, 'Local Storage/leveldb/'));\n }\n\n private encodeKey = (domain: string, key: string) => `_${domain}\\u0000\\u0001${key}`;\n private decodeKey = (key: string) => key.slice(1).split(\"\\u0000\\u0001\") as [string, string];\n private encodeValue = (value: string) => `\\u0001${value}`;\n private decodeValue = (value: string) => value.slice(1);\n\n /**\n * Get a value from localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to retreive\n */\n async getItem(domain: string, key: string): Promise<string|null> {\n const value = await this.db.get(this.encodeKey(domain, key));\n return (value === undefined) ? null : this.decodeValue(value);\n }\n\n /**\n * Set a value in localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to set\n * @param value Value to set\n */\n async setItem(domain: string, key: string, value: string) {\n await this.db.put(this.encodeKey(domain, key), this.encodeValue(value))\n }\n\n /**\n * Removes a key from localStorage\n * @param domain Domain the values is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key key to remove.\n */\n async removeItem(domain: string, key: string) {\n await this.db.del(this.encodeKey(domain, key))\n }\n\n /** Get all items in localStorage as [domain, key, value] tuples */\n async getAllItems(): Promise<[string, string, string][]> {\n const result: [string, string, string][] = []\n for await (const pair of this.db.iterator()) {\n if (pair[0].startsWith(\"_\")) { // ignore the META values\n const [domain, key] = this.decodeKey(pair[0]);\n const value = this.decodeValue(pair[1]);\n result.push([domain, key, value]);\n }\n }\n return result;\n }\n\n /**\n * Write multiple values to localStorage in batch\n * @param domain Domain the values are under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param data key/value mapping to write\n */\n async setItems(domain: string, data: Record<string, string>) {\n await this.db.batch(\n Object.entries(data).map(([key, value]) => ({\n type: \"put\",\n key: this.encodeKey(domain, key),\n value: this.encodeValue(value),\n }))\n )\n }\n\n /**\n * Close the localStorage database.\n */\n async close() {\n await this.db.close();\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { promisify } from \"util\";\nimport child_process from \"child_process\"\nimport which from \"which\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\nimport CDP from 'chrome-remote-interface'\nimport { makeTmpDir, withTmpDir, maybe, withTimeout, sleep } from \"./utils.js\";\nimport { ObsidianVersionInfo } from \"./types.js\";\nconst execFile = promisify(child_process.execFile);\n\n\nexport function normalizeGitHubRepo(repo: string) {\n return repo.replace(/^(https?:\\/\\/)?(github.com\\/)/, '')\n}\n\n/**\n * Running AppImage requires libfuse2, extracting the AppImage first avoids that.\n */\nexport async function extractObsidianAppImage(appImage: string, dest: string) {\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.chmod(appImage, 0o755);\n await execFile(appImage, [\"--appimage-extract\"], {cwd: tmpDir});\n return path.join(tmpDir, 'squashfs-root');\n })\n}\n\n/**\n * Obsidian appears to use NSIS to bundle their Window's installers. We want to extract the executable\n * files directly without running the installer. 7zip can extract the raw files from the exe.\n */\nexport async function extractObsidianExe(exe: string, appArch: string, dest: string) {\n const path7z = await which(\"7z\", { nothrow: true });\n if (!path7z) {\n throw new Error(\n \"Downloading Obsidian for Windows requires 7zip to be installed and available on the PATH. \" +\n \"You install it from https://www.7-zip.org and then add the install location to the PATH.\"\n );\n }\n exe = path.resolve(exe);\n // The installer contains several `.7z` files with files for different architectures \n const subArchive = path.join('$PLUGINSDIR', appArch + \".7z\");\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const extractedInstaller = path.join(tmpDir, \"installer\");\n await execFile(path7z, [\"x\", \"-o\" + extractedInstaller, exe, subArchive]);\n const extractedObsidian = path.join(tmpDir, \"obsidian\");\n await execFile(path7z, [\"x\", \"-o\" + extractedObsidian, path.join(extractedInstaller, subArchive)]);\n return extractedObsidian;\n })\n}\n\n/**\n * Extract the executable from the Obsidian dmg installer.\n */\nexport async function extractObsidianDmg(dmg: string, dest: string) {\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const proc = await execFile('hdiutil', ['attach', '-nobrowse', '-readonly', dmg]);\n const volume = proc.stdout.match(/\\/Volumes\\/.*$/m)![0];\n const obsidianApp = path.join(volume, \"Obsidian.app\");\n try {\n await fsAsync.cp(obsidianApp, tmpDir, {recursive: true, verbatimSymlinks: true});\n } finally {\n await execFile('hdiutil', ['detach', volume]);\n }\n return tmpDir;\n })\n}\n\n\nexport function parseObsidianDesktopRelease(fileRelease: any, isBeta: boolean): Partial<ObsidianVersionInfo> {\n return {\n version: fileRelease.latestVersion,\n minInstallerVersion: fileRelease.minimumVersion != '0.0.0' ? fileRelease.minimumVersion : undefined,\n isBeta: isBeta,\n downloads: {\n asar: fileRelease.downloadUrl,\n },\n };\n}\n\nexport function parseObsidianGithubRelease(gitHubRelease: any): Partial<ObsidianVersionInfo> {\n const version = gitHubRelease.name;\n const assets: string[] = gitHubRelease.assets.map((a: any) => a.browser_download_url);\n const downloads = {\n appImage: assets.find(u => u.match(`${version}.AppImage$`)),\n appImageArm: assets.find(u => u.match(`${version}-arm64.AppImage$`)),\n apk: assets.find(u => u.match(`${version}.apk$`)),\n asar: assets.find(u => u.match(`${version}.asar.gz$`)),\n dmg: assets.find(u => u.match(`${version}(-universal)?.dmg$`)),\n exe: assets.find(u => u.match(`${version}.exe$`)),\n }\n\n return {\n version: version,\n gitHubRelease: gitHubRelease.html_url,\n downloads: downloads,\n }\n}\n\n/**\n * Extract electron and chrome versions for an Obsidian version.\n */\nexport async function getElectronVersionInfo(\n version:string, binaryPath: string,\n): Promise<Partial<ObsidianVersionInfo>> {\n console.log(`${version}: Retrieving electron & chrome versions...`);\n\n const configDir = await makeTmpDir('fetch-obsidian-versions-');\n\n const proc = child_process.spawn(binaryPath, [\n `--remote-debugging-port=0`, // 0 will make it choose a random available port\n '--test-type=webdriver',\n `--user-data-dir=${configDir}`,\n '--no-sandbox', // Workaround for SUID issue, see https://github.com/electron/electron/issues/42510\n ]);\n const procExit = new Promise<number>((resolve) => proc.on('exit', (code) => resolve(code ?? -1)));\n // proc.stdout.on('data', data => console.log(`stdout: ${data}`));\n // proc.stderr.on('data', data => console.log(`stderr: ${data}`));\n\n let dependencyVersions: any;\n try {\n // Wait for the logs showing that Obsidian is ready, and pull the chosen DevTool Protocol port from it\n const portPromise = new Promise<number>((resolve, reject) => {\n procExit.then(() => reject(\"Processed ended without opening a port\"))\n proc.stderr.on('data', data => {\n const port = data.toString().match(/ws:\\/\\/[\\w.]+?:(\\d+)/)?.[1];\n if (port) {\n resolve(Number(port));\n }\n });\n })\n\n const port = await maybe(withTimeout(portPromise, 10 * 1000));\n if (!port.success) {\n throw new Error(\"Timed out waiting for Chrome DevTools protocol port\");\n }\n const client = await CDP({port: port.result});\n const response = await client.Runtime.evaluate({ expression: \"JSON.stringify(process.versions)\" });\n dependencyVersions = JSON.parse(response.result.value);\n await client.close();\n } finally {\n proc.kill(\"SIGTERM\");\n const timeout = await maybe(withTimeout(procExit, 4 * 1000));\n if (!timeout.success) {\n console.log(`${version}: Stuck process ${proc.pid}, using SIGKILL`);\n proc.kill(\"SIGKILL\");\n }\n await procExit;\n await sleep(1000); // Need to wait a bit or sometimes the rm fails because something else is writing to it\n await fsAsync.rm(configDir, { recursive: true, force: true });\n }\n\n if (!dependencyVersions?.electron || !dependencyVersions?.chrome) {\n throw Error(`Failed to extract electron and chrome versions for ${version}`)\n }\n\n return {\n electronVersion: dependencyVersions.electron,\n chromeVersion: dependencyVersions.chrome,\n nodeVersion: dependencyVersions.node,\n };\n}\n\n/**\n * Add some corrections to the Obsidian version data.\n */\nexport function correctObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): Partial<ObsidianVersionInfo> {\n const corrections: Partial<ObsidianVersionInfo>[] = [\n // These version's downloads are missing or broken.\n {version: '0.12.16', downloads: { asar: undefined }},\n {version: '1.4.7', downloads: { asar: undefined }},\n {version: '1.4.8', downloads: { asar: undefined }},\n \n // The minInstallerVersion here is incorrect\n {version: \"1.3.4\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.3\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.2\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.1\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.0\", minInstallerVersion: \"0.14.5\"},\n ]\n\n const result = corrections.find(v => v.version == versionInfo.version) ?? {};\n // minInstallerVersion is incorrect, running Obsidian with installer older than 1.1.9 won't boot with errors like\n // `(node:11592) electron: Failed to load URL: app://obsidian.md/starter.html with error: ERR_BLOCKED_BY_CLIENT`\n if (semver.gte(versionInfo.version!, \"1.5.3\") && semver.lt(versionInfo.minInstallerVersion!, \"1.1.9\")) {\n result.minInstallerVersion = \"1.1.9\"\n }\n\n return result;\n}\n\n/**\n * Normalize order and remove undefined values.\n */\nexport function normalizeObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): ObsidianVersionInfo {\n versionInfo = {\n version: versionInfo.version,\n minInstallerVersion: versionInfo.minInstallerVersion,\n maxInstallerVersion: versionInfo.maxInstallerVersion,\n isBeta: versionInfo.isBeta,\n gitHubRelease: versionInfo.gitHubRelease,\n downloads: {\n asar: versionInfo.downloads?.asar,\n appImage: versionInfo.downloads?.appImage,\n appImageArm: versionInfo.downloads?.appImageArm,\n apk: versionInfo.downloads?.apk,\n dmg: versionInfo.downloads?.dmg,\n exe: versionInfo.downloads?.exe,\n },\n electronVersion: versionInfo.electronVersion,\n chromeVersion: versionInfo.chromeVersion,\n nodeVersion: versionInfo.nodeVersion,\n };\n versionInfo.downloads = _.omitBy(versionInfo.downloads, _.isUndefined);\n versionInfo = _.omitBy(versionInfo, _.isUndefined);\n return versionInfo as ObsidianVersionInfo;\n}\n"],"mappings":"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","names":["fsAsync","path","child_process","semver","path","_","fsAsync","_","fsAsync","path","fsAsync","path","_","fsAsync","path","port","_","_","path","fsAsync","semver","manifestPath","child_process"]}
@@ -817,9 +817,12 @@ var ObsidianLauncher = class {
817
817
  if (!pluginId) {
818
818
  pluginId = JSON.parse(await _promises2.default.readFile(manifestPath, "utf8").catch(() => "{}")).id;
819
819
  if (!pluginId) {
820
- throw Error(`${pluginPath}/manifest.json malformed.`);
820
+ throw Error(`${manifestPath} malformed.`);
821
821
  }
822
822
  }
823
+ if (!await fileExists(_path2.default.join(pluginPath, "main.js"))) {
824
+ throw Error(`No main.js found under ${pluginPath}`);
825
+ }
823
826
  let enabled;
824
827
  if (typeof plugin == "string") {
825
828
  enabled = true;
@@ -925,6 +928,9 @@ var ObsidianLauncher = class {
925
928
  throw Error(`${themePath}/manifest.json malformed.`);
926
929
  }
927
930
  }
931
+ if (!await fileExists(_path2.default.join(themePath, "theme.css"))) {
932
+ throw Error(`No theme.css found under ${themePath}`);
933
+ }
928
934
  let enabled;
929
935
  if (typeof theme == "string") {
930
936
  enabled = true;
@@ -1269,4 +1275,4 @@ var ObsidianLauncher = class {
1269
1275
 
1270
1276
 
1271
1277
  exports.ObsidianLauncher = ObsidianLauncher;
1272
- //# sourceMappingURL=chunk-UQI73JCR.cjs.map
1278
+ //# sourceMappingURL=chunk-FZIMIXL5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/wdio-obsidian-service/wdio-obsidian-service/packages/obsidian-launcher/dist/chunk-FZIMIXL5.cjs","../src/launcher.ts","../src/utils.ts","../src/apis.ts","../src/chromeLocalStorage.ts","../src/launcherUtils.ts"],"names":["path","_","fsAsync","manifestPath"],"mappings":"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","file":"/home/runner/work/wdio-obsidian-service/wdio-obsidian-service/packages/obsidian-launcher/dist/chunk-FZIMIXL5.cjs","sourcesContent":[null,"import fsAsync from \"fs/promises\"\nimport fs from \"fs\"\nimport zlib from \"zlib\"\nimport path from \"path\"\nimport crypto from \"crypto\";\nimport extractZip from \"extract-zip\"\nimport { pipeline } from \"stream/promises\";\nimport { downloadArtifact } from '@electron/get';\nimport child_process from \"child_process\"\nimport semver from \"semver\"\nimport { fileExists, makeTmpDir, withTmpDir, linkOrCp, maybe, pool, mergeKeepUndefined } from \"./utils.js\";\nimport {\n ObsidianVersionInfo, ObsidianCommunityPlugin, ObsidianCommunityTheme,\n PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry,\n ObsidianVersionInfos,\n} from \"./types.js\";\nimport { fetchObsidianAPI, fetchGitHubAPIPaginated, fetchWithFileUrl } from \"./apis.js\";\nimport ChromeLocalStorage from \"./chromeLocalStorage.js\";\nimport {\n normalizeGitHubRepo, extractObsidianAppImage, extractObsidianExe, extractObsidianDmg,\n parseObsidianDesktopRelease, parseObsidianGithubRelease, correctObsidianVersionInfo,\n getElectronVersionInfo, normalizeObsidianVersionInfo,\n} from \"./launcherUtils.js\";\nimport _ from \"lodash\"\n\n\n/**\n * The `ObsidianLauncher` class.\n * \n * Helper class that handles downloading and installing Obsidian versions, plugins, and themes and launching Obsidian\n * with sandboxed configuration directories.\n */\nexport class ObsidianLauncher {\n readonly cacheDir: string\n\n readonly versionsUrl: string\n readonly communityPluginsUrl: string\n readonly communityThemesUrl: string\n readonly cacheDuration: number\n\n /** Cached metadata files and requests */\n private metadataCache: Record<string, any>\n\n /**\n * Construct an ObsidianLauncher.\n * @param options.cacheDir Path to the cache directory. Defaults to \"OBSIDIAN_CACHE\" env var or \".obsidian-cache\".\n * @param options.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.\n * @param options.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.\n * @param options.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.\n * @param options.cacheDuration If the cached version list is older than this (in ms), refetch it. Defaults to 30 minutes.\n */\n constructor(options: {\n cacheDir?: string,\n versionsUrl?: string,\n communityPluginsUrl?: string,\n communityThemesUrl?: string,\n cacheDuration?: number,\n } = {}) {\n this.cacheDir = path.resolve(options.cacheDir ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n \n const defaultVersionsUrl = 'https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json'\n this.versionsUrl = options.versionsUrl ?? defaultVersionsUrl;\n \n const defaultCommunityPluginsUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\";\n this.communityPluginsUrl = options.communityPluginsUrl ?? defaultCommunityPluginsUrl;\n\n const defaultCommunityThemesUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\";\n this.communityThemesUrl = options.communityThemesUrl ?? defaultCommunityThemesUrl;\n\n this.cacheDuration = options.cacheDuration ?? (30 * 60 * 1000);\n\n this.metadataCache = {};\n }\n\n /**\n * Returns file content fetched from url as JSON. Caches content to dest and uses that cache if its more recent than\n * cacheDuration ms or if there are network errors.\n */\n private async cachedFetch(url: string, dest: string): Promise<any> {\n dest = path.resolve(dest);\n if (!(dest in this.metadataCache)) {\n let fileContent: string|undefined;\n const mtime = await fileExists(dest) ? (await fsAsync.stat(dest)).mtime : undefined;\n\n if (mtime && new Date().getTime() - mtime.getTime() < this.cacheDuration) { // read from cache if its recent\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else { // otherwise try to fetch the url\n const request = await maybe(fetchWithFileUrl(url));\n if (request.success) {\n await fsAsync.mkdir(path.dirname(dest), { recursive: true });\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.writeFile(path.join(tmpDir, 'download.json'), request.result);\n return path.join(tmpDir, 'download.json');\n })\n fileContent = request.result;\n } else if (await fileExists(dest)) { // use cache on network error\n console.warn(request.error)\n console.warn(`Unable to download ${dest}, using cached file.`);\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else {\n throw request.error;\n }\n }\n\n this.metadataCache[dest] = JSON.parse(fileContent);\n }\n return this.metadataCache[dest];\n }\n\n /**\n * Get parsed content of the current project's manifest.json\n */\n private async getRootManifest(): Promise<any> {\n if (!('manifest.json' in this.metadataCache)) {\n const root = path.parse(process.cwd()).root;\n let dir = process.cwd();\n while (dir != root && !(await fileExists(path.join(dir, 'manifest.json')))) {\n dir = path.dirname(dir);\n }\n const manifestPath = path.join(dir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n this.metadataCache['manifest.json'] = JSON.parse(await fsAsync.readFile(manifestPath, 'utf-8'));\n } else {\n this.metadataCache['manifest.json'] = null;\n }\n }\n return this.metadataCache['manifest.json'];\n }\n\n /**\n * Get information about all available Obsidian versions.\n */\n async getVersions(): Promise<ObsidianVersionInfo[]> {\n const dest = path.join(this.cacheDir, \"obsidian-versions.json\");\n return (await this.cachedFetch(this.versionsUrl, dest)).versions;\n }\n\n /**\n * Get information about all available community plugins.\n */\n async getCommunityPlugins(): Promise<ObsidianCommunityPlugin[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-plugins.json\");\n return await this.cachedFetch(this.communityPluginsUrl, dest);\n }\n\n /**\n * Get information about all available community themes.\n */\n async getCommunityThemes(): Promise<ObsidianCommunityTheme[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-css-themes.json\");\n return await this.cachedFetch(this.communityThemesUrl, dest);\n }\n\n /**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * @param appVersion Obsidian version string or one of \n * - \"latest\": Get the current latest non-beta Obsidian version\n * - \"latest-beta\": Get the current latest beta Obsidian version (or latest is there is no current beta)\n * - \"earliest\": Get the `minAppVersion` set in set in your `manifest.json`\n * @param installerVersion Obsidian version string or one of \n * - \"latest\": Get the latest Obsidian installer\n * - \"earliest\": Get the oldest Obsidian installer compatible with the specified Obsidian app version\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\n async resolveVersions(appVersion: string, installerVersion = \"latest\"): Promise<[string, string]> {\n const versions = await this.getVersions();\n const appVersionInfo = await this.getVersionInfo(appVersion);\n\n if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {\n throw Error(`No compatible installers available for app version ${appVersion}`);\n }\n if (installerVersion == \"latest\") {\n installerVersion = appVersionInfo.maxInstallerVersion;\n } else if (installerVersion == \"earliest\") {\n installerVersion = appVersionInfo.minInstallerVersion;\n } else {\n installerVersion = semver.valid(installerVersion) ?? installerVersion;\n }\n const installerVersionInfo = versions.find(v => v.version == installerVersion);\n if (!installerVersionInfo || !installerVersionInfo.chromeVersion) {\n throw Error(`No Obsidian installer for version ${installerVersion} found`);\n }\n if (\n semver.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) ||\n semver.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)\n ) {\n throw Error(\n `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer ` +\n `>=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ` +\n `${installerVersionInfo.version} specified`\n )\n }\n\n return [appVersionInfo.version, installerVersionInfo.version];\n }\n\n /**\n * Gets details about an Obsidian version.\n * @param appVersion Obsidian app version (see {@link resolveVersions} for format)\n */\n async getVersionInfo(appVersion: string): Promise<ObsidianVersionInfo> {\n const versions = await this.getVersions();\n if (appVersion == \"latest-beta\") {\n appVersion = versions.at(-1)!.version;\n } else if (appVersion == \"latest\") {\n appVersion = versions.filter(v => !v.isBeta).at(-1)!.version;\n } else if (appVersion == \"earliest\") {\n appVersion = (await this.getRootManifest())?.minAppVersion;\n if (!appVersion) {\n throw Error('Unable to resolve Obsidian app appVersion \"earliest\", no manifest.json or minAppVersion found.')\n }\n } else {\n // if invalid match won't be found and we'll throw error below\n appVersion = semver.valid(appVersion) ?? appVersion;\n }\n const versionInfo = versions.find(v => v.version == appVersion);\n if (!versionInfo) {\n throw Error(`No Obsidian app version ${appVersion} found`);\n }\n\n return versionInfo;\n }\n\n /**\n * Downloads the Obsidian installer for the given version and platform. Returns the file path.\n * @param installerVersion Obsidian installer version to download. (see {@link resolveVersions} for format)\n */\n async downloadInstaller(installerVersion: string): Promise<string> {\n const installerVersionInfo = await this.getVersionInfo(installerVersion);\n return await this.downloadInstallerFromVersionInfo(installerVersionInfo);\n }\n\n /**\n * Helper for downloadInstaller that doesn't require the obsidian-versions.json file so it can be used in\n * updateObsidianVersionInfos\n */\n private async downloadInstallerFromVersionInfo(versionInfo: ObsidianVersionInfo): Promise<string> {\n const installerVersion = versionInfo.version;\n const {platform, arch} = process;\n const cacheDir = path.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);\n \n let installerPath: string\n let downloader: ((tmpDir: string) => Promise<string>)|undefined\n \n if (platform == \"linux\") {\n installerPath = path.join(cacheDir, \"obsidian\");\n let installerUrl: string|undefined\n if (arch.startsWith(\"arm\")) {\n installerUrl = versionInfo.downloads.appImageArm;\n } else {\n installerUrl = versionInfo.downloads.appImage;\n }\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const appImage = path.join(tmpDir, \"Obsidian.AppImage\");\n await fsAsync.writeFile(appImage, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianAppImage(appImage, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"win32\") {\n installerPath = path.join(cacheDir, \"Obsidian.exe\")\n const installerUrl = versionInfo.downloads.exe;\n let appArch: string|undefined\n if (arch == \"x64\") {\n appArch = \"app-64\"\n } else if (arch == \"ia32\") {\n appArch = \"app-32\"\n } else if (arch.startsWith(\"arm\")) {\n appArch = \"app-arm64\"\n }\n if (installerUrl && appArch) {\n downloader = async (tmpDir) => {\n const installerExecutable = path.join(tmpDir, \"Obsidian.exe\");\n await fsAsync.writeFile(installerExecutable, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianExe(installerExecutable, appArch, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"darwin\") {\n installerPath = path.join(cacheDir, \"Contents/MacOS/Obsidian\");\n const installerUrl = versionInfo.downloads.dmg;\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const dmg = path.join(tmpDir, \"Obsidian.dmg\");\n await fsAsync.writeFile(dmg, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianDmg(dmg, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else {\n throw Error(`Unsupported platform ${platform}`);\n }\n if (!downloader) {\n throw Error(`No Obsidian installer download available for v${installerVersion} ${platform} ${arch}`);\n }\n\n if (!(await fileExists(installerPath))) {\n console.log(`Downloading Obsidian installer v${installerVersion}...`)\n await fsAsync.mkdir(path.dirname(cacheDir), { recursive: true });\n await withTmpDir(cacheDir, downloader);\n }\n\n return installerPath;\n }\n\n /**\n * Downloads the Obsidian asar for the given version and platform. Returns the file path.\n * \n * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`\n * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * \n * @param appVersion Obsidian version to download (see {@link resolveVersions} for format)\n */\n async downloadApp(appVersion: string): Promise<string> {\n const appVersionInfo = await this.getVersionInfo(appVersion);\n const appUrl = appVersionInfo.downloads.asar;\n if (!appUrl) {\n throw Error(`No asar found for Obsidian version ${appVersion}`);\n }\n const appPath = path.join(this.cacheDir, 'obsidian-app', `obsidian-${appVersionInfo.version}.asar`);\n\n if (!(await fileExists(appPath))) {\n console.log(`Downloading Obsidian app v${appVersion} ...`)\n await fsAsync.mkdir(path.dirname(appPath), { recursive: true });\n\n await withTmpDir(appPath, async (tmpDir) => {\n const isInsidersBuild = new URL(appUrl).hostname.endsWith('.obsidian.md');\n const response = isInsidersBuild ? await fetchObsidianAPI(appUrl) : await fetch(appUrl);\n const archive = path.join(tmpDir, 'app.asar.gz');\n const asar = path.join(tmpDir, 'app.asar')\n await fsAsync.writeFile(archive, response.body as any);\n await pipeline(fs.createReadStream(archive), zlib.createGunzip(), fs.createWriteStream(asar));\n return asar;\n })\n }\n\n return appPath;\n }\n\n /**\n * Downloads chromedriver for the given Obsidian version.\n * \n * wdio will download chromedriver from the Chrome for Testing API automatically (see\n * https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints). However, Google has only put\n * chromedriver since v115.0.5763.0 in that API, so wdio can't automatically download older versions of chromedriver\n * for old Electron versions. Here we download chromedriver for older versions ourselves using the @electron/get\n * package which fetches chromedriver from https://github.com/electron/electron/releases.\n * \n * @param installerVersion Obsidian installer version (see {@link resolveVersions} for format)\n */\n async downloadChromedriver(installerVersion: string): Promise<string> {\n const versionInfo = await this.getVersionInfo(installerVersion);\n const electronVersion = versionInfo.electronVersion;\n if (!electronVersion) {\n throw Error(`${installerVersion} is not an Obsidian installer version.`)\n }\n\n const chromedriverZipPath = await downloadArtifact({\n version: electronVersion,\n artifactName: 'chromedriver',\n cacheRoot: path.join(this.cacheDir, \"chromedriver-legacy\"),\n unsafelyDisableChecksums: true, // the checksums are slow and run even on cache hit.\n });\n\n let chromedriverPath: string\n if (process.platform == \"win32\") {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver.exe\");\n } else {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver\");\n }\n\n if (!(await fileExists(chromedriverPath))) {\n console.log(`Downloading legacy chromedriver for electron ${electronVersion} ...`)\n await withTmpDir(chromedriverPath, async (tmpDir) => {\n await extractZip(chromedriverZipPath, { dir: tmpDir });\n return path.join(tmpDir, path.basename(chromedriverPath));\n })\n }\n\n return chromedriverPath;\n }\n\n /** Gets the latest version of a plugin. */\n private async getLatestPluginVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-plugins\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a plugin from a GitHub repo to the cache.\n * @param repo Repo\n * @param version Version of the plugin to install or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadGitHubPlugin(repo: string, version = \"latest\"): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n if (version == \"latest\") {\n version = await this.getLatestPluginVersion(repo);\n }\n if (!semver.valid(version)) {\n throw Error(`Invalid version \"${version}\"`);\n }\n version = semver.valid(version)!;\n\n const pluginDir = path.join(this.cacheDir, \"obsidian-plugins\", repo, version);\n if (!(await fileExists(pluginDir))) {\n await fsAsync.mkdir(path.dirname(pluginDir), { recursive: true });\n await withTmpDir(pluginDir, async (tmpDir) => {\n const assetsToDownload = {'manifest.json': true, 'main.js': true, 'styles.css': false};\n await Promise.all(\n Object.entries(assetsToDownload).map(async ([file, required]) => {\n const url = `https://github.com/${repo}/releases/download/${version}/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else if (required) {\n throw Error(`No ${file} found for ${repo} version ${version}`)\n }\n })\n )\n return tmpDir;\n });\n }\n\n return pluginDir;\n }\n\n /**\n * Downloads a community plugin to the cache.\n * @param id Id of the plugin\n * @param version Version of the plugin to install, or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadCommunityPlugin(id: string, version = \"latest\"): Promise<string> {\n const communityPlugins = await this.getCommunityPlugins();\n const pluginInfo = communityPlugins.find(p => p.id == id);\n if (!pluginInfo) {\n throw Error(`No plugin with id ${id} found.`);\n }\n return await this.downloadGitHubPlugin(pluginInfo.repo, version);\n }\n\n /**\n * Downloads a list of plugins to the cache and returns a list of `DownloadedPluginEntry` with the downloaded paths.\n * Also adds the `id` property to the plugins based on the manifest.\n * \n * You can download plugins from GitHub using `{repo: \"org/repo\"}` and community plugins using `{id: 'plugin-id'}`.\n * Local plugins will just be passed through.\n * \n * @param plugins List of plugins to download.\n */\n async downloadPlugins(plugins: PluginEntry[]): Promise<DownloadedPluginEntry[]> {\n return await Promise.all(\n plugins.map(async (plugin) => {\n if (typeof plugin == \"object\" && \"originalType\" in plugin) {\n return {...plugin as DownloadedPluginEntry}\n }\n let pluginPath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof plugin == \"string\") {\n pluginPath = path.resolve(plugin);\n originalType = \"local\";\n } else if (\"path\" in plugin) {;\n pluginPath = path.resolve(plugin.path);\n originalType = \"local\";\n } else if (\"repo\" in plugin) {\n pluginPath = await this.downloadGitHubPlugin(plugin.repo, plugin.version);\n originalType = \"github\";\n } else if (\"id\" in plugin) {\n pluginPath = await this.downloadCommunityPlugin(plugin.id, plugin.version);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of plugin path, repo, or id\")\n }\n\n const manifestPath = path.join(pluginPath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No plugin found at ${pluginPath}`)\n }\n let pluginId = (typeof plugin == \"object\" && (\"id\" in plugin)) ? plugin.id : undefined;\n if (!pluginId) {\n pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} malformed.`);\n }\n }\n if (!(await fileExists(path.join(pluginPath, \"main.js\")))) {\n throw Error(`No main.js found under ${pluginPath}`)\n }\n\n let enabled: boolean\n if (typeof plugin == \"string\") {\n enabled = true\n } else {\n enabled = plugin.enabled ?? true;\n }\n return {path: pluginPath, id: pluginId, enabled, originalType}\n })\n );\n }\n\n /** Gets the latest version of a theme. */\n private async getLatestThemeVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-themes\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a theme from a GitHub repo to the cache.\n * @param repo Repo\n * @returns path to the downloaded theme\n */\n private async downloadGitHubTheme(repo: string): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n // Obsidian theme's are just pulled from the repo HEAD, not releases, so we can't really choose a specific \n // version of a theme.\n // We use the manifest.json version to check if the theme has changed.\n const version = await this.getLatestThemeVersion(repo);\n const themeDir = path.join(this.cacheDir, \"obsidian-themes\", repo, version);\n\n if (!(await fileExists(themeDir))) {\n await fsAsync.mkdir(path.dirname(themeDir), { recursive: true });\n await withTmpDir(themeDir, async (tmpDir) => {\n const assetsToDownload = ['manifest.json', 'theme.css'];\n await Promise.all(\n assetsToDownload.map(async (file) => {\n const url = `https://raw.githubusercontent.com/${repo}/HEAD/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else {\n throw Error(`No ${file} found for ${repo}`);\n }\n })\n )\n return tmpDir;\n });\n }\n\n return themeDir;\n }\n\n /**\n * Downloads a community theme to the cache.\n * @param name name of the theme\n * @returns path to the downloaded theme\n */\n private async downloadCommunityTheme(name: string): Promise<string> {\n const communityThemes = await this.getCommunityThemes();\n const themeInfo = communityThemes.find(p => p.name == name);\n if (!themeInfo) {\n throw Error(`No theme with name ${name} found.`);\n }\n return await this.downloadGitHubTheme(themeInfo.repo);\n }\n\n /**\n * Downloads a list of themes to the cache and returns a list of `DownloadedThemeEntry` with the downloaded paths.\n * Also adds the `name` property to the plugins based on the manifest.\n * \n * You can download themes from GitHub using `{repo: \"org/repo\"}` and community themes using `{name: 'theme-name'}`.\n * Local themes will just be passed through.\n * \n * @param themes List of themes to download\n */\n async downloadThemes(themes: ThemeEntry[]): Promise<DownloadedThemeEntry[]> {\n return await Promise.all(\n themes.map(async (theme) => {\n if (typeof theme == \"object\" && \"originalType\" in theme) {\n return {...theme as DownloadedThemeEntry}\n }\n let themePath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof theme == \"string\") {\n themePath = path.resolve(theme);\n originalType = \"local\";\n } else if (\"path\" in theme) {;\n themePath = path.resolve(theme.path);\n originalType = \"local\";\n } else if (\"repo\" in theme) {\n themePath = await this.downloadGitHubTheme(theme.repo);\n originalType = \"github\";\n } else if (\"name\" in theme) {\n themePath = await this.downloadCommunityTheme(theme.name);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of theme path, repo, or name\")\n }\n\n const manifestPath = path.join(themePath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No theme found at ${themePath}`)\n }\n let themeName = (typeof theme == \"object\" && (\"name\" in theme)) ? theme.name : undefined;\n if (!themeName) {\n const manifestPath = path.join(themePath, \"manifest.json\");\n themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${themePath}/manifest.json malformed.`);\n }\n }\n if (!(await fileExists(path.join(themePath, \"theme.css\")))) {\n throw Error(`No theme.css found under ${themePath}`)\n }\n\n let enabled: boolean\n if (typeof theme == \"string\") {\n enabled = true\n } else {\n enabled = theme.enabled ?? true;\n }\n return {path: themePath, name: themeName, enabled: enabled, originalType};\n })\n );\n }\n\n /**\n * Installs plugins into an Obsidian vault\n * @param vault Path to the vault to install the plugins in\n * @param plugins List plugins to install\n */\n async installPlugins(vault: string, plugins: PluginEntry[]) {\n const downloadedPlugins = await this.downloadPlugins(plugins);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n const enabledPluginsPath = path.join(obsidianDir, 'community-plugins.json');\n let originalEnabledPlugins: string[] = [];\n if (await fileExists(enabledPluginsPath)) {\n originalEnabledPlugins = JSON.parse(await fsAsync.readFile(enabledPluginsPath, 'utf-8'));\n }\n let enabledPlugins = [...originalEnabledPlugins];\n\n for (const {path: pluginPath, enabled = true, originalType} of downloadedPlugins) {\n const manifestPath = path.join(pluginPath, 'manifest.json');\n const pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n\n const pluginDest = path.join(obsidianDir, 'plugins', pluginId);\n await fsAsync.mkdir(pluginDest, { recursive: true });\n\n const files: Record<string, [boolean, boolean]> = {\n \"manifest.json\": [true, true],\n \"main.js\": [true, true],\n \"styles.css\": [false, true],\n \"data.json\": [false, false],\n }\n for (const [file, [required, deleteIfMissing]] of Object.entries(files)) {\n if (await fileExists(path.join(pluginPath, file))) {\n await linkOrCp(path.join(pluginPath, file), path.join(pluginDest, file));\n } else if (required) {\n throw Error(`${pluginPath}/${file} missing.`);\n } else if (deleteIfMissing) {\n await fsAsync.rm(path.join(pluginDest, file), {force: true});\n }\n }\n\n const pluginAlreadyListed = enabledPlugins.includes(pluginId);\n if (enabled && !pluginAlreadyListed) {\n enabledPlugins.push(pluginId)\n } else if (!enabled && pluginAlreadyListed) {\n enabledPlugins = enabledPlugins.filter(p => p != pluginId);\n }\n\n if (originalType == \"local\") {\n // Add a .hotreload file for the https://github.com/pjeby/hot-reload plugin\n await fsAsync.writeFile(path.join(pluginDest, '.hotreload'), '');\n }\n }\n\n if (!_.isEqual(enabledPlugins, originalEnabledPlugins)) {\n await fsAsync.writeFile(enabledPluginsPath, JSON.stringify(enabledPlugins, undefined, 2));\n }\n }\n\n /** \n * Installs themes into an Obsidian vault\n * @param vault Path to the theme to install the themes in\n * @param themes List of themes to install\n */\n async installThemes(vault: string, themes: ThemeEntry[]) {\n const downloadedThemes = await this.downloadThemes(themes);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n let enabledTheme: string|undefined = undefined;\n\n for (const {path: themePath, enabled = true} of downloadedThemes) {\n const manifestPath = path.join(themePath, 'manifest.json');\n const cssPath = path.join(themePath, 'theme.css');\n\n const themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n if (!(await fileExists(cssPath))) {\n throw Error(`${cssPath} missing.`);\n }\n\n const themeDest = path.join(obsidianDir, 'themes', themeName);\n await fsAsync.mkdir(themeDest, { recursive: true });\n\n await linkOrCp(manifestPath, path.join(themeDest, \"manifest.json\"));\n await linkOrCp(cssPath, path.join(themeDest, \"theme.css\"));\n\n if (enabledTheme && enabled) {\n throw Error(\"You can only have one enabled theme.\")\n } else if (enabled) {\n enabledTheme = themeName;\n }\n }\n\n if (themes.length > 0) { // Only update appearance.json if we set the themes\n const appearancePath = path.join(obsidianDir, 'appearance.json');\n let appearance: any = {}\n if (await fileExists(appearancePath)) {\n appearance = JSON.parse(await fsAsync.readFile(appearancePath, 'utf-8'));\n }\n appearance.cssTheme = enabledTheme ?? \"\";\n await fsAsync.writeFile(appearancePath, JSON.stringify(appearance, undefined, 2));\n }\n }\n\n /**\n * Sets up the config dir to use for the `--user-data-dir` in obsidian. Returns the path to the created config dir.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format)\n * @param params.installerVersion Obsidian version string.\n * @param params.appPath Path to the asar file to install. Will download if omitted.\n * @param params.vault Path to the vault to open in Obsidian.\n * @param params.dest Destination path for the config dir. If omitted it will create a temporary directory.\n */\n async setupConfigDir(params: {\n appVersion: string, installerVersion: string,\n appPath?: string,\n vault?: string,\n dest?: string,\n }): Promise<string> {\n const [appVersion, installerVersion] = await this.resolveVersions(params.appVersion, params.installerVersion);\n // configDir will be passed to --user-data-dir, so Obsidian is somewhat sandboxed. We set up \"obsidian.json\" so\n // that Obsidian opens the vault by default and doesn't check for updates.\n const configDir = params.dest ?? await makeTmpDir('obs-launcher-config-');\n \n let obsidianJson: any = {\n updateDisabled: true, // Prevents Obsidian trying to auto-update on boot.\n }\n let localStorageData: Record<string, string> = {\n \"most-recently-installed-version\": appVersion, // prevents the changelog page on boot\n }\n\n if (params.vault !== undefined) {\n if (!await fileExists(params.vault)) {\n throw Error(`Vault path ${params.vault} doesn't exist.`)\n }\n const vaultId = crypto.randomBytes(8).toString(\"hex\");\n obsidianJson = {\n ...obsidianJson,\n vaults: {\n [vaultId]: {\n path: path.resolve(params.vault),\n ts: new Date().getTime(),\n open: true,\n },\n },\n };\n localStorageData = {\n ...localStorageData,\n [`enable-plugin-${vaultId}`]: \"true\", // Disable \"safe mode\" and enable plugins\n }\n }\n\n await fsAsync.writeFile(path.join(configDir, 'obsidian.json'), JSON.stringify(obsidianJson));\n\n let appPath = params.appPath;\n if (!appPath) {\n appPath = await this.downloadApp(appVersion);\n }\n await linkOrCp(appPath, path.join(configDir, path.basename(appPath)));\n\n const localStorage = new ChromeLocalStorage(configDir);\n await localStorage.setItems(\"app://obsidian.md\", localStorageData)\n await localStorage.close();\n\n return configDir;\n }\n\n /**\n * Sets up a vault for Obsidian, installing plugins and themes and optionally copying the vault to a temporary\n * directory first.\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @returns Path to the copied vault (or just the path to the vault if copy is false)\n */\n async setupVault(params: {\n vault: string,\n copy?: boolean,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n }): Promise<string> {\n let vault = params.vault;\n if (params.copy) {\n const dest = await makeTmpDir('obs-launcher-vault-');\n await fsAsync.cp(vault, dest, { recursive: true, preserveTimestamps: true });\n vault = dest;\n }\n await this.installPlugins(vault, params.plugins ?? []);\n await this.installThemes(vault, params.themes ?? []);\n\n return vault;\n }\n\n /**\n * Downloads and launches Obsidian with a sandboxed config dir and a specifc vault open. Optionally install plugins\n * and themes first.\n * \n * This is just a shortcut for calling `downloadApp`, `downloadInstaller`, `setupVault` and `setupConfDir`.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format). Default \"latest\"\n * @param params.installerVersion Obsidian installer version (see {@link resolveVersions} for format).\n * Default \"latest\"\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @param params.args CLI args to pass to Obsidian\n * @param params.spawnOptions Options to pass to `spawn`\n * @returns The launched child process and the created tmpdirs\n */\n async launch(params: {\n appVersion?: string, installerVersion?: string,\n copy?: boolean,\n vault?: string,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n args?: string[],\n spawnOptions?: child_process.SpawnOptions,\n }): Promise<{proc: child_process.ChildProcess, configDir: string, vault?: string}> {\n const [appVersion, installerVersion] = await this.resolveVersions(\n params.appVersion ?? \"latest\",\n params.installerVersion ?? \"latest\",\n );\n const appPath = await this.downloadApp(appVersion);\n const installerPath = await this.downloadInstaller(installerVersion);\n\n let vault = params.vault;\n if (vault) {\n vault = await this.setupVault({\n vault,\n copy: params.copy ?? false,\n plugins: params.plugins, themes: params.themes,\n })\n }\n\n const configDir = await this.setupConfigDir({ appVersion, installerVersion, appPath, vault });\n\n // Spawn child.\n const proc = child_process.spawn(installerPath, [\n `--user-data-dir=${configDir}`,\n ...(params.args ?? []),\n ], {\n ...params.spawnOptions,\n });\n\n return {proc, configDir, vault};\n }\n\n /** \n * Updates the info obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands\n * and in wdio-obsidian-service to get metadata about Obsidian versions in one place such as minInstallerVersion and\n * the internal electron version.\n */\n async updateObsidianVersionInfos(\n original?: ObsidianVersionInfos, { maxInstances = 1 } = {},\n ): Promise<ObsidianVersionInfos> {\n const repo = 'obsidianmd/obsidian-releases';\n\n let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {\n path: \"desktop-releases.json\",\n since: original?.metadata.commit_date,\n });\n commitHistory.reverse();\n if (original) {\n commitHistory = _.takeRightWhile(commitHistory, c => c.sha != original.metadata.commit_sha);\n }\n \n const fileHistory: any[] = await pool(8, commitHistory, commit =>\n fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then(r => r.json())\n );\n \n const githubReleases = await fetchGitHubAPIPaginated(`repos/${repo}/releases`);\n \n const versionMap: _.Dictionary<Partial<ObsidianVersionInfo>> = _.keyBy(\n original?.versions ?? [],\n v => v.version,\n );\n \n for (const {beta, ...current} of fileHistory) {\n if (beta && (!versionMap[beta.latestVersion] || versionMap[beta.latestVersion].isBeta)) {\n versionMap[beta.latestVersion] = _.merge({}, versionMap[beta.latestVersion],\n parseObsidianDesktopRelease(beta, true),\n );\n }\n versionMap[current.latestVersion] = _.merge({}, versionMap[current.latestVersion],\n parseObsidianDesktopRelease(current, false),\n )\n }\n \n for (const release of githubReleases) {\n if (versionMap.hasOwnProperty(release.name)) {\n versionMap[release.name] = _.merge({}, versionMap[release.name], parseObsidianGithubRelease(release));\n }\n }\n \n const dependencyVersions = await pool(maxInstances,\n Object.values(versionMap).filter(v => v.downloads?.appImage && !v.chromeVersion),\n async (v) => {\n const binaryPath = await this.downloadInstallerFromVersionInfo(v as ObsidianVersionInfo);\n const electronVersionInfo = await getElectronVersionInfo(v.version!, binaryPath);\n return {...v, ...electronVersionInfo};\n },\n )\n for (const deps of dependencyVersions) {\n versionMap[deps.version!] = _.merge({}, versionMap[deps.version!], deps);\n }\n \n // populate minInstallerVersion and maxInstallerVersion and add corrections\n let minInstallerVersion: string|undefined = undefined;\n let maxInstallerVersion: string|undefined = undefined;\n for (const version of Object.keys(versionMap).sort(semver.compare)) {\n // older versions have 0.0.0 as there min version, which doesn't exist anywhere we can download.\n // we'll set those to the first available installer version.\n if (!minInstallerVersion && versionMap[version].chromeVersion) {\n minInstallerVersion = version;\n }\n if (versionMap[version].downloads!.appImage) {\n maxInstallerVersion = version;\n }\n versionMap[version] = mergeKeepUndefined({}, versionMap[version],\n {\n minInstallerVersion: versionMap[version].minInstallerVersion ?? minInstallerVersion,\n maxInstallerVersion: maxInstallerVersion,\n },\n correctObsidianVersionInfo(versionMap[version]),\n );\n }\n \n const result: ObsidianVersionInfos = {\n metadata: {\n commit_date: commitHistory.at(-1)?.commit.committer.date ?? original?.metadata.commit_date,\n commit_sha: commitHistory.at(-1)?.sha ?? original?.metadata.commit_sha,\n timestamp: original?.metadata.timestamp ?? \"\", // set down below\n },\n versions: Object.values(versionMap)\n .map(normalizeObsidianVersionInfo)\n .sort((a, b) => semver.compare(a.version, b.version)),\n }\n\n // Update timestamp if anything has changed. Also, GitHub will cancel scheduled workflows if the repository is\n // \"inactive\" for 60 days. So we'll update the timestamp every once in a while even if there are no Obsidian\n // updates to make sure there's commit activity in the repo.\n const dayMs = 24 * 60 * 60 * 1000;\n const timeSinceLastUpdate = new Date().getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();\n if (!_.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {\n result.metadata.timestamp = new Date().toISOString();\n }\n\n return result;\n }\n\n /**\n * Returns true if the Obsidian version is already in the cache.\n * @param type on of \"app\" or \"installer\"\n * @param version Obsidian app/installer version (see {@link resolveVersions} for format)\n */\n async isInCache(type: \"app\"|\"installer\", version: string) {\n version = (await this.getVersionInfo(version)).version;\n\n let dest: string\n if (type == \"app\") {\n dest = `obsidian-app/obsidian-${version}.asar`;\n } else { // type == \"installer\"\n const {platform, arch} = process;\n dest =`obsidian-installer/${platform}-${arch}/Obsidian-${version}`;\n }\n\n return (await fileExists(path.join(this.cacheDir, dest)));\n }\n\n /**\n * Returns true if we either have the credentials to download the version or it's already in cache.\n * This is only relevant for Obsidian beta versions, as they require Obsidian insider credentials to download.\n * @param version Obsidian version (see {@link resolveVersions} for format)\n */\n async isAvailable(version: string): Promise<boolean> {\n const versionInfo = await this.getVersionInfo(version);\n const versionExists = !!(versionInfo.downloads.asar && versionInfo.minInstallerVersion)\n\n if (versionInfo.isBeta) {\n const hasCreds = !!(process.env['OBSIDIAN_USERNAME'] && process.env['OBSIDIAN_PASSWORD']);\n const inCache = await this.isInCache('app', versionInfo.version);\n return versionExists && (hasCreds || inCache);\n } else {\n return versionExists;\n }\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport os from \"os\"\nimport { PromisePool } from '@supercharge/promise-pool'\nimport _ from \"lodash\"\n\n/// Files ///\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create tmpdir under the system temporary directory.\n * @param prefix \n * @returns \n */\nexport async function makeTmpDir(prefix?: string) {\n return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? 'tmp-'));\n}\n\n/**\n * Handles creating a file \"atomically\" by creating a tmpDir, then downloading or otherwise creating the file under it,\n * then renaming it to the final location when done.\n * @param dest Path the file should end up at.\n * @param func Function takes path to a temporary directory it can use as scratch space. The path it returns will be\n * moved to `dest`. If no path is returned, it will move the whole tmpDir to dest.\n */\nexport async function withTmpDir(dest: string, func: (tmpDir: string) => Promise<string|void>): Promise<void> {\n dest = path.resolve(dest);\n const tmpDir = await fsAsync.mkdtemp(path.join(path.dirname(dest), `.${path.basename(dest)}.tmp.`));\n try {\n let result = await func(tmpDir) ?? tmpDir;\n if (!path.isAbsolute(result)) {\n result = path.join(tmpDir, result);\n } else if (!path.resolve(result).startsWith(tmpDir)) {\n throw new Error(`Returned path ${result} not under tmpDir`)\n }\n // rename will overwrite files but not directories\n if (await fileExists(dest) && (await fsAsync.stat(dest)).isDirectory()) {\n await fsAsync.rename(dest, tmpDir + \".old\")\n }\n \n await fsAsync.rename(result, dest);\n await fsAsync.rm(tmpDir + \".old\", { recursive: true, force: true });\n } finally {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Tries to hardlink a file, falls back to copy if it fails\n */\nexport async function linkOrCp(src: string, dest: string) {\n try {\n await fsAsync.link(src, dest);\n } catch {\n await fsAsync.copyFile(src, dest);\n }\n}\n\n\n/// Promises ///\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Await a promise or reject if it takes longer than timeout.\n */\nexport async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n let timer: NodeJS.Timeout;\n const result = Promise.race([\n promise,\n new Promise<T>((resolve, reject) => timer = setTimeout(() => reject(Error(\"Promise timed out\")), timeout))\n ])\n return result.finally(() => clearTimeout(timer));\n}\n\n/**\n * Wrapper around PromisePool that throws on any error.\n */\nexport async function pool<T, U>(size: number, items: T[], func: (item: T) => Promise<U>): Promise<U[]> {\n const { results } = await PromisePool\n .for(items)\n .withConcurrency(size)\n .handleError(async (error) => { throw error; })\n .useCorrespondingResults()\n .process(func);\n return results as U[];\n}\n\nexport type SuccessResult<T> = {success: true, result: T, error: undefined};\nexport type ErrorResult = {success: false, result: undefined, error: any};\nexport type Maybe<T> = SuccessResult<T>|ErrorResult;\n\n/**\n * Helper for handling asynchronous errors with less hassle.\n */\nexport async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {\n return promise\n .then(r => ({success: true, result: r, error: undefined} as const))\n .catch(e => ({success: false, result: undefined, error: e} as const));\n}\n\n\n/**\n * Lodash _.merge but overwrites values with undefined if present, instead of ignoring undefined.\n */\nexport function mergeKeepUndefined(object: any, ...sources: any[]) {\n return _.mergeWith(object, ...sources,\n (objValue: any, srcValue: any, key: any, obj: any) => {\n if (_.isPlainObject(obj) && objValue !== srcValue && srcValue === undefined) {\n obj[key] = srcValue\n }\n }\n );\n}","import _ from \"lodash\"\nimport fsAsync from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\n\n\n/**\n * GitHub API stores pagination information in the \"Link\" header. The header looks like this:\n * ```\n * <https://api.github.com/repositories/1300192/issues?page=2>; rel=\"prev\", <https://api.github.com/repositories/1300192/issues?page=4>; rel=\"next\"\n * ```\n */\nexport function parseLinkHeader(linkHeader: string): Record<string, Record<string, string>> {\n function parseLinkData(linkData: string) {\n return Object.fromEntries(\n linkData.split(\";\").flatMap(x => {\n const partMatch = x.trim().match(/^([^=]+?)\\s*=\\s*\"?([^\"]+)\"?$/);\n return partMatch ? [[partMatch[1], partMatch[2]]] : [];\n })\n )\n }\n\n const linkDatas = linkHeader\n .split(/,\\s*(?=<)/)\n .flatMap(link => {\n const linkMatch = link.trim().match(/^<([^>]*)>(.*)$/);\n if (linkMatch) {\n return [{\n url: linkMatch[1],\n ...parseLinkData(linkMatch[2]),\n } as Record<string, string>];\n } else {\n return [];\n }\n })\n .filter(l => l.rel)\n return Object.fromEntries(linkDatas.map(l => [l.rel, l]));\n};\n\n\nfunction createURL(url: string, base: string, params: Record<string, any> = {}) {\n params =_.pickBy(params, x => x !== undefined);\n const urlObj = new URL(url, base);\n const searchParams = new URLSearchParams({...Object.fromEntries(urlObj.searchParams), ...params});\n if ([...searchParams].length > 0) {\n urlObj.search = '?' + searchParams;\n }\n return urlObj.toString();\n}\n\n\n/**\n * Fetch from the GitHub API. Uses GITHUB_TOKEN if available. You can access the API without a token, but will hit\n * the usage caps very quickly.\n */\nexport async function fetchGitHubAPI(url: string, params: Record<string, any> = {}) {\n url = createURL(url, \"https://api.github.com\", params)\n const token = process.env.GITHUB_TOKEN;\n const headers: Record<string, string> = token ? {Authorization: \"Bearer \" + token} : {};\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`GitHub API error: ${await response.text()}`);\n }\n return await response;\n}\n\n\n/**\n * Fetch all data from a paginated GitHub API request.\n */\nexport async function fetchGitHubAPIPaginated(url: string, params: Record<string, any> = {}) {\n const results = [];\n let next: string|undefined = createURL(url, \"https://api.github.com\", { per_page: 100, ...params });\n while (next) {\n const response = await fetchGitHubAPI(next);\n results.push(...await response.json() as any);\n next = parseLinkHeader(response.headers.get('link') ?? '').next?.url;\n }\n return results;\n}\n\n/**\n * Fetch from the Obsidian API to download insider versions. Uses OBSIDIAN_USERNAME and\n * OBSIDIAN_PASSWORD environment variables.\n */\nexport async function fetchObsidianAPI(url: string) {\n url = createURL(url, \"https://releases.obsidian.md\");\n\n const username = process.env.OBSIDIAN_USERNAME;\n const password = process.env.OBSIDIAN_PASSWORD;\n if (!username || !password) {\n throw Error(\"OBSIDIAN_USERNAME or OBSIDIAN_PASSWORD environment variables are required to access the Obsidian API for beta versions.\")\n }\n\n const response = await fetch(url, {\n headers: {\n // For some reason you have to set the User-Agent or it won't let you download\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',\n \"Origin\": \"app://obsidian.md\",\n 'Authorization': 'Basic ' + btoa(username + \":\" + password),\n },\n })\n return response;\n}\n\n\n/**\n * Fetches a URL returning its content as a string. Throws if response is not OK.\n * URL can be a file url.\n */\nexport async function fetchWithFileUrl(url: string) {\n if (url.startsWith(\"file:\")) {\n return await fsAsync.readFile(fileURLToPath(url), 'utf-8');\n } else {\n const response = await fetch(url);\n if (response.ok) {\n return response.text()\n } else {\n throw Error(`Request failed with ${response.status}: ${response.text()}`)\n }\n }\n}\n","import path from \"path\"\nimport { ClassicLevel } from \"classic-level\"\n\n\n/**\n * Class to directly manipulate chrome/electron local storage.\n *\n * Normally you'd just manipulate `localStorage` directly during the webdriver tests. However, there's not a built in\n * way to set up localStorage values *before* the app boots. We need to set `enable-plugins` and some other keys for\n * Obsidian to read during the boot process. This class lets us setup the local storage before launching Obsidian.\n */\nexport default class ChromeLocalStorage {\n private db: ClassicLevel;\n\n /** Pass the path to the user data dir for Chrome/Electron. If it doesn't exist it will be created. */\n constructor(public readonly userDataDir: string) {\n this.db = new ClassicLevel(path.join(userDataDir, 'Local Storage/leveldb/'));\n }\n\n private encodeKey = (domain: string, key: string) => `_${domain}\\u0000\\u0001${key}`;\n private decodeKey = (key: string) => key.slice(1).split(\"\\u0000\\u0001\") as [string, string];\n private encodeValue = (value: string) => `\\u0001${value}`;\n private decodeValue = (value: string) => value.slice(1);\n\n /**\n * Get a value from localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to retreive\n */\n async getItem(domain: string, key: string): Promise<string|null> {\n const value = await this.db.get(this.encodeKey(domain, key));\n return (value === undefined) ? null : this.decodeValue(value);\n }\n\n /**\n * Set a value in localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to set\n * @param value Value to set\n */\n async setItem(domain: string, key: string, value: string) {\n await this.db.put(this.encodeKey(domain, key), this.encodeValue(value))\n }\n\n /**\n * Removes a key from localStorage\n * @param domain Domain the values is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key key to remove.\n */\n async removeItem(domain: string, key: string) {\n await this.db.del(this.encodeKey(domain, key))\n }\n\n /** Get all items in localStorage as [domain, key, value] tuples */\n async getAllItems(): Promise<[string, string, string][]> {\n const result: [string, string, string][] = []\n for await (const pair of this.db.iterator()) {\n if (pair[0].startsWith(\"_\")) { // ignore the META values\n const [domain, key] = this.decodeKey(pair[0]);\n const value = this.decodeValue(pair[1]);\n result.push([domain, key, value]);\n }\n }\n return result;\n }\n\n /**\n * Write multiple values to localStorage in batch\n * @param domain Domain the values are under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param data key/value mapping to write\n */\n async setItems(domain: string, data: Record<string, string>) {\n await this.db.batch(\n Object.entries(data).map(([key, value]) => ({\n type: \"put\",\n key: this.encodeKey(domain, key),\n value: this.encodeValue(value),\n }))\n )\n }\n\n /**\n * Close the localStorage database.\n */\n async close() {\n await this.db.close();\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { promisify } from \"util\";\nimport child_process from \"child_process\"\nimport which from \"which\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\nimport CDP from 'chrome-remote-interface'\nimport { makeTmpDir, withTmpDir, maybe, withTimeout, sleep } from \"./utils.js\";\nimport { ObsidianVersionInfo } from \"./types.js\";\nconst execFile = promisify(child_process.execFile);\n\n\nexport function normalizeGitHubRepo(repo: string) {\n return repo.replace(/^(https?:\\/\\/)?(github.com\\/)/, '')\n}\n\n/**\n * Running AppImage requires libfuse2, extracting the AppImage first avoids that.\n */\nexport async function extractObsidianAppImage(appImage: string, dest: string) {\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.chmod(appImage, 0o755);\n await execFile(appImage, [\"--appimage-extract\"], {cwd: tmpDir});\n return path.join(tmpDir, 'squashfs-root');\n })\n}\n\n/**\n * Obsidian appears to use NSIS to bundle their Window's installers. We want to extract the executable\n * files directly without running the installer. 7zip can extract the raw files from the exe.\n */\nexport async function extractObsidianExe(exe: string, appArch: string, dest: string) {\n const path7z = await which(\"7z\", { nothrow: true });\n if (!path7z) {\n throw new Error(\n \"Downloading Obsidian for Windows requires 7zip to be installed and available on the PATH. \" +\n \"You install it from https://www.7-zip.org and then add the install location to the PATH.\"\n );\n }\n exe = path.resolve(exe);\n // The installer contains several `.7z` files with files for different architectures \n const subArchive = path.join('$PLUGINSDIR', appArch + \".7z\");\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const extractedInstaller = path.join(tmpDir, \"installer\");\n await execFile(path7z, [\"x\", \"-o\" + extractedInstaller, exe, subArchive]);\n const extractedObsidian = path.join(tmpDir, \"obsidian\");\n await execFile(path7z, [\"x\", \"-o\" + extractedObsidian, path.join(extractedInstaller, subArchive)]);\n return extractedObsidian;\n })\n}\n\n/**\n * Extract the executable from the Obsidian dmg installer.\n */\nexport async function extractObsidianDmg(dmg: string, dest: string) {\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const proc = await execFile('hdiutil', ['attach', '-nobrowse', '-readonly', dmg]);\n const volume = proc.stdout.match(/\\/Volumes\\/.*$/m)![0];\n const obsidianApp = path.join(volume, \"Obsidian.app\");\n try {\n await fsAsync.cp(obsidianApp, tmpDir, {recursive: true, verbatimSymlinks: true});\n } finally {\n await execFile('hdiutil', ['detach', volume]);\n }\n return tmpDir;\n })\n}\n\n\nexport function parseObsidianDesktopRelease(fileRelease: any, isBeta: boolean): Partial<ObsidianVersionInfo> {\n return {\n version: fileRelease.latestVersion,\n minInstallerVersion: fileRelease.minimumVersion != '0.0.0' ? fileRelease.minimumVersion : undefined,\n isBeta: isBeta,\n downloads: {\n asar: fileRelease.downloadUrl,\n },\n };\n}\n\nexport function parseObsidianGithubRelease(gitHubRelease: any): Partial<ObsidianVersionInfo> {\n const version = gitHubRelease.name;\n const assets: string[] = gitHubRelease.assets.map((a: any) => a.browser_download_url);\n const downloads = {\n appImage: assets.find(u => u.match(`${version}.AppImage$`)),\n appImageArm: assets.find(u => u.match(`${version}-arm64.AppImage$`)),\n apk: assets.find(u => u.match(`${version}.apk$`)),\n asar: assets.find(u => u.match(`${version}.asar.gz$`)),\n dmg: assets.find(u => u.match(`${version}(-universal)?.dmg$`)),\n exe: assets.find(u => u.match(`${version}.exe$`)),\n }\n\n return {\n version: version,\n gitHubRelease: gitHubRelease.html_url,\n downloads: downloads,\n }\n}\n\n/**\n * Extract electron and chrome versions for an Obsidian version.\n */\nexport async function getElectronVersionInfo(\n version:string, binaryPath: string,\n): Promise<Partial<ObsidianVersionInfo>> {\n console.log(`${version}: Retrieving electron & chrome versions...`);\n\n const configDir = await makeTmpDir('fetch-obsidian-versions-');\n\n const proc = child_process.spawn(binaryPath, [\n `--remote-debugging-port=0`, // 0 will make it choose a random available port\n '--test-type=webdriver',\n `--user-data-dir=${configDir}`,\n '--no-sandbox', // Workaround for SUID issue, see https://github.com/electron/electron/issues/42510\n ]);\n const procExit = new Promise<number>((resolve) => proc.on('exit', (code) => resolve(code ?? -1)));\n // proc.stdout.on('data', data => console.log(`stdout: ${data}`));\n // proc.stderr.on('data', data => console.log(`stderr: ${data}`));\n\n let dependencyVersions: any;\n try {\n // Wait for the logs showing that Obsidian is ready, and pull the chosen DevTool Protocol port from it\n const portPromise = new Promise<number>((resolve, reject) => {\n procExit.then(() => reject(\"Processed ended without opening a port\"))\n proc.stderr.on('data', data => {\n const port = data.toString().match(/ws:\\/\\/[\\w.]+?:(\\d+)/)?.[1];\n if (port) {\n resolve(Number(port));\n }\n });\n })\n\n const port = await maybe(withTimeout(portPromise, 10 * 1000));\n if (!port.success) {\n throw new Error(\"Timed out waiting for Chrome DevTools protocol port\");\n }\n const client = await CDP({port: port.result});\n const response = await client.Runtime.evaluate({ expression: \"JSON.stringify(process.versions)\" });\n dependencyVersions = JSON.parse(response.result.value);\n await client.close();\n } finally {\n proc.kill(\"SIGTERM\");\n const timeout = await maybe(withTimeout(procExit, 4 * 1000));\n if (!timeout.success) {\n console.log(`${version}: Stuck process ${proc.pid}, using SIGKILL`);\n proc.kill(\"SIGKILL\");\n }\n await procExit;\n await sleep(1000); // Need to wait a bit or sometimes the rm fails because something else is writing to it\n await fsAsync.rm(configDir, { recursive: true, force: true });\n }\n\n if (!dependencyVersions?.electron || !dependencyVersions?.chrome) {\n throw Error(`Failed to extract electron and chrome versions for ${version}`)\n }\n\n return {\n electronVersion: dependencyVersions.electron,\n chromeVersion: dependencyVersions.chrome,\n nodeVersion: dependencyVersions.node,\n };\n}\n\n/**\n * Add some corrections to the Obsidian version data.\n */\nexport function correctObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): Partial<ObsidianVersionInfo> {\n const corrections: Partial<ObsidianVersionInfo>[] = [\n // These version's downloads are missing or broken.\n {version: '0.12.16', downloads: { asar: undefined }},\n {version: '1.4.7', downloads: { asar: undefined }},\n {version: '1.4.8', downloads: { asar: undefined }},\n \n // The minInstallerVersion here is incorrect\n {version: \"1.3.4\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.3\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.2\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.1\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.0\", minInstallerVersion: \"0.14.5\"},\n ]\n\n const result = corrections.find(v => v.version == versionInfo.version) ?? {};\n // minInstallerVersion is incorrect, running Obsidian with installer older than 1.1.9 won't boot with errors like\n // `(node:11592) electron: Failed to load URL: app://obsidian.md/starter.html with error: ERR_BLOCKED_BY_CLIENT`\n if (semver.gte(versionInfo.version!, \"1.5.3\") && semver.lt(versionInfo.minInstallerVersion!, \"1.1.9\")) {\n result.minInstallerVersion = \"1.1.9\"\n }\n\n return result;\n}\n\n/**\n * Normalize order and remove undefined values.\n */\nexport function normalizeObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): ObsidianVersionInfo {\n versionInfo = {\n version: versionInfo.version,\n minInstallerVersion: versionInfo.minInstallerVersion,\n maxInstallerVersion: versionInfo.maxInstallerVersion,\n isBeta: versionInfo.isBeta,\n gitHubRelease: versionInfo.gitHubRelease,\n downloads: {\n asar: versionInfo.downloads?.asar,\n appImage: versionInfo.downloads?.appImage,\n appImageArm: versionInfo.downloads?.appImageArm,\n apk: versionInfo.downloads?.apk,\n dmg: versionInfo.downloads?.dmg,\n exe: versionInfo.downloads?.exe,\n },\n electronVersion: versionInfo.electronVersion,\n chromeVersion: versionInfo.chromeVersion,\n nodeVersion: versionInfo.nodeVersion,\n };\n versionInfo.downloads = _.omitBy(versionInfo.downloads, _.isUndefined);\n versionInfo = _.omitBy(versionInfo, _.isUndefined);\n return versionInfo as ObsidianVersionInfo;\n}\n"]}
package/dist/cli.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
3
 
4
- var _chunkUQI73JCRcjs = require('./chunk-UQI73JCR.cjs');
4
+ var _chunkFZIMIXL5cjs = require('./chunk-FZIMIXL5.cjs');
5
5
 
6
6
  // src/cli.ts
7
7
  var _commander = require('commander');
@@ -70,7 +70,7 @@ var themeOptionArgs = [
70
70
  ];
71
71
  var program = new (0, _commander.Command)("obsidian-launcher");
72
72
  program.command("download").description("Download Obsidian to the cache").option(...cacheOptionArgs).option(...versionOptionArgs).option(...installerOptionArgs).action(async (opts) => {
73
- const launcher = new (0, _chunkUQI73JCRcjs.ObsidianLauncher)({ cacheDir: opts.cache });
73
+ const launcher = new (0, _chunkFZIMIXL5cjs.ObsidianLauncher)({ cacheDir: opts.cache });
74
74
  const [appVersion, installerVersion] = await launcher.resolveVersions(opts.version, opts.installerVersion);
75
75
  const installerPath = await launcher.downloadInstaller(installerVersion);
76
76
  console.log(`Downloaded Obsidian installer to ${installerPath}`);
@@ -78,7 +78,7 @@ program.command("download").description("Download Obsidian to the cache").option
78
78
  console.log(`Downloaded Obsidian app to ${appPath}`);
79
79
  });
80
80
  program.command("install").description("Install plugins and themes into an Obsidian vault").argument("<vault>", "Vault to install into").option(...cacheOptionArgs).option(...pluginOptionArgs).option(...themeOptionArgs).action(async (vault, opts) => {
81
- const launcher = new (0, _chunkUQI73JCRcjs.ObsidianLauncher)({ cacheDir: opts.cache });
81
+ const launcher = new (0, _chunkFZIMIXL5cjs.ObsidianLauncher)({ cacheDir: opts.cache });
82
82
  await launcher.installPlugins(vault, parsePlugins(opts.plugin));
83
83
  await launcher.installThemes(vault, parseThemes(opts.theme));
84
84
  console.log(`Installed plugins and themes into ${vault}`);
@@ -86,7 +86,7 @@ program.command("install").description("Install plugins and themes into an Obsid
86
86
  program.command("launch").summary("Download and launch Obsidian").description(
87
87
  "Download and launch Obsidian, opening the specified vault. The Obsidian instance will have a sandboxed configuration directory. You can use this option to easily compare plugin behavior on different versions of Obsidian without messing with your system installation of Obsidian."
88
88
  ).argument("[vault]", "Vault to open").option(...cacheOptionArgs).option(...versionOptionArgs).option(...installerOptionArgs).option(...pluginOptionArgs).option(...themeOptionArgs).option("--copy", "Copy the vault first").action(async (vault, opts) => {
89
- const launcher = new (0, _chunkUQI73JCRcjs.ObsidianLauncher)({ cacheDir: opts.cache });
89
+ const launcher = new (0, _chunkFZIMIXL5cjs.ObsidianLauncher)({ cacheDir: opts.cache });
90
90
  const { proc } = await launcher.launch({
91
91
  appVersion: opts.version,
92
92
  installerVersion: opts.installerVersion,
@@ -105,7 +105,7 @@ program.command("launch").summary("Download and launch Obsidian").description(
105
105
  program.command("watch").summary("Launch Obsidian and watch for changes to plugins and themes").description(
106
106
  'Downloads Obsidian and opens a vault, then watches for changes to plugins and themes.\n\nTakes the same arguments as the "launch" command but watches for changes to any local plugins or themes and updates the the vault. Automatically installs "pjeby/hot-reload" so plugins will hot reload as they are updated.'
107
107
  ).argument("[vault]", "Vault to open").option(...cacheOptionArgs).option(...versionOptionArgs).option(...installerOptionArgs).option(...pluginOptionArgs).option(...themeOptionArgs).option("--copy", "Copy the vault first").action(async (vault, opts) => {
108
- const launcher = new (0, _chunkUQI73JCRcjs.ObsidianLauncher)({ cacheDir: opts.cache });
108
+ const launcher = new (0, _chunkFZIMIXL5cjs.ObsidianLauncher)({ cacheDir: opts.cache });
109
109
  const plugins = await launcher.downloadPlugins(parsePlugins(opts.plugin));
110
110
  const themes = await launcher.downloadThemes(parseThemes(opts.theme));
111
111
  const copy = _nullishCoalesce(opts.copy, () => ( false));
@@ -187,7 +187,7 @@ program.command("create-versions-list").summary("Collect Obsidian version inform
187
187
  if (isNaN(maxInstances)) {
188
188
  throw Error(`Invalid number ${opts.maxInstances}`);
189
189
  }
190
- const launcher = new (0, _chunkUQI73JCRcjs.ObsidianLauncher)({ cacheDir: opts.cache });
190
+ const launcher = new (0, _chunkFZIMIXL5cjs.ObsidianLauncher)({ cacheDir: opts.cache });
191
191
  versionInfos = await launcher.updateObsidianVersionInfos(versionInfos, { maxInstances });
192
192
  _promises2.default.writeFile(dest, JSON.stringify(versionInfos, void 0, 4));
193
193
  console.log(`Wrote updated version information to ${dest}`);
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ObsidianLauncher
4
- } from "./chunk-PEIKWKCF.js";
4
+ } from "./chunk-22ZW6ZDG.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkUQI73JCRcjs = require('./chunk-UQI73JCR.cjs');
3
+ var _chunkFZIMIXL5cjs = require('./chunk-FZIMIXL5.cjs');
4
4
 
5
5
  // src/index.ts
6
- var index_default = _chunkUQI73JCRcjs.ObsidianLauncher;
6
+ var index_default = _chunkFZIMIXL5cjs.ObsidianLauncher;
7
7
 
8
8
 
9
9
  exports.default = index_default;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ObsidianLauncher
3
- } from "./chunk-PEIKWKCF.js";
3
+ } from "./chunk-22ZW6ZDG.js";
4
4
 
5
5
  // src/index.ts
6
6
  var index_default = ObsidianLauncher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obsidian-launcher",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Download and launch sandboxed Obsidian instances",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/launcher.ts","../src/utils.ts","../src/apis.ts","../src/chromeLocalStorage.ts","../src/launcherUtils.ts"],"sourcesContent":["import fsAsync from \"fs/promises\"\nimport fs from \"fs\"\nimport zlib from \"zlib\"\nimport path from \"path\"\nimport crypto from \"crypto\";\nimport extractZip from \"extract-zip\"\nimport { pipeline } from \"stream/promises\";\nimport { downloadArtifact } from '@electron/get';\nimport child_process from \"child_process\"\nimport semver from \"semver\"\nimport { fileExists, makeTmpDir, withTmpDir, linkOrCp, maybe, pool, mergeKeepUndefined } from \"./utils.js\";\nimport {\n ObsidianVersionInfo, ObsidianCommunityPlugin, ObsidianCommunityTheme,\n PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry,\n ObsidianVersionInfos,\n} from \"./types.js\";\nimport { fetchObsidianAPI, fetchGitHubAPIPaginated, fetchWithFileUrl } from \"./apis.js\";\nimport ChromeLocalStorage from \"./chromeLocalStorage.js\";\nimport {\n normalizeGitHubRepo, extractObsidianAppImage, extractObsidianExe, extractObsidianDmg,\n parseObsidianDesktopRelease, parseObsidianGithubRelease, correctObsidianVersionInfo,\n getElectronVersionInfo, normalizeObsidianVersionInfo,\n} from \"./launcherUtils.js\";\nimport _ from \"lodash\"\n\n\n/**\n * The `ObsidianLauncher` class.\n * \n * Helper class that handles downloading and installing Obsidian versions, plugins, and themes and launching Obsidian\n * with sandboxed configuration directories.\n */\nexport class ObsidianLauncher {\n readonly cacheDir: string\n\n readonly versionsUrl: string\n readonly communityPluginsUrl: string\n readonly communityThemesUrl: string\n readonly cacheDuration: number\n\n /** Cached metadata files and requests */\n private metadataCache: Record<string, any>\n\n /**\n * Construct an ObsidianLauncher.\n * @param options.cacheDir Path to the cache directory. Defaults to \"OBSIDIAN_CACHE\" env var or \".obsidian-cache\".\n * @param options.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.\n * @param options.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.\n * @param options.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.\n * @param options.cacheDuration If the cached version list is older than this (in ms), refetch it. Defaults to 30 minutes.\n */\n constructor(options: {\n cacheDir?: string,\n versionsUrl?: string,\n communityPluginsUrl?: string,\n communityThemesUrl?: string,\n cacheDuration?: number,\n } = {}) {\n this.cacheDir = path.resolve(options.cacheDir ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n \n const defaultVersionsUrl = 'https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json'\n this.versionsUrl = options.versionsUrl ?? defaultVersionsUrl;\n \n const defaultCommunityPluginsUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\";\n this.communityPluginsUrl = options.communityPluginsUrl ?? defaultCommunityPluginsUrl;\n\n const defaultCommunityThemesUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\";\n this.communityThemesUrl = options.communityThemesUrl ?? defaultCommunityThemesUrl;\n\n this.cacheDuration = options.cacheDuration ?? (30 * 60 * 1000);\n\n this.metadataCache = {};\n }\n\n /**\n * Returns file content fetched from url as JSON. Caches content to dest and uses that cache if its more recent than\n * cacheDuration ms or if there are network errors.\n */\n private async cachedFetch(url: string, dest: string): Promise<any> {\n dest = path.resolve(dest);\n if (!(dest in this.metadataCache)) {\n let fileContent: string|undefined;\n const mtime = await fileExists(dest) ? (await fsAsync.stat(dest)).mtime : undefined;\n\n if (mtime && new Date().getTime() - mtime.getTime() < this.cacheDuration) { // read from cache if its recent\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else { // otherwise try to fetch the url\n const request = await maybe(fetchWithFileUrl(url));\n if (request.success) {\n await fsAsync.mkdir(path.dirname(dest), { recursive: true });\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.writeFile(path.join(tmpDir, 'download.json'), request.result);\n return path.join(tmpDir, 'download.json');\n })\n fileContent = request.result;\n } else if (await fileExists(dest)) { // use cache on network error\n console.warn(request.error)\n console.warn(`Unable to download ${dest}, using cached file.`);\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else {\n throw request.error;\n }\n }\n\n this.metadataCache[dest] = JSON.parse(fileContent);\n }\n return this.metadataCache[dest];\n }\n\n /**\n * Get parsed content of the current project's manifest.json\n */\n private async getRootManifest(): Promise<any> {\n if (!('manifest.json' in this.metadataCache)) {\n const root = path.parse(process.cwd()).root;\n let dir = process.cwd();\n while (dir != root && !(await fileExists(path.join(dir, 'manifest.json')))) {\n dir = path.dirname(dir);\n }\n const manifestPath = path.join(dir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n this.metadataCache['manifest.json'] = JSON.parse(await fsAsync.readFile(manifestPath, 'utf-8'));\n } else {\n this.metadataCache['manifest.json'] = null;\n }\n }\n return this.metadataCache['manifest.json'];\n }\n\n /**\n * Get information about all available Obsidian versions.\n */\n async getVersions(): Promise<ObsidianVersionInfo[]> {\n const dest = path.join(this.cacheDir, \"obsidian-versions.json\");\n return (await this.cachedFetch(this.versionsUrl, dest)).versions;\n }\n\n /**\n * Get information about all available community plugins.\n */\n async getCommunityPlugins(): Promise<ObsidianCommunityPlugin[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-plugins.json\");\n return await this.cachedFetch(this.communityPluginsUrl, dest);\n }\n\n /**\n * Get information about all available community themes.\n */\n async getCommunityThemes(): Promise<ObsidianCommunityTheme[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-css-themes.json\");\n return await this.cachedFetch(this.communityThemesUrl, dest);\n }\n\n /**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * @param appVersion Obsidian version string or one of \n * - \"latest\": Get the current latest non-beta Obsidian version\n * - \"latest-beta\": Get the current latest beta Obsidian version (or latest is there is no current beta)\n * - \"earliest\": Get the `minAppVersion` set in set in your `manifest.json`\n * @param installerVersion Obsidian version string or one of \n * - \"latest\": Get the latest Obsidian installer\n * - \"earliest\": Get the oldest Obsidian installer compatible with the specified Obsidian app version\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\n async resolveVersions(appVersion: string, installerVersion = \"latest\"): Promise<[string, string]> {\n const versions = await this.getVersions();\n const appVersionInfo = await this.getVersionInfo(appVersion);\n\n if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {\n throw Error(`No compatible installers available for app version ${appVersion}`);\n }\n if (installerVersion == \"latest\") {\n installerVersion = appVersionInfo.maxInstallerVersion;\n } else if (installerVersion == \"earliest\") {\n installerVersion = appVersionInfo.minInstallerVersion;\n } else {\n installerVersion = semver.valid(installerVersion) ?? installerVersion;\n }\n const installerVersionInfo = versions.find(v => v.version == installerVersion);\n if (!installerVersionInfo || !installerVersionInfo.chromeVersion) {\n throw Error(`No Obsidian installer for version ${installerVersion} found`);\n }\n if (\n semver.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) ||\n semver.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)\n ) {\n throw Error(\n `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer ` +\n `>=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ` +\n `${installerVersionInfo.version} specified`\n )\n }\n\n return [appVersionInfo.version, installerVersionInfo.version];\n }\n\n /**\n * Gets details about an Obsidian version.\n * @param appVersion Obsidian app version (see {@link resolveVersions} for format)\n */\n async getVersionInfo(appVersion: string): Promise<ObsidianVersionInfo> {\n const versions = await this.getVersions();\n if (appVersion == \"latest-beta\") {\n appVersion = versions.at(-1)!.version;\n } else if (appVersion == \"latest\") {\n appVersion = versions.filter(v => !v.isBeta).at(-1)!.version;\n } else if (appVersion == \"earliest\") {\n appVersion = (await this.getRootManifest())?.minAppVersion;\n if (!appVersion) {\n throw Error('Unable to resolve Obsidian app appVersion \"earliest\", no manifest.json or minAppVersion found.')\n }\n } else {\n // if invalid match won't be found and we'll throw error below\n appVersion = semver.valid(appVersion) ?? appVersion;\n }\n const versionInfo = versions.find(v => v.version == appVersion);\n if (!versionInfo) {\n throw Error(`No Obsidian app version ${appVersion} found`);\n }\n\n return versionInfo;\n }\n\n /**\n * Downloads the Obsidian installer for the given version and platform. Returns the file path.\n * @param installerVersion Obsidian installer version to download. (see {@link resolveVersions} for format)\n */\n async downloadInstaller(installerVersion: string): Promise<string> {\n const installerVersionInfo = await this.getVersionInfo(installerVersion);\n return await this.downloadInstallerFromVersionInfo(installerVersionInfo);\n }\n\n /**\n * Helper for downloadInstaller that doesn't require the obsidian-versions.json file so it can be used in\n * updateObsidianVersionInfos\n */\n private async downloadInstallerFromVersionInfo(versionInfo: ObsidianVersionInfo): Promise<string> {\n const installerVersion = versionInfo.version;\n const {platform, arch} = process;\n const cacheDir = path.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);\n \n let installerPath: string\n let downloader: ((tmpDir: string) => Promise<string>)|undefined\n \n if (platform == \"linux\") {\n installerPath = path.join(cacheDir, \"obsidian\");\n let installerUrl: string|undefined\n if (arch.startsWith(\"arm\")) {\n installerUrl = versionInfo.downloads.appImageArm;\n } else {\n installerUrl = versionInfo.downloads.appImage;\n }\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const appImage = path.join(tmpDir, \"Obsidian.AppImage\");\n await fsAsync.writeFile(appImage, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianAppImage(appImage, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"win32\") {\n installerPath = path.join(cacheDir, \"Obsidian.exe\")\n const installerUrl = versionInfo.downloads.exe;\n let appArch: string|undefined\n if (arch == \"x64\") {\n appArch = \"app-64\"\n } else if (arch == \"ia32\") {\n appArch = \"app-32\"\n } else if (arch.startsWith(\"arm\")) {\n appArch = \"app-arm64\"\n }\n if (installerUrl && appArch) {\n downloader = async (tmpDir) => {\n const installerExecutable = path.join(tmpDir, \"Obsidian.exe\");\n await fsAsync.writeFile(installerExecutable, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianExe(installerExecutable, appArch, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"darwin\") {\n installerPath = path.join(cacheDir, \"Contents/MacOS/Obsidian\");\n const installerUrl = versionInfo.downloads.dmg;\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const dmg = path.join(tmpDir, \"Obsidian.dmg\");\n await fsAsync.writeFile(dmg, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianDmg(dmg, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else {\n throw Error(`Unsupported platform ${platform}`);\n }\n if (!downloader) {\n throw Error(`No Obsidian installer download available for v${installerVersion} ${platform} ${arch}`);\n }\n\n if (!(await fileExists(installerPath))) {\n console.log(`Downloading Obsidian installer v${installerVersion}...`)\n await fsAsync.mkdir(path.dirname(cacheDir), { recursive: true });\n await withTmpDir(cacheDir, downloader);\n }\n\n return installerPath;\n }\n\n /**\n * Downloads the Obsidian asar for the given version and platform. Returns the file path.\n * \n * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`\n * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * \n * @param appVersion Obsidian version to download (see {@link resolveVersions} for format)\n */\n async downloadApp(appVersion: string): Promise<string> {\n const appVersionInfo = await this.getVersionInfo(appVersion);\n const appUrl = appVersionInfo.downloads.asar;\n if (!appUrl) {\n throw Error(`No asar found for Obsidian version ${appVersion}`);\n }\n const appPath = path.join(this.cacheDir, 'obsidian-app', `obsidian-${appVersionInfo.version}.asar`);\n\n if (!(await fileExists(appPath))) {\n console.log(`Downloading Obsidian app v${appVersion} ...`)\n await fsAsync.mkdir(path.dirname(appPath), { recursive: true });\n\n await withTmpDir(appPath, async (tmpDir) => {\n const isInsidersBuild = new URL(appUrl).hostname.endsWith('.obsidian.md');\n const response = isInsidersBuild ? await fetchObsidianAPI(appUrl) : await fetch(appUrl);\n const archive = path.join(tmpDir, 'app.asar.gz');\n const asar = path.join(tmpDir, 'app.asar')\n await fsAsync.writeFile(archive, response.body as any);\n await pipeline(fs.createReadStream(archive), zlib.createGunzip(), fs.createWriteStream(asar));\n return asar;\n })\n }\n\n return appPath;\n }\n\n /**\n * Downloads chromedriver for the given Obsidian version.\n * \n * wdio will download chromedriver from the Chrome for Testing API automatically (see\n * https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints). However, Google has only put\n * chromedriver since v115.0.5763.0 in that API, so wdio can't automatically download older versions of chromedriver\n * for old Electron versions. Here we download chromedriver for older versions ourselves using the @electron/get\n * package which fetches chromedriver from https://github.com/electron/electron/releases.\n * \n * @param installerVersion Obsidian installer version (see {@link resolveVersions} for format)\n */\n async downloadChromedriver(installerVersion: string): Promise<string> {\n const versionInfo = await this.getVersionInfo(installerVersion);\n const electronVersion = versionInfo.electronVersion;\n if (!electronVersion) {\n throw Error(`${installerVersion} is not an Obsidian installer version.`)\n }\n\n const chromedriverZipPath = await downloadArtifact({\n version: electronVersion,\n artifactName: 'chromedriver',\n cacheRoot: path.join(this.cacheDir, \"chromedriver-legacy\"),\n unsafelyDisableChecksums: true, // the checksums are slow and run even on cache hit.\n });\n\n let chromedriverPath: string\n if (process.platform == \"win32\") {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver.exe\");\n } else {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver\");\n }\n\n if (!(await fileExists(chromedriverPath))) {\n console.log(`Downloading legacy chromedriver for electron ${electronVersion} ...`)\n await withTmpDir(chromedriverPath, async (tmpDir) => {\n await extractZip(chromedriverZipPath, { dir: tmpDir });\n return path.join(tmpDir, path.basename(chromedriverPath));\n })\n }\n\n return chromedriverPath;\n }\n\n /** Gets the latest version of a plugin. */\n private async getLatestPluginVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-plugins\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a plugin from a GitHub repo to the cache.\n * @param repo Repo\n * @param version Version of the plugin to install or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadGitHubPlugin(repo: string, version = \"latest\"): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n if (version == \"latest\") {\n version = await this.getLatestPluginVersion(repo);\n }\n if (!semver.valid(version)) {\n throw Error(`Invalid version \"${version}\"`);\n }\n version = semver.valid(version)!;\n\n const pluginDir = path.join(this.cacheDir, \"obsidian-plugins\", repo, version);\n if (!(await fileExists(pluginDir))) {\n await fsAsync.mkdir(path.dirname(pluginDir), { recursive: true });\n await withTmpDir(pluginDir, async (tmpDir) => {\n const assetsToDownload = {'manifest.json': true, 'main.js': true, 'styles.css': false};\n await Promise.all(\n Object.entries(assetsToDownload).map(async ([file, required]) => {\n const url = `https://github.com/${repo}/releases/download/${version}/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else if (required) {\n throw Error(`No ${file} found for ${repo} version ${version}`)\n }\n })\n )\n return tmpDir;\n });\n }\n\n return pluginDir;\n }\n\n /**\n * Downloads a community plugin to the cache.\n * @param id Id of the plugin\n * @param version Version of the plugin to install, or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadCommunityPlugin(id: string, version = \"latest\"): Promise<string> {\n const communityPlugins = await this.getCommunityPlugins();\n const pluginInfo = communityPlugins.find(p => p.id == id);\n if (!pluginInfo) {\n throw Error(`No plugin with id ${id} found.`);\n }\n return await this.downloadGitHubPlugin(pluginInfo.repo, version);\n }\n\n /**\n * Downloads a list of plugins to the cache and returns a list of `DownloadedPluginEntry` with the downloaded paths.\n * Also adds the `id` property to the plugins based on the manifest.\n * \n * You can download plugins from GitHub using `{repo: \"org/repo\"}` and community plugins using `{id: 'plugin-id'}`.\n * Local plugins will just be passed through.\n * \n * @param plugins List of plugins to download.\n */\n async downloadPlugins(plugins: PluginEntry[]): Promise<DownloadedPluginEntry[]> {\n return await Promise.all(\n plugins.map(async (plugin) => {\n if (typeof plugin == \"object\" && \"originalType\" in plugin) {\n return {...plugin as DownloadedPluginEntry}\n }\n let pluginPath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof plugin == \"string\") {\n pluginPath = path.resolve(plugin);\n originalType = \"local\";\n } else if (\"path\" in plugin) {;\n pluginPath = path.resolve(plugin.path);\n originalType = \"local\";\n } else if (\"repo\" in plugin) {\n pluginPath = await this.downloadGitHubPlugin(plugin.repo, plugin.version);\n originalType = \"github\";\n } else if (\"id\" in plugin) {\n pluginPath = await this.downloadCommunityPlugin(plugin.id, plugin.version);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of plugin path, repo, or id\")\n }\n\n const manifestPath = path.join(pluginPath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No plugin found at ${pluginPath}`)\n }\n let pluginId = (typeof plugin == \"object\" && (\"id\" in plugin)) ? plugin.id : undefined;\n if (!pluginId) {\n pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${pluginPath}/manifest.json malformed.`);\n }\n }\n\n let enabled: boolean\n if (typeof plugin == \"string\") {\n enabled = true\n } else {\n enabled = plugin.enabled ?? true;\n }\n return {path: pluginPath, id: pluginId, enabled, originalType}\n })\n );\n }\n\n /** Gets the latest version of a theme. */\n private async getLatestThemeVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-themes\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a theme from a GitHub repo to the cache.\n * @param repo Repo\n * @returns path to the downloaded theme\n */\n private async downloadGitHubTheme(repo: string): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n // Obsidian theme's are just pulled from the repo HEAD, not releases, so we can't really choose a specific \n // version of a theme.\n // We use the manifest.json version to check if the theme has changed.\n const version = await this.getLatestThemeVersion(repo);\n const themeDir = path.join(this.cacheDir, \"obsidian-themes\", repo, version);\n\n if (!(await fileExists(themeDir))) {\n await fsAsync.mkdir(path.dirname(themeDir), { recursive: true });\n await withTmpDir(themeDir, async (tmpDir) => {\n const assetsToDownload = ['manifest.json', 'theme.css'];\n await Promise.all(\n assetsToDownload.map(async (file) => {\n const url = `https://raw.githubusercontent.com/${repo}/HEAD/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else {\n throw Error(`No ${file} found for ${repo}`);\n }\n })\n )\n return tmpDir;\n });\n }\n\n return themeDir;\n }\n\n /**\n * Downloads a community theme to the cache.\n * @param name name of the theme\n * @returns path to the downloaded theme\n */\n private async downloadCommunityTheme(name: string): Promise<string> {\n const communityThemes = await this.getCommunityThemes();\n const themeInfo = communityThemes.find(p => p.name == name);\n if (!themeInfo) {\n throw Error(`No theme with name ${name} found.`);\n }\n return await this.downloadGitHubTheme(themeInfo.repo);\n }\n\n /**\n * Downloads a list of themes to the cache and returns a list of `DownloadedThemeEntry` with the downloaded paths.\n * Also adds the `name` property to the plugins based on the manifest.\n * \n * You can download themes from GitHub using `{repo: \"org/repo\"}` and community themes using `{name: 'theme-name'}`.\n * Local themes will just be passed through.\n * \n * @param themes List of themes to download\n */\n async downloadThemes(themes: ThemeEntry[]): Promise<DownloadedThemeEntry[]> {\n return await Promise.all(\n themes.map(async (theme) => {\n if (typeof theme == \"object\" && \"originalType\" in theme) {\n return {...theme as DownloadedThemeEntry}\n }\n let themePath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof theme == \"string\") {\n themePath = path.resolve(theme);\n originalType = \"local\";\n } else if (\"path\" in theme) {;\n themePath = path.resolve(theme.path);\n originalType = \"local\";\n } else if (\"repo\" in theme) {\n themePath = await this.downloadGitHubTheme(theme.repo);\n originalType = \"github\";\n } else if (\"name\" in theme) {\n themePath = await this.downloadCommunityTheme(theme.name);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of theme path, repo, or name\")\n }\n\n const manifestPath = path.join(themePath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No theme found at ${themePath}`)\n }\n let themeName = (typeof theme == \"object\" && (\"name\" in theme)) ? theme.name : undefined;\n if (!themeName) {\n const manifestPath = path.join(themePath, \"manifest.json\");\n themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${themePath}/manifest.json malformed.`);\n }\n }\n\n let enabled: boolean\n if (typeof theme == \"string\") {\n enabled = true\n } else {\n enabled = theme.enabled ?? true;\n }\n return {path: themePath, name: themeName, enabled: enabled, originalType};\n })\n );\n }\n\n /**\n * Installs plugins into an Obsidian vault\n * @param vault Path to the vault to install the plugins in\n * @param plugins List plugins to install\n */\n async installPlugins(vault: string, plugins: PluginEntry[]) {\n const downloadedPlugins = await this.downloadPlugins(plugins);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n const enabledPluginsPath = path.join(obsidianDir, 'community-plugins.json');\n let originalEnabledPlugins: string[] = [];\n if (await fileExists(enabledPluginsPath)) {\n originalEnabledPlugins = JSON.parse(await fsAsync.readFile(enabledPluginsPath, 'utf-8'));\n }\n let enabledPlugins = [...originalEnabledPlugins];\n\n for (const {path: pluginPath, enabled = true, originalType} of downloadedPlugins) {\n const manifestPath = path.join(pluginPath, 'manifest.json');\n const pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n\n const pluginDest = path.join(obsidianDir, 'plugins', pluginId);\n await fsAsync.mkdir(pluginDest, { recursive: true });\n\n const files: Record<string, [boolean, boolean]> = {\n \"manifest.json\": [true, true],\n \"main.js\": [true, true],\n \"styles.css\": [false, true],\n \"data.json\": [false, false],\n }\n for (const [file, [required, deleteIfMissing]] of Object.entries(files)) {\n if (await fileExists(path.join(pluginPath, file))) {\n await linkOrCp(path.join(pluginPath, file), path.join(pluginDest, file));\n } else if (required) {\n throw Error(`${pluginPath}/${file} missing.`);\n } else if (deleteIfMissing) {\n await fsAsync.rm(path.join(pluginDest, file), {force: true});\n }\n }\n\n const pluginAlreadyListed = enabledPlugins.includes(pluginId);\n if (enabled && !pluginAlreadyListed) {\n enabledPlugins.push(pluginId)\n } else if (!enabled && pluginAlreadyListed) {\n enabledPlugins = enabledPlugins.filter(p => p != pluginId);\n }\n\n if (originalType == \"local\") {\n // Add a .hotreload file for the https://github.com/pjeby/hot-reload plugin\n await fsAsync.writeFile(path.join(pluginDest, '.hotreload'), '');\n }\n }\n\n if (!_.isEqual(enabledPlugins, originalEnabledPlugins)) {\n await fsAsync.writeFile(enabledPluginsPath, JSON.stringify(enabledPlugins, undefined, 2));\n }\n }\n\n /** \n * Installs themes into an Obsidian vault\n * @param vault Path to the theme to install the themes in\n * @param themes List of themes to install\n */\n async installThemes(vault: string, themes: ThemeEntry[]) {\n const downloadedThemes = await this.downloadThemes(themes);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n let enabledTheme: string|undefined = undefined;\n\n for (const {path: themePath, enabled = true} of downloadedThemes) {\n const manifestPath = path.join(themePath, 'manifest.json');\n const cssPath = path.join(themePath, 'theme.css');\n\n const themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n if (!(await fileExists(cssPath))) {\n throw Error(`${cssPath} missing.`);\n }\n\n const themeDest = path.join(obsidianDir, 'themes', themeName);\n await fsAsync.mkdir(themeDest, { recursive: true });\n\n await linkOrCp(manifestPath, path.join(themeDest, \"manifest.json\"));\n await linkOrCp(cssPath, path.join(themeDest, \"theme.css\"));\n\n if (enabledTheme && enabled) {\n throw Error(\"You can only have one enabled theme.\")\n } else if (enabled) {\n enabledTheme = themeName;\n }\n }\n\n if (themes.length > 0) { // Only update appearance.json if we set the themes\n const appearancePath = path.join(obsidianDir, 'appearance.json');\n let appearance: any = {}\n if (await fileExists(appearancePath)) {\n appearance = JSON.parse(await fsAsync.readFile(appearancePath, 'utf-8'));\n }\n appearance.cssTheme = enabledTheme ?? \"\";\n await fsAsync.writeFile(appearancePath, JSON.stringify(appearance, undefined, 2));\n }\n }\n\n /**\n * Sets up the config dir to use for the `--user-data-dir` in obsidian. Returns the path to the created config dir.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format)\n * @param params.installerVersion Obsidian version string.\n * @param params.appPath Path to the asar file to install. Will download if omitted.\n * @param params.vault Path to the vault to open in Obsidian.\n * @param params.dest Destination path for the config dir. If omitted it will create a temporary directory.\n */\n async setupConfigDir(params: {\n appVersion: string, installerVersion: string,\n appPath?: string,\n vault?: string,\n dest?: string,\n }): Promise<string> {\n const [appVersion, installerVersion] = await this.resolveVersions(params.appVersion, params.installerVersion);\n // configDir will be passed to --user-data-dir, so Obsidian is somewhat sandboxed. We set up \"obsidian.json\" so\n // that Obsidian opens the vault by default and doesn't check for updates.\n const configDir = params.dest ?? await makeTmpDir('obs-launcher-config-');\n \n let obsidianJson: any = {\n updateDisabled: true, // Prevents Obsidian trying to auto-update on boot.\n }\n let localStorageData: Record<string, string> = {\n \"most-recently-installed-version\": appVersion, // prevents the changelog page on boot\n }\n\n if (params.vault !== undefined) {\n if (!await fileExists(params.vault)) {\n throw Error(`Vault path ${params.vault} doesn't exist.`)\n }\n const vaultId = crypto.randomBytes(8).toString(\"hex\");\n obsidianJson = {\n ...obsidianJson,\n vaults: {\n [vaultId]: {\n path: path.resolve(params.vault),\n ts: new Date().getTime(),\n open: true,\n },\n },\n };\n localStorageData = {\n ...localStorageData,\n [`enable-plugin-${vaultId}`]: \"true\", // Disable \"safe mode\" and enable plugins\n }\n }\n\n await fsAsync.writeFile(path.join(configDir, 'obsidian.json'), JSON.stringify(obsidianJson));\n\n let appPath = params.appPath;\n if (!appPath) {\n appPath = await this.downloadApp(appVersion);\n }\n await linkOrCp(appPath, path.join(configDir, path.basename(appPath)));\n\n const localStorage = new ChromeLocalStorage(configDir);\n await localStorage.setItems(\"app://obsidian.md\", localStorageData)\n await localStorage.close();\n\n return configDir;\n }\n\n /**\n * Sets up a vault for Obsidian, installing plugins and themes and optionally copying the vault to a temporary\n * directory first.\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @returns Path to the copied vault (or just the path to the vault if copy is false)\n */\n async setupVault(params: {\n vault: string,\n copy?: boolean,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n }): Promise<string> {\n let vault = params.vault;\n if (params.copy) {\n const dest = await makeTmpDir('obs-launcher-vault-');\n await fsAsync.cp(vault, dest, { recursive: true, preserveTimestamps: true });\n vault = dest;\n }\n await this.installPlugins(vault, params.plugins ?? []);\n await this.installThemes(vault, params.themes ?? []);\n\n return vault;\n }\n\n /**\n * Downloads and launches Obsidian with a sandboxed config dir and a specifc vault open. Optionally install plugins\n * and themes first.\n * \n * This is just a shortcut for calling `downloadApp`, `downloadInstaller`, `setupVault` and `setupConfDir`.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format). Default \"latest\"\n * @param params.installerVersion Obsidian installer version (see {@link resolveVersions} for format).\n * Default \"latest\"\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @param params.args CLI args to pass to Obsidian\n * @param params.spawnOptions Options to pass to `spawn`\n * @returns The launched child process and the created tmpdirs\n */\n async launch(params: {\n appVersion?: string, installerVersion?: string,\n copy?: boolean,\n vault?: string,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n args?: string[],\n spawnOptions?: child_process.SpawnOptions,\n }): Promise<{proc: child_process.ChildProcess, configDir: string, vault?: string}> {\n const [appVersion, installerVersion] = await this.resolveVersions(\n params.appVersion ?? \"latest\",\n params.installerVersion ?? \"latest\",\n );\n const appPath = await this.downloadApp(appVersion);\n const installerPath = await this.downloadInstaller(installerVersion);\n\n let vault = params.vault;\n if (vault) {\n vault = await this.setupVault({\n vault,\n copy: params.copy ?? false,\n plugins: params.plugins, themes: params.themes,\n })\n }\n\n const configDir = await this.setupConfigDir({ appVersion, installerVersion, appPath, vault });\n\n // Spawn child.\n const proc = child_process.spawn(installerPath, [\n `--user-data-dir=${configDir}`,\n ...(params.args ?? []),\n ], {\n ...params.spawnOptions,\n });\n\n return {proc, configDir, vault};\n }\n\n /** \n * Updates the info obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands\n * and in wdio-obsidian-service to get metadata about Obsidian versions in one place such as minInstallerVersion and\n * the internal electron version.\n */\n async updateObsidianVersionInfos(\n original?: ObsidianVersionInfos, { maxInstances = 1 } = {},\n ): Promise<ObsidianVersionInfos> {\n const repo = 'obsidianmd/obsidian-releases';\n\n let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {\n path: \"desktop-releases.json\",\n since: original?.metadata.commit_date,\n });\n commitHistory.reverse();\n if (original) {\n commitHistory = _.takeRightWhile(commitHistory, c => c.sha != original.metadata.commit_sha);\n }\n \n const fileHistory: any[] = await pool(8, commitHistory, commit =>\n fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then(r => r.json())\n );\n \n const githubReleases = await fetchGitHubAPIPaginated(`repos/${repo}/releases`);\n \n const versionMap: _.Dictionary<Partial<ObsidianVersionInfo>> = _.keyBy(\n original?.versions ?? [],\n v => v.version,\n );\n \n for (const {beta, ...current} of fileHistory) {\n if (beta && (!versionMap[beta.latestVersion] || versionMap[beta.latestVersion].isBeta)) {\n versionMap[beta.latestVersion] = _.merge({}, versionMap[beta.latestVersion],\n parseObsidianDesktopRelease(beta, true),\n );\n }\n versionMap[current.latestVersion] = _.merge({}, versionMap[current.latestVersion],\n parseObsidianDesktopRelease(current, false),\n )\n }\n \n for (const release of githubReleases) {\n if (versionMap.hasOwnProperty(release.name)) {\n versionMap[release.name] = _.merge({}, versionMap[release.name], parseObsidianGithubRelease(release));\n }\n }\n \n const dependencyVersions = await pool(maxInstances,\n Object.values(versionMap).filter(v => v.downloads?.appImage && !v.chromeVersion),\n async (v) => {\n const binaryPath = await this.downloadInstallerFromVersionInfo(v as ObsidianVersionInfo);\n const electronVersionInfo = await getElectronVersionInfo(v.version!, binaryPath);\n return {...v, ...electronVersionInfo};\n },\n )\n for (const deps of dependencyVersions) {\n versionMap[deps.version!] = _.merge({}, versionMap[deps.version!], deps);\n }\n \n // populate minInstallerVersion and maxInstallerVersion and add corrections\n let minInstallerVersion: string|undefined = undefined;\n let maxInstallerVersion: string|undefined = undefined;\n for (const version of Object.keys(versionMap).sort(semver.compare)) {\n // older versions have 0.0.0 as there min version, which doesn't exist anywhere we can download.\n // we'll set those to the first available installer version.\n if (!minInstallerVersion && versionMap[version].chromeVersion) {\n minInstallerVersion = version;\n }\n if (versionMap[version].downloads!.appImage) {\n maxInstallerVersion = version;\n }\n versionMap[version] = mergeKeepUndefined({}, versionMap[version],\n {\n minInstallerVersion: versionMap[version].minInstallerVersion ?? minInstallerVersion,\n maxInstallerVersion: maxInstallerVersion,\n },\n correctObsidianVersionInfo(versionMap[version]),\n );\n }\n \n const result: ObsidianVersionInfos = {\n metadata: {\n commit_date: commitHistory.at(-1)?.commit.committer.date ?? original?.metadata.commit_date,\n commit_sha: commitHistory.at(-1)?.sha ?? original?.metadata.commit_sha,\n timestamp: original?.metadata.timestamp ?? \"\", // set down below\n },\n versions: Object.values(versionMap)\n .map(normalizeObsidianVersionInfo)\n .sort((a, b) => semver.compare(a.version, b.version)),\n }\n\n // Update timestamp if anything has changed. Also, GitHub will cancel scheduled workflows if the repository is\n // \"inactive\" for 60 days. So we'll update the timestamp every once in a while even if there are no Obsidian\n // updates to make sure there's commit activity in the repo.\n const dayMs = 24 * 60 * 60 * 1000;\n const timeSinceLastUpdate = new Date().getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();\n if (!_.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {\n result.metadata.timestamp = new Date().toISOString();\n }\n\n return result;\n }\n\n /**\n * Returns true if the Obsidian version is already in the cache.\n * @param type on of \"app\" or \"installer\"\n * @param version Obsidian app/installer version (see {@link resolveVersions} for format)\n */\n async isInCache(type: \"app\"|\"installer\", version: string) {\n version = (await this.getVersionInfo(version)).version;\n\n let dest: string\n if (type == \"app\") {\n dest = `obsidian-app/obsidian-${version}.asar`;\n } else { // type == \"installer\"\n const {platform, arch} = process;\n dest =`obsidian-installer/${platform}-${arch}/Obsidian-${version}`;\n }\n\n return (await fileExists(path.join(this.cacheDir, dest)));\n }\n\n /**\n * Returns true if we either have the credentials to download the version or it's already in cache.\n * This is only relevant for Obsidian beta versions, as they require Obsidian insider credentials to download.\n * @param version Obsidian version (see {@link resolveVersions} for format)\n */\n async isAvailable(version: string): Promise<boolean> {\n const versionInfo = await this.getVersionInfo(version);\n const versionExists = !!(versionInfo.downloads.asar && versionInfo.minInstallerVersion)\n\n if (versionInfo.isBeta) {\n const hasCreds = !!(process.env['OBSIDIAN_USERNAME'] && process.env['OBSIDIAN_PASSWORD']);\n const inCache = await this.isInCache('app', versionInfo.version);\n return versionExists && (hasCreds || inCache);\n } else {\n return versionExists;\n }\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport os from \"os\"\nimport { PromisePool } from '@supercharge/promise-pool'\nimport _ from \"lodash\"\n\n/// Files ///\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create tmpdir under the system temporary directory.\n * @param prefix \n * @returns \n */\nexport async function makeTmpDir(prefix?: string) {\n return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? 'tmp-'));\n}\n\n/**\n * Handles creating a file \"atomically\" by creating a tmpDir, then downloading or otherwise creating the file under it,\n * then renaming it to the final location when done.\n * @param dest Path the file should end up at.\n * @param func Function takes path to a temporary directory it can use as scratch space. The path it returns will be\n * moved to `dest`. If no path is returned, it will move the whole tmpDir to dest.\n */\nexport async function withTmpDir(dest: string, func: (tmpDir: string) => Promise<string|void>): Promise<void> {\n dest = path.resolve(dest);\n const tmpDir = await fsAsync.mkdtemp(path.join(path.dirname(dest), `.${path.basename(dest)}.tmp.`));\n try {\n let result = await func(tmpDir) ?? tmpDir;\n if (!path.isAbsolute(result)) {\n result = path.join(tmpDir, result);\n } else if (!path.resolve(result).startsWith(tmpDir)) {\n throw new Error(`Returned path ${result} not under tmpDir`)\n }\n // rename will overwrite files but not directories\n if (await fileExists(dest) && (await fsAsync.stat(dest)).isDirectory()) {\n await fsAsync.rename(dest, tmpDir + \".old\")\n }\n \n await fsAsync.rename(result, dest);\n await fsAsync.rm(tmpDir + \".old\", { recursive: true, force: true });\n } finally {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Tries to hardlink a file, falls back to copy if it fails\n */\nexport async function linkOrCp(src: string, dest: string) {\n try {\n await fsAsync.link(src, dest);\n } catch {\n await fsAsync.copyFile(src, dest);\n }\n}\n\n\n/// Promises ///\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Await a promise or reject if it takes longer than timeout.\n */\nexport async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n let timer: NodeJS.Timeout;\n const result = Promise.race([\n promise,\n new Promise<T>((resolve, reject) => timer = setTimeout(() => reject(Error(\"Promise timed out\")), timeout))\n ])\n return result.finally(() => clearTimeout(timer));\n}\n\n/**\n * Wrapper around PromisePool that throws on any error.\n */\nexport async function pool<T, U>(size: number, items: T[], func: (item: T) => Promise<U>): Promise<U[]> {\n const { results } = await PromisePool\n .for(items)\n .withConcurrency(size)\n .handleError(async (error) => { throw error; })\n .useCorrespondingResults()\n .process(func);\n return results as U[];\n}\n\nexport type SuccessResult<T> = {success: true, result: T, error: undefined};\nexport type ErrorResult = {success: false, result: undefined, error: any};\nexport type Maybe<T> = SuccessResult<T>|ErrorResult;\n\n/**\n * Helper for handling asynchronous errors with less hassle.\n */\nexport async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {\n return promise\n .then(r => ({success: true, result: r, error: undefined} as const))\n .catch(e => ({success: false, result: undefined, error: e} as const));\n}\n\n\n/**\n * Lodash _.merge but overwrites values with undefined if present, instead of ignoring undefined.\n */\nexport function mergeKeepUndefined(object: any, ...sources: any[]) {\n return _.mergeWith(object, ...sources,\n (objValue: any, srcValue: any, key: any, obj: any) => {\n if (_.isPlainObject(obj) && objValue !== srcValue && srcValue === undefined) {\n obj[key] = srcValue\n }\n }\n );\n}","import _ from \"lodash\"\nimport fsAsync from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\n\n\n/**\n * GitHub API stores pagination information in the \"Link\" header. The header looks like this:\n * ```\n * <https://api.github.com/repositories/1300192/issues?page=2>; rel=\"prev\", <https://api.github.com/repositories/1300192/issues?page=4>; rel=\"next\"\n * ```\n */\nexport function parseLinkHeader(linkHeader: string): Record<string, Record<string, string>> {\n function parseLinkData(linkData: string) {\n return Object.fromEntries(\n linkData.split(\";\").flatMap(x => {\n const partMatch = x.trim().match(/^([^=]+?)\\s*=\\s*\"?([^\"]+)\"?$/);\n return partMatch ? [[partMatch[1], partMatch[2]]] : [];\n })\n )\n }\n\n const linkDatas = linkHeader\n .split(/,\\s*(?=<)/)\n .flatMap(link => {\n const linkMatch = link.trim().match(/^<([^>]*)>(.*)$/);\n if (linkMatch) {\n return [{\n url: linkMatch[1],\n ...parseLinkData(linkMatch[2]),\n } as Record<string, string>];\n } else {\n return [];\n }\n })\n .filter(l => l.rel)\n return Object.fromEntries(linkDatas.map(l => [l.rel, l]));\n};\n\n\nfunction createURL(url: string, base: string, params: Record<string, any> = {}) {\n params =_.pickBy(params, x => x !== undefined);\n const urlObj = new URL(url, base);\n const searchParams = new URLSearchParams({...Object.fromEntries(urlObj.searchParams), ...params});\n if ([...searchParams].length > 0) {\n urlObj.search = '?' + searchParams;\n }\n return urlObj.toString();\n}\n\n\n/**\n * Fetch from the GitHub API. Uses GITHUB_TOKEN if available. You can access the API without a token, but will hit\n * the usage caps very quickly.\n */\nexport async function fetchGitHubAPI(url: string, params: Record<string, any> = {}) {\n url = createURL(url, \"https://api.github.com\", params)\n const token = process.env.GITHUB_TOKEN;\n const headers: Record<string, string> = token ? {Authorization: \"Bearer \" + token} : {};\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`GitHub API error: ${await response.text()}`);\n }\n return await response;\n}\n\n\n/**\n * Fetch all data from a paginated GitHub API request.\n */\nexport async function fetchGitHubAPIPaginated(url: string, params: Record<string, any> = {}) {\n const results = [];\n let next: string|undefined = createURL(url, \"https://api.github.com\", { per_page: 100, ...params });\n while (next) {\n const response = await fetchGitHubAPI(next);\n results.push(...await response.json() as any);\n next = parseLinkHeader(response.headers.get('link') ?? '').next?.url;\n }\n return results;\n}\n\n/**\n * Fetch from the Obsidian API to download insider versions. Uses OBSIDIAN_USERNAME and\n * OBSIDIAN_PASSWORD environment variables.\n */\nexport async function fetchObsidianAPI(url: string) {\n url = createURL(url, \"https://releases.obsidian.md\");\n\n const username = process.env.OBSIDIAN_USERNAME;\n const password = process.env.OBSIDIAN_PASSWORD;\n if (!username || !password) {\n throw Error(\"OBSIDIAN_USERNAME or OBSIDIAN_PASSWORD environment variables are required to access the Obsidian API for beta versions.\")\n }\n\n const response = await fetch(url, {\n headers: {\n // For some reason you have to set the User-Agent or it won't let you download\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',\n \"Origin\": \"app://obsidian.md\",\n 'Authorization': 'Basic ' + btoa(username + \":\" + password),\n },\n })\n return response;\n}\n\n\n/**\n * Fetches a URL returning its content as a string. Throws if response is not OK.\n * URL can be a file url.\n */\nexport async function fetchWithFileUrl(url: string) {\n if (url.startsWith(\"file:\")) {\n return await fsAsync.readFile(fileURLToPath(url), 'utf-8');\n } else {\n const response = await fetch(url);\n if (response.ok) {\n return response.text()\n } else {\n throw Error(`Request failed with ${response.status}: ${response.text()}`)\n }\n }\n}\n","import path from \"path\"\nimport { ClassicLevel } from \"classic-level\"\n\n\n/**\n * Class to directly manipulate chrome/electron local storage.\n *\n * Normally you'd just manipulate `localStorage` directly during the webdriver tests. However, there's not a built in\n * way to set up localStorage values *before* the app boots. We need to set `enable-plugins` and some other keys for\n * Obsidian to read during the boot process. This class lets us setup the local storage before launching Obsidian.\n */\nexport default class ChromeLocalStorage {\n private db: ClassicLevel;\n\n /** Pass the path to the user data dir for Chrome/Electron. If it doesn't exist it will be created. */\n constructor(public readonly userDataDir: string) {\n this.db = new ClassicLevel(path.join(userDataDir, 'Local Storage/leveldb/'));\n }\n\n private encodeKey = (domain: string, key: string) => `_${domain}\\u0000\\u0001${key}`;\n private decodeKey = (key: string) => key.slice(1).split(\"\\u0000\\u0001\") as [string, string];\n private encodeValue = (value: string) => `\\u0001${value}`;\n private decodeValue = (value: string) => value.slice(1);\n\n /**\n * Get a value from localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to retreive\n */\n async getItem(domain: string, key: string): Promise<string|null> {\n const value = await this.db.get(this.encodeKey(domain, key));\n return (value === undefined) ? null : this.decodeValue(value);\n }\n\n /**\n * Set a value in localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to set\n * @param value Value to set\n */\n async setItem(domain: string, key: string, value: string) {\n await this.db.put(this.encodeKey(domain, key), this.encodeValue(value))\n }\n\n /**\n * Removes a key from localStorage\n * @param domain Domain the values is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key key to remove.\n */\n async removeItem(domain: string, key: string) {\n await this.db.del(this.encodeKey(domain, key))\n }\n\n /** Get all items in localStorage as [domain, key, value] tuples */\n async getAllItems(): Promise<[string, string, string][]> {\n const result: [string, string, string][] = []\n for await (const pair of this.db.iterator()) {\n if (pair[0].startsWith(\"_\")) { // ignore the META values\n const [domain, key] = this.decodeKey(pair[0]);\n const value = this.decodeValue(pair[1]);\n result.push([domain, key, value]);\n }\n }\n return result;\n }\n\n /**\n * Write multiple values to localStorage in batch\n * @param domain Domain the values are under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param data key/value mapping to write\n */\n async setItems(domain: string, data: Record<string, string>) {\n await this.db.batch(\n Object.entries(data).map(([key, value]) => ({\n type: \"put\",\n key: this.encodeKey(domain, key),\n value: this.encodeValue(value),\n }))\n )\n }\n\n /**\n * Close the localStorage database.\n */\n async close() {\n await this.db.close();\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { promisify } from \"util\";\nimport child_process from \"child_process\"\nimport which from \"which\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\nimport CDP from 'chrome-remote-interface'\nimport { makeTmpDir, withTmpDir, maybe, withTimeout, sleep } from \"./utils.js\";\nimport { ObsidianVersionInfo } from \"./types.js\";\nconst execFile = promisify(child_process.execFile);\n\n\nexport function normalizeGitHubRepo(repo: string) {\n return repo.replace(/^(https?:\\/\\/)?(github.com\\/)/, '')\n}\n\n/**\n * Running AppImage requires libfuse2, extracting the AppImage first avoids that.\n */\nexport async function extractObsidianAppImage(appImage: string, dest: string) {\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.chmod(appImage, 0o755);\n await execFile(appImage, [\"--appimage-extract\"], {cwd: tmpDir});\n return path.join(tmpDir, 'squashfs-root');\n })\n}\n\n/**\n * Obsidian appears to use NSIS to bundle their Window's installers. We want to extract the executable\n * files directly without running the installer. 7zip can extract the raw files from the exe.\n */\nexport async function extractObsidianExe(exe: string, appArch: string, dest: string) {\n const path7z = await which(\"7z\", { nothrow: true });\n if (!path7z) {\n throw new Error(\n \"Downloading Obsidian for Windows requires 7zip to be installed and available on the PATH. \" +\n \"You install it from https://www.7-zip.org and then add the install location to the PATH.\"\n );\n }\n exe = path.resolve(exe);\n // The installer contains several `.7z` files with files for different architectures \n const subArchive = path.join('$PLUGINSDIR', appArch + \".7z\");\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const extractedInstaller = path.join(tmpDir, \"installer\");\n await execFile(path7z, [\"x\", \"-o\" + extractedInstaller, exe, subArchive]);\n const extractedObsidian = path.join(tmpDir, \"obsidian\");\n await execFile(path7z, [\"x\", \"-o\" + extractedObsidian, path.join(extractedInstaller, subArchive)]);\n return extractedObsidian;\n })\n}\n\n/**\n * Extract the executable from the Obsidian dmg installer.\n */\nexport async function extractObsidianDmg(dmg: string, dest: string) {\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const proc = await execFile('hdiutil', ['attach', '-nobrowse', '-readonly', dmg]);\n const volume = proc.stdout.match(/\\/Volumes\\/.*$/m)![0];\n const obsidianApp = path.join(volume, \"Obsidian.app\");\n try {\n await fsAsync.cp(obsidianApp, tmpDir, {recursive: true, verbatimSymlinks: true});\n } finally {\n await execFile('hdiutil', ['detach', volume]);\n }\n return tmpDir;\n })\n}\n\n\nexport function parseObsidianDesktopRelease(fileRelease: any, isBeta: boolean): Partial<ObsidianVersionInfo> {\n return {\n version: fileRelease.latestVersion,\n minInstallerVersion: fileRelease.minimumVersion != '0.0.0' ? fileRelease.minimumVersion : undefined,\n isBeta: isBeta,\n downloads: {\n asar: fileRelease.downloadUrl,\n },\n };\n}\n\nexport function parseObsidianGithubRelease(gitHubRelease: any): Partial<ObsidianVersionInfo> {\n const version = gitHubRelease.name;\n const assets: string[] = gitHubRelease.assets.map((a: any) => a.browser_download_url);\n const downloads = {\n appImage: assets.find(u => u.match(`${version}.AppImage$`)),\n appImageArm: assets.find(u => u.match(`${version}-arm64.AppImage$`)),\n apk: assets.find(u => u.match(`${version}.apk$`)),\n asar: assets.find(u => u.match(`${version}.asar.gz$`)),\n dmg: assets.find(u => u.match(`${version}(-universal)?.dmg$`)),\n exe: assets.find(u => u.match(`${version}.exe$`)),\n }\n\n return {\n version: version,\n gitHubRelease: gitHubRelease.html_url,\n downloads: downloads,\n }\n}\n\n/**\n * Extract electron and chrome versions for an Obsidian version.\n */\nexport async function getElectronVersionInfo(\n version:string, binaryPath: string,\n): Promise<Partial<ObsidianVersionInfo>> {\n console.log(`${version}: Retrieving electron & chrome versions...`);\n\n const configDir = await makeTmpDir('fetch-obsidian-versions-');\n\n const proc = child_process.spawn(binaryPath, [\n `--remote-debugging-port=0`, // 0 will make it choose a random available port\n '--test-type=webdriver',\n `--user-data-dir=${configDir}`,\n '--no-sandbox', // Workaround for SUID issue, see https://github.com/electron/electron/issues/42510\n ]);\n const procExit = new Promise<number>((resolve) => proc.on('exit', (code) => resolve(code ?? -1)));\n // proc.stdout.on('data', data => console.log(`stdout: ${data}`));\n // proc.stderr.on('data', data => console.log(`stderr: ${data}`));\n\n let dependencyVersions: any;\n try {\n // Wait for the logs showing that Obsidian is ready, and pull the chosen DevTool Protocol port from it\n const portPromise = new Promise<number>((resolve, reject) => {\n procExit.then(() => reject(\"Processed ended without opening a port\"))\n proc.stderr.on('data', data => {\n const port = data.toString().match(/ws:\\/\\/[\\w.]+?:(\\d+)/)?.[1];\n if (port) {\n resolve(Number(port));\n }\n });\n })\n\n const port = await maybe(withTimeout(portPromise, 10 * 1000));\n if (!port.success) {\n throw new Error(\"Timed out waiting for Chrome DevTools protocol port\");\n }\n const client = await CDP({port: port.result});\n const response = await client.Runtime.evaluate({ expression: \"JSON.stringify(process.versions)\" });\n dependencyVersions = JSON.parse(response.result.value);\n await client.close();\n } finally {\n proc.kill(\"SIGTERM\");\n const timeout = await maybe(withTimeout(procExit, 4 * 1000));\n if (!timeout.success) {\n console.log(`${version}: Stuck process ${proc.pid}, using SIGKILL`);\n proc.kill(\"SIGKILL\");\n }\n await procExit;\n await sleep(1000); // Need to wait a bit or sometimes the rm fails because something else is writing to it\n await fsAsync.rm(configDir, { recursive: true, force: true });\n }\n\n if (!dependencyVersions?.electron || !dependencyVersions?.chrome) {\n throw Error(`Failed to extract electron and chrome versions for ${version}`)\n }\n\n return {\n electronVersion: dependencyVersions.electron,\n chromeVersion: dependencyVersions.chrome,\n nodeVersion: dependencyVersions.node,\n };\n}\n\n/**\n * Add some corrections to the Obsidian version data.\n */\nexport function correctObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): Partial<ObsidianVersionInfo> {\n const corrections: Partial<ObsidianVersionInfo>[] = [\n // These version's downloads are missing or broken.\n {version: '0.12.16', downloads: { asar: undefined }},\n {version: '1.4.7', downloads: { asar: undefined }},\n {version: '1.4.8', downloads: { asar: undefined }},\n \n // The minInstallerVersion here is incorrect\n {version: \"1.3.4\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.3\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.2\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.1\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.0\", minInstallerVersion: \"0.14.5\"},\n ]\n\n const result = corrections.find(v => v.version == versionInfo.version) ?? {};\n // minInstallerVersion is incorrect, running Obsidian with installer older than 1.1.9 won't boot with errors like\n // `(node:11592) electron: Failed to load URL: app://obsidian.md/starter.html with error: ERR_BLOCKED_BY_CLIENT`\n if (semver.gte(versionInfo.version!, \"1.5.3\") && semver.lt(versionInfo.minInstallerVersion!, \"1.1.9\")) {\n result.minInstallerVersion = \"1.1.9\"\n }\n\n return result;\n}\n\n/**\n * Normalize order and remove undefined values.\n */\nexport function normalizeObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): ObsidianVersionInfo {\n versionInfo = {\n version: versionInfo.version,\n minInstallerVersion: versionInfo.minInstallerVersion,\n maxInstallerVersion: versionInfo.maxInstallerVersion,\n isBeta: versionInfo.isBeta,\n gitHubRelease: versionInfo.gitHubRelease,\n downloads: {\n asar: versionInfo.downloads?.asar,\n appImage: versionInfo.downloads?.appImage,\n appImageArm: versionInfo.downloads?.appImageArm,\n apk: versionInfo.downloads?.apk,\n dmg: versionInfo.downloads?.dmg,\n exe: versionInfo.downloads?.exe,\n },\n electronVersion: versionInfo.electronVersion,\n chromeVersion: versionInfo.chromeVersion,\n nodeVersion: versionInfo.nodeVersion,\n };\n versionInfo.downloads = _.omitBy(versionInfo.downloads, _.isUndefined);\n versionInfo = _.omitBy(versionInfo, _.isUndefined);\n return versionInfo as ObsidianVersionInfo;\n}\n"],"mappings":"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","names":["fsAsync","path","child_process","semver","path","_","fsAsync","_","fsAsync","path","fsAsync","path","_","fsAsync","path","port","_","_","path","fsAsync","semver","manifestPath","child_process"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/wdio-obsidian-service/wdio-obsidian-service/packages/obsidian-launcher/dist/chunk-UQI73JCR.cjs","../src/launcher.ts","../src/utils.ts","../src/apis.ts","../src/chromeLocalStorage.ts","../src/launcherUtils.ts"],"names":["path","_","fsAsync","manifestPath"],"mappings":"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","file":"/home/runner/work/wdio-obsidian-service/wdio-obsidian-service/packages/obsidian-launcher/dist/chunk-UQI73JCR.cjs","sourcesContent":[null,"import fsAsync from \"fs/promises\"\nimport fs from \"fs\"\nimport zlib from \"zlib\"\nimport path from \"path\"\nimport crypto from \"crypto\";\nimport extractZip from \"extract-zip\"\nimport { pipeline } from \"stream/promises\";\nimport { downloadArtifact } from '@electron/get';\nimport child_process from \"child_process\"\nimport semver from \"semver\"\nimport { fileExists, makeTmpDir, withTmpDir, linkOrCp, maybe, pool, mergeKeepUndefined } from \"./utils.js\";\nimport {\n ObsidianVersionInfo, ObsidianCommunityPlugin, ObsidianCommunityTheme,\n PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry,\n ObsidianVersionInfos,\n} from \"./types.js\";\nimport { fetchObsidianAPI, fetchGitHubAPIPaginated, fetchWithFileUrl } from \"./apis.js\";\nimport ChromeLocalStorage from \"./chromeLocalStorage.js\";\nimport {\n normalizeGitHubRepo, extractObsidianAppImage, extractObsidianExe, extractObsidianDmg,\n parseObsidianDesktopRelease, parseObsidianGithubRelease, correctObsidianVersionInfo,\n getElectronVersionInfo, normalizeObsidianVersionInfo,\n} from \"./launcherUtils.js\";\nimport _ from \"lodash\"\n\n\n/**\n * The `ObsidianLauncher` class.\n * \n * Helper class that handles downloading and installing Obsidian versions, plugins, and themes and launching Obsidian\n * with sandboxed configuration directories.\n */\nexport class ObsidianLauncher {\n readonly cacheDir: string\n\n readonly versionsUrl: string\n readonly communityPluginsUrl: string\n readonly communityThemesUrl: string\n readonly cacheDuration: number\n\n /** Cached metadata files and requests */\n private metadataCache: Record<string, any>\n\n /**\n * Construct an ObsidianLauncher.\n * @param options.cacheDir Path to the cache directory. Defaults to \"OBSIDIAN_CACHE\" env var or \".obsidian-cache\".\n * @param options.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.\n * @param options.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.\n * @param options.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.\n * @param options.cacheDuration If the cached version list is older than this (in ms), refetch it. Defaults to 30 minutes.\n */\n constructor(options: {\n cacheDir?: string,\n versionsUrl?: string,\n communityPluginsUrl?: string,\n communityThemesUrl?: string,\n cacheDuration?: number,\n } = {}) {\n this.cacheDir = path.resolve(options.cacheDir ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n \n const defaultVersionsUrl = 'https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json'\n this.versionsUrl = options.versionsUrl ?? defaultVersionsUrl;\n \n const defaultCommunityPluginsUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\";\n this.communityPluginsUrl = options.communityPluginsUrl ?? defaultCommunityPluginsUrl;\n\n const defaultCommunityThemesUrl = \"https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\";\n this.communityThemesUrl = options.communityThemesUrl ?? defaultCommunityThemesUrl;\n\n this.cacheDuration = options.cacheDuration ?? (30 * 60 * 1000);\n\n this.metadataCache = {};\n }\n\n /**\n * Returns file content fetched from url as JSON. Caches content to dest and uses that cache if its more recent than\n * cacheDuration ms or if there are network errors.\n */\n private async cachedFetch(url: string, dest: string): Promise<any> {\n dest = path.resolve(dest);\n if (!(dest in this.metadataCache)) {\n let fileContent: string|undefined;\n const mtime = await fileExists(dest) ? (await fsAsync.stat(dest)).mtime : undefined;\n\n if (mtime && new Date().getTime() - mtime.getTime() < this.cacheDuration) { // read from cache if its recent\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else { // otherwise try to fetch the url\n const request = await maybe(fetchWithFileUrl(url));\n if (request.success) {\n await fsAsync.mkdir(path.dirname(dest), { recursive: true });\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.writeFile(path.join(tmpDir, 'download.json'), request.result);\n return path.join(tmpDir, 'download.json');\n })\n fileContent = request.result;\n } else if (await fileExists(dest)) { // use cache on network error\n console.warn(request.error)\n console.warn(`Unable to download ${dest}, using cached file.`);\n fileContent = await fsAsync.readFile(dest, 'utf-8');\n } else {\n throw request.error;\n }\n }\n\n this.metadataCache[dest] = JSON.parse(fileContent);\n }\n return this.metadataCache[dest];\n }\n\n /**\n * Get parsed content of the current project's manifest.json\n */\n private async getRootManifest(): Promise<any> {\n if (!('manifest.json' in this.metadataCache)) {\n const root = path.parse(process.cwd()).root;\n let dir = process.cwd();\n while (dir != root && !(await fileExists(path.join(dir, 'manifest.json')))) {\n dir = path.dirname(dir);\n }\n const manifestPath = path.join(dir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n this.metadataCache['manifest.json'] = JSON.parse(await fsAsync.readFile(manifestPath, 'utf-8'));\n } else {\n this.metadataCache['manifest.json'] = null;\n }\n }\n return this.metadataCache['manifest.json'];\n }\n\n /**\n * Get information about all available Obsidian versions.\n */\n async getVersions(): Promise<ObsidianVersionInfo[]> {\n const dest = path.join(this.cacheDir, \"obsidian-versions.json\");\n return (await this.cachedFetch(this.versionsUrl, dest)).versions;\n }\n\n /**\n * Get information about all available community plugins.\n */\n async getCommunityPlugins(): Promise<ObsidianCommunityPlugin[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-plugins.json\");\n return await this.cachedFetch(this.communityPluginsUrl, dest);\n }\n\n /**\n * Get information about all available community themes.\n */\n async getCommunityThemes(): Promise<ObsidianCommunityTheme[]> {\n const dest = path.join(this.cacheDir, \"obsidian-community-css-themes.json\");\n return await this.cachedFetch(this.communityThemesUrl, dest);\n }\n\n /**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * @param appVersion Obsidian version string or one of \n * - \"latest\": Get the current latest non-beta Obsidian version\n * - \"latest-beta\": Get the current latest beta Obsidian version (or latest is there is no current beta)\n * - \"earliest\": Get the `minAppVersion` set in set in your `manifest.json`\n * @param installerVersion Obsidian version string or one of \n * - \"latest\": Get the latest Obsidian installer\n * - \"earliest\": Get the oldest Obsidian installer compatible with the specified Obsidian app version\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\n async resolveVersions(appVersion: string, installerVersion = \"latest\"): Promise<[string, string]> {\n const versions = await this.getVersions();\n const appVersionInfo = await this.getVersionInfo(appVersion);\n\n if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {\n throw Error(`No compatible installers available for app version ${appVersion}`);\n }\n if (installerVersion == \"latest\") {\n installerVersion = appVersionInfo.maxInstallerVersion;\n } else if (installerVersion == \"earliest\") {\n installerVersion = appVersionInfo.minInstallerVersion;\n } else {\n installerVersion = semver.valid(installerVersion) ?? installerVersion;\n }\n const installerVersionInfo = versions.find(v => v.version == installerVersion);\n if (!installerVersionInfo || !installerVersionInfo.chromeVersion) {\n throw Error(`No Obsidian installer for version ${installerVersion} found`);\n }\n if (\n semver.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) ||\n semver.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)\n ) {\n throw Error(\n `App and installer versions incompatible: app ${appVersionInfo.version} is compatible with installer ` +\n `>=${appVersionInfo.minInstallerVersion} <=${appVersionInfo.maxInstallerVersion} but ` +\n `${installerVersionInfo.version} specified`\n )\n }\n\n return [appVersionInfo.version, installerVersionInfo.version];\n }\n\n /**\n * Gets details about an Obsidian version.\n * @param appVersion Obsidian app version (see {@link resolveVersions} for format)\n */\n async getVersionInfo(appVersion: string): Promise<ObsidianVersionInfo> {\n const versions = await this.getVersions();\n if (appVersion == \"latest-beta\") {\n appVersion = versions.at(-1)!.version;\n } else if (appVersion == \"latest\") {\n appVersion = versions.filter(v => !v.isBeta).at(-1)!.version;\n } else if (appVersion == \"earliest\") {\n appVersion = (await this.getRootManifest())?.minAppVersion;\n if (!appVersion) {\n throw Error('Unable to resolve Obsidian app appVersion \"earliest\", no manifest.json or minAppVersion found.')\n }\n } else {\n // if invalid match won't be found and we'll throw error below\n appVersion = semver.valid(appVersion) ?? appVersion;\n }\n const versionInfo = versions.find(v => v.version == appVersion);\n if (!versionInfo) {\n throw Error(`No Obsidian app version ${appVersion} found`);\n }\n\n return versionInfo;\n }\n\n /**\n * Downloads the Obsidian installer for the given version and platform. Returns the file path.\n * @param installerVersion Obsidian installer version to download. (see {@link resolveVersions} for format)\n */\n async downloadInstaller(installerVersion: string): Promise<string> {\n const installerVersionInfo = await this.getVersionInfo(installerVersion);\n return await this.downloadInstallerFromVersionInfo(installerVersionInfo);\n }\n\n /**\n * Helper for downloadInstaller that doesn't require the obsidian-versions.json file so it can be used in\n * updateObsidianVersionInfos\n */\n private async downloadInstallerFromVersionInfo(versionInfo: ObsidianVersionInfo): Promise<string> {\n const installerVersion = versionInfo.version;\n const {platform, arch} = process;\n const cacheDir = path.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);\n \n let installerPath: string\n let downloader: ((tmpDir: string) => Promise<string>)|undefined\n \n if (platform == \"linux\") {\n installerPath = path.join(cacheDir, \"obsidian\");\n let installerUrl: string|undefined\n if (arch.startsWith(\"arm\")) {\n installerUrl = versionInfo.downloads.appImageArm;\n } else {\n installerUrl = versionInfo.downloads.appImage;\n }\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const appImage = path.join(tmpDir, \"Obsidian.AppImage\");\n await fsAsync.writeFile(appImage, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianAppImage(appImage, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"win32\") {\n installerPath = path.join(cacheDir, \"Obsidian.exe\")\n const installerUrl = versionInfo.downloads.exe;\n let appArch: string|undefined\n if (arch == \"x64\") {\n appArch = \"app-64\"\n } else if (arch == \"ia32\") {\n appArch = \"app-32\"\n } else if (arch.startsWith(\"arm\")) {\n appArch = \"app-arm64\"\n }\n if (installerUrl && appArch) {\n downloader = async (tmpDir) => {\n const installerExecutable = path.join(tmpDir, \"Obsidian.exe\");\n await fsAsync.writeFile(installerExecutable, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianExe(installerExecutable, appArch, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else if (platform == \"darwin\") {\n installerPath = path.join(cacheDir, \"Contents/MacOS/Obsidian\");\n const installerUrl = versionInfo.downloads.dmg;\n if (installerUrl) {\n downloader = async (tmpDir) => {\n const dmg = path.join(tmpDir, \"Obsidian.dmg\");\n await fsAsync.writeFile(dmg, (await fetch(installerUrl)).body as any);\n const obsidianFolder = path.join(tmpDir, \"Obsidian\");\n await extractObsidianDmg(dmg, obsidianFolder);\n return obsidianFolder;\n };\n }\n } else {\n throw Error(`Unsupported platform ${platform}`);\n }\n if (!downloader) {\n throw Error(`No Obsidian installer download available for v${installerVersion} ${platform} ${arch}`);\n }\n\n if (!(await fileExists(installerPath))) {\n console.log(`Downloading Obsidian installer v${installerVersion}...`)\n await fsAsync.mkdir(path.dirname(cacheDir), { recursive: true });\n await withTmpDir(cacheDir, downloader);\n }\n\n return installerPath;\n }\n\n /**\n * Downloads the Obsidian asar for the given version and platform. Returns the file path.\n * \n * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`\n * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * \n * @param appVersion Obsidian version to download (see {@link resolveVersions} for format)\n */\n async downloadApp(appVersion: string): Promise<string> {\n const appVersionInfo = await this.getVersionInfo(appVersion);\n const appUrl = appVersionInfo.downloads.asar;\n if (!appUrl) {\n throw Error(`No asar found for Obsidian version ${appVersion}`);\n }\n const appPath = path.join(this.cacheDir, 'obsidian-app', `obsidian-${appVersionInfo.version}.asar`);\n\n if (!(await fileExists(appPath))) {\n console.log(`Downloading Obsidian app v${appVersion} ...`)\n await fsAsync.mkdir(path.dirname(appPath), { recursive: true });\n\n await withTmpDir(appPath, async (tmpDir) => {\n const isInsidersBuild = new URL(appUrl).hostname.endsWith('.obsidian.md');\n const response = isInsidersBuild ? await fetchObsidianAPI(appUrl) : await fetch(appUrl);\n const archive = path.join(tmpDir, 'app.asar.gz');\n const asar = path.join(tmpDir, 'app.asar')\n await fsAsync.writeFile(archive, response.body as any);\n await pipeline(fs.createReadStream(archive), zlib.createGunzip(), fs.createWriteStream(asar));\n return asar;\n })\n }\n\n return appPath;\n }\n\n /**\n * Downloads chromedriver for the given Obsidian version.\n * \n * wdio will download chromedriver from the Chrome for Testing API automatically (see\n * https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints). However, Google has only put\n * chromedriver since v115.0.5763.0 in that API, so wdio can't automatically download older versions of chromedriver\n * for old Electron versions. Here we download chromedriver for older versions ourselves using the @electron/get\n * package which fetches chromedriver from https://github.com/electron/electron/releases.\n * \n * @param installerVersion Obsidian installer version (see {@link resolveVersions} for format)\n */\n async downloadChromedriver(installerVersion: string): Promise<string> {\n const versionInfo = await this.getVersionInfo(installerVersion);\n const electronVersion = versionInfo.electronVersion;\n if (!electronVersion) {\n throw Error(`${installerVersion} is not an Obsidian installer version.`)\n }\n\n const chromedriverZipPath = await downloadArtifact({\n version: electronVersion,\n artifactName: 'chromedriver',\n cacheRoot: path.join(this.cacheDir, \"chromedriver-legacy\"),\n unsafelyDisableChecksums: true, // the checksums are slow and run even on cache hit.\n });\n\n let chromedriverPath: string\n if (process.platform == \"win32\") {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver.exe\");\n } else {\n chromedriverPath = path.join(path.dirname(chromedriverZipPath), \"chromedriver\");\n }\n\n if (!(await fileExists(chromedriverPath))) {\n console.log(`Downloading legacy chromedriver for electron ${electronVersion} ...`)\n await withTmpDir(chromedriverPath, async (tmpDir) => {\n await extractZip(chromedriverZipPath, { dir: tmpDir });\n return path.join(tmpDir, path.basename(chromedriverPath));\n })\n }\n\n return chromedriverPath;\n }\n\n /** Gets the latest version of a plugin. */\n private async getLatestPluginVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-plugins\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a plugin from a GitHub repo to the cache.\n * @param repo Repo\n * @param version Version of the plugin to install or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadGitHubPlugin(repo: string, version = \"latest\"): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n if (version == \"latest\") {\n version = await this.getLatestPluginVersion(repo);\n }\n if (!semver.valid(version)) {\n throw Error(`Invalid version \"${version}\"`);\n }\n version = semver.valid(version)!;\n\n const pluginDir = path.join(this.cacheDir, \"obsidian-plugins\", repo, version);\n if (!(await fileExists(pluginDir))) {\n await fsAsync.mkdir(path.dirname(pluginDir), { recursive: true });\n await withTmpDir(pluginDir, async (tmpDir) => {\n const assetsToDownload = {'manifest.json': true, 'main.js': true, 'styles.css': false};\n await Promise.all(\n Object.entries(assetsToDownload).map(async ([file, required]) => {\n const url = `https://github.com/${repo}/releases/download/${version}/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else if (required) {\n throw Error(`No ${file} found for ${repo} version ${version}`)\n }\n })\n )\n return tmpDir;\n });\n }\n\n return pluginDir;\n }\n\n /**\n * Downloads a community plugin to the cache.\n * @param id Id of the plugin\n * @param version Version of the plugin to install, or \"latest\"\n * @returns path to the downloaded plugin\n */\n private async downloadCommunityPlugin(id: string, version = \"latest\"): Promise<string> {\n const communityPlugins = await this.getCommunityPlugins();\n const pluginInfo = communityPlugins.find(p => p.id == id);\n if (!pluginInfo) {\n throw Error(`No plugin with id ${id} found.`);\n }\n return await this.downloadGitHubPlugin(pluginInfo.repo, version);\n }\n\n /**\n * Downloads a list of plugins to the cache and returns a list of `DownloadedPluginEntry` with the downloaded paths.\n * Also adds the `id` property to the plugins based on the manifest.\n * \n * You can download plugins from GitHub using `{repo: \"org/repo\"}` and community plugins using `{id: 'plugin-id'}`.\n * Local plugins will just be passed through.\n * \n * @param plugins List of plugins to download.\n */\n async downloadPlugins(plugins: PluginEntry[]): Promise<DownloadedPluginEntry[]> {\n return await Promise.all(\n plugins.map(async (plugin) => {\n if (typeof plugin == \"object\" && \"originalType\" in plugin) {\n return {...plugin as DownloadedPluginEntry}\n }\n let pluginPath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof plugin == \"string\") {\n pluginPath = path.resolve(plugin);\n originalType = \"local\";\n } else if (\"path\" in plugin) {;\n pluginPath = path.resolve(plugin.path);\n originalType = \"local\";\n } else if (\"repo\" in plugin) {\n pluginPath = await this.downloadGitHubPlugin(plugin.repo, plugin.version);\n originalType = \"github\";\n } else if (\"id\" in plugin) {\n pluginPath = await this.downloadCommunityPlugin(plugin.id, plugin.version);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of plugin path, repo, or id\")\n }\n\n const manifestPath = path.join(pluginPath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No plugin found at ${pluginPath}`)\n }\n let pluginId = (typeof plugin == \"object\" && (\"id\" in plugin)) ? plugin.id : undefined;\n if (!pluginId) {\n pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${pluginPath}/manifest.json malformed.`);\n }\n }\n\n let enabled: boolean\n if (typeof plugin == \"string\") {\n enabled = true\n } else {\n enabled = plugin.enabled ?? true;\n }\n return {path: pluginPath, id: pluginId, enabled, originalType}\n })\n );\n }\n\n /** Gets the latest version of a theme. */\n private async getLatestThemeVersion(repo: string) {\n repo = normalizeGitHubRepo(repo)\n const manifestUrl = `https://raw.githubusercontent.com/${repo}/HEAD/manifest.json`;\n const cacheDest = path.join(this.cacheDir, \"obsidian-themes\", repo, \"latest.json\");\n const manifest = await this.cachedFetch(manifestUrl, cacheDest);\n return manifest.version;\n }\n\n /**\n * Downloads a theme from a GitHub repo to the cache.\n * @param repo Repo\n * @returns path to the downloaded theme\n */\n private async downloadGitHubTheme(repo: string): Promise<string> {\n repo = normalizeGitHubRepo(repo)\n // Obsidian theme's are just pulled from the repo HEAD, not releases, so we can't really choose a specific \n // version of a theme.\n // We use the manifest.json version to check if the theme has changed.\n const version = await this.getLatestThemeVersion(repo);\n const themeDir = path.join(this.cacheDir, \"obsidian-themes\", repo, version);\n\n if (!(await fileExists(themeDir))) {\n await fsAsync.mkdir(path.dirname(themeDir), { recursive: true });\n await withTmpDir(themeDir, async (tmpDir) => {\n const assetsToDownload = ['manifest.json', 'theme.css'];\n await Promise.all(\n assetsToDownload.map(async (file) => {\n const url = `https://raw.githubusercontent.com/${repo}/HEAD/${file}`;\n const response = await fetch(url);\n if (response.ok) {\n await fsAsync.writeFile(path.join(tmpDir, file), response.body as any);\n } else {\n throw Error(`No ${file} found for ${repo}`);\n }\n })\n )\n return tmpDir;\n });\n }\n\n return themeDir;\n }\n\n /**\n * Downloads a community theme to the cache.\n * @param name name of the theme\n * @returns path to the downloaded theme\n */\n private async downloadCommunityTheme(name: string): Promise<string> {\n const communityThemes = await this.getCommunityThemes();\n const themeInfo = communityThemes.find(p => p.name == name);\n if (!themeInfo) {\n throw Error(`No theme with name ${name} found.`);\n }\n return await this.downloadGitHubTheme(themeInfo.repo);\n }\n\n /**\n * Downloads a list of themes to the cache and returns a list of `DownloadedThemeEntry` with the downloaded paths.\n * Also adds the `name` property to the plugins based on the manifest.\n * \n * You can download themes from GitHub using `{repo: \"org/repo\"}` and community themes using `{name: 'theme-name'}`.\n * Local themes will just be passed through.\n * \n * @param themes List of themes to download\n */\n async downloadThemes(themes: ThemeEntry[]): Promise<DownloadedThemeEntry[]> {\n return await Promise.all(\n themes.map(async (theme) => {\n if (typeof theme == \"object\" && \"originalType\" in theme) {\n return {...theme as DownloadedThemeEntry}\n }\n let themePath: string\n let originalType: \"local\"|\"github\"|\"community\"\n if (typeof theme == \"string\") {\n themePath = path.resolve(theme);\n originalType = \"local\";\n } else if (\"path\" in theme) {;\n themePath = path.resolve(theme.path);\n originalType = \"local\";\n } else if (\"repo\" in theme) {\n themePath = await this.downloadGitHubTheme(theme.repo);\n originalType = \"github\";\n } else if (\"name\" in theme) {\n themePath = await this.downloadCommunityTheme(theme.name);\n originalType = \"community\";\n } else {\n throw Error(\"You must specify one of theme path, repo, or name\")\n }\n\n const manifestPath = path.join(themePath, \"manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw Error(`No theme found at ${themePath}`)\n }\n let themeName = (typeof theme == \"object\" && (\"name\" in theme)) ? theme.name : undefined;\n if (!themeName) {\n const manifestPath = path.join(themePath, \"manifest.json\");\n themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${themePath}/manifest.json malformed.`);\n }\n }\n\n let enabled: boolean\n if (typeof theme == \"string\") {\n enabled = true\n } else {\n enabled = theme.enabled ?? true;\n }\n return {path: themePath, name: themeName, enabled: enabled, originalType};\n })\n );\n }\n\n /**\n * Installs plugins into an Obsidian vault\n * @param vault Path to the vault to install the plugins in\n * @param plugins List plugins to install\n */\n async installPlugins(vault: string, plugins: PluginEntry[]) {\n const downloadedPlugins = await this.downloadPlugins(plugins);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n const enabledPluginsPath = path.join(obsidianDir, 'community-plugins.json');\n let originalEnabledPlugins: string[] = [];\n if (await fileExists(enabledPluginsPath)) {\n originalEnabledPlugins = JSON.parse(await fsAsync.readFile(enabledPluginsPath, 'utf-8'));\n }\n let enabledPlugins = [...originalEnabledPlugins];\n\n for (const {path: pluginPath, enabled = true, originalType} of downloadedPlugins) {\n const manifestPath = path.join(pluginPath, 'manifest.json');\n const pluginId = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).id;\n if (!pluginId) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n\n const pluginDest = path.join(obsidianDir, 'plugins', pluginId);\n await fsAsync.mkdir(pluginDest, { recursive: true });\n\n const files: Record<string, [boolean, boolean]> = {\n \"manifest.json\": [true, true],\n \"main.js\": [true, true],\n \"styles.css\": [false, true],\n \"data.json\": [false, false],\n }\n for (const [file, [required, deleteIfMissing]] of Object.entries(files)) {\n if (await fileExists(path.join(pluginPath, file))) {\n await linkOrCp(path.join(pluginPath, file), path.join(pluginDest, file));\n } else if (required) {\n throw Error(`${pluginPath}/${file} missing.`);\n } else if (deleteIfMissing) {\n await fsAsync.rm(path.join(pluginDest, file), {force: true});\n }\n }\n\n const pluginAlreadyListed = enabledPlugins.includes(pluginId);\n if (enabled && !pluginAlreadyListed) {\n enabledPlugins.push(pluginId)\n } else if (!enabled && pluginAlreadyListed) {\n enabledPlugins = enabledPlugins.filter(p => p != pluginId);\n }\n\n if (originalType == \"local\") {\n // Add a .hotreload file for the https://github.com/pjeby/hot-reload plugin\n await fsAsync.writeFile(path.join(pluginDest, '.hotreload'), '');\n }\n }\n\n if (!_.isEqual(enabledPlugins, originalEnabledPlugins)) {\n await fsAsync.writeFile(enabledPluginsPath, JSON.stringify(enabledPlugins, undefined, 2));\n }\n }\n\n /** \n * Installs themes into an Obsidian vault\n * @param vault Path to the theme to install the themes in\n * @param themes List of themes to install\n */\n async installThemes(vault: string, themes: ThemeEntry[]) {\n const downloadedThemes = await this.downloadThemes(themes);\n\n const obsidianDir = path.join(vault, '.obsidian');\n await fsAsync.mkdir(obsidianDir, { recursive: true });\n\n let enabledTheme: string|undefined = undefined;\n\n for (const {path: themePath, enabled = true} of downloadedThemes) {\n const manifestPath = path.join(themePath, 'manifest.json');\n const cssPath = path.join(themePath, 'theme.css');\n\n const themeName = JSON.parse(await fsAsync.readFile(manifestPath, 'utf8').catch(() => \"{}\")).name;\n if (!themeName) {\n throw Error(`${manifestPath} missing or malformed.`);\n }\n if (!(await fileExists(cssPath))) {\n throw Error(`${cssPath} missing.`);\n }\n\n const themeDest = path.join(obsidianDir, 'themes', themeName);\n await fsAsync.mkdir(themeDest, { recursive: true });\n\n await linkOrCp(manifestPath, path.join(themeDest, \"manifest.json\"));\n await linkOrCp(cssPath, path.join(themeDest, \"theme.css\"));\n\n if (enabledTheme && enabled) {\n throw Error(\"You can only have one enabled theme.\")\n } else if (enabled) {\n enabledTheme = themeName;\n }\n }\n\n if (themes.length > 0) { // Only update appearance.json if we set the themes\n const appearancePath = path.join(obsidianDir, 'appearance.json');\n let appearance: any = {}\n if (await fileExists(appearancePath)) {\n appearance = JSON.parse(await fsAsync.readFile(appearancePath, 'utf-8'));\n }\n appearance.cssTheme = enabledTheme ?? \"\";\n await fsAsync.writeFile(appearancePath, JSON.stringify(appearance, undefined, 2));\n }\n }\n\n /**\n * Sets up the config dir to use for the `--user-data-dir` in obsidian. Returns the path to the created config dir.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format)\n * @param params.installerVersion Obsidian version string.\n * @param params.appPath Path to the asar file to install. Will download if omitted.\n * @param params.vault Path to the vault to open in Obsidian.\n * @param params.dest Destination path for the config dir. If omitted it will create a temporary directory.\n */\n async setupConfigDir(params: {\n appVersion: string, installerVersion: string,\n appPath?: string,\n vault?: string,\n dest?: string,\n }): Promise<string> {\n const [appVersion, installerVersion] = await this.resolveVersions(params.appVersion, params.installerVersion);\n // configDir will be passed to --user-data-dir, so Obsidian is somewhat sandboxed. We set up \"obsidian.json\" so\n // that Obsidian opens the vault by default and doesn't check for updates.\n const configDir = params.dest ?? await makeTmpDir('obs-launcher-config-');\n \n let obsidianJson: any = {\n updateDisabled: true, // Prevents Obsidian trying to auto-update on boot.\n }\n let localStorageData: Record<string, string> = {\n \"most-recently-installed-version\": appVersion, // prevents the changelog page on boot\n }\n\n if (params.vault !== undefined) {\n if (!await fileExists(params.vault)) {\n throw Error(`Vault path ${params.vault} doesn't exist.`)\n }\n const vaultId = crypto.randomBytes(8).toString(\"hex\");\n obsidianJson = {\n ...obsidianJson,\n vaults: {\n [vaultId]: {\n path: path.resolve(params.vault),\n ts: new Date().getTime(),\n open: true,\n },\n },\n };\n localStorageData = {\n ...localStorageData,\n [`enable-plugin-${vaultId}`]: \"true\", // Disable \"safe mode\" and enable plugins\n }\n }\n\n await fsAsync.writeFile(path.join(configDir, 'obsidian.json'), JSON.stringify(obsidianJson));\n\n let appPath = params.appPath;\n if (!appPath) {\n appPath = await this.downloadApp(appVersion);\n }\n await linkOrCp(appPath, path.join(configDir, path.basename(appPath)));\n\n const localStorage = new ChromeLocalStorage(configDir);\n await localStorage.setItems(\"app://obsidian.md\", localStorageData)\n await localStorage.close();\n\n return configDir;\n }\n\n /**\n * Sets up a vault for Obsidian, installing plugins and themes and optionally copying the vault to a temporary\n * directory first.\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @returns Path to the copied vault (or just the path to the vault if copy is false)\n */\n async setupVault(params: {\n vault: string,\n copy?: boolean,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n }): Promise<string> {\n let vault = params.vault;\n if (params.copy) {\n const dest = await makeTmpDir('obs-launcher-vault-');\n await fsAsync.cp(vault, dest, { recursive: true, preserveTimestamps: true });\n vault = dest;\n }\n await this.installPlugins(vault, params.plugins ?? []);\n await this.installThemes(vault, params.themes ?? []);\n\n return vault;\n }\n\n /**\n * Downloads and launches Obsidian with a sandboxed config dir and a specifc vault open. Optionally install plugins\n * and themes first.\n * \n * This is just a shortcut for calling `downloadApp`, `downloadInstaller`, `setupVault` and `setupConfDir`.\n *\n * @param params.appVersion Obsidian app version (see {@link resolveVersions} for format). Default \"latest\"\n * @param params.installerVersion Obsidian installer version (see {@link resolveVersions} for format).\n * Default \"latest\"\n * @param params.vault Path to the vault to open in Obsidian\n * @param params.copy Whether to copy the vault to a tmpdir first. Default false\n * @param params.plugins List of plugins to install in the vault\n * @param params.themes List of themes to install in the vault\n * @param params.args CLI args to pass to Obsidian\n * @param params.spawnOptions Options to pass to `spawn`\n * @returns The launched child process and the created tmpdirs\n */\n async launch(params: {\n appVersion?: string, installerVersion?: string,\n copy?: boolean,\n vault?: string,\n plugins?: PluginEntry[], themes?: ThemeEntry[],\n args?: string[],\n spawnOptions?: child_process.SpawnOptions,\n }): Promise<{proc: child_process.ChildProcess, configDir: string, vault?: string}> {\n const [appVersion, installerVersion] = await this.resolveVersions(\n params.appVersion ?? \"latest\",\n params.installerVersion ?? \"latest\",\n );\n const appPath = await this.downloadApp(appVersion);\n const installerPath = await this.downloadInstaller(installerVersion);\n\n let vault = params.vault;\n if (vault) {\n vault = await this.setupVault({\n vault,\n copy: params.copy ?? false,\n plugins: params.plugins, themes: params.themes,\n })\n }\n\n const configDir = await this.setupConfigDir({ appVersion, installerVersion, appPath, vault });\n\n // Spawn child.\n const proc = child_process.spawn(installerPath, [\n `--user-data-dir=${configDir}`,\n ...(params.args ?? []),\n ], {\n ...params.spawnOptions,\n });\n\n return {proc, configDir, vault};\n }\n\n /** \n * Updates the info obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands\n * and in wdio-obsidian-service to get metadata about Obsidian versions in one place such as minInstallerVersion and\n * the internal electron version.\n */\n async updateObsidianVersionInfos(\n original?: ObsidianVersionInfos, { maxInstances = 1 } = {},\n ): Promise<ObsidianVersionInfos> {\n const repo = 'obsidianmd/obsidian-releases';\n\n let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {\n path: \"desktop-releases.json\",\n since: original?.metadata.commit_date,\n });\n commitHistory.reverse();\n if (original) {\n commitHistory = _.takeRightWhile(commitHistory, c => c.sha != original.metadata.commit_sha);\n }\n \n const fileHistory: any[] = await pool(8, commitHistory, commit =>\n fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then(r => r.json())\n );\n \n const githubReleases = await fetchGitHubAPIPaginated(`repos/${repo}/releases`);\n \n const versionMap: _.Dictionary<Partial<ObsidianVersionInfo>> = _.keyBy(\n original?.versions ?? [],\n v => v.version,\n );\n \n for (const {beta, ...current} of fileHistory) {\n if (beta && (!versionMap[beta.latestVersion] || versionMap[beta.latestVersion].isBeta)) {\n versionMap[beta.latestVersion] = _.merge({}, versionMap[beta.latestVersion],\n parseObsidianDesktopRelease(beta, true),\n );\n }\n versionMap[current.latestVersion] = _.merge({}, versionMap[current.latestVersion],\n parseObsidianDesktopRelease(current, false),\n )\n }\n \n for (const release of githubReleases) {\n if (versionMap.hasOwnProperty(release.name)) {\n versionMap[release.name] = _.merge({}, versionMap[release.name], parseObsidianGithubRelease(release));\n }\n }\n \n const dependencyVersions = await pool(maxInstances,\n Object.values(versionMap).filter(v => v.downloads?.appImage && !v.chromeVersion),\n async (v) => {\n const binaryPath = await this.downloadInstallerFromVersionInfo(v as ObsidianVersionInfo);\n const electronVersionInfo = await getElectronVersionInfo(v.version!, binaryPath);\n return {...v, ...electronVersionInfo};\n },\n )\n for (const deps of dependencyVersions) {\n versionMap[deps.version!] = _.merge({}, versionMap[deps.version!], deps);\n }\n \n // populate minInstallerVersion and maxInstallerVersion and add corrections\n let minInstallerVersion: string|undefined = undefined;\n let maxInstallerVersion: string|undefined = undefined;\n for (const version of Object.keys(versionMap).sort(semver.compare)) {\n // older versions have 0.0.0 as there min version, which doesn't exist anywhere we can download.\n // we'll set those to the first available installer version.\n if (!minInstallerVersion && versionMap[version].chromeVersion) {\n minInstallerVersion = version;\n }\n if (versionMap[version].downloads!.appImage) {\n maxInstallerVersion = version;\n }\n versionMap[version] = mergeKeepUndefined({}, versionMap[version],\n {\n minInstallerVersion: versionMap[version].minInstallerVersion ?? minInstallerVersion,\n maxInstallerVersion: maxInstallerVersion,\n },\n correctObsidianVersionInfo(versionMap[version]),\n );\n }\n \n const result: ObsidianVersionInfos = {\n metadata: {\n commit_date: commitHistory.at(-1)?.commit.committer.date ?? original?.metadata.commit_date,\n commit_sha: commitHistory.at(-1)?.sha ?? original?.metadata.commit_sha,\n timestamp: original?.metadata.timestamp ?? \"\", // set down below\n },\n versions: Object.values(versionMap)\n .map(normalizeObsidianVersionInfo)\n .sort((a, b) => semver.compare(a.version, b.version)),\n }\n\n // Update timestamp if anything has changed. Also, GitHub will cancel scheduled workflows if the repository is\n // \"inactive\" for 60 days. So we'll update the timestamp every once in a while even if there are no Obsidian\n // updates to make sure there's commit activity in the repo.\n const dayMs = 24 * 60 * 60 * 1000;\n const timeSinceLastUpdate = new Date().getTime() - new Date(original?.metadata.timestamp ?? 0).getTime();\n if (!_.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {\n result.metadata.timestamp = new Date().toISOString();\n }\n\n return result;\n }\n\n /**\n * Returns true if the Obsidian version is already in the cache.\n * @param type on of \"app\" or \"installer\"\n * @param version Obsidian app/installer version (see {@link resolveVersions} for format)\n */\n async isInCache(type: \"app\"|\"installer\", version: string) {\n version = (await this.getVersionInfo(version)).version;\n\n let dest: string\n if (type == \"app\") {\n dest = `obsidian-app/obsidian-${version}.asar`;\n } else { // type == \"installer\"\n const {platform, arch} = process;\n dest =`obsidian-installer/${platform}-${arch}/Obsidian-${version}`;\n }\n\n return (await fileExists(path.join(this.cacheDir, dest)));\n }\n\n /**\n * Returns true if we either have the credentials to download the version or it's already in cache.\n * This is only relevant for Obsidian beta versions, as they require Obsidian insider credentials to download.\n * @param version Obsidian version (see {@link resolveVersions} for format)\n */\n async isAvailable(version: string): Promise<boolean> {\n const versionInfo = await this.getVersionInfo(version);\n const versionExists = !!(versionInfo.downloads.asar && versionInfo.minInstallerVersion)\n\n if (versionInfo.isBeta) {\n const hasCreds = !!(process.env['OBSIDIAN_USERNAME'] && process.env['OBSIDIAN_PASSWORD']);\n const inCache = await this.isInCache('app', versionInfo.version);\n return versionExists && (hasCreds || inCache);\n } else {\n return versionExists;\n }\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport os from \"os\"\nimport { PromisePool } from '@supercharge/promise-pool'\nimport _ from \"lodash\"\n\n/// Files ///\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create tmpdir under the system temporary directory.\n * @param prefix \n * @returns \n */\nexport async function makeTmpDir(prefix?: string) {\n return fsAsync.mkdtemp(path.join(os.tmpdir(), prefix ?? 'tmp-'));\n}\n\n/**\n * Handles creating a file \"atomically\" by creating a tmpDir, then downloading or otherwise creating the file under it,\n * then renaming it to the final location when done.\n * @param dest Path the file should end up at.\n * @param func Function takes path to a temporary directory it can use as scratch space. The path it returns will be\n * moved to `dest`. If no path is returned, it will move the whole tmpDir to dest.\n */\nexport async function withTmpDir(dest: string, func: (tmpDir: string) => Promise<string|void>): Promise<void> {\n dest = path.resolve(dest);\n const tmpDir = await fsAsync.mkdtemp(path.join(path.dirname(dest), `.${path.basename(dest)}.tmp.`));\n try {\n let result = await func(tmpDir) ?? tmpDir;\n if (!path.isAbsolute(result)) {\n result = path.join(tmpDir, result);\n } else if (!path.resolve(result).startsWith(tmpDir)) {\n throw new Error(`Returned path ${result} not under tmpDir`)\n }\n // rename will overwrite files but not directories\n if (await fileExists(dest) && (await fsAsync.stat(dest)).isDirectory()) {\n await fsAsync.rename(dest, tmpDir + \".old\")\n }\n \n await fsAsync.rename(result, dest);\n await fsAsync.rm(tmpDir + \".old\", { recursive: true, force: true });\n } finally {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Tries to hardlink a file, falls back to copy if it fails\n */\nexport async function linkOrCp(src: string, dest: string) {\n try {\n await fsAsync.link(src, dest);\n } catch {\n await fsAsync.copyFile(src, dest);\n }\n}\n\n\n/// Promises ///\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Await a promise or reject if it takes longer than timeout.\n */\nexport async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n let timer: NodeJS.Timeout;\n const result = Promise.race([\n promise,\n new Promise<T>((resolve, reject) => timer = setTimeout(() => reject(Error(\"Promise timed out\")), timeout))\n ])\n return result.finally(() => clearTimeout(timer));\n}\n\n/**\n * Wrapper around PromisePool that throws on any error.\n */\nexport async function pool<T, U>(size: number, items: T[], func: (item: T) => Promise<U>): Promise<U[]> {\n const { results } = await PromisePool\n .for(items)\n .withConcurrency(size)\n .handleError(async (error) => { throw error; })\n .useCorrespondingResults()\n .process(func);\n return results as U[];\n}\n\nexport type SuccessResult<T> = {success: true, result: T, error: undefined};\nexport type ErrorResult = {success: false, result: undefined, error: any};\nexport type Maybe<T> = SuccessResult<T>|ErrorResult;\n\n/**\n * Helper for handling asynchronous errors with less hassle.\n */\nexport async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {\n return promise\n .then(r => ({success: true, result: r, error: undefined} as const))\n .catch(e => ({success: false, result: undefined, error: e} as const));\n}\n\n\n/**\n * Lodash _.merge but overwrites values with undefined if present, instead of ignoring undefined.\n */\nexport function mergeKeepUndefined(object: any, ...sources: any[]) {\n return _.mergeWith(object, ...sources,\n (objValue: any, srcValue: any, key: any, obj: any) => {\n if (_.isPlainObject(obj) && objValue !== srcValue && srcValue === undefined) {\n obj[key] = srcValue\n }\n }\n );\n}","import _ from \"lodash\"\nimport fsAsync from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\n\n\n/**\n * GitHub API stores pagination information in the \"Link\" header. The header looks like this:\n * ```\n * <https://api.github.com/repositories/1300192/issues?page=2>; rel=\"prev\", <https://api.github.com/repositories/1300192/issues?page=4>; rel=\"next\"\n * ```\n */\nexport function parseLinkHeader(linkHeader: string): Record<string, Record<string, string>> {\n function parseLinkData(linkData: string) {\n return Object.fromEntries(\n linkData.split(\";\").flatMap(x => {\n const partMatch = x.trim().match(/^([^=]+?)\\s*=\\s*\"?([^\"]+)\"?$/);\n return partMatch ? [[partMatch[1], partMatch[2]]] : [];\n })\n )\n }\n\n const linkDatas = linkHeader\n .split(/,\\s*(?=<)/)\n .flatMap(link => {\n const linkMatch = link.trim().match(/^<([^>]*)>(.*)$/);\n if (linkMatch) {\n return [{\n url: linkMatch[1],\n ...parseLinkData(linkMatch[2]),\n } as Record<string, string>];\n } else {\n return [];\n }\n })\n .filter(l => l.rel)\n return Object.fromEntries(linkDatas.map(l => [l.rel, l]));\n};\n\n\nfunction createURL(url: string, base: string, params: Record<string, any> = {}) {\n params =_.pickBy(params, x => x !== undefined);\n const urlObj = new URL(url, base);\n const searchParams = new URLSearchParams({...Object.fromEntries(urlObj.searchParams), ...params});\n if ([...searchParams].length > 0) {\n urlObj.search = '?' + searchParams;\n }\n return urlObj.toString();\n}\n\n\n/**\n * Fetch from the GitHub API. Uses GITHUB_TOKEN if available. You can access the API without a token, but will hit\n * the usage caps very quickly.\n */\nexport async function fetchGitHubAPI(url: string, params: Record<string, any> = {}) {\n url = createURL(url, \"https://api.github.com\", params)\n const token = process.env.GITHUB_TOKEN;\n const headers: Record<string, string> = token ? {Authorization: \"Bearer \" + token} : {};\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`GitHub API error: ${await response.text()}`);\n }\n return await response;\n}\n\n\n/**\n * Fetch all data from a paginated GitHub API request.\n */\nexport async function fetchGitHubAPIPaginated(url: string, params: Record<string, any> = {}) {\n const results = [];\n let next: string|undefined = createURL(url, \"https://api.github.com\", { per_page: 100, ...params });\n while (next) {\n const response = await fetchGitHubAPI(next);\n results.push(...await response.json() as any);\n next = parseLinkHeader(response.headers.get('link') ?? '').next?.url;\n }\n return results;\n}\n\n/**\n * Fetch from the Obsidian API to download insider versions. Uses OBSIDIAN_USERNAME and\n * OBSIDIAN_PASSWORD environment variables.\n */\nexport async function fetchObsidianAPI(url: string) {\n url = createURL(url, \"https://releases.obsidian.md\");\n\n const username = process.env.OBSIDIAN_USERNAME;\n const password = process.env.OBSIDIAN_PASSWORD;\n if (!username || !password) {\n throw Error(\"OBSIDIAN_USERNAME or OBSIDIAN_PASSWORD environment variables are required to access the Obsidian API for beta versions.\")\n }\n\n const response = await fetch(url, {\n headers: {\n // For some reason you have to set the User-Agent or it won't let you download\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',\n \"Origin\": \"app://obsidian.md\",\n 'Authorization': 'Basic ' + btoa(username + \":\" + password),\n },\n })\n return response;\n}\n\n\n/**\n * Fetches a URL returning its content as a string. Throws if response is not OK.\n * URL can be a file url.\n */\nexport async function fetchWithFileUrl(url: string) {\n if (url.startsWith(\"file:\")) {\n return await fsAsync.readFile(fileURLToPath(url), 'utf-8');\n } else {\n const response = await fetch(url);\n if (response.ok) {\n return response.text()\n } else {\n throw Error(`Request failed with ${response.status}: ${response.text()}`)\n }\n }\n}\n","import path from \"path\"\nimport { ClassicLevel } from \"classic-level\"\n\n\n/**\n * Class to directly manipulate chrome/electron local storage.\n *\n * Normally you'd just manipulate `localStorage` directly during the webdriver tests. However, there's not a built in\n * way to set up localStorage values *before* the app boots. We need to set `enable-plugins` and some other keys for\n * Obsidian to read during the boot process. This class lets us setup the local storage before launching Obsidian.\n */\nexport default class ChromeLocalStorage {\n private db: ClassicLevel;\n\n /** Pass the path to the user data dir for Chrome/Electron. If it doesn't exist it will be created. */\n constructor(public readonly userDataDir: string) {\n this.db = new ClassicLevel(path.join(userDataDir, 'Local Storage/leveldb/'));\n }\n\n private encodeKey = (domain: string, key: string) => `_${domain}\\u0000\\u0001${key}`;\n private decodeKey = (key: string) => key.slice(1).split(\"\\u0000\\u0001\") as [string, string];\n private encodeValue = (value: string) => `\\u0001${value}`;\n private decodeValue = (value: string) => value.slice(1);\n\n /**\n * Get a value from localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to retreive\n */\n async getItem(domain: string, key: string): Promise<string|null> {\n const value = await this.db.get(this.encodeKey(domain, key));\n return (value === undefined) ? null : this.decodeValue(value);\n }\n\n /**\n * Set a value in localStorage\n * @param domain Domain the value is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key Key to set\n * @param value Value to set\n */\n async setItem(domain: string, key: string, value: string) {\n await this.db.put(this.encodeKey(domain, key), this.encodeValue(value))\n }\n\n /**\n * Removes a key from localStorage\n * @param domain Domain the values is under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param key key to remove.\n */\n async removeItem(domain: string, key: string) {\n await this.db.del(this.encodeKey(domain, key))\n }\n\n /** Get all items in localStorage as [domain, key, value] tuples */\n async getAllItems(): Promise<[string, string, string][]> {\n const result: [string, string, string][] = []\n for await (const pair of this.db.iterator()) {\n if (pair[0].startsWith(\"_\")) { // ignore the META values\n const [domain, key] = this.decodeKey(pair[0]);\n const value = this.decodeValue(pair[1]);\n result.push([domain, key, value]);\n }\n }\n return result;\n }\n\n /**\n * Write multiple values to localStorage in batch\n * @param domain Domain the values are under, e.g. \"https://example.com\" or \"app://obsidian.md\"\n * @param data key/value mapping to write\n */\n async setItems(domain: string, data: Record<string, string>) {\n await this.db.batch(\n Object.entries(data).map(([key, value]) => ({\n type: \"put\",\n key: this.encodeKey(domain, key),\n value: this.encodeValue(value),\n }))\n )\n }\n\n /**\n * Close the localStorage database.\n */\n async close() {\n await this.db.close();\n }\n}\n","import fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { promisify } from \"util\";\nimport child_process from \"child_process\"\nimport which from \"which\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\nimport CDP from 'chrome-remote-interface'\nimport { makeTmpDir, withTmpDir, maybe, withTimeout, sleep } from \"./utils.js\";\nimport { ObsidianVersionInfo } from \"./types.js\";\nconst execFile = promisify(child_process.execFile);\n\n\nexport function normalizeGitHubRepo(repo: string) {\n return repo.replace(/^(https?:\\/\\/)?(github.com\\/)/, '')\n}\n\n/**\n * Running AppImage requires libfuse2, extracting the AppImage first avoids that.\n */\nexport async function extractObsidianAppImage(appImage: string, dest: string) {\n await withTmpDir(dest, async (tmpDir) => {\n await fsAsync.chmod(appImage, 0o755);\n await execFile(appImage, [\"--appimage-extract\"], {cwd: tmpDir});\n return path.join(tmpDir, 'squashfs-root');\n })\n}\n\n/**\n * Obsidian appears to use NSIS to bundle their Window's installers. We want to extract the executable\n * files directly without running the installer. 7zip can extract the raw files from the exe.\n */\nexport async function extractObsidianExe(exe: string, appArch: string, dest: string) {\n const path7z = await which(\"7z\", { nothrow: true });\n if (!path7z) {\n throw new Error(\n \"Downloading Obsidian for Windows requires 7zip to be installed and available on the PATH. \" +\n \"You install it from https://www.7-zip.org and then add the install location to the PATH.\"\n );\n }\n exe = path.resolve(exe);\n // The installer contains several `.7z` files with files for different architectures \n const subArchive = path.join('$PLUGINSDIR', appArch + \".7z\");\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const extractedInstaller = path.join(tmpDir, \"installer\");\n await execFile(path7z, [\"x\", \"-o\" + extractedInstaller, exe, subArchive]);\n const extractedObsidian = path.join(tmpDir, \"obsidian\");\n await execFile(path7z, [\"x\", \"-o\" + extractedObsidian, path.join(extractedInstaller, subArchive)]);\n return extractedObsidian;\n })\n}\n\n/**\n * Extract the executable from the Obsidian dmg installer.\n */\nexport async function extractObsidianDmg(dmg: string, dest: string) {\n dest = path.resolve(dest);\n\n await withTmpDir(dest, async (tmpDir) => {\n const proc = await execFile('hdiutil', ['attach', '-nobrowse', '-readonly', dmg]);\n const volume = proc.stdout.match(/\\/Volumes\\/.*$/m)![0];\n const obsidianApp = path.join(volume, \"Obsidian.app\");\n try {\n await fsAsync.cp(obsidianApp, tmpDir, {recursive: true, verbatimSymlinks: true});\n } finally {\n await execFile('hdiutil', ['detach', volume]);\n }\n return tmpDir;\n })\n}\n\n\nexport function parseObsidianDesktopRelease(fileRelease: any, isBeta: boolean): Partial<ObsidianVersionInfo> {\n return {\n version: fileRelease.latestVersion,\n minInstallerVersion: fileRelease.minimumVersion != '0.0.0' ? fileRelease.minimumVersion : undefined,\n isBeta: isBeta,\n downloads: {\n asar: fileRelease.downloadUrl,\n },\n };\n}\n\nexport function parseObsidianGithubRelease(gitHubRelease: any): Partial<ObsidianVersionInfo> {\n const version = gitHubRelease.name;\n const assets: string[] = gitHubRelease.assets.map((a: any) => a.browser_download_url);\n const downloads = {\n appImage: assets.find(u => u.match(`${version}.AppImage$`)),\n appImageArm: assets.find(u => u.match(`${version}-arm64.AppImage$`)),\n apk: assets.find(u => u.match(`${version}.apk$`)),\n asar: assets.find(u => u.match(`${version}.asar.gz$`)),\n dmg: assets.find(u => u.match(`${version}(-universal)?.dmg$`)),\n exe: assets.find(u => u.match(`${version}.exe$`)),\n }\n\n return {\n version: version,\n gitHubRelease: gitHubRelease.html_url,\n downloads: downloads,\n }\n}\n\n/**\n * Extract electron and chrome versions for an Obsidian version.\n */\nexport async function getElectronVersionInfo(\n version:string, binaryPath: string,\n): Promise<Partial<ObsidianVersionInfo>> {\n console.log(`${version}: Retrieving electron & chrome versions...`);\n\n const configDir = await makeTmpDir('fetch-obsidian-versions-');\n\n const proc = child_process.spawn(binaryPath, [\n `--remote-debugging-port=0`, // 0 will make it choose a random available port\n '--test-type=webdriver',\n `--user-data-dir=${configDir}`,\n '--no-sandbox', // Workaround for SUID issue, see https://github.com/electron/electron/issues/42510\n ]);\n const procExit = new Promise<number>((resolve) => proc.on('exit', (code) => resolve(code ?? -1)));\n // proc.stdout.on('data', data => console.log(`stdout: ${data}`));\n // proc.stderr.on('data', data => console.log(`stderr: ${data}`));\n\n let dependencyVersions: any;\n try {\n // Wait for the logs showing that Obsidian is ready, and pull the chosen DevTool Protocol port from it\n const portPromise = new Promise<number>((resolve, reject) => {\n procExit.then(() => reject(\"Processed ended without opening a port\"))\n proc.stderr.on('data', data => {\n const port = data.toString().match(/ws:\\/\\/[\\w.]+?:(\\d+)/)?.[1];\n if (port) {\n resolve(Number(port));\n }\n });\n })\n\n const port = await maybe(withTimeout(portPromise, 10 * 1000));\n if (!port.success) {\n throw new Error(\"Timed out waiting for Chrome DevTools protocol port\");\n }\n const client = await CDP({port: port.result});\n const response = await client.Runtime.evaluate({ expression: \"JSON.stringify(process.versions)\" });\n dependencyVersions = JSON.parse(response.result.value);\n await client.close();\n } finally {\n proc.kill(\"SIGTERM\");\n const timeout = await maybe(withTimeout(procExit, 4 * 1000));\n if (!timeout.success) {\n console.log(`${version}: Stuck process ${proc.pid}, using SIGKILL`);\n proc.kill(\"SIGKILL\");\n }\n await procExit;\n await sleep(1000); // Need to wait a bit or sometimes the rm fails because something else is writing to it\n await fsAsync.rm(configDir, { recursive: true, force: true });\n }\n\n if (!dependencyVersions?.electron || !dependencyVersions?.chrome) {\n throw Error(`Failed to extract electron and chrome versions for ${version}`)\n }\n\n return {\n electronVersion: dependencyVersions.electron,\n chromeVersion: dependencyVersions.chrome,\n nodeVersion: dependencyVersions.node,\n };\n}\n\n/**\n * Add some corrections to the Obsidian version data.\n */\nexport function correctObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): Partial<ObsidianVersionInfo> {\n const corrections: Partial<ObsidianVersionInfo>[] = [\n // These version's downloads are missing or broken.\n {version: '0.12.16', downloads: { asar: undefined }},\n {version: '1.4.7', downloads: { asar: undefined }},\n {version: '1.4.8', downloads: { asar: undefined }},\n \n // The minInstallerVersion here is incorrect\n {version: \"1.3.4\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.3\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.2\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.1\", minInstallerVersion: \"0.14.5\"},\n {version: \"1.3.0\", minInstallerVersion: \"0.14.5\"},\n ]\n\n const result = corrections.find(v => v.version == versionInfo.version) ?? {};\n // minInstallerVersion is incorrect, running Obsidian with installer older than 1.1.9 won't boot with errors like\n // `(node:11592) electron: Failed to load URL: app://obsidian.md/starter.html with error: ERR_BLOCKED_BY_CLIENT`\n if (semver.gte(versionInfo.version!, \"1.5.3\") && semver.lt(versionInfo.minInstallerVersion!, \"1.1.9\")) {\n result.minInstallerVersion = \"1.1.9\"\n }\n\n return result;\n}\n\n/**\n * Normalize order and remove undefined values.\n */\nexport function normalizeObsidianVersionInfo(versionInfo: Partial<ObsidianVersionInfo>): ObsidianVersionInfo {\n versionInfo = {\n version: versionInfo.version,\n minInstallerVersion: versionInfo.minInstallerVersion,\n maxInstallerVersion: versionInfo.maxInstallerVersion,\n isBeta: versionInfo.isBeta,\n gitHubRelease: versionInfo.gitHubRelease,\n downloads: {\n asar: versionInfo.downloads?.asar,\n appImage: versionInfo.downloads?.appImage,\n appImageArm: versionInfo.downloads?.appImageArm,\n apk: versionInfo.downloads?.apk,\n dmg: versionInfo.downloads?.dmg,\n exe: versionInfo.downloads?.exe,\n },\n electronVersion: versionInfo.electronVersion,\n chromeVersion: versionInfo.chromeVersion,\n nodeVersion: versionInfo.nodeVersion,\n };\n versionInfo.downloads = _.omitBy(versionInfo.downloads, _.isUndefined);\n versionInfo = _.omitBy(versionInfo, _.isUndefined);\n return versionInfo as ObsidianVersionInfo;\n}\n"]}