obsidian-launcher 1.3.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -56
- package/dist/7z.cjs +41 -0
- package/dist/7z.cjs.map +1 -0
- package/dist/7z.d.cts +2 -0
- package/dist/7z.d.ts +2 -0
- package/dist/7z.js +39 -0
- package/dist/7z.js.map +1 -0
- package/dist/{chunk-MZRDQQTK.js → chunk-KPNJCYO4.js} +658 -425
- package/dist/chunk-KPNJCYO4.js.map +1 -0
- package/dist/{chunk-CUFVRSMT.cjs → chunk-KQZZMJOZ.cjs} +649 -412
- package/dist/chunk-KQZZMJOZ.cjs.map +1 -0
- package/dist/chunk-QQJCBP4N.cjs +8 -0
- package/dist/chunk-QQJCBP4N.cjs.map +1 -0
- package/dist/cli.cjs +30 -34
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +27 -32
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -53
- package/dist/index.d.ts +144 -53
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +25 -16
- package/dist/chunk-CUFVRSMT.cjs.map +0 -1
- package/dist/chunk-MZRDQQTK.js.map +0 -1
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await 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; } async function _asyncOptionalChain(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 = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await 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; } async function _asyncOptionalChain(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 = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
var _chunkQQJCBP4Ncjs = require('./chunk-QQJCBP4N.cjs');
|
|
4
|
+
|
|
5
|
+
// src/utils.ts
|
|
2
6
|
var _promises = require('fs/promises'); var _promises2 = _interopRequireDefault(_promises);
|
|
3
7
|
var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
|
|
4
|
-
var _zlib = require('zlib'); var _zlib2 = _interopRequireDefault(_zlib);
|
|
5
8
|
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
6
|
-
var _crypto = require('crypto'); var _crypto2 = _interopRequireDefault(_crypto);
|
|
7
|
-
var _extractzip = require('extract-zip'); var _extractzip2 = _interopRequireDefault(_extractzip);
|
|
8
|
-
var _promises3 = require('stream/promises');
|
|
9
|
-
var _get = require('@electron/get');
|
|
10
|
-
var _child_process = require('child_process'); var _child_process2 = _interopRequireDefault(_child_process);
|
|
11
|
-
var _semver = require('semver'); var _semver2 = _interopRequireDefault(_semver);
|
|
12
|
-
var _url = require('url');
|
|
13
|
-
|
|
14
|
-
// src/utils.ts
|
|
15
|
-
|
|
16
|
-
|
|
17
9
|
var _os = require('os'); var _os2 = _interopRequireDefault(_os);
|
|
18
10
|
var _promisepool = require('@supercharge/promise-pool');
|
|
19
11
|
var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash);
|
|
@@ -28,14 +20,14 @@ async function fileExists(path5) {
|
|
|
28
20
|
async function makeTmpDir(prefix) {
|
|
29
21
|
return _promises2.default.mkdtemp(_path2.default.join(_os2.default.tmpdir(), _nullishCoalesce(prefix, () => ( "tmp-"))));
|
|
30
22
|
}
|
|
31
|
-
async function
|
|
23
|
+
async function atomicCreate(dest, func, options = {}) {
|
|
32
24
|
dest = _path2.default.resolve(dest);
|
|
25
|
+
const createdParentDir = await _promises2.default.mkdir(_path2.default.dirname(dest), { recursive: true });
|
|
33
26
|
const tmpDir = await _promises2.default.mkdtemp(_path2.default.join(_path2.default.dirname(dest), `.${_path2.default.basename(dest)}.tmp.`));
|
|
34
27
|
try {
|
|
35
28
|
let result = await _asyncNullishCoalesce(await func(tmpDir), async () => ( tmpDir));
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} else if (!_path2.default.resolve(result).startsWith(tmpDir)) {
|
|
29
|
+
result = _path2.default.resolve(tmpDir, result);
|
|
30
|
+
if (!result.startsWith(tmpDir)) {
|
|
39
31
|
throw new Error(`Returned path ${result} not under tmpDir`);
|
|
40
32
|
}
|
|
41
33
|
if (await fileExists(dest) && (await _promises2.default.stat(dest)).isDirectory()) {
|
|
@@ -43,28 +35,22 @@ async function withTmpDir(dest, func) {
|
|
|
43
35
|
}
|
|
44
36
|
await _promises2.default.rename(result, dest);
|
|
45
37
|
await _promises2.default.rm(tmpDir + ".old", { recursive: true, force: true });
|
|
46
|
-
} finally {
|
|
47
38
|
await _promises2.default.rm(tmpDir, { recursive: true, force: true });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (!options.preserveTmpDir) {
|
|
41
|
+
await _promises2.default.rm(_nullishCoalesce(createdParentDir, () => ( tmpDir)), { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
throw e;
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
async function linkOrCp(src, dest) {
|
|
47
|
+
await _promises2.default.rm(dest, { recursive: true, force: true });
|
|
51
48
|
try {
|
|
52
49
|
await _promises2.default.link(src, dest);
|
|
53
50
|
} catch (e3) {
|
|
54
51
|
await _promises2.default.copyFile(src, dest);
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
|
-
async function sleep(ms) {
|
|
58
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
-
}
|
|
60
|
-
async function withTimeout(promise, timeout) {
|
|
61
|
-
let timer;
|
|
62
|
-
const result = Promise.race([
|
|
63
|
-
promise,
|
|
64
|
-
new Promise((resolve, reject) => timer = setTimeout(() => reject(Error("Promise timed out")), timeout))
|
|
65
|
-
]);
|
|
66
|
-
return result.finally(() => clearTimeout(timer));
|
|
67
|
-
}
|
|
68
54
|
async function pool(size, items, func) {
|
|
69
55
|
const { results } = await _promisepool.PromisePool.for(items).withConcurrency(size).handleError(async (error) => {
|
|
70
56
|
throw error;
|
|
@@ -74,23 +60,58 @@ async function pool(size, items, func) {
|
|
|
74
60
|
async function maybe(promise) {
|
|
75
61
|
return promise.then((r) => ({ success: true, result: r, error: void 0 })).catch((e) => ({ success: false, result: void 0, error: e }));
|
|
76
62
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
function watchFiles(files, func, options) {
|
|
64
|
+
const debouncedFunc = _lodash2.default.debounce((curr, prev) => {
|
|
65
|
+
if (curr.mtimeMs > prev.mtimeMs || curr.mtimeMs == 0 && prev.mtimeMs != 0) {
|
|
66
|
+
func(curr, prev);
|
|
67
|
+
}
|
|
68
|
+
}, options.debounce);
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
_fs2.default.watchFile(file, { interval: options.interval, persistent: options.persistent }, debouncedFunc);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeObject(canonical, obj) {
|
|
74
|
+
const rootCanonical = canonical, rootObj = obj;
|
|
75
|
+
function helper(canonical2, obj2) {
|
|
76
|
+
if (_lodash2.default.isPlainObject(canonical2)) {
|
|
77
|
+
if (_lodash2.default.isPlainObject(obj2)) {
|
|
78
|
+
obj2 = _lodash2.default.pick(obj2, Object.keys(canonical2));
|
|
79
|
+
obj2 = _lodash2.default.mapValues(obj2, (v, k) => helper(canonical2[k], v));
|
|
80
|
+
obj2 = _lodash2.default.omitBy(obj2, (v) => v === void 0);
|
|
81
|
+
return obj2;
|
|
82
|
+
} else {
|
|
83
|
+
return obj2;
|
|
84
84
|
}
|
|
85
|
+
} else if (canonical2 === null) {
|
|
86
|
+
return obj2;
|
|
87
|
+
} else {
|
|
88
|
+
throw Error(`Invalid canonical form ${JSON.stringify(rootCanonical)}`);
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
}
|
|
91
|
+
return helper(rootCanonical, rootObj);
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
// src/
|
|
94
|
+
// src/launcher.ts
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
var _crypto = require('crypto'); var _crypto2 = _interopRequireDefault(_crypto);
|
|
98
|
+
var _extractzip = require('extract-zip'); var _extractzip2 = _interopRequireDefault(_extractzip);
|
|
99
|
+
var _get = require('@electron/get');
|
|
100
|
+
var _child_process = require('child_process'); var _child_process2 = _interopRequireDefault(_child_process);
|
|
101
|
+
var _semver = require('semver'); var _semver2 = _interopRequireDefault(_semver);
|
|
102
|
+
var _url = require('url');
|
|
103
|
+
var _dotenv = require('dotenv'); var _dotenv2 = _interopRequireDefault(_dotenv);
|
|
104
|
+
|
|
105
|
+
// src/types.ts
|
|
106
|
+
var obsidianVersionsSchemaVersion = "2.0.0";
|
|
90
107
|
|
|
108
|
+
// src/apis.ts
|
|
91
109
|
|
|
92
110
|
|
|
111
|
+
var _promises3 = require('stream/promises');
|
|
93
112
|
var _stream = require('stream');
|
|
113
|
+
|
|
114
|
+
var _readlinesync = require('readline-sync'); var _readlinesync2 = _interopRequireDefault(_readlinesync);
|
|
94
115
|
function parseLinkHeader(linkHeader) {
|
|
95
116
|
function parseLinkData(linkData) {
|
|
96
117
|
return Object.fromEntries(
|
|
@@ -130,7 +151,7 @@ async function fetchGitHubAPI(url, params = {}) {
|
|
|
130
151
|
if (!response.ok) {
|
|
131
152
|
throw new Error(`GitHub API error: ${await response.text()}`);
|
|
132
153
|
}
|
|
133
|
-
return
|
|
154
|
+
return response;
|
|
134
155
|
}
|
|
135
156
|
async function fetchGitHubAPIPaginated(url, params = {}) {
|
|
136
157
|
const results = [];
|
|
@@ -142,24 +163,87 @@ async function fetchGitHubAPIPaginated(url, params = {}) {
|
|
|
142
163
|
}
|
|
143
164
|
return results;
|
|
144
165
|
}
|
|
145
|
-
async function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
166
|
+
async function obsidianApiLogin(opts) {
|
|
167
|
+
const { interactive = false, savePath } = opts;
|
|
168
|
+
let email = process.env.OBSIDIAN_EMAIL;
|
|
169
|
+
let password = process.env.OBSIDIAN_PASSWORD;
|
|
170
|
+
let mfa;
|
|
171
|
+
if (!email || !password) {
|
|
172
|
+
if (interactive) {
|
|
173
|
+
console.log("Obsidian Insiders account is required to download Obsidian beta versions.");
|
|
174
|
+
email = email || _readlinesync2.default.question("Obsidian email: ");
|
|
175
|
+
password = password || _readlinesync2.default.question("Obsidian password: ", { hideEchoBack: true });
|
|
176
|
+
} else {
|
|
177
|
+
throw Error(
|
|
178
|
+
"Obsidian Insiders account is required to download Obsidian beta versions. Either set the OBSIDIAN_EMAIL and OBSIDIAN_PASSWORD env vars (.env is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download -v <version>`"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
151
181
|
}
|
|
182
|
+
const headers = {
|
|
183
|
+
"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",
|
|
184
|
+
"Origin": "app://obsidian.md",
|
|
185
|
+
"Content-Type": "application/json"
|
|
186
|
+
};
|
|
187
|
+
let signin = await fetch("https://api.obsidian.md/user/signin", {
|
|
188
|
+
method: "post",
|
|
189
|
+
headers,
|
|
190
|
+
body: JSON.stringify({ email, password, mfa: "" })
|
|
191
|
+
}).then((r) => r.json());
|
|
192
|
+
if (signin.error && signin.error.includes("2FA")) {
|
|
193
|
+
if (interactive) {
|
|
194
|
+
mfa = await _readlinesync2.default.question("Obsidian 2FA: ");
|
|
195
|
+
signin = await fetch("https://api.obsidian.md/user/signin", {
|
|
196
|
+
method: "post",
|
|
197
|
+
headers,
|
|
198
|
+
body: JSON.stringify({ email, password, mfa })
|
|
199
|
+
}).then((r) => r.json());
|
|
200
|
+
} else {
|
|
201
|
+
throw Error(
|
|
202
|
+
"Can't login with 2FA in a non-interactive session. To download Obsidian beta versions, either disable 2FA on your account or pre-download the Obsidian beta with `npx obsidian-launcher download -v <version>`"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
let error = void 0;
|
|
207
|
+
if (!signin.token) {
|
|
208
|
+
error = Error(`Obsidian login failed: ${signin.error}`);
|
|
209
|
+
}
|
|
210
|
+
if (!error && !signin.license) {
|
|
211
|
+
error = Error("Obsidian Insiders account is required to download Obsidian beta versions");
|
|
212
|
+
}
|
|
213
|
+
if (error) {
|
|
214
|
+
if (savePath) {
|
|
215
|
+
await _promises2.default.rm(savePath, { force: true });
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
if (interactive && savePath && (!process.env.OBSIDIAN_EMAIL || !process.env.OBSIDIAN_PASSWORD)) {
|
|
220
|
+
const save = _readlinesync2.default.question("Save credentails to disk? [y/n]: ");
|
|
221
|
+
if (["y", "yes"].includes(save.toLocaleLowerCase())) {
|
|
222
|
+
await _promises2.default.writeFile(
|
|
223
|
+
savePath,
|
|
224
|
+
`OBSIDIAN_EMAIL='${email}'
|
|
225
|
+
OBSIDIAN_PASSWORD='${password}'
|
|
226
|
+
`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return signin.token;
|
|
231
|
+
}
|
|
232
|
+
async function fetchObsidianApi(url, opts) {
|
|
233
|
+
url = createURL(url, "https://releases.obsidian.md");
|
|
152
234
|
const response = await fetch(url, {
|
|
153
235
|
headers: {
|
|
154
|
-
// For some reason you have to set the User-Agent or it won't let you download
|
|
155
236
|
"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",
|
|
156
237
|
"Origin": "app://obsidian.md",
|
|
157
|
-
"Authorization": "
|
|
238
|
+
"Authorization": "Bearer " + opts.token
|
|
158
239
|
}
|
|
159
240
|
});
|
|
160
241
|
return response;
|
|
161
242
|
}
|
|
162
243
|
async function downloadResponse(response, dest) {
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw Error(`${response.url} failed with ${response.status}`);
|
|
246
|
+
}
|
|
163
247
|
const fileStream = _fs2.default.createWriteStream(dest, { flags: "w" });
|
|
164
248
|
const fetchStream = _stream.Readable.fromWeb(response.body);
|
|
165
249
|
await _promises3.finished.call(void 0, fetchStream.pipe(fileStream));
|
|
@@ -173,7 +257,10 @@ var ChromeLocalStorage = class {
|
|
|
173
257
|
constructor(userDataDir) {
|
|
174
258
|
this.userDataDir = userDataDir;
|
|
175
259
|
this.encodeKey = (domain, key) => `_${domain}\0${key}`;
|
|
176
|
-
this.decodeKey = (key) =>
|
|
260
|
+
this.decodeKey = (key) => {
|
|
261
|
+
const parts = key.slice(1).split("\0");
|
|
262
|
+
return [parts[0], parts.slice(1).join("\0")];
|
|
263
|
+
};
|
|
177
264
|
this.encodeValue = (value) => `${value}`;
|
|
178
265
|
this.decodeValue = (value) => value.slice(1);
|
|
179
266
|
this.db = new (0, _classiclevel.ClassicLevel)(_path2.default.join(userDataDir, "Local Storage/leveldb/"));
|
|
@@ -241,231 +328,325 @@ var ChromeLocalStorage = class {
|
|
|
241
328
|
// src/launcherUtils.ts
|
|
242
329
|
|
|
243
330
|
|
|
244
|
-
var _util = require('util');
|
|
245
331
|
|
|
246
|
-
var _which = require('which'); var _which2 = _interopRequireDefault(_which);
|
|
247
332
|
|
|
248
333
|
|
|
249
|
-
|
|
250
|
-
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
var _zlib = require('zlib'); var _zlib2 = _interopRequireDefault(_zlib);
|
|
337
|
+
|
|
251
338
|
function normalizeGitHubRepo(repo) {
|
|
252
|
-
return repo.
|
|
339
|
+
return _nullishCoalesce(_optionalChain([repo, 'access', _8 => _8.match, 'call', _9 => _9(/^(https?:\/\/)?(github.com\/)?(.*?)\/?$/), 'optionalAccess', _10 => _10[3]]), () => ( repo));
|
|
340
|
+
}
|
|
341
|
+
async function extractGz(archive, dest) {
|
|
342
|
+
await _promises3.pipeline.call(void 0, _fs2.default.createReadStream(archive), _zlib2.default.createGunzip(), _fs2.default.createWriteStream(dest));
|
|
343
|
+
}
|
|
344
|
+
async function sevenZ(args, options) {
|
|
345
|
+
const sevenZipScript = _path2.default.resolve(_url.fileURLToPath.call(void 0, _chunkQQJCBP4Ncjs.importMetaUrl), "../7z.js");
|
|
346
|
+
const proc = _child_process2.default.spawn(process.execPath, [sevenZipScript, ...args], {
|
|
347
|
+
stdio: "pipe",
|
|
348
|
+
...options
|
|
349
|
+
});
|
|
350
|
+
let stdout = "", stderr = "";
|
|
351
|
+
proc.stdout.on("data", (data) => stdout += data);
|
|
352
|
+
proc.stderr.on("data", (data) => stderr += data);
|
|
353
|
+
const procExit = new Promise((resolve) => proc.on("close", (code) => resolve(_nullishCoalesce(code, () => ( -1)))));
|
|
354
|
+
const exitCode = await procExit;
|
|
355
|
+
const result = { stdout, stderr };
|
|
356
|
+
if (exitCode != 0) {
|
|
357
|
+
throw Error(`"7z ${args.join(" ")}" failed with ${exitCode}:
|
|
358
|
+
${stdout}
|
|
359
|
+
${stderr}`);
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
253
362
|
}
|
|
254
363
|
async function extractObsidianAppImage(appImage, dest) {
|
|
255
|
-
await
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
return _path2.default.join(tmpDir, "squashfs-root");
|
|
364
|
+
await atomicCreate(dest, async (tmpDir) => {
|
|
365
|
+
await sevenZ(["x", "-o.", _path2.default.relative(tmpDir, appImage)], { cwd: tmpDir });
|
|
366
|
+
return tmpDir;
|
|
259
367
|
});
|
|
260
368
|
}
|
|
261
|
-
async function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
369
|
+
async function extractObsidianTar(tar, dest) {
|
|
370
|
+
await atomicCreate(dest, async (tmpDir) => {
|
|
371
|
+
await extractGz(tar, _path2.default.join(tmpDir, "inflated.tar"));
|
|
372
|
+
await sevenZ(["x", "-o.", "inflated.tar"], { cwd: tmpDir });
|
|
373
|
+
return (await _promises2.default.readdir(tmpDir)).find((p) => p.match("obsidian-"));
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async function extractObsidianExe(exe, arch, dest) {
|
|
377
|
+
let subArchive;
|
|
378
|
+
if (arch == "x64") {
|
|
379
|
+
subArchive = `$PLUGINSDIR/app-64.7z`;
|
|
380
|
+
} else if (arch == "ia32") {
|
|
381
|
+
subArchive = `$PLUGINSDIR/app-32.7z`;
|
|
382
|
+
} else if (arch == "arm64") {
|
|
383
|
+
subArchive = `$PLUGINSDIR/app-arm64.7z`;
|
|
384
|
+
} else {
|
|
385
|
+
throw Error(`No Obsidian installer found for ${process.platform} ${process.arch}`);
|
|
267
386
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const extractedInstaller = _path2.default.join(tmpDir, "installer");
|
|
273
|
-
await execFile(path7z, ["x", "-o" + extractedInstaller, exe, subArchive]);
|
|
274
|
-
const extractedObsidian = _path2.default.join(tmpDir, "obsidian");
|
|
275
|
-
await execFile(path7z, ["x", "-o" + extractedObsidian, _path2.default.join(extractedInstaller, subArchive)]);
|
|
276
|
-
return extractedObsidian;
|
|
387
|
+
await atomicCreate(dest, async (tmpDir) => {
|
|
388
|
+
await sevenZ(["x", "-oinstaller", _path2.default.relative(tmpDir, exe), subArchive], { cwd: tmpDir });
|
|
389
|
+
await sevenZ(["x", "-oobsidian", _path2.default.join("installer", subArchive)], { cwd: tmpDir });
|
|
390
|
+
return "obsidian";
|
|
277
391
|
});
|
|
278
392
|
}
|
|
279
393
|
async function extractObsidianDmg(dmg, dest) {
|
|
280
394
|
dest = _path2.default.resolve(dest);
|
|
281
|
-
await
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
await execFile("hdiutil", ["detach", volume]);
|
|
395
|
+
await atomicCreate(dest, async (tmpDir) => {
|
|
396
|
+
await sevenZ(["x", "-o.", _path2.default.relative(tmpDir, dmg), "*/Obsidian.app", "Obsidian.app"], { cwd: tmpDir });
|
|
397
|
+
const files = await _promises2.default.readdir(tmpDir);
|
|
398
|
+
if (files.includes("Obsidian.app")) {
|
|
399
|
+
return "Obsidian.app";
|
|
400
|
+
} else {
|
|
401
|
+
return _path2.default.join(files[0], "Obsidian.app");
|
|
289
402
|
}
|
|
290
|
-
return tmpDir;
|
|
291
403
|
});
|
|
292
404
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
405
|
+
var BROKEN_ASSETS = [
|
|
406
|
+
"https://releases.obsidian.md/release/obsidian-0.12.16.asar.gz",
|
|
407
|
+
"https://github.com/obsidianmd/obsidian-releases/releases/download/v0.12.16/obsidian-0.12.16.asar.gz",
|
|
408
|
+
"https://releases.obsidian.md/release/obsidian-1.4.7.asar.gz",
|
|
409
|
+
"https://releases.obsidian.md/release/obsidian-1.4.8.asar.gz"
|
|
410
|
+
];
|
|
411
|
+
function parseObsidianDesktopRelease(fileRelease) {
|
|
412
|
+
const parse = (r, isBeta) => {
|
|
413
|
+
const version = r.latestVersion;
|
|
414
|
+
let minInstallerVersion = r.minimumVersion;
|
|
415
|
+
if (minInstallerVersion == "0.0.0") {
|
|
416
|
+
minInstallerVersion = void 0;
|
|
417
|
+
} else if (_semver2.default.satisfies(version, ">=1.3.0 <=1.3.4")) {
|
|
418
|
+
minInstallerVersion = "0.14.5";
|
|
419
|
+
} else if (_semver2.default.gte(version, "1.5.3") && _semver2.default.lt(minInstallerVersion, "1.1.9")) {
|
|
420
|
+
minInstallerVersion = "1.1.9";
|
|
300
421
|
}
|
|
422
|
+
return {
|
|
423
|
+
version: r.latestVersion,
|
|
424
|
+
minInstallerVersion,
|
|
425
|
+
isBeta,
|
|
426
|
+
downloads: {
|
|
427
|
+
asar: BROKEN_ASSETS.includes(r.downloadUrl) ? void 0 : r.downloadUrl
|
|
428
|
+
}
|
|
429
|
+
};
|
|
301
430
|
};
|
|
431
|
+
const result = { current: parse(fileRelease, false) };
|
|
432
|
+
if (fileRelease.beta && fileRelease.beta.latestVersion !== fileRelease.latestVersion) {
|
|
433
|
+
result.beta = parse(fileRelease.beta, true);
|
|
434
|
+
}
|
|
435
|
+
return result;
|
|
302
436
|
}
|
|
303
437
|
function parseObsidianGithubRelease(gitHubRelease) {
|
|
304
438
|
const version = gitHubRelease.name;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
};
|
|
439
|
+
let assets = gitHubRelease.assets.map((a) => ({
|
|
440
|
+
url: a.browser_download_url,
|
|
441
|
+
digest: _nullishCoalesce(a.digest, () => ( `id:${a.id}`))
|
|
442
|
+
}));
|
|
443
|
+
assets = assets.filter((a) => !BROKEN_ASSETS.includes(a.url));
|
|
444
|
+
const asar = assets.find((a) => a.url.match(`${version}.asar.gz$`));
|
|
445
|
+
const appImage = assets.find((a) => a.url.match(`${version}.AppImage$`));
|
|
446
|
+
const appImageArm = assets.find((a) => a.url.match(`${version}-arm64.AppImage$`));
|
|
447
|
+
const tar = assets.find((a) => a.url.match(`${version}.tar.gz$`));
|
|
448
|
+
const tarArm = assets.find((a) => a.url.match(`${version}-arm64.tar.gz$`));
|
|
449
|
+
const dmg = assets.find((a) => a.url.match(`${version}(-universal)?.dmg$`));
|
|
450
|
+
const exe = assets.find((a) => a.url.match(`${version}.exe$`));
|
|
451
|
+
const apk = assets.find((a) => a.url.match(`${version}.apk$`));
|
|
314
452
|
return {
|
|
315
453
|
version,
|
|
316
454
|
gitHubRelease: gitHubRelease.html_url,
|
|
317
|
-
downloads
|
|
455
|
+
downloads: {
|
|
456
|
+
asar: _optionalChain([asar, 'optionalAccess', _11 => _11.url]),
|
|
457
|
+
appImage: _optionalChain([appImage, 'optionalAccess', _12 => _12.url]),
|
|
458
|
+
appImageArm: _optionalChain([appImageArm, 'optionalAccess', _13 => _13.url]),
|
|
459
|
+
tar: _optionalChain([tar, 'optionalAccess', _14 => _14.url]),
|
|
460
|
+
tarArm: _optionalChain([tarArm, 'optionalAccess', _15 => _15.url]),
|
|
461
|
+
dmg: _optionalChain([dmg, 'optionalAccess', _16 => _16.url]),
|
|
462
|
+
exe: _optionalChain([exe, 'optionalAccess', _17 => _17.url]),
|
|
463
|
+
apk: _optionalChain([apk, 'optionalAccess', _18 => _18.url])
|
|
464
|
+
},
|
|
465
|
+
installers: {
|
|
466
|
+
appImage: appImage ? { digest: appImage.digest } : void 0,
|
|
467
|
+
appImageArm: appImageArm ? { digest: appImageArm.digest } : void 0,
|
|
468
|
+
tar: tar ? { digest: tar.digest } : void 0,
|
|
469
|
+
tarArm: tarArm ? { digest: tarArm.digest } : void 0,
|
|
470
|
+
dmg: dmg ? { digest: dmg.digest } : void 0,
|
|
471
|
+
exe: exe ? { digest: exe.digest } : void 0
|
|
472
|
+
}
|
|
318
473
|
};
|
|
319
474
|
}
|
|
320
|
-
async function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
`--remote-debugging-port=0`,
|
|
325
|
-
// 0 will make it choose a random available port
|
|
326
|
-
"--test-type=webdriver",
|
|
327
|
-
`--user-data-dir=${configDir}`,
|
|
328
|
-
"--no-sandbox"
|
|
329
|
-
// Workaround for SUID issue, see https://github.com/electron/electron/issues/42510
|
|
330
|
-
]);
|
|
331
|
-
const procExit = new Promise((resolve) => proc.on("exit", (code) => resolve(_nullishCoalesce(code, () => ( -1)))));
|
|
332
|
-
let dependencyVersions;
|
|
475
|
+
async function getInstallerInfo(installerKey, url) {
|
|
476
|
+
const installerName = url.split("/").at(-1);
|
|
477
|
+
console.log(`Extrating installer info for ${installerName}...`);
|
|
478
|
+
const tmpDir = await makeTmpDir("obsidian-launcher-");
|
|
333
479
|
try {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
480
|
+
const installerPath = _path2.default.join(tmpDir, url.split("/").at(-1));
|
|
481
|
+
await downloadResponse(await fetch(url), installerPath);
|
|
482
|
+
const exractedPath = _path2.default.join(tmpDir, "Obsidian");
|
|
483
|
+
let platforms = [];
|
|
484
|
+
if (installerKey == "appImage" || installerKey == "appImageArm") {
|
|
485
|
+
await extractObsidianAppImage(installerPath, exractedPath);
|
|
486
|
+
platforms = ["linux-" + (installerKey == "appImage" ? "x64" : "arm64")];
|
|
487
|
+
} else if (installerKey == "tar" || installerKey == "tarArm") {
|
|
488
|
+
await extractObsidianTar(installerPath, exractedPath);
|
|
489
|
+
platforms = ["linux-" + (installerKey == "tar" ? "x64" : "arm64")];
|
|
490
|
+
} else if (installerKey == "exe") {
|
|
491
|
+
await extractObsidianExe(installerPath, "x64", exractedPath);
|
|
492
|
+
const { stdout } = await sevenZ(["l", "-ba", _path2.default.relative(tmpDir, installerPath)], { cwd: tmpDir });
|
|
493
|
+
const lines = stdout.trim().split("\n").map((l) => l.trim());
|
|
494
|
+
const files = lines.map((l) => l.split(/\s+/).at(-1).replace(/\\/g, "/"));
|
|
495
|
+
if (files.includes("$PLUGINSDIR/app-arm64.7z")) platforms.push("win32-arm64");
|
|
496
|
+
if (files.includes("$PLUGINSDIR/app-32.7z")) platforms.push("win32-ia32");
|
|
497
|
+
if (files.includes("$PLUGINSDIR/app-64.7z")) platforms.push("win32-x64");
|
|
498
|
+
} else if (installerKey == "dmg") {
|
|
499
|
+
await extractObsidianDmg(installerPath, exractedPath);
|
|
500
|
+
platforms = ["darwin-arm64", "darwin-x64"];
|
|
501
|
+
} else {
|
|
502
|
+
throw new Error(`Unknown installer key ${installerKey}`);
|
|
503
|
+
}
|
|
504
|
+
const matches = [];
|
|
505
|
+
const installerFiles = await _promises2.default.readdir(exractedPath, { recursive: true, withFileTypes: true });
|
|
506
|
+
for (const file of installerFiles) {
|
|
507
|
+
if (file.isFile() && !file.name.endsWith(".asar")) {
|
|
508
|
+
const stream = _fs2.default.createReadStream(_path2.default.join(file.parentPath, file.name), { encoding: "utf-8" });
|
|
509
|
+
let prev = "";
|
|
510
|
+
for await (let chunk of stream) {
|
|
511
|
+
const regex = /Chrome\/\d+\.\d+\.\d+\.\d+|Electron\/\d+\.\d+\.\d+/g;
|
|
512
|
+
chunk = prev + chunk;
|
|
513
|
+
matches.push(...[...(prev + chunk).matchAll(regex)].map((m) => m[0]));
|
|
514
|
+
prev = chunk.slice(-64);
|
|
340
515
|
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
const port = await maybe(withTimeout(portPromise, 10 * 1e3));
|
|
344
|
-
if (!port.success) {
|
|
345
|
-
throw new Error("Timed out waiting for Chrome DevTools protocol port");
|
|
516
|
+
}
|
|
346
517
|
}
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const timeout = await maybe(withTimeout(procExit, 4 * 1e3));
|
|
354
|
-
if (!timeout.success) {
|
|
355
|
-
console.log(`${version}: Stuck process ${proc.pid}, using SIGKILL`);
|
|
356
|
-
proc.kill("SIGKILL");
|
|
518
|
+
const versionSortKey = (v) => v.split(".").map((s) => s.padStart(9, "0")).join(".");
|
|
519
|
+
const versions = _lodash2.default.call(void 0, matches).map((m) => m.split("/")).groupBy(0).mapValues((ms) => ms.map((m) => m[1])).mapValues((ms) => _lodash2.default.sortBy(ms, versionSortKey).at(-1)).value();
|
|
520
|
+
const electron = versions["Electron"];
|
|
521
|
+
const chrome = versions["Chrome"];
|
|
522
|
+
if (!electron || !chrome) {
|
|
523
|
+
throw new Error(`Failed to extract Electron and Chrome versions from binary ${installerPath}`);
|
|
357
524
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (!_optionalChain([dependencyVersions, 'optionalAccess', _13 => _13.electron]) || !_optionalChain([dependencyVersions, 'optionalAccess', _14 => _14.chrome])) {
|
|
363
|
-
throw Error(`Failed to extract electron and chrome versions for ${version}`);
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
electronVersion: dependencyVersions.electron,
|
|
367
|
-
chromeVersion: dependencyVersions.chrome,
|
|
368
|
-
nodeVersion: dependencyVersions.node
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
function correctObsidianVersionInfo(versionInfo) {
|
|
372
|
-
const corrections = [
|
|
373
|
-
// These version's downloads are missing or broken.
|
|
374
|
-
{ version: "0.12.16", downloads: { asar: void 0 } },
|
|
375
|
-
{ version: "1.4.7", downloads: { asar: void 0 } },
|
|
376
|
-
{ version: "1.4.8", downloads: { asar: void 0 } },
|
|
377
|
-
// The minInstallerVersion here is incorrect
|
|
378
|
-
{ version: "1.3.4", minInstallerVersion: "0.14.5" },
|
|
379
|
-
{ version: "1.3.3", minInstallerVersion: "0.14.5" },
|
|
380
|
-
{ version: "1.3.2", minInstallerVersion: "0.14.5" },
|
|
381
|
-
{ version: "1.3.1", minInstallerVersion: "0.14.5" },
|
|
382
|
-
{ version: "1.3.0", minInstallerVersion: "0.14.5" }
|
|
383
|
-
];
|
|
384
|
-
const result = _nullishCoalesce(corrections.find((v) => v.version == versionInfo.version), () => ( {}));
|
|
385
|
-
if (_semver2.default.gte(versionInfo.version, "1.5.3") && _semver2.default.lt(versionInfo.minInstallerVersion, "1.1.9")) {
|
|
386
|
-
result.minInstallerVersion = "1.1.9";
|
|
525
|
+
console.log(`Extracted installer info for ${installerName}`);
|
|
526
|
+
return { electron, chrome, platforms };
|
|
527
|
+
} finally {
|
|
528
|
+
await _promises2.default.rm(tmpDir, { recursive: true, force: true });
|
|
387
529
|
}
|
|
388
|
-
return result;
|
|
389
530
|
}
|
|
390
531
|
function normalizeObsidianVersionInfo(versionInfo) {
|
|
391
532
|
versionInfo = {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
533
|
+
...versionInfo,
|
|
534
|
+
// kept for backwards compatibility
|
|
535
|
+
electronVersion: _optionalChain([versionInfo, 'access', _19 => _19.installers, 'optionalAccess', _20 => _20.appImage, 'optionalAccess', _21 => _21.electron]),
|
|
536
|
+
chromeVersion: _optionalChain([versionInfo, 'access', _22 => _22.installers, 'optionalAccess', _23 => _23.appImage, 'optionalAccess', _24 => _24.chrome])
|
|
537
|
+
};
|
|
538
|
+
const canonicalForm = {
|
|
539
|
+
version: null,
|
|
540
|
+
minInstallerVersion: null,
|
|
541
|
+
maxInstallerVersion: null,
|
|
542
|
+
isBeta: null,
|
|
543
|
+
gitHubRelease: null,
|
|
397
544
|
downloads: {
|
|
398
|
-
asar:
|
|
399
|
-
appImage:
|
|
400
|
-
appImageArm:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
545
|
+
asar: null,
|
|
546
|
+
appImage: null,
|
|
547
|
+
appImageArm: null,
|
|
548
|
+
tar: null,
|
|
549
|
+
tarArm: null,
|
|
550
|
+
dmg: null,
|
|
551
|
+
exe: null,
|
|
552
|
+
apk: null
|
|
553
|
+
},
|
|
554
|
+
installers: {
|
|
555
|
+
appImage: { digest: null, electron: null, chrome: null, platforms: null },
|
|
556
|
+
appImageArm: { digest: null, electron: null, chrome: null, platforms: null },
|
|
557
|
+
tar: { digest: null, electron: null, chrome: null, platforms: null },
|
|
558
|
+
tarArm: { digest: null, electron: null, chrome: null, platforms: null },
|
|
559
|
+
dmg: { digest: null, electron: null, chrome: null, platforms: null },
|
|
560
|
+
exe: { digest: null, electron: null, chrome: null, platforms: null }
|
|
404
561
|
},
|
|
405
|
-
electronVersion:
|
|
406
|
-
chromeVersion:
|
|
407
|
-
nodeVersion: versionInfo.nodeVersion
|
|
562
|
+
electronVersion: null,
|
|
563
|
+
chromeVersion: null
|
|
408
564
|
};
|
|
409
|
-
|
|
410
|
-
versionInfo = _lodash2.default.omitBy(versionInfo, _lodash2.default.isUndefined);
|
|
411
|
-
return versionInfo;
|
|
565
|
+
return normalizeObject(canonicalForm, versionInfo);
|
|
412
566
|
}
|
|
413
567
|
|
|
414
568
|
// src/launcher.ts
|
|
415
569
|
|
|
570
|
+
var currentPlatform = {
|
|
571
|
+
platform: process.platform,
|
|
572
|
+
arch: process.arch
|
|
573
|
+
};
|
|
416
574
|
var ObsidianLauncher = class {
|
|
417
575
|
/**
|
|
418
576
|
* Construct an ObsidianLauncher.
|
|
419
|
-
* @param
|
|
420
|
-
* @param
|
|
421
|
-
* @param
|
|
422
|
-
* @param
|
|
423
|
-
* @param
|
|
577
|
+
* @param opts.cacheDir Path to the cache directory. Defaults to "OBSIDIAN_CACHE" env var or ".obsidian-cache".
|
|
578
|
+
* @param opts.versionsUrl Custom `obsidian-versions.json` url. Can be a file URL.
|
|
579
|
+
* @param opts.communityPluginsUrl Custom `community-plugins.json` url. Can be a file URL.
|
|
580
|
+
* @param opts.communityThemesUrl Custom `community-css-themes.json` url. Can be a file URL.
|
|
581
|
+
* @param opts.cacheDuration If the cached version list is older than this (in ms), refetch it. Defaults to 30 minutes.
|
|
582
|
+
* @param opts.interactive If it can prompt the user for input (e.g. for Obsidian credentials). Default false.
|
|
424
583
|
*/
|
|
425
|
-
constructor(
|
|
426
|
-
this.
|
|
584
|
+
constructor(opts = {}) {
|
|
585
|
+
this.interactive = false;
|
|
586
|
+
this.cacheDir = _path2.default.resolve(_nullishCoalesce(_nullishCoalesce(opts.cacheDir, () => ( process.env.OBSIDIAN_CACHE)), () => ( "./.obsidian-cache")));
|
|
427
587
|
const defaultVersionsUrl = "https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json";
|
|
428
|
-
this.versionsUrl = _nullishCoalesce(
|
|
588
|
+
this.versionsUrl = _nullishCoalesce(opts.versionsUrl, () => ( defaultVersionsUrl));
|
|
429
589
|
const defaultCommunityPluginsUrl = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json";
|
|
430
|
-
this.communityPluginsUrl = _nullishCoalesce(
|
|
590
|
+
this.communityPluginsUrl = _nullishCoalesce(opts.communityPluginsUrl, () => ( defaultCommunityPluginsUrl));
|
|
431
591
|
const defaultCommunityThemesUrl = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json";
|
|
432
|
-
this.communityThemesUrl = _nullishCoalesce(
|
|
433
|
-
this.cacheDuration = _nullishCoalesce(
|
|
592
|
+
this.communityThemesUrl = _nullishCoalesce(opts.communityThemesUrl, () => ( defaultCommunityThemesUrl));
|
|
593
|
+
this.cacheDuration = _nullishCoalesce(opts.cacheDuration, () => ( 30 * 60 * 1e3));
|
|
594
|
+
this.interactive = _nullishCoalesce(opts.interactive, () => ( false));
|
|
434
595
|
this.metadataCache = {};
|
|
596
|
+
_dotenv2.default.config({
|
|
597
|
+
path: [".env", _path2.default.join(this.cacheDir, "obsidian-credentials.env")],
|
|
598
|
+
quiet: true
|
|
599
|
+
});
|
|
435
600
|
}
|
|
436
601
|
/**
|
|
437
602
|
* Returns file content fetched from url as JSON. Caches content to dest and uses that cache if its more recent than
|
|
438
603
|
* cacheDuration ms or if there are network errors.
|
|
439
604
|
*/
|
|
440
|
-
async cachedFetch(url, dest) {
|
|
605
|
+
async cachedFetch(url, dest, cacheValid) {
|
|
606
|
+
cacheValid = _nullishCoalesce(cacheValid, () => ( (() => true)));
|
|
441
607
|
dest = _path2.default.join(this.cacheDir, dest);
|
|
442
608
|
if (!(dest in this.metadataCache)) {
|
|
443
|
-
let
|
|
609
|
+
let data;
|
|
610
|
+
let error;
|
|
611
|
+
const cacheMtime = await _asyncOptionalChain([(await _promises2.default.stat(dest).catch(() => void 0)), 'optionalAccess', async _25 => _25.mtime]);
|
|
444
612
|
if (url.startsWith("file:")) {
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
613
|
+
data = JSON.parse(await _promises2.default.readFile(_url.fileURLToPath.call(void 0, url), "utf-8"));
|
|
614
|
+
}
|
|
615
|
+
if (!data && cacheMtime && (/* @__PURE__ */ new Date()).getTime() - cacheMtime.getTime() < this.cacheDuration) {
|
|
616
|
+
const parsed = JSON.parse(await _promises2.default.readFile(dest, "utf-8"));
|
|
617
|
+
if (cacheValid(parsed)) {
|
|
618
|
+
data = parsed;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (!data) {
|
|
622
|
+
const response = await maybe(fetch(url).then(async (r) => {
|
|
623
|
+
if (!r.ok) throw Error(`Fetch ${url} failed with status ${r.status}`);
|
|
624
|
+
const d = await r.text();
|
|
625
|
+
if (_lodash2.default.isError(_lodash2.default.attempt(JSON.parse, d))) throw Error(`Failed to parse response from ${url}`);
|
|
626
|
+
return d;
|
|
627
|
+
}));
|
|
628
|
+
if (response.success) {
|
|
629
|
+
await atomicCreate(dest, async (tmpDir) => {
|
|
630
|
+
await _promises2.default.writeFile(_path2.default.join(tmpDir, "download.json"), response.result);
|
|
631
|
+
return _path2.default.join(tmpDir, "download.json");
|
|
632
|
+
});
|
|
633
|
+
data = JSON.parse(response.result);
|
|
450
634
|
} else {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
console.warn(request.error);
|
|
461
|
-
console.warn(`Unable to download ${dest}, using cached file.`);
|
|
462
|
-
fileContent = await _promises2.default.readFile(dest, "utf-8");
|
|
463
|
-
} else {
|
|
464
|
-
throw request.error;
|
|
465
|
-
}
|
|
635
|
+
error = response.error;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (!data && await fileExists(dest)) {
|
|
639
|
+
const parsed = JSON.parse(await _promises2.default.readFile(dest, "utf-8"));
|
|
640
|
+
if (cacheValid(parsed)) {
|
|
641
|
+
console.warn(error);
|
|
642
|
+
console.warn(`Unable to download ${url}, using cached file.`);
|
|
643
|
+
data = parsed;
|
|
466
644
|
}
|
|
467
645
|
}
|
|
468
|
-
|
|
646
|
+
if (!data) {
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
this.metadataCache[dest] = data;
|
|
469
650
|
}
|
|
470
651
|
return this.metadataCache[dest];
|
|
471
652
|
}
|
|
@@ -492,7 +673,12 @@ var ObsidianLauncher = class {
|
|
|
492
673
|
* Get information about all available Obsidian versions.
|
|
493
674
|
*/
|
|
494
675
|
async getVersions() {
|
|
495
|
-
|
|
676
|
+
const isValid = (d) => _semver2.default.satisfies(_nullishCoalesce(d.metadata.schemaVersion, () => ( "1.0.0")), `^${obsidianVersionsSchemaVersion}`);
|
|
677
|
+
const versions = await this.cachedFetch(this.versionsUrl, "obsidian-versions.json", isValid);
|
|
678
|
+
if (!isValid(versions)) {
|
|
679
|
+
throw new Error(`${this.versionsUrl} format has changed, please update obsidian-launcher and wdio-obsidian-service`);
|
|
680
|
+
}
|
|
681
|
+
return versions.versions;
|
|
496
682
|
}
|
|
497
683
|
/**
|
|
498
684
|
* Get information about all available community plugins.
|
|
@@ -508,11 +694,11 @@ var ObsidianLauncher = class {
|
|
|
508
694
|
}
|
|
509
695
|
/**
|
|
510
696
|
* Resolves Obsidian app and installer version strings to absolute versions.
|
|
511
|
-
* @param appVersion
|
|
697
|
+
* @param appVersion specific version or one of
|
|
512
698
|
* - "latest": Get the current latest non-beta Obsidian version
|
|
513
699
|
* - "latest-beta": Get the current latest beta Obsidian version (or latest is there is no current beta)
|
|
514
700
|
* - "earliest": Get the `minAppVersion` set in your `manifest.json`
|
|
515
|
-
* @param installerVersion
|
|
701
|
+
* @param installerVersion specific version or one of
|
|
516
702
|
* - "latest": Get the latest Obsidian installer compatible with `appVersion`
|
|
517
703
|
* - "earliest": Get the oldest Obsidian installer compatible with `appVersion`
|
|
518
704
|
*
|
|
@@ -520,22 +706,34 @@ var ObsidianLauncher = class {
|
|
|
520
706
|
*
|
|
521
707
|
* @returns [appVersion, installerVersion] with any "latest" etc. resolved to specific versions.
|
|
522
708
|
*/
|
|
523
|
-
async
|
|
709
|
+
async resolveVersion(appVersion, installerVersion = "latest") {
|
|
524
710
|
const versions = await this.getVersions();
|
|
525
711
|
const appVersionInfo = await this.getVersionInfo(appVersion);
|
|
712
|
+
appVersion = appVersionInfo.version;
|
|
713
|
+
let installerVersionInfo;
|
|
714
|
+
const { platform, arch } = process;
|
|
526
715
|
if (!appVersionInfo.minInstallerVersion || !appVersionInfo.maxInstallerVersion) {
|
|
527
|
-
throw Error(`No
|
|
716
|
+
throw Error(`No installers available for Obsidian ${appVersion}`);
|
|
528
717
|
}
|
|
529
718
|
if (installerVersion == "latest") {
|
|
530
|
-
|
|
719
|
+
installerVersionInfo = _lodash2.default.findLast(
|
|
720
|
+
versions,
|
|
721
|
+
(v) => _semver2.default.lte(v.version, appVersionInfo.version) && !!this.getInstallerKey(v, { platform, arch })
|
|
722
|
+
);
|
|
531
723
|
} else if (installerVersion == "earliest") {
|
|
532
|
-
|
|
724
|
+
installerVersionInfo = versions.find(
|
|
725
|
+
(v) => _semver2.default.gte(v.version, appVersionInfo.minInstallerVersion) && !!this.getInstallerKey(v, { platform, arch })
|
|
726
|
+
);
|
|
533
727
|
} else {
|
|
534
728
|
installerVersion = _nullishCoalesce(_semver2.default.valid(installerVersion), () => ( installerVersion));
|
|
729
|
+
installerVersionInfo = versions.find((v) => v.version == installerVersion);
|
|
535
730
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
731
|
+
if (!installerVersionInfo) {
|
|
732
|
+
if (["earliest", "latest"].includes(installerVersion)) {
|
|
733
|
+
throw Error(`No compatible installers available for Obsidian ${appVersion}`);
|
|
734
|
+
} else {
|
|
735
|
+
throw Error(`No Obsidian installer ${installerVersion} found`);
|
|
736
|
+
}
|
|
539
737
|
}
|
|
540
738
|
if (_semver2.default.lt(installerVersionInfo.version, appVersionInfo.minInstallerVersion) || _semver2.default.gt(installerVersionInfo.version, appVersionInfo.maxInstallerVersion)) {
|
|
541
739
|
throw Error(
|
|
@@ -555,124 +753,145 @@ var ObsidianLauncher = class {
|
|
|
555
753
|
} else if (appVersion == "latest") {
|
|
556
754
|
appVersion = versions.filter((v) => !v.isBeta).at(-1).version;
|
|
557
755
|
} else if (appVersion == "earliest") {
|
|
558
|
-
appVersion = await _asyncOptionalChain([(await this.getRootManifest()), 'optionalAccess', async
|
|
756
|
+
appVersion = await _asyncOptionalChain([(await this.getRootManifest()), 'optionalAccess', async _26 => _26.minAppVersion]);
|
|
559
757
|
if (!appVersion) {
|
|
560
|
-
throw Error('Unable to resolve Obsidian
|
|
758
|
+
throw Error('Unable to resolve Obsidian appVersion "earliest", no manifest.json or minAppVersion found.');
|
|
561
759
|
}
|
|
562
760
|
} else {
|
|
563
761
|
appVersion = _nullishCoalesce(_semver2.default.valid(appVersion), () => ( appVersion));
|
|
564
762
|
}
|
|
565
763
|
const versionInfo = versions.find((v) => v.version == appVersion);
|
|
566
764
|
if (!versionInfo) {
|
|
567
|
-
throw Error(`No Obsidian app version ${appVersion} found`);
|
|
765
|
+
throw Error(`No Obsidian app version "${appVersion}" found`);
|
|
568
766
|
}
|
|
569
767
|
return versionInfo;
|
|
570
768
|
}
|
|
571
769
|
/**
|
|
572
|
-
*
|
|
573
|
-
*
|
|
770
|
+
* Parses a string of Obsidian versions into [appVersion, installerVersion] tuples.
|
|
771
|
+
*
|
|
772
|
+
* `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer
|
|
773
|
+
* version by using "appVersion/installerVersion" e.g. `"1.7.7/1.8.10"`.
|
|
774
|
+
*
|
|
775
|
+
* Example:
|
|
776
|
+
* ```js
|
|
777
|
+
* launcher.parseVersions("1.8.10/1.7.7 latest latest-beta/earliest")
|
|
778
|
+
* ```
|
|
779
|
+
*
|
|
780
|
+
* See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)
|
|
781
|
+
*
|
|
782
|
+
* @param versions string to parse
|
|
783
|
+
* @returns [appVersion, installerVersion][] resolved to specific versions.
|
|
784
|
+
*/
|
|
785
|
+
async parseVersions(versions) {
|
|
786
|
+
let parsedVersions = versions.split(/[ ,]/).filter((v) => v).map((v) => {
|
|
787
|
+
const [appVersion, installerVersion = "earliest"] = v.split("/");
|
|
788
|
+
return [appVersion, installerVersion];
|
|
789
|
+
});
|
|
790
|
+
let resolvedVersions = [];
|
|
791
|
+
for (let [appVersion, installerVersion] of parsedVersions) {
|
|
792
|
+
resolvedVersions.push(await this.resolveVersion(appVersion, installerVersion));
|
|
793
|
+
}
|
|
794
|
+
return _lodash2.default.uniqBy(resolvedVersions, (v) => v.join("/"));
|
|
795
|
+
}
|
|
796
|
+
getInstallerKey(installerVersionInfo, opts = {}) {
|
|
797
|
+
const { platform, arch } = _lodash2.default.merge({}, opts, currentPlatform);
|
|
798
|
+
const platformName = `${platform}-${arch}`;
|
|
799
|
+
const key = _lodash2.default.findKey(installerVersionInfo.installers, (v) => v && v.platforms.includes(platformName));
|
|
800
|
+
return key;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Gets details about the Obsidian installer for the given platform.
|
|
804
|
+
* @param installerVersion Obsidian installer version
|
|
805
|
+
* @param opts.platform Platform/os (defaults to host platform)
|
|
806
|
+
* @param opts.arch Architecture (defaults to host architecture)
|
|
574
807
|
*/
|
|
575
|
-
async
|
|
576
|
-
const
|
|
577
|
-
|
|
808
|
+
async getInstallerInfo(installerVersion, opts = {}) {
|
|
809
|
+
const { platform, arch } = _lodash2.default.merge({}, opts, currentPlatform);
|
|
810
|
+
const versionInfo = await this.getVersionInfo(installerVersion);
|
|
811
|
+
const key = this.getInstallerKey(versionInfo, { platform, arch });
|
|
812
|
+
if (key) {
|
|
813
|
+
return { ...versionInfo.installers[key], url: versionInfo.downloads[key] };
|
|
814
|
+
} else {
|
|
815
|
+
throw Error(
|
|
816
|
+
`No Obsidian installer for ${installerVersion} ${platform}-${arch}` + (versionInfo.isBeta ? ` (${installerVersion} is a beta version)` : "")
|
|
817
|
+
);
|
|
818
|
+
}
|
|
578
819
|
}
|
|
579
820
|
/**
|
|
580
|
-
*
|
|
581
|
-
*
|
|
821
|
+
* Downloads the Obsidian installer for the given version and platform/arch (defaults to host platform/arch).
|
|
822
|
+
* Returns the file path.
|
|
823
|
+
* @param installerVersion Obsidian installer version to download
|
|
824
|
+
* @param opts.platform Platform/os of the installer to download (defaults to host platform)
|
|
825
|
+
* @param opts.arch Architecture of the installer to download (defaults to host architecture)
|
|
582
826
|
*/
|
|
583
|
-
async
|
|
584
|
-
const
|
|
585
|
-
const
|
|
827
|
+
async downloadInstaller(installerVersion, opts = {}) {
|
|
828
|
+
const { platform, arch } = _lodash2.default.merge({}, opts, currentPlatform);
|
|
829
|
+
const versionInfo = await this.getVersionInfo(installerVersion);
|
|
830
|
+
installerVersion = versionInfo.version;
|
|
831
|
+
const installerInfo = await this.getInstallerInfo(installerVersion, { platform, arch });
|
|
586
832
|
const cacheDir = _path2.default.join(this.cacheDir, `obsidian-installer/${platform}-${arch}/Obsidian-${installerVersion}`);
|
|
587
|
-
let
|
|
588
|
-
let
|
|
833
|
+
let binaryPath;
|
|
834
|
+
let extractor;
|
|
589
835
|
if (platform == "linux") {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (arch.startsWith("arm")) {
|
|
593
|
-
installerUrl = versionInfo.downloads.appImageArm;
|
|
594
|
-
} else {
|
|
595
|
-
installerUrl = versionInfo.downloads.appImage;
|
|
596
|
-
}
|
|
597
|
-
if (installerUrl) {
|
|
598
|
-
downloader = async (tmpDir) => {
|
|
599
|
-
const appImage = _path2.default.join(tmpDir, "Obsidian.AppImage");
|
|
600
|
-
await downloadResponse(await fetch(installerUrl), appImage);
|
|
601
|
-
const obsidianFolder = _path2.default.join(tmpDir, "Obsidian");
|
|
602
|
-
await extractObsidianAppImage(appImage, obsidianFolder);
|
|
603
|
-
return obsidianFolder;
|
|
604
|
-
};
|
|
605
|
-
}
|
|
836
|
+
binaryPath = _path2.default.join(cacheDir, "obsidian");
|
|
837
|
+
extractor = (installer, dest) => extractObsidianAppImage(installer, dest);
|
|
606
838
|
} else if (platform == "win32") {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
let appArch;
|
|
610
|
-
if (arch == "x64") {
|
|
611
|
-
appArch = "app-64";
|
|
612
|
-
} else if (arch == "ia32") {
|
|
613
|
-
appArch = "app-32";
|
|
614
|
-
} else if (arch.startsWith("arm")) {
|
|
615
|
-
appArch = "app-arm64";
|
|
616
|
-
}
|
|
617
|
-
if (installerUrl && appArch) {
|
|
618
|
-
downloader = async (tmpDir) => {
|
|
619
|
-
const installerExecutable = _path2.default.join(tmpDir, "Obsidian.exe");
|
|
620
|
-
await downloadResponse(await fetch(installerUrl), installerExecutable);
|
|
621
|
-
const obsidianFolder = _path2.default.join(tmpDir, "Obsidian");
|
|
622
|
-
await extractObsidianExe(installerExecutable, appArch, obsidianFolder);
|
|
623
|
-
return obsidianFolder;
|
|
624
|
-
};
|
|
625
|
-
}
|
|
839
|
+
binaryPath = _path2.default.join(cacheDir, "Obsidian.exe");
|
|
840
|
+
extractor = (installer, dest) => extractObsidianExe(installer, arch, dest);
|
|
626
841
|
} else if (platform == "darwin") {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (installerUrl) {
|
|
630
|
-
downloader = async (tmpDir) => {
|
|
631
|
-
const dmg = _path2.default.join(tmpDir, "Obsidian.dmg");
|
|
632
|
-
await downloadResponse(await fetch(installerUrl), dmg);
|
|
633
|
-
const obsidianFolder = _path2.default.join(tmpDir, "Obsidian");
|
|
634
|
-
await extractObsidianDmg(dmg, obsidianFolder);
|
|
635
|
-
return obsidianFolder;
|
|
636
|
-
};
|
|
637
|
-
}
|
|
842
|
+
binaryPath = _path2.default.join(cacheDir, "Contents/MacOS/Obsidian");
|
|
843
|
+
extractor = (installer, dest) => extractObsidianDmg(installer, dest);
|
|
638
844
|
} else {
|
|
639
845
|
throw Error(`Unsupported platform ${platform}`);
|
|
640
846
|
}
|
|
641
|
-
if (!
|
|
642
|
-
throw Error(`No Obsidian installer download available for v${installerVersion} ${platform} ${arch}`);
|
|
643
|
-
}
|
|
644
|
-
if (!await fileExists(installerPath)) {
|
|
847
|
+
if (!await fileExists(binaryPath)) {
|
|
645
848
|
console.log(`Downloading Obsidian installer v${installerVersion}...`);
|
|
646
|
-
await
|
|
647
|
-
|
|
849
|
+
await atomicCreate(cacheDir, async (tmpDir) => {
|
|
850
|
+
const installer = _path2.default.join(tmpDir, "installer");
|
|
851
|
+
await downloadResponse(await fetch(installerInfo.url), installer);
|
|
852
|
+
const extracted = _path2.default.join(tmpDir, "extracted");
|
|
853
|
+
await extractor(installer, extracted);
|
|
854
|
+
return extracted;
|
|
855
|
+
});
|
|
648
856
|
}
|
|
649
|
-
return
|
|
857
|
+
return binaryPath;
|
|
650
858
|
}
|
|
651
859
|
/**
|
|
652
860
|
* Downloads the Obsidian asar for the given version. Returns the file path.
|
|
653
861
|
*
|
|
654
|
-
* To download beta versions you'll need
|
|
655
|
-
* and `OBSIDIAN_PASSWORD`
|
|
862
|
+
* To download Obsidian beta versions you'll need have an Obsidian Insiders account and either set the
|
|
863
|
+
* `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` is supported) or pre-download the Obsidian beta with
|
|
864
|
+
* `npx obsidian-launcher download -v latest-beta`
|
|
656
865
|
*
|
|
657
866
|
* @param appVersion Obsidian version to download
|
|
658
867
|
*/
|
|
659
868
|
async downloadApp(appVersion) {
|
|
660
|
-
const
|
|
661
|
-
const appUrl =
|
|
869
|
+
const versionInfo = await this.getVersionInfo(appVersion);
|
|
870
|
+
const appUrl = versionInfo.downloads.asar;
|
|
662
871
|
if (!appUrl) {
|
|
663
872
|
throw Error(`No asar found for Obsidian version ${appVersion}`);
|
|
664
873
|
}
|
|
665
|
-
const appPath = _path2.default.join(this.cacheDir, "obsidian-app", `obsidian-${
|
|
874
|
+
const appPath = _path2.default.join(this.cacheDir, "obsidian-app", `obsidian-${versionInfo.version}.asar`);
|
|
666
875
|
if (!await fileExists(appPath)) {
|
|
667
|
-
console.log(`Downloading Obsidian app v${
|
|
668
|
-
await
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
876
|
+
console.log(`Downloading Obsidian app v${versionInfo.version} ...`);
|
|
877
|
+
await atomicCreate(appPath, async (tmpDir) => {
|
|
878
|
+
const isInsiders = new URL(appUrl).hostname.endsWith(".obsidian.md");
|
|
879
|
+
let response;
|
|
880
|
+
if (isInsiders) {
|
|
881
|
+
if (!this.obsidianApiToken) {
|
|
882
|
+
this.obsidianApiToken = await obsidianApiLogin({
|
|
883
|
+
interactive: this.interactive,
|
|
884
|
+
savePath: _path2.default.join(this.cacheDir, "obsidian-credentials.env")
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
response = await fetchObsidianApi(appUrl, { token: this.obsidianApiToken });
|
|
888
|
+
} else {
|
|
889
|
+
response = await fetch(appUrl);
|
|
890
|
+
}
|
|
672
891
|
const archive = _path2.default.join(tmpDir, "app.asar.gz");
|
|
673
892
|
const asar = _path2.default.join(tmpDir, "app.asar");
|
|
674
893
|
await downloadResponse(response, archive);
|
|
675
|
-
await
|
|
894
|
+
await extractGz(archive, asar);
|
|
676
895
|
return asar;
|
|
677
896
|
});
|
|
678
897
|
}
|
|
@@ -689,34 +908,53 @@ var ObsidianLauncher = class {
|
|
|
689
908
|
*
|
|
690
909
|
* @param installerVersion Obsidian installer version
|
|
691
910
|
*/
|
|
692
|
-
async downloadChromedriver(installerVersion) {
|
|
693
|
-
const
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
throw Error(`${installerVersion} is not an Obsidian installer version.`);
|
|
697
|
-
}
|
|
698
|
-
const chromedriverZipPath = await _get.downloadArtifact.call(void 0, {
|
|
699
|
-
version: electronVersion,
|
|
700
|
-
artifactName: "chromedriver",
|
|
701
|
-
cacheRoot: _path2.default.join(this.cacheDir, "chromedriver-legacy"),
|
|
702
|
-
unsafelyDisableChecksums: true
|
|
703
|
-
// the checksums are slow and run even on cache hit.
|
|
704
|
-
});
|
|
911
|
+
async downloadChromedriver(installerVersion, opts = {}) {
|
|
912
|
+
const { platform, arch } = _lodash2.default.merge({}, opts, currentPlatform);
|
|
913
|
+
const installerInfo = await this.getInstallerInfo(installerVersion, { platform, arch });
|
|
914
|
+
const cacheDir = _path2.default.join(this.cacheDir, `electron-chromedriver/${platform}-${arch}/${installerInfo.electron}`);
|
|
705
915
|
let chromedriverPath;
|
|
706
916
|
if (process.platform == "win32") {
|
|
707
|
-
chromedriverPath = _path2.default.join(
|
|
917
|
+
chromedriverPath = _path2.default.join(cacheDir, `chromedriver.exe`);
|
|
708
918
|
} else {
|
|
709
|
-
chromedriverPath = _path2.default.join(
|
|
919
|
+
chromedriverPath = _path2.default.join(cacheDir, `chromedriver`);
|
|
710
920
|
}
|
|
711
921
|
if (!await fileExists(chromedriverPath)) {
|
|
712
|
-
console.log(`Downloading
|
|
713
|
-
await
|
|
714
|
-
await
|
|
715
|
-
|
|
922
|
+
console.log(`Downloading chromedriver for electron ${installerInfo.electron} ...`);
|
|
923
|
+
await atomicCreate(cacheDir, async (tmpDir) => {
|
|
924
|
+
const chromedriverZipPath = await _get.downloadArtifact.call(void 0, {
|
|
925
|
+
version: installerInfo.electron,
|
|
926
|
+
artifactName: "chromedriver",
|
|
927
|
+
cacheRoot: _path2.default.join(tmpDir, "download")
|
|
928
|
+
});
|
|
929
|
+
const extracted = _path2.default.join(tmpDir, "extracted");
|
|
930
|
+
await _extractzip2.default.call(void 0, chromedriverZipPath, { dir: extracted });
|
|
931
|
+
return extracted;
|
|
716
932
|
});
|
|
717
933
|
}
|
|
718
934
|
return chromedriverPath;
|
|
719
935
|
}
|
|
936
|
+
/**
|
|
937
|
+
* Downloads the Obsidian apk.
|
|
938
|
+
*/
|
|
939
|
+
async downloadAndroid(version) {
|
|
940
|
+
const versionInfo = await this.getVersionInfo(version);
|
|
941
|
+
const apkUrl = versionInfo.downloads.apk;
|
|
942
|
+
if (!apkUrl) {
|
|
943
|
+
throw Error(
|
|
944
|
+
`No apk found for Obsidian version ${version}` + (versionInfo.isBeta ? ` (${version} is a beta version)` : "")
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
const apkPath = _path2.default.join(this.cacheDir, "obsidian-apk", `obsidian-${versionInfo.version}.apk`);
|
|
948
|
+
if (!await fileExists(apkPath)) {
|
|
949
|
+
console.log(`Downloading Obsidian apk v${versionInfo.version} ...`);
|
|
950
|
+
await atomicCreate(apkPath, async (tmpDir) => {
|
|
951
|
+
const dest = _path2.default.join(tmpDir, "obsidian.apk");
|
|
952
|
+
await downloadResponse(await fetch(apkUrl), dest);
|
|
953
|
+
return dest;
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return apkPath;
|
|
957
|
+
}
|
|
720
958
|
/** Gets the latest version of a plugin. */
|
|
721
959
|
async getLatestPluginVersion(repo) {
|
|
722
960
|
repo = normalizeGitHubRepo(repo);
|
|
@@ -742,8 +980,7 @@ var ObsidianLauncher = class {
|
|
|
742
980
|
version = _semver2.default.valid(version);
|
|
743
981
|
const pluginDir = _path2.default.join(this.cacheDir, "obsidian-plugins", repo, version);
|
|
744
982
|
if (!await fileExists(pluginDir)) {
|
|
745
|
-
await
|
|
746
|
-
await withTmpDir(pluginDir, async (tmpDir) => {
|
|
983
|
+
await atomicCreate(pluginDir, async (tmpDir) => {
|
|
747
984
|
const assetsToDownload = { "manifest.json": true, "main.js": true, "styles.css": false };
|
|
748
985
|
await Promise.all(
|
|
749
986
|
Object.entries(assetsToDownload).map(async ([file, required]) => {
|
|
@@ -776,8 +1013,8 @@ var ObsidianLauncher = class {
|
|
|
776
1013
|
return await this.downloadGitHubPlugin(pluginInfo.repo, version);
|
|
777
1014
|
}
|
|
778
1015
|
/**
|
|
779
|
-
* Downloads a list of plugins to the cache and returns a list of
|
|
780
|
-
* Also adds the `id` property to the plugins based on the manifest.
|
|
1016
|
+
* Downloads a list of plugins to the cache and returns a list of {@link DownloadedPluginEntry} with the downloaded
|
|
1017
|
+
* paths. Also adds the `id` property to the plugins based on the manifest.
|
|
781
1018
|
*
|
|
782
1019
|
* You can download plugins from GitHub using `{repo: "org/repo"}` and community plugins using `{id: 'plugin-id'}`.
|
|
783
1020
|
* Local plugins will just be passed through.
|
|
@@ -850,8 +1087,7 @@ var ObsidianLauncher = class {
|
|
|
850
1087
|
const version = await this.getLatestThemeVersion(repo);
|
|
851
1088
|
const themeDir = _path2.default.join(this.cacheDir, "obsidian-themes", repo, version);
|
|
852
1089
|
if (!await fileExists(themeDir)) {
|
|
853
|
-
await
|
|
854
|
-
await withTmpDir(themeDir, async (tmpDir) => {
|
|
1090
|
+
await atomicCreate(themeDir, async (tmpDir) => {
|
|
855
1091
|
const assetsToDownload = ["manifest.json", "theme.css"];
|
|
856
1092
|
await Promise.all(
|
|
857
1093
|
assetsToDownload.map(async (file) => {
|
|
@@ -883,8 +1119,8 @@ var ObsidianLauncher = class {
|
|
|
883
1119
|
return await this.downloadGitHubTheme(themeInfo.repo);
|
|
884
1120
|
}
|
|
885
1121
|
/**
|
|
886
|
-
* Downloads a list of themes to the cache and returns a list of
|
|
887
|
-
* Also adds the `name` property to the plugins based on the manifest.
|
|
1122
|
+
* Downloads a list of themes to the cache and returns a list of {@link DownloadedThemeEntry} with the downloaded
|
|
1123
|
+
* paths. Also adds the `name` property to the plugins based on the manifest.
|
|
888
1124
|
*
|
|
889
1125
|
* You can download themes from GitHub using `{repo: "org/repo"}` and community themes using `{name: 'theme-name'}`.
|
|
890
1126
|
* Local themes will just be passed through.
|
|
@@ -1038,27 +1274,35 @@ var ObsidianLauncher = class {
|
|
|
1038
1274
|
* @param params.appVersion Obsidian app version
|
|
1039
1275
|
* @param params.installerVersion Obsidian version string.
|
|
1040
1276
|
* @param params.appPath Path to the asar file to install. Will download if omitted.
|
|
1041
|
-
* @param params.vault Path to the vault to open in Obsidian
|
|
1042
|
-
* @param params.
|
|
1277
|
+
* @param params.vault Path to the vault to open in Obsidian
|
|
1278
|
+
* @param params.localStorage items to add to localStorage. `$vaultId` in the keys will be replaced with the vaultId
|
|
1279
|
+
* @param params.chromePreferences Chrome preferences to add to the Preferences file
|
|
1043
1280
|
*/
|
|
1044
1281
|
async setupConfigDir(params) {
|
|
1045
|
-
const [appVersion, installerVersion] = await this.
|
|
1046
|
-
const configDir = await
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
};
|
|
1051
|
-
let localStorageData = {
|
|
1052
|
-
"most-recently-installed-version": appVersion
|
|
1282
|
+
const [appVersion, installerVersion] = await this.resolveVersion(params.appVersion, params.installerVersion);
|
|
1283
|
+
const configDir = await makeTmpDir("obsidian-launcher-config-");
|
|
1284
|
+
const vaultId = _crypto2.default.randomBytes(8).toString("hex");
|
|
1285
|
+
const localStorageData = {
|
|
1286
|
+
"most-recently-installed-version": appVersion,
|
|
1053
1287
|
// prevents the changelog page on boot
|
|
1288
|
+
[`enable-plugin-${vaultId}`]: "true",
|
|
1289
|
+
// Disable "safe mode" and enable plugins
|
|
1290
|
+
..._lodash2.default.mapKeys(_nullishCoalesce(params.localStorage, () => ( {})), (v, k) => k.replace("$vaultId", _nullishCoalesce(vaultId, () => ( ""))))
|
|
1291
|
+
};
|
|
1292
|
+
const chromePreferences = _lodash2.default.merge(
|
|
1293
|
+
// disables the "allow pasting" bit in the dev tools console
|
|
1294
|
+
{ "electron": { "devtools": { "preferences": { "disable-self-xss-warning": "true" } } } },
|
|
1295
|
+
_nullishCoalesce(params.chromePreferences, () => ( {}))
|
|
1296
|
+
);
|
|
1297
|
+
const obsidianJson = {
|
|
1298
|
+
updateDisabled: true
|
|
1299
|
+
// prevents Obsidian trying to auto-update on boot.
|
|
1054
1300
|
};
|
|
1055
1301
|
if (params.vault !== void 0) {
|
|
1056
1302
|
if (!await fileExists(params.vault)) {
|
|
1057
1303
|
throw Error(`Vault path ${params.vault} doesn't exist.`);
|
|
1058
1304
|
}
|
|
1059
|
-
|
|
1060
|
-
obsidianJson = {
|
|
1061
|
-
...obsidianJson,
|
|
1305
|
+
Object.assign(obsidianJson, {
|
|
1062
1306
|
vaults: {
|
|
1063
1307
|
[vaultId]: {
|
|
1064
1308
|
path: _path2.default.resolve(params.vault),
|
|
@@ -1066,14 +1310,10 @@ var ObsidianLauncher = class {
|
|
|
1066
1310
|
open: true
|
|
1067
1311
|
}
|
|
1068
1312
|
}
|
|
1069
|
-
};
|
|
1070
|
-
localStorageData = {
|
|
1071
|
-
...localStorageData,
|
|
1072
|
-
[`enable-plugin-${vaultId}`]: "true"
|
|
1073
|
-
// Disable "safe mode" and enable plugins
|
|
1074
|
-
};
|
|
1313
|
+
});
|
|
1075
1314
|
}
|
|
1076
1315
|
await _promises2.default.writeFile(_path2.default.join(configDir, "obsidian.json"), JSON.stringify(obsidianJson));
|
|
1316
|
+
await _promises2.default.writeFile(_path2.default.join(configDir, "Preferences"), JSON.stringify(chromePreferences));
|
|
1077
1317
|
let appPath = params.appPath;
|
|
1078
1318
|
if (!appPath) {
|
|
1079
1319
|
appPath = await this.downloadApp(appVersion);
|
|
@@ -1108,8 +1348,6 @@ var ObsidianLauncher = class {
|
|
|
1108
1348
|
* Downloads and launches Obsidian with a sandboxed config dir and a specifc vault open. Optionally install plugins
|
|
1109
1349
|
* and themes first.
|
|
1110
1350
|
*
|
|
1111
|
-
* This is just a shortcut for calling `downloadApp`, `downloadInstaller`, `setupVault` and `setupConfDir`.
|
|
1112
|
-
*
|
|
1113
1351
|
* @param params.appVersion Obsidian app version. Default "latest"
|
|
1114
1352
|
* @param params.installerVersion Obsidian installer version. Default "latest"
|
|
1115
1353
|
* @param params.vault Path to the vault to open in Obsidian
|
|
@@ -1117,11 +1355,12 @@ var ObsidianLauncher = class {
|
|
|
1117
1355
|
* @param params.plugins List of plugins to install in the vault
|
|
1118
1356
|
* @param params.themes List of themes to install in the vault
|
|
1119
1357
|
* @param params.args CLI args to pass to Obsidian
|
|
1358
|
+
* @param params.localStorage items to add to localStorage. `$vaultId` in the keys will be replaced with the vaultId
|
|
1120
1359
|
* @param params.spawnOptions Options to pass to `spawn`
|
|
1121
1360
|
* @returns The launched child process and the created tmpdirs
|
|
1122
1361
|
*/
|
|
1123
1362
|
async launch(params) {
|
|
1124
|
-
const [appVersion, installerVersion] = await this.
|
|
1363
|
+
const [appVersion, installerVersion] = await this.resolveVersion(
|
|
1125
1364
|
_nullishCoalesce(params.appVersion, () => ( "latest")),
|
|
1126
1365
|
_nullishCoalesce(params.installerVersion, () => ( "latest"))
|
|
1127
1366
|
);
|
|
@@ -1136,10 +1375,16 @@ var ObsidianLauncher = class {
|
|
|
1136
1375
|
themes: params.themes
|
|
1137
1376
|
});
|
|
1138
1377
|
}
|
|
1139
|
-
const configDir = await this.setupConfigDir({
|
|
1378
|
+
const configDir = await this.setupConfigDir({
|
|
1379
|
+
appVersion,
|
|
1380
|
+
installerVersion,
|
|
1381
|
+
appPath,
|
|
1382
|
+
vault,
|
|
1383
|
+
localStorage: params.localStorage
|
|
1384
|
+
});
|
|
1140
1385
|
const proc = _child_process2.default.spawn(installerPath, [
|
|
1141
1386
|
`--user-data-dir=${configDir}`,
|
|
1142
|
-
// Workaround for SUID issue on
|
|
1387
|
+
// Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510
|
|
1143
1388
|
...process.platform == "linux" ? ["--no-sandbox"] : [],
|
|
1144
1389
|
..._nullishCoalesce(params.args, () => ( []))
|
|
1145
1390
|
], {
|
|
@@ -1148,91 +1393,80 @@ var ObsidianLauncher = class {
|
|
|
1148
1393
|
return { proc, configDir, vault };
|
|
1149
1394
|
}
|
|
1150
1395
|
/**
|
|
1151
|
-
* Updates the info obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands
|
|
1396
|
+
* Updates the info in obsidian-versions.json. The obsidian-versions.json file is used in other launcher commands
|
|
1152
1397
|
* and in wdio-obsidian-service to get metadata about Obsidian versions in one place such as minInstallerVersion and
|
|
1153
|
-
* the internal
|
|
1398
|
+
* the internal Electron version.
|
|
1154
1399
|
*/
|
|
1155
|
-
async
|
|
1400
|
+
async updateVersionList(original, opts = {}) {
|
|
1401
|
+
const { maxInstances = 1 } = opts;
|
|
1156
1402
|
const repo = "obsidianmd/obsidian-releases";
|
|
1403
|
+
const originalVersions = _lodash2.default.keyBy(_nullishCoalesce(_optionalChain([original, 'optionalAccess', _27 => _27.versions]), () => ( [])), (v) => v.version);
|
|
1404
|
+
const versions = _lodash2.default.cloneDeep(originalVersions);
|
|
1157
1405
|
let commitHistory = await fetchGitHubAPIPaginated(`repos/${repo}/commits`, {
|
|
1158
1406
|
path: "desktop-releases.json",
|
|
1159
|
-
since: _optionalChain([original, 'optionalAccess', _28 => _28.metadata, '
|
|
1407
|
+
since: _optionalChain([original, 'optionalAccess', _28 => _28.metadata, 'optionalAccess', _29 => _29.commitDate])
|
|
1160
1408
|
});
|
|
1161
1409
|
commitHistory.reverse();
|
|
1162
|
-
if (original) {
|
|
1410
|
+
if (_optionalChain([original, 'optionalAccess', _30 => _30.metadata, 'optionalAccess', _31 => _31.commitSha])) {
|
|
1163
1411
|
commitHistory = _lodash2.default.takeRightWhile(commitHistory, (c) => c.sha != original.metadata.commitSha);
|
|
1164
1412
|
}
|
|
1165
1413
|
const fileHistory = await pool(
|
|
1166
|
-
|
|
1414
|
+
maxInstances,
|
|
1167
1415
|
commitHistory,
|
|
1168
1416
|
(commit) => fetch(`https://raw.githubusercontent.com/${repo}/${commit.sha}/desktop-releases.json`).then((r) => r.json())
|
|
1169
1417
|
);
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
);
|
|
1175
|
-
for (const { beta, ...current } of fileHistory) {
|
|
1176
|
-
if (beta && (!versionMap[beta.latestVersion] || versionMap[beta.latestVersion].isBeta)) {
|
|
1177
|
-
versionMap[beta.latestVersion] = _lodash2.default.merge(
|
|
1178
|
-
{},
|
|
1179
|
-
versionMap[beta.latestVersion],
|
|
1180
|
-
parseObsidianDesktopRelease(beta, true)
|
|
1181
|
-
);
|
|
1418
|
+
for (const fileVersion of fileHistory) {
|
|
1419
|
+
const { current, beta } = parseObsidianDesktopRelease(fileVersion);
|
|
1420
|
+
if (beta) {
|
|
1421
|
+
versions[beta.version] = _lodash2.default.merge(_nullishCoalesce(versions[beta.version], () => ( {})), beta);
|
|
1182
1422
|
}
|
|
1183
|
-
|
|
1184
|
-
{},
|
|
1185
|
-
versionMap[current.latestVersion],
|
|
1186
|
-
parseObsidianDesktopRelease(current, false)
|
|
1187
|
-
);
|
|
1423
|
+
versions[current.version] = _lodash2.default.merge(_nullishCoalesce(versions[current.version], () => ( {})), current);
|
|
1188
1424
|
}
|
|
1425
|
+
const githubReleases = (await fetchGitHubAPIPaginated(`repos/${repo}/releases`)).reverse();
|
|
1189
1426
|
for (const release of githubReleases) {
|
|
1190
|
-
if (
|
|
1191
|
-
|
|
1427
|
+
if (_semver2.default.valid(release.name)) {
|
|
1428
|
+
const parsed = parseObsidianGithubRelease(release);
|
|
1429
|
+
versions[parsed.version] = _lodash2.default.merge(_nullishCoalesce(versions[parsed.version], () => ( {})), parsed);
|
|
1192
1430
|
}
|
|
1193
1431
|
}
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1432
|
+
const installerKeys = ["appImage", "appImageArm", "tar", "tarArm", "dmg", "exe"];
|
|
1433
|
+
const newInstallers = Object.values(versions).flatMap((v) => installerKeys.map((k) => [v, k]));
|
|
1434
|
+
const installerInfos = await pool(maxInstances, newInstallers, async ([v, key]) => {
|
|
1435
|
+
let installerInfo;
|
|
1436
|
+
const hasAsset = !!_optionalChain([v, 'access', _32 => _32.downloads, 'optionalAccess', _33 => _33[key]]);
|
|
1437
|
+
const alreadyExtracted = !!_optionalChain([v, 'access', _34 => _34.installers, 'optionalAccess', _35 => _35[key], 'optionalAccess', _36 => _36.chrome]);
|
|
1438
|
+
const changed = _optionalChain([v, 'access', _37 => _37.installers, 'optionalAccess', _38 => _38[key], 'optionalAccess', _39 => _39.digest]) != _optionalChain([originalVersions, 'access', _40 => _40[v.version], 'optionalAccess', _41 => _41.installers, 'access', _42 => _42[key], 'optionalAccess', _43 => _43.digest]);
|
|
1439
|
+
if (hasAsset && (!alreadyExtracted || changed)) {
|
|
1440
|
+
installerInfo = await getInstallerInfo(key, v.downloads[key]);
|
|
1201
1441
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1442
|
+
return { version: v.version, installerInfo: { [key]: installerInfo } };
|
|
1443
|
+
});
|
|
1444
|
+
for (const installerInfo of installerInfos) {
|
|
1445
|
+
versions[installerInfo.version] = _lodash2.default.merge(_nullishCoalesce(versions[installerInfo.version], () => ( {})), installerInfo);
|
|
1205
1446
|
}
|
|
1206
1447
|
let minInstallerVersion = void 0;
|
|
1207
1448
|
let maxInstallerVersion = void 0;
|
|
1208
|
-
for (const version of Object.keys(
|
|
1209
|
-
if (!minInstallerVersion &&
|
|
1449
|
+
for (const version of Object.keys(versions).sort(_semver2.default.compare)) {
|
|
1450
|
+
if (!minInstallerVersion && versions[version].downloads.appImage) {
|
|
1210
1451
|
minInstallerVersion = version;
|
|
1211
1452
|
}
|
|
1212
|
-
if (
|
|
1453
|
+
if (versions[version].downloads.appImage) {
|
|
1213
1454
|
maxInstallerVersion = version;
|
|
1214
1455
|
}
|
|
1215
|
-
|
|
1216
|
-
{},
|
|
1217
|
-
versionMap[version],
|
|
1218
|
-
{
|
|
1219
|
-
minInstallerVersion: _nullishCoalesce(versionMap[version].minInstallerVersion, () => ( minInstallerVersion)),
|
|
1220
|
-
maxInstallerVersion
|
|
1221
|
-
},
|
|
1222
|
-
correctObsidianVersionInfo(versionMap[version])
|
|
1223
|
-
);
|
|
1456
|
+
versions[version] = _lodash2.default.merge({ minInstallerVersion, maxInstallerVersion }, versions[version]);
|
|
1224
1457
|
}
|
|
1225
1458
|
const result = {
|
|
1226
1459
|
metadata: {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1460
|
+
schemaVersion: obsidianVersionsSchemaVersion,
|
|
1461
|
+
commitDate: _nullishCoalesce(_optionalChain([commitHistory, 'access', _44 => _44.at, 'call', _45 => _45(-1), 'optionalAccess', _46 => _46.commit, 'access', _47 => _47.committer, 'access', _48 => _48.date]), () => ( _optionalChain([original, 'optionalAccess', _49 => _49.metadata, 'access', _50 => _50.commitDate]))),
|
|
1462
|
+
commitSha: _nullishCoalesce(_optionalChain([commitHistory, 'access', _51 => _51.at, 'call', _52 => _52(-1), 'optionalAccess', _53 => _53.sha]), () => ( _optionalChain([original, 'optionalAccess', _54 => _54.metadata, 'access', _55 => _55.commitSha]))),
|
|
1463
|
+
timestamp: _nullishCoalesce(_optionalChain([original, 'optionalAccess', _56 => _56.metadata, 'access', _57 => _57.timestamp]), () => ( ""))
|
|
1230
1464
|
// set down below
|
|
1231
1465
|
},
|
|
1232
|
-
versions: Object.values(
|
|
1466
|
+
versions: Object.values(versions).map(normalizeObsidianVersionInfo).sort((a, b) => _semver2.default.compare(a.version, b.version))
|
|
1233
1467
|
};
|
|
1234
1468
|
const dayMs = 24 * 60 * 60 * 1e3;
|
|
1235
|
-
const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(_nullishCoalesce(_optionalChain([original, 'optionalAccess',
|
|
1469
|
+
const timeSinceLastUpdate = (/* @__PURE__ */ new Date()).getTime() - new Date(_nullishCoalesce(_optionalChain([original, 'optionalAccess', _58 => _58.metadata, 'access', _59 => _59.timestamp]), () => ( 0))).getTime();
|
|
1236
1470
|
if (!_lodash2.default.isEqual(original, result) || timeSinceLastUpdate > 29 * dayMs) {
|
|
1237
1471
|
result.metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1238
1472
|
}
|
|
@@ -1261,18 +1495,21 @@ var ObsidianLauncher = class {
|
|
|
1261
1495
|
*/
|
|
1262
1496
|
async isAvailable(appVersion) {
|
|
1263
1497
|
const versionInfo = await this.getVersionInfo(appVersion);
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1498
|
+
if (!versionInfo.downloads.asar || !versionInfo.minInstallerVersion) {
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
if (new URL(versionInfo.downloads.asar).hostname.endsWith(".obsidian.md")) {
|
|
1502
|
+
const hasCreds = !!(process.env["OBSIDIAN_EMAIL"] && process.env["OBSIDIAN_PASSWORD"]);
|
|
1267
1503
|
const inCache = await this.isInCache("app", versionInfo.version);
|
|
1268
|
-
return
|
|
1504
|
+
return hasCreds || inCache;
|
|
1269
1505
|
} else {
|
|
1270
|
-
return
|
|
1506
|
+
return true;
|
|
1271
1507
|
}
|
|
1272
1508
|
}
|
|
1273
1509
|
};
|
|
1274
1510
|
|
|
1275
1511
|
|
|
1276
1512
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1513
|
+
|
|
1514
|
+
exports.watchFiles = watchFiles; exports.ObsidianLauncher = ObsidianLauncher;
|
|
1515
|
+
//# sourceMappingURL=chunk-KQZZMJOZ.cjs.map
|