obsidian-integration-testing 4.3.0 → 5.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 +51 -54
- package/dist/lib/cjs/global-setup-core.cjs +39 -12
- package/dist/lib/cjs/global-setup-core.d.cts +1 -1
- package/dist/lib/cjs/index.cjs +1 -4
- package/dist/lib/cjs/index.d.cts +2 -3
- package/dist/lib/cjs/kill-process-tree.cjs +57 -0
- package/dist/lib/cjs/kill-process-tree.d.cts +16 -0
- package/dist/lib/cjs/library.cjs +1 -1
- package/dist/lib/cjs/obsidian-config.cjs +11 -85
- package/dist/lib/cjs/obsidian-config.d.cts +12 -72
- package/dist/lib/cjs/obsidian-installer.cjs +180 -0
- package/dist/lib/cjs/obsidian-installer.d.cts +45 -0
- package/dist/lib/cjs/obsidian-instance.cjs +100 -0
- package/dist/lib/cjs/obsidian-instance.d.cts +55 -0
- package/dist/lib/cjs/obsidian-version-switch.cjs +105 -0
- package/dist/lib/cjs/obsidian-version-switch.d.cts +63 -0
- package/dist/lib/cjs/obsidian-version.cjs +85 -0
- package/dist/lib/cjs/obsidian-version.d.cts +111 -0
- package/dist/lib/cjs/setup-lock.cjs +158 -0
- package/dist/lib/cjs/setup-lock.d.cts +55 -0
- package/dist/lib/cjs/transport-desktop-cdp.cjs +121 -7
- package/dist/lib/cjs/transport-desktop-cdp.d.cts +75 -14
- package/dist/lib/cjs/transport-factory.cjs +63 -32
- package/dist/lib/cjs/transport-options.cjs +1 -1
- package/dist/lib/cjs/transport-options.d.cts +37 -14
- package/dist/lib/cjs/transport.cjs +1 -1
- package/dist/lib/cjs/transport.d.cts +1 -1
- package/dist/lib/cjs/vitest/global-setup.cjs +2 -2
- package/dist/lib/cjs/vitest/typings.d.cts +1 -1
- package/dist/lib/esm/global-setup-core.d.mts +1 -1
- package/dist/lib/esm/global-setup-core.mjs +39 -12
- package/dist/lib/esm/index.d.mts +2 -3
- package/dist/lib/esm/index.mjs +1 -3
- package/dist/lib/esm/kill-process-tree.d.mts +16 -0
- package/dist/lib/esm/kill-process-tree.mjs +23 -0
- package/dist/lib/esm/library.mjs +1 -1
- package/dist/lib/esm/obsidian-config.d.mts +12 -72
- package/dist/lib/esm/obsidian-config.mjs +10 -78
- package/dist/lib/esm/obsidian-installer.d.mts +45 -0
- package/dist/lib/esm/obsidian-installer.mjs +158 -0
- package/dist/lib/esm/obsidian-instance.d.mts +55 -0
- package/dist/lib/esm/obsidian-instance.mjs +75 -0
- package/dist/lib/esm/obsidian-version-switch.d.mts +63 -0
- package/dist/lib/esm/obsidian-version-switch.mjs +89 -0
- package/dist/lib/esm/obsidian-version.d.mts +111 -0
- package/dist/lib/esm/obsidian-version.mjs +55 -0
- package/dist/lib/esm/setup-lock.d.mts +55 -0
- package/dist/lib/esm/setup-lock.mjs +132 -0
- package/dist/lib/esm/transport-desktop-cdp.d.mts +75 -14
- package/dist/lib/esm/transport-desktop-cdp.mjs +125 -7
- package/dist/lib/esm/transport-factory.mjs +68 -28
- package/dist/lib/esm/transport-options.d.mts +37 -14
- package/dist/lib/esm/transport.d.mts +1 -1
- package/dist/lib/esm/vitest/global-setup.mjs +2 -2
- package/dist/lib/esm/vitest/typings.d.mts +1 -1
- package/dist/obsidian-integration-testing-5.0.0.tgz +0 -0
- package/package.json +16 -15
- package/dist/lib/cjs/transport-desktop-cli.cjs +0 -540
- package/dist/lib/cjs/transport-desktop-cli.d.cts +0 -154
- package/dist/lib/esm/transport-desktop-cli.d.mts +0 -154
- package/dist/lib/esm/transport-desktop-cli.mjs +0 -520
- package/dist/obsidian-integration-testing-4.3.0.tgz +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import { killProcessTree } from "./kill-process-tree.mjs";
|
|
4
|
+
import { log } from "./log.mjs";
|
|
5
|
+
const DEFAULT_CDP_HOST = "127.0.0.1";
|
|
6
|
+
const CDP_READY_POLL_INTERVAL_IN_MILLISECONDS = 1e3;
|
|
7
|
+
const CDP_READY_TIMEOUT_IN_MILLISECONDS = 6e4;
|
|
8
|
+
async function launchOwnedObsidianInstance(params) {
|
|
9
|
+
const cdpHost = params.cdpHost ?? DEFAULT_CDP_HOST;
|
|
10
|
+
const port = await pickFreePort();
|
|
11
|
+
const cdpUrl = `http://${cdpHost}:${String(port)}`;
|
|
12
|
+
log(`[obsidian-instance] Launching owned Obsidian: userData=${params.userDataDir}, cdpPort=${String(port)}`);
|
|
13
|
+
const child = spawn(
|
|
14
|
+
params.exePath,
|
|
15
|
+
[`--user-data-dir=${params.userDataDir}`, `--remote-debugging-port=${String(port)}`],
|
|
16
|
+
{ detached: true, stdio: "ignore" }
|
|
17
|
+
);
|
|
18
|
+
child.unref();
|
|
19
|
+
try {
|
|
20
|
+
await waitForCdpReady(cdpUrl);
|
|
21
|
+
log(`[obsidian-instance] Owned Obsidian is serving CDP at ${cdpUrl}.`);
|
|
22
|
+
return { cdpUrl, kill, port };
|
|
23
|
+
} catch (error) {
|
|
24
|
+
kill();
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
function kill() {
|
|
28
|
+
killProcessTree(child);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function pickFreePort() {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const server = createServer();
|
|
34
|
+
server.once("error", reject);
|
|
35
|
+
server.listen(0, DEFAULT_CDP_HOST, () => {
|
|
36
|
+
const address = server.address();
|
|
37
|
+
if (address === null || typeof address === "string") {
|
|
38
|
+
server.close();
|
|
39
|
+
reject(new Error("Failed to determine a free port."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { port } = address;
|
|
43
|
+
server.close(() => {
|
|
44
|
+
resolve(port);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function delay(ms) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
setTimeout(resolve, ms);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function waitForCdpReady(cdpUrl) {
|
|
55
|
+
const deadline = Date.now() + CDP_READY_TIMEOUT_IN_MILLISECONDS;
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(`${cdpUrl}/json`);
|
|
59
|
+
const targets = await response.json();
|
|
60
|
+
if (targets.some((target) => target.type === "page")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
await delay(CDP_READY_POLL_INTERVAL_IN_MILLISECONDS);
|
|
66
|
+
}
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Owned Obsidian instance did not expose CDP at ${cdpUrl} within ${String(CDP_READY_TIMEOUT_IN_MILLISECONDS)}ms.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
launchOwnedObsidianInstance,
|
|
73
|
+
pickFreePort
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL29ic2lkaWFuLWluc3RhbmNlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogTGF1bmNoZXMgYW5kIG1hbmFnZXMgYSBoYXJuZXNzLW93bmVkLCBpc29sYXRlZCBPYnNpZGlhbiBkZXNrdG9wIGluc3RhbmNlLlxuICpcbiAqIFRoZSBpbnN0YW5jZSBydW5zIGFnYWluc3QgYSBkZWRpY2F0ZWQgYC0tdXNlci1kYXRhLWRpcmAgKHNvIGl0IG5ldmVyIHRvdWNoZXNcbiAqIHRoZSB1c2VyJ3MgT2JzaWRpYW4gY29uZmlnLCByZWdpc3RyeSwgb3IgcnVubmluZyB3aW5kb3cpIGFuZCBleHBvc2VzIENEUCBvbiBhXG4gKiBkZWRpY2F0ZWQgYC0tcmVtb3RlLWRlYnVnZ2luZy1wb3J0YC4gQmVjYXVzZSBFbGVjdHJvbidzIHNpbmdsZS1pbnN0YW5jZSBsb2NrXG4gKiBpcyBrZXllZCB0byB0aGUgdXNlci1kYXRhIGRpciwgdGhlIG93bmVkIGluc3RhbmNlIHJ1bnMgaW4gcGFyYWxsZWwgd2l0aCB0aGVcbiAqIHVzZXIncyBvd24gT2JzaWRpYW4uIENvbmZpcm1lZCBieSB0aGUgUGhhc2UgMCBzcGlrZSAoc2VlIHRoZSBwcm9qZWN0IHBsYW4pLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIHByb2Nlc3MgbWFuYWdlbWVudCBjb3ZlcmVkIGJ5IGludGVncmF0aW9uIHRlc3RzLCBub3QgdW5pdCB0ZXN0cy4gKi9cblxuaW1wb3J0IHsgc3Bhd24gfSBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0IHsgY3JlYXRlU2VydmVyIH0gZnJvbSAnbm9kZTpuZXQnO1xuXG5pbXBvcnQgeyBraWxsUHJvY2Vzc1RyZWUgfSBmcm9tICcuL2tpbGwtcHJvY2Vzcy10cmVlLm1qcyc7XG5pbXBvcnQgeyBsb2cgfSBmcm9tICcuL2xvZy5tanMnO1xuXG4vKipcbiAqIFBhcmFtZXRlcnMgZm9yIHtAbGluayBsYXVuY2hPd25lZE9ic2lkaWFuSW5zdGFuY2V9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIExhdW5jaE93bmVkT2JzaWRpYW5JbnN0YW5jZVBhcmFtcyB7XG4gIC8qKlxuICAgKiBDRFAgaG9zdCB0byBiaW5kL3BvbGwuXG4gICAqXG4gICAqIEBkZWZhdWx0IGAnMTI3LjAuMC4xJ2BcbiAgICovXG4gIHJlYWRvbmx5IGNkcEhvc3Q/OiBzdHJpbmc7XG5cbiAgLyoqIEFic29sdXRlIHBhdGggdG8gdGhlIE9ic2lkaWFuIGV4ZWN1dGFibGUgKHNoZWxsKSB0byBsYXVuY2guICovXG4gIHJlYWRvbmx5IGV4ZVBhdGg6IHN0cmluZztcblxuICAvKiogQWJzb2x1dGUgcGF0aCB0byB0aGUgaXNvbGF0ZWQgdXNlci1kYXRhIGRpciB0byBwYXNzIHZpYSBgLS11c2VyLWRhdGEtZGlyYC4gKi9cbiAgcmVhZG9ubHkgdXNlckRhdGFEaXI6IHN0cmluZztcbn1cblxuLyoqXG4gKiBBIHJ1bm5pbmcsIGhhcm5lc3Mtb3duZWQgT2JzaWRpYW4gaW5zdGFuY2UuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgT3duZWRPYnNpZGlhbkluc3RhbmNlIHtcbiAgLyoqIEJhc2UgQ0RQIFVSTCwgZS5nLiBgaHR0cDovLzEyNy4wLjAuMTo1MTg4OGAuICovXG4gIHJlYWRvbmx5IGNkcFVybDogc3RyaW5nO1xuXG4gIC8qKiBLaWxscyB0aGUgaW5zdGFuY2UgYW5kIGl0cyBlbnRpcmUgcHJvY2VzcyB0cmVlLiAqL1xuICBraWxsKCk6IHZvaWQ7XG5cbiAgLyoqIFRoZSBDRFAgcmVtb3RlLWRlYnVnZ2luZyBwb3J0IHRoZSBpbnN0YW5jZSB3YXMgbGF1bmNoZWQgd2l0aC4gKi9cbiAgcmVhZG9ubHkgcG9ydDogbnVtYmVyO1xufVxuXG5jb25zdCBERUZBVUxUX0NEUF9IT1NUID0gJzEyNy4wLjAuMSc7XG5jb25zdCBDRFBfUkVBRFlfUE9MTF9JTlRFUlZBTF9JTl9NSUxMSVNFQ09ORFMgPSAxMDAwO1xuY29uc3QgQ0RQX1JFQURZX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTID0gNjAwMDA7XG5cbmludGVyZmFjZSBDZHBUYXJnZXQge1xuICB0eXBlOiBzdHJpbmc7XG59XG5cbi8qKlxuICogTGF1bmNoZXMgYW4gaXNvbGF0ZWQsIGhhcm5lc3Mtb3duZWQgT2JzaWRpYW4gaW5zdGFuY2UgYW5kIHdhaXRzIHVudGlsIGl0cyBDRFBcbiAqIGVuZHBvaW50IGlzIHNlcnZpbmcgcGFnZSB0YXJnZXRzLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBMYXVuY2ggcGFyYW1ldGVycy5cbiAqIEByZXR1cm5zIFRoZSBydW5uaW5nIG93bmVkIGluc3RhbmNlLlxuICogQHRocm93cyBFcnJvciBpZiBDRFAgZG9lcyBub3QgYmVjb21lIHJlYWNoYWJsZSB3aXRoaW4gdGhlIHRpbWVvdXQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsYXVuY2hPd25lZE9ic2lkaWFuSW5zdGFuY2UoXG4gIHBhcmFtczogTGF1bmNoT3duZWRPYnNpZGlhbkluc3RhbmNlUGFyYW1zXG4pOiBQcm9taXNlPE93bmVkT2JzaWRpYW5JbnN0YW5jZT4ge1xuICBjb25zdCBjZHBIb3N0ID0gcGFyYW1zLmNkcEhvc3QgPz8gREVGQVVMVF9DRFBfSE9TVDtcbiAgY29uc3QgcG9ydCA9IGF3YWl0IHBpY2tGcmVlUG9ydCgpO1xuICBjb25zdCBjZHBVcmwgPSBgaHR0cDovLyR7Y2RwSG9zdH06JHtTdHJpbmcocG9ydCl9YDtcblxuICBsb2coYFtvYnNpZGlhbi1pbnN0YW5jZV0gTGF1bmNoaW5nIG93bmVkIE9ic2lkaWFuOiB1c2VyRGF0YT0ke3BhcmFtcy51c2VyRGF0YURpcn0sIGNkcFBvcnQ9JHtTdHJpbmcocG9ydCl9YCk7XG4gIGNvbnN0IGNoaWxkID0gc3Bhd24oXG4gICAgcGFyYW1zLmV4ZVBhdGgsXG4gICAgW2AtLXVzZXItZGF0YS1kaXI9JHtwYXJhbXMudXNlckRhdGFEaXJ9YCwgYC0tcmVtb3RlLWRlYnVnZ2luZy1wb3J0PSR7U3RyaW5nKHBvcnQpfWBdLFxuICAgIHsgZGV0YWNoZWQ6IHRydWUsIHN0ZGlvOiAnaWdub3JlJyB9XG4gICk7XG4gIGNoaWxkLnVucmVmKCk7XG5cbiAgdHJ5IHtcbiAgICBhd2FpdCB3YWl0Rm9yQ2RwUmVhZHkoY2RwVXJsKTtcbiAgICBsb2coYFtvYnNpZGlhbi1pbnN0YW5jZV0gT3duZWQgT2JzaWRpYW4gaXMgc2VydmluZyBDRFAgYXQgJHtjZHBVcmx9LmApO1xuICAgIHJldHVybiB7IGNkcFVybCwga2lsbCwgcG9ydCB9O1xuICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgIGtpbGwoKTtcbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxuXG4gIGZ1bmN0aW9uIGtpbGwoKTogdm9pZCB7XG4gICAga2lsbFByb2Nlc3NUcmVlKGNoaWxkKTtcbiAgfVxufVxuXG4vKipcbiAqIFBpY2tzIGEgZnJlZSBUQ1AgcG9ydCBieSBiaW5kaW5nIHRvIHBvcnQgYDBgIGFuZCByZWFkaW5nIHRoZSBhc3NpZ25lZCBwb3J0LlxuICpcbiAqIFRoZXJlIGlzIGEgc21hbGwgdGltZS1vZi1jaGVjay90aW1lLW9mLXVzZSB3aW5kb3cgYmV0d2VlbiByZWxlYXNpbmcgdGhlIHBvcnRcbiAqIGhlcmUgYW5kIE9ic2lkaWFuIGJpbmRpbmcgaXQ7IGluIHByYWN0aWNlIGl0IGlzIG5lZ2xpZ2libGUgZm9yIHRlc3QgcnVucy5cbiAqXG4gKiBAcmV0dXJucyBBIGZyZWUgVENQIHBvcnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwaWNrRnJlZVBvcnQoKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICBjb25zdCBzZXJ2ZXIgPSBjcmVhdGVTZXJ2ZXIoKTtcbiAgICBzZXJ2ZXIub25jZSgnZXJyb3InLCByZWplY3QpO1xuICAgIHNlcnZlci5saXN0ZW4oMCwgREVGQVVMVF9DRFBfSE9TVCwgKCkgPT4ge1xuICAgICAgY29uc3QgYWRkcmVzcyA9IHNlcnZlci5hZGRyZXNzKCk7XG4gICAgICBpZiAoYWRkcmVzcyA9PT0gbnVsbCB8fCB0eXBlb2YgYWRkcmVzcyA9PT0gJ3N0cmluZycpIHtcbiAgICAgICAgc2VydmVyLmNsb3NlKCk7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoJ0ZhaWxlZCB0byBkZXRlcm1pbmUgYSBmcmVlIHBvcnQuJykpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCB7IHBvcnQgfSA9IGFkZHJlc3M7XG4gICAgICBzZXJ2ZXIuY2xvc2UoKCkgPT4ge1xuICAgICAgICByZXNvbHZlKHBvcnQpO1xuICAgICAgfSk7XG4gICAgfSk7XG4gIH0pO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgYWZ0ZXIgdGhlIGdpdmVuIGRlbGF5LlxuICpcbiAqIEBwYXJhbSBtcyAtIFRoZSBkZWxheSBpbiBtaWxsaXNlY29uZHMuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgZGVsYXkuXG4gKi9cbmZ1bmN0aW9uIGRlbGF5KG1zOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG4gICAgc2V0VGltZW91dChyZXNvbHZlLCBtcyk7XG4gIH0pO1xufVxuXG4vKipcbiAqIFBvbGxzIHRoZSBDRFAgYC9qc29uYCBlbmRwb2ludCB1bnRpbCBhdCBsZWFzdCBvbmUgcGFnZSB0YXJnZXQgYXBwZWFycy5cbiAqXG4gKiBAcGFyYW0gY2RwVXJsIC0gVGhlIGJhc2UgQ0RQIFVSTC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gd2FpdEZvckNkcFJlYWR5KGNkcFVybDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IGRlYWRsaW5lID0gRGF0ZS5ub3coKSArIENEUF9SRUFEWV9USU1FT1VUX0lOX01JTExJU0VDT05EUztcbiAgd2hpbGUgKERhdGUubm93KCkgPCBkZWFkbGluZSkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKGAke2NkcFVybH0vanNvbmApO1xuICAgICAgY29uc3QgdGFyZ2V0cyA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKSBhcyBDZHBUYXJnZXRbXTtcbiAgICAgIGlmICh0YXJnZXRzLnNvbWUoKHRhcmdldCkgPT4gdGFyZ2V0LnR5cGUgPT09ICdwYWdlJykpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gRW5kcG9pbnQgbm90IHVwIHlldCBcdTIwMTQga2VlcCBwb2xsaW5nLlxuICAgIH1cbiAgICBhd2FpdCBkZWxheShDRFBfUkVBRFlfUE9MTF9JTlRFUlZBTF9JTl9NSUxMSVNFQ09ORFMpO1xuICB9XG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgT3duZWQgT2JzaWRpYW4gaW5zdGFuY2UgZGlkIG5vdCBleHBvc2UgQ0RQIGF0ICR7Y2RwVXJsfSB3aXRoaW4gJHtTdHJpbmcoQ0RQX1JFQURZX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTKX1tcy5gXG4gICk7XG59XG5cbi8qIHY4IGlnbm9yZSBzdG9wICovXG4iXSwKICAibWFwcGluZ3MiOiAiQUFjQSxTQUFTLGFBQWE7QUFDdEIsU0FBUyxvQkFBb0I7QUFFN0IsU0FBUyx1QkFBdUI7QUFDaEMsU0FBUyxXQUFXO0FBa0NwQixNQUFNLG1CQUFtQjtBQUN6QixNQUFNLDBDQUEwQztBQUNoRCxNQUFNLG9DQUFvQztBQWMxQyxlQUFzQiw0QkFDcEIsUUFDZ0M7QUFDaEMsUUFBTSxVQUFVLE9BQU8sV0FBVztBQUNsQyxRQUFNLE9BQU8sTUFBTSxhQUFhO0FBQ2hDLFFBQU0sU0FBUyxVQUFVLE9BQU8sSUFBSSxPQUFPLElBQUksQ0FBQztBQUVoRCxNQUFJLDBEQUEwRCxPQUFPLFdBQVcsYUFBYSxPQUFPLElBQUksQ0FBQyxFQUFFO0FBQzNHLFFBQU0sUUFBUTtBQUFBLElBQ1osT0FBTztBQUFBLElBQ1AsQ0FBQyxtQkFBbUIsT0FBTyxXQUFXLElBQUksMkJBQTJCLE9BQU8sSUFBSSxDQUFDLEVBQUU7QUFBQSxJQUNuRixFQUFFLFVBQVUsTUFBTSxPQUFPLFNBQVM7QUFBQSxFQUNwQztBQUNBLFFBQU0sTUFBTTtBQUVaLE1BQUk7QUFDRixVQUFNLGdCQUFnQixNQUFNO0FBQzVCLFFBQUksd0RBQXdELE1BQU0sR0FBRztBQUNyRSxXQUFPLEVBQUUsUUFBUSxNQUFNLEtBQUs7QUFBQSxFQUM5QixTQUFTLE9BQWdCO0FBQ3ZCLFNBQUs7QUFDTCxVQUFNO0FBQUEsRUFDUjtBQUVBLFdBQVMsT0FBYTtBQUNwQixvQkFBZ0IsS0FBSztBQUFBLEVBQ3ZCO0FBQ0Y7QUFVTyxTQUFTLGVBQWdDO0FBQzlDLFNBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3RDLFVBQU0sU0FBUyxhQUFhO0FBQzVCLFdBQU8sS0FBSyxTQUFTLE1BQU07QUFDM0IsV0FBTyxPQUFPLEdBQUcsa0JBQWtCLE1BQU07QUFDdkMsWUFBTSxVQUFVLE9BQU8sUUFBUTtBQUMvQixVQUFJLFlBQVksUUFBUSxPQUFPLFlBQVksVUFBVTtBQUNuRCxlQUFPLE1BQU07QUFDYixlQUFPLElBQUksTUFBTSxrQ0FBa0MsQ0FBQztBQUNwRDtBQUFBLE1BQ0Y7QUFDQSxZQUFNLEVBQUUsS0FBSyxJQUFJO0FBQ2pCLGFBQU8sTUFBTSxNQUFNO0FBQ2pCLGdCQUFRLElBQUk7QUFBQSxNQUNkLENBQUM7QUFBQSxJQUNILENBQUM7QUFBQSxFQUNILENBQUM7QUFDSDtBQVFBLFNBQVMsTUFBTSxJQUEyQjtBQUN4QyxTQUFPLElBQUksUUFBUSxDQUFDLFlBQVk7QUFDOUIsZUFBVyxTQUFTLEVBQUU7QUFBQSxFQUN4QixDQUFDO0FBQ0g7QUFPQSxlQUFlLGdCQUFnQixRQUErQjtBQUM1RCxRQUFNLFdBQVcsS0FBSyxJQUFJLElBQUk7QUFDOUIsU0FBTyxLQUFLLElBQUksSUFBSSxVQUFVO0FBQzVCLFFBQUk7QUFDRixZQUFNLFdBQVcsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPO0FBQzdDLFlBQU0sVUFBVSxNQUFNLFNBQVMsS0FBSztBQUNwQyxVQUFJLFFBQVEsS0FBSyxDQUFDLFdBQVcsT0FBTyxTQUFTLE1BQU0sR0FBRztBQUNwRDtBQUFBLE1BQ0Y7QUFBQSxJQUNGLFFBQVE7QUFBQSxJQUVSO0FBQ0EsVUFBTSxNQUFNLHVDQUF1QztBQUFBLEVBQ3JEO0FBQ0EsUUFBTSxJQUFJO0FBQUEsSUFDUixpREFBaUQsTUFBTSxXQUFXLE9BQU8saUNBQWlDLENBQUM7QUFBQSxFQUM3RztBQUNGOyIsCiAgIm5hbWVzIjogW10KfQo=
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Integration-time helpers for provisioning a specific Obsidian **asar** (the
|
|
5
|
+
* app code) into an isolated user-data dir.
|
|
6
|
+
*
|
|
7
|
+
* Obsidian's bootstrap loads the highest of (its shell's bundled
|
|
8
|
+
* `resources/obsidian.asar`, the `obsidian-<version>.asar` files in the
|
|
9
|
+
* user-data dir). So placing an asar here only takes effect when its version is
|
|
10
|
+
* **>= the shell's bundled version** (upgrade-only — confirmed by the Phase 0
|
|
11
|
+
* spike). Running an older version requires pinning the installer/shell instead
|
|
12
|
+
* (see `obsidian-installer.ts`).
|
|
13
|
+
*/
|
|
14
|
+
import type { DesktopReleasesManifest } from './obsidian-version.mjs';
|
|
15
|
+
/**
|
|
16
|
+
* A discovered `obsidian-<version>.asar` file.
|
|
17
|
+
*/
|
|
18
|
+
export interface DiscoveredAsar {
|
|
19
|
+
/** Absolute path to the asar file. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
/** The `x.y.z` version parsed from the file name. */
|
|
22
|
+
readonly version: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Copies an asar file into a user-data dir under its canonical
|
|
26
|
+
* `obsidian-<version>.asar` name.
|
|
27
|
+
*
|
|
28
|
+
* @param asarPath - Source asar path.
|
|
29
|
+
* @param version - The asar's version (used for the destination file name).
|
|
30
|
+
* @param userDataDir - Destination user-data dir.
|
|
31
|
+
*/
|
|
32
|
+
export declare function copyAsarIntoUserData(asarPath: string, version: string, userDataDir: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Ensures the asar for a concrete version is present in the local cache,
|
|
35
|
+
* downloading and decompressing it if necessary.
|
|
36
|
+
*
|
|
37
|
+
* @param version - A concrete `x.y.z` version.
|
|
38
|
+
* @returns The absolute path to the cached asar.
|
|
39
|
+
* @throws Error if the download fails.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ensureAsarCached(version: string): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Fetches and parses Obsidian's desktop releases manifest.
|
|
44
|
+
*
|
|
45
|
+
* @returns The parsed manifest.
|
|
46
|
+
* @throws Error if the manifest cannot be fetched.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fetchDesktopReleasesManifest(): Promise<DesktopReleasesManifest>;
|
|
49
|
+
/**
|
|
50
|
+
* Finds the newest `obsidian-<version>.asar` in a directory.
|
|
51
|
+
*
|
|
52
|
+
* @param dir - The directory to scan (e.g. the user's Obsidian config dir).
|
|
53
|
+
* @returns The newest discovered asar, or `undefined` if none / the dir is unreadable.
|
|
54
|
+
*/
|
|
55
|
+
export declare function findNewestAsar(dir: string): DiscoveredAsar | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Resolves a version specifier (explicit or channel alias) to a concrete
|
|
58
|
+
* `x.y.z` version, fetching the manifest only when a channel alias is used.
|
|
59
|
+
*
|
|
60
|
+
* @param spec - The version specifier.
|
|
61
|
+
* @returns The concrete version.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveConcreteVersion(spec: string): Promise<string>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
writeFileSync
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { gunzipSync } from "node:zlib";
|
|
11
|
+
import { log } from "./log.mjs";
|
|
12
|
+
import {
|
|
13
|
+
compareVersions,
|
|
14
|
+
DESKTOP_RELEASES_MANIFEST_URL,
|
|
15
|
+
getVersionAsarFileName,
|
|
16
|
+
parseVersionSpec,
|
|
17
|
+
resolveVersionFromManifest
|
|
18
|
+
} from "./obsidian-version.mjs";
|
|
19
|
+
const DOWNLOAD_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";
|
|
20
|
+
const CACHE_ROOT = join(tmpdir(), "obsidian-integration-testing");
|
|
21
|
+
const ASAR_CACHE_DIR = join(CACHE_ROOT, "asar-cache");
|
|
22
|
+
const ASAR_FILE_PATTERN = /^obsidian-(?<version>\d+\.\d+\.\d+)\.asar$/;
|
|
23
|
+
function copyAsarIntoUserData(asarPath, version, userDataDir) {
|
|
24
|
+
mkdirSync(userDataDir, { recursive: true });
|
|
25
|
+
const dest = join(userDataDir, getVersionAsarFileName(version));
|
|
26
|
+
copyFileSync(asarPath, dest);
|
|
27
|
+
log(`[version-switch] Provisioned ${getVersionAsarFileName(version)} into ${userDataDir}.`);
|
|
28
|
+
}
|
|
29
|
+
async function ensureAsarCached(version) {
|
|
30
|
+
mkdirSync(ASAR_CACHE_DIR, { recursive: true });
|
|
31
|
+
const cachedPath = join(ASAR_CACHE_DIR, getVersionAsarFileName(version));
|
|
32
|
+
if (existsSync(cachedPath)) {
|
|
33
|
+
log(`[version-switch] Using cached asar for ${version}.`);
|
|
34
|
+
return cachedPath;
|
|
35
|
+
}
|
|
36
|
+
const url = `https://releases.obsidian.md/release/${getVersionAsarFileName(version)}.gz`;
|
|
37
|
+
log(`[version-switch] Downloading asar ${version} from ${url} ...`);
|
|
38
|
+
const response = await fetch(url, { headers: { "User-Agent": DOWNLOAD_USER_AGENT } });
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Failed to download Obsidian asar ${version}: HTTP ${String(response.status)} from ${url}`);
|
|
41
|
+
}
|
|
42
|
+
const gz = Buffer.from(await response.arrayBuffer());
|
|
43
|
+
writeFileSync(cachedPath, gunzipSync(gz));
|
|
44
|
+
log(`[version-switch] Cached asar ${version} (${String(gz.length)} bytes compressed) -> ${cachedPath}`);
|
|
45
|
+
return cachedPath;
|
|
46
|
+
}
|
|
47
|
+
async function fetchDesktopReleasesManifest() {
|
|
48
|
+
const response = await fetch(DESKTOP_RELEASES_MANIFEST_URL);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`Failed to fetch desktop releases manifest: HTTP ${String(response.status)}`);
|
|
51
|
+
}
|
|
52
|
+
return await response.json();
|
|
53
|
+
}
|
|
54
|
+
function findNewestAsar(dir) {
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = readdirSync(dir);
|
|
58
|
+
} catch {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
let newest;
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const match = ASAR_FILE_PATTERN.exec(entry);
|
|
64
|
+
if (!match) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const version = match.groups?.["version"] ?? "";
|
|
68
|
+
if (!newest || compareVersions(version, newest.version) > 0) {
|
|
69
|
+
newest = { path: join(dir, entry), version };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return newest;
|
|
73
|
+
}
|
|
74
|
+
async function resolveConcreteVersion(spec) {
|
|
75
|
+
const parsed = parseVersionSpec(spec);
|
|
76
|
+
if (parsed.kind === "explicit") {
|
|
77
|
+
return parsed.version;
|
|
78
|
+
}
|
|
79
|
+
const manifest = await fetchDesktopReleasesManifest();
|
|
80
|
+
return resolveVersionFromManifest(manifest, parsed.channel);
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
copyAsarIntoUserData,
|
|
84
|
+
ensureAsarCached,
|
|
85
|
+
fetchDesktopReleasesManifest,
|
|
86
|
+
findNewestAsar,
|
|
87
|
+
resolveConcreteVersion
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL29ic2lkaWFuLXZlcnNpb24tc3dpdGNoLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogSW50ZWdyYXRpb24tdGltZSBoZWxwZXJzIGZvciBwcm92aXNpb25pbmcgYSBzcGVjaWZpYyBPYnNpZGlhbiAqKmFzYXIqKiAodGhlXG4gKiBhcHAgY29kZSkgaW50byBhbiBpc29sYXRlZCB1c2VyLWRhdGEgZGlyLlxuICpcbiAqIE9ic2lkaWFuJ3MgYm9vdHN0cmFwIGxvYWRzIHRoZSBoaWdoZXN0IG9mIChpdHMgc2hlbGwncyBidW5kbGVkXG4gKiBgcmVzb3VyY2VzL29ic2lkaWFuLmFzYXJgLCB0aGUgYG9ic2lkaWFuLTx2ZXJzaW9uPi5hc2FyYCBmaWxlcyBpbiB0aGVcbiAqIHVzZXItZGF0YSBkaXIpLiBTbyBwbGFjaW5nIGFuIGFzYXIgaGVyZSBvbmx5IHRha2VzIGVmZmVjdCB3aGVuIGl0cyB2ZXJzaW9uIGlzXG4gKiAqKj49IHRoZSBzaGVsbCdzIGJ1bmRsZWQgdmVyc2lvbioqICh1cGdyYWRlLW9ubHkgXHUyMDE0IGNvbmZpcm1lZCBieSB0aGUgUGhhc2UgMFxuICogc3Bpa2UpLiBSdW5uaW5nIGFuIG9sZGVyIHZlcnNpb24gcmVxdWlyZXMgcGlubmluZyB0aGUgaW5zdGFsbGVyL3NoZWxsIGluc3RlYWRcbiAqIChzZWUgYG9ic2lkaWFuLWluc3RhbGxlci50c2ApLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGRvd25sb2FkL2ZpbGVzeXN0ZW0gZ2x1ZSBjb3ZlcmVkIGJ5IGludGVncmF0aW9uIHRlc3RzLCBub3QgdW5pdCB0ZXN0cy4gKi9cblxuaW1wb3J0IHtcbiAgY29weUZpbGVTeW5jLFxuICBleGlzdHNTeW5jLFxuICBta2RpclN5bmMsXG4gIHJlYWRkaXJTeW5jLFxuICB3cml0ZUZpbGVTeW5jXG59IGZyb20gJ25vZGU6ZnMnO1xuaW1wb3J0IHsgdG1wZGlyIH0gZnJvbSAnbm9kZTpvcyc7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGd1bnppcFN5bmMgfSBmcm9tICdub2RlOnpsaWInO1xuXG5pbXBvcnQgdHlwZSB7IERlc2t0b3BSZWxlYXNlc01hbmlmZXN0IH0gZnJvbSAnLi9vYnNpZGlhbi12ZXJzaW9uLm1qcyc7XG5cbmltcG9ydCB7IGxvZyB9IGZyb20gJy4vbG9nLm1qcyc7XG5pbXBvcnQge1xuICBjb21wYXJlVmVyc2lvbnMsXG4gIERFU0tUT1BfUkVMRUFTRVNfTUFOSUZFU1RfVVJMLFxuICBnZXRWZXJzaW9uQXNhckZpbGVOYW1lLFxuICBwYXJzZVZlcnNpb25TcGVjLFxuICByZXNvbHZlVmVyc2lvbkZyb21NYW5pZmVzdFxufSBmcm9tICcuL29ic2lkaWFuLXZlcnNpb24ubWpzJztcblxuLyoqXG4gKiBCcm93c2VyIFVzZXItQWdlbnQgcmVxdWlyZWQgdG8gZmV0Y2ggYXNhciBwYWNrYWdlcyBmcm9tXG4gKiBgcmVsZWFzZXMub2JzaWRpYW4ubWRgICh0aGUgZW5kcG9pbnQgaXMgQ2xvdWRmbGFyZS1nYXRlZCBhZ2FpbnN0IG5vbi1icm93c2VyXG4gKiBjbGllbnRzKS4gTWF0Y2hlcyB0aGUgVUEgZG9jdW1lbnRlZCBpbiB0aGUgb2JzaWRpYW4tdmVyc2lvbnMgcmVwby5cbiAqL1xuY29uc3QgRE9XTkxPQURfVVNFUl9BR0VOVCA9ICdNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvODYuMC40MjQwLjE4MyBTYWZhcmkvNTM3LjM2JztcblxuY29uc3QgQ0FDSEVfUk9PVCA9IGpvaW4odG1wZGlyKCksICdvYnNpZGlhbi1pbnRlZ3JhdGlvbi10ZXN0aW5nJyk7XG5jb25zdCBBU0FSX0NBQ0hFX0RJUiA9IGpvaW4oQ0FDSEVfUk9PVCwgJ2FzYXItY2FjaGUnKTtcbmNvbnN0IEFTQVJfRklMRV9QQVRURVJOID0gL15vYnNpZGlhbi0oPzx2ZXJzaW9uPlxcZCtcXC5cXGQrXFwuXFxkKylcXC5hc2FyJC87XG5cbi8qKlxuICogQSBkaXNjb3ZlcmVkIGBvYnNpZGlhbi08dmVyc2lvbj4uYXNhcmAgZmlsZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEaXNjb3ZlcmVkQXNhciB7XG4gIC8qKiBBYnNvbHV0ZSBwYXRoIHRvIHRoZSBhc2FyIGZpbGUuICovXG4gIHJlYWRvbmx5IHBhdGg6IHN0cmluZztcblxuICAvKiogVGhlIGB4LnkuemAgdmVyc2lvbiBwYXJzZWQgZnJvbSB0aGUgZmlsZSBuYW1lLiAqL1xuICByZWFkb25seSB2ZXJzaW9uOiBzdHJpbmc7XG59XG5cbi8qKlxuICogQ29waWVzIGFuIGFzYXIgZmlsZSBpbnRvIGEgdXNlci1kYXRhIGRpciB1bmRlciBpdHMgY2Fub25pY2FsXG4gKiBgb2JzaWRpYW4tPHZlcnNpb24+LmFzYXJgIG5hbWUuXG4gKlxuICogQHBhcmFtIGFzYXJQYXRoIC0gU291cmNlIGFzYXIgcGF0aC5cbiAqIEBwYXJhbSB2ZXJzaW9uIC0gVGhlIGFzYXIncyB2ZXJzaW9uICh1c2VkIGZvciB0aGUgZGVzdGluYXRpb24gZmlsZSBuYW1lKS5cbiAqIEBwYXJhbSB1c2VyRGF0YURpciAtIERlc3RpbmF0aW9uIHVzZXItZGF0YSBkaXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb3B5QXNhckludG9Vc2VyRGF0YShhc2FyUGF0aDogc3RyaW5nLCB2ZXJzaW9uOiBzdHJpbmcsIHVzZXJEYXRhRGlyOiBzdHJpbmcpOiB2b2lkIHtcbiAgbWtkaXJTeW5jKHVzZXJEYXRhRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgY29uc3QgZGVzdCA9IGpvaW4odXNlckRhdGFEaXIsIGdldFZlcnNpb25Bc2FyRmlsZU5hbWUodmVyc2lvbikpO1xuICBjb3B5RmlsZVN5bmMoYXNhclBhdGgsIGRlc3QpO1xuICBsb2coYFt2ZXJzaW9uLXN3aXRjaF0gUHJvdmlzaW9uZWQgJHtnZXRWZXJzaW9uQXNhckZpbGVOYW1lKHZlcnNpb24pfSBpbnRvICR7dXNlckRhdGFEaXJ9LmApO1xufVxuXG4vKipcbiAqIEVuc3VyZXMgdGhlIGFzYXIgZm9yIGEgY29uY3JldGUgdmVyc2lvbiBpcyBwcmVzZW50IGluIHRoZSBsb2NhbCBjYWNoZSxcbiAqIGRvd25sb2FkaW5nIGFuZCBkZWNvbXByZXNzaW5nIGl0IGlmIG5lY2Vzc2FyeS5cbiAqXG4gKiBAcGFyYW0gdmVyc2lvbiAtIEEgY29uY3JldGUgYHgueS56YCB2ZXJzaW9uLlxuICogQHJldHVybnMgVGhlIGFic29sdXRlIHBhdGggdG8gdGhlIGNhY2hlZCBhc2FyLlxuICogQHRocm93cyBFcnJvciBpZiB0aGUgZG93bmxvYWQgZmFpbHMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBlbnN1cmVBc2FyQ2FjaGVkKHZlcnNpb246IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gIG1rZGlyU3luYyhBU0FSX0NBQ0hFX0RJUiwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gIGNvbnN0IGNhY2hlZFBhdGggPSBqb2luKEFTQVJfQ0FDSEVfRElSLCBnZXRWZXJzaW9uQXNhckZpbGVOYW1lKHZlcnNpb24pKTtcbiAgaWYgKGV4aXN0c1N5bmMoY2FjaGVkUGF0aCkpIHtcbiAgICBsb2coYFt2ZXJzaW9uLXN3aXRjaF0gVXNpbmcgY2FjaGVkIGFzYXIgZm9yICR7dmVyc2lvbn0uYCk7XG4gICAgcmV0dXJuIGNhY2hlZFBhdGg7XG4gIH1cblxuICBjb25zdCB1cmwgPSBgaHR0cHM6Ly9yZWxlYXNlcy5vYnNpZGlhbi5tZC9yZWxlYXNlLyR7Z2V0VmVyc2lvbkFzYXJGaWxlTmFtZSh2ZXJzaW9uKX0uZ3pgO1xuICBsb2coYFt2ZXJzaW9uLXN3aXRjaF0gRG93bmxvYWRpbmcgYXNhciAke3ZlcnNpb259IGZyb20gJHt1cmx9IC4uLmApO1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwgeyBoZWFkZXJzOiB7ICdVc2VyLUFnZW50JzogRE9XTkxPQURfVVNFUl9BR0VOVCB9IH0pO1xuICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gZG93bmxvYWQgT2JzaWRpYW4gYXNhciAke3ZlcnNpb259OiBIVFRQICR7U3RyaW5nKHJlc3BvbnNlLnN0YXR1cyl9IGZyb20gJHt1cmx9YCk7XG4gIH1cblxuICBjb25zdCBneiA9IEJ1ZmZlci5mcm9tKGF3YWl0IHJlc3BvbnNlLmFycmF5QnVmZmVyKCkpO1xuICB3cml0ZUZpbGVTeW5jKGNhY2hlZFBhdGgsIGd1bnppcFN5bmMoZ3opKTtcbiAgbG9nKGBbdmVyc2lvbi1zd2l0Y2hdIENhY2hlZCBhc2FyICR7dmVyc2lvbn0gKCR7U3RyaW5nKGd6Lmxlbmd0aCl9IGJ5dGVzIGNvbXByZXNzZWQpIC0+ICR7Y2FjaGVkUGF0aH1gKTtcbiAgcmV0dXJuIGNhY2hlZFBhdGg7XG59XG5cbi8qKlxuICogRmV0Y2hlcyBhbmQgcGFyc2VzIE9ic2lkaWFuJ3MgZGVza3RvcCByZWxlYXNlcyBtYW5pZmVzdC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgcGFyc2VkIG1hbmlmZXN0LlxuICogQHRocm93cyBFcnJvciBpZiB0aGUgbWFuaWZlc3QgY2Fubm90IGJlIGZldGNoZWQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBmZXRjaERlc2t0b3BSZWxlYXNlc01hbmlmZXN0KCk6IFByb21pc2U8RGVza3RvcFJlbGVhc2VzTWFuaWZlc3Q+IHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChERVNLVE9QX1JFTEVBU0VTX01BTklGRVNUX1VSTCk7XG4gIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBmZXRjaCBkZXNrdG9wIHJlbGVhc2VzIG1hbmlmZXN0OiBIVFRQICR7U3RyaW5nKHJlc3BvbnNlLnN0YXR1cyl9YCk7XG4gIH1cbiAgcmV0dXJuIGF3YWl0IHJlc3BvbnNlLmpzb24oKSBhcyBEZXNrdG9wUmVsZWFzZXNNYW5pZmVzdDtcbn1cblxuLyoqXG4gKiBGaW5kcyB0aGUgbmV3ZXN0IGBvYnNpZGlhbi08dmVyc2lvbj4uYXNhcmAgaW4gYSBkaXJlY3RvcnkuXG4gKlxuICogQHBhcmFtIGRpciAtIFRoZSBkaXJlY3RvcnkgdG8gc2NhbiAoZS5nLiB0aGUgdXNlcidzIE9ic2lkaWFuIGNvbmZpZyBkaXIpLlxuICogQHJldHVybnMgVGhlIG5ld2VzdCBkaXNjb3ZlcmVkIGFzYXIsIG9yIGB1bmRlZmluZWRgIGlmIG5vbmUgLyB0aGUgZGlyIGlzIHVucmVhZGFibGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmaW5kTmV3ZXN0QXNhcihkaXI6IHN0cmluZyk6IERpc2NvdmVyZWRBc2FyIHwgdW5kZWZpbmVkIHtcbiAgbGV0IGVudHJpZXM6IHN0cmluZ1tdO1xuICB0cnkge1xuICAgIGVudHJpZXMgPSByZWFkZGlyU3luYyhkaXIpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgbGV0IG5ld2VzdDogRGlzY292ZXJlZEFzYXIgfCB1bmRlZmluZWQ7XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGNvbnN0IG1hdGNoID0gQVNBUl9GSUxFX1BBVFRFUk4uZXhlYyhlbnRyeSk7XG4gICAgaWYgKCFtYXRjaCkge1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGNvbnN0IHZlcnNpb24gPSBtYXRjaC5ncm91cHM/LlsndmVyc2lvbiddID8/ICcnO1xuICAgIGlmICghbmV3ZXN0IHx8IGNvbXBhcmVWZXJzaW9ucyh2ZXJzaW9uLCBuZXdlc3QudmVyc2lvbikgPiAwKSB7XG4gICAgICBuZXdlc3QgPSB7IHBhdGg6IGpvaW4oZGlyLCBlbnRyeSksIHZlcnNpb24gfTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIG5ld2VzdDtcbn1cblxuLyoqXG4gKiBSZXNvbHZlcyBhIHZlcnNpb24gc3BlY2lmaWVyIChleHBsaWNpdCBvciBjaGFubmVsIGFsaWFzKSB0byBhIGNvbmNyZXRlXG4gKiBgeC55LnpgIHZlcnNpb24sIGZldGNoaW5nIHRoZSBtYW5pZmVzdCBvbmx5IHdoZW4gYSBjaGFubmVsIGFsaWFzIGlzIHVzZWQuXG4gKlxuICogQHBhcmFtIHNwZWMgLSBUaGUgdmVyc2lvbiBzcGVjaWZpZXIuXG4gKiBAcmV0dXJucyBUaGUgY29uY3JldGUgdmVyc2lvbi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlc29sdmVDb25jcmV0ZVZlcnNpb24oc3BlYzogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgcGFyc2VkID0gcGFyc2VWZXJzaW9uU3BlYyhzcGVjKTtcbiAgaWYgKHBhcnNlZC5raW5kID09PSAnZXhwbGljaXQnKSB7XG4gICAgcmV0dXJuIHBhcnNlZC52ZXJzaW9uO1xuICB9XG4gIGNvbnN0IG1hbmlmZXN0ID0gYXdhaXQgZmV0Y2hEZXNrdG9wUmVsZWFzZXNNYW5pZmVzdCgpO1xuICByZXR1cm4gcmVzb2x2ZVZlcnNpb25Gcm9tTWFuaWZlc3QobWFuaWZlc3QsIHBhcnNlZC5jaGFubmVsKTtcbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQWdCQTtBQUFBLEVBQ0U7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMsY0FBYztBQUN2QixTQUFTLFlBQVk7QUFDckIsU0FBUyxrQkFBa0I7QUFJM0IsU0FBUyxXQUFXO0FBQ3BCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxPQUNLO0FBT1AsTUFBTSxzQkFBc0I7QUFFNUIsTUFBTSxhQUFhLEtBQUssT0FBTyxHQUFHLDhCQUE4QjtBQUNoRSxNQUFNLGlCQUFpQixLQUFLLFlBQVksWUFBWTtBQUNwRCxNQUFNLG9CQUFvQjtBQXFCbkIsU0FBUyxxQkFBcUIsVUFBa0IsU0FBaUIsYUFBMkI7QUFDakcsWUFBVSxhQUFhLEVBQUUsV0FBVyxLQUFLLENBQUM7QUFDMUMsUUFBTSxPQUFPLEtBQUssYUFBYSx1QkFBdUIsT0FBTyxDQUFDO0FBQzlELGVBQWEsVUFBVSxJQUFJO0FBQzNCLE1BQUksZ0NBQWdDLHVCQUF1QixPQUFPLENBQUMsU0FBUyxXQUFXLEdBQUc7QUFDNUY7QUFVQSxlQUFzQixpQkFBaUIsU0FBa0M7QUFDdkUsWUFBVSxnQkFBZ0IsRUFBRSxXQUFXLEtBQUssQ0FBQztBQUM3QyxRQUFNLGFBQWEsS0FBSyxnQkFBZ0IsdUJBQXVCLE9BQU8sQ0FBQztBQUN2RSxNQUFJLFdBQVcsVUFBVSxHQUFHO0FBQzFCLFFBQUksMENBQTBDLE9BQU8sR0FBRztBQUN4RCxXQUFPO0FBQUEsRUFDVDtBQUVBLFFBQU0sTUFBTSx3Q0FBd0MsdUJBQXVCLE9BQU8sQ0FBQztBQUNuRixNQUFJLHFDQUFxQyxPQUFPLFNBQVMsR0FBRyxNQUFNO0FBQ2xFLFFBQU0sV0FBVyxNQUFNLE1BQU0sS0FBSyxFQUFFLFNBQVMsRUFBRSxjQUFjLG9CQUFvQixFQUFFLENBQUM7QUFDcEYsTUFBSSxDQUFDLFNBQVMsSUFBSTtBQUNoQixVQUFNLElBQUksTUFBTSxvQ0FBb0MsT0FBTyxVQUFVLE9BQU8sU0FBUyxNQUFNLENBQUMsU0FBUyxHQUFHLEVBQUU7QUFBQSxFQUM1RztBQUVBLFFBQU0sS0FBSyxPQUFPLEtBQUssTUFBTSxTQUFTLFlBQVksQ0FBQztBQUNuRCxnQkFBYyxZQUFZLFdBQVcsRUFBRSxDQUFDO0FBQ3hDLE1BQUksZ0NBQWdDLE9BQU8sS0FBSyxPQUFPLEdBQUcsTUFBTSxDQUFDLHlCQUF5QixVQUFVLEVBQUU7QUFDdEcsU0FBTztBQUNUO0FBUUEsZUFBc0IsK0JBQWlFO0FBQ3JGLFFBQU0sV0FBVyxNQUFNLE1BQU0sNkJBQTZCO0FBQzFELE1BQUksQ0FBQyxTQUFTLElBQUk7QUFDaEIsVUFBTSxJQUFJLE1BQU0sbURBQW1ELE9BQU8sU0FBUyxNQUFNLENBQUMsRUFBRTtBQUFBLEVBQzlGO0FBQ0EsU0FBTyxNQUFNLFNBQVMsS0FBSztBQUM3QjtBQVFPLFNBQVMsZUFBZSxLQUF5QztBQUN0RSxNQUFJO0FBQ0osTUFBSTtBQUNGLGNBQVUsWUFBWSxHQUFHO0FBQUEsRUFDM0IsUUFBUTtBQUNOLFdBQU87QUFBQSxFQUNUO0FBRUEsTUFBSTtBQUNKLGFBQVcsU0FBUyxTQUFTO0FBQzNCLFVBQU0sUUFBUSxrQkFBa0IsS0FBSyxLQUFLO0FBQzFDLFFBQUksQ0FBQyxPQUFPO0FBQ1Y7QUFBQSxJQUNGO0FBQ0EsVUFBTSxVQUFVLE1BQU0sU0FBUyxTQUFTLEtBQUs7QUFDN0MsUUFBSSxDQUFDLFVBQVUsZ0JBQWdCLFNBQVMsT0FBTyxPQUFPLElBQUksR0FBRztBQUMzRCxlQUFTLEVBQUUsTUFBTSxLQUFLLEtBQUssS0FBSyxHQUFHLFFBQVE7QUFBQSxJQUM3QztBQUFBLEVBQ0Y7QUFDQSxTQUFPO0FBQ1Q7QUFTQSxlQUFzQix1QkFBdUIsTUFBK0I7QUFDMUUsUUFBTSxTQUFTLGlCQUFpQixJQUFJO0FBQ3BDLE1BQUksT0FBTyxTQUFTLFlBQVk7QUFDOUIsV0FBTyxPQUFPO0FBQUEsRUFDaEI7QUFDQSxRQUFNLFdBQVcsTUFBTSw2QkFBNkI7QUFDcEQsU0FBTywyQkFBMkIsVUFBVSxPQUFPLE9BQU87QUFDNUQ7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers for resolving Obsidian desktop version specifiers.
|
|
5
|
+
*
|
|
6
|
+
* A version specifier is either an explicit `x.y.z` string or one of the
|
|
7
|
+
* channel aliases {@link PUBLIC_LATEST} / {@link CATALYST_LATEST}, which resolve
|
|
8
|
+
* against Obsidian's published desktop releases manifest.
|
|
9
|
+
*
|
|
10
|
+
* The network call that fetches the manifest lives in the integration-time
|
|
11
|
+
* version-switch module; everything here is pure and unit-tested.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Version specifier alias resolving to the latest **public** desktop release
|
|
15
|
+
* (the manifest's `latestVersion`).
|
|
16
|
+
*/
|
|
17
|
+
export declare const PUBLIC_LATEST = "public-latest";
|
|
18
|
+
/**
|
|
19
|
+
* Version specifier alias resolving to the latest **catalyst** (early access /
|
|
20
|
+
* insider) desktop release (the manifest's `beta.latestVersion`).
|
|
21
|
+
*/
|
|
22
|
+
export declare const CATALYST_LATEST = "catalyst-latest";
|
|
23
|
+
/**
|
|
24
|
+
* URL of Obsidian's desktop releases manifest (raw JSON).
|
|
25
|
+
*/
|
|
26
|
+
export declare const DESKTOP_RELEASES_MANIFEST_URL = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/desktop-releases.json";
|
|
27
|
+
/**
|
|
28
|
+
* A version specifier resolved against a release channel.
|
|
29
|
+
*/
|
|
30
|
+
export interface ChannelVersionSpec {
|
|
31
|
+
/** The channel to resolve against the manifest. */
|
|
32
|
+
readonly channel: ReleaseChannel;
|
|
33
|
+
/** Discriminant. */
|
|
34
|
+
readonly kind: 'channel';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The `beta` (catalyst) entry of {@link DesktopReleasesManifest}.
|
|
38
|
+
*/
|
|
39
|
+
export interface DesktopReleasesBeta {
|
|
40
|
+
/** The latest catalyst version, e.g. `'1.13.1'`. */
|
|
41
|
+
readonly latestVersion: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The subset of Obsidian's `desktop-releases.json` this library reads.
|
|
45
|
+
*/
|
|
46
|
+
export interface DesktopReleasesManifest {
|
|
47
|
+
/**
|
|
48
|
+
* The latest catalyst (early access / insider) release, when present.
|
|
49
|
+
*/
|
|
50
|
+
readonly beta?: DesktopReleasesBeta;
|
|
51
|
+
/**
|
|
52
|
+
* The latest public (stable) version, e.g. `'1.12.7'`.
|
|
53
|
+
*/
|
|
54
|
+
readonly latestVersion: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A version specifier naming a concrete `x.y.z` version.
|
|
58
|
+
*/
|
|
59
|
+
export interface ExplicitVersionSpec {
|
|
60
|
+
/** Discriminant. */
|
|
61
|
+
readonly kind: 'explicit';
|
|
62
|
+
/** The concrete `x.y.z` version. */
|
|
63
|
+
readonly version: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A release channel resolved from a channel-alias specifier.
|
|
67
|
+
*
|
|
68
|
+
* - `public` — the latest stable release.
|
|
69
|
+
* - `catalyst` — the latest early access / insider release.
|
|
70
|
+
*/
|
|
71
|
+
export type ReleaseChannel = 'catalyst' | 'public';
|
|
72
|
+
/**
|
|
73
|
+
* The parsed form of a version specifier.
|
|
74
|
+
*
|
|
75
|
+
* - `explicit` — a concrete `x.y.z` version.
|
|
76
|
+
* - `channel` — a channel alias to be resolved against the manifest.
|
|
77
|
+
*/
|
|
78
|
+
export type VersionSpec = ChannelVersionSpec | ExplicitVersionSpec;
|
|
79
|
+
/**
|
|
80
|
+
* Compares two `x.y.z` version strings numerically, segment by segment.
|
|
81
|
+
*
|
|
82
|
+
* @param a - The first version.
|
|
83
|
+
* @param b - The second version.
|
|
84
|
+
* @returns A negative number if `a < b`, `0` if equal, a positive number if `a > b`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
87
|
+
/**
|
|
88
|
+
* Returns the user-data asar file name for a given version, matching the name
|
|
89
|
+
* Obsidian itself uses when it downloads an update (`obsidian-<version>.asar`).
|
|
90
|
+
*
|
|
91
|
+
* @param version - A concrete `x.y.z` version.
|
|
92
|
+
* @returns The asar file name.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getVersionAsarFileName(version: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Parses a version specifier into either an explicit version or a channel alias.
|
|
97
|
+
*
|
|
98
|
+
* @param spec - The specifier: an `x.y.z` string, {@link PUBLIC_LATEST}, or {@link CATALYST_LATEST}.
|
|
99
|
+
* @returns The parsed {@link VersionSpec}.
|
|
100
|
+
* @throws Error if the specifier is neither a valid `x.y.z` version nor a known alias.
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseVersionSpec(spec: string): VersionSpec;
|
|
103
|
+
/**
|
|
104
|
+
* Resolves a channel to a concrete version using a desktop releases manifest.
|
|
105
|
+
*
|
|
106
|
+
* @param manifest - The parsed manifest.
|
|
107
|
+
* @param channel - The channel to resolve.
|
|
108
|
+
* @returns The concrete `x.y.z` version for the channel.
|
|
109
|
+
* @throws Error if the catalyst channel is requested but the manifest has no `beta` entry.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveVersionFromManifest(manifest: DesktopReleasesManifest, channel: ReleaseChannel): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const PUBLIC_LATEST = "public-latest";
|
|
2
|
+
const CATALYST_LATEST = "catalyst-latest";
|
|
3
|
+
const DESKTOP_RELEASES_MANIFEST_URL = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/desktop-releases.json";
|
|
4
|
+
const EXPLICIT_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
5
|
+
const VERSION_SEGMENT_COUNT = 3;
|
|
6
|
+
function compareVersions(a, b) {
|
|
7
|
+
const aSegments = parseVersionSegments(a);
|
|
8
|
+
const bSegments = parseVersionSegments(b);
|
|
9
|
+
for (let i = 0; i < VERSION_SEGMENT_COUNT; i++) {
|
|
10
|
+
const diff = (aSegments[i] ?? 0) - (bSegments[i] ?? 0);
|
|
11
|
+
if (diff !== 0) {
|
|
12
|
+
return diff;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
function getVersionAsarFileName(version) {
|
|
18
|
+
return `obsidian-${version}.asar`;
|
|
19
|
+
}
|
|
20
|
+
function parseVersionSpec(spec) {
|
|
21
|
+
if (spec === PUBLIC_LATEST) {
|
|
22
|
+
return { channel: "public", kind: "channel" };
|
|
23
|
+
}
|
|
24
|
+
if (spec === CATALYST_LATEST) {
|
|
25
|
+
return { channel: "catalyst", kind: "channel" };
|
|
26
|
+
}
|
|
27
|
+
if (EXPLICIT_VERSION_PATTERN.test(spec)) {
|
|
28
|
+
return { kind: "explicit", version: spec };
|
|
29
|
+
}
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid Obsidian version "${spec}". Expected an "x.y.z" version, "${PUBLIC_LATEST}", or "${CATALYST_LATEST}".`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function resolveVersionFromManifest(manifest, channel) {
|
|
35
|
+
if (channel === "public") {
|
|
36
|
+
return manifest.latestVersion;
|
|
37
|
+
}
|
|
38
|
+
if (!manifest.beta) {
|
|
39
|
+
throw new Error("Desktop releases manifest has no catalyst (beta) release.");
|
|
40
|
+
}
|
|
41
|
+
return manifest.beta.latestVersion;
|
|
42
|
+
}
|
|
43
|
+
function parseVersionSegments(version) {
|
|
44
|
+
return version.split(".").map((segment) => Number.parseInt(segment, 10));
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
CATALYST_LATEST,
|
|
48
|
+
DESKTOP_RELEASES_MANIFEST_URL,
|
|
49
|
+
PUBLIC_LATEST,
|
|
50
|
+
compareVersions,
|
|
51
|
+
getVersionAsarFileName,
|
|
52
|
+
parseVersionSpec,
|
|
53
|
+
resolveVersionFromManifest
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL29ic2lkaWFuLXZlcnNpb24udHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBQdXJlIGhlbHBlcnMgZm9yIHJlc29sdmluZyBPYnNpZGlhbiBkZXNrdG9wIHZlcnNpb24gc3BlY2lmaWVycy5cbiAqXG4gKiBBIHZlcnNpb24gc3BlY2lmaWVyIGlzIGVpdGhlciBhbiBleHBsaWNpdCBgeC55LnpgIHN0cmluZyBvciBvbmUgb2YgdGhlXG4gKiBjaGFubmVsIGFsaWFzZXMge0BsaW5rIFBVQkxJQ19MQVRFU1R9IC8ge0BsaW5rIENBVEFMWVNUX0xBVEVTVH0sIHdoaWNoIHJlc29sdmVcbiAqIGFnYWluc3QgT2JzaWRpYW4ncyBwdWJsaXNoZWQgZGVza3RvcCByZWxlYXNlcyBtYW5pZmVzdC5cbiAqXG4gKiBUaGUgbmV0d29yayBjYWxsIHRoYXQgZmV0Y2hlcyB0aGUgbWFuaWZlc3QgbGl2ZXMgaW4gdGhlIGludGVncmF0aW9uLXRpbWVcbiAqIHZlcnNpb24tc3dpdGNoIG1vZHVsZTsgZXZlcnl0aGluZyBoZXJlIGlzIHB1cmUgYW5kIHVuaXQtdGVzdGVkLlxuICovXG5cbi8qKlxuICogVmVyc2lvbiBzcGVjaWZpZXIgYWxpYXMgcmVzb2x2aW5nIHRvIHRoZSBsYXRlc3QgKipwdWJsaWMqKiBkZXNrdG9wIHJlbGVhc2VcbiAqICh0aGUgbWFuaWZlc3QncyBgbGF0ZXN0VmVyc2lvbmApLlxuICovXG5leHBvcnQgY29uc3QgUFVCTElDX0xBVEVTVCA9ICdwdWJsaWMtbGF0ZXN0JztcblxuLyoqXG4gKiBWZXJzaW9uIHNwZWNpZmllciBhbGlhcyByZXNvbHZpbmcgdG8gdGhlIGxhdGVzdCAqKmNhdGFseXN0KiogKGVhcmx5IGFjY2VzcyAvXG4gKiBpbnNpZGVyKSBkZXNrdG9wIHJlbGVhc2UgKHRoZSBtYW5pZmVzdCdzIGBiZXRhLmxhdGVzdFZlcnNpb25gKS5cbiAqL1xuZXhwb3J0IGNvbnN0IENBVEFMWVNUX0xBVEVTVCA9ICdjYXRhbHlzdC1sYXRlc3QnO1xuXG4vKipcbiAqIFVSTCBvZiBPYnNpZGlhbidzIGRlc2t0b3AgcmVsZWFzZXMgbWFuaWZlc3QgKHJhdyBKU09OKS5cbiAqL1xuZXhwb3J0IGNvbnN0IERFU0tUT1BfUkVMRUFTRVNfTUFOSUZFU1RfVVJMID0gJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9vYnNpZGlhbm1kL29ic2lkaWFuLXJlbGVhc2VzL21hc3Rlci9kZXNrdG9wLXJlbGVhc2VzLmpzb24nO1xuXG5jb25zdCBFWFBMSUNJVF9WRVJTSU9OX1BBVFRFUk4gPSAvXlxcZCtcXC5cXGQrXFwuXFxkKyQvO1xuY29uc3QgVkVSU0lPTl9TRUdNRU5UX0NPVU5UID0gMztcblxuLyoqXG4gKiBBIHZlcnNpb24gc3BlY2lmaWVyIHJlc29sdmVkIGFnYWluc3QgYSByZWxlYXNlIGNoYW5uZWwuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ2hhbm5lbFZlcnNpb25TcGVjIHtcbiAgLyoqIFRoZSBjaGFubmVsIHRvIHJlc29sdmUgYWdhaW5zdCB0aGUgbWFuaWZlc3QuICovXG4gIHJlYWRvbmx5IGNoYW5uZWw6IFJlbGVhc2VDaGFubmVsO1xuXG4gIC8qKiBEaXNjcmltaW5hbnQuICovXG4gIHJlYWRvbmx5IGtpbmQ6ICdjaGFubmVsJztcbn1cblxuLyoqXG4gKiBUaGUgYGJldGFgIChjYXRhbHlzdCkgZW50cnkgb2Yge0BsaW5rIERlc2t0b3BSZWxlYXNlc01hbmlmZXN0fS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEZXNrdG9wUmVsZWFzZXNCZXRhIHtcbiAgLyoqIFRoZSBsYXRlc3QgY2F0YWx5c3QgdmVyc2lvbiwgZS5nLiBgJzEuMTMuMSdgLiAqL1xuICByZWFkb25seSBsYXRlc3RWZXJzaW9uOiBzdHJpbmc7XG59XG5cbi8qKlxuICogVGhlIHN1YnNldCBvZiBPYnNpZGlhbidzIGBkZXNrdG9wLXJlbGVhc2VzLmpzb25gIHRoaXMgbGlicmFyeSByZWFkcy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEZXNrdG9wUmVsZWFzZXNNYW5pZmVzdCB7XG4gIC8qKlxuICAgKiBUaGUgbGF0ZXN0IGNhdGFseXN0IChlYXJseSBhY2Nlc3MgLyBpbnNpZGVyKSByZWxlYXNlLCB3aGVuIHByZXNlbnQuXG4gICAqL1xuICByZWFkb25seSBiZXRhPzogRGVza3RvcFJlbGVhc2VzQmV0YTtcblxuICAvKipcbiAgICogVGhlIGxhdGVzdCBwdWJsaWMgKHN0YWJsZSkgdmVyc2lvbiwgZS5nLiBgJzEuMTIuNydgLlxuICAgKi9cbiAgcmVhZG9ubHkgbGF0ZXN0VmVyc2lvbjogc3RyaW5nO1xufVxuXG4vKipcbiAqIEEgdmVyc2lvbiBzcGVjaWZpZXIgbmFtaW5nIGEgY29uY3JldGUgYHgueS56YCB2ZXJzaW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEV4cGxpY2l0VmVyc2lvblNwZWMge1xuICAvKiogRGlzY3JpbWluYW50LiAqL1xuICByZWFkb25seSBraW5kOiAnZXhwbGljaXQnO1xuXG4gIC8qKiBUaGUgY29uY3JldGUgYHgueS56YCB2ZXJzaW9uLiAqL1xuICByZWFkb25seSB2ZXJzaW9uOiBzdHJpbmc7XG59XG5cbi8qKlxuICogQSByZWxlYXNlIGNoYW5uZWwgcmVzb2x2ZWQgZnJvbSBhIGNoYW5uZWwtYWxpYXMgc3BlY2lmaWVyLlxuICpcbiAqIC0gYHB1YmxpY2AgXHUyMDE0IHRoZSBsYXRlc3Qgc3RhYmxlIHJlbGVhc2UuXG4gKiAtIGBjYXRhbHlzdGAgXHUyMDE0IHRoZSBsYXRlc3QgZWFybHkgYWNjZXNzIC8gaW5zaWRlciByZWxlYXNlLlxuICovXG5leHBvcnQgdHlwZSBSZWxlYXNlQ2hhbm5lbCA9ICdjYXRhbHlzdCcgfCAncHVibGljJztcblxuLyoqXG4gKiBUaGUgcGFyc2VkIGZvcm0gb2YgYSB2ZXJzaW9uIHNwZWNpZmllci5cbiAqXG4gKiAtIGBleHBsaWNpdGAgXHUyMDE0IGEgY29uY3JldGUgYHgueS56YCB2ZXJzaW9uLlxuICogLSBgY2hhbm5lbGAgXHUyMDE0IGEgY2hhbm5lbCBhbGlhcyB0byBiZSByZXNvbHZlZCBhZ2FpbnN0IHRoZSBtYW5pZmVzdC5cbiAqL1xuZXhwb3J0IHR5cGUgVmVyc2lvblNwZWMgPSBDaGFubmVsVmVyc2lvblNwZWMgfCBFeHBsaWNpdFZlcnNpb25TcGVjO1xuXG4vKipcbiAqIENvbXBhcmVzIHR3byBgeC55LnpgIHZlcnNpb24gc3RyaW5ncyBudW1lcmljYWxseSwgc2VnbWVudCBieSBzZWdtZW50LlxuICpcbiAqIEBwYXJhbSBhIC0gVGhlIGZpcnN0IHZlcnNpb24uXG4gKiBAcGFyYW0gYiAtIFRoZSBzZWNvbmQgdmVyc2lvbi5cbiAqIEByZXR1cm5zIEEgbmVnYXRpdmUgbnVtYmVyIGlmIGBhIDwgYmAsIGAwYCBpZiBlcXVhbCwgYSBwb3NpdGl2ZSBudW1iZXIgaWYgYGEgPiBiYC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvbXBhcmVWZXJzaW9ucyhhOiBzdHJpbmcsIGI6IHN0cmluZyk6IG51bWJlciB7XG4gIGNvbnN0IGFTZWdtZW50cyA9IHBhcnNlVmVyc2lvblNlZ21lbnRzKGEpO1xuICBjb25zdCBiU2VnbWVudHMgPSBwYXJzZVZlcnNpb25TZWdtZW50cyhiKTtcblxuICBmb3IgKGxldCBpID0gMDsgaSA8IFZFUlNJT05fU0VHTUVOVF9DT1VOVDsgaSsrKSB7XG4gICAgY29uc3QgZGlmZiA9IChhU2VnbWVudHNbaV0gPz8gMCkgLSAoYlNlZ21lbnRzW2ldID8/IDApO1xuICAgIGlmIChkaWZmICE9PSAwKSB7XG4gICAgICByZXR1cm4gZGlmZjtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gMDtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSB1c2VyLWRhdGEgYXNhciBmaWxlIG5hbWUgZm9yIGEgZ2l2ZW4gdmVyc2lvbiwgbWF0Y2hpbmcgdGhlIG5hbWVcbiAqIE9ic2lkaWFuIGl0c2VsZiB1c2VzIHdoZW4gaXQgZG93bmxvYWRzIGFuIHVwZGF0ZSAoYG9ic2lkaWFuLTx2ZXJzaW9uPi5hc2FyYCkuXG4gKlxuICogQHBhcmFtIHZlcnNpb24gLSBBIGNvbmNyZXRlIGB4LnkuemAgdmVyc2lvbi5cbiAqIEByZXR1cm5zIFRoZSBhc2FyIGZpbGUgbmFtZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFZlcnNpb25Bc2FyRmlsZU5hbWUodmVyc2lvbjogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIGBvYnNpZGlhbi0ke3ZlcnNpb259LmFzYXJgO1xufVxuXG4vKipcbiAqIFBhcnNlcyBhIHZlcnNpb24gc3BlY2lmaWVyIGludG8gZWl0aGVyIGFuIGV4cGxpY2l0IHZlcnNpb24gb3IgYSBjaGFubmVsIGFsaWFzLlxuICpcbiAqIEBwYXJhbSBzcGVjIC0gVGhlIHNwZWNpZmllcjogYW4gYHgueS56YCBzdHJpbmcsIHtAbGluayBQVUJMSUNfTEFURVNUfSwgb3Ige0BsaW5rIENBVEFMWVNUX0xBVEVTVH0uXG4gKiBAcmV0dXJucyBUaGUgcGFyc2VkIHtAbGluayBWZXJzaW9uU3BlY30uXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSBzcGVjaWZpZXIgaXMgbmVpdGhlciBhIHZhbGlkIGB4LnkuemAgdmVyc2lvbiBub3IgYSBrbm93biBhbGlhcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVmVyc2lvblNwZWMoc3BlYzogc3RyaW5nKTogVmVyc2lvblNwZWMge1xuICBpZiAoc3BlYyA9PT0gUFVCTElDX0xBVEVTVCkge1xuICAgIHJldHVybiB7IGNoYW5uZWw6ICdwdWJsaWMnLCBraW5kOiAnY2hhbm5lbCcgfTtcbiAgfVxuXG4gIGlmIChzcGVjID09PSBDQVRBTFlTVF9MQVRFU1QpIHtcbiAgICByZXR1cm4geyBjaGFubmVsOiAnY2F0YWx5c3QnLCBraW5kOiAnY2hhbm5lbCcgfTtcbiAgfVxuXG4gIGlmIChFWFBMSUNJVF9WRVJTSU9OX1BBVFRFUk4udGVzdChzcGVjKSkge1xuICAgIHJldHVybiB7IGtpbmQ6ICdleHBsaWNpdCcsIHZlcnNpb246IHNwZWMgfTtcbiAgfVxuXG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgSW52YWxpZCBPYnNpZGlhbiB2ZXJzaW9uIFwiJHtzcGVjfVwiLiBFeHBlY3RlZCBhbiBcIngueS56XCIgdmVyc2lvbiwgXCIke1BVQkxJQ19MQVRFU1R9XCIsIG9yIFwiJHtDQVRBTFlTVF9MQVRFU1R9XCIuYFxuICApO1xufVxuXG4vKipcbiAqIFJlc29sdmVzIGEgY2hhbm5lbCB0byBhIGNvbmNyZXRlIHZlcnNpb24gdXNpbmcgYSBkZXNrdG9wIHJlbGVhc2VzIG1hbmlmZXN0LlxuICpcbiAqIEBwYXJhbSBtYW5pZmVzdCAtIFRoZSBwYXJzZWQgbWFuaWZlc3QuXG4gKiBAcGFyYW0gY2hhbm5lbCAtIFRoZSBjaGFubmVsIHRvIHJlc29sdmUuXG4gKiBAcmV0dXJucyBUaGUgY29uY3JldGUgYHgueS56YCB2ZXJzaW9uIGZvciB0aGUgY2hhbm5lbC5cbiAqIEB0aHJvd3MgRXJyb3IgaWYgdGhlIGNhdGFseXN0IGNoYW5uZWwgaXMgcmVxdWVzdGVkIGJ1dCB0aGUgbWFuaWZlc3QgaGFzIG5vIGBiZXRhYCBlbnRyeS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVWZXJzaW9uRnJvbU1hbmlmZXN0KG1hbmlmZXN0OiBEZXNrdG9wUmVsZWFzZXNNYW5pZmVzdCwgY2hhbm5lbDogUmVsZWFzZUNoYW5uZWwpOiBzdHJpbmcge1xuICBpZiAoY2hhbm5lbCA9PT0gJ3B1YmxpYycpIHtcbiAgICByZXR1cm4gbWFuaWZlc3QubGF0ZXN0VmVyc2lvbjtcbiAgfVxuXG4gIGlmICghbWFuaWZlc3QuYmV0YSkge1xuICAgIHRocm93IG5ldyBFcnJvcignRGVza3RvcCByZWxlYXNlcyBtYW5pZmVzdCBoYXMgbm8gY2F0YWx5c3QgKGJldGEpIHJlbGVhc2UuJyk7XG4gIH1cblxuICByZXR1cm4gbWFuaWZlc3QuYmV0YS5sYXRlc3RWZXJzaW9uO1xufVxuXG4vKipcbiAqIFNwbGl0cyBhbiBgeC55LnpgIHZlcnNpb24gaW50byBpdHMgbnVtZXJpYyBzZWdtZW50cy5cbiAqXG4gKiBAcGFyYW0gdmVyc2lvbiAtIFRoZSB2ZXJzaW9uIHN0cmluZy5cbiAqIEByZXR1cm5zIFRoZSBudW1lcmljIHNlZ21lbnRzLlxuICovXG5mdW5jdGlvbiBwYXJzZVZlcnNpb25TZWdtZW50cyh2ZXJzaW9uOiBzdHJpbmcpOiBudW1iZXJbXSB7XG4gIHJldHVybiB2ZXJzaW9uLnNwbGl0KCcuJykubWFwKChzZWdtZW50KSA9PiBOdW1iZXIucGFyc2VJbnQoc2VnbWVudCwgMTApKTtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICJBQWlCTyxNQUFNLGdCQUFnQjtBQU10QixNQUFNLGtCQUFrQjtBQUt4QixNQUFNLGdDQUFnQztBQUU3QyxNQUFNLDJCQUEyQjtBQUNqQyxNQUFNLHdCQUF3QjtBQXNFdkIsU0FBUyxnQkFBZ0IsR0FBVyxHQUFtQjtBQUM1RCxRQUFNLFlBQVkscUJBQXFCLENBQUM7QUFDeEMsUUFBTSxZQUFZLHFCQUFxQixDQUFDO0FBRXhDLFdBQVMsSUFBSSxHQUFHLElBQUksdUJBQXVCLEtBQUs7QUFDOUMsVUFBTSxRQUFRLFVBQVUsQ0FBQyxLQUFLLE1BQU0sVUFBVSxDQUFDLEtBQUs7QUFDcEQsUUFBSSxTQUFTLEdBQUc7QUFDZCxhQUFPO0FBQUEsSUFDVDtBQUFBLEVBQ0Y7QUFFQSxTQUFPO0FBQ1Q7QUFTTyxTQUFTLHVCQUF1QixTQUF5QjtBQUM5RCxTQUFPLFlBQVksT0FBTztBQUM1QjtBQVNPLFNBQVMsaUJBQWlCLE1BQTJCO0FBQzFELE1BQUksU0FBUyxlQUFlO0FBQzFCLFdBQU8sRUFBRSxTQUFTLFVBQVUsTUFBTSxVQUFVO0FBQUEsRUFDOUM7QUFFQSxNQUFJLFNBQVMsaUJBQWlCO0FBQzVCLFdBQU8sRUFBRSxTQUFTLFlBQVksTUFBTSxVQUFVO0FBQUEsRUFDaEQ7QUFFQSxNQUFJLHlCQUF5QixLQUFLLElBQUksR0FBRztBQUN2QyxXQUFPLEVBQUUsTUFBTSxZQUFZLFNBQVMsS0FBSztBQUFBLEVBQzNDO0FBRUEsUUFBTSxJQUFJO0FBQUEsSUFDUiw2QkFBNkIsSUFBSSxvQ0FBb0MsYUFBYSxVQUFVLGVBQWU7QUFBQSxFQUM3RztBQUNGO0FBVU8sU0FBUywyQkFBMkIsVUFBbUMsU0FBaUM7QUFDN0csTUFBSSxZQUFZLFVBQVU7QUFDeEIsV0FBTyxTQUFTO0FBQUEsRUFDbEI7QUFFQSxNQUFJLENBQUMsU0FBUyxNQUFNO0FBQ2xCLFVBQU0sSUFBSSxNQUFNLDJEQUEyRDtBQUFBLEVBQzdFO0FBRUEsU0FBTyxTQUFTLEtBQUs7QUFDdkI7QUFRQSxTQUFTLHFCQUFxQixTQUEyQjtBQUN2RCxTQUFPLFFBQVEsTUFBTSxHQUFHLEVBQUUsSUFBSSxDQUFDLFlBQVksT0FBTyxTQUFTLFNBQVMsRUFBRSxDQUFDO0FBQ3pFOyIsCiAgIm5hbWVzIjogW10KfQo=
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Cross-process advisory lock that serializes whole integration-test runs.
|
|
5
|
+
*
|
|
6
|
+
* Two test runs that drive the same shared resources corrupt each other: on
|
|
7
|
+
* desktop they share the single local Obsidian instance, its `obsidian.json`
|
|
8
|
+
* registry, and the CDP port; on Android they share the emulator and the Appium
|
|
9
|
+
* server. One run's setup/teardown kills or reconfigures the instance the other
|
|
10
|
+
* run is mid-eval on (observed as `ECONNREFUSED` or "vault not open"), so both
|
|
11
|
+
* runs fail.
|
|
12
|
+
*
|
|
13
|
+
* This lock makes the second run **wait** for the first to finish instead of
|
|
14
|
+
* running concurrently. It is held for the entire run: acquired at the start of
|
|
15
|
+
* the core setup and released by the core teardown (and by the process cleanup
|
|
16
|
+
* handlers). The lock is a sentinel file in a shared temp directory, carrying
|
|
17
|
+
* the holder's PID so a run that crashed without releasing leaves a lock that
|
|
18
|
+
* the next run detects as stale and steals.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Parameters for {@link acquireSetupLock}.
|
|
22
|
+
*/
|
|
23
|
+
export interface AcquireSetupLockParams {
|
|
24
|
+
/** Short transport label for log messages (e.g. `"obsidian-cli"`). */
|
|
25
|
+
readonly label: string;
|
|
26
|
+
/**
|
|
27
|
+
* Logical scope the lock serializes within. Runs that share resources must
|
|
28
|
+
* use the same scope (e.g. `"desktop"` for the CLI/CDP transports, `"android"`
|
|
29
|
+
* for the Appium transport).
|
|
30
|
+
*/
|
|
31
|
+
readonly scope: string;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum time to wait for a competing run to release the lock before giving
|
|
34
|
+
* up and throwing.
|
|
35
|
+
*
|
|
36
|
+
* @default 1 hour
|
|
37
|
+
*/
|
|
38
|
+
readonly timeoutInMilliseconds?: number | undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A held setup lock. Release it once the run's teardown is complete.
|
|
42
|
+
*/
|
|
43
|
+
export interface SetupLock {
|
|
44
|
+
/** Releases the lock. Safe to call more than once. */
|
|
45
|
+
release(): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Acquires the cross-process setup lock for the given scope, waiting until any
|
|
49
|
+
* competing run releases it (or its lock is detected as stale).
|
|
50
|
+
*
|
|
51
|
+
* @param params - The lock parameters.
|
|
52
|
+
* @returns A handle whose {@link SetupLock.release} frees the lock.
|
|
53
|
+
* @throws If the lock cannot be acquired within `timeoutInMilliseconds`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function acquireSetupLock(params: AcquireSetupLockParams): Promise<SetupLock>;
|