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
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
mkdir,
|
|
4
|
-
readFile,
|
|
5
|
-
unlink,
|
|
6
|
-
writeFile
|
|
7
|
-
} from "node:fs/promises";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import process from "node:process";
|
|
11
|
-
import { DISMISS_TRUST_DIALOG_EXPR } from "./dismiss-trust-dialog.mjs";
|
|
12
|
-
import { exec } from "./exec.mjs";
|
|
13
|
-
import { getFunctionExpressionString } from "./function-expression.mjs";
|
|
14
|
-
import { log } from "./log.mjs";
|
|
15
|
-
import { ensureNamespaceBootstrapped } from "./namespace-bootstrap.mjs";
|
|
16
|
-
import { noop } from "./noop.mjs";
|
|
17
|
-
import {
|
|
18
|
-
enableCliInConfig,
|
|
19
|
-
getAnyOpenVaultPath,
|
|
20
|
-
getRegisteredVaults,
|
|
21
|
-
getVaultId,
|
|
22
|
-
isCliEnabled,
|
|
23
|
-
isVaultOpen,
|
|
24
|
-
isVaultRegistered,
|
|
25
|
-
registerVaultInConfig,
|
|
26
|
-
removeVaultFromConfig
|
|
27
|
-
} from "./obsidian-config.mjs";
|
|
28
|
-
import { resolveObsidianExecutable } from "./obsidian-executable.mjs";
|
|
29
|
-
import { serializeError } from "./serialize-error.mjs";
|
|
30
|
-
const UNABLE_TO_FIND_OBSIDIAN = "unable to find Obsidian";
|
|
31
|
-
const AUTO_START_POLL_INTERVAL_IN_MILLISECONDS = 2e3;
|
|
32
|
-
const AUTO_START_TIMEOUT_IN_MILLISECONDS = 3e4;
|
|
33
|
-
const VAULT_POLL_INTERVAL_IN_MILLISECONDS = 500;
|
|
34
|
-
const VAULT_POLL_TIMEOUT_IN_MILLISECONDS = 3e4;
|
|
35
|
-
const VAULT_CLOSE_DELAY_IN_MILLISECONDS = 1e3;
|
|
36
|
-
const VAULT_EVAL_TIMEOUT_IN_MILLISECONDS = 1e4;
|
|
37
|
-
class DesktopCliTransport {
|
|
38
|
-
/**
|
|
39
|
-
* Indicates whether this transport is for a mobile platform. Always `false` for this transport.
|
|
40
|
-
*/
|
|
41
|
-
isMobile = false;
|
|
42
|
-
/**
|
|
43
|
-
* Evaluates a JavaScript expression inside Obsidian via `obsidian eval`.
|
|
44
|
-
*
|
|
45
|
-
* Writes the expression to a temporary `.cjs` file and executes a tiny
|
|
46
|
-
* `require().invoke()` call via the CLI. The result is written to a
|
|
47
|
-
* `.result.json` file by the script, avoiding CLI stdout size limits
|
|
48
|
-
* and IPC buffer issues.
|
|
49
|
-
*
|
|
50
|
-
* Handles auto-start: if Obsidian is not running, launches it via URI protocol
|
|
51
|
-
* and retries until it becomes available.
|
|
52
|
-
*
|
|
53
|
-
* @param expression - The JavaScript expression to evaluate.
|
|
54
|
-
* @param options - Evaluation options.
|
|
55
|
-
* @returns The normalized result string (transport-specific prefixes stripped).
|
|
56
|
-
*/
|
|
57
|
-
async evaluate(expression, options) {
|
|
58
|
-
const SLICE_START = 2;
|
|
59
|
-
const scriptId = String(Math.random()).slice(SLICE_START);
|
|
60
|
-
const scriptDir = join(tmpdir(), "obsidian-integration-testing");
|
|
61
|
-
const scriptPath = join(scriptDir, `${scriptId}.js`);
|
|
62
|
-
const resultPath = join(scriptDir, `${scriptId}.result.json`);
|
|
63
|
-
await mkdir(scriptDir, { recursive: true });
|
|
64
|
-
const scriptContent = buildScriptFile(expression, resultPath);
|
|
65
|
-
await writeFile(scriptPath, scriptContent);
|
|
66
|
-
try {
|
|
67
|
-
const safeScriptPath = scriptPath.replace(/\\/g, "/");
|
|
68
|
-
const cliExpr = `(${getFunctionExpressionString(executeScriptFile)})(${JSON.stringify(safeScriptPath)})`;
|
|
69
|
-
const vaultId = getVaultId(options.cwd);
|
|
70
|
-
const command = ["obsidian", ...vaultId ? [`vault=${vaultId}`] : [], "eval", "--allow-focus-steal", `code=${cliExpr}`];
|
|
71
|
-
let execResult;
|
|
72
|
-
try {
|
|
73
|
-
execResult = await exec(command, {
|
|
74
|
-
cwd: options.cwd,
|
|
75
|
-
isQuiet: true,
|
|
76
|
-
shouldIncludeDetails: true,
|
|
77
|
-
...options.timeoutInMilliseconds !== void 0 && { timeoutInMilliseconds: options.timeoutInMilliseconds }
|
|
78
|
-
});
|
|
79
|
-
} catch (error) {
|
|
80
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
-
if (message.includes(UNABLE_TO_FIND_OBSIDIAN)) {
|
|
82
|
-
await this.ensureObsidianRunningAndRetry(command, options.cwd);
|
|
83
|
-
} else {
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!existsSync(resultPath)) {
|
|
88
|
-
const diagnostics = await buildDiagnostics({
|
|
89
|
-
command,
|
|
90
|
-
cwd: options.cwd,
|
|
91
|
-
execResult,
|
|
92
|
-
resultPath,
|
|
93
|
-
scriptContent,
|
|
94
|
-
scriptPath
|
|
95
|
-
});
|
|
96
|
-
throw new Error(`Script did not execute for path: ${options.cwd}
|
|
97
|
-
${diagnostics}`);
|
|
98
|
-
}
|
|
99
|
-
const resultStr = await readFile(resultPath, "utf-8");
|
|
100
|
-
const envelope = JSON.parse(resultStr);
|
|
101
|
-
if ("type" in envelope) {
|
|
102
|
-
if (envelope.type === "error") {
|
|
103
|
-
throw new Error(`Script error in Obsidian for path: ${options.cwd}: ${envelope.value}`);
|
|
104
|
-
}
|
|
105
|
-
return "";
|
|
106
|
-
}
|
|
107
|
-
return envelope.value;
|
|
108
|
-
} finally {
|
|
109
|
-
await unlink(scriptPath).catch(noop);
|
|
110
|
-
await unlink(resultPath).catch(noop);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Runs CLI-specific preflight checks.
|
|
115
|
-
*
|
|
116
|
-
* Verifies that the vault is registered, CLI is enabled in Obsidian settings,
|
|
117
|
-
* and the `obsidian` CLI binary is available in PATH. If the vault is registered
|
|
118
|
-
* but not open, opens it via URI protocol and polls until it is ready.
|
|
119
|
-
*
|
|
120
|
-
* @param vaultPath - The absolute path to the vault folder.
|
|
121
|
-
*/
|
|
122
|
-
async preflightCheck(vaultPath) {
|
|
123
|
-
log(`[cli-transport] Running preflight check for vault: ${vaultPath}`);
|
|
124
|
-
if (!isVaultRegistered(vaultPath)) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`Vault is not registered in Obsidian: ${vaultPath}. Register the vault first with registerVault() or TempVault.register().`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
if (!isCliEnabled()) {
|
|
130
|
-
log("[cli-transport] CLI is disabled in obsidian.json. Enabling and restarting Obsidian...");
|
|
131
|
-
enableCliInConfig();
|
|
132
|
-
await this.restartObsidian(vaultPath);
|
|
133
|
-
}
|
|
134
|
-
await this.assertCliAvailable();
|
|
135
|
-
if (!isVaultOpen(vaultPath)) {
|
|
136
|
-
log(`[cli-transport] Vault is registered but not open: ${vaultPath}. Opening via URI...`);
|
|
137
|
-
await openVaultViaUri(vaultPath);
|
|
138
|
-
await this.pollVaultReady(vaultPath);
|
|
139
|
-
}
|
|
140
|
-
log("[cli-transport] Preflight check passed.");
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Registers a vault in the running Obsidian instance via Electron IPC.
|
|
144
|
-
*
|
|
145
|
-
* Uses `vault-open` IPC to register and open the vault, then polls until
|
|
146
|
-
* the vault's CLI is ready and `getBasePath()` matches `vaultPath`.
|
|
147
|
-
*
|
|
148
|
-
* @param vaultPath - The absolute path to the vault folder.
|
|
149
|
-
*/
|
|
150
|
-
async registerVault(vaultPath) {
|
|
151
|
-
log(`[cli-transport] Registering vault: ${vaultPath}`);
|
|
152
|
-
const openVaultPath = await this.getOpenEvalTargetVaultPath();
|
|
153
|
-
if (openVaultPath) {
|
|
154
|
-
await ensureNamespaceBootstrapped(this, openVaultPath);
|
|
155
|
-
const registerExpr = `window.__obsidianIntegrationTesting.ipcSendSync(${JSON.stringify({ args: [vaultPath, false], channel: "vault-open" })})`;
|
|
156
|
-
await this.evaluate(registerExpr, { cwd: openVaultPath });
|
|
157
|
-
await this.enablePluginsInLocalStorage(vaultPath, openVaultPath);
|
|
158
|
-
} else {
|
|
159
|
-
log("[cli-transport] No open vault to target. Writing vault entry directly to obsidian.json...");
|
|
160
|
-
registerVaultInConfig(vaultPath);
|
|
161
|
-
if (await isObsidianRunning()) {
|
|
162
|
-
log("[cli-transport] Obsidian is running without an open vault \u2014 restarting so it reads the new registry entry...");
|
|
163
|
-
await this.killObsidian();
|
|
164
|
-
}
|
|
165
|
-
log("[cli-transport] Vault entry written. Opening vault via URI protocol...");
|
|
166
|
-
await openVaultViaUri(vaultPath);
|
|
167
|
-
await this.pollVaultReady(vaultPath);
|
|
168
|
-
await this.enablePluginsInLocalStorage(vaultPath, vaultPath);
|
|
169
|
-
}
|
|
170
|
-
await this.pollVaultReady(vaultPath);
|
|
171
|
-
await this.dismissTrustDialog(vaultPath);
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Unregisters a vault from the running Obsidian instance.
|
|
175
|
-
*
|
|
176
|
-
* Schedules the vault window to close, waits, then removes the vault
|
|
177
|
-
* from the registry via `vault-remove` IPC.
|
|
178
|
-
*
|
|
179
|
-
* @param vaultPath - The absolute path to the vault folder.
|
|
180
|
-
*/
|
|
181
|
-
async unregisterVault(vaultPath) {
|
|
182
|
-
log(`[cli-transport] Unregistering vault: closing window for ${vaultPath}...`);
|
|
183
|
-
if (isVaultOpen(vaultPath)) {
|
|
184
|
-
try {
|
|
185
|
-
await ensureNamespaceBootstrapped(this, vaultPath);
|
|
186
|
-
const destroyExpr = "window.__obsidianIntegrationTesting.destroyCurrentWindow()";
|
|
187
|
-
await this.evaluate(destroyExpr, { cwd: vaultPath, timeoutInMilliseconds: VAULT_EVAL_TIMEOUT_IN_MILLISECONDS });
|
|
188
|
-
log("[cli-transport] Window destroy command sent.");
|
|
189
|
-
} catch (error) {
|
|
190
|
-
log(`[cli-transport] Window destroy failed (non-fatal): ${serializeError(error)}`);
|
|
191
|
-
}
|
|
192
|
-
log("[cli-transport] Waiting for window to close...");
|
|
193
|
-
await delay(VAULT_CLOSE_DELAY_IN_MILLISECONDS);
|
|
194
|
-
} else {
|
|
195
|
-
log("[cli-transport] Vault is not open \u2014 skipping destroy (would otherwise spawn a stray window via the CLI eval).");
|
|
196
|
-
}
|
|
197
|
-
log("[cli-transport] Removing vault from registry...");
|
|
198
|
-
const evalTargetForRemoval = await this.getOpenEvalTargetVaultPath();
|
|
199
|
-
try {
|
|
200
|
-
if (evalTargetForRemoval) {
|
|
201
|
-
await ensureNamespaceBootstrapped(this, evalTargetForRemoval);
|
|
202
|
-
const removeExpr = `window.__obsidianIntegrationTesting.ipcSendSync(${JSON.stringify({ args: [vaultPath], channel: "vault-remove" })})`;
|
|
203
|
-
await this.evaluate(removeExpr, { cwd: evalTargetForRemoval, timeoutInMilliseconds: VAULT_EVAL_TIMEOUT_IN_MILLISECONDS });
|
|
204
|
-
} else {
|
|
205
|
-
log("[cli-transport] No open vault to target for removal IPC \u2014 removing directly from obsidian.json.");
|
|
206
|
-
removeVaultFromConfig(vaultPath);
|
|
207
|
-
}
|
|
208
|
-
log("[cli-transport] Vault removed from registry.");
|
|
209
|
-
} catch (error) {
|
|
210
|
-
log(`[cli-transport] Vault registry removal failed (non-fatal): ${serializeError(error)}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Checks whether the Obsidian CLI binary is available in the system PATH.
|
|
215
|
-
*/
|
|
216
|
-
async assertCliAvailable() {
|
|
217
|
-
const command = process.platform === "win32" ? "where.exe obsidian" : "which obsidian";
|
|
218
|
-
try {
|
|
219
|
-
await exec(command, { isQuiet: true });
|
|
220
|
-
} catch {
|
|
221
|
-
throw new Error("Obsidian CLI is not available. Ensure Obsidian is installed and its CLI is in your PATH.");
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Dismisses the "Do you trust the author of this vault?" dialog if present.
|
|
226
|
-
*
|
|
227
|
-
* Clicks the "Trust author and enable plugins" button (`.mod-cta`) inside any
|
|
228
|
-
* modal whose text includes "Do you trust the author". Acts as a safety net
|
|
229
|
-
* for both registration paths:
|
|
230
|
-
*
|
|
231
|
-
* - First vault registered via config file (no prior `enable-plugin-<id>` entry).
|
|
232
|
-
* - Subsequent vault registered while another vault is already open. The
|
|
233
|
-
* `enable-plugin-<newVaultId>` write happens in the existing vault's renderer,
|
|
234
|
-
* but the new vault's renderer may read `localStorage` before that cross-renderer
|
|
235
|
-
* write becomes visible (race observed in Obsidian 1.13.0).
|
|
236
|
-
*
|
|
237
|
-
* @param vaultPath - The vault path to evaluate in.
|
|
238
|
-
*/
|
|
239
|
-
async dismissTrustDialog(vaultPath) {
|
|
240
|
-
await ensureNamespaceBootstrapped(this, vaultPath);
|
|
241
|
-
const result = await this.evaluate(DISMISS_TRUST_DIALOG_EXPR, { cwd: vaultPath });
|
|
242
|
-
if (result === "true") {
|
|
243
|
-
log('[cli-transport] Dismissed "Do you trust the author" dialog.');
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Sets `enable-plugin-<vaultId>` in Obsidian's localStorage to prevent
|
|
248
|
-
* the "Do you trust the author of this vault?" dialog from appearing
|
|
249
|
-
* when a vault with community plugins is opened for the first time.
|
|
250
|
-
*
|
|
251
|
-
* Must be called after the `vault-open` IPC (so the vault ID exists in
|
|
252
|
-
* `obsidian.json`) and before the new vault window finishes loading.
|
|
253
|
-
*
|
|
254
|
-
* @param vaultPath - The absolute path to the vault folder.
|
|
255
|
-
* @param evalTargetVaultPath - The vault path to use as eval target (an existing, loaded vault).
|
|
256
|
-
* localStorage is shared across all Obsidian windows (same Electron origin), so the
|
|
257
|
-
* `localStorage.setItem` call can run in any loaded vault window.
|
|
258
|
-
*/
|
|
259
|
-
async enablePluginsInLocalStorage(vaultPath, evalTargetVaultPath) {
|
|
260
|
-
const vaultId = getVaultId(vaultPath);
|
|
261
|
-
if (!vaultId) {
|
|
262
|
-
log("[cli-transport] Could not find vault ID \u2014 skipping localStorage trust flag.");
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
await ensureNamespaceBootstrapped(this, evalTargetVaultPath);
|
|
266
|
-
const enableExpr = `window.__obsidianIntegrationTesting.setLocalStorageItem(${JSON.stringify({ key: `enable-plugin-${vaultId}`, value: "true" })})`;
|
|
267
|
-
await this.evaluate(enableExpr, { cwd: evalTargetVaultPath });
|
|
268
|
-
log(`[cli-transport] Set enable-plugin-${vaultId} in localStorage.`);
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Launches Obsidian binary and retries the eval until it responds.
|
|
272
|
-
*
|
|
273
|
-
* @param command - The CLI command array to retry.
|
|
274
|
-
* @param cwd - The working directory.
|
|
275
|
-
*/
|
|
276
|
-
async ensureObsidianRunningAndRetry(command, cwd) {
|
|
277
|
-
log("[cli-transport] Obsidian is not running. Starting Obsidian...");
|
|
278
|
-
const launchCommand = await getObsidianLaunchCommand();
|
|
279
|
-
try {
|
|
280
|
-
await exec(launchCommand, { isQuiet: true });
|
|
281
|
-
} catch {
|
|
282
|
-
}
|
|
283
|
-
log(`[cli-transport] Polling for Obsidian CLI (timeout=${String(AUTO_START_TIMEOUT_IN_MILLISECONDS)}ms)...`);
|
|
284
|
-
const deadline = Date.now() + AUTO_START_TIMEOUT_IN_MILLISECONDS;
|
|
285
|
-
while (Date.now() < deadline) {
|
|
286
|
-
await delay(AUTO_START_POLL_INTERVAL_IN_MILLISECONDS);
|
|
287
|
-
try {
|
|
288
|
-
await exec(command, { cwd, isQuiet: true });
|
|
289
|
-
log("[cli-transport] Obsidian CLI responded.");
|
|
290
|
-
return;
|
|
291
|
-
} catch (error) {
|
|
292
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
293
|
-
if (!message.includes(UNABLE_TO_FIND_OBSIDIAN)) {
|
|
294
|
-
throw error;
|
|
295
|
-
}
|
|
296
|
-
log("[cli-transport] Obsidian not ready yet, retrying...");
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
throw new Error(`Obsidian did not start within ${String(AUTO_START_TIMEOUT_IN_MILLISECONDS)}ms.`);
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Returns the path of an already-open vault to use as an `obsidian eval`
|
|
303
|
-
* target, or `undefined` if there is none.
|
|
304
|
-
*
|
|
305
|
-
* The `open` flag in `obsidian.json` is only trustworthy while Obsidian is
|
|
306
|
-
* running — a killed or crashed process never resets it, leaving the user's
|
|
307
|
-
* last-session vault marked open. Acting on that stale flag would target the
|
|
308
|
-
* (possibly heavy) last-session vault and cause auto-start to reopen it.
|
|
309
|
-
* When Obsidian is not running we therefore report no open vault, so callers
|
|
310
|
-
* fall back to the direct-config path that opens only the temp vault.
|
|
311
|
-
*
|
|
312
|
-
* @returns The absolute path to an open vault, or `undefined`.
|
|
313
|
-
*/
|
|
314
|
-
async getOpenEvalTargetVaultPath() {
|
|
315
|
-
if (!await isObsidianRunning()) {
|
|
316
|
-
return void 0;
|
|
317
|
-
}
|
|
318
|
-
return getAnyOpenVaultPath();
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Kills the running Obsidian process (if any) and waits for it to exit.
|
|
322
|
-
*/
|
|
323
|
-
async killObsidian() {
|
|
324
|
-
log("[cli-transport] Killing Obsidian process...");
|
|
325
|
-
try {
|
|
326
|
-
await exec(getKillObsidianCommand(), { isQuiet: true });
|
|
327
|
-
} catch {
|
|
328
|
-
}
|
|
329
|
-
await delay(VAULT_CLOSE_DELAY_IN_MILLISECONDS);
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Polls until the vault is ready and responding to eval calls.
|
|
333
|
-
*
|
|
334
|
-
* Each `obsidian eval` in the loop is given a per-call timeout. The overall
|
|
335
|
-
* deadline is only re-checked between awaits, so without it a single hung
|
|
336
|
-
* eval (e.g. a vault window that opened but never finished loading) would
|
|
337
|
-
* block past the deadline forever instead of failing and retrying.
|
|
338
|
-
*
|
|
339
|
-
* @param vaultPath - The absolute path to the vault folder.
|
|
340
|
-
*/
|
|
341
|
-
async pollVaultReady(vaultPath) {
|
|
342
|
-
const pollExpr = "window.__obsidianIntegrationTesting.pollVaultBasePath()";
|
|
343
|
-
log(`[cli-transport] Polling for vault readiness (timeout=${String(VAULT_POLL_TIMEOUT_IN_MILLISECONDS)}ms)...`);
|
|
344
|
-
const deadline = Date.now() + VAULT_POLL_TIMEOUT_IN_MILLISECONDS;
|
|
345
|
-
let isBootstrapped = false;
|
|
346
|
-
while (Date.now() < deadline) {
|
|
347
|
-
try {
|
|
348
|
-
if (!isBootstrapped) {
|
|
349
|
-
await ensureNamespaceBootstrapped(this, vaultPath, VAULT_EVAL_TIMEOUT_IN_MILLISECONDS);
|
|
350
|
-
isBootstrapped = true;
|
|
351
|
-
}
|
|
352
|
-
const basePath = await this.evaluate(pollExpr, { cwd: vaultPath, timeoutInMilliseconds: VAULT_EVAL_TIMEOUT_IN_MILLISECONDS });
|
|
353
|
-
if (JSON.parse(basePath) === vaultPath) {
|
|
354
|
-
log("[cli-transport] Vault is ready.");
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
} catch {
|
|
358
|
-
isBootstrapped = false;
|
|
359
|
-
}
|
|
360
|
-
await delay(VAULT_POLL_INTERVAL_IN_MILLISECONDS);
|
|
361
|
-
}
|
|
362
|
-
throw new Error(`Vault at ${vaultPath} did not become ready within ${String(VAULT_POLL_TIMEOUT_IN_MILLISECONDS)}ms`);
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Restarts Obsidian by killing the running process and relaunching it.
|
|
366
|
-
*
|
|
367
|
-
* Used when a config change (like enabling CLI) requires a restart to take effect.
|
|
368
|
-
*
|
|
369
|
-
* @param vaultPath - The vault path to use for polling after restart.
|
|
370
|
-
*/
|
|
371
|
-
async restartObsidian(vaultPath) {
|
|
372
|
-
await this.killObsidian();
|
|
373
|
-
const restartVaultId = getVaultId(vaultPath);
|
|
374
|
-
const command = ["obsidian", ...restartVaultId ? [`vault=${restartVaultId}`] : [], "eval", 'code="1"'];
|
|
375
|
-
await this.ensureObsidianRunningAndRetry(command, vaultPath);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
async function invokeAndWriteResult(params) {
|
|
379
|
-
const fsPromises = Reflect.get(window.app.vault.adapter, "fsPromises");
|
|
380
|
-
try {
|
|
381
|
-
const result = await params.evaluate();
|
|
382
|
-
if (result === void 0) {
|
|
383
|
-
await fsPromises.writeFile(params.resultPath, JSON.stringify({ type: "undefined", value: "" }));
|
|
384
|
-
} else if (result === null) {
|
|
385
|
-
await fsPromises.writeFile(params.resultPath, JSON.stringify({ type: "null", value: "" }));
|
|
386
|
-
} else {
|
|
387
|
-
await fsPromises.writeFile(params.resultPath, JSON.stringify({ value: String(result) }));
|
|
388
|
-
}
|
|
389
|
-
} catch (err) {
|
|
390
|
-
await fsPromises.writeFile(params.resultPath, JSON.stringify({ type: "error", value: params.serializeError(err) }));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
async function buildDiagnostics(params) {
|
|
394
|
-
const lines = [];
|
|
395
|
-
lines.push(` Command: ${params.command.join(" ")}`);
|
|
396
|
-
lines.push(` Working directory: ${params.cwd}`);
|
|
397
|
-
lines.push(` Script path: ${params.scriptPath}`);
|
|
398
|
-
lines.push(` Expected result file: ${params.resultPath}`);
|
|
399
|
-
if (params.execResult) {
|
|
400
|
-
lines.push(` Exit code: ${params.execResult.exitCode === null ? "(null \u2014 process did not exit normally)" : String(params.execResult.exitCode)}`);
|
|
401
|
-
if (params.execResult.exitSignal) {
|
|
402
|
-
lines.push(` Exit signal: ${params.execResult.exitSignal}`);
|
|
403
|
-
}
|
|
404
|
-
if (params.execResult.stdout.trim()) {
|
|
405
|
-
lines.push(` stdout: ${params.execResult.stdout.trim()}`);
|
|
406
|
-
}
|
|
407
|
-
if (params.execResult.stderr.trim()) {
|
|
408
|
-
lines.push(` stderr: ${params.execResult.stderr.trim()}`);
|
|
409
|
-
}
|
|
410
|
-
if (!params.execResult.stdout.trim() && !params.execResult.stderr.trim()) {
|
|
411
|
-
lines.push(" stdout/stderr: (empty)");
|
|
412
|
-
}
|
|
413
|
-
} else {
|
|
414
|
-
lines.push(" exec result: (not available \u2014 Obsidian was auto-started and retried)");
|
|
415
|
-
}
|
|
416
|
-
lines.push("Environment checks:");
|
|
417
|
-
lines.push(` Obsidian running: ${await isObsidianRunning() ? "yes" : "NO"}`);
|
|
418
|
-
lines.push(` CLI enabled in obsidian.json: ${isCliEnabled() ? "yes" : "NO"}`);
|
|
419
|
-
lines.push(` Vault registered: ${isVaultRegistered(params.cwd) ? "yes" : "NO"}`);
|
|
420
|
-
lines.push(` Script file exists: ${existsSync(params.scriptPath) ? "yes" : "no (already cleaned up)"}`);
|
|
421
|
-
lines.push(` Result file exists: ${existsSync(params.resultPath) ? "yes" : "no"}`);
|
|
422
|
-
const registeredVaults = getRegisteredVaults();
|
|
423
|
-
if (registeredVaults.length === 0) {
|
|
424
|
-
lines.push(" Registered vaults: (none)");
|
|
425
|
-
} else {
|
|
426
|
-
lines.push(" Registered vaults:");
|
|
427
|
-
for (const vault of registeredVaults) {
|
|
428
|
-
lines.push(` - ${vault.path} (open: ${String(vault.open)})`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
lines.push(` Script content:
|
|
432
|
-
${params.scriptContent}`);
|
|
433
|
-
return lines.join("\n");
|
|
434
|
-
}
|
|
435
|
-
function buildScriptFile(expression, resultPath) {
|
|
436
|
-
const fnStr = getFunctionExpressionString(invokeAndWriteResult);
|
|
437
|
-
const serializeErrorStr = getFunctionExpressionString(serializeError);
|
|
438
|
-
const resultPathJson = JSON.stringify(resultPath.replace(/\\/g, "/"));
|
|
439
|
-
return `(${fnStr})({ evaluate: async () => (${expression}), resultPath: ${resultPathJson}, serializeError: ${serializeErrorStr} })`;
|
|
440
|
-
}
|
|
441
|
-
function delay(ms) {
|
|
442
|
-
return new Promise((resolve) => {
|
|
443
|
-
setTimeout(resolve, ms);
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
async function executeScriptFile(scriptPath) {
|
|
447
|
-
const fsPromises = Reflect.get(window.app.vault.adapter, "fsPromises");
|
|
448
|
-
try {
|
|
449
|
-
await fsPromises.access(scriptPath);
|
|
450
|
-
} catch (cause) {
|
|
451
|
-
throw new Error(`Script file not found: ${scriptPath}`, { cause });
|
|
452
|
-
}
|
|
453
|
-
const script = await fsPromises.readFile(scriptPath, "utf-8");
|
|
454
|
-
console.debug(`Executing ${scriptPath}:
|
|
455
|
-
${script}`);
|
|
456
|
-
let fn;
|
|
457
|
-
try {
|
|
458
|
-
fn = new Function(`return ${script}`);
|
|
459
|
-
} catch (cause) {
|
|
460
|
-
throw new Error(`Error parsing ${scriptPath}`, { cause });
|
|
461
|
-
}
|
|
462
|
-
try {
|
|
463
|
-
await fn();
|
|
464
|
-
} catch (cause) {
|
|
465
|
-
throw new Error(`Error executing ${scriptPath}`, { cause });
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
function getKillObsidianCommand() {
|
|
469
|
-
if (process.platform === "win32") {
|
|
470
|
-
return "taskkill /IM Obsidian.exe /F";
|
|
471
|
-
}
|
|
472
|
-
return "pkill -f Obsidian";
|
|
473
|
-
}
|
|
474
|
-
async function getObsidianLaunchCommand() {
|
|
475
|
-
const exePath = await resolveObsidianExecutable();
|
|
476
|
-
if (process.platform === "win32") {
|
|
477
|
-
return `start "" "${exePath}"`;
|
|
478
|
-
}
|
|
479
|
-
return `"${exePath}" &`;
|
|
480
|
-
}
|
|
481
|
-
function getOpenUriCommand(uri) {
|
|
482
|
-
if (process.platform === "win32") {
|
|
483
|
-
return `start "" "${uri}"`;
|
|
484
|
-
}
|
|
485
|
-
if (process.platform === "darwin") {
|
|
486
|
-
return `open "${uri}"`;
|
|
487
|
-
}
|
|
488
|
-
return `xdg-open "${uri}"`;
|
|
489
|
-
}
|
|
490
|
-
async function isObsidianRunning() {
|
|
491
|
-
const command = process.platform === "win32" ? 'tasklist /FI "IMAGENAME eq Obsidian.exe" /NH' : "pgrep -f Obsidian";
|
|
492
|
-
try {
|
|
493
|
-
const output = await exec(command, { isQuiet: true });
|
|
494
|
-
if (process.platform === "win32") {
|
|
495
|
-
return output.includes("Obsidian.exe");
|
|
496
|
-
}
|
|
497
|
-
return output.trim().length > 0;
|
|
498
|
-
} catch {
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
async function openVaultViaUri(vaultPath) {
|
|
503
|
-
const vaultId = getVaultId(vaultPath);
|
|
504
|
-
if (!vaultId) {
|
|
505
|
-
throw new Error(`Cannot open vault via URI: vault is not registered in obsidian.json: ${vaultPath}`);
|
|
506
|
-
}
|
|
507
|
-
const uri = `obsidian://open?vault=${encodeURIComponent(vaultId)}`;
|
|
508
|
-
const command = getOpenUriCommand(uri);
|
|
509
|
-
log(`[cli-transport] Opening vault via URI: ${uri}`);
|
|
510
|
-
try {
|
|
511
|
-
await exec(command, { isQuiet: true });
|
|
512
|
-
} catch (error) {
|
|
513
|
-
log(`[cli-transport] URI open failed (non-fatal): ${serializeError(error)}`);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
export {
|
|
517
|
-
DesktopCliTransport,
|
|
518
|
-
invokeAndWriteResult
|
|
519
|
-
};
|
|
520
|
-
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3RyYW5zcG9ydC1kZXNrdG9wLWNsaS50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIERlc2t0b3AgQ0xJIHRyYW5zcG9ydCBcdTIwMTQgZXZhbHVhdGVzIGV4cHJlc3Npb25zIHZpYSBgb2JzaWRpYW4gZXZhbGAgYW5kIG1hbmFnZXNcbiAqIHZhdWx0cyB2aWEgRWxlY3Ryb24gSVBDLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGNvZGUgY292ZXJlZCBieSBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IHVuaXQgdGVzdHMuICovXG5cbmltcG9ydCB7IGV4aXN0c1N5bmMgfSBmcm9tICdub2RlOmZzJztcbmltcG9ydCB7XG4gIG1rZGlyLFxuICByZWFkRmlsZSxcbiAgdW5saW5rLFxuICB3cml0ZUZpbGVcbn0gZnJvbSAnbm9kZTpmcy9wcm9taXNlcyc7XG5pbXBvcnQgeyB0bXBkaXIgfSBmcm9tICdub2RlOm9zJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHByb2Nlc3MgZnJvbSAnbm9kZTpwcm9jZXNzJztcblxuaW1wb3J0IHR5cGUgeyBFeGVjUmVzdWx0IH0gZnJvbSAnLi9leGVjLm1qcyc7XG5pbXBvcnQgdHlwZSB7XG4gIE9ic2lkaWFuVHJhbnNwb3J0LFxuICBUcmFuc3BvcnRFdmFsT3B0aW9uc1xufSBmcm9tICcuL3RyYW5zcG9ydC5tanMnO1xuXG5pbXBvcnQgeyBESVNNSVNTX1RSVVNUX0RJQUxPR19FWFBSIH0gZnJvbSAnLi9kaXNtaXNzLXRydXN0LWRpYWxvZy5tanMnO1xuaW1wb3J0IHsgZXhlYyB9IGZyb20gJy4vZXhlYy5tanMnO1xuaW1wb3J0IHsgZ2V0RnVuY3Rpb25FeHByZXNzaW9uU3RyaW5nIH0gZnJvbSAnLi9mdW5jdGlvbi1leHByZXNzaW9uLm1qcyc7XG5pbXBvcnQgeyBsb2cgfSBmcm9tICcuL2xvZy5tanMnO1xuaW1wb3J0IHsgZW5zdXJlTmFtZXNwYWNlQm9vdHN0cmFwcGVkIH0gZnJvbSAnLi9uYW1lc3BhY2UtYm9vdHN0cmFwLm1qcyc7XG5pbXBvcnQgeyBub29wIH0gZnJvbSAnLi9ub29wLm1qcyc7XG5pbXBvcnQge1xuICBlbmFibGVDbGlJbkNvbmZpZyxcbiAgZ2V0QW55T3BlblZhdWx0UGF0aCxcbiAgZ2V0UmVnaXN0ZXJlZFZhdWx0cyxcbiAgZ2V0VmF1bHRJZCxcbiAgaXNDbGlFbmFibGVkLFxuICBpc1ZhdWx0T3BlbixcbiAgaXNWYXVsdFJlZ2lzdGVyZWQsXG4gIHJlZ2lzdGVyVmF1bHRJbkNvbmZpZyxcbiAgcmVtb3ZlVmF1bHRGcm9tQ29uZmlnXG59IGZyb20gJy4vb2JzaWRpYW4tY29uZmlnLm1qcyc7XG5pbXBvcnQgeyByZXNvbHZlT2JzaWRpYW5FeGVjdXRhYmxlIH0gZnJvbSAnLi9vYnNpZGlhbi1leGVjdXRhYmxlLm1qcyc7XG5pbXBvcnQgeyBzZXJpYWxpemVFcnJvciB9IGZyb20gJy4vc2VyaWFsaXplLWVycm9yLm1qcyc7XG5cbmludGVyZmFjZSBFeGVjdXRlU2NyaXB0RmlsZUZzUHJvbWlzZXMge1xuICBhY2Nlc3MocGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPjtcbiAgcmVhZEZpbGUocGF0aDogc3RyaW5nLCBlbmNvZGluZzogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+O1xufVxuXG5pbnRlcmZhY2UgSW52b2tlQW5kV3JpdGVSZXN1bHRGc1Byb21pc2VzIHtcbiAgd3JpdGVGaWxlKHBhdGg6IHN0cmluZywgZGF0YTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuaW50ZXJmYWNlIEludm9rZUFuZFdyaXRlUmVzdWx0UGFyYW1zIHtcbiAgZXZhbHVhdGUoKTogUHJvbWlzZTx1bmtub3duPjtcbiAgcmVhZG9ubHkgcmVzdWx0UGF0aDogc3RyaW5nO1xuICBzZXJpYWxpemVFcnJvcihlcnJvcjogdW5rbm93bik6IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIFNjcmlwdEVycm9yRW52ZWxvcGUge1xuICB0eXBlOiAnZXJyb3InIHwgJ251bGwnIHwgJ3VuZGVmaW5lZCc7XG4gIHZhbHVlOiBzdHJpbmc7XG59XG5cbi8qKlxuICogRGlzY3JpbWluYXRlZCBlbnZlbG9wZSB3cml0dGVuIGJ5IHRoZSB0ZW1wb3Jhcnkgc2NyaXB0IGZpbGUuXG4gKlxuICogLSBgU2NyaXB0RXJyb3JFbnZlbG9wZWAgd2l0aCBgdHlwZTogJ2Vycm9yJ2AgXHUyMDE0IHRoZSBleHByZXNzaW9uIHRocmV3OyBgdmFsdWVgIGlzIHRoZSBlcnJvciBtZXNzYWdlLlxuICogLSBgU2NyaXB0RXJyb3JFbnZlbG9wZWAgd2l0aCBgdHlwZTogJ251bGwnYCBcdTIwMTQgdGhlIGV4cHJlc3Npb24gcmV0dXJuZWQgYG51bGxgLlxuICogLSBgU2NyaXB0RXJyb3JFbnZlbG9wZWAgd2l0aCBgdHlwZTogJ3VuZGVmaW5lZCdgIFx1MjAxNCB0aGUgZXhwcmVzc2lvbiByZXR1cm5lZCBgdW5kZWZpbmVkYC5cbiAqIC0gYFNjcmlwdFZhbHVlRW52ZWxvcGVgIFx1MjAxNCB0aGUgZXhwcmVzc2lvbiByZXR1cm5lZCBhIHN0cmluZyB2YWx1ZSAobm8gYHR5cGVgIGZpZWxkKS5cbiAqL1xudHlwZSBTY3JpcHRSZXN1bHRFbnZlbG9wZSA9XG4gIHwgU2NyaXB0RXJyb3JFbnZlbG9wZVxuICB8IFNjcmlwdFZhbHVlRW52ZWxvcGU7XG5cbmludGVyZmFjZSBTY3JpcHRWYWx1ZUVudmVsb3BlIHtcbiAgdmFsdWU6IHN0cmluZztcbn1cblxuY29uc3QgVU5BQkxFX1RPX0ZJTkRfT0JTSURJQU4gPSAndW5hYmxlIHRvIGZpbmQgT2JzaWRpYW4nO1xuY29uc3QgQVVUT19TVEFSVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyA9IDIwMDA7XG5jb25zdCBBVVRPX1NUQVJUX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTID0gMzAwMDA7XG5jb25zdCBWQVVMVF9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyA9IDUwMDtcbmNvbnN0IFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMgPSAzMDAwMDtcbmNvbnN0IFZBVUxUX0NMT1NFX0RFTEFZX0lOX01JTExJU0VDT05EUyA9IDEwMDA7XG5jb25zdCBWQVVMVF9FVkFMX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTID0gMTAwMDA7XG5cbmludGVyZmFjZSBCdWlsZERpYWdub3N0aWNzUGFyYW1zIHtcbiAgcmVhZG9ubHkgY29tbWFuZDogc3RyaW5nW107XG4gIHJlYWRvbmx5IGN3ZDogc3RyaW5nO1xuICByZWFkb25seSBleGVjUmVzdWx0OiBFeGVjUmVzdWx0IHwgdW5kZWZpbmVkO1xuICByZWFkb25seSByZXN1bHRQYXRoOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHNjcmlwdENvbnRlbnQ6IHN0cmluZztcbiAgcmVhZG9ubHkgc2NyaXB0UGF0aDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFRyYW5zcG9ydCB0aGF0IGNvbW11bmljYXRlcyB3aXRoIERlc2t0b3AgT2JzaWRpYW4gdmlhIHRoZSBgb2JzaWRpYW4gZXZhbGAgQ0xJIGNvbW1hbmRcbiAqIGFuZCBtYW5hZ2VzIHZhdWx0cyB2aWEgRWxlY3Ryb24gSVBDLlxuICovXG5leHBvcnQgY2xhc3MgRGVza3RvcENsaVRyYW5zcG9ydCBpbXBsZW1lbnRzIE9ic2lkaWFuVHJhbnNwb3J0IHtcbiAgLyoqXG4gICAqIEluZGljYXRlcyB3aGV0aGVyIHRoaXMgdHJhbnNwb3J0IGlzIGZvciBhIG1vYmlsZSBwbGF0Zm9ybS4gQWx3YXlzIGBmYWxzZWAgZm9yIHRoaXMgdHJhbnNwb3J0LlxuICAgKi9cbiAgcHVibGljIHJlYWRvbmx5IGlzTW9iaWxlID0gZmFsc2U7XG5cbiAgLyoqXG4gICAqIEV2YWx1YXRlcyBhIEphdmFTY3JpcHQgZXhwcmVzc2lvbiBpbnNpZGUgT2JzaWRpYW4gdmlhIGBvYnNpZGlhbiBldmFsYC5cbiAgICpcbiAgICogV3JpdGVzIHRoZSBleHByZXNzaW9uIHRvIGEgdGVtcG9yYXJ5IGAuY2pzYCBmaWxlIGFuZCBleGVjdXRlcyBhIHRpbnlcbiAgICogYHJlcXVpcmUoKS5pbnZva2UoKWAgY2FsbCB2aWEgdGhlIENMSS4gVGhlIHJlc3VsdCBpcyB3cml0dGVuIHRvIGFcbiAgICogYC5yZXN1bHQuanNvbmAgZmlsZSBieSB0aGUgc2NyaXB0LCBhdm9pZGluZyBDTEkgc3Rkb3V0IHNpemUgbGltaXRzXG4gICAqIGFuZCBJUEMgYnVmZmVyIGlzc3Vlcy5cbiAgICpcbiAgICogSGFuZGxlcyBhdXRvLXN0YXJ0OiBpZiBPYnNpZGlhbiBpcyBub3QgcnVubmluZywgbGF1bmNoZXMgaXQgdmlhIFVSSSBwcm90b2NvbFxuICAgKiBhbmQgcmV0cmllcyB1bnRpbCBpdCBiZWNvbWVzIGF2YWlsYWJsZS5cbiAgICpcbiAgICogQHBhcmFtIGV4cHJlc3Npb24gLSBUaGUgSmF2YVNjcmlwdCBleHByZXNzaW9uIHRvIGV2YWx1YXRlLlxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIEV2YWx1YXRpb24gb3B0aW9ucy5cbiAgICogQHJldHVybnMgVGhlIG5vcm1hbGl6ZWQgcmVzdWx0IHN0cmluZyAodHJhbnNwb3J0LXNwZWNpZmljIHByZWZpeGVzIHN0cmlwcGVkKS5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBldmFsdWF0ZShleHByZXNzaW9uOiBzdHJpbmcsIG9wdGlvbnM6IFRyYW5zcG9ydEV2YWxPcHRpb25zKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBTTElDRV9TVEFSVCA9IDI7XG4gICAgY29uc3Qgc2NyaXB0SWQgPSBTdHJpbmcoTWF0aC5yYW5kb20oKSkuc2xpY2UoU0xJQ0VfU1RBUlQpO1xuICAgIGNvbnN0IHNjcmlwdERpciA9IGpvaW4odG1wZGlyKCksICdvYnNpZGlhbi1pbnRlZ3JhdGlvbi10ZXN0aW5nJyk7XG4gICAgY29uc3Qgc2NyaXB0UGF0aCA9IGpvaW4oc2NyaXB0RGlyLCBgJHtzY3JpcHRJZH0uanNgKTtcbiAgICBjb25zdCByZXN1bHRQYXRoID0gam9pbihzY3JpcHREaXIsIGAke3NjcmlwdElkfS5yZXN1bHQuanNvbmApO1xuXG4gICAgYXdhaXQgbWtkaXIoc2NyaXB0RGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcblxuICAgIGNvbnN0IHNjcmlwdENvbnRlbnQgPSBidWlsZFNjcmlwdEZpbGUoZXhwcmVzc2lvbiwgcmVzdWx0UGF0aCk7XG4gICAgYXdhaXQgd3JpdGVGaWxlKHNjcmlwdFBhdGgsIHNjcmlwdENvbnRlbnQpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHNhZmVTY3JpcHRQYXRoID0gc2NyaXB0UGF0aC5yZXBsYWNlKC9cXFxcL2csICcvJyk7XG4gICAgICBjb25zdCBjbGlFeHByID0gYCgke2dldEZ1bmN0aW9uRXhwcmVzc2lvblN0cmluZyhleGVjdXRlU2NyaXB0RmlsZSl9KSgke0pTT04uc3RyaW5naWZ5KHNhZmVTY3JpcHRQYXRoKX0pYDtcbiAgICAgIGNvbnN0IHZhdWx0SWQgPSBnZXRWYXVsdElkKG9wdGlvbnMuY3dkKTtcbiAgICAgIGNvbnN0IGNvbW1hbmQgPSBbJ29ic2lkaWFuJywgLi4uKHZhdWx0SWQgPyBbYHZhdWx0PSR7dmF1bHRJZH1gXSA6IFtdKSwgJ2V2YWwnLCAnLS1hbGxvdy1mb2N1cy1zdGVhbCcsIGBjb2RlPSR7Y2xpRXhwcn1gXTtcblxuICAgICAgbGV0IGV4ZWNSZXN1bHQ6IEV4ZWNSZXN1bHQgfCB1bmRlZmluZWQ7XG4gICAgICB0cnkge1xuICAgICAgICBleGVjUmVzdWx0ID0gYXdhaXQgZXhlYyhjb21tYW5kLCB7XG4gICAgICAgICAgY3dkOiBvcHRpb25zLmN3ZCxcbiAgICAgICAgICBpc1F1aWV0OiB0cnVlLFxuICAgICAgICAgIHNob3VsZEluY2x1ZGVEZXRhaWxzOiB0cnVlLFxuICAgICAgICAgIC4uLihvcHRpb25zLnRpbWVvdXRJbk1pbGxpc2Vjb25kcyAhPT0gdW5kZWZpbmVkICYmIHsgdGltZW91dEluTWlsbGlzZWNvbmRzOiBvcHRpb25zLnRpbWVvdXRJbk1pbGxpc2Vjb25kcyB9KVxuICAgICAgICB9KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcik7XG4gICAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKFVOQUJMRV9UT19GSU5EX09CU0lESUFOKSkge1xuICAgICAgICAgIGF3YWl0IHRoaXMuZW5zdXJlT2JzaWRpYW5SdW5uaW5nQW5kUmV0cnkoY29tbWFuZCwgb3B0aW9ucy5jd2QpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGlmICghZXhpc3RzU3luYyhyZXN1bHRQYXRoKSkge1xuICAgICAgICBjb25zdCBkaWFnbm9zdGljcyA9IGF3YWl0IGJ1aWxkRGlhZ25vc3RpY3Moe1xuICAgICAgICAgIGNvbW1hbmQsXG4gICAgICAgICAgY3dkOiBvcHRpb25zLmN3ZCxcbiAgICAgICAgICBleGVjUmVzdWx0LFxuICAgICAgICAgIHJlc3VsdFBhdGgsXG4gICAgICAgICAgc2NyaXB0Q29udGVudCxcbiAgICAgICAgICBzY3JpcHRQYXRoXG4gICAgICAgIH0pO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFNjcmlwdCBkaWQgbm90IGV4ZWN1dGUgZm9yIHBhdGg6ICR7b3B0aW9ucy5jd2R9XFxuJHtkaWFnbm9zdGljc31gKTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVzdWx0U3RyID0gYXdhaXQgcmVhZEZpbGUocmVzdWx0UGF0aCwgJ3V0Zi04Jyk7XG4gICAgICBjb25zdCBlbnZlbG9wZSA9IEpTT04ucGFyc2UocmVzdWx0U3RyKSBhcyBTY3JpcHRSZXN1bHRFbnZlbG9wZTtcblxuICAgICAgaWYgKCd0eXBlJyBpbiBlbnZlbG9wZSkge1xuICAgICAgICBpZiAoZW52ZWxvcGUudHlwZSA9PT0gJ2Vycm9yJykge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgU2NyaXB0IGVycm9yIGluIE9ic2lkaWFuIGZvciBwYXRoOiAke29wdGlvbnMuY3dkfTogJHtlbnZlbG9wZS52YWx1ZX1gKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gJyc7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBlbnZlbG9wZS52YWx1ZTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgYXdhaXQgdW5saW5rKHNjcmlwdFBhdGgpLmNhdGNoKG5vb3ApO1xuICAgICAgYXdhaXQgdW5saW5rKHJlc3VsdFBhdGgpLmNhdGNoKG5vb3ApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIENMSS1zcGVjaWZpYyBwcmVmbGlnaHQgY2hlY2tzLlxuICAgKlxuICAgKiBWZXJpZmllcyB0aGF0IHRoZSB2YXVsdCBpcyByZWdpc3RlcmVkLCBDTEkgaXMgZW5hYmxlZCBpbiBPYnNpZGlhbiBzZXR0aW5ncyxcbiAgICogYW5kIHRoZSBgb2JzaWRpYW5gIENMSSBiaW5hcnkgaXMgYXZhaWxhYmxlIGluIFBBVEguIElmIHRoZSB2YXVsdCBpcyByZWdpc3RlcmVkXG4gICAqIGJ1dCBub3Qgb3Blbiwgb3BlbnMgaXQgdmlhIFVSSSBwcm90b2NvbCBhbmQgcG9sbHMgdW50aWwgaXQgaXMgcmVhZHkuXG4gICAqXG4gICAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICAgKi9cbiAgcHVibGljIGFzeW5jIHByZWZsaWdodENoZWNrKHZhdWx0UGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gUnVubmluZyBwcmVmbGlnaHQgY2hlY2sgZm9yIHZhdWx0OiAke3ZhdWx0UGF0aH1gKTtcbiAgICBpZiAoIWlzVmF1bHRSZWdpc3RlcmVkKHZhdWx0UGF0aCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYFZhdWx0IGlzIG5vdCByZWdpc3RlcmVkIGluIE9ic2lkaWFuOiAke3ZhdWx0UGF0aH0uIFJlZ2lzdGVyIHRoZSB2YXVsdCBmaXJzdCB3aXRoIHJlZ2lzdGVyVmF1bHQoKSBvciBUZW1wVmF1bHQucmVnaXN0ZXIoKS5gXG4gICAgICApO1xuICAgIH1cblxuICAgIGlmICghaXNDbGlFbmFibGVkKCkpIHtcbiAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIENMSSBpcyBkaXNhYmxlZCBpbiBvYnNpZGlhbi5qc29uLiBFbmFibGluZyBhbmQgcmVzdGFydGluZyBPYnNpZGlhbi4uLicpO1xuICAgICAgZW5hYmxlQ2xpSW5Db25maWcoKTtcbiAgICAgIGF3YWl0IHRoaXMucmVzdGFydE9ic2lkaWFuKHZhdWx0UGF0aCk7XG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5hc3NlcnRDbGlBdmFpbGFibGUoKTtcblxuICAgIGlmICghaXNWYXVsdE9wZW4odmF1bHRQYXRoKSkge1xuICAgICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gVmF1bHQgaXMgcmVnaXN0ZXJlZCBidXQgbm90IG9wZW46ICR7dmF1bHRQYXRofS4gT3BlbmluZyB2aWEgVVJJLi4uYCk7XG4gICAgICBhd2FpdCBvcGVuVmF1bHRWaWFVcmkodmF1bHRQYXRoKTtcbiAgICAgIGF3YWl0IHRoaXMucG9sbFZhdWx0UmVhZHkodmF1bHRQYXRoKTtcbiAgICB9XG5cbiAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBQcmVmbGlnaHQgY2hlY2sgcGFzc2VkLicpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZ2lzdGVycyBhIHZhdWx0IGluIHRoZSBydW5uaW5nIE9ic2lkaWFuIGluc3RhbmNlIHZpYSBFbGVjdHJvbiBJUEMuXG4gICAqXG4gICAqIFVzZXMgYHZhdWx0LW9wZW5gIElQQyB0byByZWdpc3RlciBhbmQgb3BlbiB0aGUgdmF1bHQsIHRoZW4gcG9sbHMgdW50aWxcbiAgICogdGhlIHZhdWx0J3MgQ0xJIGlzIHJlYWR5IGFuZCBgZ2V0QmFzZVBhdGgoKWAgbWF0Y2hlcyBgdmF1bHRQYXRoYC5cbiAgICpcbiAgICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIHRoZSB2YXVsdCBmb2xkZXIuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgcmVnaXN0ZXJWYXVsdCh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxvZyhgW2NsaS10cmFuc3BvcnRdIFJlZ2lzdGVyaW5nIHZhdWx0OiAke3ZhdWx0UGF0aH1gKTtcbiAgICBjb25zdCBvcGVuVmF1bHRQYXRoID0gYXdhaXQgdGhpcy5nZXRPcGVuRXZhbFRhcmdldFZhdWx0UGF0aCgpO1xuXG4gICAgaWYgKG9wZW5WYXVsdFBhdGgpIHtcbiAgICAgIGF3YWl0IGVuc3VyZU5hbWVzcGFjZUJvb3RzdHJhcHBlZCh0aGlzLCBvcGVuVmF1bHRQYXRoKTtcbiAgICAgIGNvbnN0IHJlZ2lzdGVyRXhwciA9IGB3aW5kb3cuX19vYnNpZGlhbkludGVncmF0aW9uVGVzdGluZy5pcGNTZW5kU3luYygke0pTT04uc3RyaW5naWZ5KHsgYXJnczogW3ZhdWx0UGF0aCwgZmFsc2VdLCBjaGFubmVsOiAndmF1bHQtb3BlbicgfSl9KWA7XG4gICAgICBhd2FpdCB0aGlzLmV2YWx1YXRlKHJlZ2lzdGVyRXhwciwgeyBjd2Q6IG9wZW5WYXVsdFBhdGggfSk7XG4gICAgICBhd2FpdCB0aGlzLmVuYWJsZVBsdWdpbnNJbkxvY2FsU3RvcmFnZSh2YXVsdFBhdGgsIG9wZW5WYXVsdFBhdGgpO1xuICAgIH0gZWxzZSB7XG4gICAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBObyBvcGVuIHZhdWx0IHRvIHRhcmdldC4gV3JpdGluZyB2YXVsdCBlbnRyeSBkaXJlY3RseSB0byBvYnNpZGlhbi5qc29uLi4uJyk7XG4gICAgICByZWdpc3RlclZhdWx0SW5Db25maWcodmF1bHRQYXRoKTtcbiAgICAgIC8qXG4gICAgICAgKiBBIHJ1bm5pbmcgT2JzaWRpYW4gd2l0aCBubyBvcGVuIHZhdWx0IChlLmcuIHNpdHRpbmcgb24gdGhlIHZhdWx0XG4gICAgICAgKiBzd2l0Y2hlcikgbG9hZGVkIGl0cyByZWdpc3RyeSBhdCBzdGFydHVwIGFuZCB3b24ndCBzZWUgdGhlIGVudHJ5IGp1c3RcbiAgICAgICAqIHdyaXR0ZW4sIHNvIGFuIGBvYnNpZGlhbjovL29wZW5gIFVSSSBmb3IgaXQgZmFpbHMgd2l0aCBcIlZhdWx0IG5vdFxuICAgICAgICogZm91bmRcIi4gS2lsbCBpdCBmaXJzdCBzbyB0aGUgVVJJIGJlbG93IHRyaWdnZXJzIGEgZnJlc2ggbGF1bmNoIHRoYXRcbiAgICAgICAqIHJlYWRzIHRoZSB1cGRhdGVkIG9ic2lkaWFuLmpzb24uIFdlIG9ubHkgcmVhY2ggdGhpcyBicmFuY2ggd2l0aCBub1xuICAgICAgICogdmF1bHQgb3Blbiwgc28gbm90aGluZyB0aGUgdXNlciBpcyB3b3JraW5nIGluIGlzIGxvc3QuXG4gICAgICAgKi9cbiAgICAgIGlmIChhd2FpdCBpc09ic2lkaWFuUnVubmluZygpKSB7XG4gICAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIE9ic2lkaWFuIGlzIHJ1bm5pbmcgd2l0aG91dCBhbiBvcGVuIHZhdWx0IFx1MjAxNCByZXN0YXJ0aW5nIHNvIGl0IHJlYWRzIHRoZSBuZXcgcmVnaXN0cnkgZW50cnkuLi4nKTtcbiAgICAgICAgYXdhaXQgdGhpcy5raWxsT2JzaWRpYW4oKTtcbiAgICAgIH1cbiAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIFZhdWx0IGVudHJ5IHdyaXR0ZW4uIE9wZW5pbmcgdmF1bHQgdmlhIFVSSSBwcm90b2NvbC4uLicpO1xuICAgICAgYXdhaXQgb3BlblZhdWx0VmlhVXJpKHZhdWx0UGF0aCk7XG4gICAgICBhd2FpdCB0aGlzLnBvbGxWYXVsdFJlYWR5KHZhdWx0UGF0aCk7XG4gICAgICBhd2FpdCB0aGlzLmVuYWJsZVBsdWdpbnNJbkxvY2FsU3RvcmFnZSh2YXVsdFBhdGgsIHZhdWx0UGF0aCk7XG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5wb2xsVmF1bHRSZWFkeSh2YXVsdFBhdGgpO1xuICAgIGF3YWl0IHRoaXMuZGlzbWlzc1RydXN0RGlhbG9nKHZhdWx0UGF0aCk7XG4gIH1cblxuICAvKipcbiAgICogVW5yZWdpc3RlcnMgYSB2YXVsdCBmcm9tIHRoZSBydW5uaW5nIE9ic2lkaWFuIGluc3RhbmNlLlxuICAgKlxuICAgKiBTY2hlZHVsZXMgdGhlIHZhdWx0IHdpbmRvdyB0byBjbG9zZSwgd2FpdHMsIHRoZW4gcmVtb3ZlcyB0aGUgdmF1bHRcbiAgICogZnJvbSB0aGUgcmVnaXN0cnkgdmlhIGB2YXVsdC1yZW1vdmVgIElQQy5cbiAgICpcbiAgICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIHRoZSB2YXVsdCBmb2xkZXIuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgdW5yZWdpc3RlclZhdWx0KHZhdWx0UGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gVW5yZWdpc3RlcmluZyB2YXVsdDogY2xvc2luZyB3aW5kb3cgZm9yICR7dmF1bHRQYXRofS4uLmApO1xuICAgIGlmIChpc1ZhdWx0T3Blbih2YXVsdFBhdGgpKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCBlbnN1cmVOYW1lc3BhY2VCb290c3RyYXBwZWQodGhpcywgdmF1bHRQYXRoKTtcbiAgICAgICAgY29uc3QgZGVzdHJveUV4cHIgPSAnd2luZG93Ll9fb2JzaWRpYW5JbnRlZ3JhdGlvblRlc3RpbmcuZGVzdHJveUN1cnJlbnRXaW5kb3coKSc7XG4gICAgICAgIGF3YWl0IHRoaXMuZXZhbHVhdGUoZGVzdHJveUV4cHIsIHsgY3dkOiB2YXVsdFBhdGgsIHRpbWVvdXRJbk1pbGxpc2Vjb25kczogVkFVTFRfRVZBTF9USU1FT1VUX0lOX01JTExJU0VDT05EUyB9KTtcbiAgICAgICAgbG9nKCdbY2xpLXRyYW5zcG9ydF0gV2luZG93IGRlc3Ryb3kgY29tbWFuZCBzZW50LicpO1xuICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gV2luZG93IGRlc3Ryb3kgZmFpbGVkIChub24tZmF0YWwpOiAke3NlcmlhbGl6ZUVycm9yKGVycm9yKX1gKTtcbiAgICAgIH1cblxuICAgICAgbG9nKCdbY2xpLXRyYW5zcG9ydF0gV2FpdGluZyBmb3Igd2luZG93IHRvIGNsb3NlLi4uJyk7XG4gICAgICBhd2FpdCBkZWxheShWQVVMVF9DTE9TRV9ERUxBWV9JTl9NSUxMSVNFQ09ORFMpO1xuICAgIH0gZWxzZSB7XG4gICAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBWYXVsdCBpcyBub3Qgb3BlbiBcdTIwMTQgc2tpcHBpbmcgZGVzdHJveSAod291bGQgb3RoZXJ3aXNlIHNwYXduIGEgc3RyYXkgd2luZG93IHZpYSB0aGUgQ0xJIGV2YWwpLicpO1xuICAgIH1cblxuICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIFJlbW92aW5nIHZhdWx0IGZyb20gcmVnaXN0cnkuLi4nKTtcbiAgICBjb25zdCBldmFsVGFyZ2V0Rm9yUmVtb3ZhbCA9IGF3YWl0IHRoaXMuZ2V0T3BlbkV2YWxUYXJnZXRWYXVsdFBhdGgoKTtcbiAgICB0cnkge1xuICAgICAgaWYgKGV2YWxUYXJnZXRGb3JSZW1vdmFsKSB7XG4gICAgICAgIGF3YWl0IGVuc3VyZU5hbWVzcGFjZUJvb3RzdHJhcHBlZCh0aGlzLCBldmFsVGFyZ2V0Rm9yUmVtb3ZhbCk7XG4gICAgICAgIGNvbnN0IHJlbW92ZUV4cHIgPSBgd2luZG93Ll9fb2JzaWRpYW5JbnRlZ3JhdGlvblRlc3RpbmcuaXBjU2VuZFN5bmMoJHtKU09OLnN0cmluZ2lmeSh7IGFyZ3M6IFt2YXVsdFBhdGhdLCBjaGFubmVsOiAndmF1bHQtcmVtb3ZlJyB9KX0pYDtcbiAgICAgICAgYXdhaXQgdGhpcy5ldmFsdWF0ZShyZW1vdmVFeHByLCB7IGN3ZDogZXZhbFRhcmdldEZvclJlbW92YWwsIHRpbWVvdXRJbk1pbGxpc2Vjb25kczogVkFVTFRfRVZBTF9USU1FT1VUX0lOX01JTExJU0VDT05EUyB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIE5vIG9wZW4gdmF1bHQgdG8gdGFyZ2V0IGZvciByZW1vdmFsIElQQyBcdTIwMTQgcmVtb3ZpbmcgZGlyZWN0bHkgZnJvbSBvYnNpZGlhbi5qc29uLicpO1xuICAgICAgICByZW1vdmVWYXVsdEZyb21Db25maWcodmF1bHRQYXRoKTtcbiAgICAgIH1cbiAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIFZhdWx0IHJlbW92ZWQgZnJvbSByZWdpc3RyeS4nKTtcbiAgICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gVmF1bHQgcmVnaXN0cnkgcmVtb3ZhbCBmYWlsZWQgKG5vbi1mYXRhbCk6ICR7c2VyaWFsaXplRXJyb3IoZXJyb3IpfWApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3Mgd2hldGhlciB0aGUgT2JzaWRpYW4gQ0xJIGJpbmFyeSBpcyBhdmFpbGFibGUgaW4gdGhlIHN5c3RlbSBQQVRILlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBhc3NlcnRDbGlBdmFpbGFibGUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgY29tbWFuZCA9IHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicgPyAnd2hlcmUuZXhlIG9ic2lkaWFuJyA6ICd3aGljaCBvYnNpZGlhbic7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGV4ZWMoY29tbWFuZCwgeyBpc1F1aWV0OiB0cnVlIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdPYnNpZGlhbiBDTEkgaXMgbm90IGF2YWlsYWJsZS4gRW5zdXJlIE9ic2lkaWFuIGlzIGluc3RhbGxlZCBhbmQgaXRzIENMSSBpcyBpbiB5b3VyIFBBVEguJyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIERpc21pc3NlcyB0aGUgXCJEbyB5b3UgdHJ1c3QgdGhlIGF1dGhvciBvZiB0aGlzIHZhdWx0P1wiIGRpYWxvZyBpZiBwcmVzZW50LlxuICAgKlxuICAgKiBDbGlja3MgdGhlIFwiVHJ1c3QgYXV0aG9yIGFuZCBlbmFibGUgcGx1Z2luc1wiIGJ1dHRvbiAoYC5tb2QtY3RhYCkgaW5zaWRlIGFueVxuICAgKiBtb2RhbCB3aG9zZSB0ZXh0IGluY2x1ZGVzIFwiRG8geW91IHRydXN0IHRoZSBhdXRob3JcIi4gQWN0cyBhcyBhIHNhZmV0eSBuZXRcbiAgICogZm9yIGJvdGggcmVnaXN0cmF0aW9uIHBhdGhzOlxuICAgKlxuICAgKiAtIEZpcnN0IHZhdWx0IHJlZ2lzdGVyZWQgdmlhIGNvbmZpZyBmaWxlIChubyBwcmlvciBgZW5hYmxlLXBsdWdpbi08aWQ+YCBlbnRyeSkuXG4gICAqIC0gU3Vic2VxdWVudCB2YXVsdCByZWdpc3RlcmVkIHdoaWxlIGFub3RoZXIgdmF1bHQgaXMgYWxyZWFkeSBvcGVuLiBUaGVcbiAgICogICBgZW5hYmxlLXBsdWdpbi08bmV3VmF1bHRJZD5gIHdyaXRlIGhhcHBlbnMgaW4gdGhlIGV4aXN0aW5nIHZhdWx0J3MgcmVuZGVyZXIsXG4gICAqICAgYnV0IHRoZSBuZXcgdmF1bHQncyByZW5kZXJlciBtYXkgcmVhZCBgbG9jYWxTdG9yYWdlYCBiZWZvcmUgdGhhdCBjcm9zcy1yZW5kZXJlclxuICAgKiAgIHdyaXRlIGJlY29tZXMgdmlzaWJsZSAocmFjZSBvYnNlcnZlZCBpbiBPYnNpZGlhbiAxLjEzLjApLlxuICAgKlxuICAgKiBAcGFyYW0gdmF1bHRQYXRoIC0gVGhlIHZhdWx0IHBhdGggdG8gZXZhbHVhdGUgaW4uXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGRpc21pc3NUcnVzdERpYWxvZyh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IGVuc3VyZU5hbWVzcGFjZUJvb3RzdHJhcHBlZCh0aGlzLCB2YXVsdFBhdGgpO1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRoaXMuZXZhbHVhdGUoRElTTUlTU19UUlVTVF9ESUFMT0dfRVhQUiwgeyBjd2Q6IHZhdWx0UGF0aCB9KTtcbiAgICBpZiAocmVzdWx0ID09PSAndHJ1ZScpIHtcbiAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIERpc21pc3NlZCBcIkRvIHlvdSB0cnVzdCB0aGUgYXV0aG9yXCIgZGlhbG9nLicpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTZXRzIGBlbmFibGUtcGx1Z2luLTx2YXVsdElkPmAgaW4gT2JzaWRpYW4ncyBsb2NhbFN0b3JhZ2UgdG8gcHJldmVudFxuICAgKiB0aGUgXCJEbyB5b3UgdHJ1c3QgdGhlIGF1dGhvciBvZiB0aGlzIHZhdWx0P1wiIGRpYWxvZyBmcm9tIGFwcGVhcmluZ1xuICAgKiB3aGVuIGEgdmF1bHQgd2l0aCBjb21tdW5pdHkgcGx1Z2lucyBpcyBvcGVuZWQgZm9yIHRoZSBmaXJzdCB0aW1lLlxuICAgKlxuICAgKiBNdXN0IGJlIGNhbGxlZCBhZnRlciB0aGUgYHZhdWx0LW9wZW5gIElQQyAoc28gdGhlIHZhdWx0IElEIGV4aXN0cyBpblxuICAgKiBgb2JzaWRpYW4uanNvbmApIGFuZCBiZWZvcmUgdGhlIG5ldyB2YXVsdCB3aW5kb3cgZmluaXNoZXMgbG9hZGluZy5cbiAgICpcbiAgICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIHRoZSB2YXVsdCBmb2xkZXIuXG4gICAqIEBwYXJhbSBldmFsVGFyZ2V0VmF1bHRQYXRoIC0gVGhlIHZhdWx0IHBhdGggdG8gdXNlIGFzIGV2YWwgdGFyZ2V0IChhbiBleGlzdGluZywgbG9hZGVkIHZhdWx0KS5cbiAgICogICBsb2NhbFN0b3JhZ2UgaXMgc2hhcmVkIGFjcm9zcyBhbGwgT2JzaWRpYW4gd2luZG93cyAoc2FtZSBFbGVjdHJvbiBvcmlnaW4pLCBzbyB0aGVcbiAgICogICBgbG9jYWxTdG9yYWdlLnNldEl0ZW1gIGNhbGwgY2FuIHJ1biBpbiBhbnkgbG9hZGVkIHZhdWx0IHdpbmRvdy5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW5hYmxlUGx1Z2luc0luTG9jYWxTdG9yYWdlKHZhdWx0UGF0aDogc3RyaW5nLCBldmFsVGFyZ2V0VmF1bHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCB2YXVsdElkID0gZ2V0VmF1bHRJZCh2YXVsdFBhdGgpO1xuICAgIGlmICghdmF1bHRJZCkge1xuICAgICAgbG9nKCdbY2xpLXRyYW5zcG9ydF0gQ291bGQgbm90IGZpbmQgdmF1bHQgSUQgXHUyMDE0IHNraXBwaW5nIGxvY2FsU3RvcmFnZSB0cnVzdCBmbGFnLicpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGF3YWl0IGVuc3VyZU5hbWVzcGFjZUJvb3RzdHJhcHBlZCh0aGlzLCBldmFsVGFyZ2V0VmF1bHRQYXRoKTtcbiAgICBjb25zdCBlbmFibGVFeHByID0gYHdpbmRvdy5fX29ic2lkaWFuSW50ZWdyYXRpb25UZXN0aW5nLnNldExvY2FsU3RvcmFnZUl0ZW0oJHtKU09OLnN0cmluZ2lmeSh7IGtleTogYGVuYWJsZS1wbHVnaW4tJHt2YXVsdElkfWAsIHZhbHVlOiAndHJ1ZScgfSl9KWA7XG4gICAgYXdhaXQgdGhpcy5ldmFsdWF0ZShlbmFibGVFeHByLCB7IGN3ZDogZXZhbFRhcmdldFZhdWx0UGF0aCB9KTtcbiAgICBsb2coYFtjbGktdHJhbnNwb3J0XSBTZXQgZW5hYmxlLXBsdWdpbi0ke3ZhdWx0SWR9IGluIGxvY2FsU3RvcmFnZS5gKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBMYXVuY2hlcyBPYnNpZGlhbiBiaW5hcnkgYW5kIHJldHJpZXMgdGhlIGV2YWwgdW50aWwgaXQgcmVzcG9uZHMuXG4gICAqXG4gICAqIEBwYXJhbSBjb21tYW5kIC0gVGhlIENMSSBjb21tYW5kIGFycmF5IHRvIHJldHJ5LlxuICAgKiBAcGFyYW0gY3dkIC0gVGhlIHdvcmtpbmcgZGlyZWN0b3J5LlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBlbnN1cmVPYnNpZGlhblJ1bm5pbmdBbmRSZXRyeShjb21tYW5kOiBzdHJpbmdbXSwgY3dkOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBPYnNpZGlhbiBpcyBub3QgcnVubmluZy4gU3RhcnRpbmcgT2JzaWRpYW4uLi4nKTtcblxuICAgIGNvbnN0IGxhdW5jaENvbW1hbmQgPSBhd2FpdCBnZXRPYnNpZGlhbkxhdW5jaENvbW1hbmQoKTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZXhlYyhsYXVuY2hDb21tYW5kLCB7IGlzUXVpZXQ6IHRydWUgfSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBUaGUgbGF1bmNoIGNvbW1hbmQgbWF5IGZhaWwgb24gc29tZSBzeXN0ZW1zIFx1MjAxNCB3ZSdsbCBzdGlsbCB0cnkgcG9sbGluZy5cbiAgICB9XG5cbiAgICBsb2coYFtjbGktdHJhbnNwb3J0XSBQb2xsaW5nIGZvciBPYnNpZGlhbiBDTEkgKHRpbWVvdXQ9JHtTdHJpbmcoQVVUT19TVEFSVF9USU1FT1VUX0lOX01JTExJU0VDT05EUyl9bXMpLi4uYCk7XG4gICAgY29uc3QgZGVhZGxpbmUgPSBEYXRlLm5vdygpICsgQVVUT19TVEFSVF9USU1FT1VUX0lOX01JTExJU0VDT05EUztcbiAgICB3aGlsZSAoRGF0ZS5ub3coKSA8IGRlYWRsaW5lKSB7XG4gICAgICBhd2FpdCBkZWxheShBVVRPX1NUQVJUX1BPTExfSU5URVJWQUxfSU5fTUlMTElTRUNPTkRTKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGV4ZWMoY29tbWFuZCwgeyBjd2QsIGlzUXVpZXQ6IHRydWUgfSk7XG4gICAgICAgIGxvZygnW2NsaS10cmFuc3BvcnRdIE9ic2lkaWFuIENMSSByZXNwb25kZWQuJyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcik7XG4gICAgICAgIGlmICghbWVzc2FnZS5pbmNsdWRlcyhVTkFCTEVfVE9fRklORF9PQlNJRElBTikpIHtcbiAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgICAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBPYnNpZGlhbiBub3QgcmVhZHkgeWV0LCByZXRyeWluZy4uLicpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHRocm93IG5ldyBFcnJvcihgT2JzaWRpYW4gZGlkIG5vdCBzdGFydCB3aXRoaW4gJHtTdHJpbmcoQVVUT19TVEFSVF9USU1FT1VUX0lOX01JTExJU0VDT05EUyl9bXMuYCk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgcGF0aCBvZiBhbiBhbHJlYWR5LW9wZW4gdmF1bHQgdG8gdXNlIGFzIGFuIGBvYnNpZGlhbiBldmFsYFxuICAgKiB0YXJnZXQsIG9yIGB1bmRlZmluZWRgIGlmIHRoZXJlIGlzIG5vbmUuXG4gICAqXG4gICAqIFRoZSBgb3BlbmAgZmxhZyBpbiBgb2JzaWRpYW4uanNvbmAgaXMgb25seSB0cnVzdHdvcnRoeSB3aGlsZSBPYnNpZGlhbiBpc1xuICAgKiBydW5uaW5nIFx1MjAxNCBhIGtpbGxlZCBvciBjcmFzaGVkIHByb2Nlc3MgbmV2ZXIgcmVzZXRzIGl0LCBsZWF2aW5nIHRoZSB1c2VyJ3NcbiAgICogbGFzdC1zZXNzaW9uIHZhdWx0IG1hcmtlZCBvcGVuLiBBY3Rpbmcgb24gdGhhdCBzdGFsZSBmbGFnIHdvdWxkIHRhcmdldCB0aGVcbiAgICogKHBvc3NpYmx5IGhlYXZ5KSBsYXN0LXNlc3Npb24gdmF1bHQgYW5kIGNhdXNlIGF1dG8tc3RhcnQgdG8gcmVvcGVuIGl0LlxuICAgKiBXaGVuIE9ic2lkaWFuIGlzIG5vdCBydW5uaW5nIHdlIHRoZXJlZm9yZSByZXBvcnQgbm8gb3BlbiB2YXVsdCwgc28gY2FsbGVyc1xuICAgKiBmYWxsIGJhY2sgdG8gdGhlIGRpcmVjdC1jb25maWcgcGF0aCB0aGF0IG9wZW5zIG9ubHkgdGhlIHRlbXAgdmF1bHQuXG4gICAqXG4gICAqIEByZXR1cm5zIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIGFuIG9wZW4gdmF1bHQsIG9yIGB1bmRlZmluZWRgLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBnZXRPcGVuRXZhbFRhcmdldFZhdWx0UGF0aCgpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICAgIGlmICghYXdhaXQgaXNPYnNpZGlhblJ1bm5pbmcoKSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICByZXR1cm4gZ2V0QW55T3BlblZhdWx0UGF0aCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEtpbGxzIHRoZSBydW5uaW5nIE9ic2lkaWFuIHByb2Nlc3MgKGlmIGFueSkgYW5kIHdhaXRzIGZvciBpdCB0byBleGl0LlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBraWxsT2JzaWRpYW4oKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbG9nKCdbY2xpLXRyYW5zcG9ydF0gS2lsbGluZyBPYnNpZGlhbiBwcm9jZXNzLi4uJyk7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGV4ZWMoZ2V0S2lsbE9ic2lkaWFuQ29tbWFuZCgpLCB7IGlzUXVpZXQ6IHRydWUgfSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBQcm9jZXNzIG1heSBub3QgYmUgcnVubmluZyBcdTIwMTQgdGhhdCdzIGZpbmUuXG4gICAgfVxuXG4gICAgYXdhaXQgZGVsYXkoVkFVTFRfQ0xPU0VfREVMQVlfSU5fTUlMTElTRUNPTkRTKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQb2xscyB1bnRpbCB0aGUgdmF1bHQgaXMgcmVhZHkgYW5kIHJlc3BvbmRpbmcgdG8gZXZhbCBjYWxscy5cbiAgICpcbiAgICogRWFjaCBgb2JzaWRpYW4gZXZhbGAgaW4gdGhlIGxvb3AgaXMgZ2l2ZW4gYSBwZXItY2FsbCB0aW1lb3V0LiBUaGUgb3ZlcmFsbFxuICAgKiBkZWFkbGluZSBpcyBvbmx5IHJlLWNoZWNrZWQgYmV0d2VlbiBhd2FpdHMsIHNvIHdpdGhvdXQgaXQgYSBzaW5nbGUgaHVuZ1xuICAgKiBldmFsIChlLmcuIGEgdmF1bHQgd2luZG93IHRoYXQgb3BlbmVkIGJ1dCBuZXZlciBmaW5pc2hlZCBsb2FkaW5nKSB3b3VsZFxuICAgKiBibG9jayBwYXN0IHRoZSBkZWFkbGluZSBmb3JldmVyIGluc3RlYWQgb2YgZmFpbGluZyBhbmQgcmV0cnlpbmcuXG4gICAqXG4gICAqIEBwYXJhbSB2YXVsdFBhdGggLSBUaGUgYWJzb2x1dGUgcGF0aCB0byB0aGUgdmF1bHQgZm9sZGVyLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBwb2xsVmF1bHRSZWFkeSh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHBvbGxFeHByID0gJ3dpbmRvdy5fX29ic2lkaWFuSW50ZWdyYXRpb25UZXN0aW5nLnBvbGxWYXVsdEJhc2VQYXRoKCknO1xuXG4gICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gUG9sbGluZyBmb3IgdmF1bHQgcmVhZGluZXNzICh0aW1lb3V0PSR7U3RyaW5nKFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMpfW1zKS4uLmApO1xuICAgIGNvbnN0IGRlYWRsaW5lID0gRGF0ZS5ub3coKSArIFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFM7XG4gICAgbGV0IGlzQm9vdHN0cmFwcGVkID0gZmFsc2U7XG4gICAgd2hpbGUgKERhdGUubm93KCkgPCBkZWFkbGluZSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgaWYgKCFpc0Jvb3RzdHJhcHBlZCkge1xuICAgICAgICAgIGF3YWl0IGVuc3VyZU5hbWVzcGFjZUJvb3RzdHJhcHBlZCh0aGlzLCB2YXVsdFBhdGgsIFZBVUxUX0VWQUxfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMpO1xuICAgICAgICAgIGlzQm9vdHN0cmFwcGVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBiYXNlUGF0aCA9IGF3YWl0IHRoaXMuZXZhbHVhdGUocG9sbEV4cHIsIHsgY3dkOiB2YXVsdFBhdGgsIHRpbWVvdXRJbk1pbGxpc2Vjb25kczogVkFVTFRfRVZBTF9USU1FT1VUX0lOX01JTExJU0VDT05EUyB9KTtcbiAgICAgICAgaWYgKEpTT04ucGFyc2UoYmFzZVBhdGgpID09PSB2YXVsdFBhdGgpIHtcbiAgICAgICAgICBsb2coJ1tjbGktdHJhbnNwb3J0XSBWYXVsdCBpcyByZWFkeS4nKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2gge1xuICAgICAgICAvLyBWYXVsdCBub3QgcmVhZHkgeWV0IFx1MjAxNCBuYW1lc3BhY2UgbWF5IG5vdCBiZSBib290c3RyYXBwZWQgeWV0IGVpdGhlci5cbiAgICAgICAgaXNCb290c3RyYXBwZWQgPSBmYWxzZTtcbiAgICAgIH1cbiAgICAgIGF3YWl0IGRlbGF5KFZBVUxUX1BPTExfSU5URVJWQUxfSU5fTUlMTElTRUNPTkRTKTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKGBWYXVsdCBhdCAke3ZhdWx0UGF0aH0gZGlkIG5vdCBiZWNvbWUgcmVhZHkgd2l0aGluICR7U3RyaW5nKFZBVUxUX1BPTExfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMpfW1zYCk7XG4gIH1cblxuICAvKipcbiAgICogUmVzdGFydHMgT2JzaWRpYW4gYnkga2lsbGluZyB0aGUgcnVubmluZyBwcm9jZXNzIGFuZCByZWxhdW5jaGluZyBpdC5cbiAgICpcbiAgICogVXNlZCB3aGVuIGEgY29uZmlnIGNoYW5nZSAobGlrZSBlbmFibGluZyBDTEkpIHJlcXVpcmVzIGEgcmVzdGFydCB0byB0YWtlIGVmZmVjdC5cbiAgICpcbiAgICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSB2YXVsdCBwYXRoIHRvIHVzZSBmb3IgcG9sbGluZyBhZnRlciByZXN0YXJ0LlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyByZXN0YXJ0T2JzaWRpYW4odmF1bHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBhd2FpdCB0aGlzLmtpbGxPYnNpZGlhbigpO1xuXG4gICAgY29uc3QgcmVzdGFydFZhdWx0SWQgPSBnZXRWYXVsdElkKHZhdWx0UGF0aCk7XG4gICAgY29uc3QgY29tbWFuZCA9IFsnb2JzaWRpYW4nLCAuLi4ocmVzdGFydFZhdWx0SWQgPyBbYHZhdWx0PSR7cmVzdGFydFZhdWx0SWR9YF0gOiBbXSksICdldmFsJywgJ2NvZGU9XCIxXCInXTtcbiAgICBhd2FpdCB0aGlzLmVuc3VyZU9ic2lkaWFuUnVubmluZ0FuZFJldHJ5KGNvbW1hbmQsIHZhdWx0UGF0aCk7XG4gIH1cbn1cblxuLyoqXG4gKiBFdmFsdWF0ZXMgYW4gZXhwcmVzc2lvbiwgd3JpdGVzIHRoZSByZXN1bHQgdG8gYSBKU09OIGZpbGUuXG4gKlxuICogU2VyaWFsaXplZCB2aWEgYHRvU3RyaW5nKClgIGFuZCBleGVjdXRlZCBpbnNpZGUgdGhlIE9ic2lkaWFuIHByb2Nlc3MuXG4gKiBNdXN0IE5PVCByZWZlcmVuY2UgYW55IG91dGVyIHNjb3BlLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgaW52b2NhdGlvbiBwYXJhbWV0ZXJzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaW52b2tlQW5kV3JpdGVSZXN1bHQocGFyYW1zOiBJbnZva2VBbmRXcml0ZVJlc3VsdFBhcmFtcyk6IFByb21pc2U8dm9pZD4ge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWRlcHJlY2F0ZWQgLS0gd2luZG93LmFwcCBpcyBzZXQgYnkgT2JzaWRpYW4gYXQgcnVudGltZS5cbiAgY29uc3QgZnNQcm9taXNlcyA9IFJlZmxlY3QuZ2V0KHdpbmRvdy5hcHAudmF1bHQuYWRhcHRlciwgJ2ZzUHJvbWlzZXMnKSBhcyBJbnZva2VBbmRXcml0ZVJlc3VsdEZzUHJvbWlzZXM7XG4gIHRyeSB7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgcGFyYW1zLmV2YWx1YXRlKCk7XG4gICAgaWYgKHJlc3VsdCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBhd2FpdCBmc1Byb21pc2VzLndyaXRlRmlsZShwYXJhbXMucmVzdWx0UGF0aCwgSlNPTi5zdHJpbmdpZnkoeyB0eXBlOiAndW5kZWZpbmVkJywgdmFsdWU6ICcnIH0pKTtcbiAgICB9IGVsc2UgaWYgKHJlc3VsdCA9PT0gbnVsbCkge1xuICAgICAgYXdhaXQgZnNQcm9taXNlcy53cml0ZUZpbGUocGFyYW1zLnJlc3VsdFBhdGgsIEpTT04uc3RyaW5naWZ5KHsgdHlwZTogJ251bGwnLCB2YWx1ZTogJycgfSkpO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWJhc2UtdG8tc3RyaW5nIC0tIEludGVudGlvbmFsOiByZXN1bHQgaXMgYW4gYXJiaXRyYXJ5IHZhbHVlIGZyb20gdGhlIGV2YWx1YXRlZCBleHByZXNzaW9uLlxuICAgICAgYXdhaXQgZnNQcm9taXNlcy53cml0ZUZpbGUocGFyYW1zLnJlc3VsdFBhdGgsIEpTT04uc3RyaW5naWZ5KHsgdmFsdWU6IFN0cmluZyhyZXN1bHQpIH0pKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGF3YWl0IGZzUHJvbWlzZXMud3JpdGVGaWxlKHBhcmFtcy5yZXN1bHRQYXRoLCBKU09OLnN0cmluZ2lmeSh7IHR5cGU6ICdlcnJvcicsIHZhbHVlOiBwYXJhbXMuc2VyaWFsaXplRXJyb3IoZXJyKSB9KSk7XG4gIH1cbn1cblxuLyoqXG4gKiBCdWlsZHMgYSBkZXRhaWxlZCBkaWFnbm9zdGljIHN0cmluZyBmb3IgdGhlIFwiU2NyaXB0IGRpZCBub3QgZXhlY3V0ZVwiIGVycm9yLlxuICpcbiAqIFBlcmZvcm1zIGxpdmUgZW52aXJvbm1lbnQgY2hlY2tzIChwcm9jZXNzIGxpc3QsIHZhdWx0IHJlZ2lzdHJ5LCBDTEkgY29uZmlnKVxuICogYW5kIGluY2x1ZGVzIHRoZSBjb21tYW5kLCBleGVjIG91dHB1dCwgYW5kIHNjcmlwdCBjb250ZW50LlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZGlhZ25vc3RpYyBwYXJhbWV0ZXJzLlxuICogQHJldHVybnMgQSBmb3JtYXR0ZWQgZGlhZ25vc3RpYyBzdHJpbmcuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGJ1aWxkRGlhZ25vc3RpY3MocGFyYW1zOiBCdWlsZERpYWdub3N0aWNzUGFyYW1zKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgbGluZXM6IHN0cmluZ1tdID0gW107XG5cbiAgbGluZXMucHVzaChgICBDb21tYW5kOiAke3BhcmFtcy5jb21tYW5kLmpvaW4oJyAnKX1gKTtcbiAgbGluZXMucHVzaChgICBXb3JraW5nIGRpcmVjdG9yeTogJHtwYXJhbXMuY3dkfWApO1xuICBsaW5lcy5wdXNoKGAgIFNjcmlwdCBwYXRoOiAke3BhcmFtcy5zY3JpcHRQYXRofWApO1xuICBsaW5lcy5wdXNoKGAgIEV4cGVjdGVkIHJlc3VsdCBmaWxlOiAke3BhcmFtcy5yZXN1bHRQYXRofWApO1xuXG4gIGlmIChwYXJhbXMuZXhlY1Jlc3VsdCkge1xuICAgIGxpbmVzLnB1c2goYCAgRXhpdCBjb2RlOiAke3BhcmFtcy5leGVjUmVzdWx0LmV4aXRDb2RlID09PSBudWxsID8gJyhudWxsIFx1MjAxNCBwcm9jZXNzIGRpZCBub3QgZXhpdCBub3JtYWxseSknIDogU3RyaW5nKHBhcmFtcy5leGVjUmVzdWx0LmV4aXRDb2RlKX1gKTtcbiAgICBpZiAocGFyYW1zLmV4ZWNSZXN1bHQuZXhpdFNpZ25hbCkge1xuICAgICAgbGluZXMucHVzaChgICBFeGl0IHNpZ25hbDogJHtwYXJhbXMuZXhlY1Jlc3VsdC5leGl0U2lnbmFsfWApO1xuICAgIH1cbiAgICBpZiAocGFyYW1zLmV4ZWNSZXN1bHQuc3Rkb3V0LnRyaW0oKSkge1xuICAgICAgbGluZXMucHVzaChgICBzdGRvdXQ6ICR7cGFyYW1zLmV4ZWNSZXN1bHQuc3Rkb3V0LnRyaW0oKX1gKTtcbiAgICB9XG4gICAgaWYgKHBhcmFtcy5leGVjUmVzdWx0LnN0ZGVyci50cmltKCkpIHtcbiAgICAgIGxpbmVzLnB1c2goYCAgc3RkZXJyOiAke3BhcmFtcy5leGVjUmVzdWx0LnN0ZGVyci50cmltKCl9YCk7XG4gICAgfVxuICAgIGlmICghcGFyYW1zLmV4ZWNSZXN1bHQuc3Rkb3V0LnRyaW0oKSAmJiAhcGFyYW1zLmV4ZWNSZXN1bHQuc3RkZXJyLnRyaW0oKSkge1xuICAgICAgbGluZXMucHVzaCgnICBzdGRvdXQvc3RkZXJyOiAoZW1wdHkpJyk7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIGxpbmVzLnB1c2goJyAgZXhlYyByZXN1bHQ6IChub3QgYXZhaWxhYmxlIFx1MjAxNCBPYnNpZGlhbiB3YXMgYXV0by1zdGFydGVkIGFuZCByZXRyaWVkKScpO1xuICB9XG5cbiAgbGluZXMucHVzaCgnRW52aXJvbm1lbnQgY2hlY2tzOicpO1xuICBsaW5lcy5wdXNoKGAgIE9ic2lkaWFuIHJ1bm5pbmc6ICR7YXdhaXQgaXNPYnNpZGlhblJ1bm5pbmcoKSA/ICd5ZXMnIDogJ05PJ31gKTtcbiAgbGluZXMucHVzaChgICBDTEkgZW5hYmxlZCBpbiBvYnNpZGlhbi5qc29uOiAke2lzQ2xpRW5hYmxlZCgpID8gJ3llcycgOiAnTk8nfWApO1xuICBsaW5lcy5wdXNoKGAgIFZhdWx0IHJlZ2lzdGVyZWQ6ICR7aXNWYXVsdFJlZ2lzdGVyZWQocGFyYW1zLmN3ZCkgPyAneWVzJyA6ICdOTyd9YCk7XG4gIGxpbmVzLnB1c2goYCAgU2NyaXB0IGZpbGUgZXhpc3RzOiAke2V4aXN0c1N5bmMocGFyYW1zLnNjcmlwdFBhdGgpID8gJ3llcycgOiAnbm8gKGFscmVhZHkgY2xlYW5lZCB1cCknfWApO1xuICBsaW5lcy5wdXNoKGAgIFJlc3VsdCBmaWxlIGV4aXN0czogJHtleGlzdHNTeW5jKHBhcmFtcy5yZXN1bHRQYXRoKSA/ICd5ZXMnIDogJ25vJ31gKTtcblxuICBjb25zdCByZWdpc3RlcmVkVmF1bHRzID0gZ2V0UmVnaXN0ZXJlZFZhdWx0cygpO1xuICBpZiAocmVnaXN0ZXJlZFZhdWx0cy5sZW5ndGggPT09IDApIHtcbiAgICBsaW5lcy5wdXNoKCcgIFJlZ2lzdGVyZWQgdmF1bHRzOiAobm9uZSknKTtcbiAgfSBlbHNlIHtcbiAgICBsaW5lcy5wdXNoKCcgIFJlZ2lzdGVyZWQgdmF1bHRzOicpO1xuICAgIGZvciAoY29uc3QgdmF1bHQgb2YgcmVnaXN0ZXJlZFZhdWx0cykge1xuICAgICAgbGluZXMucHVzaChgICAgIC0gJHt2YXVsdC5wYXRofSAob3BlbjogJHtTdHJpbmcodmF1bHQub3Blbil9KWApO1xuICAgIH1cbiAgfVxuXG4gIGxpbmVzLnB1c2goYCAgU2NyaXB0IGNvbnRlbnQ6XFxuJHtwYXJhbXMuc2NyaXB0Q29udGVudH1gKTtcblxuICByZXR1cm4gbGluZXMuam9pbignXFxuJyk7XG59XG5cbi8qKlxuICogQnVpbGRzIHRoZSBjb250ZW50IG9mIGEgdGVtcG9yYXJ5IHNjcmlwdCBmaWxlLlxuICpcbiAqIFNlcmlhbGl6ZXMge0BsaW5rIGludm9rZUFuZFdyaXRlUmVzdWx0fSBpbnRvIGEgc2VsZi1jb250YWluZWQgSUlGRSB0aGF0XG4gKiBldmFsdWF0ZXMgdGhlIGV4cHJlc3Npb24sIHdyaXRlcyB0aGUgcmVzdWx0IChvciBlcnJvcikgdG8gYHJlc3VsdFBhdGhgLlxuICpcbiAqIEBwYXJhbSBleHByZXNzaW9uIC0gVGhlIEphdmFTY3JpcHQgZXhwcmVzc2lvbiB0byBldmFsdWF0ZS5cbiAqIEBwYXJhbSByZXN1bHRQYXRoIC0gVGhlIGFic29sdXRlIHBhdGggdG8gdGhlIHJlc3VsdCBmaWxlLlxuICogQHJldHVybnMgVGhlIHNjcmlwdCBmaWxlIGNvbnRlbnQuXG4gKi9cbmZ1bmN0aW9uIGJ1aWxkU2NyaXB0RmlsZShleHByZXNzaW9uOiBzdHJpbmcsIHJlc3VsdFBhdGg6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IGZuU3RyID0gZ2V0RnVuY3Rpb25FeHByZXNzaW9uU3RyaW5nKGludm9rZUFuZFdyaXRlUmVzdWx0KTtcbiAgY29uc3Qgc2VyaWFsaXplRXJyb3JTdHIgPSBnZXRGdW5jdGlvbkV4cHJlc3Npb25TdHJpbmcoc2VyaWFsaXplRXJyb3IpO1xuICBjb25zdCByZXN1bHRQYXRoSnNvbiA9IEpTT04uc3RyaW5naWZ5KHJlc3VsdFBhdGgucmVwbGFjZSgvXFxcXC9nLCAnLycpKTtcbiAgcmV0dXJuIGAoJHtmblN0cn0pKHsgZXZhbHVhdGU6IGFzeW5jICgpID0+ICgke2V4cHJlc3Npb259KSwgcmVzdWx0UGF0aDogJHtyZXN1bHRQYXRoSnNvbn0sIHNlcmlhbGl6ZUVycm9yOiAke3NlcmlhbGl6ZUVycm9yU3RyfSB9KWA7XG59XG5cbi8qKlxuICogUmV0dXJucyBhIHByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgZ2l2ZW4gZGVsYXkuXG4gKlxuICogQHBhcmFtIG1zIC0gVGhlIGRlbGF5IGluIG1pbGxpc2Vjb25kcy5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIGFmdGVyIHRoZSBkZWxheS5cbiAqL1xuZnVuY3Rpb24gZGVsYXkobXM6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHtcbiAgICBzZXRUaW1lb3V0KHJlc29sdmUsIG1zKTtcbiAgfSk7XG59XG5cbi8qKlxuICogUmVhZHMgYSBzY3JpcHQgZmlsZSBmcm9tIGRpc2ssIGxvZ3MgaXRzIGNvbnRlbnQsIGFuZCBleGVjdXRlcyBpdCB2aWEgYG5ldyBGdW5jdGlvbmAuXG4gKlxuICogU2VyaWFsaXplZCB2aWEgYHRvU3RyaW5nKClgIGFuZCBleGVjdXRlZCBpbnNpZGUgdGhlIE9ic2lkaWFuIHByb2Nlc3MuXG4gKiBNdXN0IE5PVCByZWZlcmVuY2UgYW55IG91dGVyIHNjb3BlLlxuICpcbiAqIEBwYXJhbSBzY3JpcHRQYXRoIC0gVGhlIGFic29sdXRlIHBhdGggdG8gdGhlIHNjcmlwdCBmaWxlLlxuICovXG5hc3luYyBmdW5jdGlvbiBleGVjdXRlU2NyaXB0RmlsZShzY3JpcHRQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1kZXByZWNhdGVkIC0tIHdpbmRvdy5hcHAgaXMgc2V0IGJ5IE9ic2lkaWFuIGF0IHJ1bnRpbWUuXG4gIGNvbnN0IGZzUHJvbWlzZXMgPSBSZWZsZWN0LmdldCh3aW5kb3cuYXBwLnZhdWx0LmFkYXB0ZXIsICdmc1Byb21pc2VzJykgYXMgRXhlY3V0ZVNjcmlwdEZpbGVGc1Byb21pc2VzO1xuXG4gIHRyeSB7XG4gICAgYXdhaXQgZnNQcm9taXNlcy5hY2Nlc3Moc2NyaXB0UGF0aCk7XG4gIH0gY2F0Y2ggKGNhdXNlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBTY3JpcHQgZmlsZSBub3QgZm91bmQ6ICR7c2NyaXB0UGF0aH1gLCB7IGNhdXNlIH0pO1xuICB9XG5cbiAgY29uc3Qgc2NyaXB0ID0gYXdhaXQgZnNQcm9taXNlcy5yZWFkRmlsZShzY3JpcHRQYXRoLCAndXRmLTgnKTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGUgLS0gRGlhZ25vc3RpYyBsb2dnaW5nIGluc2lkZSBPYnNpZGlhbiBwcm9jZXNzOyBubyBsb2dnZXIgYXZhaWxhYmxlLlxuICBjb25zb2xlLmRlYnVnKGBFeGVjdXRpbmcgJHtzY3JpcHRQYXRofTpcXG4ke3NjcmlwdH1gKTtcblxuICBsZXQgZm46ICgpID0+IFByb21pc2U8dm9pZD47XG4gIHRyeSB7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1pbXBsaWVkLWV2YWwsIG5vLW5ldy1mdW5jIC0tIFRoZSBzY3JpcHQgaXMgYSB0cnVzdGVkLCBzZWxmLWdlbmVyYXRlZCBJSUZFIGV4cHJlc3Npb24uXG4gICAgZm4gPSBuZXcgRnVuY3Rpb24oYHJldHVybiAke3NjcmlwdH1gKSBhcyAoKSA9PiBQcm9taXNlPHZvaWQ+O1xuICB9IGNhdGNoIChjYXVzZSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgRXJyb3IgcGFyc2luZyAke3NjcmlwdFBhdGh9YCwgeyBjYXVzZSB9KTtcbiAgfVxuXG4gIHRyeSB7XG4gICAgYXdhaXQgZm4oKTtcbiAgfSBjYXRjaCAoY2F1c2UpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEVycm9yIGV4ZWN1dGluZyAke3NjcmlwdFBhdGh9YCwgeyBjYXVzZSB9KTtcbiAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIHBsYXRmb3JtLXNwZWNpZmljIGNvbW1hbmQgdG8ga2lsbCB0aGUgT2JzaWRpYW4gcHJvY2Vzcy5cbiAqXG4gKiBAcmV0dXJucyBUaGUgc2hlbGwgY29tbWFuZCBzdHJpbmcuXG4gKi9cbmZ1bmN0aW9uIGdldEtpbGxPYnNpZGlhbkNvbW1hbmQoKTogc3RyaW5nIHtcbiAgaWYgKHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicpIHtcbiAgICByZXR1cm4gJ3Rhc2traWxsIC9JTSBPYnNpZGlhbi5leGUgL0YnO1xuICB9XG5cbiAgcmV0dXJuICdwa2lsbCAtZiBPYnNpZGlhbic7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgcGxhdGZvcm0tc3BlY2lmaWMgY29tbWFuZCB0byBsYXVuY2ggdGhlIE9ic2lkaWFuIGJpbmFyeS5cbiAqXG4gKiBSZXNvbHZlcyB0aGUgYWN0dWFsIGluc3RhbGxlZCBPYnNpZGlhbiBleGVjdXRhYmxlIChjb3ZlcmluZyBpbnN0YWxsZXItYmFzZWRcbiAqIGFuZCBgUEFUSGAtYmFzZWQgaW5zdGFsbHMgc3VjaCBhcyBgc2Nvb3BgKSBhbmQgdmVyaWZpZXMgaXQgZXhpc3RzIGJlZm9yZVxuICogcmV0dXJuaW5nIGEgY29tbWFuZC4gVGhyb3dzIGlmIE9ic2lkaWFuIGlzIG5vdCBpbnN0YWxsZWQuXG4gKlxuICogQHJldHVybnMgVGhlIHNoZWxsIGNvbW1hbmQgc3RyaW5nLlxuICogQHRocm93cyBFcnJvciBpZiBPYnNpZGlhbiBjYW5ub3QgYmUgbG9jYXRlZC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZ2V0T2JzaWRpYW5MYXVuY2hDb21tYW5kKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gIGNvbnN0IGV4ZVBhdGggPSBhd2FpdCByZXNvbHZlT2JzaWRpYW5FeGVjdXRhYmxlKCk7XG5cbiAgaWYgKHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicpIHtcbiAgICByZXR1cm4gYHN0YXJ0IFwiXCIgXCIke2V4ZVBhdGh9XCJgO1xuICB9XG5cbiAgcmV0dXJuIGBcIiR7ZXhlUGF0aH1cIiAmYDtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBwbGF0Zm9ybS1zcGVjaWZpYyBjb21tYW5kIHRvIG9wZW4gYSBVUkkuXG4gKlxuICogQHBhcmFtIHVyaSAtIFRoZSBVUkkgdG8gb3Blbi5cbiAqIEByZXR1cm5zIFRoZSBzaGVsbCBjb21tYW5kIHN0cmluZy5cbiAqL1xuZnVuY3Rpb24gZ2V0T3BlblVyaUNvbW1hbmQodXJpOiBzdHJpbmcpOiBzdHJpbmcge1xuICBpZiAocHJvY2Vzcy5wbGF0Zm9ybSA9PT0gJ3dpbjMyJykge1xuICAgIHJldHVybiBgc3RhcnQgXCJcIiBcIiR7dXJpfVwiYDtcbiAgfVxuXG4gIGlmIChwcm9jZXNzLnBsYXRmb3JtID09PSAnZGFyd2luJykge1xuICAgIHJldHVybiBgb3BlbiBcIiR7dXJpfVwiYDtcbiAgfVxuXG4gIHJldHVybiBgeGRnLW9wZW4gXCIke3VyaX1cImA7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgdGhlIE9ic2lkaWFuIHByb2Nlc3MgaXMgY3VycmVudGx5IHJ1bm5pbmcuXG4gKlxuICogQHJldHVybnMgYHRydWVgIGlmIHRoZSBPYnNpZGlhbiBwcm9jZXNzIGlzIGZvdW5kLCBgZmFsc2VgIG90aGVyd2lzZS5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNPYnNpZGlhblJ1bm5pbmcoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IGNvbW1hbmQgPSBwcm9jZXNzLnBsYXRmb3JtID09PSAnd2luMzInXG4gICAgPyAndGFza2xpc3QgL0ZJIFwiSU1BR0VOQU1FIGVxIE9ic2lkaWFuLmV4ZVwiIC9OSCdcbiAgICA6ICdwZ3JlcCAtZiBPYnNpZGlhbic7XG4gIHRyeSB7XG4gICAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgZXhlYyhjb21tYW5kLCB7IGlzUXVpZXQ6IHRydWUgfSk7XG4gICAgaWYgKHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicpIHtcbiAgICAgIHJldHVybiBvdXRwdXQuaW5jbHVkZXMoJ09ic2lkaWFuLmV4ZScpO1xuICAgIH1cbiAgICByZXR1cm4gb3V0cHV0LnRyaW0oKS5sZW5ndGggPiAwO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqXG4gKiBPcGVucyBhIHZhdWx0IGluIE9ic2lkaWFuIHVzaW5nIHRoZSBgb2JzaWRpYW46Ly9vcGVuYCBVUkkgcHJvdG9jb2wuXG4gKlxuICogVGhpcyB3b3JrcyB3aGV0aGVyIE9ic2lkaWFuIGlzIGFscmVhZHkgcnVubmluZyAoc2lnbmFscyBpdCB0byBvcGVuIHRoZSB2YXVsdClcbiAqIG9yIG5vdCAobGF1bmNoZXMgT2JzaWRpYW4gd2l0aCB0aGUgdmF1bHQpLiBUaGUgVVJJIHByb3RvY29sIGlzIGhhbmRsZWQgYnkgdGhlXG4gKiBPUywgd2hpY2ggcm91dGVzIGl0IHRvIHRoZSByZWdpc3RlcmVkIE9ic2lkaWFuIGhhbmRsZXIuXG4gKlxuICogQHBhcmFtIHZhdWx0UGF0aCAtIFRoZSBhYnNvbHV0ZSBwYXRoIHRvIHRoZSB2YXVsdCBmb2xkZXIuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIG9wZW5WYXVsdFZpYVVyaSh2YXVsdFBhdGg6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCB2YXVsdElkID0gZ2V0VmF1bHRJZCh2YXVsdFBhdGgpO1xuICBpZiAoIXZhdWx0SWQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBvcGVuIHZhdWx0IHZpYSBVUkk6IHZhdWx0IGlzIG5vdCByZWdpc3RlcmVkIGluIG9ic2lkaWFuLmpzb246ICR7dmF1bHRQYXRofWApO1xuICB9XG4gIGNvbnN0IHVyaSA9IGBvYnNpZGlhbjovL29wZW4/dmF1bHQ9JHtlbmNvZGVVUklDb21wb25lbnQodmF1bHRJZCl9YDtcbiAgY29uc3QgY29tbWFuZCA9IGdldE9wZW5VcmlDb21tYW5kKHVyaSk7XG4gIGxvZyhgW2NsaS10cmFuc3BvcnRdIE9wZW5pbmcgdmF1bHQgdmlhIFVSSTogJHt1cml9YCk7XG4gIHRyeSB7XG4gICAgYXdhaXQgZXhlYyhjb21tYW5kLCB7IGlzUXVpZXQ6IHRydWUgfSk7XG4gIH0gY2F0Y2ggKGVycm9yOiB1bmtub3duKSB7XG4gICAgbG9nKGBbY2xpLXRyYW5zcG9ydF0gVVJJIG9wZW4gZmFpbGVkIChub24tZmF0YWwpOiAke3NlcmlhbGl6ZUVycm9yKGVycm9yKX1gKTtcbiAgfVxufVxuXG4vKiB2OCBpZ25vcmUgc3RvcCAqL1xuIl0sCiAgIm1hcHBpbmdzIjogIkFBU0EsU0FBUyxrQkFBa0I7QUFDM0I7QUFBQSxFQUNFO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMsY0FBYztBQUN2QixTQUFTLFlBQVk7QUFDckIsT0FBTyxhQUFhO0FBUXBCLFNBQVMsaUNBQWlDO0FBQzFDLFNBQVMsWUFBWTtBQUNyQixTQUFTLG1DQUFtQztBQUM1QyxTQUFTLFdBQVc7QUFDcEIsU0FBUyxtQ0FBbUM7QUFDNUMsU0FBUyxZQUFZO0FBQ3JCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMsaUNBQWlDO0FBQzFDLFNBQVMsc0JBQXNCO0FBc0MvQixNQUFNLDBCQUEwQjtBQUNoQyxNQUFNLDJDQUEyQztBQUNqRCxNQUFNLHFDQUFxQztBQUMzQyxNQUFNLHNDQUFzQztBQUM1QyxNQUFNLHFDQUFxQztBQUMzQyxNQUFNLG9DQUFvQztBQUMxQyxNQUFNLHFDQUFxQztBQWVwQyxNQUFNLG9CQUFpRDtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBSTVDLFdBQVc7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQWlCM0IsTUFBYSxTQUFTLFlBQW9CLFNBQWdEO0FBQ3hGLFVBQU0sY0FBYztBQUNwQixVQUFNLFdBQVcsT0FBTyxLQUFLLE9BQU8sQ0FBQyxFQUFFLE1BQU0sV0FBVztBQUN4RCxVQUFNLFlBQVksS0FBSyxPQUFPLEdBQUcsOEJBQThCO0FBQy9ELFVBQU0sYUFBYSxLQUFLLFdBQVcsR0FBRyxRQUFRLEtBQUs7QUFDbkQsVUFBTSxhQUFhLEtBQUssV0FBVyxHQUFHLFFBQVEsY0FBYztBQUU1RCxVQUFNLE1BQU0sV0FBVyxFQUFFLFdBQVcsS0FBSyxDQUFDO0FBRTFDLFVBQU0sZ0JBQWdCLGdCQUFnQixZQUFZLFVBQVU7QUFDNUQsVUFBTSxVQUFVLFlBQVksYUFBYTtBQUV6QyxRQUFJO0FBQ0YsWUFBTSxpQkFBaUIsV0FBVyxRQUFRLE9BQU8sR0FBRztBQUNwRCxZQUFNLFVBQVUsSUFBSSw0QkFBNEIsaUJBQWlCLENBQUMsS0FBSyxLQUFLLFVBQVUsY0FBYyxDQUFDO0FBQ3JHLFlBQU0sVUFBVSxXQUFXLFFBQVEsR0FBRztBQUN0QyxZQUFNLFVBQVUsQ0FBQyxZQUFZLEdBQUksVUFBVSxDQUFDLFNBQVMsT0FBTyxFQUFFLElBQUksQ0FBQyxHQUFJLFFBQVEsdUJBQXVCLFFBQVEsT0FBTyxFQUFFO0FBRXZILFVBQUk7QUFDSixVQUFJO0FBQ0YscUJBQWEsTUFBTSxLQUFLLFNBQVM7QUFBQSxVQUMvQixLQUFLLFFBQVE7QUFBQSxVQUNiLFNBQVM7QUFBQSxVQUNULHNCQUFzQjtBQUFBLFVBQ3RCLEdBQUksUUFBUSwwQkFBMEIsVUFBYSxFQUFFLHVCQUF1QixRQUFRLHNCQUFzQjtBQUFBLFFBQzVHLENBQUM7QUFBQSxNQUNILFNBQVMsT0FBTztBQUNkLGNBQU0sVUFBVSxpQkFBaUIsUUFBUSxNQUFNLFVBQVUsT0FBTyxLQUFLO0FBQ3JFLFlBQUksUUFBUSxTQUFTLHVCQUF1QixHQUFHO0FBQzdDLGdCQUFNLEtBQUssOEJBQThCLFNBQVMsUUFBUSxHQUFHO0FBQUEsUUFDL0QsT0FBTztBQUNMLGdCQUFNO0FBQUEsUUFDUjtBQUFBLE1BQ0Y7QUFFQSxVQUFJLENBQUMsV0FBVyxVQUFVLEdBQUc7QUFDM0IsY0FBTSxjQUFjLE1BQU0saUJBQWlCO0FBQUEsVUFDekM7QUFBQSxVQUNBLEtBQUssUUFBUTtBQUFBLFVBQ2I7QUFBQSxVQUNBO0FBQUEsVUFDQTtBQUFBLFVBQ0E7QUFBQSxRQUNGLENBQUM7QUFDRCxjQUFNLElBQUksTUFBTSxvQ0FBb0MsUUFBUSxHQUFHO0FBQUEsRUFBSyxXQUFXLEVBQUU7QUFBQSxNQUNuRjtBQUVBLFlBQU0sWUFBWSxNQUFNLFNBQVMsWUFBWSxPQUFPO0FBQ3BELFlBQU0sV0FBVyxLQUFLLE1BQU0sU0FBUztBQUVyQyxVQUFJLFVBQVUsVUFBVTtBQUN0QixZQUFJLFNBQVMsU0FBUyxTQUFTO0FBQzdCLGdCQUFNLElBQUksTUFBTSxzQ0FBc0MsUUFBUSxHQUFHLEtBQUssU0FBUyxLQUFLLEVBQUU7QUFBQSxRQUN4RjtBQUNBLGVBQU87QUFBQSxNQUNUO0FBRUEsYUFBTyxTQUFTO0FBQUEsSUFDbEIsVUFBRTtBQUNBLFlBQU0sT0FBTyxVQUFVLEVBQUUsTUFBTSxJQUFJO0FBQ25DLFlBQU0sT0FBTyxVQUFVLEVBQUUsTUFBTSxJQUFJO0FBQUEsSUFDckM7QUFBQSxFQUNGO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFXQSxNQUFhLGVBQWUsV0FBa0M7QUFDNUQsUUFBSSxzREFBc0QsU0FBUyxFQUFFO0FBQ3JFLFFBQUksQ0FBQyxrQkFBa0IsU0FBUyxHQUFHO0FBQ2pDLFlBQU0sSUFBSTtBQUFBLFFBQ1Isd0NBQXdDLFNBQVM7QUFBQSxNQUNuRDtBQUFBLElBQ0Y7QUFFQSxRQUFJLENBQUMsYUFBYSxHQUFHO0FBQ25CLFVBQUksdUZBQXVGO0FBQzNGLHdCQUFrQjtBQUNsQixZQUFNLEtBQUssZ0JBQWdCLFNBQVM7QUFBQSxJQUN0QztBQUVBLFVBQU0sS0FBSyxtQkFBbUI7QUFFOUIsUUFBSSxDQUFDLFlBQVksU0FBUyxHQUFHO0FBQzNCLFVBQUkscURBQXFELFNBQVMsc0JBQXNCO0FBQ3hGLFlBQU0sZ0JBQWdCLFNBQVM7QUFDL0IsWUFBTSxLQUFLLGVBQWUsU0FBUztBQUFBLElBQ3JDO0FBRUEsUUFBSSx5Q0FBeUM7QUFBQSxFQUMvQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVVBLE1BQWEsY0FBYyxXQUFrQztBQUMzRCxRQUFJLHNDQUFzQyxTQUFTLEVBQUU7QUFDckQsVUFBTSxnQkFBZ0IsTUFBTSxLQUFLLDJCQUEyQjtBQUU1RCxRQUFJLGVBQWU7QUFDakIsWUFBTSw0QkFBNEIsTUFBTSxhQUFhO0FBQ3JELFlBQU0sZUFBZSxtREFBbUQsS0FBSyxVQUFVLEVBQUUsTUFBTSxDQUFDLFdBQVcsS0FBSyxHQUFHLFNBQVMsYUFBYSxDQUFDLENBQUM7QUFDM0ksWUFBTSxLQUFLLFNBQVMsY0FBYyxFQUFFLEtBQUssY0FBYyxDQUFDO0FBQ3hELFlBQU0sS0FBSyw0QkFBNEIsV0FBVyxhQUFhO0FBQUEsSUFDakUsT0FBTztBQUNMLFVBQUksMkZBQTJGO0FBQy9GLDRCQUFzQixTQUFTO0FBUy9CLFVBQUksTUFBTSxrQkFBa0IsR0FBRztBQUM3QixZQUFJLG1IQUE4RztBQUNsSCxjQUFNLEtBQUssYUFBYTtBQUFBLE1BQzFCO0FBQ0EsVUFBSSx3RUFBd0U7QUFDNUUsWUFBTSxnQkFBZ0IsU0FBUztBQUMvQixZQUFNLEtBQUssZUFBZSxTQUFTO0FBQ25DLFlBQU0sS0FBSyw0QkFBNEIsV0FBVyxTQUFTO0FBQUEsSUFDN0Q7QUFFQSxVQUFNLEtBQUssZUFBZSxTQUFTO0FBQ25DLFVBQU0sS0FBSyxtQkFBbUIsU0FBUztBQUFBLEVBQ3pDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBVUEsTUFBYSxnQkFBZ0IsV0FBa0M7QUFDN0QsUUFBSSwyREFBMkQsU0FBUyxLQUFLO0FBQzdFLFFBQUksWUFBWSxTQUFTLEdBQUc7QUFDMUIsVUFBSTtBQUNGLGNBQU0sNEJBQTRCLE1BQU0sU0FBUztBQUNqRCxjQUFNLGNBQWM7QUFDcEIsY0FBTSxLQUFLLFNBQVMsYUFBYSxFQUFFLEtBQUssV0FBVyx1QkFBdUIsbUNBQW1DLENBQUM7QUFDOUcsWUFBSSw4Q0FBOEM7QUFBQSxNQUNwRCxTQUFTLE9BQWdCO0FBQ3ZCLFlBQUksc0RBQXNELGVBQWUsS0FBSyxDQUFDLEVBQUU7QUFBQSxNQUNuRjtBQUVBLFVBQUksZ0RBQWdEO0FBQ3BELFlBQU0sTUFBTSxpQ0FBaUM7QUFBQSxJQUMvQyxPQUFPO0FBQ0wsVUFBSSxvSEFBK0c7QUFBQSxJQUNySDtBQUVBLFFBQUksaURBQWlEO0FBQ3JELFVBQU0sdUJBQXVCLE1BQU0sS0FBSywyQkFBMkI7QUFDbkUsUUFBSTtBQUNGLFVBQUksc0JBQXNCO0FBQ3hCLGNBQU0sNEJBQTRCLE1BQU0sb0JBQW9CO0FBQzVELGNBQU0sYUFBYSxtREFBbUQsS0FBSyxVQUFVLEVBQUUsTUFBTSxDQUFDLFNBQVMsR0FBRyxTQUFTLGVBQWUsQ0FBQyxDQUFDO0FBQ3BJLGNBQU0sS0FBSyxTQUFTLFlBQVksRUFBRSxLQUFLLHNCQUFzQix1QkFBdUIsbUNBQW1DLENBQUM7QUFBQSxNQUMxSCxPQUFPO0FBQ0wsWUFBSSxzR0FBaUc7QUFDckcsOEJBQXNCLFNBQVM7QUFBQSxNQUNqQztBQUNBLFVBQUksOENBQThDO0FBQUEsSUFDcEQsU0FBUyxPQUFnQjtBQUN2QixVQUFJLDhEQUE4RCxlQUFlLEtBQUssQ0FBQyxFQUFFO0FBQUEsSUFDM0Y7QUFBQSxFQUNGO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxNQUFjLHFCQUFvQztBQUNoRCxVQUFNLFVBQVUsUUFBUSxhQUFhLFVBQVUsdUJBQXVCO0FBQ3RFLFFBQUk7QUFDRixZQUFNLEtBQUssU0FBUyxFQUFFLFNBQVMsS0FBSyxDQUFDO0FBQUEsSUFDdkMsUUFBUTtBQUNOLFlBQU0sSUFBSSxNQUFNLDBGQUEwRjtBQUFBLElBQzVHO0FBQUEsRUFDRjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBaUJBLE1BQWMsbUJBQW1CLFdBQWtDO0FBQ2pFLFVBQU0sNEJBQTRCLE1BQU0sU0FBUztBQUNqRCxVQUFNLFNBQVMsTUFBTSxLQUFLLFNBQVMsMkJBQTJCLEVBQUUsS0FBSyxVQUFVLENBQUM7QUFDaEYsUUFBSSxXQUFXLFFBQVE7QUFDckIsVUFBSSw2REFBNkQ7QUFBQSxJQUNuRTtBQUFBLEVBQ0Y7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBZUEsTUFBYyw0QkFBNEIsV0FBbUIscUJBQTRDO0FBQ3ZHLFVBQU0sVUFBVSxXQUFXLFNBQVM7QUFDcEMsUUFBSSxDQUFDLFNBQVM7QUFDWixVQUFJLGtGQUE2RTtBQUNqRjtBQUFBLElBQ0Y7QUFFQSxVQUFNLDRCQUE0QixNQUFNLG1CQUFtQjtBQUMzRCxVQUFNLGFBQWEsMkRBQTJELEtBQUssVUFBVSxFQUFFLEtBQUssaUJBQWlCLE9BQU8sSUFBSSxPQUFPLE9BQU8sQ0FBQyxDQUFDO0FBQ2hKLFVBQU0sS0FBSyxTQUFTLFlBQVksRUFBRSxLQUFLLG9CQUFvQixDQUFDO0FBQzVELFFBQUkscUNBQXFDLE9BQU8sbUJBQW1CO0FBQUEsRUFDckU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVFBLE1BQWMsOEJBQThCLFNBQW1CLEtBQTRCO0FBQ3pGLFFBQUksK0RBQStEO0FBRW5FLFVBQU0sZ0JBQWdCLE1BQU0seUJBQXlCO0FBQ3JELFFBQUk7QUFDRixZQUFNLEtBQUssZUFBZSxFQUFFLFNBQVMsS0FBSyxDQUFDO0FBQUEsSUFDN0MsUUFBUTtBQUFBLElBRVI7QUFFQSxRQUFJLHFEQUFxRCxPQUFPLGtDQUFrQyxDQUFDLFFBQVE7QUFDM0csVUFBTSxXQUFXLEtBQUssSUFBSSxJQUFJO0FBQzlCLFdBQU8sS0FBSyxJQUFJLElBQUksVUFBVTtBQUM1QixZQUFNLE1BQU0sd0NBQXdDO0FBQ3BELFVBQUk7QUFDRixjQUFNLEtBQUssU0FBUyxFQUFFLEtBQUssU0FBUyxLQUFLLENBQUM7QUFDMUMsWUFBSSx5Q0FBeUM7QUFDN0M7QUFBQSxNQUNGLFNBQVMsT0FBTztBQUNkLGNBQU0sVUFBVSxpQkFBaUIsUUFBUSxNQUFNLFVBQVUsT0FBTyxLQUFLO0FBQ3JFLFlBQUksQ0FBQyxRQUFRLFNBQVMsdUJBQXVCLEdBQUc7QUFDOUMsZ0JBQU07QUFBQSxRQUNSO0FBQ0EsWUFBSSxxREFBcUQ7QUFBQSxNQUMzRDtBQUFBLElBQ0Y7QUFFQSxVQUFNLElBQUksTUFBTSxpQ0FBaUMsT0FBTyxrQ0FBa0MsQ0FBQyxLQUFLO0FBQUEsRUFDbEc7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBZUEsTUFBYyw2QkFBMEQ7QUFDdEUsUUFBSSxDQUFDLE1BQU0sa0JBQWtCLEdBQUc7QUFDOUIsYUFBTztBQUFBLElBQ1Q7QUFFQSxXQUFPLG9CQUFvQjtBQUFBLEVBQzdCO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFLQSxNQUFjLGVBQThCO0FBQzFDLFFBQUksNkNBQTZDO0FBQ2pELFFBQUk7QUFDRixZQUFNLEtBQUssdUJBQXVCLEdBQUcsRUFBRSxTQUFTLEtBQUssQ0FBQztBQUFBLElBQ3hELFFBQVE7QUFBQSxJQUVSO0FBRUEsVUFBTSxNQUFNLGlDQUFpQztBQUFBLEVBQy9DO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVlBLE1BQWMsZUFBZSxXQUFrQztBQUM3RCxVQUFNLFdBQVc7QUFFakIsUUFBSSx3REFBd0QsT0FBTyxrQ0FBa0MsQ0FBQyxRQUFRO0FBQzlHLFVBQU0sV0FBVyxLQUFLLElBQUksSUFBSTtBQUM5QixRQUFJLGlCQUFpQjtBQUNyQixXQUFPLEtBQUssSUFBSSxJQUFJLFVBQVU7QUFDNUIsVUFBSTtBQUNGLFlBQUksQ0FBQyxnQkFBZ0I7QUFDbkIsZ0JBQU0sNEJBQTRCLE1BQU0sV0FBVyxrQ0FBa0M7QUFDckYsMkJBQWlCO0FBQUEsUUFDbkI7QUFDQSxjQUFNLFdBQVcsTUFBTSxLQUFLLFNBQVMsVUFBVSxFQUFFLEtBQUssV0FBVyx1QkFBdUIsbUNBQW1DLENBQUM7QUFDNUgsWUFBSSxLQUFLLE1BQU0sUUFBUSxNQUFNLFdBQVc7QUFDdEMsY0FBSSxpQ0FBaUM7QUFDckM7QUFBQSxRQUNGO0FBQUEsTUFDRixRQUFRO0FBRU4seUJBQWlCO0FBQUEsTUFDbkI7QUFDQSxZQUFNLE1BQU0sbUNBQW1DO0FBQUEsSUFDakQ7QUFDQSxVQUFNLElBQUksTUFBTSxZQUFZLFNBQVMsZ0NBQWdDLE9BQU8sa0NBQWtDLENBQUMsSUFBSTtBQUFBLEVBQ3JIO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVNBLE1BQWMsZ0JBQWdCLFdBQWtDO0FBQzlELFVBQU0sS0FBSyxhQUFhO0FBRXhCLFVBQU0saUJBQWlCLFdBQVcsU0FBUztBQUMzQyxVQUFNLFVBQVUsQ0FBQyxZQUFZLEdBQUksaUJBQWlCLENBQUMsU0FBUyxjQUFjLEVBQUUsSUFBSSxDQUFDLEdBQUksUUFBUSxVQUFVO0FBQ3ZHLFVBQU0sS0FBSyw4QkFBOEIsU0FBUyxTQUFTO0FBQUEsRUFDN0Q7QUFDRjtBQVVBLGVBQXNCLHFCQUFxQixRQUFtRDtBQUU1RixRQUFNLGFBQWEsUUFBUSxJQUFJLE9BQU8sSUFBSSxNQUFNLFNBQVMsWUFBWTtBQUNyRSxNQUFJO0FBQ0YsVUFBTSxTQUFTLE1BQU0sT0FBTyxTQUFTO0FBQ3JDLFFBQUksV0FBVyxRQUFXO0FBQ3hCLFlBQU0sV0FBVyxVQUFVLE9BQU8sWUFBWSxLQUFLLFVBQVUsRUFBRSxNQUFNLGFBQWEsT0FBTyxHQUFHLENBQUMsQ0FBQztBQUFBLElBQ2hHLFdBQVcsV0FBVyxNQUFNO0FBQzFCLFlBQU0sV0FBVyxVQUFVLE9BQU8sWUFBWSxLQUFLLFVBQVUsRUFBRSxNQUFNLFFBQVEsT0FBTyxHQUFHLENBQUMsQ0FBQztBQUFBLElBQzNGLE9BQU87QUFFTCxZQUFNLFdBQVcsVUFBVSxPQUFPLFlBQVksS0FBSyxVQUFVLEVBQUUsT0FBTyxPQUFPLE1BQU0sRUFBRSxDQUFDLENBQUM7QUFBQSxJQUN6RjtBQUFBLEVBQ0YsU0FBUyxLQUFLO0FBQ1osVUFBTSxXQUFXLFVBQVUsT0FBTyxZQUFZLEtBQUssVUFBVSxFQUFFLE1BQU0sU0FBUyxPQUFPLE9BQU8sZUFBZSxHQUFHLEVBQUUsQ0FBQyxDQUFDO0FBQUEsRUFDcEg7QUFDRjtBQVdBLGVBQWUsaUJBQWlCLFFBQWlEO0FBQy9FLFFBQU0sUUFBa0IsQ0FBQztBQUV6QixRQUFNLEtBQUssY0FBYyxPQUFPLFFBQVEsS0FBSyxHQUFHLENBQUMsRUFBRTtBQUNuRCxRQUFNLEtBQUssd0JBQXdCLE9BQU8sR0FBRyxFQUFFO0FBQy9DLFFBQU0sS0FBSyxrQkFBa0IsT0FBTyxVQUFVLEVBQUU7QUFDaEQsUUFBTSxLQUFLLDJCQUEyQixPQUFPLFVBQVUsRUFBRTtBQUV6RCxNQUFJLE9BQU8sWUFBWTtBQUNyQixVQUFNLEtBQUssZ0JBQWdCLE9BQU8sV0FBVyxhQUFhLE9BQU8sZ0RBQTJDLE9BQU8sT0FBTyxXQUFXLFFBQVEsQ0FBQyxFQUFFO0FBQ2hKLFFBQUksT0FBTyxXQUFXLFlBQVk7QUFDaEMsWUFBTSxLQUFLLGtCQUFrQixPQUFPLFdBQVcsVUFBVSxFQUFFO0FBQUEsSUFDN0Q7QUFDQSxRQUFJLE9BQU8sV0FBVyxPQUFPLEtBQUssR0FBRztBQUNuQyxZQUFNLEtBQUssYUFBYSxPQUFPLFdBQVcsT0FBTyxLQUFLLENBQUMsRUFBRTtBQUFBLElBQzNEO0FBQ0EsUUFBSSxPQUFPLFdBQVcsT0FBTyxLQUFLLEdBQUc7QUFDbkMsWUFBTSxLQUFLLGFBQWEsT0FBTyxXQUFXLE9BQU8sS0FBSyxDQUFDLEVBQUU7QUFBQSxJQUMzRDtBQUNBLFFBQUksQ0FBQyxPQUFPLFdBQVcsT0FBTyxLQUFLLEtBQUssQ0FBQyxPQUFPLFdBQVcsT0FBTyxLQUFLLEdBQUc7QUFDeEUsWUFBTSxLQUFLLDBCQUEwQjtBQUFBLElBQ3ZDO0FBQUEsRUFDRixPQUFPO0FBQ0wsVUFBTSxLQUFLLDZFQUF3RTtBQUFBLEVBQ3JGO0FBRUEsUUFBTSxLQUFLLHFCQUFxQjtBQUNoQyxRQUFNLEtBQUssdUJBQXVCLE1BQU0sa0JBQWtCLElBQUksUUFBUSxJQUFJLEVBQUU7QUFDNUUsUUFBTSxLQUFLLG1DQUFtQyxhQUFhLElBQUksUUFBUSxJQUFJLEVBQUU7QUFDN0UsUUFBTSxLQUFLLHVCQUF1QixrQkFBa0IsT0FBTyxHQUFHLElBQUksUUFBUSxJQUFJLEVBQUU7QUFDaEYsUUFBTSxLQUFLLHlCQUF5QixXQUFXLE9BQU8sVUFBVSxJQUFJLFFBQVEseUJBQXlCLEVBQUU7QUFDdkcsUUFBTSxLQUFLLHlCQUF5QixXQUFXLE9BQU8sVUFBVSxJQUFJLFFBQVEsSUFBSSxFQUFFO0FBRWxGLFFBQU0sbUJBQW1CLG9CQUFvQjtBQUM3QyxNQUFJLGlCQUFpQixXQUFXLEdBQUc7QUFDakMsVUFBTSxLQUFLLDZCQUE2QjtBQUFBLEVBQzFDLE9BQU87QUFDTCxVQUFNLEtBQUssc0JBQXNCO0FBQ2pDLGVBQVcsU0FBUyxrQkFBa0I7QUFDcEMsWUFBTSxLQUFLLFNBQVMsTUFBTSxJQUFJLFdBQVcsT0FBTyxNQUFNLElBQUksQ0FBQyxHQUFHO0FBQUEsSUFDaEU7QUFBQSxFQUNGO0FBRUEsUUFBTSxLQUFLO0FBQUEsRUFBc0IsT0FBTyxhQUFhLEVBQUU7QUFFdkQsU0FBTyxNQUFNLEtBQUssSUFBSTtBQUN4QjtBQVlBLFNBQVMsZ0JBQWdCLFlBQW9CLFlBQTRCO0FBQ3ZFLFFBQU0sUUFBUSw0QkFBNEIsb0JBQW9CO0FBQzlELFFBQU0sb0JBQW9CLDRCQUE0QixjQUFjO0FBQ3BFLFFBQU0saUJBQWlCLEtBQUssVUFBVSxXQUFXLFFBQVEsT0FBTyxHQUFHLENBQUM7QUFDcEUsU0FBTyxJQUFJLEtBQUssOEJBQThCLFVBQVUsa0JBQWtCLGNBQWMscUJBQXFCLGlCQUFpQjtBQUNoSTtBQVFBLFNBQVMsTUFBTSxJQUEyQjtBQUN4QyxTQUFPLElBQUksUUFBUSxDQUFDLFlBQVk7QUFDOUIsZUFBVyxTQUFTLEVBQUU7QUFBQSxFQUN4QixDQUFDO0FBQ0g7QUFVQSxlQUFlLGtCQUFrQixZQUFtQztBQUVsRSxRQUFNLGFBQWEsUUFBUSxJQUFJLE9BQU8sSUFBSSxNQUFNLFNBQVMsWUFBWTtBQUVyRSxNQUFJO0FBQ0YsVUFBTSxXQUFXLE9BQU8sVUFBVTtBQUFBLEVBQ3BDLFNBQVMsT0FBTztBQUNkLFVBQU0sSUFBSSxNQUFNLDBCQUEwQixVQUFVLElBQUksRUFBRSxNQUFNLENBQUM7QUFBQSxFQUNuRTtBQUVBLFFBQU0sU0FBUyxNQUFNLFdBQVcsU0FBUyxZQUFZLE9BQU87QUFFNUQsVUFBUSxNQUFNLGFBQWEsVUFBVTtBQUFBLEVBQU0sTUFBTSxFQUFFO0FBRW5ELE1BQUk7QUFDSixNQUFJO0FBRUYsU0FBSyxJQUFJLFNBQVMsVUFBVSxNQUFNLEVBQUU7QUFBQSxFQUN0QyxTQUFTLE9BQU87QUFDZCxVQUFNLElBQUksTUFBTSxpQkFBaUIsVUFBVSxJQUFJLEVBQUUsTUFBTSxDQUFDO0FBQUEsRUFDMUQ7QUFFQSxNQUFJO0FBQ0YsVUFBTSxHQUFHO0FBQUEsRUFDWCxTQUFTLE9BQU87QUFDZCxVQUFNLElBQUksTUFBTSxtQkFBbUIsVUFBVSxJQUFJLEVBQUUsTUFBTSxDQUFDO0FBQUEsRUFDNUQ7QUFDRjtBQU9BLFNBQVMseUJBQWlDO0FBQ3hDLE1BQUksUUFBUSxhQUFhLFNBQVM7QUFDaEMsV0FBTztBQUFBLEVBQ1Q7QUFFQSxTQUFPO0FBQ1Q7QUFZQSxlQUFlLDJCQUE0QztBQUN6RCxRQUFNLFVBQVUsTUFBTSwwQkFBMEI7QUFFaEQsTUFBSSxRQUFRLGFBQWEsU0FBUztBQUNoQyxXQUFPLGFBQWEsT0FBTztBQUFBLEVBQzdCO0FBRUEsU0FBTyxJQUFJLE9BQU87QUFDcEI7QUFRQSxTQUFTLGtCQUFrQixLQUFxQjtBQUM5QyxNQUFJLFFBQVEsYUFBYSxTQUFTO0FBQ2hDLFdBQU8sYUFBYSxHQUFHO0FBQUEsRUFDekI7QUFFQSxNQUFJLFFBQVEsYUFBYSxVQUFVO0FBQ2pDLFdBQU8sU0FBUyxHQUFHO0FBQUEsRUFDckI7QUFFQSxTQUFPLGFBQWEsR0FBRztBQUN6QjtBQU9BLGVBQWUsb0JBQXNDO0FBQ25ELFFBQU0sVUFBVSxRQUFRLGFBQWEsVUFDakMsaURBQ0E7QUFDSixNQUFJO0FBQ0YsVUFBTSxTQUFTLE1BQU0sS0FBSyxTQUFTLEVBQUUsU0FBUyxLQUFLLENBQUM7QUFDcEQsUUFBSSxRQUFRLGFBQWEsU0FBUztBQUNoQyxhQUFPLE9BQU8sU0FBUyxjQUFjO0FBQUEsSUFDdkM7QUFDQSxXQUFPLE9BQU8sS0FBSyxFQUFFLFNBQVM7QUFBQSxFQUNoQyxRQUFRO0FBQ04sV0FBTztBQUFBLEVBQ1Q7QUFDRjtBQVdBLGVBQWUsZ0JBQWdCLFdBQWtDO0FBQy9ELFFBQU0sVUFBVSxXQUFXLFNBQVM7QUFDcEMsTUFBSSxDQUFDLFNBQVM7QUFDWixVQUFNLElBQUksTUFBTSx3RUFBd0UsU0FBUyxFQUFFO0FBQUEsRUFDckc7QUFDQSxRQUFNLE1BQU0seUJBQXlCLG1CQUFtQixPQUFPLENBQUM7QUFDaEUsUUFBTSxVQUFVLGtCQUFrQixHQUFHO0FBQ3JDLE1BQUksMENBQTBDLEdBQUcsRUFBRTtBQUNuRCxNQUFJO0FBQ0YsVUFBTSxLQUFLLFNBQVMsRUFBRSxTQUFTLEtBQUssQ0FBQztBQUFBLEVBQ3ZDLFNBQVMsT0FBZ0I7QUFDdkIsUUFBSSxnREFBZ0QsZUFBZSxLQUFLLENBQUMsRUFBRTtBQUFBLEVBQzdFO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
|
Binary file
|