obsidian-integration-testing 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +254 -13
  2. package/dist/lib/cjs/context-id.cjs +1 -1
  3. package/dist/lib/cjs/context-id.d.cts +1 -1
  4. package/dist/lib/cjs/enable-plugin.cjs +54 -0
  5. package/dist/lib/cjs/enable-plugin.d.cts +44 -0
  6. package/dist/lib/cjs/exec.cjs +4 -4
  7. package/dist/lib/cjs/exec.d.cts +8 -8
  8. package/dist/lib/cjs/function-expression.cjs +1 -1
  9. package/dist/lib/cjs/function-expression.d.cts +1 -1
  10. package/dist/lib/cjs/index.cjs +13 -1
  11. package/dist/lib/cjs/index.d.cts +9 -1
  12. package/dist/lib/cjs/integration-global-setup.cjs +19 -6
  13. package/dist/lib/cjs/integration-global-setup.d.cts +1 -6
  14. package/dist/lib/cjs/json-with-functions.cjs +1 -1
  15. package/dist/lib/cjs/json-with-functions.d.cts +1 -1
  16. package/dist/lib/cjs/noop.cjs +1 -1
  17. package/dist/lib/cjs/noop.d.cts +1 -1
  18. package/dist/lib/cjs/obsidian-cli.cjs +10 -76
  19. package/dist/lib/cjs/obsidian-cli.d.cts +9 -3
  20. package/dist/lib/cjs/obsidian-config.cjs +1 -1
  21. package/dist/lib/cjs/obsidian-config.d.cts +1 -1
  22. package/dist/lib/cjs/temp-vault.cjs +47 -5
  23. package/dist/lib/cjs/temp-vault.d.cts +19 -1
  24. package/dist/lib/cjs/transport-appium.cjs +232 -0
  25. package/dist/lib/cjs/transport-appium.d.cts +164 -0
  26. package/dist/lib/cjs/transport-desktop-cdp.cjs +390 -0
  27. package/dist/lib/cjs/transport-desktop-cdp.d.cts +158 -0
  28. package/dist/lib/cjs/transport-desktop-cli.cjs +221 -0
  29. package/dist/lib/cjs/transport-desktop-cli.d.cts +65 -0
  30. package/dist/lib/cjs/transport-factory.cjs +94 -0
  31. package/dist/lib/cjs/transport-factory.d.cts +25 -0
  32. package/dist/lib/cjs/transport-options.cjs +17 -0
  33. package/dist/lib/cjs/transport-options.d.cts +100 -0
  34. package/dist/lib/cjs/transport.cjs +17 -0
  35. package/dist/lib/cjs/transport.d.cts +86 -0
  36. package/dist/lib/cjs/type-guards.cjs +1 -1
  37. package/dist/lib/cjs/type-guards.d.cts +1 -1
  38. package/dist/lib/cjs/vault-registry.cjs +7 -56
  39. package/dist/lib/cjs/vault-registry.d.cts +3 -5
  40. package/dist/lib/esm/context-id.d.mts +1 -1
  41. package/dist/lib/esm/context-id.mjs +1 -1
  42. package/dist/lib/esm/enable-plugin.d.mts +44 -0
  43. package/dist/lib/esm/enable-plugin.mjs +30 -0
  44. package/dist/lib/esm/exec.d.mts +8 -8
  45. package/dist/lib/esm/exec.mjs +4 -4
  46. package/dist/lib/esm/function-expression.d.mts +1 -1
  47. package/dist/lib/esm/function-expression.mjs +1 -1
  48. package/dist/lib/esm/index.d.mts +9 -1
  49. package/dist/lib/esm/index.mjs +9 -1
  50. package/dist/lib/esm/integration-global-setup.d.mts +1 -6
  51. package/dist/lib/esm/integration-global-setup.mjs +19 -6
  52. package/dist/lib/esm/json-with-functions.d.mts +1 -1
  53. package/dist/lib/esm/json-with-functions.mjs +1 -1
  54. package/dist/lib/esm/noop.d.mts +1 -1
  55. package/dist/lib/esm/noop.mjs +1 -1
  56. package/dist/lib/esm/obsidian-cli.d.mts +9 -3
  57. package/dist/lib/esm/obsidian-cli.mjs +10 -80
  58. package/dist/lib/esm/obsidian-config.d.mts +1 -1
  59. package/dist/lib/esm/obsidian-config.mjs +1 -1
  60. package/dist/lib/esm/temp-vault.d.mts +19 -1
  61. package/dist/lib/esm/temp-vault.mjs +54 -7
  62. package/dist/lib/esm/transport-appium.d.mts +164 -0
  63. package/dist/lib/esm/transport-appium.mjs +208 -0
  64. package/dist/lib/esm/transport-desktop-cdp.d.mts +158 -0
  65. package/dist/lib/esm/transport-desktop-cdp.mjs +356 -0
  66. package/dist/lib/esm/transport-desktop-cli.d.mts +65 -0
  67. package/dist/lib/esm/transport-desktop-cli.mjs +190 -0
  68. package/dist/lib/esm/transport-factory.d.mts +25 -0
  69. package/dist/lib/esm/transport-factory.mjs +69 -0
  70. package/dist/lib/esm/transport-options.d.mts +100 -0
  71. package/dist/lib/esm/transport-options.mjs +1 -0
  72. package/dist/lib/esm/transport.d.mts +86 -0
  73. package/dist/lib/esm/transport.mjs +1 -0
  74. package/dist/lib/esm/type-guards.d.mts +1 -1
  75. package/dist/lib/esm/type-guards.mjs +1 -1
  76. package/dist/lib/esm/vault-registry.d.mts +3 -5
  77. package/dist/lib/esm/vault-registry.mjs +7 -56
  78. package/dist/obsidian-integration-testing-1.2.0.tgz +0 -0
  79. package/package.json +16 -2
  80. package/scripts/postinstall.mjs +29 -0
  81. package/dist/obsidian-integration-testing-1.1.1.tgz +0 -0
@@ -0,0 +1,190 @@
1
+ import process from "node:process";
2
+ import { exec } from "./exec.mjs";
3
+ import {
4
+ isCliEnabled,
5
+ isVaultRegistered
6
+ } from "./obsidian-config.mjs";
7
+ const UNABLE_TO_FIND_OBSIDIAN = "unable to find Obsidian";
8
+ const AUTO_START_POLL_INTERVAL_IN_MILLISECONDS = 2e3;
9
+ const AUTO_START_TIMEOUT_IN_MILLISECONDS = 3e4;
10
+ const VAULT_POLL_INTERVAL_IN_MILLISECONDS = 500;
11
+ const VAULT_POLL_TIMEOUT_IN_MILLISECONDS = 3e4;
12
+ const VAULT_CLOSE_DELAY_IN_MILLISECONDS = 1e3;
13
+ class DesktopCliTransport {
14
+ /** */
15
+ isMobile = false;
16
+ /**
17
+ * Evaluates a JavaScript expression inside Obsidian via `obsidian eval`.
18
+ *
19
+ * Handles auto-start: if Obsidian is not running, launches it via URI protocol
20
+ * and retries until it becomes available.
21
+ *
22
+ * @param expression - The JavaScript expression to evaluate.
23
+ * @param options - Evaluation options.
24
+ * @returns The normalized result string (transport-specific prefixes stripped).
25
+ */
26
+ async evaluate(expression, options) {
27
+ const command = ["obsidian", "eval", `code=${expression}`];
28
+ let resultStr;
29
+ try {
30
+ resultStr = await exec(command, { cwd: options.cwd, isQuiet: true });
31
+ } catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ if (message.includes(UNABLE_TO_FIND_OBSIDIAN)) {
34
+ resultStr = await this.ensureObsidianRunningAndRetry(command, options.cwd);
35
+ } else {
36
+ throw error;
37
+ }
38
+ }
39
+ if (resultStr === "" || resultStr === "Vault not found.") {
40
+ throw new Error(`Unexpected empty response from Obsidian for path: ${options.cwd}`);
41
+ }
42
+ const lines = resultStr.split("\n");
43
+ const resultLine = lines.findLast((line) => line.startsWith("=> "));
44
+ if (resultLine) {
45
+ return resultLine.slice("=> ".length);
46
+ }
47
+ return resultStr;
48
+ }
49
+ /**
50
+ * Runs CLI-specific preflight checks.
51
+ *
52
+ * Verifies that the vault is registered, CLI is enabled in Obsidian settings,
53
+ * and the `obsidian` CLI binary is available in PATH.
54
+ *
55
+ * @param vaultPath - The absolute path to the vault folder.
56
+ */
57
+ async preflightCheck(vaultPath) {
58
+ if (!isVaultRegistered(vaultPath)) {
59
+ throw new Error(
60
+ `Vault is not registered in Obsidian: ${vaultPath}. Register the vault first with registerVault() or TempVault.register().`
61
+ );
62
+ }
63
+ if (!isCliEnabled()) {
64
+ throw new Error("Obsidian CLI is disabled. Enable it in Obsidian Settings \u2192 General \u2192 CLI.");
65
+ }
66
+ await this.assertCliAvailable();
67
+ }
68
+ /**
69
+ * Registers a vault in the running Obsidian instance via Electron IPC.
70
+ *
71
+ * Uses `vault-open` IPC to register and open the vault, then polls until
72
+ * the vault's CLI is ready and `getBasePath()` matches `vaultPath`.
73
+ *
74
+ * @param vaultPath - The absolute path to the vault folder.
75
+ */
76
+ async registerVault(vaultPath) {
77
+ const registerExpr = buildIpcExpression(
78
+ `window.electron.ipcRenderer.sendSync('vault-open', ${JSON.stringify(vaultPath)}, false);`
79
+ );
80
+ await this.evaluate(registerExpr, { cwd: process.cwd() });
81
+ const pollExpr = buildSimpleExpression(
82
+ "return JSON.stringify(app.vault.adapter.getBasePath());"
83
+ );
84
+ const deadline = Date.now() + VAULT_POLL_TIMEOUT_IN_MILLISECONDS;
85
+ while (Date.now() < deadline) {
86
+ try {
87
+ const basePath = await this.evaluate(pollExpr, { cwd: vaultPath });
88
+ if (JSON.parse(basePath) === vaultPath) {
89
+ return;
90
+ }
91
+ } catch {
92
+ }
93
+ await delay(VAULT_POLL_INTERVAL_IN_MILLISECONDS);
94
+ }
95
+ throw new Error(`Vault at ${vaultPath} did not become ready within ${String(VAULT_POLL_TIMEOUT_IN_MILLISECONDS)}ms`);
96
+ }
97
+ /**
98
+ * Unregisters a vault from the running Obsidian instance.
99
+ *
100
+ * Schedules the vault window to close, waits, then removes the vault
101
+ * from the registry via `vault-remove` IPC.
102
+ *
103
+ * @param vaultPath - The absolute path to the vault folder.
104
+ */
105
+ async unregisterVault(vaultPath) {
106
+ const destroyExpr = buildSimpleExpression(`
107
+ setTimeout(() => {
108
+ if (window.electron && window.electron.remote) {
109
+ window.electron.remote.getCurrentWindow().destroy();
110
+ }
111
+ }, 0);
112
+ `);
113
+ try {
114
+ await this.evaluate(destroyExpr, { cwd: vaultPath });
115
+ } catch {
116
+ }
117
+ await delay(VAULT_CLOSE_DELAY_IN_MILLISECONDS);
118
+ const removeExpr = buildIpcExpression(
119
+ `window.electron.ipcRenderer.sendSync('vault-remove', ${JSON.stringify(vaultPath)});`
120
+ );
121
+ await this.evaluate(removeExpr, { cwd: process.cwd() });
122
+ }
123
+ /**
124
+ * Checks whether the Obsidian CLI binary is available in the system PATH.
125
+ */
126
+ async assertCliAvailable() {
127
+ const command = process.platform === "win32" ? "where.exe obsidian" : "which obsidian";
128
+ try {
129
+ await exec(command, { isQuiet: true });
130
+ } catch {
131
+ throw new Error("Obsidian CLI is not available. Ensure Obsidian is installed and its CLI is in your PATH.");
132
+ }
133
+ }
134
+ /**
135
+ * Launches Obsidian binary and retries the eval until it responds.
136
+ *
137
+ * @param command - The CLI command array to retry.
138
+ * @param cwd - The working directory.
139
+ * @returns The result string from the successful eval.
140
+ */
141
+ async ensureObsidianRunningAndRetry(command, cwd) {
142
+ console.warn("Obsidian is not running. Starting Obsidian...");
143
+ try {
144
+ await exec(getObsidianLaunchCommand(), { isQuiet: true });
145
+ } catch {
146
+ }
147
+ const deadline = Date.now() + AUTO_START_TIMEOUT_IN_MILLISECONDS;
148
+ while (Date.now() < deadline) {
149
+ await delay(AUTO_START_POLL_INTERVAL_IN_MILLISECONDS);
150
+ try {
151
+ return await exec(command, { cwd, isQuiet: true });
152
+ } catch (error) {
153
+ const message = error instanceof Error ? error.message : String(error);
154
+ if (!message.includes(UNABLE_TO_FIND_OBSIDIAN)) {
155
+ throw error;
156
+ }
157
+ }
158
+ }
159
+ throw new Error(`Obsidian did not start within ${String(AUTO_START_TIMEOUT_IN_MILLISECONDS)}ms.`);
160
+ }
161
+ }
162
+ function buildIpcExpression(ipcStatement) {
163
+ return buildSimpleExpression(ipcStatement);
164
+ }
165
+ function buildSimpleExpression(body) {
166
+ return `(async () => {
167
+ if (!app.workspace.layoutReady) {
168
+ await new Promise((resolve) => app.workspace.onLayoutReady(resolve));
169
+ }
170
+ ${body}
171
+ })()`;
172
+ }
173
+ function delay(ms) {
174
+ return new Promise((resolve) => {
175
+ setTimeout(resolve, ms);
176
+ });
177
+ }
178
+ function getObsidianLaunchCommand() {
179
+ if (process.platform === "win32") {
180
+ return 'start "" "%LOCALAPPDATA%\\Programs\\Obsidian\\Obsidian.exe"';
181
+ }
182
+ if (process.platform === "darwin") {
183
+ return "/Applications/Obsidian.app/Contents/MacOS/Obsidian &";
184
+ }
185
+ return "obsidian &";
186
+ }
187
+ export {
188
+ DesktopCliTransport
189
+ };
190
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3RyYW5zcG9ydC1kZXNrdG9wLWNsaS50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIERlc2t0b3AgQ0xJIHRyYW5zcG9ydCBcdTIwMTQgZXZhbHVhdGVzIGV4cHJlc3Npb25zIHZpYSBgb2JzaWRpYW4gZXZhbGAgYW5kIG1hbmFnZXNcbiAqIHZhdWx0cyB2aWEgRWxlY3Ryb24gSVBDLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGNvZGUgY292ZXJlZCBieSBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IHVuaXQgdGVzdHMuICovXG5cbmltcG9ydCBwcm9jZXNzIGZyb20gJ25vZGU6cHJvY2Vzcyc7XG5cbmltcG9ydCB0eXBlIHtcbiAgT2JzaWRpYW5UcmFuc3BvcnQsXG4gIFRyYW5zcG9ydEV2YWxPcHRpb25zXG59IGZyb20gJy4vdHJhbnNwb3J0Lm1qcyc7XG5cbmltcG9ydCB7IGV4ZWMgfSBmcm9tICcuL2V4ZWMubWpzJztcbmltcG9ydCB7XG4gIGlzQ2xpRW5hYmxlZCxcbiAgaXNWYXVsdFJlZ2lzdGVyZWRcbn0gZnJvbSAnLi9vYnNpZGlhbi1jb25maWcubWpzJztcblxuY29uc3QgVU5BQkxFX1RPX0ZJTkRfT0JTSURJQU4gPSAndW5hYmxlIHRvIGZpbmQgT2JzaWRpYW4nO1xuY29uc3QgQVVUT19TVEFSVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyA9IDIwMDA7XG5jb25zdCBBVVRPX1NUQVJUX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTID0gMzAwMDA7XG5jb25zdCBWQVVMVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyA9IDUwMDtcbmNvbnN0IFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMgPSAzMDAwMDtcbmNvbnN0IFZBVUxUX0NMT1NFX0RFTEFZX0lOX01JTExJU0VDT05EUyA9IDEwMDA7XG5cbi8qKlxuICogVHJhbnNwb3J0IHRoYXQgY29tbXVuaWNhdGVzIHdpdGggRGVza3RvcCBPYnNpZGlhbiB2aWEgdGhlIGBvYnNpZGlhbiBldmFsYCBDTEkgY29tbWFuZFxuICogYW5kIG1hbmFnZXMgdmF1bHRzIHZpYSBFbGVjdHJvbiBJUEMuXG4gKi9cbmV4cG9ydCBjbGFzcyBEZXNrdG9wQ2xpVHJhbnNwb3J0IGltcGxlbWVudHMgT2JzaWRpYW5UcmFuc3BvcnQge1xuICAvKiogKi9cbiAgcHVibGljIHJlYWRvbmx5IGlzTW9iaWxlID0gZmFsc2U7XG5cbiAgLyoqXG4gICAqIEV2YWx1YXRlcyBhIEphdmFTY3JpcHQgZXhwcmVzc2lvbiBpbnNpZGUgT2JzaWRpYW4gdmlhIGBvYnNpZGlhbiBldmFsYC5cbiAgICpcbiAgICogSGFuZGxlcyBhdXRvLXN0YXJ0OiBpZiBPYnNpZGlhbiBpcyBub3QgcnVubmluZywgbGF1bmNoZXMgaXQgdmlhIFVSSSBwcm90b2NvbFxuICAgKiBhbmQgcmV0cmllcyB1bnRpbCBpdCBiZWNvbWVzIGF2YWlsYWJsZS5cbiAgICpcbiAgICogQHBhcmFtIGV4cHJlc3Npb24gLSBUaGUgSmF2YVNjcmlwdCBleHByZXNzaW9uIHRvIGV2YWx1YXRlLlxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIEV2YWx1YXRpb24gb3B0aW9ucy5cbiAgICogQHJldHVybnMgVGhlIG5vcm1hbGl6ZWQgcmVzdWx0IHN0cmluZyAodHJhbnNwb3J0LXNwZWNpZmljIHByZWZpeGVzIHN0cmlwcGVkKS5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBldmFsdWF0ZShleHByZXNzaW9uOiBzdHJpbmcsIG9wdGlvbnM6IFRyYW5zcG9ydEV2YWxPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBjb21tYW5kID0gWydvYnNpZGlhbicsICdldmFsJywgYGNvZGU9JHtleHByZXNzaW9ufWBdO1xuXG4gICAgbGV0IHJlc3VsdFN0cjogc3RyaW5nO1xuICAgIHRyeSB7XG4gICAgICByZXN1bHRTdHIgPSBhd2FpdCBleGVjKGNvbW1hbmQsIHsgY3dkOiBvcHRpb25zLmN3ZCwgaXNRdWlldDogdHJ1ZSB9KTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbiAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKFVOQUJMRV9UT19GSU5EX09CU0lESUFOKSkge1xuICAgICAgICByZXN1bHRTdHIgPSBhd2FpdCB0aGlzLmVuc3VyZU9ic2lkaWFuUnVubmluZ0FuZFJldHJ5KGNvbW1hbmQsIG9wdGlvbnMuY3dkKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChyZXN1bHRTdHIgPT09ICcnIHx8IHJlc3VsdFN0ciA9PT0gJ1ZhdWx0IG5vdCBmb3VuZC4nKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFVuZXhwZWN0ZWQgZW1wdHkgcmVzcG9uc2UgZnJvbSBPYnNpZGlhbiBmb3IgcGF0aDogJHtvcHRpb25zLmN3ZH1gKTtcbiAgICB9XG5cbiAgICAvLyBUaGUgQ0xJIG1heSBwcmVwZW5kIGVycm9yL3dhcm5pbmcgbGluZXMgYmVmb3JlIHRoZSByZXN1bHQuXG4gICAgLy8gRmluZCB0aGUgbGFzdCBgPT4gYCBsaW5lIHdoaWNoIGNvbnRhaW5zIHRoZSBhY3R1YWwgcmVzdWx0LlxuICAgIGNvbnN0IGxpbmVzID0gcmVzdWx0U3RyLnNwbGl0KCdcXG4nKTtcbiAgICBjb25zdCByZXN1bHRMaW5lID0gbGluZXMuZmluZExhc3QoKGxpbmUpID0+IGxpbmUuc3RhcnRzV2l0aCgnPT4gJykpO1xuICAgIGlmIChyZXN1bHRMaW5lKSB7XG4gICAgICByZXR1cm4gcmVzdWx0TGluZS5zbGljZSgnPT4gJy5sZW5ndGgpO1xuICAgIH1cblxuICAgIHJldHVybiByZXN1bHRTdHI7XG4gIH1cblxuICAvKipcbiAgICogUnVucyBDTEktc3BlY2lmaWMgcHJlZmxpZ2h0IGNoZWNrcy5cbiAgICpcbiAgICogVmVyaWZpZXMgdGhhdCB0aGUgdmF1bHQgaXMgcmVnaXN0ZXJlZCwgQ0xJIGlzIGVuYWJsZWQgaW4gT2JzaWRpYW4gc2V0dGluZ3MsXG4gICAqIGFuZCB0aGUgYG9ic2lkaWFuYCBDTEkgYmluYXJ5IGlzIGF2YWlsYWJsZSBpbiBQQVRILlxuICAgKlxuICAgKiBAcGFyYW0gdmF1bHRQYXRoIC0gVGhlIGFic29sdXRlIHBhdGggdG8gdGhlIHZhdWx0IGZvbGRlci5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBwcmVmbGlnaHRDaGVjayh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICghaXNWYXVsdFJlZ2lzdGVyZWQodmF1bHRQYXRoKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgVmF1bHQgaXMgbm90IHJlZ2lzdGVyZWQgaW4gT2JzaWRpYW46ICR7dmF1bHRQYXRofS4gUmVnaXN0ZXIgdGhlIHZhdWx0IGZpcnN0IHdpdGggcmVnaXN0ZXJWYXVsdCgpIG9yIFRlbXBWYXVsdC5yZWdpc3RlcigpLmBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgaWYgKCFpc0NsaUVuYWJsZWQoKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdPYnNpZGlhbiBDTEkgaXMgZGlzYWJsZWQuIEVuYWJsZSBpdCBpbiBPYnNpZGlhbiBTZXR0aW5ncyBcXHUyMTkyIEdlbmVyYWwgXFx1MjE5MiBDTEkuJyk7XG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5hc3NlcnRDbGlBdmFpbGFibGUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWdpc3RlcnMgYSB2YXVsdCBpbiB0aGUgcnVubmluZyBPYnNpZGlhbiBpbnN0YW5jZSB2aWEgRWxlY3Ryb24gSVBDLlxuICAgKlxuICAgKiBVc2VzIGB2YXVsdC1vcGVuYCBJUEMgdG8gcmVnaXN0ZXIgYW5kIG9wZW4gdGhlIHZhdWx0LCB0aGVuIHBvbGxzIHVudGlsXG4gICAqIHRoZSB2YXVsdCdzIENMSSBpcyByZWFkeSBhbmQgYGdldEJhc2VQYXRoKClgIG1hdGNoZXMgYHZhdWx0UGF0aGAuXG4gICAqXG4gICAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICAgKi9cbiAgcHVibGljIGFzeW5jIHJlZ2lzdGVyVmF1bHQodmF1bHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZWdpc3RlckV4cHIgPSBidWlsZElwY0V4cHJlc3Npb24oXG4gICAgICBgd2luZG93LmVsZWN0cm9uLmlwY1JlbmRlcmVyLnNlbmRTeW5jKCd2YXVsdC1vcGVuJywgJHtKU09OLnN0cmluZ2lmeSh2YXVsdFBhdGgpfSwgZmFsc2UpO2BcbiAgICApO1xuICAgIGF3YWl0IHRoaXMuZXZhbHVhdGUocmVnaXN0ZXJFeHByLCB7IGN3ZDogcHJvY2Vzcy5jd2QoKSB9KTtcblxuICAgIGNvbnN0IHBvbGxFeHByID0gYnVpbGRTaW1wbGVFeHByZXNzaW9uKFxuICAgICAgJ3JldHVybiBKU09OLnN0cmluZ2lmeShhcHAudmF1bHQuYWRhcHRlci5nZXRCYXNlUGF0aCgpKTsnXG4gICAgKTtcblxuICAgIGNvbnN0IGRlYWRsaW5lID0gRGF0ZS5ub3coKSArIFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFM7XG4gICAgd2hpbGUgKERhdGUubm93KCkgPCBkZWFkbGluZSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgYmFzZVBhdGggPSBhd2FpdCB0aGlzLmV2YWx1YXRlKHBvbGxFeHByLCB7IGN3ZDogdmF1bHRQYXRoIH0pO1xuICAgICAgICBpZiAoSlNPTi5wYXJzZShiYXNlUGF0aCkgPT09IHZhdWx0UGF0aCkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIC8vIFZhdWx0IG5vdCByZWFkeSB5ZXQuXG4gICAgICB9XG4gICAgICBhd2FpdCBkZWxheShWQVVMVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyk7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgVmF1bHQgYXQgJHt2YXVsdFBhdGh9IGRpZCBub3QgYmVjb21lIHJlYWR5IHdpdGhpbiAke1N0cmluZyhWQVVMVF9QT0xMX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTKX1tc2ApO1xuICB9XG5cbiAgLyoqXG4gICAqIFVucmVnaXN0ZXJzIGEgdmF1bHQgZnJvbSB0aGUgcnVubmluZyBPYnNpZGlhbiBpbnN0YW5jZS5cbiAgICpcbiAgICogU2NoZWR1bGVzIHRoZSB2YXVsdCB3aW5kb3cgdG8gY2xvc2UsIHdhaXRzLCB0aGVuIHJlbW92ZXMgdGhlIHZhdWx0XG4gICAqIGZyb20gdGhlIHJlZ2lzdHJ5IHZpYSBgdmF1bHQtcmVtb3ZlYCBJUEMuXG4gICAqXG4gICAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICAgKi9cbiAgcHVibGljIGFzeW5jIHVucmVnaXN0ZXJWYXVsdCh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGRlc3Ryb3lFeHByID0gYnVpbGRTaW1wbGVFeHByZXNzaW9uKGBcbiAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICBpZiAod2luZG93LmVsZWN0cm9uICYmIHdpbmRvdy5lbGVjdHJvbi5yZW1vdGUpIHtcbiAgICAgICAgICB3aW5kb3cuZWxlY3Ryb24ucmVtb3RlLmdldEN1cnJlbnRXaW5kb3coKS5kZXN0cm95KCk7XG4gICAgICAgIH1cbiAgICAgIH0sIDApO1xuICAgIGApO1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLmV2YWx1YXRlKGRlc3Ryb3lFeHByLCB7IGN3ZDogdmF1bHRQYXRoIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gVGhlIHdpbmRvdyBtYXkgaGF2ZSBjbG9zZWQgYmVmb3JlIHRoZSByZXNwb25zZSB3YXMgc2VudCBcdTIwMTQgdGhhdCdzIE9LLlxuICAgIH1cblxuICAgIGF3YWl0IGRlbGF5KFZBVUxUX0NMT1NFX0RFTEFZX0lOX01JTExJU0VDT05EUyk7XG5cbiAgICBjb25zdCByZW1vdmVFeHByID0gYnVpbGRJcGNFeHByZXNzaW9uKFxuICAgICAgYHdpbmRvdy5lbGVjdHJvbi5pcGNSZW5kZXJlci5zZW5kU3luYygndmF1bHQtcmVtb3ZlJywgJHtKU09OLnN0cmluZ2lmeSh2YXVsdFBhdGgpfSk7YFxuICAgICk7XG4gICAgYXdhaXQgdGhpcy5ldmFsdWF0ZShyZW1vdmVFeHByLCB7IGN3ZDogcHJvY2Vzcy5jd2QoKSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3Mgd2hldGhlciB0aGUgT2JzaWRpYW4gQ0xJIGJpbmFyeSBpcyBhdmFpbGFibGUgaW4gdGhlIHN5c3RlbSBQQVRILlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBhc3NlcnRDbGlBdmFpbGFibGUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgY29tbWFuZCA9IHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicgPyAnd2hlcmUuZXhlIG9ic2lkaWFuJyA6ICd3aGljaCBvYnNpZGlhbic7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGV4ZWMoY29tbWFuZCwgeyBpc1F1aWV0OiB0cnVlIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdPYnNpZGlhbiBDTEkgaXMgbm90IGF2YWlsYWJsZS4gRW5zdXJlIE9ic2lkaWFuIGlzIGluc3RhbGxlZCBhbmQgaXRzIENMSSBpcyBpbiB5b3VyIFBBVEguJyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIExhdW5jaGVzIE9ic2lkaWFuIGJpbmFyeSBhbmQgcmV0cmllcyB0aGUgZXZhbCB1bnRpbCBpdCByZXNwb25kcy5cbiAgICpcbiAgICogQHBhcmFtIGNvbW1hbmQgLSBUaGUgQ0xJIGNvbW1hbmQgYXJyYXkgdG8gcmV0cnkuXG4gICAqIEBwYXJhbSBjd2QgLSBUaGUgd29ya2luZyBkaXJlY3RvcnkuXG4gICAqIEByZXR1cm5zIFRoZSByZXN1bHQgc3RyaW5nIGZyb20gdGhlIHN1Y2Nlc3NmdWwgZXZhbC5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW5zdXJlT2JzaWRpYW5SdW5uaW5nQW5kUmV0cnkoY29tbWFuZDogc3RyaW5nW10sIGN3ZDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zb2xlLndhcm4oJ09ic2lkaWFuIGlzIG5vdCBydW5uaW5nLiBTdGFydGluZyBPYnNpZGlhbi4uLicpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGV4ZWMoZ2V0T2JzaWRpYW5MYXVuY2hDb21tYW5kKCksIHsgaXNRdWlldDogdHJ1ZSB9KTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIFRoZSBsYXVuY2ggY29tbWFuZCBtYXkgZmFpbCBvbiBzb21lIHN5c3RlbXMgXHUyMDE0IHdlJ2xsIHN0aWxsIHRyeSBwb2xsaW5nLlxuICAgIH1cblxuICAgIGNvbnN0IGRlYWRsaW5lID0gRGF0ZS5ub3coKSArIEFVVE9fU1RBUlRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFM7XG4gICAgd2hpbGUgKERhdGUubm93KCkgPCBkZWFkbGluZSkge1xuICAgICAgYXdhaXQgZGVsYXkoQVVUT19TVEFSVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyk7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gYXdhaXQgZXhlYyhjb21tYW5kLCB7IGN3ZCwgaXNRdWlldDogdHJ1ZSB9KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcik7XG4gICAgICAgIGlmICghbWVzc2FnZS5pbmNsdWRlcyhVTkFCTEVfVE9fRklORF9PQlNJRElBTikpIHtcbiAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIHRocm93IG5ldyBFcnJvcihgT2JzaWRpYW4gZGlkIG5vdCBzdGFydCB3aXRoaW4gJHtTdHJpbmcoQVVUT19TVEFSVF9USU1FT1VUX0lOX01JTExJU0VDT05EUyl9bXMuYCk7XG4gIH1cbn1cblxuLyoqXG4gKiBCdWlsZHMgYW4gYXN5bmMgSUlGRSB0aGF0IHdhaXRzIGZvciBsYXlvdXQgcmVhZHkgYW5kIGV4ZWN1dGVzIGFuIElQQyBjYWxsLlxuICogVGhlIHJldHVybiB2YWx1ZSBpcyBgSlNPTi5zdHJpbmdpZnkodW5kZWZpbmVkKWAgc28gdGhlIENMSSBwcm9kdWNlcyBgKG5vIG91dHB1dClgLlxuICpcbiAqIEBwYXJhbSBpcGNTdGF0ZW1lbnQgLSBUaGUgSVBDIHN0YXRlbWVudCB0byBleGVjdXRlLlxuICogQHJldHVybnMgVGhlIElJRkUgZXhwcmVzc2lvbiBzdHJpbmcuXG4gKi9cbmZ1bmN0aW9uIGJ1aWxkSXBjRXhwcmVzc2lvbihpcGNTdGF0ZW1lbnQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBidWlsZFNpbXBsZUV4cHJlc3Npb24oaXBjU3RhdGVtZW50KTtcbn1cblxuLyoqXG4gKiBCdWlsZHMgYSBzaW1wbGUgYXN5bmMgSUlGRSBleHByZXNzaW9uIHRoYXQgd2FpdHMgZm9yIGxheW91dCByZWFkeS5cbiAqXG4gKiBAcGFyYW0gYm9keSAtIFRoZSBKYXZhU2NyaXB0IHN0YXRlbWVudHMgdG8gZXhlY3V0ZS5cbiAqIEByZXR1cm5zIFRoZSBJSUZFIGV4cHJlc3Npb24gc3RyaW5nLlxuICovXG5mdW5jdGlvbiBidWlsZFNpbXBsZUV4cHJlc3Npb24oYm9keTogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIGAoYXN5bmMgKCkgPT4ge1xuICBpZiAoIWFwcC53b3Jrc3BhY2UubGF5b3V0UmVhZHkpIHtcbiAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gYXBwLndvcmtzcGFjZS5vbkxheW91dFJlYWR5KHJlc29sdmUpKTtcbiAgfVxuICAke2JvZHl9XG59KSgpYDtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIGFmdGVyIHRoZSBnaXZlbiBkZWxheS5cbiAqXG4gKiBAcGFyYW0gbXMgLSBUaGUgZGVsYXkgaW4gbWlsbGlzZWNvbmRzLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgYWZ0ZXIgdGhlIGRlbGF5LlxuICovXG5mdW5jdGlvbiBkZWxheShtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIHNldFRpbWVvdXQocmVzb2x2ZSwgbXMpO1xuICB9KTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBwbGF0Zm9ybS1zcGVjaWZpYyBjb21tYW5kIHRvIGxhdW5jaCB0aGUgT2JzaWRpYW4gYmluYXJ5LlxuICpcbiAqIEByZXR1cm5zIFRoZSBzaGVsbCBjb21tYW5kIHN0cmluZy5cbiAqL1xuZnVuY3Rpb24gZ2V0T2JzaWRpYW5MYXVuY2hDb21tYW5kKCk6IHN0cmluZyB7XG4gIGlmIChwcm9jZXNzLnBsYXRmb3JtID09PSAnd2luMzInKSB7XG4gICAgcmV0dXJuICdzdGFydCBcIlwiIFwiJUxPQ0FMQVBQREFUQSVcXFxcUHJvZ3JhbXNcXFxcT2JzaWRpYW5cXFxcT2JzaWRpYW4uZXhlXCInO1xuICB9XG5cbiAgaWYgKHByb2Nlc3MucGxhdGZvcm0gPT09ICdkYXJ3aW4nKSB7XG4gICAgcmV0dXJuICcvQXBwbGljYXRpb25zL09ic2lkaWFuLmFwcC9Db250ZW50cy9NYWNPUy9PYnNpZGlhbiAmJztcbiAgfVxuXG4gIHJldHVybiAnb2JzaWRpYW4gJic7XG59XG5cbi8qIHY4IGlnbm9yZSBzdG9wICovXG4iXSwKICAibWFwcGluZ3MiOiAiQUFTQSxPQUFPLGFBQWE7QUFPcEIsU0FBUyxZQUFZO0FBQ3JCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxPQUNLO0FBRVAsTUFBTSwwQkFBMEI7QUFDaEMsTUFBTSwy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
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Factory for creating transport instances from {@link ObsidianTransportOptions}.
5
+ */
6
+ import type { ObsidianTransportOptions } from './transport-options.mjs';
7
+ import type { ObsidianTransport } from './transport.mjs';
8
+ /**
9
+ * Creates a new transport instance from the given options.
10
+ *
11
+ * @param options - Transport configuration. Defaults to CLI transport.
12
+ * @returns A new transport instance.
13
+ */
14
+ export declare function createTransportFromOptions(options?: ObsidianTransportOptions): Promise<ObsidianTransport>;
15
+ /**
16
+ * Returns a cached transport instance, creating one from the given options
17
+ * if not already cached.
18
+ *
19
+ * The transport is cached per worker process so WebSocket/Appium sessions
20
+ * are reused across calls within the same test worker.
21
+ *
22
+ * @param options - Transport configuration. Defaults to CLI transport.
23
+ * @returns The cached or newly created transport.
24
+ */
25
+ export declare function getOrCreateTransport(options?: ObsidianTransportOptions): Promise<ObsidianTransport>;
@@ -0,0 +1,69 @@
1
+ import { remote } from "webdriverio";
2
+ import { AppiumTransport } from "./transport-appium.mjs";
3
+ import { DesktopCdpTransport } from "./transport-desktop-cdp.mjs";
4
+ import { DesktopCliTransport } from "./transport-desktop-cli.mjs";
5
+ const APP_PACKAGE = "md.obsidian";
6
+ const APP_ACTIVITY = `${APP_PACKAGE}.MainActivity`;
7
+ const COMMAND_TIMEOUT_IN_MILLISECONDS = 300;
8
+ const SERVER_LAUNCH_TIMEOUT_IN_MILLISECONDS = 3e4;
9
+ let cachedTransport;
10
+ async function createTransportFromOptions(options) {
11
+ if (!options || options.type === "obsidian-cli") {
12
+ return new DesktopCliTransport();
13
+ }
14
+ if (options.type === "obsidian-cdp") {
15
+ return new DesktopCdpTransport({
16
+ ...options.host !== void 0 && { cdpHost: options.host },
17
+ ...options.port !== void 0 && { cdpPort: options.port },
18
+ ...options.commandTimeoutInMilliseconds !== void 0 && { commandTimeoutInMilliseconds: options.commandTimeoutInMilliseconds }
19
+ });
20
+ }
21
+ return createAppiumTransport(options);
22
+ }
23
+ async function getOrCreateTransport(options) {
24
+ if (cachedTransport) {
25
+ return cachedTransport;
26
+ }
27
+ const result = await createTransportFromOptions(options);
28
+ cachedTransport = result;
29
+ return result;
30
+ }
31
+ async function createAppiumTransport(options) {
32
+ const url = new URL(options.appiumUrl);
33
+ const port = Number(url.port);
34
+ if (isNaN(port)) {
35
+ throw new Error(`Invalid port in appiumUrl: ${url.port}`);
36
+ }
37
+ const appId = options.appId ?? APP_PACKAGE;
38
+ const browser = await remote({
39
+ capabilities: {
40
+ "appium:appActivity": APP_ACTIVITY,
41
+ "appium:appPackage": appId,
42
+ "appium:autoGrantPermissions": true,
43
+ "appium:automationName": "UiAutomator2",
44
+ "appium:newCommandTimeout": COMMAND_TIMEOUT_IN_MILLISECONDS,
45
+ "appium:noReset": true,
46
+ "appium:settings": {
47
+ "appium:chromedriverAutodownload": true
48
+ },
49
+ "appium:skipServerInstallation": true,
50
+ "appium:udid": options.deviceId,
51
+ "appium:uiautomator2ServerLaunchTimeout": SERVER_LAUNCH_TIMEOUT_IN_MILLISECONDS,
52
+ "platformName": "Android"
53
+ },
54
+ hostname: url.hostname,
55
+ path: url.pathname,
56
+ port
57
+ });
58
+ return new AppiumTransport({
59
+ appId,
60
+ browser,
61
+ platform: "android",
62
+ ...options.vaultBasePath !== void 0 && { vaultBasePath: options.vaultBasePath }
63
+ });
64
+ }
65
+ export {
66
+ createTransportFromOptions,
67
+ getOrCreateTransport
68
+ };
69
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3RyYW5zcG9ydC1mYWN0b3J5LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogRmFjdG9yeSBmb3IgY3JlYXRpbmcgdHJhbnNwb3J0IGluc3RhbmNlcyBmcm9tIHtAbGluayBPYnNpZGlhblRyYW5zcG9ydE9wdGlvbnN9LlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGZhY3RvcnkgY292ZXJlZCBieSBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IHVuaXQgdGVzdHMuICovXG5cbmltcG9ydCB7IHJlbW90ZSB9IGZyb20gJ3dlYmRyaXZlcmlvJztcblxuaW1wb3J0IHR5cGUge1xuICBPYnNpZGlhbkFuZHJvaWRBcHBpdW1UcmFuc3BvcnRPcHRpb25zLFxuICBPYnNpZGlhblRyYW5zcG9ydE9wdGlvbnNcbn0gZnJvbSAnLi90cmFuc3BvcnQtb3B0aW9ucy5tanMnO1xuaW1wb3J0IHR5cGUgeyBPYnNpZGlhblRyYW5zcG9ydCB9IGZyb20gJy4vdHJhbnNwb3J0Lm1qcyc7XG5cbmltcG9ydCB7IEFwcGl1bVRyYW5zcG9ydCB9IGZyb20gJy4vdHJhbnNwb3J0LWFwcGl1bS5tanMnO1xuaW1wb3J0IHsgRGVza3RvcENkcFRyYW5zcG9ydCB9IGZyb20gJy4vdHJhbnNwb3J0LWRlc2t0b3AtY2RwLm1qcyc7XG5pbXBvcnQgeyBEZXNrdG9wQ2xpVHJhbnNwb3J0IH0gZnJvbSAnLi90cmFuc3BvcnQtZGVza3RvcC1jbGkubWpzJztcblxuY29uc3QgQVBQX1BBQ0tBR0UgPSAnbWQub2JzaWRpYW4nO1xuY29uc3QgQVBQX0FDVElWSVRZID0gYCR7QVBQX1BBQ0tBR0V9Lk1haW5BY3Rpdml0eWA7XG5jb25zdCBDT01NQU5EX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTID0gMzAwO1xuY29uc3QgU0VSVkVSX0xBVU5DSF9USU1FT1VUX0lOX01JTExJU0VDT05EUyA9IDMwMDAwO1xuXG5sZXQgY2FjaGVkVHJhbnNwb3J0OiBPYnNpZGlhblRyYW5zcG9ydCB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBDcmVhdGVzIGEgbmV3IHRyYW5zcG9ydCBpbnN0YW5jZSBmcm9tIHRoZSBnaXZlbiBvcHRpb25zLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gVHJhbnNwb3J0IGNvbmZpZ3VyYXRpb24uIERlZmF1bHRzIHRvIENMSSB0cmFuc3BvcnQuXG4gKiBAcmV0dXJucyBBIG5ldyB0cmFuc3BvcnQgaW5zdGFuY2UuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVUcmFuc3BvcnRGcm9tT3B0aW9ucyhvcHRpb25zPzogT2JzaWRpYW5UcmFuc3BvcnRPcHRpb25zKTogUHJvbWlzZTxPYnNpZGlhblRyYW5zcG9ydD4ge1xuICBpZiAoIW9wdGlvbnMgfHwgb3B0aW9ucy50eXBlID09PSAnb2JzaWRpYW4tY2xpJykge1xuICAgIHJldHVybiBuZXcgRGVza3RvcENsaVRyYW5zcG9ydCgpO1xuICB9XG5cbiAgaWYgKG9wdGlvbnMudHlwZSA9PT0gJ29ic2lkaWFuLWNkcCcpIHtcbiAgICByZXR1cm4gbmV3IERlc2t0b3BDZHBUcmFuc3BvcnQoe1xuICAgICAgLi4uKG9wdGlvbnMuaG9zdCAhPT0gdW5kZWZpbmVkICYmIHsgY2RwSG9zdDogb3B0aW9ucy5ob3N0IH0pLFxuICAgICAgLi4uKG9wdGlvbnMucG9ydCAhPT0gdW5kZWZpbmVkICYmIHsgY2RwUG9ydDogb3B0aW9ucy5wb3J0IH0pLFxuICAgICAgLi4uKG9wdGlvbnMuY29tbWFuZFRpbWVvdXRJbk1pbGxpc2Vjb25kcyAhPT0gdW5kZWZpbmVkICYmIHsgY29tbWFuZFRpbWVvdXRJbk1pbGxpc2Vjb25kczogb3B0aW9ucy5jb21tYW5kVGltZW91dEluTWlsbGlzZWNvbmRzIH0pXG4gICAgfSk7XG4gIH1cblxuICByZXR1cm4gY3JlYXRlQXBwaXVtVHJhbnNwb3J0KG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBjYWNoZWQgdHJhbnNwb3J0IGluc3RhbmNlLCBjcmVhdGluZyBvbmUgZnJvbSB0aGUgZ2l2ZW4gb3B0aW9uc1xuICogaWYgbm90IGFscmVhZHkgY2FjaGVkLlxuICpcbiAqIFRoZSB0cmFuc3BvcnQgaXMgY2FjaGVkIHBlciB3b3JrZXIgcHJvY2VzcyBzbyBXZWJTb2NrZXQvQXBwaXVtIHNlc3Npb25zXG4gKiBhcmUgcmV1c2VkIGFjcm9zcyBjYWxscyB3aXRoaW4gdGhlIHNhbWUgdGVzdCB3b3JrZXIuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBUcmFuc3BvcnQgY29uZmlndXJhdGlvbi4gRGVmYXVsdHMgdG8gQ0xJIHRyYW5zcG9ydC5cbiAqIEByZXR1cm5zIFRoZSBjYWNoZWQgb3IgbmV3bHkgY3JlYXRlZCB0cmFuc3BvcnQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRPckNyZWF0ZVRyYW5zcG9ydChvcHRpb25zPzogT2JzaWRpYW5UcmFuc3BvcnRPcHRpb25zKTogUHJvbWlzZTxPYnNpZGlhblRyYW5zcG9ydD4ge1xuICBpZiAoY2FjaGVkVHJhbnNwb3J0KSB7XG4gICAgcmV0dXJuIGNhY2hlZFRyYW5zcG9ydDtcbiAgfVxuXG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGNyZWF0ZVRyYW5zcG9ydEZyb21PcHRpb25zKG9wdGlvbnMpO1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVxdWlyZS1hdG9taWMtdXBkYXRlcyAtLSBTaW5nbGUtdGhyZWFkZWQgd29ya2VyOyBubyBjb25jdXJyZW50IHdyaXRlcy5cbiAgY2FjaGVkVHJhbnNwb3J0ID0gcmVzdWx0O1xuICByZXR1cm4gcmVzdWx0O1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYW4gQXBwaXVtIHRyYW5zcG9ydCBieSBlc3RhYmxpc2hpbmcgYSBXZWJEcml2ZXJJTyBzZXNzaW9uLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gQW5kcm9pZCBBcHBpdW0gdHJhbnNwb3J0IG9wdGlvbnMuXG4gKiBAcmV0dXJucyBBIGNvbmZpZ3VyZWQgQXBwaXVtIHRyYW5zcG9ydC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gY3JlYXRlQXBwaXVtVHJhbnNwb3J0KG9wdGlvbnM6IE9ic2lkaWFuQW5kcm9pZEFwcGl1bVRyYW5zcG9ydE9wdGlvbnMpOiBQcm9taXNlPEFwcGl1bVRyYW5zcG9ydD4ge1xuICBjb25zdCB1cmwgPSBuZXcgVVJMKG9wdGlvbnMuYXBwaXVtVXJsKTtcblxuICBjb25zdCBwb3J0ID0gTnVtYmVyKHVybC5wb3J0KTtcbiAgaWYgKGlzTmFOKHBvcnQpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHBvcnQgaW4gYXBwaXVtVXJsOiAke3VybC5wb3J0fWApO1xuICB9XG5cbiAgY29uc3QgYXBwSWQgPSBvcHRpb25zLmFwcElkID8/IEFQUF9QQUNLQUdFO1xuXG4gIGNvbnN0IGJyb3dzZXIgPSBhd2FpdCByZW1vdGUoe1xuICAgIGNhcGFiaWxpdGllczoge1xuICAgICAgJ2FwcGl1bTphcHBBY3Rpdml0eSc6IEFQUF9BQ1RJVklUWSxcbiAgICAgICdhcHBpdW06YXBwUGFja2FnZSc6IGFwcElkLFxuICAgICAgJ2FwcGl1bTphdXRvR3JhbnRQZXJtaXNzaW9ucyc6IHRydWUsXG4gICAgICAnYXBwaXVtOmF1dG9tYXRpb25OYW1lJzogJ1VpQXV0b21hdG9yMicsXG4gICAgICAnYXBwaXVtOm5ld0NvbW1hbmRUaW1lb3V0JzogQ09NTUFORF9USU1FT1VUX0lOX01JTExJU0VDT05EUyxcbiAgICAgICdhcHBpdW06bm9SZXNldCc6IHRydWUsXG4gICAgICAnYXBwaXVtOnNldHRpbmdzJzoge1xuICAgICAgICAnYXBwaXVtOmNocm9tZWRyaXZlckF1dG9kb3dubG9hZCc6IHRydWVcbiAgICAgIH0sXG4gICAgICAnYXBwaXVtOnNraXBTZXJ2ZXJJbnN0YWxsYXRpb24nOiB0cnVlLFxuICAgICAgJ2FwcGl1bTp1ZGlkJzogb3B0aW9ucy5kZXZpY2VJZCxcbiAgICAgICdhcHBpdW06dWlhdXRvbWF0b3IyU2VydmVyTGF1bmNoVGltZW91dCc6IFNFUlZFUl9MQVVOQ0hfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMsXG4gICAgICAncGxhdGZvcm1OYW1lJzogJ0FuZHJvaWQnXG4gICAgfSxcbiAgICBob3N0bmFtZTogdXJsLmhvc3RuYW1lLFxuICAgIHBhdGg6IHVybC5wYXRobmFtZSxcbiAgICBwb3J0XG4gIH0pO1xuXG4gIHJldHVybiBuZXcgQXBwaXVtVHJhbnNwb3J0KHtcbiAgICBhcHBJZCxcbiAgICBicm93c2VyLFxuICAgIHBsYXRmb3JtOiAnYW5kcm9pZCcsXG4gICAgLi4uKG9wdGlvbnMudmF1bHRCYXNlUGF0aCAhPT0gdW5kZWZpbmVkICYmIHsgdmF1bHRCYXNlUGF0aDogb3B0aW9ucy52YXVsdEJhc2VQYXRoIH0pXG4gIH0pO1xufVxuXG4vKiB2OCBpZ25vcmUgc3RvcCAqL1xuIl0sCiAgIm1hcHBpbmdzIjogIkFBUUEsU0FBUyxjQUFjO0FBUXZCLFNBQVMsdUJBQXVCO0FBQ2hDLFNBQVMsMkJBQTJCO0FBQ3BDLFNBQVMsMkJBQTJCO0FBRXBDLE1BQU0sY0FBYztBQUNwQixNQUFNLGVBQWUsR0FBRyxXQUFXO0FBQ25DLE1BQU0sa0NBQWtDO0FBQ3hDLE1BQU0sd0NBQXdDO0FBRTlDLElBQUk7QUFRSixlQUFzQiwyQkFBMkIsU0FBZ0U7QUFDL0csTUFBSSxDQUFDLFdBQVcsUUFBUSxTQUFTLGdCQUFnQjtBQUMvQyxXQUFPLElBQUksb0JBQW9CO0FBQUEsRUFDakM7QUFFQSxNQUFJLFFBQVEsU0FBUyxnQkFBZ0I7QUFDbkMsV0FBTyxJQUFJLG9CQUFvQjtBQUFBLE1BQzdCLEdBQUksUUFBUSxTQUFTLFVBQWEsRUFBRSxTQUFTLFFBQVEsS0FBSztBQUFBLE1BQzFELEdBQUksUUFBUSxTQUFTLFVBQWEsRUFBRSxTQUFTLFFBQVEsS0FBSztBQUFBLE1BQzFELEdBQUksUUFBUSxpQ0FBaUMsVUFBYSxFQUFFLDhCQUE4QixRQUFRLDZCQUE2QjtBQUFBLElBQ2pJLENBQUM7QUFBQSxFQUNIO0FBRUEsU0FBTyxzQkFBc0IsT0FBTztBQUN0QztBQVlBLGVBQXNCLHFCQUFxQixTQUFnRTtBQUN6RyxNQUFJLGlCQUFpQjtBQUNuQixXQUFPO0FBQUEsRUFDVDtBQUVBLFFBQU0sU0FBUyxNQUFNLDJCQUEyQixPQUFPO0FBRXZELG9CQUFrQjtBQUNsQixTQUFPO0FBQ1Q7QUFRQSxlQUFlLHNCQUFzQixTQUEwRTtBQUM3RyxRQUFNLE1BQU0sSUFBSSxJQUFJLFFBQVEsU0FBUztBQUVyQyxRQUFNLE9BQU8sT0FBTyxJQUFJLElBQUk7QUFDNUIsTUFBSSxNQUFNLElBQUksR0FBRztBQUNmLFVBQU0sSUFBSSxNQUFNLDhCQUE4QixJQUFJLElBQUksRUFBRTtBQUFBLEVBQzFEO0FBRUEsUUFBTSxRQUFRLFFBQVEsU0FBUztBQUUvQixRQUFNLFVBQVUsTUFBTSxPQUFPO0FBQUEsSUFDM0IsY0FBYztBQUFBLE1BQ1osc0JBQXNCO0FBQUEsTUFDdEIscUJBQXFCO0FBQUEsTUFDckIsK0JBQStCO0FBQUEsTUFDL0IseUJBQXlCO0FBQUEsTUFDekIsNEJBQTRCO0FBQUEsTUFDNUIsa0JBQWtCO0FBQUEsTUFDbEIsbUJBQW1CO0FBQUEsUUFDakIsbUNBQW1DO0FBQUEsTUFDckM7QUFBQSxNQUNBLGlDQUFpQztBQUFBLE1BQ2pDLGVBQWUsUUFBUTtBQUFBLE1BQ3ZCLDBDQUEwQztBQUFBLE1BQzFDLGdCQUFnQjtBQUFBLElBQ2xCO0FBQUEsSUFDQSxVQUFVLElBQUk7QUFBQSxJQUNkLE1BQU0sSUFBSTtBQUFBLElBQ1Y7QUFBQSxFQUNGLENBQUM7QUFFRCxTQUFPLElBQUksZ0JBQWdCO0FBQUEsSUFDekI7QUFBQSxJQUNBO0FBQUEsSUFDQSxVQUFVO0FBQUEsSUFDVixHQUFJLFFBQVEsa0JBQWtCLFVBQWEsRUFBRSxlQUFlLFFBQVEsY0FBYztBQUFBLEVBQ3BGLENBQUM7QUFDSDsiLAogICJuYW1lcyI6IFtdCn0K
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Discriminated union types for configuring the Obsidian transport
5
+ * via vitest `environmentOptions`.
6
+ */
7
+ /**
8
+ * Transport options for Android testing via Appium WebView injection.
9
+ */
10
+ export interface ObsidianAndroidAppiumTransportOptions {
11
+ /**
12
+ * App package (Android) or bundle ID (iOS).
13
+ * Defaults to `'md.obsidian'`
14
+ */
15
+ appId?: string;
16
+ /**
17
+ * The Appium server URL (e.g. `'http://localhost:4723'`).
18
+ */
19
+ appiumUrl: string;
20
+ /**
21
+ * The device UDID (e.g. `'emulator-5554'`).
22
+ */
23
+ deviceId: string;
24
+ /**
25
+ * Discriminant for the transport type.
26
+ */
27
+ type: 'obsidian-android-appium';
28
+ /**
29
+ * Base path on the device where Obsidian stores vaults.
30
+ *
31
+ * Defaults:
32
+ * - Android: `/sdcard/Documents/`
33
+ * - iOS: `@md.obsidian:documents/`
34
+ */
35
+ vaultBasePath?: string;
36
+ }
37
+ /**
38
+ * Transport options for desktop testing via Chrome DevTools Protocol.
39
+ */
40
+ export interface ObsidianCdpTransportOptions {
41
+ /**
42
+ * Timeout in milliseconds for individual CDP commands.
43
+ * Defaults to 30000
44
+ */
45
+ commandTimeoutInMilliseconds?: number;
46
+ /**
47
+ * CDP host.
48
+ * Defaults to `'localhost'`
49
+ */
50
+ host?: string;
51
+ /**
52
+ * CDP port.
53
+ * Defaults to 8315
54
+ */
55
+ port?: number;
56
+ /**
57
+ * Discriminant for the transport type.
58
+ */
59
+ type: 'obsidian-cdp';
60
+ }
61
+ /**
62
+ * Transport options for desktop testing via the Obsidian CLI.
63
+ *
64
+ * This is the default transport when no `obsidianTransport` is configured.
65
+ */
66
+ export interface ObsidianCliTransportOptions {
67
+ /**
68
+ * Discriminant for the transport type.
69
+ */
70
+ type: 'obsidian-cli';
71
+ }
72
+ /**
73
+ * Discriminated union of all supported transport configurations.
74
+ *
75
+ * Used in vitest `environmentOptions.obsidianTransport` to select and
76
+ * configure the transport for integration tests.
77
+ */
78
+ export type ObsidianTransportOptions = ObsidianAndroidAppiumTransportOptions | ObsidianCdpTransportOptions | ObsidianCliTransportOptions;
79
+ declare module 'vitest' {
80
+ interface EnvironmentOptions {
81
+ /**
82
+ * Configures the transport used by `obsidian-integration-testing` to
83
+ * communicate with a running Obsidian instance.
84
+ *
85
+ * When omitted, defaults to the CLI transport (`obsidian-cli`).
86
+ */
87
+ obsidianTransport?: ObsidianTransportOptions;
88
+ }
89
+ interface ProvidedContext {
90
+ /**
91
+ * Transport options provided by the global setup, consumed by
92
+ * `evalInObsidian` and other library functions via `inject()`.
93
+ */
94
+ obsidianTransport?: ObsidianTransportOptions;
95
+ /**
96
+ * Path to the temporary vault created by the global setup.
97
+ */
98
+ tempVaultPath: string;
99
+ }
100
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFtdLAogICJtYXBwaW5ncyI6ICIiLAogICJuYW1lcyI6IFtdCn0K
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * Defines the transport interface for communicating with a running Obsidian instance.
5
+ */
6
+ /**
7
+ * A pluggable transport that evaluates JavaScript expressions inside a running
8
+ * Obsidian instance and manages vault lifecycle.
9
+ *
10
+ * Implementations handle the platform-specific details:
11
+ * - {@link DesktopCliTransport} — Desktop Obsidian via CLI (`obsidian eval`)
12
+ * - {@link DesktopCdpTransport} — Desktop Obsidian via Chrome DevTools Protocol
13
+ * - {@link AppiumTransport} — Mobile Obsidian via Appium WebView JS injection
14
+ */
15
+ export interface ObsidianTransport {
16
+ /**
17
+ * Disposes of transport resources (e.g. WebSocket connections, Appium sessions).
18
+ */
19
+ dispose?(): Promise<void>;
20
+ /**
21
+ * Evaluates a JavaScript expression string inside Obsidian and returns the
22
+ * raw string result.
23
+ *
24
+ * The transport normalizes the output — stripping transport-specific prefixes
25
+ * and handling transport-specific errors — so callers receive a clean result
26
+ * string (e.g. a JSON string, or `(no output)`).
27
+ *
28
+ * @param expression - A self-contained JavaScript expression (typically an async IIFE).
29
+ * @param options - Evaluation options including the working directory.
30
+ * @returns The raw result string from Obsidian.
31
+ */
32
+ evaluate(expression: string, options: TransportEvalOptions): Promise<string>;
33
+ /**
34
+ * Whether this transport targets a mobile Obsidian instance.
35
+ *
36
+ * When `true`, desktop-only plugins (with `isDesktopOnly: true` in manifest)
37
+ * will refuse to run integration tests.
38
+ */
39
+ isMobile: boolean;
40
+ /**
41
+ * Runs transport-specific preflight checks before evaluation.
42
+ *
43
+ * For example, the CLI transport verifies that the vault is registered,
44
+ * the CLI is enabled, and the CLI binary is in PATH.
45
+ *
46
+ * @param vaultPath - The absolute path to the vault folder.
47
+ */
48
+ preflightCheck(vaultPath: string): Promise<void>;
49
+ /**
50
+ * Pushes files into a vault directory on the target device.
51
+ *
52
+ * On desktop this is a no-op (files are written to the local filesystem directly).
53
+ * On mobile this uses the device's file transfer mechanism (e.g. Appium `pushFile`).
54
+ *
55
+ * @param vaultPath - The absolute path to the vault folder.
56
+ * @param files - Map of relative file paths to content strings.
57
+ */
58
+ pushFiles?(vaultPath: string, files: Record<string, string>): Promise<void>;
59
+ /**
60
+ * Registers a vault path so Obsidian can target it.
61
+ *
62
+ * On desktop: uses Electron IPC to open the vault and polls for readiness.
63
+ * On mobile: pushes vault files to the device and restarts the app.
64
+ *
65
+ * @param vaultPath - The absolute path to the vault folder.
66
+ */
67
+ registerVault(vaultPath: string): Promise<void>;
68
+ /**
69
+ * Unregisters a vault path from the running Obsidian instance.
70
+ *
71
+ * On desktop: closes the vault window and removes it from the registry.
72
+ * On mobile: removes vault files from the device.
73
+ *
74
+ * @param vaultPath - The absolute path to the vault folder.
75
+ */
76
+ unregisterVault(vaultPath: string): Promise<void>;
77
+ }
78
+ /**
79
+ * Options for {@link ObsidianTransport.evaluate}.
80
+ */
81
+ export interface TransportEvalOptions {
82
+ /**
83
+ * The working directory (vault path) for the evaluation.
84
+ */
85
+ readonly cwd: string;
86
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFtdLAogICJtYXBwaW5ncyI6ICIiLAogICJuYW1lcyI6IFtdCn0K
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @packageDocumentation
2
+ * @file
3
3
  *
4
4
  * Contains utility functions for type guards.
5
5
  */
@@ -14,4 +14,4 @@ export {
14
14
  assertNonNullable,
15
15
  ensureNonNullable
16
16
  };
17
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3R5cGUtZ3VhcmRzLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBwYWNrYWdlRG9jdW1lbnRhdGlvblxuICpcbiAqIENvbnRhaW5zIHV0aWxpdHkgZnVuY3Rpb25zIGZvciB0eXBlIGd1YXJkcy5cbiAqL1xuXG50eXBlIE51bGxhYmxlQ29uc3RyYWludDxUPiA9IG51bGwgZXh0ZW5kcyBUID8gdW5rbm93biA6IHVuZGVmaW5lZCBleHRlbmRzIFQgPyB1bmtub3duIDogbmV2ZXI7XG5cbi8qKlxuICogQXNzZXJ0cyB0aGF0IGEgdmFsdWUgaXMgbm90IGBudWxsYCBvciBgdW5kZWZpbmVkYCwgbmFycm93aW5nIGl0cyB0eXBlIGluIHBsYWNlLlxuICpcbiAqIE9ubHkgY2FsbGFibGUgd2hlbiBgVGAgaW5jbHVkZXMgYG51bGxgIG9yIGB1bmRlZmluZWRgLiBQYXNzaW5nIGFuIGFscmVhZHkgbm9uLW51bGxhYmxlIHR5cGUgaXMgYSBjb21waWxlIGVycm9yLlxuICpcbiAqIEB0eXBlUGFyYW0gVCAtIFRoZSB0eXBlIG9mIHRoZSB2YWx1ZS5cbiAqIEBwYXJhbSB2YWx1ZSAtIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEBwYXJhbSBlcnJvck9yTWVzc2FnZSAtIE9wdGlvbmFsIHtAbGluayBFcnJvcn0gb3IgZXJyb3IgbWVzc2FnZSBzdHJpbmcuXG4gKiBAdGhyb3dzIElmIHRoZSB2YWx1ZSBpcyBgbnVsbGAgb3IgYHVuZGVmaW5lZGAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnROb25OdWxsYWJsZTxUIGV4dGVuZHMgTnVsbGFibGVDb25zdHJhaW50PFQ+Pih2YWx1ZTogVCwgZXJyb3JPck1lc3NhZ2U/OiBFcnJvciB8IHN0cmluZyk6IGFzc2VydHMgdmFsdWUgaXMgTm9uTnVsbGFibGU8VD4ge1xuICBpZiAodmFsdWUgIT09IG51bGwgJiYgdmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGVycm9yT3JNZXNzYWdlID8/PSB2YWx1ZSA9PT0gbnVsbCA/ICdWYWx1ZSBpcyBudWxsJyA6ICdWYWx1ZSBpcyB1bmRlZmluZWQnO1xuICBjb25zdCBlcnJvciA9IHR5cGVvZiBlcnJvck9yTWVzc2FnZSA9PT0gJ3N0cmluZycgPyBuZXcgRXJyb3IoZXJyb3JPck1lc3NhZ2UpIDogZXJyb3JPck1lc3NhZ2U7XG4gIHRocm93IGVycm9yO1xufVxuXG4vKipcbiAqIEVuc3VyZXMgdGhhdCBhIHZhbHVlIGlzIG5vdCBgbnVsbGAgb3IgYHVuZGVmaW5lZGAgYW5kIHJldHVybnMgaXQgd2l0aCBuYXJyb3dlZCB0eXBlLlxuICpcbiAqIE9ubHkgY2FsbGFibGUgd2hlbiBgVGAgaW5jbHVkZXMgYG51bGxgIG9yIGB1bmRlZmluZWRgLiBQYXNzaW5nIGFuIGFscmVhZHkgbm9uLW51bGxhYmxlIHR5cGUgaXMgYSBjb21waWxlIGVycm9yLlxuICpcbiAqIEB0eXBlUGFyYW0gVCAtIFRoZSB0eXBlIG9mIHRoZSB2YWx1ZS5cbiAqIEBwYXJhbSB2YWx1ZSAtIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEBwYXJhbSBlcnJvck9yTWVzc2FnZSAtIE9wdGlvbmFsIHtAbGluayBFcnJvcn0gb3IgZXJyb3IgbWVzc2FnZSBzdHJpbmcuXG4gKiBAcmV0dXJucyBUaGUgdmFsdWUgd2l0aCBgbnVsbGAgYW5kIGB1bmRlZmluZWRgIGV4Y2x1ZGVkIGZyb20gaXRzIHR5cGUuXG4gKiBAdGhyb3dzIElmIHRoZSB2YWx1ZSBpcyBgbnVsbGAgb3IgYHVuZGVmaW5lZGAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlbnN1cmVOb25OdWxsYWJsZTxUIGV4dGVuZHMgTnVsbGFibGVDb25zdHJhaW50PFQ+Pih2YWx1ZTogVCwgZXJyb3JPck1lc3NhZ2U/OiBFcnJvciB8IHN0cmluZyk6IE5vbk51bGxhYmxlPFQ+IHtcbiAgYXNzZXJ0Tm9uTnVsbGFibGUodmFsdWUsIGVycm9yT3JNZXNzYWdlKTtcbiAgcmV0dXJuIHZhbHVlO1xufVxuIl0sCiAgIm1hcHBpbmdzIjogIkFBa0JPLFNBQVMsa0JBQW1ELE9BQVUsZ0JBQWtFO0FBQzdJLE1BQUksVUFBVSxRQUFRLFVBQVUsUUFBVztBQUN6QztBQUFBLEVBQ0Y7QUFFQSxxQkFBbUIsVUFBVSxPQUFPLGtCQUFrQjtBQUN0RCxRQUFNLFFBQVEsT0FBTyxtQkFBbUIsV0FBVyxJQUFJLE1BQU0sY0FBYyxJQUFJO0FBQy9FLFFBQU07QUFDUjtBQWFPLFNBQVMsa0JBQW1ELE9BQVUsZ0JBQWlEO0FBQzVILG9CQUFrQixPQUFPLGNBQWM7QUFDdkMsU0FBTztBQUNUOyIsCiAgIm5hbWVzIjogW10KfQo=
17
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3R5cGUtZ3VhcmRzLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogQ29udGFpbnMgdXRpbGl0eSBmdW5jdGlvbnMgZm9yIHR5cGUgZ3VhcmRzLlxuICovXG5cbnR5cGUgTnVsbGFibGVDb25zdHJhaW50PFQ+ID0gbnVsbCBleHRlbmRzIFQgPyB1bmtub3duIDogdW5kZWZpbmVkIGV4dGVuZHMgVCA/IHVua25vd24gOiBuZXZlcjtcblxuLyoqXG4gKiBBc3NlcnRzIHRoYXQgYSB2YWx1ZSBpcyBub3QgYG51bGxgIG9yIGB1bmRlZmluZWRgLCBuYXJyb3dpbmcgaXRzIHR5cGUgaW4gcGxhY2UuXG4gKlxuICogT25seSBjYWxsYWJsZSB3aGVuIGBUYCBpbmNsdWRlcyBgbnVsbGAgb3IgYHVuZGVmaW5lZGAuIFBhc3NpbmcgYW4gYWxyZWFkeSBub24tbnVsbGFibGUgdHlwZSBpcyBhIGNvbXBpbGUgZXJyb3IuXG4gKlxuICogQHR5cGVQYXJhbSBUIC0gVGhlIHR5cGUgb2YgdGhlIHZhbHVlLlxuICogQHBhcmFtIHZhbHVlIC0gVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHBhcmFtIGVycm9yT3JNZXNzYWdlIC0gT3B0aW9uYWwge0BsaW5rIEVycm9yfSBvciBlcnJvciBtZXNzYWdlIHN0cmluZy5cbiAqIEB0aHJvd3MgSWYgdGhlIHZhbHVlIGlzIGBudWxsYCBvciBgdW5kZWZpbmVkYC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydE5vbk51bGxhYmxlPFQgZXh0ZW5kcyBOdWxsYWJsZUNvbnN0cmFpbnQ8VD4+KHZhbHVlOiBULCBlcnJvck9yTWVzc2FnZT86IEVycm9yIHwgc3RyaW5nKTogYXNzZXJ0cyB2YWx1ZSBpcyBOb25OdWxsYWJsZTxUPiB7XG4gIGlmICh2YWx1ZSAhPT0gbnVsbCAmJiB2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgZXJyb3JPck1lc3NhZ2UgPz89IHZhbHVlID09PSBudWxsID8gJ1ZhbHVlIGlzIG51bGwnIDogJ1ZhbHVlIGlzIHVuZGVmaW5lZCc7XG4gIGNvbnN0IGVycm9yID0gdHlwZW9mIGVycm9yT3JNZXNzYWdlID09PSAnc3RyaW5nJyA/IG5ldyBFcnJvcihlcnJvck9yTWVzc2FnZSkgOiBlcnJvck9yTWVzc2FnZTtcbiAgdGhyb3cgZXJyb3I7XG59XG5cbi8qKlxuICogRW5zdXJlcyB0aGF0IGEgdmFsdWUgaXMgbm90IGBudWxsYCBvciBgdW5kZWZpbmVkYCBhbmQgcmV0dXJucyBpdCB3aXRoIG5hcnJvd2VkIHR5cGUuXG4gKlxuICogT25seSBjYWxsYWJsZSB3aGVuIGBUYCBpbmNsdWRlcyBgbnVsbGAgb3IgYHVuZGVmaW5lZGAuIFBhc3NpbmcgYW4gYWxyZWFkeSBub24tbnVsbGFibGUgdHlwZSBpcyBhIGNvbXBpbGUgZXJyb3IuXG4gKlxuICogQHR5cGVQYXJhbSBUIC0gVGhlIHR5cGUgb2YgdGhlIHZhbHVlLlxuICogQHBhcmFtIHZhbHVlIC0gVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHBhcmFtIGVycm9yT3JNZXNzYWdlIC0gT3B0aW9uYWwge0BsaW5rIEVycm9yfSBvciBlcnJvciBtZXNzYWdlIHN0cmluZy5cbiAqIEByZXR1cm5zIFRoZSB2YWx1ZSB3aXRoIGBudWxsYCBhbmQgYHVuZGVmaW5lZGAgZXhjbHVkZWQgZnJvbSBpdHMgdHlwZS5cbiAqIEB0aHJvd3MgSWYgdGhlIHZhbHVlIGlzIGBudWxsYCBvciBgdW5kZWZpbmVkYC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGVuc3VyZU5vbk51bGxhYmxlPFQgZXh0ZW5kcyBOdWxsYWJsZUNvbnN0cmFpbnQ8VD4+KHZhbHVlOiBULCBlcnJvck9yTWVzc2FnZT86IEVycm9yIHwgc3RyaW5nKTogTm9uTnVsbGFibGU8VD4ge1xuICBhc3NlcnROb25OdWxsYWJsZSh2YWx1ZSwgZXJyb3JPck1lc3NhZ2UpO1xuICByZXR1cm4gdmFsdWU7XG59XG4iXSwKICAibWFwcGluZ3MiOiAiQUFrQk8sU0FBUyxrQkFBbUQsT0FBVSxnQkFBa0U7QUFDN0ksTUFBSSxVQUFVLFFBQVEsVUFBVSxRQUFXO0FBQ3pDO0FBQUEsRUFDRjtBQUVBLHFCQUFtQixVQUFVLE9BQU8sa0JBQWtCO0FBQ3RELFFBQU0sUUFBUSxPQUFPLG1CQUFtQixXQUFXLElBQUksTUFBTSxjQUFjLElBQUk7QUFDL0UsUUFBTTtBQUNSO0FBYU8sU0FBUyxrQkFBbUQsT0FBVSxnQkFBaUQ7QUFDNUgsb0JBQWtCLE9BQU8sY0FBYztBQUN2QyxTQUFPO0FBQ1Q7IiwKICAibmFtZXMiOiBbXQp9Cg==
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Registers a vault path in the running Obsidian instance so the CLI can target it via `cwd`.
2
+ * Registers a vault path in the running Obsidian instance.
3
3
  *
4
- * Uses the `vault-open` IPC to register and open the vault, then polls until
5
- * the vault's CLI is ready and `getBasePath()` matches `vaultPath`.
4
+ * Delegates to the active transport's {@link ObsidianTransport.registerVault}.
6
5
  *
7
6
  * @param vaultPath - The absolute path to the vault folder.
8
7
  */
@@ -10,8 +9,7 @@ export declare function registerVault(vaultPath: string): Promise<void>;
10
9
  /**
11
10
  * Unregisters a vault path from the running Obsidian instance.
12
11
  *
13
- * Schedules the vault window to close, waits for it, then removes
14
- * the vault from the registry via `vault-remove` IPC.
12
+ * Delegates to the active transport's {@link ObsidianTransport.unregisterVault}.
15
13
  *
16
14
  * @param vaultPath - The absolute path to the vault folder.
17
15
  */
@@ -1,64 +1,15 @@
1
- import { evalInObsidian } from "./obsidian-cli.mjs";
2
- const POLL_INTERVAL_MS = 500;
3
- const POLL_TIMEOUT_MS = 3e4;
4
- const CLOSE_DELAY_MS = 1e3;
1
+ import { inject } from "vitest";
2
+ import { getOrCreateTransport } from "./transport-factory.mjs";
5
3
  async function registerVault(vaultPath) {
6
- await evalInObsidian({
7
- args: { vaultPath },
8
- // eslint-disable-next-line no-shadow -- No actual shadowing as the function is executed externally.
9
- fn({ vaultPath: vaultPath2 }) {
10
- window.electron.ipcRenderer.sendSync("vault-open", vaultPath2, false);
11
- },
12
- shouldSkipPreflightChecks: true
13
- });
14
- const deadline = Date.now() + POLL_TIMEOUT_MS;
15
- while (Date.now() < deadline) {
16
- try {
17
- const basePath = await evalInObsidian({
18
- fn({ app }) {
19
- return app.vault.adapter.getBasePath();
20
- },
21
- shouldSkipPreflightChecks: true,
22
- vaultPath
23
- });
24
- if (basePath === vaultPath) {
25
- return;
26
- }
27
- } catch {
28
- }
29
- await new Promise((r) => {
30
- setTimeout(r, POLL_INTERVAL_MS);
31
- });
32
- }
33
- throw new Error(`Vault at ${vaultPath} did not become ready within ${String(POLL_TIMEOUT_MS)}ms`);
4
+ const transport = await getOrCreateTransport(inject("obsidianTransport"));
5
+ await transport.registerVault(vaultPath);
34
6
  }
35
7
  async function unregisterVault(vaultPath) {
36
- try {
37
- await evalInObsidian({
38
- fn() {
39
- setTimeout(() => {
40
- window.electron.remote?.getCurrentWindow().destroy();
41
- }, 0);
42
- },
43
- shouldSkipPreflightChecks: true,
44
- vaultPath
45
- });
46
- } catch {
47
- }
48
- await new Promise((r) => {
49
- setTimeout(r, CLOSE_DELAY_MS);
50
- });
51
- await evalInObsidian({
52
- args: { vaultPath },
53
- // eslint-disable-next-line no-shadow -- No actual shadowing as the function is executed externally.
54
- fn({ vaultPath: vaultPath2 }) {
55
- window.electron.ipcRenderer.sendSync("vault-remove", vaultPath2);
56
- },
57
- shouldSkipPreflightChecks: true
58
- });
8
+ const transport = await getOrCreateTransport(inject("obsidianTransport"));
9
+ await transport.unregisterVault(vaultPath);
59
10
  }
60
11
  export {
61
12
  registerVault,
62
13
  unregisterVault
63
14
  };
64
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3ZhdWx0LXJlZ2lzdHJ5LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG4vKipcbiAqIEBwYWNrYWdlRG9jdW1lbnRhdGlvblxuICpcbiAqIE1hbmFnZXMgdmF1bHQgcmVnaXN0cmF0aW9uIGluIHRoZSBydW5uaW5nIE9ic2lkaWFuIGluc3RhbmNlLlxuICovXG5cbmltcG9ydCB0eXBlIHsgRmlsZVN5c3RlbUFkYXB0ZXIgfSBmcm9tICdvYnNpZGlhbic7XG5cbmltcG9ydCB7IGV2YWxJbk9ic2lkaWFuIH0gZnJvbSAnLi9vYnNpZGlhbi1jbGkubWpzJztcblxuY29uc3QgUE9MTF9JTlRFUlZBTF9NUyA9IDUwMDtcbmNvbnN0IFBPTExfVElNRU9VVF9NUyA9IDMwMDAwO1xuY29uc3QgQ0xPU0VfREVMQVlfTVMgPSAxMDAwO1xuXG5pbnRlcmZhY2UgRWxlY3Ryb24ge1xuICByZW1vdGU6IEVsZWN0cm9uUmVtb3RlO1xufVxuXG5pbnRlcmZhY2UgRWxlY3Ryb25SZW1vdGUge1xuICBnZXRDdXJyZW50V2luZG93KCk6IEdldEN1cnJlbnRXaW5kb3dSZXN1bHQ7XG59XG5cbmludGVyZmFjZSBHZXRDdXJyZW50V2luZG93UmVzdWx0IHtcbiAgZGVzdHJveSgpOiB2b2lkO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVycyBhIHZhdWx0IHBhdGggaW4gdGhlIHJ1bm5pbmcgT2JzaWRpYW4gaW5zdGFuY2Ugc28gdGhlIENMSSBjYW4gdGFyZ2V0IGl0IHZpYSBgY3dkYC5cbiAqXG4gKiBVc2VzIHRoZSBgdmF1bHQtb3BlbmAgSVBDIHRvIHJlZ2lzdGVyIGFuZCBvcGVuIHRoZSB2YXVsdCwgdGhlbiBwb2xscyB1bnRpbFxuICogdGhlIHZhdWx0J3MgQ0xJIGlzIHJlYWR5IGFuZCBgZ2V0QmFzZVBhdGgoKWAgbWF0Y2hlcyBgdmF1bHRQYXRoYC5cbiAqXG4gKiBAcGFyYW0gdmF1bHRQYXRoIC0gVGhlIGFic29sdXRlIHBhdGggdG8gdGhlIHZhdWx0IGZvbGRlci5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlZ2lzdGVyVmF1bHQodmF1bHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgLy8gT3BlbiB0aGUgdmF1bHQgdmlhIElQQy4gVGhlIHNlbmRTeW5jIHJldHVybiB2YWx1ZSBpcyB1bnJlbGlhYmxlXG4gIC8vIChDTEkgb3V0cHV0IG1heSBiZSBsb3N0IHdoZW4gdGhlIG5ldyB3aW5kb3cgb3BlbnMpLCBzbyB3ZSBpZ25vcmUgaXRcbiAgLy8gQW5kIHBvbGwgZm9yIHJlYWRpbmVzcyBpbnN0ZWFkLlxuICBhd2FpdCBldmFsSW5PYnNpZGlhbih7XG4gICAgYXJnczogeyB2YXVsdFBhdGggfSxcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tc2hhZG93IC0tIE5vIGFjdHVhbCBzaGFkb3dpbmcgYXMgdGhlIGZ1bmN0aW9uIGlzIGV4ZWN1dGVkIGV4dGVybmFsbHkuXG4gICAgZm4oeyB2YXVsdFBhdGggfSk6IHZvaWQge1xuICAgICAgd2luZG93LmVsZWN0cm9uLmlwY1JlbmRlcmVyLnNlbmRTeW5jKCd2YXVsdC1vcGVuJywgdmF1bHRQYXRoLCBmYWxzZSk7XG4gICAgfSxcbiAgICBzaG91bGRTa2lwUHJlZmxpZ2h0Q2hlY2tzOiB0cnVlXG4gIH0pO1xuXG4gIC8vIFBvbGwgdW50aWwgdGhlIG5ldyB2YXVsdCdzIENMSSByZXNwb25kcyB3aXRoIHRoZSBjb3JyZWN0IGJhc2VQYXRoLlxuICBjb25zdCBkZWFkbGluZSA9IERhdGUubm93KCkgKyBQT0xMX1RJTUVPVVRfTVM7XG4gIHdoaWxlIChEYXRlLm5vdygpIDwgZGVhZGxpbmUpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgYmFzZVBhdGggPSBhd2FpdCBldmFsSW5PYnNpZGlhbih7XG4gICAgICAgIGZuKHsgYXBwIH0pOiBzdHJpbmcge1xuICAgICAgICAgIHJldHVybiAoYXBwLnZhdWx0LmFkYXB0ZXIgYXMgRmlsZVN5c3RlbUFkYXB0ZXIpLmdldEJhc2VQYXRoKCk7XG4gICAgICAgIH0sXG4gICAgICAgIHNob3VsZFNraXBQcmVmbGlnaHRDaGVja3M6IHRydWUsXG4gICAgICAgIHZhdWx0UGF0aFxuICAgICAgfSk7XG4gICAgICBpZiAoYmFzZVBhdGggPT09IHZhdWx0UGF0aCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBWYXVsdCBub3QgcmVhZHkgeWV0LlxuICAgIH1cbiAgICBhd2FpdCBuZXcgUHJvbWlzZSgocikgPT4ge1xuICAgICAgc2V0VGltZW91dChyLCBQT0xMX0lOVEVSVkFMX01TKTtcbiAgICB9KTtcbiAgfVxuICB0aHJvdyBuZXcgRXJyb3IoYFZhdWx0IGF0ICR7dmF1bHRQYXRofSBkaWQgbm90IGJlY29tZSByZWFkeSB3aXRoaW4gJHtTdHJpbmcoUE9MTF9USU1FT1VUX01TKX1tc2ApO1xufVxuXG4vKipcbiAqIFVucmVnaXN0ZXJzIGEgdmF1bHQgcGF0aCBmcm9tIHRoZSBydW5uaW5nIE9ic2lkaWFuIGluc3RhbmNlLlxuICpcbiAqIFNjaGVkdWxlcyB0aGUgdmF1bHQgd2luZG93IHRvIGNsb3NlLCB3YWl0cyBmb3IgaXQsIHRoZW4gcmVtb3Zlc1xuICogdGhlIHZhdWx0IGZyb20gdGhlIHJlZ2lzdHJ5IHZpYSBgdmF1bHQtcmVtb3ZlYCBJUEMuXG4gKlxuICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIHRoZSB2YXVsdCBmb2xkZXIuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB1bnJlZ2lzdGVyVmF1bHQodmF1bHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgLy8gU2NoZWR1bGUgdGhlIHZhdWx0IHdpbmRvdyB0byBjbG9zZSBmcm9tIHdpdGhpbiBpdHMgb3duIGNvbnRleHQuXG4gIC8vIFVzZSBzZXRUaW1lb3V0IHNvIHRoZSBDTEkgcmVzcG9uc2UgaXMgc2VudCBiZWZvcmUgdGhlIHdpbmRvdyBjbG9zZXMuXG4gIHRyeSB7XG4gICAgYXdhaXQgZXZhbEluT2JzaWRpYW4oe1xuICAgICAgZm4oKTogdm9pZCB7XG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICh3aW5kb3cuZWxlY3Ryb24gYXMgUGFydGlhbDxFbGVjdHJvbj4pLnJlbW90ZT8uZ2V0Q3VycmVudFdpbmRvdygpLmRlc3Ryb3koKTtcbiAgICAgICAgfSwgMCk7XG4gICAgICB9LFxuICAgICAgc2hvdWxkU2tpcFByZWZsaWdodENoZWNrczogdHJ1ZSxcbiAgICAgIHZhdWx0UGF0aFxuICAgIH0pO1xuICB9IGNhdGNoIHtcbiAgICAvLyBUaGUgd2luZG93IG1heSBoYXZlIGNsb3NlZCBiZWZvcmUgdGhlIHJlc3BvbnNlIHdhcyBzZW50IFx1MjAxNCB0aGF0J3MgT0suXG4gIH1cblxuICAvLyBXYWl0IGZvciB0aGUgd2luZG93IHRvIGFjdHVhbGx5IGNsb3NlLlxuICBhd2FpdCBuZXcgUHJvbWlzZSgocikgPT4ge1xuICAgIHNldFRpbWVvdXQociwgQ0xPU0VfREVMQVlfTVMpO1xuICB9KTtcblxuICAvLyBSZW1vdmUgZnJvbSB0aGUgcmVnaXN0cnkgKGZyb20gYW55IHJlbWFpbmluZyB2YXVsdCdzIGNvbnRleHQpLlxuICBhd2FpdCBldmFsSW5PYnNpZGlhbih7XG4gICAgYXJnczogeyB2YXVsdFBhdGggfSxcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tc2hhZG93IC0tIE5vIGFjdHVhbCBzaGFkb3dpbmcgYXMgdGhlIGZ1bmN0aW9uIGlzIGV4ZWN1dGVkIGV4dGVybmFsbHkuXG4gICAgZm4oeyB2YXVsdFBhdGggfSk6IHZvaWQge1xuICAgICAgd2luZG93LmVsZWN0cm9uLmlwY1JlbmRlcmVyLnNlbmRTeW5jKCd2YXVsdC1yZW1vdmUnLCB2YXVsdFBhdGgpO1xuICAgIH0sXG4gICAgc2hvdWxkU2tpcFByZWZsaWdodENoZWNrczogdHJ1ZVxuICB9KTtcbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQVVBLFNBQVMsc0JBQXNCO0FBRS9CLE1BQU0sbUJBQW1CO0FBQ3pCLE1BQU0sa0JBQWtCO0FBQ3hCLE1BQU0saUJBQWlCO0FBc0J2QixlQUFzQixjQUFjLFdBQWtDO0FBSXBFLFFBQU0sZUFBZTtBQUFBLElBQ25CLE1BQU0sRUFBRSxVQUFVO0FBQUE7QUFBQSxJQUVsQixHQUFHLEVBQUUsV0FBQUEsV0FBVSxHQUFTO0FBQ3RCLGFBQU8sU0FBUyxZQUFZLFNBQVMsY0FBY0EsWUFBVyxLQUFLO0FBQUEsSUFDckU7QUFBQSxJQUNBLDJCQUEyQjtBQUFBLEVBQzdCLENBQUM7QUFHRCxRQUFNLFdBQVcsS0FBSyxJQUFJLElBQUk7QUFDOUIsU0FBTyxLQUFLLElBQUksSUFBSSxVQUFVO0FBQzVCLFFBQUk7QUFDRixZQUFNLFdBQVcsTUFBTSxlQUFlO0FBQUEsUUFDcEMsR0FBRyxFQUFFLElBQUksR0FBVztBQUNsQixpQkFBUSxJQUFJLE1BQU0sUUFBOEIsWUFBWTtBQUFBLFFBQzlEO0FBQUEsUUFDQSwyQkFBMkI7QUFBQSxRQUMzQjtBQUFBLE1BQ0YsQ0FBQztBQUNELFVBQUksYUFBYSxXQUFXO0FBQzFCO0FBQUEsTUFDRjtBQUFBLElBQ0YsUUFBUTtBQUFBLElBRVI7QUFDQSxVQUFNLElBQUksUUFBUSxDQUFDLE1BQU07QUFDdkIsaUJBQVcsR0FBRyxnQkFBZ0I7QUFBQSxJQUNoQyxDQUFDO0FBQUEsRUFDSDtBQUNBLFFBQU0sSUFBSSxNQUFNLFlBQVksU0FBUyxnQ0FBZ0MsT0FBTyxlQUFlLENBQUMsSUFBSTtBQUNsRztBQVVBLGVBQXNCLGdCQUFnQixXQUFrQztBQUd0RSxNQUFJO0FBQ0YsVUFBTSxlQUFlO0FBQUEsTUFDbkIsS0FBVztBQUNULG1CQUFXLE1BQU07QUFDZixVQUFDLE9BQU8sU0FBK0IsUUFBUSxpQkFBaUIsRUFBRSxRQUFRO0FBQUEsUUFDNUUsR0FBRyxDQUFDO0FBQUEsTUFDTjtBQUFBLE1BQ0EsMkJBQTJCO0FBQUEsTUFDM0I7QUFBQSxJQUNGLENBQUM7QUFBQSxFQUNILFFBQVE7QUFBQSxFQUVSO0FBR0EsUUFBTSxJQUFJLFFBQVEsQ0FBQyxNQUFNO0FBQ3ZCLGVBQVcsR0FBRyxjQUFjO0FBQUEsRUFDOUIsQ0FBQztBQUdELFFBQU0sZUFBZTtBQUFBLElBQ25CLE1BQU0sRUFBRSxVQUFVO0FBQUE7QUFBQSxJQUVsQixHQUFHLEVBQUUsV0FBQUEsV0FBVSxHQUFTO0FBQ3RCLGFBQU8sU0FBUyxZQUFZLFNBQVMsZ0JBQWdCQSxVQUFTO0FBQUEsSUFDaEU7QUFBQSxJQUNBLDJCQUEyQjtBQUFBLEVBQzdCLENBQUM7QUFDSDsiLAogICJuYW1lcyI6IFsidmF1bHRQYXRoIl0KfQo=
15
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3ZhdWx0LXJlZ2lzdHJ5LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG4vKipcbiAqIEBmaWxlXG4gKlxuICogTWFuYWdlcyB2YXVsdCByZWdpc3RyYXRpb24gaW4gdGhlIHJ1bm5pbmcgT2JzaWRpYW4gaW5zdGFuY2UuXG4gKiBEZWxlZ2F0ZXMgdG8gdGhlIHRyYW5zcG9ydCByZXNvbHZlZCBmcm9tIHZpdGVzdC1wcm92aWRlZCBvcHRpb25zLlxuICovXG5cbmltcG9ydCB7IGluamVjdCB9IGZyb20gJ3ZpdGVzdCc7XG5cbmltcG9ydCB7IGdldE9yQ3JlYXRlVHJhbnNwb3J0IH0gZnJvbSAnLi90cmFuc3BvcnQtZmFjdG9yeS5tanMnO1xuXG4vKipcbiAqIFJlZ2lzdGVycyBhIHZhdWx0IHBhdGggaW4gdGhlIHJ1bm5pbmcgT2JzaWRpYW4gaW5zdGFuY2UuXG4gKlxuICogRGVsZWdhdGVzIHRvIHRoZSBhY3RpdmUgdHJhbnNwb3J0J3Mge0BsaW5rIE9ic2lkaWFuVHJhbnNwb3J0LnJlZ2lzdGVyVmF1bHR9LlxuICpcbiAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVnaXN0ZXJWYXVsdCh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCB0cmFuc3BvcnQgPSBhd2FpdCBnZXRPckNyZWF0ZVRyYW5zcG9ydChpbmplY3QoJ29ic2lkaWFuVHJhbnNwb3J0JykpO1xuICBhd2FpdCB0cmFuc3BvcnQucmVnaXN0ZXJWYXVsdCh2YXVsdFBhdGgpO1xufVxuXG4vKipcbiAqIFVucmVnaXN0ZXJzIGEgdmF1bHQgcGF0aCBmcm9tIHRoZSBydW5uaW5nIE9ic2lkaWFuIGluc3RhbmNlLlxuICpcbiAqIERlbGVnYXRlcyB0byB0aGUgYWN0aXZlIHRyYW5zcG9ydCdzIHtAbGluayBPYnNpZGlhblRyYW5zcG9ydC51bnJlZ2lzdGVyVmF1bHR9LlxuICpcbiAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gdW5yZWdpc3RlclZhdWx0KHZhdWx0UGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHRyYW5zcG9ydCA9IGF3YWl0IGdldE9yQ3JlYXRlVHJhbnNwb3J0KGluamVjdCgnb2JzaWRpYW5UcmFuc3BvcnQnKSk7XG4gIGF3YWl0IHRyYW5zcG9ydC51bnJlZ2lzdGVyVmF1bHQodmF1bHRQYXRoKTtcbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQVNBLFNBQVMsY0FBYztBQUV2QixTQUFTLDRCQUE0QjtBQVNyQyxlQUFzQixjQUFjLFdBQWtDO0FBQ3BFLFFBQU0sWUFBWSxNQUFNLHFCQUFxQixPQUFPLG1CQUFtQixDQUFDO0FBQ3hFLFFBQU0sVUFBVSxjQUFjLFNBQVM7QUFDekM7QUFTQSxlQUFzQixnQkFBZ0IsV0FBa0M7QUFDdEUsUUFBTSxZQUFZLE1BQU0scUJBQXFCLE9BQU8sbUJBQW1CLENBQUM7QUFDeEUsUUFBTSxVQUFVLGdCQUFnQixTQUFTO0FBQzNDOyIsCiAgIm5hbWVzIjogW10KfQo=