@mnaoumov/obsidian-lib 10.3.0 → 10.4.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 (49) hide show
  1. package/command-handlers/undo-bulk-operation-command-handler/package.json +6 -0
  2. package/components/bulk-operation-commit-component/package.json +6 -0
  3. package/dist/lib/cjs/__merged.cjs +42 -1
  4. package/dist/lib/cjs/__merged.d.cts +5 -0
  5. package/dist/lib/cjs/bulk-operation-progress.cjs +120 -0
  6. package/dist/lib/cjs/bulk-operation-progress.d.cts +33 -0
  7. package/dist/lib/cjs/bulk-operation.cjs +220 -0
  8. package/dist/lib/cjs/bulk-operation.d.cts +237 -0
  9. package/dist/lib/cjs/command-handlers/index.cjs +4 -1
  10. package/dist/lib/cjs/command-handlers/index.d.cts +1 -0
  11. package/dist/lib/cjs/command-handlers/undo-bulk-operation-command-handler.cjs +164 -0
  12. package/dist/lib/cjs/command-handlers/undo-bulk-operation-command-handler.d.cts +51 -0
  13. package/dist/lib/cjs/components/bulk-operation-commit-component.cjs +337 -0
  14. package/dist/lib/cjs/components/bulk-operation-commit-component.d.cts +152 -0
  15. package/dist/lib/cjs/components/index.cjs +4 -1
  16. package/dist/lib/cjs/components/index.d.cts +1 -0
  17. package/dist/lib/cjs/desktop-bulk-operation-git.cjs +534 -0
  18. package/dist/lib/cjs/desktop-bulk-operation-git.d.cts +196 -0
  19. package/dist/lib/cjs/generated-during-build.cjs +1 -1
  20. package/dist/lib/cjs/i18n/locales/en.cjs +10 -1
  21. package/dist/lib/cjs/i18n/locales/en.d.cts +9 -0
  22. package/dist/lib/cjs/i18n/locales/translations-map.d.cts +9 -0
  23. package/dist/lib/cjs/index.cjs +10 -1
  24. package/dist/lib/cjs/index.d.cts +3 -0
  25. package/dist/lib/cjs/loop.cjs +3 -1
  26. package/dist/lib/esm/__merged.d.mts +5 -0
  27. package/dist/lib/esm/__merged.mjs +43 -1
  28. package/dist/lib/esm/bulk-operation-progress.d.mts +33 -0
  29. package/dist/lib/esm/bulk-operation-progress.mjs +31 -0
  30. package/dist/lib/esm/bulk-operation.d.mts +237 -0
  31. package/dist/lib/esm/bulk-operation.mjs +125 -0
  32. package/dist/lib/esm/command-handlers/index.d.mts +1 -0
  33. package/dist/lib/esm/command-handlers/index.mjs +3 -1
  34. package/dist/lib/esm/command-handlers/undo-bulk-operation-command-handler.d.mts +51 -0
  35. package/dist/lib/esm/command-handlers/undo-bulk-operation-command-handler.mjs +76 -0
  36. package/dist/lib/esm/components/bulk-operation-commit-component.d.mts +152 -0
  37. package/dist/lib/esm/components/bulk-operation-commit-component.mjs +245 -0
  38. package/dist/lib/esm/components/index.d.mts +1 -0
  39. package/dist/lib/esm/components/index.mjs +3 -1
  40. package/dist/lib/esm/desktop-bulk-operation-git.d.mts +196 -0
  41. package/dist/lib/esm/desktop-bulk-operation-git.mjs +463 -0
  42. package/dist/lib/esm/generated-during-build.mjs +1 -1
  43. package/dist/lib/esm/i18n/locales/en.d.mts +9 -0
  44. package/dist/lib/esm/i18n/locales/en.mjs +10 -1
  45. package/dist/lib/esm/i18n/locales/translations-map.d.mts +9 -0
  46. package/dist/lib/esm/index.d.mts +3 -0
  47. package/dist/lib/esm/index.mjs +7 -1
  48. package/dist/lib/esm/loop.mjs +3 -1
  49. package/package.json +1 -1
@@ -0,0 +1,463 @@
1
+ /*
2
+ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
3
+ if you want to view the source, please visit the github repository of this plugin
4
+ */
5
+ (function () {
6
+ function ensureBrowserProcess() {
7
+ const browserProcess = {
8
+ browser: true,
9
+ cwd() {
10
+ return "/";
11
+ },
12
+ env: {},
13
+ platform: "android"
14
+ };
15
+ const existingProcess = globalThis.process;
16
+ if (!existingProcess) {
17
+ globalThis.process = browserProcess;
18
+ return;
19
+ }
20
+ if (existingProcess.versions?.node) {
21
+ return;
22
+ }
23
+ const existingProcessRecord = existingProcess;
24
+ for (const [key, value] of Object.entries(browserProcess)) {
25
+ existingProcessRecord[key] ??= value;
26
+ }
27
+ existingProcessRecord["browser"] = true;
28
+ }
29
+
30
+ ensureBrowserProcess();
31
+ })();
32
+
33
+ import { setTimeoutAsync } from "@mnaoumov/typescript-lib/async";
34
+ import { noopAsync } from "@mnaoumov/typescript-lib/function";
35
+ import { escapeRegExp } from "@mnaoumov/typescript-lib/reg-exp";
36
+ import { ensureNonNullable } from "@mnaoumov/typescript-lib/type-guards";
37
+ import { execFile } from "node:child_process";
38
+ import {
39
+ readFile,
40
+ rename,
41
+ stat,
42
+ unlink,
43
+ writeFile
44
+ } from "node:fs/promises";
45
+ import {
46
+ hostname,
47
+ uptime
48
+ } from "node:os";
49
+ import { join } from "node:path";
50
+ import { registerBulkOperationProgressListener } from "./bulk-operation-progress.mjs";
51
+ import {
52
+ buildBeforeOperationCommitSubject,
53
+ buildOperationCommitMessage,
54
+ BULK_OPERATION_LOCK_FILE_NAME,
55
+ getNextOperationId,
56
+ isBulkOperationLock,
57
+ isBulkOperationLockStale,
58
+ parseBulkOperationCommit,
59
+ PLUGIN_TRAILER_KEY
60
+ } from "./bulk-operation.mjs";
61
+ const DEFAULT_LOCK_WAIT_TIMEOUT_IN_MILLISECONDS = 6e5;
62
+ const HEARTBEAT_THROTTLE_IN_MILLISECONDS = 5e3;
63
+ const INDEX_LOCK_RETRY_COUNT = 20;
64
+ const INDEX_LOCK_RETRY_DELAY_IN_MILLISECONDS = 500;
65
+ const LOCK_POLL_INTERVAL_IN_MILLISECONDS = 1e3;
66
+ const MAX_BUFFER_IN_BYTES = 268435456;
67
+ const MILLISECONDS_IN_SECOND = 1e3;
68
+ const GIT_FATAL_EXIT_CODE = 128;
69
+ const REV_PARSE_OUTPUT_LINE_COUNT = 2;
70
+ const UNIT_SEPARATOR = "";
71
+ class BulkOperationGitRepo {
72
+ /**
73
+ * The absolute path of the repo's git directory.
74
+ */
75
+ gitDirectory;
76
+ /**
77
+ * The absolute path of the folder whose changes are committed.
78
+ */
79
+ workingDirectory;
80
+ lockFilePath;
81
+ lockWaitTimeoutInMilliseconds;
82
+ ownLock = null;
83
+ /**
84
+ * Creates a new instance. Use {@link BulkOperationGitRepo.open} to find the repo of a folder.
85
+ *
86
+ * @param params - The constructor parameters.
87
+ */
88
+ constructor(params) {
89
+ this.gitDirectory = params.gitDirectory;
90
+ this.workingDirectory = params.workingDirectory;
91
+ this.lockWaitTimeoutInMilliseconds = params.lockWaitTimeoutInMilliseconds ?? DEFAULT_LOCK_WAIT_TIMEOUT_IN_MILLISECONDS;
92
+ this.lockFilePath = join(this.gitDirectory, BULK_OPERATION_LOCK_FILE_NAME);
93
+ }
94
+ /**
95
+ * Finds the git repo a folder belongs to.
96
+ *
97
+ * @param workingDirectory - The folder, normally the vault root.
98
+ * @returns The repo, or `null` when the folder is not inside a git work tree or `git` cannot be run.
99
+ */
100
+ static async open(workingDirectory) {
101
+ let result;
102
+ try {
103
+ result = await execGit({ cwd: workingDirectory, gitArguments: ["rev-parse", "--is-inside-work-tree", "--absolute-git-dir"] });
104
+ } catch {
105
+ return null;
106
+ }
107
+ const [isInsideWorkTree, gitDirectory] = result.stdout.split(/\r?\n/, REV_PARSE_OUTPUT_LINE_COUNT);
108
+ return result.exitCode !== 0 || isInsideWorkTree !== "true" || !gitDirectory ? null : new BulkOperationGitRepo({ gitDirectory, workingDirectory });
109
+ }
110
+ /**
111
+ * Refuses to go on when the repo routes files through git-annex but `git-annex` cannot be run, since a
112
+ * plain `git add` would then put the large files into git itself.
113
+ */
114
+ async assertCanCommitLargeFiles() {
115
+ const annexFilters = await this.runGit({ allowedExitCodes: [0, 1], gitArguments: ["config", "--get-regexp", String.raw`^filter\.annex\.`] });
116
+ if (!annexFilters.stdout.trim()) {
117
+ return;
118
+ }
119
+ const annexVersion = await this.runGit({ gitArguments: ["annex", "version", "--raw"] }).catch(() => null);
120
+ if (annexVersion === null) {
121
+ throw new Error(
122
+ "The vault repository routes files through git-annex, but git-annex is not on PATH. Bulk operation commits are refused, because they would add large files to git itself."
123
+ );
124
+ }
125
+ }
126
+ /**
127
+ * Lists the bulk-operation commits a plugin made on the current branch, newest first.
128
+ *
129
+ * @param pluginId - The plugin id.
130
+ * @returns The commits.
131
+ */
132
+ async listBulkOperationCommits(pluginId) {
133
+ const result = await this.runGit({
134
+ allowedExitCodes: [0, GIT_FATAL_EXIT_CODE],
135
+ gitArguments: [
136
+ "log",
137
+ `--format=%H${UNIT_SEPARATOR}%aI${UNIT_SEPARATOR}%s`,
138
+ "--extended-regexp",
139
+ `--grep=^${PLUGIN_TRAILER_KEY}: ${escapeRegExp(pluginId)}$`
140
+ ]
141
+ });
142
+ if (result.exitCode !== 0) {
143
+ return [];
144
+ }
145
+ const commits = [];
146
+ for (const line of result.stdout.split(/\r?\n/)) {
147
+ const [sha = "", date = "", ...subjectParts] = line.split(UNIT_SEPARATOR);
148
+ const commit = parseBulkOperationCommit({ date, sha, subject: subjectParts.join(UNIT_SEPARATOR) });
149
+ if (commit) {
150
+ commits.push(commit);
151
+ }
152
+ }
153
+ return commits;
154
+ }
155
+ /**
156
+ * Commits whatever a stale lock's operation left behind as `OP<n> interrupted` and removes the lock.
157
+ *
158
+ * @returns The id of the recovered operation, or `null` when there was no stale lock.
159
+ */
160
+ async recoverStaleLock() {
161
+ const lock = await this.readLock();
162
+ return !lock || !this.isStale(lock) ? null : await this.recover(lock);
163
+ }
164
+ /**
165
+ * Reverts a bulk-operation commit with `git revert --no-edit`.
166
+ *
167
+ * Whatever is pending is committed first, so the revert starts from a clean tree. A conflict is NOT aborted: the
168
+ * repo is left mid-revert for the user to resolve.
169
+ *
170
+ * @param commit - The commit to revert.
171
+ * @returns Whether the revert stopped on a conflict, and what git printed.
172
+ */
173
+ async revertBulkOperationCommit(commit) {
174
+ await this.assertCanCommitLargeFiles();
175
+ await this.acquireLock();
176
+ try {
177
+ await this.commitPending({ body: "", subject: `chore: before revert of ${commit.operationId}` });
178
+ const result = await this.runGit({ allowedExitCodes: [0, 1], gitArguments: ["revert", "--no-edit", commit.sha] });
179
+ return { isConflict: result.exitCode !== 0, output: `${result.stdout}${result.stderr}`.trim() };
180
+ } finally {
181
+ await this.releaseLock();
182
+ }
183
+ }
184
+ /**
185
+ * Runs a bulk operation between two commits.
186
+ *
187
+ * Takes the lease lock, commits whatever is pending as `chore: before OP<n>`, runs the operation, waits for its
188
+ * follow-ups to settle, commits the result as `OP<n>: <summary>` and releases the lock. An operation that throws
189
+ * still gets its partial changes committed, as `OP<n> interrupted`, before the error is rethrown.
190
+ *
191
+ * @typeParam T - The type of the operation's result.
192
+ * @param params - The parameters.
193
+ * @returns The operation's result, its id and the hash of its commit.
194
+ */
195
+ async runBulkOperation(params) {
196
+ await this.assertCanCommitLargeFiles();
197
+ await this.acquireLock();
198
+ let unregisterProgressListener = null;
199
+ try {
200
+ const operationId = await this.allocateOperationId(params.pluginId);
201
+ await this.commitPending({ body: "", subject: buildBeforeOperationCommitSubject(operationId) });
202
+ unregisterProgressListener = registerBulkOperationProgressListener(this.createThrottledHeartbeat());
203
+ let result;
204
+ try {
205
+ result = await params.operation();
206
+ await params.settle();
207
+ } catch (error) {
208
+ await this.heartbeat();
209
+ await this.commitOperation({ isInterrupted: true, operationId, pluginId: params.pluginId });
210
+ throw error;
211
+ }
212
+ await this.heartbeat();
213
+ const commitSha = await this.commitOperation({ operationId, pluginId: params.pluginId, summary: params.summary });
214
+ return { commitSha, operationId, result };
215
+ } finally {
216
+ unregisterProgressListener?.();
217
+ await this.releaseLock();
218
+ }
219
+ }
220
+ async acquireLock() {
221
+ const deadline = Date.now() + this.lockWaitTimeoutInMilliseconds;
222
+ for (; ; ) {
223
+ if (await this.tryCreateLock()) {
224
+ return;
225
+ }
226
+ const lock = await this.readLock();
227
+ if (!lock) {
228
+ continue;
229
+ }
230
+ if (this.isStale(lock)) {
231
+ await this.recover(lock);
232
+ continue;
233
+ }
234
+ if (Date.now() >= deadline) {
235
+ throw new Error(
236
+ `Timed out waiting for ${lock.operationId || "an operation"} of ${lock.pluginId} on ${lock.host} to release the bulk operation lock ${this.lockFilePath}.`
237
+ );
238
+ }
239
+ await setTimeoutAsync(LOCK_POLL_INTERVAL_IN_MILLISECONDS);
240
+ }
241
+ }
242
+ async allocateOperationId(pluginId) {
243
+ const operationId = await this.findNextOperationId();
244
+ const ownLock = ensureNonNullable(this.ownLock);
245
+ this.ownLock = { ...ownLock, heartbeatAt: Date.now(), operationId, pluginId };
246
+ await this.writeLock(this.ownLock);
247
+ return operationId;
248
+ }
249
+ async commitOperation(params) {
250
+ const changedFileCount = await this.stageAll();
251
+ if (changedFileCount === 0) {
252
+ return null;
253
+ }
254
+ const message = buildOperationCommitMessage({ ...params, changedFileCount, timestamp: /* @__PURE__ */ new Date() });
255
+ await this.commitStaged(message);
256
+ const headResult = await this.runGit({ gitArguments: ["rev-parse", "HEAD"] });
257
+ return headResult.stdout.trim();
258
+ }
259
+ async commitPending(message) {
260
+ if (await this.stageAll() === 0) {
261
+ return;
262
+ }
263
+ await this.commitStaged(message);
264
+ }
265
+ async commitStaged(message) {
266
+ const gitArguments = ["commit", "--quiet", "-m", message.subject];
267
+ if (message.body) {
268
+ gitArguments.push("-m", message.body);
269
+ }
270
+ gitArguments.push("--", ".");
271
+ await this.runGit({ gitArguments });
272
+ }
273
+ createThrottledHeartbeat() {
274
+ let lastHeartbeatAt = Date.now();
275
+ let pendingHeartbeat = noopAsync();
276
+ return () => {
277
+ const now = Date.now();
278
+ if (now - lastHeartbeatAt < HEARTBEAT_THROTTLE_IN_MILLISECONDS) {
279
+ return;
280
+ }
281
+ lastHeartbeatAt = now;
282
+ pendingHeartbeat = pendingHeartbeat.then(() => this.heartbeat()).catch((error) => {
283
+ console.error("Failed to refresh the bulk operation lock heartbeat", error);
284
+ });
285
+ };
286
+ }
287
+ async findNextOperationId() {
288
+ const result = await this.runGit({
289
+ gitArguments: ["log", "--all", "--format=%s", "--extended-regexp", "--grep=^(chore: before )?OP[0-9]+"]
290
+ });
291
+ return getNextOperationId(result.stdout.split(/\r?\n/));
292
+ }
293
+ async heartbeat() {
294
+ const lock = await this.readLock();
295
+ if (!lock || !this.isOwnLock(lock)) {
296
+ return;
297
+ }
298
+ await this.writeLock({ ...lock, heartbeatAt: Date.now() });
299
+ }
300
+ isOwnLock(lock) {
301
+ return this.ownLock !== null && lock.host === this.ownLock.host && lock.processId === this.ownLock.processId && lock.startedAt === this.ownLock.startedAt;
302
+ }
303
+ isStale(lock) {
304
+ return isBulkOperationLockStale(lock, {
305
+ bootedAt: Date.now() - uptime() * MILLISECONDS_IN_SECOND,
306
+ host: hostname(),
307
+ isProcessAlive,
308
+ now: Date.now()
309
+ });
310
+ }
311
+ async readLock() {
312
+ let text;
313
+ try {
314
+ text = await readFile(this.lockFilePath, "utf-8");
315
+ } catch (error) {
316
+ if (isErrorWithCode(error, "ENOENT")) {
317
+ return null;
318
+ }
319
+ throw error;
320
+ }
321
+ try {
322
+ const value = JSON.parse(text);
323
+ if (isBulkOperationLock(value)) {
324
+ return value;
325
+ }
326
+ } catch {
327
+ }
328
+ const fileStats = await stat(this.lockFilePath);
329
+ return {
330
+ heartbeatAt: fileStats.mtimeMs,
331
+ host: "",
332
+ operationId: "",
333
+ pluginId: "",
334
+ processId: 0,
335
+ processStartedAt: 0,
336
+ startedAt: 0
337
+ };
338
+ }
339
+ async recover(lock) {
340
+ const operationId = lock.operationId || await this.findNextOperationId();
341
+ await this.commitOperation({ isInterrupted: true, operationId, pluginId: lock.pluginId || "unknown" });
342
+ await unlink(this.lockFilePath).catch((error) => {
343
+ if (!isErrorWithCode(error, "ENOENT")) {
344
+ throw error;
345
+ }
346
+ });
347
+ return operationId;
348
+ }
349
+ async releaseLock() {
350
+ const lock = await this.readLock();
351
+ const isOwnLock = lock !== null && this.isOwnLock(lock);
352
+ this.ownLock = null;
353
+ if (!isOwnLock) {
354
+ return;
355
+ }
356
+ await unlink(this.lockFilePath).catch((error) => {
357
+ if (!isErrorWithCode(error, "ENOENT")) {
358
+ throw error;
359
+ }
360
+ });
361
+ }
362
+ async runGit(params) {
363
+ const allowedExitCodes = params.allowedExitCodes ?? [0];
364
+ for (let attempt = 1; ; attempt++) {
365
+ const result = await execGit({ cwd: this.workingDirectory, gitArguments: params.gitArguments });
366
+ if (allowedExitCodes.includes(result.exitCode)) {
367
+ return result;
368
+ }
369
+ if (attempt < INDEX_LOCK_RETRY_COUNT && result.stderr.includes("index.lock")) {
370
+ await setTimeoutAsync(INDEX_LOCK_RETRY_DELAY_IN_MILLISECONDS);
371
+ continue;
372
+ }
373
+ throw new GitCommandError(params.gitArguments, result.exitCode, result.stderr);
374
+ }
375
+ }
376
+ async stageAll() {
377
+ await this.runGit({ gitArguments: ["add", "--all", "--", "."] });
378
+ const result = await this.runGit({ gitArguments: ["diff", "--cached", "--name-only", "-z", "--", "."] });
379
+ return result.stdout.split("\0").filter(Boolean).length;
380
+ }
381
+ async tryCreateLock() {
382
+ const lock = createLockRecord();
383
+ try {
384
+ await writeFile(this.lockFilePath, JSON.stringify(lock), { flag: "wx" });
385
+ this.ownLock = lock;
386
+ return true;
387
+ } catch (error) {
388
+ if (isErrorWithCode(error, "EEXIST")) {
389
+ return false;
390
+ }
391
+ throw error;
392
+ }
393
+ }
394
+ async writeLock(lock) {
395
+ const temporaryPath = `${this.lockFilePath}.${String(lock.processId)}.tmp`;
396
+ await writeFile(temporaryPath, JSON.stringify(lock));
397
+ await rename(temporaryPath, this.lockFilePath);
398
+ }
399
+ }
400
+ class GitCommandError extends Error {
401
+ /**
402
+ * Creates a new instance.
403
+ *
404
+ * @param gitArguments - The arguments `git` was run with.
405
+ * @param exitCode - The exit code.
406
+ * @param stderr - What `git` printed to stderr.
407
+ */
408
+ constructor(gitArguments, exitCode, stderr) {
409
+ super(`git ${gitArguments.join(" ")} failed with exit code ${String(exitCode)}: ${stderr.trim()}`);
410
+ this.gitArguments = gitArguments;
411
+ this.exitCode = exitCode;
412
+ this.stderr = stderr;
413
+ this.name = "GitCommandError";
414
+ }
415
+ gitArguments;
416
+ exitCode;
417
+ stderr;
418
+ }
419
+ function createLockRecord() {
420
+ const now = Date.now();
421
+ return {
422
+ heartbeatAt: now,
423
+ host: hostname(),
424
+ operationId: "",
425
+ pluginId: "",
426
+ processId: process.pid,
427
+ processStartedAt: getProcessStartedAt(),
428
+ startedAt: now
429
+ };
430
+ }
431
+ async function execGit(params) {
432
+ return await new Promise((resolve, reject) => {
433
+ execFile("git", [...params.gitArguments], { cwd: params.cwd, maxBuffer: MAX_BUFFER_IN_BYTES, windowsHide: true }, (error, stdout, stderr) => {
434
+ if (error && typeof error.code !== "number") {
435
+ reject(new Error(`Failed to run git ${params.gitArguments.join(" ")}`, { cause: error }));
436
+ return;
437
+ }
438
+ resolve({ exitCode: typeof error?.code === "number" ? error.code : 0, stderr, stdout });
439
+ });
440
+ });
441
+ }
442
+ function getProcessStartedAt() {
443
+ return Math.round(performance.timeOrigin);
444
+ }
445
+ function isErrorWithCode(error, code) {
446
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
447
+ }
448
+ function isProcessAlive(processId) {
449
+ if (processId <= 0) {
450
+ return false;
451
+ }
452
+ try {
453
+ process.kill(processId, 0);
454
+ return true;
455
+ } catch (error) {
456
+ return !isErrorWithCode(error, "ESRCH") && (isErrorWithCode(error, "EPERM") || null);
457
+ }
458
+ }
459
+ export {
460
+ BulkOperationGitRepo,
461
+ GitCommandError
462
+ };
463
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9kZXNrdG9wLWJ1bGstb3BlcmF0aW9uLWdpdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBmaWxlXG4gKlxuICogVGhlIGRlc2t0b3AgaGFsZiBvZiBidWxrLW9wZXJhdGlvbiBnaXQgY29tbWl0czogZHJpdmVzIHRoZSBzeXN0ZW0gYGdpdGAgQ0xJIHRvIHdyYXAgYSBidWxrIG9wZXJhdGlvbiBpbiBjb21taXRzLFxuICogZ3VhcmRlZCBieSBhIGxlYXNlIGxvY2sgaW4gdGhlIHJlcG8ncyBnaXQgZGlyZWN0b3J5LlxuICpcbiAqIFRoZSBzeXN0ZW0gQ0xJIGlzIHVzZWQgcmF0aGVyIHRoYW4gYSBidW5kbGVkIGltcGxlbWVudGF0aW9uIGJlY2F1c2UgaXQgaG9ub3JzIHRoZSByZXBvJ3Mgb3duIGNvbmZpZywgaG9va3NcbiAqIGFuZCBjbGVhbi9zbXVkZ2UgZmlsdGVycywgZ2l0LWFubmV4J3MgYW1vbmcgdGhlbSwgYW5kIGJlY2F1c2UgYGdpdCByZXZlcnRgIGlzIHRoZSB3aG9sZSB1bmRvIHN0b3J5LlxuICpcbiAqIEV2ZXJ5IGNvbW1hbmQgcnVucyB3aXRoIHRoZSB2YXVsdCBmb2xkZXIgYXMgaXRzIHdvcmtpbmcgZGlyZWN0b3J5IGFuZCBgLmAgYXMgaXRzIHBhdGhzcGVjLCBzbyB3aGVuIHRoZSB2YXVsdCBpcyBhXG4gKiBzdWJmb2xkZXIgb2YgYSBsYXJnZXIgcmVwbyBvbmx5IHRoZSB2YXVsdCdzIGNoYW5nZXMgYXJlIGNvbW1pdHRlZC5cbiAqXG4gKiBEZXNrdG9wLW9ubHkgKGBub2RlOmNoaWxkX3Byb2Nlc3NgLCBgbm9kZTpmc2AsIGBub2RlOm9zYCksIGJ1dCBpbmVydCB0byBpbXBvcnQgb24gbW9iaWxlOiBub3RoaW5nIGhlcmUgdG91Y2hlcyBhXG4gKiBOb2RlIGJ1aWx0aW4gdW50aWwgYSBmdW5jdGlvbiBpcyBjYWxsZWQuXG4gKi9cblxuaW1wb3J0IHsgc2V0VGltZW91dEFzeW5jIH0gZnJvbSAnQG1uYW91bW92L3R5cGVzY3JpcHQtbGliL2FzeW5jJztcbmltcG9ydCB7IG5vb3BBc3luYyB9IGZyb20gJ0BtbmFvdW1vdi90eXBlc2NyaXB0LWxpYi9mdW5jdGlvbic7XG5pbXBvcnQgeyBlc2NhcGVSZWdFeHAgfSBmcm9tICdAbW5hb3Vtb3YvdHlwZXNjcmlwdC1saWIvcmVnLWV4cCc7XG5pbXBvcnQgeyBlbnN1cmVOb25OdWxsYWJsZSB9IGZyb20gJ0BtbmFvdW1vdi90eXBlc2NyaXB0LWxpYi90eXBlLWd1YXJkcyc7XG5pbXBvcnQgeyBleGVjRmlsZSB9IGZyb20gJ25vZGU6Y2hpbGRfcHJvY2Vzcyc7XG5pbXBvcnQge1xuICByZWFkRmlsZSxcbiAgcmVuYW1lLFxuICBzdGF0LFxuICB1bmxpbmssXG4gIHdyaXRlRmlsZVxufSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCB7XG4gIGhvc3RuYW1lLFxuICB1cHRpbWVcbn0gZnJvbSAnbm9kZTpvcyc7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSAnbm9kZTpwYXRoJztcblxuaW1wb3J0IHR5cGUge1xuICBCdWxrT3BlcmF0aW9uQ29tbWl0LFxuICBCdWxrT3BlcmF0aW9uTG9ja1xufSBmcm9tICcuL2J1bGstb3BlcmF0aW9uLnRzJztcblxuaW1wb3J0IHsgcmVnaXN0ZXJCdWxrT3BlcmF0aW9uUHJvZ3Jlc3NMaXN0ZW5lciB9IGZyb20gJy4vYnVsay1vcGVyYXRpb24tcHJvZ3Jlc3MudHMnO1xuaW1wb3J0IHtcbiAgYnVpbGRCZWZvcmVPcGVyYXRpb25Db21taXRTdWJqZWN0LFxuICBidWlsZE9wZXJhdGlvbkNvbW1pdE1lc3NhZ2UsXG4gIEJVTEtfT1BFUkFUSU9OX0xPQ0tfRklMRV9OQU1FLFxuICBnZXROZXh0T3BlcmF0aW9uSWQsXG4gIGlzQnVsa09wZXJhdGlvbkxvY2ssXG4gIGlzQnVsa09wZXJhdGlvbkxvY2tTdGFsZSxcbiAgcGFyc2VCdWxrT3BlcmF0aW9uQ29tbWl0LFxuICBQTFVHSU5fVFJBSUxFUl9LRVlcbn0gZnJvbSAnLi9idWxrLW9wZXJhdGlvbi50cyc7XG5cbi8qKlxuICogQ29uc3RydWN0b3IgcGFyYW1ldGVycyBmb3Ige0BsaW5rIEJ1bGtPcGVyYXRpb25HaXRSZXBvfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBCdWxrT3BlcmF0aW9uR2l0UmVwb0NvbnN0cnVjdG9yUGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBhYnNvbHV0ZSBwYXRoIG9mIHRoZSByZXBvJ3MgZ2l0IGRpcmVjdG9yeSwgd2hlcmUgdGhlIGxlYXNlIGxvY2sgbGl2ZXMuXG4gICAqL1xuICByZWFkb25seSBnaXREaXJlY3Rvcnk6IHN0cmluZztcblxuICAvKipcbiAgICogSG93IGxvbmcgdG8gd2FpdCBmb3IgYW5vdGhlciBvcGVyYXRpb24ncyBsb2NrIGJlZm9yZSBnaXZpbmcgdXAuXG4gICAqXG4gICAqIEBkZWZhdWx0IGA2MDBfMDAwYFxuICAgKi9cbiAgcmVhZG9ubHkgbG9ja1dhaXRUaW1lb3V0SW5NaWxsaXNlY29uZHM/OiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFRoZSBhYnNvbHV0ZSBwYXRoIG9mIHRoZSBmb2xkZXIgd2hvc2UgY2hhbmdlcyBhcmUgY29tbWl0dGVkOiB0aGUgdmF1bHQgcm9vdC5cbiAgICovXG4gIHJlYWRvbmx5IHdvcmtpbmdEaXJlY3Rvcnk6IHN0cmluZztcbn1cblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgQnVsa09wZXJhdGlvbkdpdFJlcG8ucnVuQnVsa09wZXJhdGlvbn0uXG4gKlxuICogQHR5cGVQYXJhbSBUIC0gVGhlIHR5cGUgb2YgdGhlIG9wZXJhdGlvbidzIHJlc3VsdC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBCdWxrT3BlcmF0aW9uR2l0UmVwb1J1bkJ1bGtPcGVyYXRpb25QYXJhbXM8VD4ge1xuICAvKipcbiAgICogUnVucyB0aGUgYnVsayBvcGVyYXRpb24uXG4gICAqXG4gICAqIEByZXR1cm5zIFRoZSBvcGVyYXRpb24ncyByZXN1bHQuXG4gICAqL1xuICByZWFkb25seSBvcGVyYXRpb246ICgpID0+IFByb21pc2U8VD47XG5cbiAgLyoqXG4gICAqIFRoZSBpZCBvZiB0aGUgcGx1Z2luIHJ1bm5pbmcgdGhlIG9wZXJhdGlvbi5cbiAgICovXG4gIHJlYWRvbmx5IHBsdWdpbklkOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFdhaXRzIGZvciB0aGUgZm9sbG93LXVwcyB0aGUgb3BlcmF0aW9uIHNldCBvZmYgKHJlbmFtZSBoYW5kbGVycywgbGluayB1cGRhdGVzLCB0aGUgbWV0YWRhdGEgY2FjaGUpIHRvIGZpbmlzaCwgc29cbiAgICogdGhlaXIgY2hhbmdlcyBsYW5kIGluIHRoZSBvcGVyYXRpb24ncyBjb21taXQuXG4gICAqL1xuICByZWFkb25seSBzZXR0bGU6ICgpID0+IFByb21pc2U8dm9pZD47XG5cbiAgLyoqXG4gICAqIEEgb25lLWxpbmUgZGVzY3JpcHRpb24gb2YgdGhlIG9wZXJhdGlvbiwgdXNlZCBpbiB0aGUgY29tbWl0IHN1YmplY3QuXG4gICAqL1xuICByZWFkb25seSBzdW1tYXJ5OiBzdHJpbmc7XG59XG5cbi8qKlxuICogVGhlIHJlc3VsdCBvZiB7QGxpbmsgQnVsa09wZXJhdGlvbkdpdFJlcG8ucnVuQnVsa09wZXJhdGlvbn0uXG4gKlxuICogQHR5cGVQYXJhbSBUIC0gVGhlIHR5cGUgb2YgdGhlIG9wZXJhdGlvbidzIHJlc3VsdC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBHaXRCdWxrT3BlcmF0aW9uUmVzdWx0PFQ+IHtcbiAgLyoqXG4gICAqIFRoZSBoYXNoIG9mIHRoZSBjb21taXQgcmVjb3JkaW5nIHRoZSBvcGVyYXRpb24sIG9yIGBudWxsYCB3aGVuIHRoZSBvcGVyYXRpb24gY2hhbmdlZCBub3RoaW5nLlxuICAgKi9cbiAgcmVhZG9ubHkgY29tbWl0U2hhOiBudWxsIHwgc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgb3BlcmF0aW9uIGlkLCBzdWNoIGFzIGBPUDEyYC5cbiAgICovXG4gIHJlYWRvbmx5IG9wZXJhdGlvbklkOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBvcGVyYXRpb24ncyByZXN1bHQuXG4gICAqL1xuICByZWFkb25seSByZXN1bHQ6IFQ7XG59XG5cbi8qKlxuICogVGhlIHJlc3VsdCBvZiB7QGxpbmsgQnVsa09wZXJhdGlvbkdpdFJlcG8ucmV2ZXJ0QnVsa09wZXJhdGlvbkNvbW1pdH0uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgR2l0UmV2ZXJ0UmVzdWx0IHtcbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhlIHJldmVydCBzdG9wcGVkIG9uIGEgY29uZmxpY3QsIGxlYXZpbmcgdGhlIHJlcG8gbWlkLXJldmVydCBmb3IgdGhlIHVzZXIgdG8gcmVzb2x2ZS5cbiAgICovXG4gIHJlYWRvbmx5IGlzQ29uZmxpY3Q6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFdoYXQgZ2l0IHByaW50ZWQuXG4gICAqL1xuICByZWFkb25seSBvdXRwdXQ6IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIEJ1bGtPcGVyYXRpb25HaXRSZXBvUnVuR2l0UGFyYW1zIHtcbiAgcmVhZG9ubHkgYWxsb3dlZEV4aXRDb2Rlcz86IHJlYWRvbmx5IG51bWJlcltdO1xuICByZWFkb25seSBnaXRBcmd1bWVudHM6IHJlYWRvbmx5IHN0cmluZ1tdO1xufVxuXG5pbnRlcmZhY2UgR2l0Q29tbWFuZFJlc3VsdCB7XG4gIHJlYWRvbmx5IGV4aXRDb2RlOiBudW1iZXI7XG4gIHJlYWRvbmx5IHN0ZGVycjogc3RyaW5nO1xuICByZWFkb25seSBzdGRvdXQ6IHN0cmluZztcbn1cblxuY29uc3QgREVGQVVMVF9MT0NLX1dBSVRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMgPSA2MDBfMDAwO1xuY29uc3QgSEVBUlRCRUFUX1RIUk9UVExFX0lOX01JTExJU0VDT05EUyA9IDUwMDA7XG5jb25zdCBJTkRFWF9MT0NLX1JFVFJZX0NPVU5UID0gMjA7XG5jb25zdCBJTkRFWF9MT0NLX1JFVFJZX0RFTEFZX0lOX01JTExJU0VDT05EUyA9IDUwMDtcbmNvbnN0IExPQ0tfUE9MTF9JTlRFUlZBTF9JTl9NSUxMSVNFQ09ORFMgPSAxMDAwO1xuY29uc3QgTUFYX0JVRkZFUl9JTl9CWVRFUyA9IDI2OF80MzVfNDU2O1xuY29uc3QgTUlMTElTRUNPTkRTX0lOX1NFQ09ORCA9IDEwMDA7XG5jb25zdCBHSVRfRkFUQUxfRVhJVF9DT0RFID0gMTI4O1xuY29uc3QgUkVWX1BBUlNFX09VVFBVVF9MSU5FX0NPVU5UID0gMjtcbmNvbnN0IFVOSVRfU0VQQVJBVE9SID0gJ1xcdXsxRn0nO1xuXG5pbnRlcmZhY2UgQnVsa09wZXJhdGlvbkdpdFJlcG9Db21taXRPcGVyYXRpb25QYXJhbXMge1xuICByZWFkb25seSBpc0ludGVycnVwdGVkPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgb3BlcmF0aW9uSWQ6IHN0cmluZztcbiAgcmVhZG9ubHkgcGx1Z2luSWQ6IHN0cmluZztcbiAgcmVhZG9ubHkgc3VtbWFyeT86IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIENvbW1pdE1lc3NhZ2Uge1xuICByZWFkb25seSBib2R5OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHN1YmplY3Q6IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIEV4ZWNHaXRQYXJhbXMge1xuICByZWFkb25seSBjd2Q6IHN0cmluZztcbiAgcmVhZG9ubHkgZ2l0QXJndW1lbnRzOiByZWFkb25seSBzdHJpbmdbXTtcbn1cblxuLyoqXG4gKiBBIGdpdCByZXBvIGhvbGRpbmcgYSB2YXVsdCwgZHJpdmVuIHRvIHdyYXAgYnVsayBvcGVyYXRpb25zIGluIGNvbW1pdHMuXG4gKi9cbmV4cG9ydCBjbGFzcyBCdWxrT3BlcmF0aW9uR2l0UmVwbyB7XG4gIC8qKlxuICAgKiBUaGUgYWJzb2x1dGUgcGF0aCBvZiB0aGUgcmVwbydzIGdpdCBkaXJlY3RvcnkuXG4gICAqL1xuICBwdWJsaWMgcmVhZG9ubHkgZ2l0RGlyZWN0b3J5OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBhYnNvbHV0ZSBwYXRoIG9mIHRoZSBmb2xkZXIgd2hvc2UgY2hhbmdlcyBhcmUgY29tbWl0dGVkLlxuICAgKi9cbiAgcHVibGljIHJlYWRvbmx5IHdvcmtpbmdEaXJlY3Rvcnk6IHN0cmluZztcblxuICBwcml2YXRlIHJlYWRvbmx5IGxvY2tGaWxlUGF0aDogc3RyaW5nO1xuICBwcml2YXRlIHJlYWRvbmx5IGxvY2tXYWl0VGltZW91dEluTWlsbGlzZWNvbmRzOiBudW1iZXI7XG4gIHByaXZhdGUgb3duTG9jazogQnVsa09wZXJhdGlvbkxvY2sgfCBudWxsID0gbnVsbDtcblxuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZS4gVXNlIHtAbGluayBCdWxrT3BlcmF0aW9uR2l0UmVwby5vcGVufSB0byBmaW5kIHRoZSByZXBvIG9mIGEgZm9sZGVyLlxuICAgKlxuICAgKiBAcGFyYW0gcGFyYW1zIC0gVGhlIGNvbnN0cnVjdG9yIHBhcmFtZXRlcnMuXG4gICAqL1xuICBwdWJsaWMgY29uc3RydWN0b3IocGFyYW1zOiBCdWxrT3BlcmF0aW9uR2l0UmVwb0NvbnN0cnVjdG9yUGFyYW1zKSB7XG4gICAgdGhpcy5naXREaXJlY3RvcnkgPSBwYXJhbXMuZ2l0RGlyZWN0b3J5O1xuICAgIHRoaXMud29ya2luZ0RpcmVjdG9yeSA9IHBhcmFtcy53b3JraW5nRGlyZWN0b3J5O1xuICAgIHRoaXMubG9ja1dhaXRUaW1lb3V0SW5NaWxsaXNlY29uZHMgPSBwYXJhbXMubG9ja1dhaXRUaW1lb3V0SW5NaWxsaXNlY29uZHMgPz8gREVGQVVMVF9MT0NLX1dBSVRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFM7XG4gICAgdGhpcy5sb2NrRmlsZVBhdGggPSBqb2luKHRoaXMuZ2l0RGlyZWN0b3J5LCBCVUxLX09QRVJBVElPTl9MT0NLX0ZJTEVfTkFNRSk7XG4gIH1cblxuICAvKipcbiAgICogRmluZHMgdGhlIGdpdCByZXBvIGEgZm9sZGVyIGJlbG9uZ3MgdG8uXG4gICAqXG4gICAqIEBwYXJhbSB3b3JraW5nRGlyZWN0b3J5IC0gVGhlIGZvbGRlciwgbm9ybWFsbHkgdGhlIHZhdWx0IHJvb3QuXG4gICAqIEByZXR1cm5zIFRoZSByZXBvLCBvciBgbnVsbGAgd2hlbiB0aGUgZm9sZGVyIGlzIG5vdCBpbnNpZGUgYSBnaXQgd29yayB0cmVlIG9yIGBnaXRgIGNhbm5vdCBiZSBydW4uXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGFzeW5jIG9wZW4od29ya2luZ0RpcmVjdG9yeTogc3RyaW5nKTogUHJvbWlzZTxCdWxrT3BlcmF0aW9uR2l0UmVwbyB8IG51bGw+IHtcbiAgICBsZXQgcmVzdWx0OiBHaXRDb21tYW5kUmVzdWx0O1xuICAgIHRyeSB7XG4gICAgICByZXN1bHQgPSBhd2FpdCBleGVjR2l0KHsgY3dkOiB3b3JraW5nRGlyZWN0b3J5LCBnaXRBcmd1bWVudHM6IFsncmV2LXBhcnNlJywgJy0taXMtaW5zaWRlLXdvcmstdHJlZScsICctLWFic29sdXRlLWdpdC1kaXInXSB9KTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBjb25zdCBbaXNJbnNpZGVXb3JrVHJlZSwgZ2l0RGlyZWN0b3J5XSA9IHJlc3VsdC5zdGRvdXQuc3BsaXQoL1xccj9cXG4vLCBSRVZfUEFSU0VfT1VUUFVUX0xJTkVfQ09VTlQpO1xuICAgIHJldHVybiByZXN1bHQuZXhpdENvZGUgIT09IDAgfHwgaXNJbnNpZGVXb3JrVHJlZSAhPT0gJ3RydWUnIHx8ICFnaXREaXJlY3RvcnkgPyBudWxsIDogbmV3IEJ1bGtPcGVyYXRpb25HaXRSZXBvKHsgZ2l0RGlyZWN0b3J5LCB3b3JraW5nRGlyZWN0b3J5IH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZnVzZXMgdG8gZ28gb24gd2hlbiB0aGUgcmVwbyByb3V0ZXMgZmlsZXMgdGhyb3VnaCBnaXQtYW5uZXggYnV0IGBnaXQtYW5uZXhgIGNhbm5vdCBiZSBydW4sIHNpbmNlIGFcbiAgICogcGxhaW4gYGdpdCBhZGRgIHdvdWxkIHRoZW4gcHV0IHRoZSBsYXJnZSBmaWxlcyBpbnRvIGdpdCBpdHNlbGYuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgYXNzZXJ0Q2FuQ29tbWl0TGFyZ2VGaWxlcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBhbm5leEZpbHRlcnMgPSBhd2FpdCB0aGlzLnJ1bkdpdCh7IGFsbG93ZWRFeGl0Q29kZXM6IFswLCAxXSwgZ2l0QXJndW1lbnRzOiBbJ2NvbmZpZycsICctLWdldC1yZWdleHAnLCBTdHJpbmcucmF3YF5maWx0ZXJcXC5hbm5leFxcLmBdIH0pO1xuICAgIGlmICghYW5uZXhGaWx0ZXJzLnN0ZG91dC50cmltKCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgYW5uZXhWZXJzaW9uID0gYXdhaXQgdGhpcy5ydW5HaXQoeyBnaXRBcmd1bWVudHM6IFsnYW5uZXgnLCAndmVyc2lvbicsICctLXJhdyddIH0pLmNhdGNoKCgpID0+IG51bGwpO1xuICAgIGlmIChhbm5leFZlcnNpb24gPT09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgJ1RoZSB2YXVsdCByZXBvc2l0b3J5IHJvdXRlcyBmaWxlcyB0aHJvdWdoIGdpdC1hbm5leCwgYnV0IGdpdC1hbm5leCBpcyBub3Qgb24gUEFUSC4gQnVsayBvcGVyYXRpb24gY29tbWl0cyBhcmUgcmVmdXNlZCwgYmVjYXVzZSB0aGV5IHdvdWxkIGFkZCBsYXJnZSBmaWxlcyB0byBnaXQgaXRzZWxmLidcbiAgICAgICk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIExpc3RzIHRoZSBidWxrLW9wZXJhdGlvbiBjb21taXRzIGEgcGx1Z2luIG1hZGUgb24gdGhlIGN1cnJlbnQgYnJhbmNoLCBuZXdlc3QgZmlyc3QuXG4gICAqXG4gICAqIEBwYXJhbSBwbHVnaW5JZCAtIFRoZSBwbHVnaW4gaWQuXG4gICAqIEByZXR1cm5zIFRoZSBjb21taXRzLlxuICAgKi9cbiAgcHVibGljIGFzeW5jIGxpc3RCdWxrT3BlcmF0aW9uQ29tbWl0cyhwbHVnaW5JZDogc3RyaW5nKTogUHJvbWlzZTxCdWxrT3BlcmF0aW9uQ29tbWl0W10+IHtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnJ1bkdpdCh7XG4gICAgICBhbGxvd2VkRXhpdENvZGVzOiBbMCwgR0lUX0ZBVEFMX0VYSVRfQ09ERV0sXG4gICAgICBnaXRBcmd1bWVudHM6IFtcbiAgICAgICAgJ2xvZycsXG4gICAgICAgIGAtLWZvcm1hdD0lSCR7VU5JVF9TRVBBUkFUT1J9JWFJJHtVTklUX1NFUEFSQVRPUn0lc2AsXG4gICAgICAgICctLWV4dGVuZGVkLXJlZ2V4cCcsXG4gICAgICAgIGAtLWdyZXA9XiR7UExVR0lOX1RSQUlMRVJfS0VZfTogJHtlc2NhcGVSZWdFeHAocGx1Z2luSWQpfSRgXG4gICAgICBdXG4gICAgfSk7XG4gICAgaWYgKHJlc3VsdC5leGl0Q29kZSAhPT0gMCkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICBjb25zdCBjb21taXRzOiBCdWxrT3BlcmF0aW9uQ29tbWl0W10gPSBbXTtcbiAgICBmb3IgKGNvbnN0IGxpbmUgb2YgcmVzdWx0LnN0ZG91dC5zcGxpdCgvXFxyP1xcbi8pKSB7XG4gICAgICBjb25zdCBbc2hhID0gJycsIGRhdGUgPSAnJywgLi4uc3ViamVjdFBhcnRzXSA9IGxpbmUuc3BsaXQoVU5JVF9TRVBBUkFUT1IpO1xuICAgICAgY29uc3QgY29tbWl0ID0gcGFyc2VCdWxrT3BlcmF0aW9uQ29tbWl0KHsgZGF0ZSwgc2hhLCBzdWJqZWN0OiBzdWJqZWN0UGFydHMuam9pbihVTklUX1NFUEFSQVRPUikgfSk7XG4gICAgICBpZiAoY29tbWl0KSB7XG4gICAgICAgIGNvbW1pdHMucHVzaChjb21taXQpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gY29tbWl0cztcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21taXRzIHdoYXRldmVyIGEgc3RhbGUgbG9jaydzIG9wZXJhdGlvbiBsZWZ0IGJlaGluZCBhcyBgT1A8bj4gaW50ZXJydXB0ZWRgIGFuZCByZW1vdmVzIHRoZSBsb2NrLlxuICAgKlxuICAgKiBAcmV0dXJucyBUaGUgaWQgb2YgdGhlIHJlY292ZXJlZCBvcGVyYXRpb24sIG9yIGBudWxsYCB3aGVuIHRoZXJlIHdhcyBubyBzdGFsZSBsb2NrLlxuICAgKi9cbiAgcHVibGljIGFzeW5jIHJlY292ZXJTdGFsZUxvY2soKTogUHJvbWlzZTxudWxsIHwgc3RyaW5nPiB7XG4gICAgY29uc3QgbG9jayA9IGF3YWl0IHRoaXMucmVhZExvY2soKTtcbiAgICByZXR1cm4gIWxvY2sgfHwgIXRoaXMuaXNTdGFsZShsb2NrKSA/IG51bGwgOiAoYXdhaXQgdGhpcy5yZWNvdmVyKGxvY2spKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXZlcnRzIGEgYnVsay1vcGVyYXRpb24gY29tbWl0IHdpdGggYGdpdCByZXZlcnQgLS1uby1lZGl0YC5cbiAgICpcbiAgICogV2hhdGV2ZXIgaXMgcGVuZGluZyBpcyBjb21taXR0ZWQgZmlyc3QsIHNvIHRoZSByZXZlcnQgc3RhcnRzIGZyb20gYSBjbGVhbiB0cmVlLiBBIGNvbmZsaWN0IGlzIE5PVCBhYm9ydGVkOiB0aGVcbiAgICogcmVwbyBpcyBsZWZ0IG1pZC1yZXZlcnQgZm9yIHRoZSB1c2VyIHRvIHJlc29sdmUuXG4gICAqXG4gICAqIEBwYXJhbSBjb21taXQgLSBUaGUgY29tbWl0IHRvIHJldmVydC5cbiAgICogQHJldHVybnMgV2hldGhlciB0aGUgcmV2ZXJ0IHN0b3BwZWQgb24gYSBjb25mbGljdCwgYW5kIHdoYXQgZ2l0IHByaW50ZWQuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgcmV2ZXJ0QnVsa09wZXJhdGlvbkNvbW1pdChjb21taXQ6IEJ1bGtPcGVyYXRpb25Db21taXQpOiBQcm9taXNlPEdpdFJldmVydFJlc3VsdD4ge1xuICAgIGF3YWl0IHRoaXMuYXNzZXJ0Q2FuQ29tbWl0TGFyZ2VGaWxlcygpO1xuICAgIGF3YWl0IHRoaXMuYWNxdWlyZUxvY2soKTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5jb21taXRQZW5kaW5nKHsgYm9keTogJycsIHN1YmplY3Q6IGBjaG9yZTogYmVmb3JlIHJldmVydCBvZiAke2NvbW1pdC5vcGVyYXRpb25JZH1gIH0pO1xuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdGhpcy5ydW5HaXQoeyBhbGxvd2VkRXhpdENvZGVzOiBbMCwgMV0sIGdpdEFyZ3VtZW50czogWydyZXZlcnQnLCAnLS1uby1lZGl0JywgY29tbWl0LnNoYV0gfSk7XG4gICAgICByZXR1cm4geyBpc0NvbmZsaWN0OiByZXN1bHQuZXhpdENvZGUgIT09IDAsIG91dHB1dDogYCR7cmVzdWx0LnN0ZG91dH0ke3Jlc3VsdC5zdGRlcnJ9YC50cmltKCkgfTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgYXdhaXQgdGhpcy5yZWxlYXNlTG9jaygpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGEgYnVsayBvcGVyYXRpb24gYmV0d2VlbiB0d28gY29tbWl0cy5cbiAgICpcbiAgICogVGFrZXMgdGhlIGxlYXNlIGxvY2ssIGNvbW1pdHMgd2hhdGV2ZXIgaXMgcGVuZGluZyBhcyBgY2hvcmU6IGJlZm9yZSBPUDxuPmAsIHJ1bnMgdGhlIG9wZXJhdGlvbiwgd2FpdHMgZm9yIGl0c1xuICAgKiBmb2xsb3ctdXBzIHRvIHNldHRsZSwgY29tbWl0cyB0aGUgcmVzdWx0IGFzIGBPUDxuPjogPHN1bW1hcnk+YCBhbmQgcmVsZWFzZXMgdGhlIGxvY2suIEFuIG9wZXJhdGlvbiB0aGF0IHRocm93c1xuICAgKiBzdGlsbCBnZXRzIGl0cyBwYXJ0aWFsIGNoYW5nZXMgY29tbWl0dGVkLCBhcyBgT1A8bj4gaW50ZXJydXB0ZWRgLCBiZWZvcmUgdGhlIGVycm9yIGlzIHJldGhyb3duLlxuICAgKlxuICAgKiBAdHlwZVBhcmFtIFQgLSBUaGUgdHlwZSBvZiB0aGUgb3BlcmF0aW9uJ3MgcmVzdWx0LlxuICAgKiBAcGFyYW0gcGFyYW1zIC0gVGhlIHBhcmFtZXRlcnMuXG4gICAqIEByZXR1cm5zIFRoZSBvcGVyYXRpb24ncyByZXN1bHQsIGl0cyBpZCBhbmQgdGhlIGhhc2ggb2YgaXRzIGNvbW1pdC5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBydW5CdWxrT3BlcmF0aW9uPFQ+KHBhcmFtczogQnVsa09wZXJhdGlvbkdpdFJlcG9SdW5CdWxrT3BlcmF0aW9uUGFyYW1zPFQ+KTogUHJvbWlzZTxHaXRCdWxrT3BlcmF0aW9uUmVzdWx0PFQ+PiB7XG4gICAgYXdhaXQgdGhpcy5hc3NlcnRDYW5Db21taXRMYXJnZUZpbGVzKCk7XG4gICAgYXdhaXQgdGhpcy5hY3F1aXJlTG9jaygpO1xuICAgIGxldCB1bnJlZ2lzdGVyUHJvZ3Jlc3NMaXN0ZW5lcjogKCgpID0+IHZvaWQpIHwgbnVsbCA9IG51bGw7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IG9wZXJhdGlvbklkID0gYXdhaXQgdGhpcy5hbGxvY2F0ZU9wZXJhdGlvbklkKHBhcmFtcy5wbHVnaW5JZCk7XG4gICAgICBhd2FpdCB0aGlzLmNvbW1pdFBlbmRpbmcoeyBib2R5OiAnJywgc3ViamVjdDogYnVpbGRCZWZvcmVPcGVyYXRpb25Db21taXRTdWJqZWN0KG9wZXJhdGlvbklkKSB9KTtcblxuICAgICAgdW5yZWdpc3RlclByb2dyZXNzTGlzdGVuZXIgPSByZWdpc3RlckJ1bGtPcGVyYXRpb25Qcm9ncmVzc0xpc3RlbmVyKHRoaXMuY3JlYXRlVGhyb3R0bGVkSGVhcnRiZWF0KCkpO1xuXG4gICAgICBsZXQgcmVzdWx0OiBUO1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmVzdWx0ID0gYXdhaXQgcGFyYW1zLm9wZXJhdGlvbigpO1xuICAgICAgICBhd2FpdCBwYXJhbXMuc2V0dGxlKCk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBhd2FpdCB0aGlzLmhlYXJ0YmVhdCgpO1xuICAgICAgICBhd2FpdCB0aGlzLmNvbW1pdE9wZXJhdGlvbih7IGlzSW50ZXJydXB0ZWQ6IHRydWUsIG9wZXJhdGlvbklkLCBwbHVnaW5JZDogcGFyYW1zLnBsdWdpbklkIH0pO1xuICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgIH1cblxuICAgICAgYXdhaXQgdGhpcy5oZWFydGJlYXQoKTtcbiAgICAgIGNvbnN0IGNvbW1pdFNoYSA9IGF3YWl0IHRoaXMuY29tbWl0T3BlcmF0aW9uKHsgb3BlcmF0aW9uSWQsIHBsdWdpbklkOiBwYXJhbXMucGx1Z2luSWQsIHN1bW1hcnk6IHBhcmFtcy5zdW1tYXJ5IH0pO1xuICAgICAgcmV0dXJuIHsgY29tbWl0U2hhLCBvcGVyYXRpb25JZCwgcmVzdWx0IH07XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHVucmVnaXN0ZXJQcm9ncmVzc0xpc3RlbmVyPy4oKTtcbiAgICAgIGF3YWl0IHRoaXMucmVsZWFzZUxvY2soKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGFjcXVpcmVMb2NrKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGRlYWRsaW5lID0gRGF0ZS5ub3coKSArIHRoaXMubG9ja1dhaXRUaW1lb3V0SW5NaWxsaXNlY29uZHM7XG4gICAgZm9yICg7Oykge1xuICAgICAgaWYgKGF3YWl0IHRoaXMudHJ5Q3JlYXRlTG9jaygpKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cblxuICAgICAgY29uc3QgbG9jayA9IGF3YWl0IHRoaXMucmVhZExvY2soKTtcbiAgICAgIGlmICghbG9jaykge1xuICAgICAgICAvLyBSZWxlYXNlZCBiZXR3ZWVuIHRoZSB0d28gY2FsbHM6IHRyeSB0byB0YWtlIGl0IGFnYWluIGF0IG9uY2UuXG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICBpZiAodGhpcy5pc1N0YWxlKGxvY2spKSB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVjb3Zlcihsb2NrKTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIGlmIChEYXRlLm5vdygpID49IGRlYWRsaW5lKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICBgVGltZWQgb3V0IHdhaXRpbmcgZm9yICR7bG9jay5vcGVyYXRpb25JZCB8fCAnYW4gb3BlcmF0aW9uJ30gb2YgJHtsb2NrLnBsdWdpbklkfSBvbiAke2xvY2suaG9zdH0gdG8gcmVsZWFzZSB0aGUgYnVsayBvcGVyYXRpb24gbG9jayAke3RoaXMubG9ja0ZpbGVQYXRofS5gXG4gICAgICAgICk7XG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHNldFRpbWVvdXRBc3luYyhMT0NLX1BPTExfSU5URVJWQUxfSU5fTUlMTElTRUNPTkRTKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGFsbG9jYXRlT3BlcmF0aW9uSWQocGx1Z2luSWQ6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3Qgb3BlcmF0aW9uSWQgPSBhd2FpdCB0aGlzLmZpbmROZXh0T3BlcmF0aW9uSWQoKTtcbiAgICBjb25zdCBvd25Mb2NrID0gZW5zdXJlTm9uTnVsbGFibGUodGhpcy5vd25Mb2NrKTtcbiAgICB0aGlzLm93bkxvY2sgPSB7IC4uLm93bkxvY2ssIGhlYXJ0YmVhdEF0OiBEYXRlLm5vdygpLCBvcGVyYXRpb25JZCwgcGx1Z2luSWQgfTtcbiAgICBhd2FpdCB0aGlzLndyaXRlTG9jayh0aGlzLm93bkxvY2spO1xuICAgIHJldHVybiBvcGVyYXRpb25JZDtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgY29tbWl0T3BlcmF0aW9uKHBhcmFtczogQnVsa09wZXJhdGlvbkdpdFJlcG9Db21taXRPcGVyYXRpb25QYXJhbXMpOiBQcm9taXNlPG51bGwgfCBzdHJpbmc+IHtcbiAgICBjb25zdCBjaGFuZ2VkRmlsZUNvdW50ID0gYXdhaXQgdGhpcy5zdGFnZUFsbCgpO1xuICAgIGlmIChjaGFuZ2VkRmlsZUNvdW50ID09PSAwKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgY29uc3QgbWVzc2FnZSA9IGJ1aWxkT3BlcmF0aW9uQ29tbWl0TWVzc2FnZSh7IC4uLnBhcmFtcywgY2hhbmdlZEZpbGVDb3VudCwgdGltZXN0YW1wOiBuZXcgRGF0ZSgpIH0pO1xuICAgIGF3YWl0IHRoaXMuY29tbWl0U3RhZ2VkKG1lc3NhZ2UpO1xuICAgIGNvbnN0IGhlYWRSZXN1bHQgPSBhd2FpdCB0aGlzLnJ1bkdpdCh7IGdpdEFyZ3VtZW50czogWydyZXYtcGFyc2UnLCAnSEVBRCddIH0pO1xuICAgIHJldHVybiBoZWFkUmVzdWx0LnN0ZG91dC50cmltKCk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGNvbW1pdFBlbmRpbmcobWVzc2FnZTogQ29tbWl0TWVzc2FnZSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmIChhd2FpdCB0aGlzLnN0YWdlQWxsKCkgPT09IDApIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgYXdhaXQgdGhpcy5jb21taXRTdGFnZWQobWVzc2FnZSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGNvbW1pdFN0YWdlZChtZXNzYWdlOiBDb21taXRNZXNzYWdlKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgZ2l0QXJndW1lbnRzID0gWydjb21taXQnLCAnLS1xdWlldCcsICctbScsIG1lc3NhZ2Uuc3ViamVjdF07XG4gICAgaWYgKG1lc3NhZ2UuYm9keSkge1xuICAgICAgZ2l0QXJndW1lbnRzLnB1c2goJy1tJywgbWVzc2FnZS5ib2R5KTtcbiAgICB9XG4gICAgZ2l0QXJndW1lbnRzLnB1c2goJy0tJywgJy4nKTtcbiAgICBhd2FpdCB0aGlzLnJ1bkdpdCh7IGdpdEFyZ3VtZW50cyB9KTtcbiAgfVxuXG4gIHByaXZhdGUgY3JlYXRlVGhyb3R0bGVkSGVhcnRiZWF0KCk6ICgpID0+IHZvaWQge1xuICAgIGxldCBsYXN0SGVhcnRiZWF0QXQgPSBEYXRlLm5vdygpO1xuICAgIGxldCBwZW5kaW5nSGVhcnRiZWF0OiBQcm9taXNlPHZvaWQ+ID0gbm9vcEFzeW5jKCk7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gICAgICBpZiAobm93IC0gbGFzdEhlYXJ0YmVhdEF0IDwgSEVBUlRCRUFUX1RIUk9UVExFX0lOX01JTExJU0VDT05EUykge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBsYXN0SGVhcnRiZWF0QXQgPSBub3c7XG4gICAgICBwZW5kaW5nSGVhcnRiZWF0ID0gcGVuZGluZ0hlYXJ0YmVhdC50aGVuKCgpID0+IHRoaXMuaGVhcnRiZWF0KCkpLmNhdGNoKChlcnJvcjogdW5rbm93bikgPT4ge1xuICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gcmVmcmVzaCB0aGUgYnVsayBvcGVyYXRpb24gbG9jayBoZWFydGJlYXQnLCBlcnJvcik7XG4gICAgICB9KTtcbiAgICB9O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBmaW5kTmV4dE9wZXJhdGlvbklkKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgLy8gYC0tYWxsYCBhbHNvIHJlYWRzIGEgcmVwb3NpdG9yeSB3aXRoIG5vIGNvbW1pdCB5ZXQsIHdoZXJlIGEgcGxhaW4gYGdpdCBsb2dgIGZhaWxzLlxuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRoaXMucnVuR2l0KHtcbiAgICAgIGdpdEFyZ3VtZW50czogWydsb2cnLCAnLS1hbGwnLCAnLS1mb3JtYXQ9JXMnLCAnLS1leHRlbmRlZC1yZWdleHAnLCAnLS1ncmVwPV4oY2hvcmU6IGJlZm9yZSApP09QWzAtOV0rJ11cbiAgICB9KTtcbiAgICByZXR1cm4gZ2V0TmV4dE9wZXJhdGlvbklkKHJlc3VsdC5zdGRvdXQuc3BsaXQoL1xccj9cXG4vKSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGhlYXJ0YmVhdCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBsb2NrID0gYXdhaXQgdGhpcy5yZWFkTG9jaygpO1xuICAgIGlmICghbG9jayB8fCAhdGhpcy5pc093bkxvY2sobG9jaykpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgYXdhaXQgdGhpcy53cml0ZUxvY2soeyAuLi5sb2NrLCBoZWFydGJlYXRBdDogRGF0ZS5ub3coKSB9KTtcbiAgfVxuXG4gIHByaXZhdGUgaXNPd25Mb2NrKGxvY2s6IEJ1bGtPcGVyYXRpb25Mb2NrKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMub3duTG9jayAhPT0gbnVsbFxuICAgICAgJiYgbG9jay5ob3N0ID09PSB0aGlzLm93bkxvY2suaG9zdFxuICAgICAgJiYgbG9jay5wcm9jZXNzSWQgPT09IHRoaXMub3duTG9jay5wcm9jZXNzSWRcbiAgICAgICYmIGxvY2suc3RhcnRlZEF0ID09PSB0aGlzLm93bkxvY2suc3RhcnRlZEF0O1xuICB9XG5cbiAgcHJpdmF0ZSBpc1N0YWxlKGxvY2s6IEJ1bGtPcGVyYXRpb25Mb2NrKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIGlzQnVsa09wZXJhdGlvbkxvY2tTdGFsZShsb2NrLCB7XG4gICAgICBib290ZWRBdDogRGF0ZS5ub3coKSAtIHVwdGltZSgpICogTUlMTElTRUNPTkRTX0lOX1NFQ09ORCxcbiAgICAgIGhvc3Q6IGhvc3RuYW1lKCksXG4gICAgICBpc1Byb2Nlc3NBbGl2ZSxcbiAgICAgIG5vdzogRGF0ZS5ub3coKVxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZWFkTG9jaygpOiBQcm9taXNlPEJ1bGtPcGVyYXRpb25Mb2NrIHwgbnVsbD4ge1xuICAgIGxldCB0ZXh0OiBzdHJpbmc7XG4gICAgdHJ5IHtcbiAgICAgIHRleHQgPSBhd2FpdCByZWFkRmlsZSh0aGlzLmxvY2tGaWxlUGF0aCwgJ3V0Zi04Jyk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChpc0Vycm9yV2l0aENvZGUoZXJyb3IsICdFTk9FTlQnKSkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICBjb25zdCB2YWx1ZTogdW5rbm93biA9IEpTT04ucGFyc2UodGV4dCk7XG4gICAgICBpZiAoaXNCdWxrT3BlcmF0aW9uTG9jayh2YWx1ZSkpIHtcbiAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgfVxuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gVXBkYXRlcyBhcmUgYXRvbWljLCBidXQgYSBsb2NrIGJlaW5nIGNyZWF0ZWQgY2FuIGJlIHJlYWQgYmVmb3JlIGl0cyBjb250ZW50IGlzIHdyaXR0ZW4uIFRoZSBmaWxlJ3MgYWdlXG4gICAgICAvLyBzdGFuZHMgaW4gZm9yIHRoZSBoZWFydGJlYXQsIHNvIHN1Y2ggYSBsb2NrIHJlYWRzIGFzIGZyZXNoIGFuZCBhIGNvcnJ1cHQgb25lIGdvZXMgc3RhbGUgbGlrZSBhbnkgb3RoZXIuXG4gICAgfVxuICAgIGNvbnN0IGZpbGVTdGF0cyA9IGF3YWl0IHN0YXQodGhpcy5sb2NrRmlsZVBhdGgpO1xuICAgIHJldHVybiB7XG4gICAgICBoZWFydGJlYXRBdDogZmlsZVN0YXRzLm10aW1lTXMsXG4gICAgICBob3N0OiAnJyxcbiAgICAgIG9wZXJhdGlvbklkOiAnJyxcbiAgICAgIHBsdWdpbklkOiAnJyxcbiAgICAgIHByb2Nlc3NJZDogMCxcbiAgICAgIHByb2Nlc3NTdGFydGVkQXQ6IDAsXG4gICAgICBzdGFydGVkQXQ6IDBcbiAgICB9O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZWNvdmVyKGxvY2s6IEJ1bGtPcGVyYXRpb25Mb2NrKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBvcGVyYXRpb25JZCA9IGxvY2sub3BlcmF0aW9uSWQgfHwgYXdhaXQgdGhpcy5maW5kTmV4dE9wZXJhdGlvbklkKCk7XG4gICAgYXdhaXQgdGhpcy5jb21taXRPcGVyYXRpb24oeyBpc0ludGVycnVwdGVkOiB0cnVlLCBvcGVyYXRpb25JZCwgcGx1Z2luSWQ6IGxvY2sucGx1Z2luSWQgfHwgJ3Vua25vd24nIH0pO1xuICAgIGF3YWl0IHVubGluayh0aGlzLmxvY2tGaWxlUGF0aCkuY2F0Y2goKGVycm9yOiB1bmtub3duKSA9PiB7XG4gICAgICBpZiAoIWlzRXJyb3JXaXRoQ29kZShlcnJvciwgJ0VOT0VOVCcpKSB7XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBvcGVyYXRpb25JZDtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcmVsZWFzZUxvY2soKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgbG9jayA9IGF3YWl0IHRoaXMucmVhZExvY2soKTtcbiAgICBjb25zdCBpc093bkxvY2sgPSBsb2NrICE9PSBudWxsICYmIHRoaXMuaXNPd25Mb2NrKGxvY2spO1xuICAgIHRoaXMub3duTG9jayA9IG51bGw7XG4gICAgaWYgKCFpc093bkxvY2spIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgYXdhaXQgdW5saW5rKHRoaXMubG9ja0ZpbGVQYXRoKS5jYXRjaCgoZXJyb3I6IHVua25vd24pID0+IHtcbiAgICAgIGlmICghaXNFcnJvcldpdGhDb2RlKGVycm9yLCAnRU5PRU5UJykpIHtcbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHJ1bkdpdChwYXJhbXM6IEJ1bGtPcGVyYXRpb25HaXRSZXBvUnVuR2l0UGFyYW1zKTogUHJvbWlzZTxHaXRDb21tYW5kUmVzdWx0PiB7XG4gICAgY29uc3QgYWxsb3dlZEV4aXRDb2RlcyA9IHBhcmFtcy5hbGxvd2VkRXhpdENvZGVzID8/IFswXTtcbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTs7IGF0dGVtcHQrKykge1xuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZXhlY0dpdCh7IGN3ZDogdGhpcy53b3JraW5nRGlyZWN0b3J5LCBnaXRBcmd1bWVudHM6IHBhcmFtcy5naXRBcmd1bWVudHMgfSk7XG4gICAgICBpZiAoYWxsb3dlZEV4aXRDb2Rlcy5pbmNsdWRlcyhyZXN1bHQuZXhpdENvZGUpKSB7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICB9XG4gICAgICBpZiAoYXR0ZW1wdCA8IElOREVYX0xPQ0tfUkVUUllfQ09VTlQgJiYgcmVzdWx0LnN0ZGVyci5pbmNsdWRlcygnaW5kZXgubG9jaycpKSB7XG4gICAgICAgIGF3YWl0IHNldFRpbWVvdXRBc3luYyhJTkRFWF9MT0NLX1JFVFJZX0RFTEFZX0lOX01JTExJU0VDT05EUyk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgdGhyb3cgbmV3IEdpdENvbW1hbmRFcnJvcihwYXJhbXMuZ2l0QXJndW1lbnRzLCByZXN1bHQuZXhpdENvZGUsIHJlc3VsdC5zdGRlcnIpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgc3RhZ2VBbGwoKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICBhd2FpdCB0aGlzLnJ1bkdpdCh7IGdpdEFyZ3VtZW50czogWydhZGQnLCAnLS1hbGwnLCAnLS0nLCAnLiddIH0pO1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRoaXMucnVuR2l0KHsgZ2l0QXJndW1lbnRzOiBbJ2RpZmYnLCAnLS1jYWNoZWQnLCAnLS1uYW1lLW9ubHknLCAnLXonLCAnLS0nLCAnLiddIH0pO1xuICAgIHJldHVybiByZXN1bHQuc3Rkb3V0LnNwbGl0KCdcXDAnKS5maWx0ZXIoQm9vbGVhbikubGVuZ3RoO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyB0cnlDcmVhdGVMb2NrKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIGNvbnN0IGxvY2sgPSBjcmVhdGVMb2NrUmVjb3JkKCk7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHdyaXRlRmlsZSh0aGlzLmxvY2tGaWxlUGF0aCwgSlNPTi5zdHJpbmdpZnkobG9jayksIHsgZmxhZzogJ3d4JyB9KTtcbiAgICAgIHRoaXMub3duTG9jayA9IGxvY2s7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGlzRXJyb3JXaXRoQ29kZShlcnJvciwgJ0VFWElTVCcpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgd3JpdGVMb2NrKGxvY2s6IEJ1bGtPcGVyYXRpb25Mb2NrKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gV3JpdHRlbiBiZXNpZGUgdGhlIGxvY2sgYW5kIHJlbmFtZWQgb3ZlciBpdCwgc28gYSByZWFkZXIgbmV2ZXIgc2VlcyBhIGhhbGYtd3JpdHRlbiBoZWFydGJlYXQuXG4gICAgY29uc3QgdGVtcG9yYXJ5UGF0aCA9IGAke3RoaXMubG9ja0ZpbGVQYXRofS4ke1N0cmluZyhsb2NrLnByb2Nlc3NJZCl9LnRtcGA7XG4gICAgYXdhaXQgd3JpdGVGaWxlKHRlbXBvcmFyeVBhdGgsIEpTT04uc3RyaW5naWZ5KGxvY2spKTtcbiAgICBhd2FpdCByZW5hbWUodGVtcG9yYXJ5UGF0aCwgdGhpcy5sb2NrRmlsZVBhdGgpO1xuICB9XG59XG5cbi8qKlxuICogQW4gZXJyb3IgdGhyb3duIHdoZW4gYSBgZ2l0YCBjb21tYW5kIGZhaWxzLlxuICovXG5leHBvcnQgY2xhc3MgR2l0Q29tbWFuZEVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZS5cbiAgICpcbiAgICogQHBhcmFtIGdpdEFyZ3VtZW50cyAtIFRoZSBhcmd1bWVudHMgYGdpdGAgd2FzIHJ1biB3aXRoLlxuICAgKiBAcGFyYW0gZXhpdENvZGUgLSBUaGUgZXhpdCBjb2RlLlxuICAgKiBAcGFyYW0gc3RkZXJyIC0gV2hhdCBgZ2l0YCBwcmludGVkIHRvIHN0ZGVyci5cbiAgICovXG4gIHB1YmxpYyBjb25zdHJ1Y3RvcihwdWJsaWMgcmVhZG9ubHkgZ2l0QXJndW1lbnRzOiByZWFkb25seSBzdHJpbmdbXSwgcHVibGljIHJlYWRvbmx5IGV4aXRDb2RlOiBudW1iZXIsIHB1YmxpYyByZWFkb25seSBzdGRlcnI6IHN0cmluZykge1xuICAgIHN1cGVyKGBnaXQgJHtnaXRBcmd1bWVudHMuam9pbignICcpfSBmYWlsZWQgd2l0aCBleGl0IGNvZGUgJHtTdHJpbmcoZXhpdENvZGUpfTogJHtzdGRlcnIudHJpbSgpfWApO1xuICAgIHRoaXMubmFtZSA9ICdHaXRDb21tYW5kRXJyb3InO1xuICB9XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZUxvY2tSZWNvcmQoKTogQnVsa09wZXJhdGlvbkxvY2sge1xuICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICByZXR1cm4ge1xuICAgIGhlYXJ0YmVhdEF0OiBub3csXG4gICAgaG9zdDogaG9zdG5hbWUoKSxcbiAgICBvcGVyYXRpb25JZDogJycsXG4gICAgcGx1Z2luSWQ6ICcnLFxuICAgIHByb2Nlc3NJZDogcHJvY2Vzcy5waWQsXG4gICAgcHJvY2Vzc1N0YXJ0ZWRBdDogZ2V0UHJvY2Vzc1N0YXJ0ZWRBdCgpLFxuICAgIHN0YXJ0ZWRBdDogbm93XG4gIH07XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGV4ZWNHaXQocGFyYW1zOiBFeGVjR2l0UGFyYW1zKTogUHJvbWlzZTxHaXRDb21tYW5kUmVzdWx0PiB7XG4gIHJldHVybiBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgZXhlY0ZpbGUoJ2dpdCcsIFsuLi5wYXJhbXMuZ2l0QXJndW1lbnRzXSwgeyBjd2Q6IHBhcmFtcy5jd2QsIG1heEJ1ZmZlcjogTUFYX0JVRkZFUl9JTl9CWVRFUywgd2luZG93c0hpZGU6IHRydWUgfSwgKGVycm9yLCBzdGRvdXQsIHN0ZGVycikgPT4ge1xuICAgICAgaWYgKGVycm9yICYmIHR5cGVvZiBlcnJvci5jb2RlICE9PSAnbnVtYmVyJykge1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gcnVuIGdpdCAke3BhcmFtcy5naXRBcmd1bWVudHMuam9pbignICcpfWAsIHsgY2F1c2U6IGVycm9yIH0pKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgcmVzb2x2ZSh7IGV4aXRDb2RlOiB0eXBlb2YgZXJyb3I/LmNvZGUgPT09ICdudW1iZXInID8gZXJyb3IuY29kZSA6IDAsIHN0ZGVyciwgc3Rkb3V0IH0pO1xuICAgIH0pO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gZ2V0UHJvY2Vzc1N0YXJ0ZWRBdCgpOiBudW1iZXIge1xuICByZXR1cm4gTWF0aC5yb3VuZChwZXJmb3JtYW5jZS50aW1lT3JpZ2luKTtcbn1cblxuZnVuY3Rpb24gaXNFcnJvcldpdGhDb2RlKGVycm9yOiB1bmtub3duLCBjb2RlOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgcmV0dXJuIHR5cGVvZiBlcnJvciA9PT0gJ29iamVjdCcgJiYgZXJyb3IgIT09IG51bGwgJiYgJ2NvZGUnIGluIGVycm9yICYmIGVycm9yLmNvZGUgPT09IGNvZGU7XG59XG5cbmZ1bmN0aW9uIGlzUHJvY2Vzc0FsaXZlKHByb2Nlc3NJZDogbnVtYmVyKTogYm9vbGVhbiB8IG51bGwge1xuICBpZiAocHJvY2Vzc0lkIDw9IDApIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgdHJ5IHtcbiAgICBwcm9jZXNzLmtpbGwocHJvY2Vzc0lkLCAwKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICByZXR1cm4gIWlzRXJyb3JXaXRoQ29kZShlcnJvciwgJ0VTUkNIJykgJiYgKGlzRXJyb3JXaXRoQ29kZShlcnJvciwgJ0VQRVJNJykgfHwgbnVsbCk7XG4gIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFnQkEsU0FBUyx1QkFBdUI7QUFDaEMsU0FBUyxpQkFBaUI7QUFDMUIsU0FBUyxvQkFBb0I7QUFDN0IsU0FBUyx5QkFBeUI7QUFDbEMsU0FBUyxnQkFBZ0I7QUFDekI7QUFBQSxFQUNFO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLE9BQ0s7QUFDUDtBQUFBLEVBQ0U7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMsWUFBWTtBQU9yQixTQUFTLDZDQUE2QztBQUN0RDtBQUFBLEVBQ0U7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsT0FDSztBQXNHUCxN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