sonamu 0.7.53 → 0.8.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/dist/api/config.d.ts +9 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/sonamu.d.ts +21 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +159 -65
- package/dist/auth/plugins/entity-definitions/anonymous.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/anonymous.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/anonymous.js +23 -0
- package/dist/auth/plugins/entity-definitions/api-key.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/api-key.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/api-key.js +199 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts +6 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/index.js +20 -2
- package/dist/auth/plugins/entity-definitions/jwt.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/jwt.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/jwt.js +67 -0
- package/dist/auth/plugins/entity-definitions/organization.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/organization.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/organization.js +424 -0
- package/dist/auth/plugins/entity-definitions/passkey.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/passkey.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/passkey.js +129 -0
- package/dist/auth/plugins/entity-definitions/sso.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/sso.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/sso.js +110 -0
- package/dist/auth/plugins/entity-definitions/types.d.ts +1 -1
- package/dist/auth/plugins/entity-definitions/types.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/types.js +1 -1
- package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/admin.js +2 -4
- package/dist/auth/plugins/wrappers/anonymous.d.ts +18 -0
- package/dist/auth/plugins/wrappers/anonymous.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/anonymous.js +26 -0
- package/dist/auth/plugins/wrappers/api-key.d.ts +18 -0
- package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/api-key.js +38 -0
- package/dist/auth/plugins/wrappers/index.d.ts +6 -0
- package/dist/auth/plugins/wrappers/index.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/index.js +7 -1
- package/dist/auth/plugins/wrappers/jwt.d.ts +18 -0
- package/dist/auth/plugins/wrappers/jwt.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/jwt.js +30 -0
- package/dist/auth/plugins/wrappers/organization.d.ts +18 -0
- package/dist/auth/plugins/wrappers/organization.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/organization.js +67 -0
- package/dist/auth/plugins/wrappers/passkey.d.ts +18 -0
- package/dist/auth/plugins/wrappers/passkey.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/passkey.js +32 -0
- package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/phone-number.js +2 -4
- package/dist/auth/plugins/wrappers/sso.d.ts +853 -0
- package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/sso.js +36 -0
- package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/two-factor.js +2 -4
- package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/username.js +2 -4
- package/dist/bin/build-config.d.ts +2 -2
- package/dist/bin/build-config.js +6 -7
- package/dist/bin/cli.js +417 -32
- package/dist/bin/fixture.d.ts +27 -0
- package/dist/bin/fixture.d.ts.map +1 -0
- package/dist/bin/fixture.js +245 -0
- package/dist/cache/decorator.d.ts +4 -3
- package/dist/cache/decorator.d.ts.map +1 -1
- package/dist/cache/decorator.js +5 -4
- package/dist/cone/cone-generator.d.ts +33 -0
- package/dist/cone/cone-generator.d.ts.map +1 -0
- package/dist/cone/cone-generator.js +286 -0
- package/dist/database/_batch_update.d.ts.map +1 -1
- package/dist/database/_batch_update.js +16 -2
- package/dist/database/puri-subset.test-d.js +1 -1
- package/dist/database/puri-subset.types.d.ts +1 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/database/puri.d.ts +4 -0
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +20 -2
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +19 -3
- package/dist/dict/en.d.ts +15 -0
- package/dist/dict/en.d.ts.map +1 -1
- package/dist/dict/en.js +2 -1
- package/dist/dict/ko.d.ts +15 -0
- package/dist/dict/ko.d.ts.map +1 -1
- package/dist/dict/ko.js +2 -1
- package/dist/dict/rc-keys.d.ts +28 -0
- package/dist/dict/rc-keys.d.ts.map +1 -1
- package/dist/dict/rc-keys.js +31 -1
- package/dist/dict/sd.d.ts.map +1 -1
- package/dist/dict/sd.js +20 -4
- package/dist/entity/entity-manager.d.ts +298 -2
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +4 -1
- package/dist/entity/entity-template-cone.d.ts +14 -0
- package/dist/entity/entity-template-cone.d.ts.map +1 -0
- package/dist/entity/entity-template-cone.js +222 -0
- package/dist/entity/entity.d.ts +47 -2
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +161 -14
- package/dist/ssr/renderer.js +3 -3
- package/dist/syncer/api-parser.js +12 -1
- package/dist/syncer/checksum.d.ts +0 -14
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +1 -23
- package/dist/syncer/syncer-actions.d.ts.map +1 -1
- package/dist/syncer/syncer-actions.js +8 -2
- package/dist/syncer/syncer.d.ts +1 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +17 -10
- package/dist/tasks/workflow-manager.d.ts +13 -1
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +18 -1
- package/dist/template/entity-converter.js +4 -4
- package/dist/template/helpers.d.ts +10 -0
- package/dist/template/helpers.d.ts.map +1 -1
- package/dist/template/helpers.js +48 -1
- package/dist/template/implementations/entry-server.template.d.ts +1 -1
- package/dist/template/implementations/entry-server.template.js +7 -2
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +5 -1
- package/dist/template/implementations/generated_http.template.d.ts +1 -0
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_http.template.js +6 -2
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +29 -8
- package/dist/template/implementations/queries.template.d.ts.map +1 -1
- package/dist/template/implementations/queries.template.js +9 -1
- package/dist/template/implementations/sd.template.d.ts +1 -1
- package/dist/template/implementations/sd.template.d.ts.map +1 -1
- package/dist/template/implementations/sd.template.js +28 -4
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/services.template.js +12 -12
- package/dist/template/implementations/view_form.template.d.ts +11 -7
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +97 -87
- package/dist/template/implementations/view_list.template.d.ts +3 -3
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +115 -109
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.js +18 -14
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +95 -7
- package/dist/testing/_relation-graph.js +1 -1
- package/dist/testing/data-explorer.d.ts +61 -0
- package/dist/testing/data-explorer.d.ts.map +1 -0
- package/dist/testing/data-explorer.js +274 -0
- package/dist/testing/faker-mappings.d.ts +20 -0
- package/dist/testing/faker-mappings.d.ts.map +1 -0
- package/dist/testing/faker-mappings.js +421 -0
- package/dist/testing/fixture-generator.d.ts +161 -0
- package/dist/testing/fixture-generator.d.ts.map +1 -0
- package/dist/testing/fixture-generator.js +954 -0
- package/dist/testing/fixture-manager.d.ts +6 -1
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +72 -4
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -1
- package/dist/types/types.d.ts +1520 -26
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +136 -22
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +9 -4
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +303 -24
- package/dist/ui-web/assets/index-CsUr-_pV.js +254 -0
- package/dist/ui-web/assets/index-T42zzs1K.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/fs-utils.d.ts +2 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +14 -3
- package/package.json +19 -11
- package/src/api/config.ts +12 -1
- package/src/api/sonamu.ts +179 -65
- package/src/auth/plugins/entity-definitions/anonymous.ts +17 -0
- package/src/auth/plugins/entity-definitions/api-key.ts +93 -0
- package/src/auth/plugins/entity-definitions/index.ts +18 -0
- package/src/auth/plugins/entity-definitions/jwt.ts +35 -0
- package/src/auth/plugins/entity-definitions/organization.ts +215 -0
- package/src/auth/plugins/entity-definitions/passkey.ts +64 -0
- package/src/auth/plugins/entity-definitions/sso.ts +62 -0
- package/src/auth/plugins/entity-definitions/types.ts +11 -1
- package/src/auth/plugins/wrappers/admin.ts +1 -3
- package/src/auth/plugins/wrappers/anonymous.ts +30 -0
- package/src/auth/plugins/wrappers/api-key.ts +42 -0
- package/src/auth/plugins/wrappers/index.ts +6 -0
- package/src/auth/plugins/wrappers/jwt.ts +34 -0
- package/src/auth/plugins/wrappers/organization.ts +73 -0
- package/src/auth/plugins/wrappers/passkey.ts +36 -0
- package/src/auth/plugins/wrappers/phone-number.ts +1 -3
- package/src/auth/plugins/wrappers/sso.ts +40 -0
- package/src/auth/plugins/wrappers/two-factor.ts +1 -3
- package/src/auth/plugins/wrappers/username.ts +1 -3
- package/src/bin/build-config.ts +6 -6
- package/src/bin/cli.ts +452 -31
- package/src/bin/fixture.ts +302 -0
- package/src/cache/decorator.ts +4 -3
- package/src/cone/cone-generator.ts +363 -0
- package/src/database/_batch_update.ts +11 -0
- package/src/database/puri-subset.test-d.ts +13 -13
- package/src/database/puri-subset.types.ts +1 -1
- package/src/database/puri.ts +43 -1
- package/src/database/upsert-builder.ts +16 -2
- package/src/dict/en.ts +1 -0
- package/src/dict/ko.ts +1 -0
- package/src/dict/rc-keys.ts +32 -0
- package/src/dict/sd.ts +23 -3
- package/src/entity/entity-manager.ts +4 -0
- package/src/entity/entity-template-cone.ts +298 -0
- package/src/entity/entity.ts +189 -13
- package/src/shared/app.shared.ts.txt +5 -0
- package/src/shared/web.shared.ts.txt +9 -5
- package/src/skills/project/README.md +21 -0
- package/src/skills/project/architecture.md +373 -0
- package/src/skills/project/business-logic.md +270 -0
- package/src/skills/project/requirements.md +160 -0
- package/src/skills/sonamu/SKILL.md +168 -3
- package/src/skills/sonamu/api.md +102 -0
- package/src/skills/sonamu/database.md +220 -1
- package/src/skills/sonamu/entity-relations.md +89 -1
- package/src/skills/sonamu/fixture-cli.md +501 -0
- package/src/skills/sonamu/frontend.md +214 -0
- package/src/skills/sonamu/i18n.md +95 -0
- package/src/skills/sonamu/model.md +153 -0
- package/src/skills/sonamu/project-init.md +178 -8
- package/src/skills/sonamu/scaffolding.md +112 -0
- package/src/skills/sonamu/subset.md +9 -3
- package/src/skills/sonamu/testing.md +287 -2
- package/src/skills/sonamu/workflow.md +70 -5
- package/src/ssr/renderer.ts +2 -2
- package/src/syncer/api-parser.ts +12 -0
- package/src/syncer/checksum.ts +0 -38
- package/src/syncer/syncer-actions.ts +7 -1
- package/src/syncer/syncer.ts +16 -5
- package/src/tasks/workflow-manager.ts +29 -8
- package/src/template/entity-converter.ts +3 -3
- package/src/template/helpers.ts +49 -0
- package/src/template/implementations/entry-server.template.ts +1 -1
- package/src/template/implementations/generated.template.ts +4 -0
- package/src/template/implementations/generated_http.template.ts +1 -0
- package/src/template/implementations/generated_sso.template.ts +40 -11
- package/src/template/implementations/queries.template.ts +8 -0
- package/src/template/implementations/sd.template.ts +22 -3
- package/src/template/implementations/services.template.ts +11 -10
- package/src/template/implementations/view_form.template.ts +111 -101
- package/src/template/implementations/view_list.template.ts +120 -119
- package/src/template/implementations/view_search_input.template.ts +17 -13
- package/src/template/zod-converter.ts +103 -6
- package/src/testing/_relation-graph.ts +1 -1
- package/src/testing/data-explorer.ts +427 -0
- package/src/testing/faker-mappings.ts +434 -0
- package/src/testing/fixture-generator.ts +1166 -0
- package/src/testing/fixture-manager.ts +91 -6
- package/src/testing/index.ts +3 -0
- package/src/types/types.ts +222 -26
- package/src/ui/ai-client.ts +9 -1
- package/src/ui/api.ts +429 -23
- package/src/utils/fs-utils.ts +14 -1
- package/dist/template/implementations/view_enums_select.template.d.ts +0 -17
- package/dist/template/implementations/view_enums_select.template.d.ts.map +0 -1
- package/dist/template/implementations/view_enums_select.template.js +0 -62
- package/dist/template/implementations/view_id_async_select.template.d.ts +0 -17
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +0 -1
- package/dist/template/implementations/view_id_async_select.template.js +0 -125
- package/dist/ui-web/assets/index-Bd_2AkLb.css +0 -1
- package/dist/ui-web/assets/index-BpSbhQWo.js +0 -225
- package/src/template/implementations/view_enums_select.template.ts +0 -65
- package/src/template/implementations/view_id_async_select.template.ts +0 -139
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { isBelongsToOneRelationProp, isOneToOneRelationProp, isRelationProp } from "../types/types.js";
|
|
3
|
+
import { DataExplorer } from "./data-explorer.js";
|
|
4
|
+
import { fakerMappings } from "./faker-mappings.js";
|
|
5
|
+
import { FixtureManager } from "./fixture-manager.js";
|
|
6
|
+
export class FixtureGenerator {
|
|
7
|
+
sourceDb;
|
|
8
|
+
targetDbName;
|
|
9
|
+
entityManager;
|
|
10
|
+
dataExplorer;
|
|
11
|
+
locale;
|
|
12
|
+
mappings;
|
|
13
|
+
llmCache = new Map();
|
|
14
|
+
options;
|
|
15
|
+
constructor(sourceDb, // FixtureManager.insertFixtures가 dbName 문자열을 받기 때문에 직접 사용하지 않습니다
|
|
16
|
+
// 미래 확장성을 위해 API 시그니처에는 포함시켰습니다
|
|
17
|
+
_targetDb, targetDbName, entityManager, options){
|
|
18
|
+
this.sourceDb = sourceDb;
|
|
19
|
+
this.targetDbName = targetDbName;
|
|
20
|
+
this.entityManager = entityManager;
|
|
21
|
+
this.dataExplorer = new DataExplorer(sourceDb, entityManager);
|
|
22
|
+
this.locale = options?.locale || "ko";
|
|
23
|
+
this.mappings = fakerMappings;
|
|
24
|
+
this.options = {
|
|
25
|
+
locale: options?.locale || "ko",
|
|
26
|
+
useLLM: options?.useLLM || false,
|
|
27
|
+
enableLLMCache: options?.enableLLMCache !== false,
|
|
28
|
+
llmModel: options?.llmModel || "claude-sonnet-4-5"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fixture 생성 (단일)
|
|
33
|
+
* @returns 생성된 fixture 데이터 (메모리 상)
|
|
34
|
+
*/ async generate(entityName, overrides = {}, context = this.createContext()) {
|
|
35
|
+
const entity = this.entityManager.get(entityName);
|
|
36
|
+
const tempId = `${entityName}#temp#${Date.now()}`; // 임시 ID
|
|
37
|
+
// 각 prop별 값 생성
|
|
38
|
+
const fixture = {};
|
|
39
|
+
for (const prop of entity.props){
|
|
40
|
+
// Virtual prop은 스킵
|
|
41
|
+
if ("virtual" in prop && prop.virtual) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// override가 있으면 사용
|
|
45
|
+
if (prop.name in overrides) {
|
|
46
|
+
fixture[prop.name] = overrides[prop.name];
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// cone에서 생성 전략 확인
|
|
50
|
+
const cone = prop.cone;
|
|
51
|
+
// 1. Relation prop 처리
|
|
52
|
+
if (isRelationProp(prop)) {
|
|
53
|
+
const relationValue = await this.generateRelationValue(entity, prop, context);
|
|
54
|
+
// BelongsToOne, OneToOne(hasJoinColumn)의 경우 foreign key 컬럼명으로 저장
|
|
55
|
+
if (isBelongsToOneRelationProp(prop) || isOneToOneRelationProp(prop) && prop.hasJoinColumn) {
|
|
56
|
+
fixture[`${prop.name}_id`] = relationValue;
|
|
57
|
+
} else {
|
|
58
|
+
fixture[prop.name] = relationValue;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// 2. fixtureGenerator 사용
|
|
63
|
+
if (cone?.fixtureGenerator) {
|
|
64
|
+
fixture[prop.name] = await this.executeGenerator(cone.fixtureGenerator, prop, entity);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// 2.5. fixtureHint + LLM 사용
|
|
68
|
+
if (cone?.fixtureHint && this.options.useLLM) {
|
|
69
|
+
try {
|
|
70
|
+
fixture[prop.name] = await this.generateWithLLM(cone.fixtureHint, prop, entity);
|
|
71
|
+
continue;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn(`[FixtureGenerator] LLM generation failed for ${entity.id}.${prop.name}, falling back to default`, error instanceof Error ? error.message : error);
|
|
74
|
+
// fallback: fixtureDefault 또는 기본값으로 계속
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 3. fixtureDefault 사용
|
|
78
|
+
if (cone?.fixtureDefault !== undefined) {
|
|
79
|
+
fixture[prop.name] = cone.fixtureDefault;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// 4. 타입별 기본 생성
|
|
83
|
+
fixture[prop.name] = await this.generateDefaultValue(prop, entity);
|
|
84
|
+
}
|
|
85
|
+
// 5. password 필드 암호화
|
|
86
|
+
if ("password" in fixture && fixture.password && typeof fixture.password === "string") {
|
|
87
|
+
const bcrypt = await import("bcrypt");
|
|
88
|
+
fixture.password = await bcrypt.hash(fixture.password, 10);
|
|
89
|
+
}
|
|
90
|
+
context.fixtures.set(tempId, fixture);
|
|
91
|
+
return fixture;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Relation 값 생성 + 자동 Import
|
|
95
|
+
*/ async generateRelationValue(entity, prop, context) {
|
|
96
|
+
if (!isRelationProp(prop)) {
|
|
97
|
+
throw new Error(`FixtureGenerator: ${entity.id}.${prop.name} is not a relation prop`);
|
|
98
|
+
}
|
|
99
|
+
// BelongsToOne, OneToOne(hasJoinColumn)만 처리
|
|
100
|
+
if (!isBelongsToOneRelationProp(prop) && !(isOneToOneRelationProp(prop) && prop.hasJoinColumn)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const cone = prop.cone;
|
|
104
|
+
const dataSource = cone?.dataSource;
|
|
105
|
+
// DataExplorer로 참조 데이터 조회 (sourceDb)
|
|
106
|
+
// 관계 체인을 따라가기 위해 exploreWithRelations 사용
|
|
107
|
+
if (dataSource) {
|
|
108
|
+
const cacheKey = `${prop.with}:${JSON.stringify(dataSource)}`;
|
|
109
|
+
if (!context.referenceCache.has(cacheKey)) {
|
|
110
|
+
const exploreResult = await this.dataExplorer.exploreWithRelations(prop.with, {
|
|
111
|
+
strategy: dataSource.strategy,
|
|
112
|
+
limit: dataSource.config?.limit || 10,
|
|
113
|
+
includeRelations: true,
|
|
114
|
+
maxDepth: 3,
|
|
115
|
+
...dataSource.config
|
|
116
|
+
});
|
|
117
|
+
context.referenceCache.set(cacheKey, exploreResult.main.records);
|
|
118
|
+
// 조회한 데이터와 관계된 모든 엔티티를 targetDb에 import
|
|
119
|
+
await this.importExploreResult(exploreResult, context);
|
|
120
|
+
}
|
|
121
|
+
const candidates = context.referenceCache.get(cacheKey);
|
|
122
|
+
if (candidates && candidates.length > 0) {
|
|
123
|
+
// 랜덤하게 하나 선택
|
|
124
|
+
const selected = candidates[Math.floor(Math.random() * candidates.length)];
|
|
125
|
+
return selected.id;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// dataSource가 없을 때 자동으로 fixture DB에서 조회 시도
|
|
129
|
+
// 관계 체인을 따라가기 위해 exploreWithRelations 사용
|
|
130
|
+
const autoKey = `${prop.with}:auto`;
|
|
131
|
+
if (!context.referenceCache.has(autoKey)) {
|
|
132
|
+
// fixture DB(sourceDb)에서 자동 조회 (관계 포함)
|
|
133
|
+
const autoExploreResult = await this.dataExplorer.exploreWithRelations(prop.with, {
|
|
134
|
+
strategy: "random",
|
|
135
|
+
limit: 10,
|
|
136
|
+
includeRelations: true,
|
|
137
|
+
maxDepth: 3
|
|
138
|
+
});
|
|
139
|
+
context.referenceCache.set(autoKey, autoExploreResult.main.records);
|
|
140
|
+
// 조회한 데이터와 관계된 모든 엔티티를 targetDb에 import
|
|
141
|
+
if (autoExploreResult.main.records.length > 0) {
|
|
142
|
+
await this.importExploreResult(autoExploreResult, context);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const autoCandidates = context.referenceCache.get(autoKey);
|
|
146
|
+
if (autoCandidates && autoCandidates.length > 0) {
|
|
147
|
+
// 랜덤하게 하나 선택
|
|
148
|
+
const selected = autoCandidates[Math.floor(Math.random() * autoCandidates.length)];
|
|
149
|
+
return selected.id;
|
|
150
|
+
}
|
|
151
|
+
// 참조 데이터가 없으면 null 반환 (nullable인 경우)
|
|
152
|
+
if (prop.nullable) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// nullable이 아니고 데이터도 없으면 에러
|
|
156
|
+
throw new Error(`FixtureGenerator: ${entity.id}.${prop.name}에 필요한 ${prop.with} 데이터가 없습니다. ` + `먼저 ${prop.with}를 생성하거나 cone.dataSource를 설정하세요.`);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* ExploreWithRelations 결과를 targetDb에 import
|
|
160
|
+
*
|
|
161
|
+
* 관계 체인을 따라간 결과(main + related)를 모두 import합니다.
|
|
162
|
+
* 의존성 순서는 FixtureManager.insertFixtures가 자동으로 처리합니다.
|
|
163
|
+
*/ async importExploreResult(exploreResult, context) {
|
|
164
|
+
const allFixtureRecords = [];
|
|
165
|
+
// 1. Related entities import (Company, Department 등)
|
|
166
|
+
for (const [entityId, records] of exploreResult.related.entries()){
|
|
167
|
+
const entity = this.entityManager.get(entityId);
|
|
168
|
+
const recordsToImport = [];
|
|
169
|
+
console.log(chalk.cyan(`Importing related entity: ${entityId} (${records.length} records)`));
|
|
170
|
+
for (const record of records){
|
|
171
|
+
const recordKey = `${entityId}#${record.id}`;
|
|
172
|
+
if (!context.importedRecords.has(recordKey)) {
|
|
173
|
+
recordsToImport.push(record);
|
|
174
|
+
context.importedRecords.add(recordKey);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (recordsToImport.length > 0) {
|
|
178
|
+
for (const record of recordsToImport){
|
|
179
|
+
console.log(chalk.gray(` - Processing ${entityId} record:`, JSON.stringify(record).slice(0, 100)));
|
|
180
|
+
const fixtureRecords = await FixtureManager.createFixtureRecord(entity, record, {
|
|
181
|
+
_db: this.sourceDb,
|
|
182
|
+
singleRecord: true
|
|
183
|
+
});
|
|
184
|
+
allFixtureRecords.push(...fixtureRecords);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 2. Main entity import (Employee 등)
|
|
189
|
+
const mainEntity = this.entityManager.get(exploreResult.main.entityId);
|
|
190
|
+
const mainRecordsToImport = [];
|
|
191
|
+
console.log(chalk.cyan(`Importing main entity: ${exploreResult.main.entityId} (${exploreResult.main.records.length} records)`));
|
|
192
|
+
for (const record of exploreResult.main.records){
|
|
193
|
+
const recordKey = `${exploreResult.main.entityId}#${record.id}`;
|
|
194
|
+
if (!context.importedRecords.has(recordKey)) {
|
|
195
|
+
mainRecordsToImport.push(record);
|
|
196
|
+
context.importedRecords.add(recordKey);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (mainRecordsToImport.length > 0) {
|
|
200
|
+
for (const record of mainRecordsToImport){
|
|
201
|
+
console.log(chalk.gray(` - Processing ${exploreResult.main.entityId} record:`, JSON.stringify(record).slice(0, 100)));
|
|
202
|
+
const fixtureRecords = await FixtureManager.createFixtureRecord(mainEntity, record, {
|
|
203
|
+
_db: this.sourceDb,
|
|
204
|
+
singleRecord: true
|
|
205
|
+
});
|
|
206
|
+
allFixtureRecords.push(...fixtureRecords);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// 3. 모든 fixture를 한 번에 삽입 (의존성 순서 자동 처리)
|
|
210
|
+
if (allFixtureRecords.length > 0) {
|
|
211
|
+
await FixtureManager.insertFixtures(this.targetDbName, allFixtureRecords);
|
|
212
|
+
console.log(chalk.green(`Auto-imported ${exploreResult.main.entityId} with relations: ` + `${exploreResult.main.records.length} main + ${exploreResult.related.size} related entities`));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* fixtureGenerator 실행 (Faker.js만 지원)
|
|
217
|
+
*
|
|
218
|
+
* faker.* 형식의 표현식을 안전하게 파싱하여 실행합니다.
|
|
219
|
+
* 예: "faker.internet.email()" → faker.internet.email()
|
|
220
|
+
* 예: "faker.lorem.words(3)" → faker.lorem.words(3)
|
|
221
|
+
*/ async executeGenerator(generator, prop, entity) {
|
|
222
|
+
// Faker.js 표현식만 지원
|
|
223
|
+
if (generator.startsWith("faker.")) {
|
|
224
|
+
// username이나 name 필드는 한국어 faker 사용
|
|
225
|
+
const isNameField = prop.name === "username" || prop.name === "name";
|
|
226
|
+
const fakerModule = await import("@faker-js/faker");
|
|
227
|
+
const faker = isNameField ? fakerModule.fakerKO : fakerModule.faker;
|
|
228
|
+
const expr = generator.slice(6); // "faker." 제거
|
|
229
|
+
try {
|
|
230
|
+
// 함수 경로와 인자 파싱
|
|
231
|
+
const match = expr.match(/^([\w.]+)(?:\((.*?)\))?$/);
|
|
232
|
+
if (!match) {
|
|
233
|
+
throw new Error(`FixtureGenerator: Invalid faker expression for ${prop.name}: ${generator}`);
|
|
234
|
+
}
|
|
235
|
+
const [, path, argsStr] = match;
|
|
236
|
+
const parts = path.split(".");
|
|
237
|
+
// faker 객체에서 함수 찾기
|
|
238
|
+
let fn = faker;
|
|
239
|
+
for (const part of parts){
|
|
240
|
+
if (typeof fn === "object" && fn !== null && part in fn) {
|
|
241
|
+
fn = fn[part];
|
|
242
|
+
} else {
|
|
243
|
+
throw new Error(`FixtureGenerator: Invalid faker path for ${prop.name}: faker.${path}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// 함수가 아니면 에러
|
|
247
|
+
if (typeof fn !== "function") {
|
|
248
|
+
throw new Error(`FixtureGenerator: faker.${path} is not a function (for ${prop.name})`);
|
|
249
|
+
}
|
|
250
|
+
// 인자 파싱 (JSON 형식만 지원)
|
|
251
|
+
let args = [];
|
|
252
|
+
if (argsStr?.trim()) {
|
|
253
|
+
try {
|
|
254
|
+
// JSON 배열로 파싱 시도
|
|
255
|
+
const parsed = JSON.parse(`[${argsStr}]`);
|
|
256
|
+
args = Array.isArray(parsed) ? parsed : [
|
|
257
|
+
parsed
|
|
258
|
+
];
|
|
259
|
+
} catch {
|
|
260
|
+
// 숫자나 문자열 단일 인자 처리
|
|
261
|
+
const trimmed = argsStr.trim();
|
|
262
|
+
if (!Number.isNaN(Number(trimmed))) {
|
|
263
|
+
args = [
|
|
264
|
+
Number(trimmed)
|
|
265
|
+
];
|
|
266
|
+
} else if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
267
|
+
args = [
|
|
268
|
+
trimmed.slice(1, -1)
|
|
269
|
+
];
|
|
270
|
+
} else {
|
|
271
|
+
throw new Error(`FixtureGenerator: Cannot parse arguments for ${prop.name}: ${argsStr}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return fn(...args);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.log(chalk.yellow(`Failed to execute generator "${generator}" for ${prop.name}, falling back to default:`), error);
|
|
278
|
+
return this.generateDefaultValue(prop, entity);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// faker 이외의 표현식은 지원하지 않음
|
|
282
|
+
console.log(chalk.yellow(`Unsupported generator expression for ${prop.name}: ${generator}. Only faker.* expressions are supported. Using default value.`));
|
|
283
|
+
return this.generateDefaultValue(prop, entity);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 필드의 타입과 이름을 분석하여 적절한 기본값을 생성합니다.
|
|
287
|
+
*
|
|
288
|
+
* 우선순위:
|
|
289
|
+
* 1. 필드명 패턴 매칭 (salary, budget 등 의미있는 데이터)
|
|
290
|
+
* 2. 특수 케이스 (Department name 등 도메인 지식)
|
|
291
|
+
* 3. 배열 타입 (JSON 배열)
|
|
292
|
+
* 4. Enum 타입
|
|
293
|
+
* 5. 타입별 기본값
|
|
294
|
+
*/ async generateDefaultValue(prop, entity) {
|
|
295
|
+
const fakerModule = await import("@faker-js/faker");
|
|
296
|
+
const faker = fakerModule.faker;
|
|
297
|
+
const fakerKO = fakerModule.fakerKO;
|
|
298
|
+
const fakerJA = fakerModule.fakerJA;
|
|
299
|
+
const localeFaker = this.locale === "ko" ? fakerKO : this.locale === "ja" ? fakerJA : faker;
|
|
300
|
+
/**
|
|
301
|
+
* 1. 필드명에서 의미를 추론하여 현실적인 데이터를 생성합니다.
|
|
302
|
+
* 예: salary → 30M~150M (한국 연봉 범위)
|
|
303
|
+
* budget → 10M~500M (프로젝트 예산 범위)
|
|
304
|
+
*/ const localeMappings = this.mappings[this.locale] || this.mappings.en;
|
|
305
|
+
const normalizedName = prop.name.toLowerCase().replace(/_/g, "");
|
|
306
|
+
for (const [pattern, config] of Object.entries(localeMappings.field_patterns)){
|
|
307
|
+
if (normalizedName.includes(pattern.toLowerCase())) {
|
|
308
|
+
try {
|
|
309
|
+
return await this.executeFakerExpression(config.faker, prop);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.log(chalk.yellow(`Failed to execute field pattern "${pattern}" for ${prop.name}, falling back:`), error);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 2. Department name은 한국어 부서명 목록에서 선택합니다.
|
|
318
|
+
* 고유성을 위해 70% 확률로 prefix/suffix를 추가합니다.
|
|
319
|
+
*/ if (entity?.id === "Department" && prop.name === "name") {
|
|
320
|
+
const departments = [
|
|
321
|
+
"개발팀",
|
|
322
|
+
"기획팀",
|
|
323
|
+
"마케팅팀",
|
|
324
|
+
"영업팀",
|
|
325
|
+
"인사팀",
|
|
326
|
+
"총무팀",
|
|
327
|
+
"재무팀",
|
|
328
|
+
"회계팀",
|
|
329
|
+
"법무팀",
|
|
330
|
+
"디자인팀",
|
|
331
|
+
"IT팀",
|
|
332
|
+
"고객지원팀",
|
|
333
|
+
"품질관리팀",
|
|
334
|
+
"연구개발팀",
|
|
335
|
+
"생산팀",
|
|
336
|
+
"구매팀",
|
|
337
|
+
"물류팀"
|
|
338
|
+
];
|
|
339
|
+
const prefixes = [
|
|
340
|
+
"신규",
|
|
341
|
+
"통합",
|
|
342
|
+
"전략",
|
|
343
|
+
"글로벌",
|
|
344
|
+
"디지털",
|
|
345
|
+
"핵심"
|
|
346
|
+
];
|
|
347
|
+
const suffixes = [
|
|
348
|
+
"1팀",
|
|
349
|
+
"2팀",
|
|
350
|
+
"3팀",
|
|
351
|
+
"A팀",
|
|
352
|
+
"B팀",
|
|
353
|
+
"본부",
|
|
354
|
+
"센터",
|
|
355
|
+
"그룹"
|
|
356
|
+
];
|
|
357
|
+
const dept = faker.helpers.arrayElement(departments);
|
|
358
|
+
const random = Math.random();
|
|
359
|
+
if (random > 0.7) {
|
|
360
|
+
const prefix = faker.helpers.arrayElement(prefixes);
|
|
361
|
+
return `${prefix} ${dept}`;
|
|
362
|
+
}
|
|
363
|
+
if (random > 0.4) {
|
|
364
|
+
const suffix = faker.helpers.arrayElement(suffixes);
|
|
365
|
+
return `${dept} ${suffix}`;
|
|
366
|
+
}
|
|
367
|
+
return dept;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 3. JSON 타입이면서 배열인 경우 (SonamuFile[], string[] 등)
|
|
371
|
+
* 필드명 패턴을 보고 적절한 배열 데이터를 생성합니다.
|
|
372
|
+
*/ if (prop.type === "json" && "id" in prop && prop.id) {
|
|
373
|
+
if (prop.id.endsWith("[]")) {
|
|
374
|
+
return this.generateArrayValue(prop, entity, faker, localeFaker);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** 4. Enum 타입은 정의된 값 중 하나를 랜덤 선택합니다 */ if (prop.type === "enum") {
|
|
378
|
+
let enumValues = [];
|
|
379
|
+
if ("enum" in prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
380
|
+
enumValues = prop.enum;
|
|
381
|
+
} else if ("id" in prop && prop.id && entity?.enumLabels?.[prop.id]) {
|
|
382
|
+
enumValues = Object.keys(entity.enumLabels[prop.id]);
|
|
383
|
+
}
|
|
384
|
+
if (enumValues.length > 0) {
|
|
385
|
+
return faker.helpers.arrayElement(enumValues);
|
|
386
|
+
}
|
|
387
|
+
return prop.nullable ? null : "UNKNOWN";
|
|
388
|
+
}
|
|
389
|
+
if (prop.type === "enum[]") {
|
|
390
|
+
let enumValues = [];
|
|
391
|
+
if ("enum" in prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
392
|
+
enumValues = prop.enum;
|
|
393
|
+
} else if ("id" in prop && prop.id && entity?.enumLabels?.[prop.id]) {
|
|
394
|
+
enumValues = Object.keys(entity.enumLabels[prop.id]);
|
|
395
|
+
}
|
|
396
|
+
if (enumValues.length > 0) {
|
|
397
|
+
return [
|
|
398
|
+
faker.helpers.arrayElement(enumValues)
|
|
399
|
+
];
|
|
400
|
+
}
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 5. Vector 타입은 현재 지원하지 않으므로 null을 반환합니다.
|
|
405
|
+
* 향후 AI embedding 생성 기능 추가 시 구현 예정입니다.
|
|
406
|
+
*/ if (prop.type === "vector" || prop.type === "vector[]" || prop.type === "tsvector") {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
/** 6. 타입별 기본 Faker 표현식을 실행합니다 */ const typeDefault = localeMappings.type_defaults[prop.type];
|
|
410
|
+
if (typeDefault) {
|
|
411
|
+
try {
|
|
412
|
+
return await this.executeFakerExpression(typeDefault.faker, prop);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.log(chalk.yellow(`Failed to execute type default for ${prop.type}, using fallback:`, error));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/** 7. 매핑되지 않은 타입은 기본 Faker 함수로 처리합니다 */ switch(prop.type){
|
|
418
|
+
case "string":
|
|
419
|
+
case "string[]":
|
|
420
|
+
return faker.lorem.words(3);
|
|
421
|
+
case "integer":
|
|
422
|
+
return faker.number.int({
|
|
423
|
+
min: 1,
|
|
424
|
+
max: 1000
|
|
425
|
+
});
|
|
426
|
+
case "integer[]":
|
|
427
|
+
return [
|
|
428
|
+
faker.number.int({
|
|
429
|
+
min: 1,
|
|
430
|
+
max: 1000
|
|
431
|
+
})
|
|
432
|
+
];
|
|
433
|
+
case "bigInteger":
|
|
434
|
+
return faker.number.bigInt({
|
|
435
|
+
min: 1n,
|
|
436
|
+
max: 1000n
|
|
437
|
+
});
|
|
438
|
+
case "bigInteger[]":
|
|
439
|
+
return [
|
|
440
|
+
faker.number.bigInt({
|
|
441
|
+
min: 1n,
|
|
442
|
+
max: 1000n
|
|
443
|
+
})
|
|
444
|
+
];
|
|
445
|
+
case "number":
|
|
446
|
+
case "numeric":
|
|
447
|
+
return faker.number.float({
|
|
448
|
+
min: 0,
|
|
449
|
+
max: 1000
|
|
450
|
+
});
|
|
451
|
+
case "number[]":
|
|
452
|
+
case "numeric[]":
|
|
453
|
+
return [
|
|
454
|
+
faker.number.float({
|
|
455
|
+
min: 0,
|
|
456
|
+
max: 1000
|
|
457
|
+
})
|
|
458
|
+
];
|
|
459
|
+
case "boolean":
|
|
460
|
+
return faker.datatype.boolean();
|
|
461
|
+
case "boolean[]":
|
|
462
|
+
return [
|
|
463
|
+
faker.datatype.boolean()
|
|
464
|
+
];
|
|
465
|
+
case "date":
|
|
466
|
+
case "date[]":
|
|
467
|
+
return faker.date.past();
|
|
468
|
+
case "json":
|
|
469
|
+
return {};
|
|
470
|
+
case "uuid":
|
|
471
|
+
case "uuid[]":
|
|
472
|
+
return faker.string.uuid();
|
|
473
|
+
default:
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 배열 타입의 값을 생성합니다.
|
|
479
|
+
*
|
|
480
|
+
* 타입 ID와 필드명 패턴을 분석하여 적절한 배열 데이터를 생성합니다.
|
|
481
|
+
* 예: image_urls → [{url, name, mime_type}, ...]
|
|
482
|
+
* tag_ids → [1, 23, 45]
|
|
483
|
+
*/ generateArrayValue(prop, _entity, faker, _localeFaker) {
|
|
484
|
+
const count = faker.number.int({
|
|
485
|
+
min: 1,
|
|
486
|
+
max: 3
|
|
487
|
+
});
|
|
488
|
+
/** SonamuFile[]은 Sonamu 내장 타입으로 구조가 정해져 있습니다 */ if ("id" in prop && prop.id === "SonamuFile[]") {
|
|
489
|
+
return Array.from({
|
|
490
|
+
length: count
|
|
491
|
+
}, ()=>({
|
|
492
|
+
url: faker.image.url(),
|
|
493
|
+
name: faker.system.fileName(),
|
|
494
|
+
mime_type: faker.helpers.arrayElement([
|
|
495
|
+
"image/jpeg",
|
|
496
|
+
"image/png",
|
|
497
|
+
"image/gif",
|
|
498
|
+
"application/pdf"
|
|
499
|
+
])
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
/** 필드명에서 배열의 용도를 추론합니다 */ const normalizedName = prop.name.toLowerCase().replace(/_/g, "");
|
|
503
|
+
if (normalizedName.includes("url") || normalizedName.includes("image")) {
|
|
504
|
+
return Array.from({
|
|
505
|
+
length: count
|
|
506
|
+
}, ()=>faker.internet.url());
|
|
507
|
+
}
|
|
508
|
+
if (normalizedName.includes("id") && normalizedName.endsWith("s")) {
|
|
509
|
+
return Array.from({
|
|
510
|
+
length: count
|
|
511
|
+
}, ()=>faker.number.int({
|
|
512
|
+
min: 1,
|
|
513
|
+
max: 100
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
if (normalizedName.includes("tag") || normalizedName.includes("name")) {
|
|
517
|
+
return Array.from({
|
|
518
|
+
length: count
|
|
519
|
+
}, ()=>faker.lorem.word());
|
|
520
|
+
}
|
|
521
|
+
/** 패턴 매칭되지 않으면 빈 배열을 반환합니다 */ return [];
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* JSON 매핑의 Faker 표현식을 파싱하여 실행합니다.
|
|
525
|
+
*
|
|
526
|
+
* 표현식 예시:
|
|
527
|
+
* - "faker.internet.email()" → 인자 없음
|
|
528
|
+
* - "faker.number.int({ min: 1, max: 100 })" → JSON 인자
|
|
529
|
+
* - "{}" → 리터럴 값 (JSON.parse)
|
|
530
|
+
*
|
|
531
|
+
* fakerKO, fakerJA도 지원하여 다국어 데이터를 생성합니다.
|
|
532
|
+
*/ async executeFakerExpression(expression, prop) {
|
|
533
|
+
const fakerModule = await import("@faker-js/faker");
|
|
534
|
+
const faker = fakerModule.faker;
|
|
535
|
+
const fakerKO = fakerModule.fakerKO;
|
|
536
|
+
const fakerJA = fakerModule.fakerJA;
|
|
537
|
+
/** Faker 표현식이 아닌 리터럴 값은 JSON으로 파싱합니다 */ if (!expression.startsWith("faker")) {
|
|
538
|
+
try {
|
|
539
|
+
return JSON.parse(expression);
|
|
540
|
+
} catch {
|
|
541
|
+
return expression;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/** 표현식에서 Faker 객체와 경로를 추출합니다 */ const match = expression.match(/^(faker|fakerKO|fakerJA)\.(.*?)$/);
|
|
545
|
+
if (!match) {
|
|
546
|
+
throw new Error(`Invalid faker expression: ${expression}`);
|
|
547
|
+
}
|
|
548
|
+
const [, fakerName, expr] = match;
|
|
549
|
+
const selectedFaker = fakerName === "fakerKO" ? fakerKO : fakerName === "fakerJA" ? fakerJA : faker;
|
|
550
|
+
const funcMatch = expr.match(/^([\w.]+)(?:\((.*?)\))?$/);
|
|
551
|
+
if (!funcMatch) {
|
|
552
|
+
throw new Error(`Invalid faker expression for ${prop.name}: ${expression}`);
|
|
553
|
+
}
|
|
554
|
+
const [, path, argsStr] = funcMatch;
|
|
555
|
+
const parts = path.split(".");
|
|
556
|
+
/** 점 표기법(dot notation)으로 Faker 함수를 찾아갑니다 */ let fn = selectedFaker;
|
|
557
|
+
for (const part of parts){
|
|
558
|
+
if (typeof fn === "object" && fn !== null && part in fn) {
|
|
559
|
+
fn = fn[part];
|
|
560
|
+
} else {
|
|
561
|
+
throw new Error(`Invalid faker path for ${prop.name}: ${fakerName}.${path}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (typeof fn !== "function") {
|
|
565
|
+
throw new Error(`${fakerName}.${path} is not a function (for ${prop.name})`);
|
|
566
|
+
}
|
|
567
|
+
/** 함수 인자를 JSON으로 파싱합니다 */ let args = [];
|
|
568
|
+
if (argsStr?.trim()) {
|
|
569
|
+
try {
|
|
570
|
+
const parsed = JSON.parse(`[${argsStr}]`);
|
|
571
|
+
args = Array.isArray(parsed) ? parsed : [
|
|
572
|
+
parsed
|
|
573
|
+
];
|
|
574
|
+
} catch {
|
|
575
|
+
/** JSON 파싱 실패 시 단순 숫자/문자열로 시도합니다 */ const trimmed = argsStr.trim();
|
|
576
|
+
if (!Number.isNaN(Number(trimmed))) {
|
|
577
|
+
args = [
|
|
578
|
+
Number(trimmed)
|
|
579
|
+
];
|
|
580
|
+
} else if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
581
|
+
args = [
|
|
582
|
+
trimmed.slice(1, -1)
|
|
583
|
+
];
|
|
584
|
+
} else {
|
|
585
|
+
throw new Error(`Cannot parse arguments for ${prop.name}: ${argsStr}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return fn(...args);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* fixtureHint를 LLM에게 전달하여 현실적인 테스트 데이터를 생성합니다.
|
|
593
|
+
*
|
|
594
|
+
* faker.js로는 생성하기 어려운 복잡한 텍스트(자기소개, 설명문 등)를
|
|
595
|
+
* LLM을 활용하여 생성합니다. 동일한 hint에 대한 중복 호출을 방지하기 위해
|
|
596
|
+
* 캐싱을 기본으로 지원합니다 (LLM API 비용 절감).
|
|
597
|
+
*
|
|
598
|
+
* ai 패키지는 dynamic import로 불러오므로, useLLM이 false인 경우
|
|
599
|
+
* 의존성이 설치되지 않아도 fixture 생성이 정상 동작합니다.
|
|
600
|
+
*/ async generateWithLLM(fixtureHint, prop, entity) {
|
|
601
|
+
const cacheKey = `${entity.id}:${prop.name}:${fixtureHint}`;
|
|
602
|
+
if (this.options.enableLLMCache && this.llmCache.has(cacheKey)) {
|
|
603
|
+
return this.llmCache.get(cacheKey);
|
|
604
|
+
}
|
|
605
|
+
const apiKey = this.getApiKey();
|
|
606
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
607
|
+
const { generateText } = await import("ai");
|
|
608
|
+
const { text } = await generateText({
|
|
609
|
+
model: createAnthropic({
|
|
610
|
+
apiKey
|
|
611
|
+
})(this.options.llmModel || "claude-sonnet-4-5"),
|
|
612
|
+
prompt: this.buildLLMPrompt(fixtureHint, prop, entity)
|
|
613
|
+
});
|
|
614
|
+
const value = this.parseLLMResponse(text, prop.type);
|
|
615
|
+
if (this.options.enableLLMCache) {
|
|
616
|
+
this.llmCache.set(cacheKey, value);
|
|
617
|
+
}
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
buildLLMPrompt(hint, prop, entity) {
|
|
621
|
+
const locale = this.options.locale || "ko";
|
|
622
|
+
const language = locale === "ko" ? "Korean" : locale === "ja" ? "Japanese" : "English";
|
|
623
|
+
let prompt = `Generate test data for ${entity.id}.${prop.name} (type: ${prop.type})
|
|
624
|
+
|
|
625
|
+
Requirement: ${hint}
|
|
626
|
+
|
|
627
|
+
Rules:
|
|
628
|
+
- Return ONLY the value, no explanation or markdown
|
|
629
|
+
- Use ${language} language if applicable
|
|
630
|
+
- Format: ${this.getExpectedFormat(prop.type)}`;
|
|
631
|
+
// enum 타입인 경우 가능한 값 목록 추가
|
|
632
|
+
if (prop.type === "enum" || prop.type === "enum[]") {
|
|
633
|
+
let enumValues = [];
|
|
634
|
+
if ("enum" in prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
635
|
+
enumValues = prop.enum;
|
|
636
|
+
} else if ("id" in prop && prop.id && entity?.enumLabels?.[prop.id]) {
|
|
637
|
+
enumValues = Object.keys(entity.enumLabels[prop.id]);
|
|
638
|
+
}
|
|
639
|
+
if (enumValues.length > 0) {
|
|
640
|
+
prompt += `\n- IMPORTANT: Choose ONLY from these allowed values: ${enumValues.join(", ")}`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
prompt += `\n\nExample: ${this.getExampleForType(prop.type, locale)}`;
|
|
644
|
+
return prompt;
|
|
645
|
+
}
|
|
646
|
+
parseLLMResponse(text, propType) {
|
|
647
|
+
const cleaned = text.trim();
|
|
648
|
+
// 배열 타입 처리
|
|
649
|
+
if (propType.endsWith("[]")) {
|
|
650
|
+
try {
|
|
651
|
+
const parsed = JSON.parse(cleaned);
|
|
652
|
+
const baseType = propType.slice(0, -2); // "integer[]" -> "integer"
|
|
653
|
+
if (Array.isArray(parsed)) {
|
|
654
|
+
return parsed.map((item)=>{
|
|
655
|
+
// null/undefined는 타입별 기본값으로
|
|
656
|
+
if (item === null || item === undefined) {
|
|
657
|
+
return this.getDefaultValueForType(baseType);
|
|
658
|
+
}
|
|
659
|
+
// 객체는 JSON.stringify 후 파싱 (json 타입인 경우)
|
|
660
|
+
if (typeof item === "object") {
|
|
661
|
+
return baseType === "json" ? item : this.parseScalarValue(JSON.stringify(item), baseType);
|
|
662
|
+
}
|
|
663
|
+
// primitive 값은 문자열로 변환 후 파싱
|
|
664
|
+
return this.parseScalarValue(String(item), baseType);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
// 단일 값이 온 경우 배열로 감싸기
|
|
668
|
+
if (parsed === null || parsed === undefined) {
|
|
669
|
+
return [
|
|
670
|
+
this.getDefaultValueForType(baseType)
|
|
671
|
+
];
|
|
672
|
+
}
|
|
673
|
+
return [
|
|
674
|
+
this.parseScalarValue(String(parsed), baseType)
|
|
675
|
+
];
|
|
676
|
+
} catch {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return this.parseScalarValue(cleaned, propType);
|
|
681
|
+
}
|
|
682
|
+
getDefaultValueForType(propType) {
|
|
683
|
+
switch(propType){
|
|
684
|
+
case "integer":
|
|
685
|
+
return 0;
|
|
686
|
+
case "bigInteger":
|
|
687
|
+
return 0n;
|
|
688
|
+
case "float":
|
|
689
|
+
case "number":
|
|
690
|
+
case "numeric":
|
|
691
|
+
return 0;
|
|
692
|
+
case "boolean":
|
|
693
|
+
return false;
|
|
694
|
+
case "date":
|
|
695
|
+
return new Date();
|
|
696
|
+
case "json":
|
|
697
|
+
return {};
|
|
698
|
+
case "uuid":
|
|
699
|
+
return "00000000-0000-0000-0000-000000000000";
|
|
700
|
+
default:
|
|
701
|
+
return "";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
parseScalarValue(text, propType) {
|
|
705
|
+
const cleaned = text.trim();
|
|
706
|
+
switch(propType){
|
|
707
|
+
case "integer":
|
|
708
|
+
{
|
|
709
|
+
const num = parseInt(cleaned, 10);
|
|
710
|
+
return Number.isNaN(num) ? 0 : num;
|
|
711
|
+
}
|
|
712
|
+
case "bigInteger":
|
|
713
|
+
{
|
|
714
|
+
try {
|
|
715
|
+
return BigInt(cleaned);
|
|
716
|
+
} catch {
|
|
717
|
+
return 0n;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
case "float":
|
|
721
|
+
case "number":
|
|
722
|
+
case "numeric":
|
|
723
|
+
{
|
|
724
|
+
const num = parseFloat(cleaned);
|
|
725
|
+
return Number.isNaN(num) ? 0 : num;
|
|
726
|
+
}
|
|
727
|
+
case "boolean":
|
|
728
|
+
return cleaned.toLowerCase() === "true";
|
|
729
|
+
case "date":
|
|
730
|
+
{
|
|
731
|
+
const date = new Date(cleaned);
|
|
732
|
+
return Number.isNaN(date.getTime()) ? new Date() : date;
|
|
733
|
+
}
|
|
734
|
+
case "json":
|
|
735
|
+
try {
|
|
736
|
+
return JSON.parse(cleaned);
|
|
737
|
+
} catch {
|
|
738
|
+
return cleaned;
|
|
739
|
+
}
|
|
740
|
+
case "uuid":
|
|
741
|
+
case "enum":
|
|
742
|
+
return cleaned;
|
|
743
|
+
default:
|
|
744
|
+
return cleaned;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Sonamu.secret을 우선으로 하고, 없으면 환경변수에서 API 키를 읽습니다.
|
|
749
|
+
*
|
|
750
|
+
* Sonamu.secret은 프로젝트별 설정(sonamu.config.ts)이므로 더 높은 우선순위를 가지며,
|
|
751
|
+
* 환경변수는 개발 환경이나 CI/CD에서 fallback으로 사용됩니다.
|
|
752
|
+
*/ getApiKey() {
|
|
753
|
+
let apiKey;
|
|
754
|
+
try {
|
|
755
|
+
const { Sonamu } = require("../api");
|
|
756
|
+
apiKey = Sonamu.secret?.anthropic_api_key;
|
|
757
|
+
} catch {
|
|
758
|
+
// Sonamu가 초기화되지 않은 경우 (테스트 환경 등)
|
|
759
|
+
}
|
|
760
|
+
if (!apiKey) {
|
|
761
|
+
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
762
|
+
}
|
|
763
|
+
if (!apiKey) {
|
|
764
|
+
throw new Error("ANTHROPIC_API_KEY not found. Set it in environment variables or Sonamu.secret.anthropic_api_key");
|
|
765
|
+
}
|
|
766
|
+
return apiKey;
|
|
767
|
+
}
|
|
768
|
+
getExpectedFormat(propType) {
|
|
769
|
+
// 배열 타입 처리
|
|
770
|
+
if (propType.endsWith("[]")) {
|
|
771
|
+
const baseType = propType.slice(0, -2);
|
|
772
|
+
const baseFormat = this.getScalarFormat(baseType);
|
|
773
|
+
return `JSON array of ${baseFormat} (e.g., [${this.getExampleForType(baseType, "en")}, ...])`;
|
|
774
|
+
}
|
|
775
|
+
return this.getScalarFormat(propType);
|
|
776
|
+
}
|
|
777
|
+
getScalarFormat(propType) {
|
|
778
|
+
switch(propType){
|
|
779
|
+
case "integer":
|
|
780
|
+
case "bigInteger":
|
|
781
|
+
return "integer numbers";
|
|
782
|
+
case "float":
|
|
783
|
+
case "number":
|
|
784
|
+
case "numeric":
|
|
785
|
+
return "decimal numbers";
|
|
786
|
+
case "boolean":
|
|
787
|
+
return "booleans (true or false)";
|
|
788
|
+
case "date":
|
|
789
|
+
return "ISO 8601 date strings";
|
|
790
|
+
case "json":
|
|
791
|
+
return "valid JSON object or array";
|
|
792
|
+
case "uuid":
|
|
793
|
+
return "UUID strings";
|
|
794
|
+
case "enum":
|
|
795
|
+
return "one of the allowed enum values";
|
|
796
|
+
default:
|
|
797
|
+
return "plain text strings";
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
getExampleForType(propType, locale) {
|
|
801
|
+
// 배열 타입 처리
|
|
802
|
+
if (propType.endsWith("[]")) {
|
|
803
|
+
const baseType = propType.slice(0, -2);
|
|
804
|
+
const baseExample = this.getScalarExample(baseType, locale);
|
|
805
|
+
return `[${baseExample}]`;
|
|
806
|
+
}
|
|
807
|
+
return this.getScalarExample(propType, locale);
|
|
808
|
+
}
|
|
809
|
+
getScalarExample(propType, locale) {
|
|
810
|
+
const isKorean = locale === "ko";
|
|
811
|
+
switch(propType){
|
|
812
|
+
case "integer":
|
|
813
|
+
case "bigInteger":
|
|
814
|
+
return "42";
|
|
815
|
+
case "float":
|
|
816
|
+
case "number":
|
|
817
|
+
case "numeric":
|
|
818
|
+
return "3.14";
|
|
819
|
+
case "boolean":
|
|
820
|
+
return "true";
|
|
821
|
+
case "date":
|
|
822
|
+
return "2024-01-01";
|
|
823
|
+
case "json":
|
|
824
|
+
return '{"key": "value"}';
|
|
825
|
+
case "uuid":
|
|
826
|
+
return "550e8400-e29b-41d4-a716-446655440000";
|
|
827
|
+
case "enum":
|
|
828
|
+
return "ENUM_VALUE";
|
|
829
|
+
default:
|
|
830
|
+
return isKorean ? "안녕하세요" : "Hello";
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* LLM 캐시 통계를 반환합니다.
|
|
835
|
+
*/ getLLMCacheStats() {
|
|
836
|
+
return {
|
|
837
|
+
size: this.llmCache.size,
|
|
838
|
+
enabled: this.options.enableLLMCache
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* LLM 캐시를 초기화합니다.
|
|
843
|
+
*/ clearLLMCache() {
|
|
844
|
+
this.llmCache.clear();
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* 컨텍스트 생성
|
|
848
|
+
*/ createContext() {
|
|
849
|
+
return {
|
|
850
|
+
fixtures: new Map(),
|
|
851
|
+
referenceCache: new Map(),
|
|
852
|
+
importedRecords: new Set()
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* 배치 생성 및 자동 저장
|
|
857
|
+
*
|
|
858
|
+
* 1. 각 spec별로 fixture 생성 (메모리)
|
|
859
|
+
* 2. FixtureRecord로 변환
|
|
860
|
+
* 3. FixtureManager.insertFixtures()로 targetDb에 저장
|
|
861
|
+
*
|
|
862
|
+
* @returns 저장된 fixture 데이터 (실제 DB ID 포함)
|
|
863
|
+
*/ async generateBatch(specs) {
|
|
864
|
+
const context = this.createContext();
|
|
865
|
+
const generatedFixtures = [];
|
|
866
|
+
// 1. 각 spec별로 fixture 생성
|
|
867
|
+
for (const spec of specs){
|
|
868
|
+
for(let i = 0; i < spec.count; i++){
|
|
869
|
+
const fixture = await this.generate(spec.entity, spec.overrides || {}, context);
|
|
870
|
+
generatedFixtures.push({
|
|
871
|
+
entity: spec.entity,
|
|
872
|
+
data: fixture
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// 2. FixtureRecord로 변환
|
|
877
|
+
const fixtureRecords = [];
|
|
878
|
+
for (const { entity: entityName, data } of generatedFixtures){
|
|
879
|
+
const entity = this.entityManager.get(entityName);
|
|
880
|
+
// 임시 ID 생성 (targetDb에 INSERT 후 실제 ID를 받음)
|
|
881
|
+
const tempId = Math.floor(Math.random() * 1000000);
|
|
882
|
+
const records = await FixtureManager.createFixtureRecord(entity, {
|
|
883
|
+
...data,
|
|
884
|
+
id: tempId
|
|
885
|
+
}, {
|
|
886
|
+
singleRecord: true
|
|
887
|
+
});
|
|
888
|
+
fixtureRecords.push(...records);
|
|
889
|
+
}
|
|
890
|
+
// 3. targetDb에 삽입 (FixtureManager가 의존성 정렬 처리)
|
|
891
|
+
const results = await FixtureManager.insertFixtures(this.targetDbName, fixtureRecords);
|
|
892
|
+
console.log(chalk.green(`Generated and saved ${results.length} fixtures to ${this.targetDbName}`));
|
|
893
|
+
return results;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* 실제 DB(sourceDb)에서 데이터를 조회하여 fixture DB(targetDb)에 import합니다.
|
|
897
|
+
*
|
|
898
|
+
* 1. DataExplorer로 sourceDb에서 데이터 조회 (관련 데이터 포함)
|
|
899
|
+
* 2. FixtureRecord로 변환
|
|
900
|
+
* 3. targetDb에 삽입
|
|
901
|
+
*
|
|
902
|
+
* @param entityName - 조회할 entity 이름
|
|
903
|
+
* @param options - 조회 옵션 (strategy, limit, includeRelations 등)
|
|
904
|
+
* @returns 저장된 fixture 데이터 (실제 DB ID 포함)
|
|
905
|
+
*
|
|
906
|
+
* @example
|
|
907
|
+
* // 프로덕션 DB에서 User 10명 + 관련 Employee, Department 가져오기
|
|
908
|
+
* await generator.importFromSource("User", {
|
|
909
|
+
* strategy: "sample",
|
|
910
|
+
* limit: 10,
|
|
911
|
+
* includeRelations: true,
|
|
912
|
+
* maxDepth: 2
|
|
913
|
+
* });
|
|
914
|
+
*/ async importFromSource(entityName, options) {
|
|
915
|
+
console.log(chalk.blue(`Importing ${entityName} from source DB with options: ${JSON.stringify({
|
|
916
|
+
strategy: options.strategy,
|
|
917
|
+
limit: options.limit,
|
|
918
|
+
includeRelations: options.includeRelations,
|
|
919
|
+
maxDepth: options.maxDepth
|
|
920
|
+
})}`));
|
|
921
|
+
// 1. DataExplorer로 sourceDb에서 데이터 조회 (관련 데이터 포함)
|
|
922
|
+
const exploreResult = await this.dataExplorer.exploreWithRelations(entityName, options);
|
|
923
|
+
console.log(chalk.cyan(`Found ${exploreResult.main.records.length} ${entityName} records and ${exploreResult.related.size} related entities`));
|
|
924
|
+
// 2. FixtureRecord로 변환
|
|
925
|
+
const fixtureRecords = [];
|
|
926
|
+
// 메인 entity의 records를 FixtureRecord로 변환
|
|
927
|
+
const mainEntity = this.entityManager.get(entityName);
|
|
928
|
+
for (const record of exploreResult.main.records){
|
|
929
|
+
const records = await FixtureManager.createFixtureRecord(mainEntity, record, {
|
|
930
|
+
_db: this.sourceDb,
|
|
931
|
+
singleRecord: true
|
|
932
|
+
});
|
|
933
|
+
fixtureRecords.push(...records);
|
|
934
|
+
}
|
|
935
|
+
// 관련 entity의 records를 FixtureRecord로 변환
|
|
936
|
+
for (const [relatedEntityName, relatedRecords] of exploreResult.related.entries()){
|
|
937
|
+
const relatedEntity = this.entityManager.get(relatedEntityName);
|
|
938
|
+
for (const record of relatedRecords){
|
|
939
|
+
const records = await FixtureManager.createFixtureRecord(relatedEntity, record, {
|
|
940
|
+
_db: this.sourceDb,
|
|
941
|
+
singleRecord: true
|
|
942
|
+
});
|
|
943
|
+
fixtureRecords.push(...records);
|
|
944
|
+
}
|
|
945
|
+
console.log(chalk.gray(` - ${relatedEntityName}: ${relatedRecords.length} records`));
|
|
946
|
+
}
|
|
947
|
+
// 3. targetDb에 삽입 (FixtureManager가 의존성 정렬 처리)
|
|
948
|
+
const results = await FixtureManager.insertFixtures(this.targetDbName, fixtureRecords);
|
|
949
|
+
console.log(chalk.green(`Successfully imported ${results.length} records to ${this.targetDbName} (${exploreResult.main.records.length} ${entityName} + ${results.length - exploreResult.main.records.length} related)`));
|
|
950
|
+
return results;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0aW5nL2ZpeHR1cmUtZ2VuZXJhdG9yLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjaGFsayBmcm9tIFwiY2hhbGtcIjtcbmltcG9ydCB0eXBlIHsgS25leCB9IGZyb20gXCJrbmV4XCI7XG5pbXBvcnQgdHlwZSB7IEVudGl0eSB9IGZyb20gXCIuLi9lbnRpdHkvZW50aXR5XCI7XG5pbXBvcnQgdHlwZSB7IEVudGl0eU1hbmFnZXIgfSBmcm9tIFwiLi4vZW50aXR5L2VudGl0eS1tYW5hZ2VyXCI7XG5pbXBvcnQgdHlwZSB7IEVudGl0eVByb3AsIEZpeHR1cmVJbXBvcnRSZXN1bHQsIEZpeHR1cmVSZWNvcmQgfSBmcm9tIFwiLi4vdHlwZXMvdHlwZXNcIjtcbmltcG9ydCB7IGlzQmVsb25nc1RvT25lUmVsYXRpb25Qcm9wLCBpc09uZVRvT25lUmVsYXRpb25Qcm9wLCBpc1JlbGF0aW9uUHJvcCB9IGZyb20gXCIuLi90eXBlcy90eXBlc1wiO1xuaW1wb3J0IHtcbiAgRGF0YUV4cGxvcmVyLFxuICB0eXBlIEV4cGxvcmVXaXRoUmVsYXRpb25zT3B0aW9ucyxcbiAgdHlwZSBFeHBsb3JlV2l0aFJlbGF0aW9uc1Jlc3VsdCxcbn0gZnJvbSBcIi4vZGF0YS1leHBsb3JlclwiO1xuaW1wb3J0IHsgdHlwZSBGYWtlck1hcHBpbmdzLCBmYWtlck1hcHBpbmdzIH0gZnJvbSBcIi4vZmFrZXItbWFwcGluZ3NcIjtcbmltcG9ydCB7IEZpeHR1cmVNYW5hZ2VyIH0gZnJvbSBcIi4vZml4dHVyZS1tYW5hZ2VyXCI7XG5cbmV4cG9ydCB0eXBlIExvY2FsZSA9IFwia29cIiB8IFwiZW5cIiB8IFwiamFcIjtcblxuZXhwb3J0IHR5cGUgRml4dHVyZUdlbmVyYXRvck9wdGlvbnMgPSB7XG4gIGxvY2FsZT86IExvY2FsZTtcbiAgdXNlTExNPzogYm9vbGVhbjtcbiAgZW5hYmxlTExNQ2FjaGU/OiBib29sZWFuO1xuICBsbG1Nb2RlbD86IHN0cmluZztcbn07XG5cbmV4cG9ydCB0eXBlIEdlbmVyYXRvckNvbnRleHQgPSB7XG4gIC8qKiDsg53shLEg7KSR7J24IGZpeHR1cmXrk6QgKOuplOuqqOumrCDsg4EpICovXG4gIGZpeHR1cmVzOiBNYXA8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj47XG5cbiAgLyoqIOywuOyhsCDrjbDsnbTthLAg7LqQ7IucIChEYXRhRXhwbG9yZXIg6rKw6rO8KSAqL1xuICByZWZlcmVuY2VDYWNoZTogTWFwPHN0cmluZywgUmVjb3JkPHN0cmluZywgdW5rbm93bj5bXT47XG5cbiAgLyoqIOydtOuvuCBpbXBvcnTrkJwg66CI7L2U65Oc66W8IOy2lOygge2VmOyXrCDspJHrs7UgaW1wb3J066W8IOuwqeyngO2VqeuLiOuLpCAqL1xuICBpbXBvcnRlZFJlY29yZHM6IFNldDxzdHJpbmc+OyAvLyBcIlVzZXIjMTIzXCJcbn07XG5cbmV4cG9ydCBjbGFzcyBGaXh0dXJlR2VuZXJhdG9yIHtcbiAgcHJpdmF0ZSBkYXRhRXhwbG9yZXI6IERhdGFFeHBsb3JlcjtcbiAgcHJpdmF0ZSBsb2NhbGU6IExvY2FsZTtcbiAgcHJpdmF0ZSBtYXBwaW5nczogRmFrZXJNYXBwaW5ncztcbiAgcHJpdmF0ZSBsbG1DYWNoZTogTWFwPHN0cmluZywgdW5rbm93bj4gPSBuZXcgTWFwKCk7XG4gIHByaXZhdGUgb3B0aW9uczogRml4dHVyZUdlbmVyYXRvck9wdGlvbnM7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBzb3VyY2VEYjogS25leCxcbiAgICAvLyBGaXh0dXJlTWFuYWdlci5pbnNlcnRGaXh0dXJlc+qwgCBkYk5hbWUg66y47J6Q7Je07J2EIOuwm+q4sCDrlYzrrLjsl5Ag7KeB7KCRIOyCrOyaqe2VmOyngCDslYrsirXri4jri6RcbiAgICAvLyDrr7jrnpgg7ZmV7J6l7ISx7J2EIOychO2VtCBBUEkg7Iuc6re464uI7LKY7JeQ64qUIO2PrO2VqOyLnOy8sOyKteuLiOuLpFxuICAgIF90YXJnZXREYjogS25leCxcbiAgICBwcml2YXRlIHRhcmdldERiTmFtZTogXCJmaXh0dXJlXCIgfCBcInRlc3RcIiB8IFwicHJvZHVjdGlvbl9tYXN0ZXJcIixcbiAgICBwcml2YXRlIGVudGl0eU1hbmFnZXI6IHR5cGVvZiBFbnRpdHlNYW5hZ2VyLFxuICAgIG9wdGlvbnM/OiBGaXh0dXJlR2VuZXJhdG9yT3B0aW9ucyxcbiAgKSB7XG4gICAgdGhpcy5kYXRhRXhwbG9yZXIgPSBuZXcgRGF0YUV4cGxvcmVyKHNvdXJjZURiLCBlbnRpdHlNYW5hZ2VyKTtcbiAgICB0aGlzLmxvY2FsZSA9IG9wdGlvbnM/LmxvY2FsZSB8fCBcImtvXCI7XG4gICAgdGhpcy5tYXBwaW5ncyA9IGZha2VyTWFwcGluZ3M7XG4gICAgdGhpcy5vcHRpb25zID0ge1xuICAgICAgbG9jYWxlOiBvcHRpb25zPy5sb2NhbGUgfHwgXCJrb1wiLFxuICAgICAgdXNlTExNOiBvcHRpb25zPy51c2VMTE0gfHwgZmFsc2UsXG4gICAgICBlbmFibGVMTE1DYWNoZTogb3B0aW9ucz8uZW5hYmxlTExNQ2FjaGUgIT09IGZhbHNlLFxuICAgICAgbGxtTW9kZWw6IG9wdGlvbnM/LmxsbU1vZGVsIHx8IFwiY2xhdWRlLXNvbm5ldC00LTVcIixcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIEZpeHR1cmUg7IOd7ISxICjri6jsnbwpXG4gICAqIEByZXR1cm5zIOyDneyEseuQnCBmaXh0dXJlIOuNsOydtO2EsCAo66mU66qo66asIOyDgSlcbiAgICovXG4gIGFzeW5jIGdlbmVyYXRlKFxuICAgIGVudGl0eU5hbWU6IHN0cmluZyxcbiAgICBvdmVycmlkZXM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge30sXG4gICAgY29udGV4dDogR2VuZXJhdG9yQ29udGV4dCA9IHRoaXMuY3JlYXRlQ29udGV4dCgpLFxuICApOiBQcm9taXNlPFJlY29yZDxzdHJpbmcsIHVua25vd24+PiB7XG4gICAgY29uc3QgZW50aXR5ID0gdGhpcy5lbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlOYW1lKTtcbiAgICBjb25zdCB0ZW1wSWQgPSBgJHtlbnRpdHlOYW1lfSN0ZW1wIyR7RGF0ZS5ub3coKX1gOyAvLyDsnoTsi5wgSURcblxuICAgIC8vIOqwgSBwcm9w67OEIOqwkiDsg53shLFcbiAgICBjb25zdCBmaXh0dXJlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuXG4gICAgZm9yIChjb25zdCBwcm9wIG9mIGVudGl0eS5wcm9wcykge1xuICAgICAgLy8gVmlydHVhbCBwcm9w7J2AIOyKpO2CtVxuICAgICAgaWYgKFwidmlydHVhbFwiIGluIHByb3AgJiYgcHJvcC52aXJ0dWFsKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICAvLyBvdmVycmlkZeqwgCDsnojsnLzrqbQg7IKs7JqpXG4gICAgICBpZiAocHJvcC5uYW1lIGluIG92ZXJyaWRlcykge1xuICAgICAgICBmaXh0dXJlW3Byb3AubmFtZV0gPSBvdmVycmlkZXNbcHJvcC5uYW1lXTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIC8vIGNvbmXsl5DshJwg7IOd7ISxIOyghOuetSDtmZXsnbhcbiAgICAgIGNvbnN0IGNvbmUgPSBwcm9wLmNvbmU7XG5cbiAgICAgIC8vIDEuIFJlbGF0aW9uIHByb3Ag7LKY66asXG4gICAgICBpZiAoaXNSZWxhdGlvblByb3AocHJvcCkpIHtcbiAgICAgICAgY29uc3QgcmVsYXRpb25WYWx1ZSA9IGF3YWl0IHRoaXMuZ2VuZXJhdGVSZWxhdGlvblZhbHVlKGVudGl0eSwgcHJvcCwgY29udGV4dCk7XG4gICAgICAgIC8vIEJlbG9uZ3NUb09uZSwgT25lVG9PbmUoaGFzSm9pbkNvbHVtbinsnZgg6rK97JqwIGZvcmVpZ24ga2V5IOy7rOufvOuqheycvOuhnCDsoIDsnqVcbiAgICAgICAgaWYgKFxuICAgICAgICAgIGlzQmVsb25nc1RvT25lUmVsYXRpb25Qcm9wKHByb3ApIHx8XG4gICAgICAgICAgKGlzT25lVG9PbmVSZWxhdGlvblByb3AocHJvcCkgJiYgcHJvcC5oYXNKb2luQ29sdW1uKVxuICAgICAgICApIHtcbiAgICAgICAgICBmaXh0dXJlW2Ake3Byb3AubmFtZX1faWRgXSA9IHJlbGF0aW9uVmFsdWU7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZml4dHVyZVtwcm9wLm5hbWVdID0gcmVsYXRpb25WYWx1ZTtcbiAgICAgICAgfVxuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgLy8gMi4gZml4dHVyZUdlbmVyYXRvciDsgqzsmqlcbiAgICAgIGlmIChjb25lPy5maXh0dXJlR2VuZXJhdG9yKSB7XG4gICAgICAgIGZpeHR1cmVbcHJvcC5uYW1lXSA9IGF3YWl0IHRoaXMuZXhlY3V0ZUdlbmVyYXRvcihcbiAgICAgICAgICBjb25lLmZpeHR1cmVHZW5lcmF0b3IgYXMgc3RyaW5nLFxuICAgICAgICAgIHByb3AsXG4gICAgICAgICAgZW50aXR5LFxuICAgICAgICApO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgLy8gMi41LiBmaXh0dXJlSGludCArIExMTSDsgqzsmqlcbiAgICAgIGlmIChjb25lPy5maXh0dXJlSGludCAmJiB0aGlzLm9wdGlvbnMudXNlTExNKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgZml4dHVyZVtwcm9wLm5hbWVdID0gYXdhaXQgdGhpcy5nZW5lcmF0ZVdpdGhMTE0oY29uZS5maXh0dXJlSGludCwgcHJvcCwgZW50aXR5KTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBjb25zb2xlLndhcm4oXG4gICAgICAgICAgICBgW0ZpeHR1cmVHZW5lcmF0b3JdIExMTSBnZW5lcmF0aW9uIGZhaWxlZCBmb3IgJHtlbnRpdHkuaWR9LiR7cHJvcC5uYW1lfSwgZmFsbGluZyBiYWNrIHRvIGRlZmF1bHRgLFxuICAgICAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBlcnJvcixcbiAgICAgICAgICApO1xuICAgICAgICAgIC8vIGZhbGxiYWNrOiBmaXh0dXJlRGVmYXVsdCDrmJDripQg6riw67O46rCS7Jy866GcIOqzhOyGjVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIDMuIGZpeHR1cmVEZWZhdWx0IOyCrOyaqVxuICAgICAgaWYgKGNvbmU/LmZpeHR1cmVEZWZhdWx0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZml4dHVyZVtwcm9wLm5hbWVdID0gY29uZS5maXh0dXJlRGVmYXVsdDtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIC8vIDQuIO2DgOyeheuzhCDquLDrs7gg7IOd7ISxXG4gICAgICBmaXh0dXJlW3Byb3AubmFtZV0gPSBhd2FpdCB0aGlzLmdlbmVyYXRlRGVmYXVsdFZhbHVlKHByb3AsIGVudGl0eSk7XG4gICAgfVxuXG4gICAgLy8gNS4gcGFzc3dvcmQg7ZWE65OcIOyVlO2YuO2ZlFxuICAgIGlmIChcInBhc3N3b3JkXCIgaW4gZml4dHVyZSAmJiBmaXh0dXJlLnBhc3N3b3JkICYmIHR5cGVvZiBmaXh0dXJlLnBhc3N3b3JkID09PSBcInN0cmluZ1wiKSB7XG4gICAgICBjb25zdCBiY3J5cHQgPSBhd2FpdCBpbXBvcnQoXCJiY3J5cHRcIik7XG4gICAgICBmaXh0dXJlLnBhc3N3b3JkID0gYXdhaXQgYmNyeXB0Lmhhc2goZml4dHVyZS5wYXNzd29yZCwgMTApO1xuICAgIH1cblxuICAgIGNvbnRleHQuZml4dHVyZXMuc2V0KHRlbXBJZCwgZml4dHVyZSk7XG4gICAgcmV0dXJuIGZpeHR1cmU7XG4gIH1cblxuICAvKipcbiAgICogUmVsYXRpb24g6rCSIOyDneyEsSArIOyekOuPmSBJbXBvcnRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZ2VuZXJhdGVSZWxhdGlvblZhbHVlKFxuICAgIGVudGl0eTogRW50aXR5LFxuICAgIHByb3A6IEVudGl0eVByb3AsXG4gICAgY29udGV4dDogR2VuZXJhdG9yQ29udGV4dCxcbiAgKTogUHJvbWlzZTxudW1iZXIgfCBudWxsPiB7XG4gICAgaWYgKCFpc1JlbGF0aW9uUHJvcChwcm9wKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBGaXh0dXJlR2VuZXJhdG9yOiAke2VudGl0eS5pZH0uJHtwcm9wLm5hbWV9IGlzIG5vdCBhIHJlbGF0aW9uIHByb3BgKTtcbiAgICB9XG5cbiAgICAvLyBCZWxvbmdzVG9PbmUsIE9uZVRvT25lKGhhc0pvaW5Db2x1bW4p66eMIOyymOumrFxuICAgIGlmIChcbiAgICAgICFpc0JlbG9uZ3NUb09uZVJlbGF0aW9uUHJvcChwcm9wKSAmJlxuICAgICAgIShpc09uZVRvT25lUmVsYXRpb25Qcm9wKHByb3ApICYmIHByb3AuaGFzSm9pbkNvbHVtbilcbiAgICApIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIGNvbnN0IGNvbmUgPSBwcm9wLmNvbmU7XG4gICAgY29uc3QgZGF0YVNvdXJjZSA9IGNvbmU/LmRhdGFTb3VyY2U7XG5cbiAgICAvLyBEYXRhRXhwbG9yZXLroZwg7LC47KGwIOuNsOydtO2EsCDsobDtmowgKHNvdXJjZURiKVxuICAgIC8vIOq0gOqzhCDssrTsnbjsnYQg65Sw65286rCA6riwIOychO2VtCBleHBsb3JlV2l0aFJlbGF0aW9ucyDsgqzsmqlcbiAgICBpZiAoZGF0YVNvdXJjZSkge1xuICAgICAgY29uc3QgY2FjaGVLZXkgPSBgJHtwcm9wLndpdGh9OiR7SlNPTi5zdHJpbmdpZnkoZGF0YVNvdXJjZSl9YDtcblxuICAgICAgaWYgKCFjb250ZXh0LnJlZmVyZW5jZUNhY2hlLmhhcyhjYWNoZUtleSkpIHtcbiAgICAgICAgY29uc3QgZXhwbG9yZVJlc3VsdCA9IGF3YWl0IHRoaXMuZGF0YUV4cGxvcmVyLmV4cGxvcmVXaXRoUmVsYXRpb25zKHByb3Aud2l0aCwge1xuICAgICAgICAgIHN0cmF0ZWd5OiBkYXRhU291cmNlLnN0cmF0ZWd5LFxuICAgICAgICAgIGxpbWl0OlxuICAgICAgICAgICAgKChkYXRhU291cmNlLmNvbmZpZyBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZCk/LmxpbWl0IGFzXG4gICAgICAgICAgICAgIHwgbnVtYmVyXG4gICAgICAgICAgICAgIHwgdW5kZWZpbmVkKSB8fCAxMCxcbiAgICAgICAgICBpbmNsdWRlUmVsYXRpb25zOiB0cnVlLFxuICAgICAgICAgIG1heERlcHRoOiAzLFxuICAgICAgICAgIC4uLihkYXRhU291cmNlLmNvbmZpZyBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZCksXG4gICAgICAgIH0pO1xuICAgICAgICBjb250ZXh0LnJlZmVyZW5jZUNhY2hlLnNldChjYWNoZUtleSwgZXhwbG9yZVJlc3VsdC5tYWluLnJlY29yZHMpO1xuXG4gICAgICAgIC8vIOyhsO2ajO2VnCDrjbDsnbTthLDsmYAg6rSA6rOE65CcIOuqqOuToCDsl5Tti7Dti7DrpbwgdGFyZ2V0RGLsl5AgaW1wb3J0XG4gICAgICAgIGF3YWl0IHRoaXMuaW1wb3J0RXhwbG9yZVJlc3VsdChleHBsb3JlUmVzdWx0LCBjb250ZXh0KTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgY2FuZGlkYXRlcyA9IGNvbnRleHQucmVmZXJlbmNlQ2FjaGUuZ2V0KGNhY2hlS2V5KTtcbiAgICAgIGlmIChjYW5kaWRhdGVzICYmIGNhbmRpZGF0ZXMubGVuZ3RoID4gMCkge1xuICAgICAgICAvLyDrnpzrjaTtlZjqsowg7ZWY64KYIOyEoO2DnVxuICAgICAgICBjb25zdCBzZWxlY3RlZCA9IGNhbmRpZGF0ZXNbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogY2FuZGlkYXRlcy5sZW5ndGgpXTtcbiAgICAgICAgcmV0dXJuIHNlbGVjdGVkLmlkIGFzIG51bWJlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBkYXRhU291cmNl6rCAIOyXhuydhCDrlYwg7J6Q64+Z7Jy866GcIGZpeHR1cmUgRELsl5DshJwg7KGw7ZqMIOyLnOuPhFxuICAgIC8vIOq0gOqzhCDssrTsnbjsnYQg65Sw65286rCA6riwIOychO2VtCBleHBsb3JlV2l0aFJlbGF0aW9ucyDsgqzsmqlcbiAgICBjb25zdCBhdXRvS2V5ID0gYCR7cHJvcC53aXRofTphdXRvYDtcbiAgICBpZiAoIWNvbnRleHQucmVmZXJlbmNlQ2FjaGUuaGFzKGF1dG9LZXkpKSB7XG4gICAgICAvLyBmaXh0dXJlIERCKHNvdXJjZURiKeyXkOyEnCDsnpDrj5kg7KGw7ZqMICjqtIDqs4Qg7Y+s7ZWoKVxuICAgICAgY29uc3QgYXV0b0V4cGxvcmVSZXN1bHQgPSBhd2FpdCB0aGlzLmRhdGFFeHBsb3Jlci5leHBsb3JlV2l0aFJlbGF0aW9ucyhwcm9wLndpdGgsIHtcbiAgICAgICAgc3RyYXRlZ3k6IFwicmFuZG9tXCIsXG4gICAgICAgIGxpbWl0OiAxMCxcbiAgICAgICAgaW5jbHVkZVJlbGF0aW9uczogdHJ1ZSxcbiAgICAgICAgbWF4RGVwdGg6IDMsXG4gICAgICB9KTtcbiAgICAgIGNvbnRleHQucmVmZXJlbmNlQ2FjaGUuc2V0KGF1dG9LZXksIGF1dG9FeHBsb3JlUmVzdWx0Lm1haW4ucmVjb3Jkcyk7XG5cbiAgICAgIC8vIOyhsO2ajO2VnCDrjbDsnbTthLDsmYAg6rSA6rOE65CcIOuqqOuToCDsl5Tti7Dti7DrpbwgdGFyZ2V0RGLsl5AgaW1wb3J0XG4gICAgICBpZiAoYXV0b0V4cGxvcmVSZXN1bHQubWFpbi5yZWNvcmRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgYXdhaXQgdGhpcy5pbXBvcnRFeHBsb3JlUmVzdWx0KGF1dG9FeHBsb3JlUmVzdWx0LCBjb250ZXh0KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCBhdXRvQ2FuZGlkYXRlcyA9IGNvbnRleHQucmVmZXJlbmNlQ2FjaGUuZ2V0KGF1dG9LZXkpO1xuICAgIGlmIChhdXRvQ2FuZGlkYXRlcyAmJiBhdXRvQ2FuZGlkYXRlcy5sZW5ndGggPiAwKSB7XG4gICAgICAvLyDrnpzrjaTtlZjqsowg7ZWY64KYIOyEoO2DnVxuICAgICAgY29uc3Qgc2VsZWN0ZWQgPSBhdXRvQ2FuZGlkYXRlc1tNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiBhdXRvQ2FuZGlkYXRlcy5sZW5ndGgpXTtcbiAgICAgIHJldHVybiBzZWxlY3RlZC5pZCBhcyBudW1iZXI7XG4gICAgfVxuXG4gICAgLy8g7LC47KGwIOuNsOydtO2EsOqwgCDsl4bsnLzrqbQgbnVsbCDrsJjtmZggKG51bGxhYmxl7J24IOqyveyasClcbiAgICBpZiAocHJvcC5udWxsYWJsZSkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgLy8gbnVsbGFibGXsnbQg7JWE64uI6rOgIOuNsOydtO2EsOuPhCDsl4bsnLzrqbQg7JeQ65+sXG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYEZpeHR1cmVHZW5lcmF0b3I6ICR7ZW50aXR5LmlkfS4ke3Byb3AubmFtZX3sl5Ag7ZWE7JqU7ZWcICR7cHJvcC53aXRofSDrjbDsnbTthLDqsIAg7JeG7Iq164uI64ukLiBgICtcbiAgICAgICAgYOuovOyggCAke3Byb3Aud2l0aH3rpbwg7IOd7ISx7ZWY6rGw64KYIGNvbmUuZGF0YVNvdXJjZeulvCDshKTsoJXtlZjshLjsmpQuYCxcbiAgICApO1xuICB9XG5cbiAgLyoqXG4gICAqIEV4cGxvcmVXaXRoUmVsYXRpb25zIOqysOqzvOulvCB0YXJnZXREYuyXkCBpbXBvcnRcbiAgICpcbiAgICog6rSA6rOEIOyytOyduOydhCDrlLDrnbzqsIQg6rKw6rO8KG1haW4gKyByZWxhdGVkKeulvCDrqqjrkZAgaW1wb3J07ZWp64uI64ukLlxuICAgKiDsnZjsobTshLEg7Iic7ISc64qUIEZpeHR1cmVNYW5hZ2VyLmluc2VydEZpeHR1cmVz6rCAIOyekOuPmeycvOuhnCDsspjrpqztlanri4jri6QuXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGltcG9ydEV4cGxvcmVSZXN1bHQoXG4gICAgZXhwbG9yZVJlc3VsdDogRXhwbG9yZVdpdGhSZWxhdGlvbnNSZXN1bHQsXG4gICAgY29udGV4dDogR2VuZXJhdG9yQ29udGV4dCxcbiAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgYWxsRml4dHVyZVJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSA9IFtdO1xuXG4gICAgLy8gMS4gUmVsYXRlZCBlbnRpdGllcyBpbXBvcnQgKENvbXBhbnksIERlcGFydG1lbnQg65OxKVxuICAgIGZvciAoY29uc3QgW2VudGl0eUlkLCByZWNvcmRzXSBvZiBleHBsb3JlUmVzdWx0LnJlbGF0ZWQuZW50cmllcygpKSB7XG4gICAgICBjb25zdCBlbnRpdHkgPSB0aGlzLmVudGl0eU1hbmFnZXIuZ2V0KGVudGl0eUlkKTtcbiAgICAgIGNvbnN0IHJlY29yZHNUb0ltcG9ydDogUmVjb3JkPHN0cmluZywgdW5rbm93bj5bXSA9IFtdO1xuXG4gICAgICBjb25zb2xlLmxvZyhjaGFsay5jeWFuKGBJbXBvcnRpbmcgcmVsYXRlZCBlbnRpdHk6ICR7ZW50aXR5SWR9ICgke3JlY29yZHMubGVuZ3RofSByZWNvcmRzKWApKTtcblxuICAgICAgZm9yIChjb25zdCByZWNvcmQgb2YgcmVjb3Jkcykge1xuICAgICAgICBjb25zdCByZWNvcmRLZXkgPSBgJHtlbnRpdHlJZH0jJHtyZWNvcmQuaWR9YDtcbiAgICAgICAgaWYgKCFjb250ZXh0LmltcG9ydGVkUmVjb3Jkcy5oYXMocmVjb3JkS2V5KSkge1xuICAgICAgICAgIHJlY29yZHNUb0ltcG9ydC5wdXNoKHJlY29yZCk7XG4gICAgICAgICAgY29udGV4dC5pbXBvcnRlZFJlY29yZHMuYWRkKHJlY29yZEtleSk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKHJlY29yZHNUb0ltcG9ydC5sZW5ndGggPiAwKSB7XG4gICAgICAgIGZvciAoY29uc3QgcmVjb3JkIG9mIHJlY29yZHNUb0ltcG9ydCkge1xuICAgICAgICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAgICAgY2hhbGsuZ3JheShgICAtIFByb2Nlc3NpbmcgJHtlbnRpdHlJZH0gcmVjb3JkOmAsIEpTT04uc3RyaW5naWZ5KHJlY29yZCkuc2xpY2UoMCwgMTAwKSksXG4gICAgICAgICAgKTtcbiAgICAgICAgICBjb25zdCBmaXh0dXJlUmVjb3JkcyA9IGF3YWl0IEZpeHR1cmVNYW5hZ2VyLmNyZWF0ZUZpeHR1cmVSZWNvcmQoXG4gICAgICAgICAgICBlbnRpdHksXG4gICAgICAgICAgICByZWNvcmQgYXMgeyBpZDogbnVtYmVyIHwgc3RyaW5nOyBba2V5OiBzdHJpbmddOiBzdHJpbmcgfCBudW1iZXIgfCBib29sZWFuIHwgbnVsbCB9LFxuICAgICAgICAgICAgeyBfZGI6IHRoaXMuc291cmNlRGIsIHNpbmdsZVJlY29yZDogdHJ1ZSB9LFxuICAgICAgICAgICk7XG4gICAgICAgICAgYWxsRml4dHVyZVJlY29yZHMucHVzaCguLi5maXh0dXJlUmVjb3Jkcyk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyAyLiBNYWluIGVudGl0eSBpbXBvcnQgKEVtcGxveWVlIOuTsSlcbiAgICBjb25zdCBtYWluRW50aXR5ID0gdGhpcy5lbnRpdHlNYW5hZ2VyLmdldChleHBsb3JlUmVzdWx0Lm1haW4uZW50aXR5SWQpO1xuICAgIGNvbnN0IG1haW5SZWNvcmRzVG9JbXBvcnQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+W10gPSBbXTtcblxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgY2hhbGsuY3lhbihcbiAgICAgICAgYEltcG9ydGluZyBtYWluIGVudGl0eTogJHtleHBsb3JlUmVzdWx0Lm1haW4uZW50aXR5SWR9ICgke2V4cGxvcmVSZXN1bHQubWFpbi5yZWNvcmRzLmxlbmd0aH0gcmVjb3JkcylgLFxuICAgICAgKSxcbiAgICApO1xuXG4gICAgZm9yIChjb25zdCByZWNvcmQgb2YgZXhwbG9yZVJlc3VsdC5tYWluLnJlY29yZHMpIHtcbiAgICAgIGNvbnN0IHJlY29yZEtleSA9IGAke2V4cGxvcmVSZXN1bHQubWFpbi5lbnRpdHlJZH0jJHtyZWNvcmQuaWR9YDtcbiAgICAgIGlmICghY29udGV4dC5pbXBvcnRlZFJlY29yZHMuaGFzKHJlY29yZEtleSkpIHtcbiAgICAgICAgbWFpblJlY29yZHNUb0ltcG9ydC5wdXNoKHJlY29yZCk7XG4gICAgICAgIGNvbnRleHQuaW1wb3J0ZWRSZWNvcmRzLmFkZChyZWNvcmRLZXkpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChtYWluUmVjb3Jkc1RvSW1wb3J0Lmxlbmd0aCA+IDApIHtcbiAgICAgIGZvciAoY29uc3QgcmVjb3JkIG9mIG1haW5SZWNvcmRzVG9JbXBvcnQpIHtcbiAgICAgICAgY29uc29sZS5sb2coXG4gICAgICAgICAgY2hhbGsuZ3JheShcbiAgICAgICAgICAgIGAgIC0gUHJvY2Vzc2luZyAke2V4cGxvcmVSZXN1bHQubWFpbi5lbnRpdHlJZH0gcmVjb3JkOmAsXG4gICAgICAgICAgICBKU09OLnN0cmluZ2lmeShyZWNvcmQpLnNsaWNlKDAsIDEwMCksXG4gICAgICAgICAgKSxcbiAgICAgICAgKTtcbiAgICAgICAgY29uc3QgZml4dHVyZVJlY29yZHMgPSBhd2FpdCBGaXh0dXJlTWFuYWdlci5jcmVhdGVGaXh0dXJlUmVjb3JkKFxuICAgICAgICAgIG1haW5FbnRpdHksXG4gICAgICAgICAgcmVjb3JkIGFzIHsgaWQ6IG51bWJlciB8IHN0cmluZzsgW2tleTogc3RyaW5nXTogc3RyaW5nIHwgbnVtYmVyIHwgYm9vbGVhbiB8IG51bGwgfSxcbiAgICAgICAgICB7IF9kYjogdGhpcy5zb3VyY2VEYiwgc2luZ2xlUmVjb3JkOiB0cnVlIH0sXG4gICAgICAgICk7XG4gICAgICAgIGFsbEZpeHR1cmVSZWNvcmRzLnB1c2goLi4uZml4dHVyZVJlY29yZHMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIDMuIOuqqOuToCBmaXh0dXJl66W8IO2VnCDrsojsl5Ag7IK97J6FICjsnZjsobTshLEg7Iic7IScIOyekOuPmSDsspjrpqwpXG4gICAgaWYgKGFsbEZpeHR1cmVSZWNvcmRzLmxlbmd0aCA+IDApIHtcbiAgICAgIGF3YWl0IEZpeHR1cmVNYW5hZ2VyLmluc2VydEZpeHR1cmVzKHRoaXMudGFyZ2V0RGJOYW1lLCBhbGxGaXh0dXJlUmVjb3Jkcyk7XG5cbiAgICAgIGNvbnNvbGUubG9nKFxuICAgICAgICBjaGFsay5ncmVlbihcbiAgICAgICAgICBgQXV0by1pbXBvcnRlZCAke2V4cGxvcmVSZXN1bHQubWFpbi5lbnRpdHlJZH0gd2l0aCByZWxhdGlvbnM6IGAgK1xuICAgICAgICAgICAgYCR7ZXhwbG9yZVJlc3VsdC5tYWluLnJlY29yZHMubGVuZ3RofSBtYWluICsgJHtleHBsb3JlUmVzdWx0LnJlbGF0ZWQuc2l6ZX0gcmVsYXRlZCBlbnRpdGllc2AsXG4gICAgICAgICksXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBmaXh0dXJlR2VuZXJhdG9yIOyLpO2WiSAoRmFrZXIuanPrp4wg7KeA7JuQKVxuICAgKlxuICAgKiBmYWtlci4qIO2YleyLneydmCDtkZztmITsi53snYQg7JWI7KCE7ZWY6rKMIO2MjOyLse2VmOyXrCDsi6Ttlontlanri4jri6QuXG4gICAqIOyYiDogXCJmYWtlci5pbnRlcm5ldC5lbWFpbCgpXCIg4oaSIGZha2VyLmludGVybmV0LmVtYWlsKClcbiAgICog7JiIOiBcImZha2VyLmxvcmVtLndvcmRzKDMpXCIg4oaSIGZha2VyLmxvcmVtLndvcmRzKDMpXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGV4ZWN1dGVHZW5lcmF0b3IoXG4gICAgZ2VuZXJhdG9yOiBzdHJpbmcsXG4gICAgcHJvcDogRW50aXR5UHJvcCxcbiAgICBlbnRpdHk6IEVudGl0eSxcbiAgKTogUHJvbWlzZTx1bmtub3duPiB7XG4gICAgLy8gRmFrZXIuanMg7ZGc7ZiE7Iud66eMIOyngOybkFxuICAgIGlmIChnZW5lcmF0b3Iuc3RhcnRzV2l0aChcImZha2VyLlwiKSkge1xuICAgICAgLy8gdXNlcm5hbWXsnbTrgpggbmFtZSDtlYTrk5zripQg7ZWc6rWt7Ja0IGZha2VyIOyCrOyaqVxuICAgICAgY29uc3QgaXNOYW1lRmllbGQgPSBwcm9wLm5hbWUgPT09IFwidXNlcm5hbWVcIiB8fCBwcm9wLm5hbWUgPT09IFwibmFtZVwiO1xuICAgICAgY29uc3QgZmFrZXJNb2R1bGUgPSBhd2FpdCBpbXBvcnQoXCJAZmFrZXItanMvZmFrZXJcIik7XG4gICAgICBjb25zdCBmYWtlciA9IGlzTmFtZUZpZWxkID8gZmFrZXJNb2R1bGUuZmFrZXJLTyA6IGZha2VyTW9kdWxlLmZha2VyO1xuICAgICAgY29uc3QgZXhwciA9IGdlbmVyYXRvci5zbGljZSg2KTsgLy8gXCJmYWtlci5cIiDsoJzqsbBcblxuICAgICAgdHJ5IHtcbiAgICAgICAgLy8g7ZWo7IiYIOqyveuhnOyZgCDsnbjsnpAg7YyM7IuxXG4gICAgICAgIGNvbnN0IG1hdGNoID0gZXhwci5tYXRjaCgvXihbXFx3Ll0rKSg/OlxcKCguKj8pXFwpKT8kLyk7XG4gICAgICAgIGlmICghbWF0Y2gpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRml4dHVyZUdlbmVyYXRvcjogSW52YWxpZCBmYWtlciBleHByZXNzaW9uIGZvciAke3Byb3AubmFtZX06ICR7Z2VuZXJhdG9yfWAsXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IFssIHBhdGgsIGFyZ3NTdHJdID0gbWF0Y2g7XG4gICAgICAgIGNvbnN0IHBhcnRzID0gcGF0aC5zcGxpdChcIi5cIik7XG5cbiAgICAgICAgLy8gZmFrZXIg6rCd7LK07JeQ7IScIO2VqOyImCDssL7quLBcbiAgICAgICAgbGV0IGZuOiB1bmtub3duID0gZmFrZXI7XG4gICAgICAgIGZvciAoY29uc3QgcGFydCBvZiBwYXJ0cykge1xuICAgICAgICAgIGlmICh0eXBlb2YgZm4gPT09IFwib2JqZWN0XCIgJiYgZm4gIT09IG51bGwgJiYgcGFydCBpbiBmbikge1xuICAgICAgICAgICAgZm4gPSAoZm4gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pW3BhcnRdO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZpeHR1cmVHZW5lcmF0b3I6IEludmFsaWQgZmFrZXIgcGF0aCBmb3IgJHtwcm9wLm5hbWV9OiBmYWtlci4ke3BhdGh9YCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8g7ZWo7IiY6rCAIOyVhOuLiOuptCDsl5Drn6xcbiAgICAgICAgaWYgKHR5cGVvZiBmbiAhPT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGaXh0dXJlR2VuZXJhdG9yOiBmYWtlci4ke3BhdGh9IGlzIG5vdCBhIGZ1bmN0aW9uIChmb3IgJHtwcm9wLm5hbWV9KWApO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8g7J247J6QIO2MjOyLsSAoSlNPTiDtmJXsi53rp4wg7KeA7JuQKVxuICAgICAgICBsZXQgYXJnczogdW5rbm93bltdID0gW107XG4gICAgICAgIGlmIChhcmdzU3RyPy50cmltKCkpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gSlNPTiDrsLDsl7TroZwg7YyM7IuxIOyLnOuPhFxuICAgICAgICAgICAgY29uc3QgcGFyc2VkID0gSlNPTi5wYXJzZShgWyR7YXJnc1N0cn1dYCkgYXMgdW5rbm93bjtcbiAgICAgICAgICAgIGFyZ3MgPSBBcnJheS5pc0FycmF5KHBhcnNlZCkgPyBwYXJzZWQgOiBbcGFyc2VkXTtcbiAgICAgICAgICB9IGNhdGNoIHtcbiAgICAgICAgICAgIC8vIOyIq+yekOuCmCDrrLjsnpDsl7Qg64uo7J28IOyduOyekCDsspjrpqxcbiAgICAgICAgICAgIGNvbnN0IHRyaW1tZWQgPSBhcmdzU3RyLnRyaW0oKTtcbiAgICAgICAgICAgIGlmICghTnVtYmVyLmlzTmFOKE51bWJlcih0cmltbWVkKSkpIHtcbiAgICAgICAgICAgICAgYXJncyA9IFtOdW1iZXIodHJpbW1lZCldO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgICAgICAgKHRyaW1tZWQuc3RhcnRzV2l0aCgnXCInKSAmJiB0cmltbWVkLmVuZHNXaXRoKCdcIicpKSB8fFxuICAgICAgICAgICAgICAodHJpbW1lZC5zdGFydHNXaXRoKFwiJ1wiKSAmJiB0cmltbWVkLmVuZHNXaXRoKFwiJ1wiKSlcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICBhcmdzID0gW3RyaW1tZWQuc2xpY2UoMSwgLTEpXTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBgRml4dHVyZUdlbmVyYXRvcjogQ2Fubm90IHBhcnNlIGFyZ3VtZW50cyBmb3IgJHtwcm9wLm5hbWV9OiAke2FyZ3NTdHJ9YCxcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZm4oLi4uYXJncyk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBjb25zb2xlLmxvZyhcbiAgICAgICAgICBjaGFsay55ZWxsb3coXG4gICAgICAgICAgICBgRmFpbGVkIHRvIGV4ZWN1dGUgZ2VuZXJhdG9yIFwiJHtnZW5lcmF0b3J9XCIgZm9yICR7cHJvcC5uYW1lfSwgZmFsbGluZyBiYWNrIHRvIGRlZmF1bHQ6YCxcbiAgICAgICAgICApLFxuICAgICAgICAgIGVycm9yLFxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdGhpcy5nZW5lcmF0ZURlZmF1bHRWYWx1ZShwcm9wLCBlbnRpdHkpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIGZha2VyIOydtOyZuOydmCDtkZztmITsi53snYAg7KeA7JuQ7ZWY7KeAIOyViuydjFxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgY2hhbGsueWVsbG93KFxuICAgICAgICBgVW5zdXBwb3J0ZWQgZ2VuZXJhdG9yIGV4cHJlc3Npb24gZm9yICR7cHJvcC5uYW1lfTogJHtnZW5lcmF0b3J9LiBPbmx5IGZha2VyLiogZXhwcmVzc2lvbnMgYXJlIHN1cHBvcnRlZC4gVXNpbmcgZGVmYXVsdCB2YWx1ZS5gLFxuICAgICAgKSxcbiAgICApO1xuICAgIHJldHVybiB0aGlzLmdlbmVyYXRlRGVmYXVsdFZhbHVlKHByb3AsIGVudGl0eSk7XG4gIH1cblxuICAvKipcbiAgICog7ZWE65Oc7J2YIO2DgOyeheqzvCDsnbTrpoTsnYQg67aE7ISd7ZWY7JesIOyggeygiO2VnCDquLDrs7jqsJLsnYQg7IOd7ISx7ZWp64uI64ukLlxuICAgKlxuICAgKiDsmrDshKDsiJzsnIQ6XG4gICAqIDEuIO2VhOuTnOuqhSDtjKjthLQg66ek7LmtIChzYWxhcnksIGJ1ZGdldCDrk7Eg7J2Y66+47J6I64qUIOuNsOydtO2EsClcbiAgICogMi4g7Yq57IiYIOy8gOydtOyKpCAoRGVwYXJ0bWVudCBuYW1lIOuTsSDrj4TrqZTsnbgg7KeA7IudKVxuICAgKiAzLiDrsLDsl7Qg7YOA7J6FIChKU09OIOuwsOyXtClcbiAgICogNC4gRW51bSDtg4DsnoVcbiAgICogNS4g7YOA7J6F67OEIOq4sOuzuOqwklxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBnZW5lcmF0ZURlZmF1bHRWYWx1ZShwcm9wOiBFbnRpdHlQcm9wLCBlbnRpdHk/OiBFbnRpdHkpOiBQcm9taXNlPHVua25vd24+IHtcbiAgICBjb25zdCBmYWtlck1vZHVsZSA9IGF3YWl0IGltcG9ydChcIkBmYWtlci1qcy9mYWtlclwiKTtcbiAgICBjb25zdCBmYWtlciA9IGZha2VyTW9kdWxlLmZha2VyO1xuICAgIGNvbnN0IGZha2VyS08gPSBmYWtlck1vZHVsZS5mYWtlcktPO1xuICAgIGNvbnN0IGZha2VySkEgPSBmYWtlck1vZHVsZS5mYWtlckpBO1xuXG4gICAgY29uc3QgbG9jYWxlRmFrZXIgPSB0aGlzLmxvY2FsZSA9PT0gXCJrb1wiID8gZmFrZXJLTyA6IHRoaXMubG9jYWxlID09PSBcImphXCIgPyBmYWtlckpBIDogZmFrZXI7XG5cbiAgICAvKipcbiAgICAgKiAxLiDtlYTrk5zrqoXsl5DshJwg7J2Y66+466W8IOy2lOuhoO2VmOyXrCDtmITsi6TsoIHsnbgg642w7J207YSw66W8IOyDneyEse2VqeuLiOuLpC5cbiAgICAgKiDsmIg6IHNhbGFyeSDihpIgMzBNfjE1ME0gKO2VnOq1rSDsl7DrtIkg67KU7JyEKVxuICAgICAqICAgICBidWRnZXQg4oaSIDEwTX41MDBNICjtlITroZzsoJ3tirgg7JiI7IKwIOuylOychClcbiAgICAgKi9cbiAgICBjb25zdCBsb2NhbGVNYXBwaW5ncyA9IHRoaXMubWFwcGluZ3NbdGhpcy5sb2NhbGVdIHx8IHRoaXMubWFwcGluZ3MuZW47XG4gICAgY29uc3Qgbm9ybWFsaXplZE5hbWUgPSBwcm9wLm5hbWUudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9fL2csIFwiXCIpO1xuXG4gICAgZm9yIChjb25zdCBbcGF0dGVybiwgY29uZmlnXSBvZiBPYmplY3QuZW50cmllcyhsb2NhbGVNYXBwaW5ncy5maWVsZF9wYXR0ZXJucykpIHtcbiAgICAgIGlmIChub3JtYWxpemVkTmFtZS5pbmNsdWRlcyhwYXR0ZXJuLnRvTG93ZXJDYXNlKCkpKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMuZXhlY3V0ZUZha2VyRXhwcmVzc2lvbihjb25maWcuZmFrZXIsIHByb3ApO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAgICAgY2hhbGsueWVsbG93KFxuICAgICAgICAgICAgICBgRmFpbGVkIHRvIGV4ZWN1dGUgZmllbGQgcGF0dGVybiBcIiR7cGF0dGVybn1cIiBmb3IgJHtwcm9wLm5hbWV9LCBmYWxsaW5nIGJhY2s6YCxcbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBlcnJvcixcbiAgICAgICAgICApO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogMi4gRGVwYXJ0bWVudCBuYW1l7J2AIO2VnOq1reyWtCDrtoDshJzrqoUg66qp66Gd7JeQ7IScIOyEoO2Dne2VqeuLiOuLpC5cbiAgICAgKiDqs6DsnKDshLHsnYQg7JyE7ZW0IDcwJSDtmZXrpaDroZwgcHJlZml4L3N1ZmZpeOulvCDstpTqsIDtlanri4jri6QuXG4gICAgICovXG4gICAgaWYgKGVudGl0eT8uaWQgPT09IFwiRGVwYXJ0bWVudFwiICYmIHByb3AubmFtZSA9PT0gXCJuYW1lXCIpIHtcbiAgICAgIGNvbnN0IGRlcGFydG1lbnRzID0gW1xuICAgICAgICBcIuqwnOuwnO2MgFwiLFxuICAgICAgICBcIuq4sO2aje2MgFwiLFxuICAgICAgICBcIuuniOy8gO2Mhe2MgFwiLFxuICAgICAgICBcIuyYgeyXhe2MgFwiLFxuICAgICAgICBcIuyduOyCrO2MgFwiLFxuICAgICAgICBcIuy0neustO2MgFwiLFxuICAgICAgICBcIuyerOustO2MgFwiLFxuICAgICAgICBcIu2ajOqzhO2MgFwiLFxuICAgICAgICBcIuuyleustO2MgFwiLFxuICAgICAgICBcIuuUlOyekOyduO2MgFwiLFxuICAgICAgICBcIklU7YyAXCIsXG4gICAgICAgIFwi6rOg6rCd7KeA7JuQ7YyAXCIsXG4gICAgICAgIFwi7ZKI7KeI6rSA66as7YyAXCIsXG4gICAgICAgIFwi7Jew6rWs6rCc67Cc7YyAXCIsXG4gICAgICAgIFwi7IOd7IKw7YyAXCIsXG4gICAgICAgIFwi6rWs66ek7YyAXCIsXG4gICAgICAgIFwi66y866WY7YyAXCIsXG4gICAgICBdO1xuICAgICAgY29uc3QgcHJlZml4ZXMgPSBbXCLsi6Dqt5xcIiwgXCLthrXtlalcIiwgXCLsoITrnrVcIiwgXCLquIDroZzrsoxcIiwgXCLrlJTsp4DthLhcIiwgXCLtlbXsi6xcIl07XG4gICAgICBjb25zdCBzdWZmaXhlcyA9IFtcIjHtjIBcIiwgXCIy7YyAXCIsIFwiM+2MgFwiLCBcIkHtjIBcIiwgXCJC7YyAXCIsIFwi67O467aAXCIsIFwi7IS87YSwXCIsIFwi6re466O5XCJdO1xuXG4gICAgICBjb25zdCBkZXB0ID0gZmFrZXIuaGVscGVycy5hcnJheUVsZW1lbnQoZGVwYXJ0bWVudHMpO1xuXG4gICAgICBjb25zdCByYW5kb20gPSBNYXRoLnJhbmRvbSgpO1xuICAgICAgaWYgKHJhbmRvbSA+IDAuNykge1xuICAgICAgICBjb25zdCBwcmVmaXggPSBmYWtlci5oZWxwZXJzLmFycmF5RWxlbWVudChwcmVmaXhlcyk7XG4gICAgICAgIHJldHVybiBgJHtwcmVmaXh9ICR7ZGVwdH1gO1xuICAgICAgfVxuICAgICAgaWYgKHJhbmRvbSA+IDAuNCkge1xuICAgICAgICBjb25zdCBzdWZmaXggPSBmYWtlci5oZWxwZXJzLmFycmF5RWxlbWVudChzdWZmaXhlcyk7XG4gICAgICAgIHJldHVybiBgJHtkZXB0fSAke3N1ZmZpeH1gO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGRlcHQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogMy4gSlNPTiDtg4DsnoXsnbTrqbTshJwg67Cw7Je07J24IOqyveyasCAoU29uYW11RmlsZVtdLCBzdHJpbmdbXSDrk7EpXG4gICAgICog7ZWE65Oc66qFIO2MqO2EtOydhCDrs7Tqs6Ag7KCB7KCI7ZWcIOuwsOyXtCDrjbDsnbTthLDrpbwg7IOd7ISx7ZWp64uI64ukLlxuICAgICAqL1xuICAgIGlmIChwcm9wLnR5cGUgPT09IFwianNvblwiICYmIFwiaWRcIiBpbiBwcm9wICYmIHByb3AuaWQpIHtcbiAgICAgIGlmIChwcm9wLmlkLmVuZHNXaXRoKFwiW11cIikpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2VuZXJhdGVBcnJheVZhbHVlKHByb3AsIGVudGl0eSwgZmFrZXIsIGxvY2FsZUZha2VyKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKiogNC4gRW51bSDtg4DsnoXsnYAg7KCV7J2Y65CcIOqwkiDspJEg7ZWY64KY66W8IOuenOuNpCDshKDtg53tlanri4jri6QgKi9cbiAgICBpZiAocHJvcC50eXBlID09PSBcImVudW1cIikge1xuICAgICAgbGV0IGVudW1WYWx1ZXM6IHN0cmluZ1tdID0gW107XG5cbiAgICAgIGlmIChcImVudW1cIiBpbiBwcm9wICYmIEFycmF5LmlzQXJyYXkocHJvcC5lbnVtKSAmJiBwcm9wLmVudW0ubGVuZ3RoID4gMCkge1xuICAgICAgICBlbnVtVmFsdWVzID0gcHJvcC5lbnVtO1xuICAgICAgfSBlbHNlIGlmIChcImlkXCIgaW4gcHJvcCAmJiBwcm9wLmlkICYmIGVudGl0eT8uZW51bUxhYmVscz8uW3Byb3AuaWRdKSB7XG4gICAgICAgIGVudW1WYWx1ZXMgPSBPYmplY3Qua2V5cyhlbnRpdHkuZW51bUxhYmVsc1twcm9wLmlkXSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChlbnVtVmFsdWVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgcmV0dXJuIGZha2VyLmhlbHBlcnMuYXJyYXlFbGVtZW50KGVudW1WYWx1ZXMpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHByb3AubnVsbGFibGUgPyBudWxsIDogXCJVTktOT1dOXCI7XG4gICAgfVxuXG4gICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtW11cIikge1xuICAgICAgbGV0IGVudW1WYWx1ZXM6IHN0cmluZ1tdID0gW107XG5cbiAgICAgIGlmIChcImVudW1cIiBpbiBwcm9wICYmIEFycmF5LmlzQXJyYXkocHJvcC5lbnVtKSAmJiBwcm9wLmVudW0ubGVuZ3RoID4gMCkge1xuICAgICAgICBlbnVtVmFsdWVzID0gcHJvcC5lbnVtO1xuICAgICAgfSBlbHNlIGlmIChcImlkXCIgaW4gcHJvcCAmJiBwcm9wLmlkICYmIGVudGl0eT8uZW51bUxhYmVscz8uW3Byb3AuaWRdKSB7XG4gICAgICAgIGVudW1WYWx1ZXMgPSBPYmplY3Qua2V5cyhlbnRpdHkuZW51bUxhYmVsc1twcm9wLmlkXSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChlbnVtVmFsdWVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgcmV0dXJuIFtmYWtlci5oZWxwZXJzLmFycmF5RWxlbWVudChlbnVtVmFsdWVzKV07XG4gICAgICB9XG4gICAgICByZXR1cm4gW107XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogNS4gVmVjdG9yIO2DgOyeheydgCDtmITsnqwg7KeA7JuQ7ZWY7KeAIOyViuycvOuvgOuhnCBudWxs7J2EIOuwmO2ZmO2VqeuLiOuLpC5cbiAgICAgKiDtlqXtm4QgQUkgZW1iZWRkaW5nIOyDneyEsSDquLDriqUg7LaU6rCAIOyLnCDqtaztmIQg7JiI7KCV7J6F64uI64ukLlxuICAgICAqL1xuICAgIGlmIChwcm9wLnR5cGUgPT09IFwidmVjdG9yXCIgfHwgcHJvcC50eXBlID09PSBcInZlY3RvcltdXCIgfHwgcHJvcC50eXBlID09PSBcInRzdmVjdG9yXCIpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIC8qKiA2LiDtg4DsnoXrs4Qg6riw67O4IEZha2VyIO2RnO2YhOyLneydhCDsi6Ttlontlanri4jri6QgKi9cbiAgICBjb25zdCB0eXBlRGVmYXVsdCA9IGxvY2FsZU1hcHBpbmdzLnR5cGVfZGVmYXVsdHNbcHJvcC50eXBlXTtcbiAgICBpZiAodHlwZURlZmF1bHQpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCB0aGlzLmV4ZWN1dGVGYWtlckV4cHJlc3Npb24odHlwZURlZmF1bHQuZmFrZXIsIHByb3ApO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc29sZS5sb2coXG4gICAgICAgICAgY2hhbGsueWVsbG93KGBGYWlsZWQgdG8gZXhlY3V0ZSB0eXBlIGRlZmF1bHQgZm9yICR7cHJvcC50eXBlfSwgdXNpbmcgZmFsbGJhY2s6YCwgZXJyb3IpLFxuICAgICAgICApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qKiA3LiDrp6TtlZHrkJjsp4Ag7JWK7J2AIO2DgOyeheydgCDquLDrs7ggRmFrZXIg7ZWo7IiY66GcIOyymOumrO2VqeuLiOuLpCAqL1xuICAgIHN3aXRjaCAocHJvcC50eXBlKSB7XG4gICAgICBjYXNlIFwic3RyaW5nXCI6XG4gICAgICBjYXNlIFwic3RyaW5nW11cIjpcbiAgICAgICAgcmV0dXJuIGZha2VyLmxvcmVtLndvcmRzKDMpO1xuICAgICAgY2FzZSBcImludGVnZXJcIjpcbiAgICAgICAgcmV0dXJuIGZha2VyLm51bWJlci5pbnQoeyBtaW46IDEsIG1heDogMTAwMCB9KTtcbiAgICAgIGNhc2UgXCJpbnRlZ2VyW11cIjpcbiAgICAgICAgcmV0dXJuIFtmYWtlci5udW1iZXIuaW50KHsgbWluOiAxLCBtYXg6IDEwMDAgfSldO1xuICAgICAgY2FzZSBcImJpZ0ludGVnZXJcIjpcbiAgICAgICAgcmV0dXJuIGZha2VyLm51bWJlci5iaWdJbnQoeyBtaW46IDFuLCBtYXg6IDEwMDBuIH0pO1xuICAgICAgY2FzZSBcImJpZ0ludGVnZXJbXVwiOlxuICAgICAgICByZXR1cm4gW2Zha2VyLm51bWJlci5iaWdJbnQoeyBtaW46IDFuLCBtYXg6IDEwMDBuIH0pXTtcbiAgICAgIGNhc2UgXCJudW1iZXJcIjpcbiAgICAgIGNhc2UgXCJudW1lcmljXCI6XG4gICAgICAgIHJldHVybiBmYWtlci5udW1iZXIuZmxvYXQoeyBtaW46IDAsIG1heDogMTAwMCB9KTtcbiAgICAgIGNhc2UgXCJudW1iZXJbXVwiOlxuICAgICAgY2FzZSBcIm51bWVyaWNbXVwiOlxuICAgICAgICByZXR1cm4gW2Zha2VyLm51bWJlci5mbG9hdCh7IG1pbjogMCwgbWF4OiAxMDAwIH0pXTtcbiAgICAgIGNhc2UgXCJib29sZWFuXCI6XG4gICAgICAgIHJldHVybiBmYWtlci5kYXRhdHlwZS5ib29sZWFuKCk7XG4gICAgICBjYXNlIFwiYm9vbGVhbltdXCI6XG4gICAgICAgIHJldHVybiBbZmFrZXIuZGF0YXR5cGUuYm9vbGVhbigpXTtcbiAgICAgIGNhc2UgXCJkYXRlXCI6XG4gICAgICBjYXNlIFwiZGF0ZVtdXCI6XG4gICAgICAgIHJldHVybiBmYWtlci5kYXRlLnBhc3QoKTtcbiAgICAgIGNhc2UgXCJqc29uXCI6XG4gICAgICAgIHJldHVybiB7fTtcbiAgICAgIGNhc2UgXCJ1dWlkXCI6XG4gICAgICBjYXNlIFwidXVpZFtdXCI6XG4gICAgICAgIHJldHVybiBmYWtlci5zdHJpbmcudXVpZCgpO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIOuwsOyXtCDtg4DsnoXsnZgg6rCS7J2EIOyDneyEse2VqeuLiOuLpC5cbiAgICpcbiAgICog7YOA7J6FIElE7JmAIO2VhOuTnOuqhSDtjKjthLTsnYQg67aE7ISd7ZWY7JesIOyggeygiO2VnCDrsLDsl7Qg642w7J207YSw66W8IOyDneyEse2VqeuLiOuLpC5cbiAgICog7JiIOiBpbWFnZV91cmxzIOKGkiBbe3VybCwgbmFtZSwgbWltZV90eXBlfSwgLi4uXVxuICAgKiAgICAgdGFnX2lkcyDihpIgWzEsIDIzLCA0NV1cbiAgICovXG4gIHByaXZhdGUgZ2VuZXJhdGVBcnJheVZhbHVlKFxuICAgIHByb3A6IEVudGl0eVByb3AsXG4gICAgX2VudGl0eTogRW50aXR5IHwgdW5kZWZpbmVkLFxuICAgIGZha2VyOiB0eXBlb2YgaW1wb3J0KFwiQGZha2VyLWpzL2Zha2VyXCIpLmZha2VyLFxuICAgIF9sb2NhbGVGYWtlcjogdHlwZW9mIGltcG9ydChcIkBmYWtlci1qcy9mYWtlclwiKS5mYWtlcixcbiAgKTogdW5rbm93bltdIHtcbiAgICBjb25zdCBjb3VudCA9IGZha2VyLm51bWJlci5pbnQoeyBtaW46IDEsIG1heDogMyB9KTtcblxuICAgIC8qKiBTb25hbXVGaWxlW13snYAgU29uYW11IOuCtOyepSDtg4DsnoXsnLzroZwg6rWs7KGw6rCAIOygle2VtOyguCDsnojsirXri4jri6QgKi9cbiAgICBpZiAoXCJpZFwiIGluIHByb3AgJiYgcHJvcC5pZCA9PT0gXCJTb25hbXVGaWxlW11cIikge1xuICAgICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IGNvdW50IH0sICgpID0+ICh7XG4gICAgICAgIHVybDogZmFrZXIuaW1hZ2UudXJsKCksXG4gICAgICAgIG5hbWU6IGZha2VyLnN5c3RlbS5maWxlTmFtZSgpLFxuICAgICAgICBtaW1lX3R5cGU6IGZha2VyLmhlbHBlcnMuYXJyYXlFbGVtZW50KFtcbiAgICAgICAgICBcImltYWdlL2pwZWdcIixcbiAgICAgICAgICBcImltYWdlL3BuZ1wiLFxuICAgICAgICAgIFwiaW1hZ2UvZ2lmXCIsXG4gICAgICAgICAgXCJhcHBsaWNhdGlvbi9wZGZcIixcbiAgICAgICAgXSksXG4gICAgICB9KSk7XG4gICAgfVxuXG4gICAgLyoqIO2VhOuTnOuqheyXkOyEnCDrsLDsl7TsnZgg7Jqp64+E66W8IOy2lOuhoO2VqeuLiOuLpCAqL1xuICAgIGNvbnN0IG5vcm1hbGl6ZWROYW1lID0gcHJvcC5uYW1lLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgvXy9nLCBcIlwiKTtcblxuICAgIGlmIChub3JtYWxpemVkTmFtZS5pbmNsdWRlcyhcInVybFwiKSB8fCBub3JtYWxpemVkTmFtZS5pbmNsdWRlcyhcImltYWdlXCIpKSB7XG4gICAgICByZXR1cm4gQXJyYXkuZnJvbSh7IGxlbmd0aDogY291bnQgfSwgKCkgPT4gZmFrZXIuaW50ZXJuZXQudXJsKCkpO1xuICAgIH1cblxuICAgIGlmIChub3JtYWxpemVkTmFtZS5pbmNsdWRlcyhcImlkXCIpICYmIG5vcm1hbGl6ZWROYW1lLmVuZHNXaXRoKFwic1wiKSkge1xuICAgICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IGNvdW50IH0sICgpID0+IGZha2VyLm51bWJlci5pbnQoeyBtaW46IDEsIG1heDogMTAwIH0pKTtcbiAgICB9XG5cbiAgICBpZiAobm9ybWFsaXplZE5hbWUuaW5jbHVkZXMoXCJ0YWdcIikgfHwgbm9ybWFsaXplZE5hbWUuaW5jbHVkZXMoXCJuYW1lXCIpKSB7XG4gICAgICByZXR1cm4gQXJyYXkuZnJvbSh7IGxlbmd0aDogY291bnQgfSwgKCkgPT4gZmFrZXIubG9yZW0ud29yZCgpKTtcbiAgICB9XG5cbiAgICAvKiog7Yyo7YS0IOunpOy5reuQmOyngCDslYrsnLzrqbQg67mIIOuwsOyXtOydhCDrsJjtmZjtlanri4jri6QgKi9cbiAgICByZXR1cm4gW107XG4gIH1cblxuICAvKipcbiAgICogSlNPTiDrp6TtlZHsnZggRmFrZXIg7ZGc7ZiE7Iud7J2EIO2MjOyLse2VmOyXrCDsi6Ttlontlanri4jri6QuXG4gICAqXG4gICAqIO2RnO2YhOyLnSDsmIjsi5w6XG4gICAqIC0gXCJmYWtlci5pbnRlcm5ldC5lbWFpbCgpXCIg4oaSIOyduOyekCDsl4bsnYxcbiAgICogLSBcImZha2VyLm51bWJlci5pbnQoeyBtaW46IDEsIG1heDogMTAwIH0pXCIg4oaSIEpTT04g7J247J6QXG4gICAqIC0gXCJ7fVwiIOKGkiDrpqzthLDrn7Qg6rCSIChKU09OLnBhcnNlKVxuICAgKlxuICAgKiBmYWtlcktPLCBmYWtlckpB64+EIOyngOybkO2VmOyXrCDri6Tqta3slrQg642w7J207YSw66W8IOyDneyEse2VqeuLiOuLpC5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZXhlY3V0ZUZha2VyRXhwcmVzc2lvbihleHByZXNzaW9uOiBzdHJpbmcsIHByb3A6IEVudGl0eVByb3ApOiBQcm9taXNlPHVua25vd24+IHtcbiAgICBjb25zdCBmYWtlck1vZHVsZSA9IGF3YWl0IGltcG9ydChcIkBmYWtlci1qcy9mYWtlclwiKTtcbiAgICBjb25zdCBmYWtlciA9IGZha2VyTW9kdWxlLmZha2VyO1xuICAgIGNvbnN0IGZha2VyS08gPSBmYWtlck1vZHVsZS5mYWtlcktPO1xuICAgIGNvbnN0IGZha2VySkEgPSBmYWtlck1vZHVsZS5mYWtlckpBO1xuXG4gICAgLyoqIEZha2VyIO2RnO2YhOyLneydtCDslYTri4wg66as7YSw65+0IOqwkuydgCBKU09O7Jy866GcIO2MjOyLse2VqeuLiOuLpCAqL1xuICAgIGlmICghZXhwcmVzc2lvbi5zdGFydHNXaXRoKFwiZmFrZXJcIikpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBKU09OLnBhcnNlKGV4cHJlc3Npb24pO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiBleHByZXNzaW9uO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qKiDtkZztmITsi53sl5DshJwgRmFrZXIg6rCd7LK07JmAIOqyveuhnOulvCDstpTstpztlanri4jri6QgKi9cbiAgICBjb25zdCBtYXRjaCA9IGV4cHJlc3Npb24ubWF0Y2goL14oZmFrZXJ8ZmFrZXJLT3xmYWtlckpBKVxcLiguKj8pJC8pO1xuICAgIGlmICghbWF0Y2gpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBmYWtlciBleHByZXNzaW9uOiAke2V4cHJlc3Npb259YCk7XG4gICAgfVxuXG4gICAgY29uc3QgWywgZmFrZXJOYW1lLCBleHByXSA9IG1hdGNoO1xuICAgIGNvbnN0IHNlbGVjdGVkRmFrZXIgPVxuICAgICAgZmFrZXJOYW1lID09PSBcImZha2VyS09cIiA/IGZha2VyS08gOiBmYWtlck5hbWUgPT09IFwiZmFrZXJKQVwiID8gZmFrZXJKQSA6IGZha2VyO1xuXG4gICAgY29uc3QgZnVuY01hdGNoID0gZXhwci5tYXRjaCgvXihbXFx3Ll0rKSg/OlxcKCguKj8pXFwpKT8kLyk7XG4gICAgaWYgKCFmdW5jTWF0Y2gpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBmYWtlciBleHByZXNzaW9uIGZvciAke3Byb3AubmFtZX06ICR7ZXhwcmVzc2lvbn1gKTtcbiAgICB9XG5cbiAgICBjb25zdCBbLCBwYXRoLCBhcmdzU3RyXSA9IGZ1bmNNYXRjaDtcbiAgICBjb25zdCBwYXJ0cyA9IHBhdGguc3BsaXQoXCIuXCIpO1xuXG4gICAgLyoqIOygkCDtkZzquLDrspUoZG90IG5vdGF0aW9uKeycvOuhnCBGYWtlciDtlajsiJjrpbwg7LC+7JWE6rCR64uI64ukICovXG4gICAgbGV0IGZuOiB1bmtub3duID0gc2VsZWN0ZWRGYWtlcjtcbiAgICBmb3IgKGNvbnN0IHBhcnQgb2YgcGFydHMpIHtcbiAgICAgIGlmICh0eXBlb2YgZm4gPT09IFwib2JqZWN0XCIgJiYgZm4gIT09IG51bGwgJiYgcGFydCBpbiBmbikge1xuICAgICAgICBmbiA9IChmbiBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPilbcGFydF07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgZmFrZXIgcGF0aCBmb3IgJHtwcm9wLm5hbWV9OiAke2Zha2VyTmFtZX0uJHtwYXRofWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh0eXBlb2YgZm4gIT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGAke2Zha2VyTmFtZX0uJHtwYXRofSBpcyBub3QgYSBmdW5jdGlvbiAoZm9yICR7cHJvcC5uYW1lfSlgKTtcbiAgICB9XG5cbiAgICAvKiog7ZWo7IiYIOyduOyekOulvCBKU09O7Jy866GcIO2MjOyLse2VqeuLiOuLpCAqL1xuICAgIGxldCBhcmdzOiB1bmtub3duW10gPSBbXTtcbiAgICBpZiAoYXJnc1N0cj8udHJpbSgpKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGBbJHthcmdzU3RyfV1gKSBhcyB1bmtub3duO1xuICAgICAgICBhcmdzID0gQXJyYXkuaXNBcnJheShwYXJzZWQpID8gcGFyc2VkIDogW3BhcnNlZF07XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLyoqIEpTT04g7YyM7IuxIOyLpO2MqCDsi5wg64uo7IicIOyIq+yekC/rrLjsnpDsl7TroZwg7Iuc64+E7ZWp64uI64ukICovXG4gICAgICAgIGNvbnN0IHRyaW1tZWQgPSBhcmdzU3RyLnRyaW0oKTtcbiAgICAgICAgaWYgKCFOdW1iZXIuaXNOYU4oTnVtYmVyKHRyaW1tZWQpKSkge1xuICAgICAgICAgIGFyZ3MgPSBbTnVtYmVyKHRyaW1tZWQpXTtcbiAgICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgICAodHJpbW1lZC5zdGFydHNXaXRoKCdcIicpICYmIHRyaW1tZWQuZW5kc1dpdGgoJ1wiJykpIHx8XG4gICAgICAgICAgKHRyaW1tZWQuc3RhcnRzV2l0aChcIidcIikgJiYgdHJpbW1lZC5lbmRzV2l0aChcIidcIikpXG4gICAgICAgICkge1xuICAgICAgICAgIGFyZ3MgPSBbdHJpbW1lZC5zbGljZSgxLCAtMSldO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgQ2Fubm90IHBhcnNlIGFyZ3VtZW50cyBmb3IgJHtwcm9wLm5hbWV9OiAke2FyZ3NTdHJ9YCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gZm4oLi4uYXJncyk7XG4gIH1cblxuICAvKipcbiAgICogZml4dHVyZUhpbnTrpbwgTExN7JeQ6rKMIOyghOuLrO2VmOyXrCDtmITsi6TsoIHsnbgg7YWM7Iqk7Yq4IOuNsOydtO2EsOulvCDsg53shLHtlanri4jri6QuXG4gICAqXG4gICAqIGZha2VyLmpz66Gc64qUIOyDneyEse2VmOq4sCDslrTroKTsmrQg67O17J6h7ZWcIO2FjeyKpO2KuCjsnpDquLDshozqsJwsIOyEpOuqheusuCDrk7Ep66W8XG4gICAqIExMTeydhCDtmZzsmqntlZjsl6wg7IOd7ISx7ZWp64uI64ukLiDrj5nsnbztlZwgaGludOyXkCDrjIDtlZwg7KSR67O1IO2YuOy2nOydhCDrsKnsp4DtlZjquLAg7JyE7ZW0XG4gICAqIOy6kOyLseydhCDquLDrs7jsnLzroZwg7KeA7JuQ7ZWp64uI64ukIChMTE0gQVBJIOu5hOyaqSDsoIjqsJApLlxuICAgKlxuICAgKiBhaSDtjKjtgqTsp4DripQgZHluYW1pYyBpbXBvcnTroZwg67aI65+s7Jik66+A66GcLCB1c2VMTE3snbQgZmFsc2Xsnbgg6rK97JqwXG4gICAqIOydmOyhtOyEseydtCDshKTsuZjrkJjsp4Ag7JWK7JWE64+EIGZpeHR1cmUg7IOd7ISx7J20IOygleyDgSDrj5nsnpHtlanri4jri6QuXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdlbmVyYXRlV2l0aExMTShcbiAgICBmaXh0dXJlSGludDogc3RyaW5nLFxuICAgIHByb3A6IEVudGl0eVByb3AsXG4gICAgZW50aXR5OiBFbnRpdHksXG4gICk6IFByb21pc2U8dW5rbm93bj4ge1xuICAgIGNvbnN0IGNhY2hlS2V5ID0gYCR7ZW50aXR5LmlkfToke3Byb3AubmFtZX06JHtmaXh0dXJlSGludH1gO1xuICAgIGlmICh0aGlzLm9wdGlvbnMuZW5hYmxlTExNQ2FjaGUgJiYgdGhpcy5sbG1DYWNoZS5oYXMoY2FjaGVLZXkpKSB7XG4gICAgICByZXR1cm4gdGhpcy5sbG1DYWNoZS5nZXQoY2FjaGVLZXkpO1xuICAgIH1cblxuICAgIGNvbnN0IGFwaUtleSA9IHRoaXMuZ2V0QXBpS2V5KCk7XG4gICAgY29uc3QgeyBjcmVhdGVBbnRocm9waWMgfSA9IGF3YWl0IGltcG9ydChcIkBhaS1zZGsvYW50aHJvcGljXCIpO1xuICAgIGNvbnN0IHsgZ2VuZXJhdGVUZXh0IH0gPSBhd2FpdCBpbXBvcnQoXCJhaVwiKTtcblxuICAgIGNvbnN0IHsgdGV4dCB9ID0gYXdhaXQgZ2VuZXJhdGVUZXh0KHtcbiAgICAgIG1vZGVsOiBjcmVhdGVBbnRocm9waWMoeyBhcGlLZXkgfSkodGhpcy5vcHRpb25zLmxsbU1vZGVsIHx8IFwiY2xhdWRlLXNvbm5ldC00LTVcIiksXG4gICAgICBwcm9tcHQ6IHRoaXMuYnVpbGRMTE1Qcm9tcHQoZml4dHVyZUhpbnQsIHByb3AsIGVudGl0eSksXG4gICAgfSk7XG5cbiAgICBjb25zdCB2YWx1ZSA9IHRoaXMucGFyc2VMTE1SZXNwb25zZSh0ZXh0LCBwcm9wLnR5cGUpO1xuICAgIGlmICh0aGlzLm9wdGlvbnMuZW5hYmxlTExNQ2FjaGUpIHtcbiAgICAgIHRoaXMubGxtQ2FjaGUuc2V0KGNhY2hlS2V5LCB2YWx1ZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG5cbiAgcHJpdmF0ZSBidWlsZExMTVByb21wdChoaW50OiBzdHJpbmcsIHByb3A6IEVudGl0eVByb3AsIGVudGl0eTogRW50aXR5KTogc3RyaW5nIHtcbiAgICBjb25zdCBsb2NhbGUgPSB0aGlzLm9wdGlvbnMubG9jYWxlIHx8IFwia29cIjtcbiAgICBjb25zdCBsYW5ndWFnZSA9IGxvY2FsZSA9PT0gXCJrb1wiID8gXCJLb3JlYW5cIiA6IGxvY2FsZSA9PT0gXCJqYVwiID8gXCJKYXBhbmVzZVwiIDogXCJFbmdsaXNoXCI7XG5cbiAgICBsZXQgcHJvbXB0ID0gYEdlbmVyYXRlIHRlc3QgZGF0YSBmb3IgJHtlbnRpdHkuaWR9LiR7cHJvcC5uYW1lfSAodHlwZTogJHtwcm9wLnR5cGV9KVxuXG5SZXF1aXJlbWVudDogJHtoaW50fVxuXG5SdWxlczpcbi0gUmV0dXJuIE9OTFkgdGhlIHZhbHVlLCBubyBleHBsYW5hdGlvbiBvciBtYXJrZG93blxuLSBVc2UgJHtsYW5ndWFnZX0gbGFuZ3VhZ2UgaWYgYXBwbGljYWJsZVxuLSBGb3JtYXQ6ICR7dGhpcy5nZXRFeHBlY3RlZEZvcm1hdChwcm9wLnR5cGUpfWA7XG5cbiAgICAvLyBlbnVtIO2DgOyeheyduCDqsr3smrAg6rCA64ql7ZWcIOqwkiDrqqnroZ0g7LaU6rCAXG4gICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtXCIgfHwgcHJvcC50eXBlID09PSBcImVudW1bXVwiKSB7XG4gICAgICBsZXQgZW51bVZhbHVlczogc3RyaW5nW10gPSBbXTtcblxuICAgICAgaWYgKFwiZW51bVwiIGluIHByb3AgJiYgQXJyYXkuaXNBcnJheShwcm9wLmVudW0pICYmIHByb3AuZW51bS5sZW5ndGggPiAwKSB7XG4gICAgICAgIGVudW1WYWx1ZXMgPSBwcm9wLmVudW07XG4gICAgICB9IGVsc2UgaWYgKFwiaWRcIiBpbiBwcm9wICYmIHByb3AuaWQgJiYgZW50aXR5Py5lbnVtTGFiZWxzPy5bcHJvcC5pZF0pIHtcbiAgICAgICAgZW51bVZhbHVlcyA9IE9iamVjdC5rZXlzKGVudGl0eS5lbnVtTGFiZWxzW3Byb3AuaWRdKTtcbiAgICAgIH1cblxuICAgICAgaWYgKGVudW1WYWx1ZXMubGVuZ3RoID4gMCkge1xuICAgICAgICBwcm9tcHQgKz0gYFxcbi0gSU1QT1JUQU5UOiBDaG9vc2UgT05MWSBmcm9tIHRoZXNlIGFsbG93ZWQgdmFsdWVzOiAke2VudW1WYWx1ZXMuam9pbihcIiwgXCIpfWA7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcHJvbXB0ICs9IGBcXG5cXG5FeGFtcGxlOiAke3RoaXMuZ2V0RXhhbXBsZUZvclR5cGUocHJvcC50eXBlLCBsb2NhbGUpfWA7XG5cbiAgICByZXR1cm4gcHJvbXB0O1xuICB9XG5cbiAgcHJpdmF0ZSBwYXJzZUxMTVJlc3BvbnNlKHRleHQ6IHN0cmluZywgcHJvcFR5cGU6IHN0cmluZyk6IHVua25vd24ge1xuICAgIGNvbnN0IGNsZWFuZWQgPSB0ZXh0LnRyaW0oKTtcblxuICAgIC8vIOuwsOyXtCDtg4DsnoUg7LKY66asXG4gICAgaWYgKHByb3BUeXBlLmVuZHNXaXRoKFwiW11cIikpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHBhcnNlZCA9IEpTT04ucGFyc2UoY2xlYW5lZCk7XG4gICAgICAgIGNvbnN0IGJhc2VUeXBlID0gcHJvcFR5cGUuc2xpY2UoMCwgLTIpOyAvLyBcImludGVnZXJbXVwiIC0+IFwiaW50ZWdlclwiXG5cbiAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkocGFyc2VkKSkge1xuICAgICAgICAgIHJldHVybiBwYXJzZWQubWFwKChpdGVtKSA9PiB7XG4gICAgICAgICAgICAvLyBudWxsL3VuZGVmaW5lZOuKlCDtg4DsnoXrs4Qg6riw67O46rCS7Jy866GcXG4gICAgICAgICAgICBpZiAoaXRlbSA9PT0gbnVsbCB8fCBpdGVtID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuZ2V0RGVmYXVsdFZhbHVlRm9yVHlwZShiYXNlVHlwZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyDqsJ3ssrTripQgSlNPTi5zdHJpbmdpZnkg7ZuEIO2MjOyLsSAoanNvbiDtg4DsnoXsnbgg6rK97JqwKVxuICAgICAgICAgICAgaWYgKHR5cGVvZiBpdGVtID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgICAgICAgIHJldHVybiBiYXNlVHlwZSA9PT0gXCJqc29uXCJcbiAgICAgICAgICAgICAgICA/IGl0ZW1cbiAgICAgICAgICAgICAgICA6IHRoaXMucGFyc2VTY2FsYXJWYWx1ZShKU09OLnN0cmluZ2lmeShpdGVtKSwgYmFzZVR5cGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gcHJpbWl0aXZlIOqwkuydgCDrrLjsnpDsl7TroZwg67OA7ZmYIO2bhCDtjIzsi7FcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnBhcnNlU2NhbGFyVmFsdWUoU3RyaW5nKGl0ZW0pLCBiYXNlVHlwZSk7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyDri6jsnbwg6rCS7J20IOyYqCDqsr3smrAg67Cw7Je066GcIOqwkOyLuOq4sFxuICAgICAgICBpZiAocGFyc2VkID09PSBudWxsIHx8IHBhcnNlZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgcmV0dXJuIFt0aGlzLmdldERlZmF1bHRWYWx1ZUZvclR5cGUoYmFzZVR5cGUpXTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gW3RoaXMucGFyc2VTY2FsYXJWYWx1ZShTdHJpbmcocGFyc2VkKSwgYmFzZVR5cGUpXTtcbiAgICAgIH0gY2F0Y2gge1xuICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucGFyc2VTY2FsYXJWYWx1ZShjbGVhbmVkLCBwcm9wVHlwZSk7XG4gIH1cblxuICBwcml2YXRlIGdldERlZmF1bHRWYWx1ZUZvclR5cGUocHJvcFR5cGU6IHN0cmluZyk6IHVua25vd24ge1xuICAgIHN3aXRjaCAocHJvcFR5cGUpIHtcbiAgICAgIGNhc2UgXCJpbnRlZ2VyXCI6XG4gICAgICAgIHJldHVybiAwO1xuICAgICAgY2FzZSBcImJpZ0ludGVnZXJcIjpcbiAgICAgICAgcmV0dXJuIDBuO1xuICAgICAgY2FzZSBcImZsb2F0XCI6XG4gICAgICBjYXNlIFwibnVtYmVyXCI6XG4gICAgICBjYXNlIFwibnVtZXJpY1wiOlxuICAgICAgICByZXR1cm4gMDtcbiAgICAgIGNhc2UgXCJib29sZWFuXCI6XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIGNhc2UgXCJkYXRlXCI6XG4gICAgICAgIHJldHVybiBuZXcgRGF0ZSgpO1xuICAgICAgY2FzZSBcImpzb25cIjpcbiAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgY2FzZSBcInV1aWRcIjpcbiAgICAgICAgcmV0dXJuIFwiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwXCI7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gXCJcIjtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIHBhcnNlU2NhbGFyVmFsdWUodGV4dDogc3RyaW5nLCBwcm9wVHlwZTogc3RyaW5nKTogdW5rbm93biB7XG4gICAgY29uc3QgY2xlYW5lZCA9IHRleHQudHJpbSgpO1xuXG4gICAgc3dpdGNoIChwcm9wVHlwZSkge1xuICAgICAgY2FzZSBcImludGVnZXJcIjoge1xuICAgICAgICBjb25zdCBudW0gPSBwYXJzZUludChjbGVhbmVkLCAxMCk7XG4gICAgICAgIHJldHVybiBOdW1iZXIuaXNOYU4obnVtKSA/IDAgOiBudW07XG4gICAgICB9XG4gICAgICBjYXNlIFwiYmlnSW50ZWdlclwiOiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcmV0dXJuIEJpZ0ludChjbGVhbmVkKTtcbiAgICAgICAgfSBjYXRjaCB7XG4gICAgICAgICAgcmV0dXJuIDBuO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjYXNlIFwiZmxvYXRcIjpcbiAgICAgIGNhc2UgXCJudW1iZXJcIjpcbiAgICAgIGNhc2UgXCJudW1lcmljXCI6IHtcbiAgICAgICAgY29uc3QgbnVtID0gcGFyc2VGbG9hdChjbGVhbmVkKTtcbiAgICAgICAgcmV0dXJuIE51bWJlci5pc05hTihudW0pID8gMCA6IG51bTtcbiAgICAgIH1cbiAgICAgIGNhc2UgXCJib29sZWFuXCI6XG4gICAgICAgIHJldHVybiBjbGVhbmVkLnRvTG93ZXJDYXNlKCkgPT09IFwidHJ1ZVwiO1xuICAgICAgY2FzZSBcImRhdGVcIjoge1xuICAgICAgICBjb25zdCBkYXRlID0gbmV3IERhdGUoY2xlYW5lZCk7XG4gICAgICAgIHJldHVybiBOdW1iZXIuaXNOYU4oZGF0ZS5nZXRUaW1lKCkpID8gbmV3IERhdGUoKSA6IGRhdGU7XG4gICAgICB9XG4gICAgICBjYXNlIFwianNvblwiOlxuICAgICAgICB0cnkge1xuICAgICAgICAgIHJldHVybiBKU09OLnBhcnNlKGNsZWFuZWQpO1xuICAgICAgICB9IGNhdGNoIHtcbiAgICAgICAgICByZXR1cm4gY2xlYW5lZDtcbiAgICAgICAgfVxuICAgICAgY2FzZSBcInV1aWRcIjpcbiAgICAgIGNhc2UgXCJlbnVtXCI6XG4gICAgICAgIHJldHVybiBjbGVhbmVkO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIGNsZWFuZWQ7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNvbmFtdS5zZWNyZXTsnYQg7Jqw7ISg7Jy866GcIO2VmOqzoCwg7JeG7Jy866m0IO2ZmOqyveuzgOyImOyXkOyEnCBBUEkg7YKk66W8IOydveyKteuLiOuLpC5cbiAgICpcbiAgICogU29uYW11LnNlY3JldOydgCDtlITroZzsoJ3tirjrs4Qg7ISk7KCVKHNvbmFtdS5jb25maWcudHMp7J2066+A66GcIOuNlCDrhpLsnYAg7Jqw7ISg7Iic7JyE66W8IOqwgOyngOupsCxcbiAgICog7ZmY6rK967OA7IiY64qUIOqwnOuwnCDtmZjqsr3snbTrgpggQ0kvQ0Tsl5DshJwgZmFsbGJhY2vsnLzroZwg7IKs7Jqp65Cp64uI64ukLlxuICAgKi9cbiAgcHJpdmF0ZSBnZXRBcGlLZXkoKTogc3RyaW5nIHtcbiAgICBsZXQgYXBpS2V5OiBzdHJpbmcgfCB1bmRlZmluZWQ7XG5cbiAgICB0cnkge1xuICAgICAgY29uc3QgeyBTb25hbXUgfSA9IHJlcXVpcmUoXCIuLi9hcGlcIik7XG4gICAgICBhcGlLZXkgPSBTb25hbXUuc2VjcmV0Py5hbnRocm9waWNfYXBpX2tleTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIFNvbmFtdeqwgCDstIjquLDtmZTrkJjsp4Ag7JWK7J2AIOqyveyasCAo7YWM7Iqk7Yq4IO2ZmOqyvSDrk7EpXG4gICAgfVxuXG4gICAgaWYgKCFhcGlLZXkpIHtcbiAgICAgIGFwaUtleSA9IHByb2Nlc3MuZW52LkFOVEhST1BJQ19BUElfS0VZO1xuICAgIH1cblxuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIFwiQU5USFJPUElDX0FQSV9LRVkgbm90IGZvdW5kLiBTZXQgaXQgaW4gZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIFNvbmFtdS5zZWNyZXQuYW50aHJvcGljX2FwaV9rZXlcIixcbiAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGFwaUtleTtcbiAgfVxuXG4gIHByaXZhdGUgZ2V0RXhwZWN0ZWRGb3JtYXQocHJvcFR5cGU6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgLy8g67Cw7Je0IO2DgOyehSDsspjrpqxcbiAgICBpZiAocHJvcFR5cGUuZW5kc1dpdGgoXCJbXVwiKSkge1xuICAgICAgY29uc3QgYmFzZVR5cGUgPSBwcm9wVHlwZS5zbGljZSgwLCAtMik7XG4gICAgICBjb25zdCBiYXNlRm9ybWF0ID0gdGhpcy5nZXRTY2FsYXJGb3JtYXQoYmFzZVR5cGUpO1xuICAgICAgcmV0dXJuIGBKU09OIGFycmF5IG9mICR7YmFzZUZvcm1hdH0gKGUuZy4sIFske3RoaXMuZ2V0RXhhbXBsZUZvclR5cGUoYmFzZVR5cGUsIFwiZW5cIil9LCAuLi5dKWA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMuZ2V0U2NhbGFyRm9ybWF0KHByb3BUeXBlKTtcbiAgfVxuXG4gIHByaXZhdGUgZ2V0U2NhbGFyRm9ybWF0KHByb3BUeXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHN3aXRjaCAocHJvcFR5cGUpIHtcbiAgICAgIGNhc2UgXCJpbnRlZ2VyXCI6XG4gICAgICBjYXNlIFwiYmlnSW50ZWdlclwiOlxuICAgICAgICByZXR1cm4gXCJpbnRlZ2VyIG51bWJlcnNcIjtcbiAgICAgIGNhc2UgXCJmbG9hdFwiOlxuICAgICAgY2FzZSBcIm51bWJlclwiOlxuICAgICAgY2FzZSBcIm51bWVyaWNcIjpcbiAgICAgICAgcmV0dXJuIFwiZGVjaW1hbCBudW1iZXJzXCI7XG4gICAgICBjYXNlIFwiYm9vbGVhblwiOlxuICAgICAgICByZXR1cm4gXCJib29sZWFucyAodHJ1ZSBvciBmYWxzZSlcIjtcbiAgICAgIGNhc2UgXCJkYXRlXCI6XG4gICAgICAgIHJldHVybiBcIklTTyA4NjAxIGRhdGUgc3RyaW5nc1wiO1xuICAgICAgY2FzZSBcImpzb25cIjpcbiAgICAgICAgcmV0dXJuIFwidmFsaWQgSlNPTiBvYmplY3Qgb3IgYXJyYXlcIjtcbiAgICAgIGNhc2UgXCJ1dWlkXCI6XG4gICAgICAgIHJldHVybiBcIlVVSUQgc3RyaW5nc1wiO1xuICAgICAgY2FzZSBcImVudW1cIjpcbiAgICAgICAgcmV0dXJuIFwib25lIG9mIHRoZSBhbGxvd2VkIGVudW0gdmFsdWVzXCI7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gXCJwbGFpbiB0ZXh0IHN0cmluZ3NcIjtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGdldEV4YW1wbGVGb3JUeXBlKHByb3BUeXBlOiBzdHJpbmcsIGxvY2FsZTogTG9jYWxlKTogc3RyaW5nIHtcbiAgICAvLyDrsLDsl7Qg7YOA7J6FIOyymOumrFxuICAgIGlmIChwcm9wVHlwZS5lbmRzV2l0aChcIltdXCIpKSB7XG4gICAgICBjb25zdCBiYXNlVHlwZSA9IHByb3BUeXBlLnNsaWNlKDAsIC0yKTtcbiAgICAgIGNvbnN0IGJhc2VFeGFtcGxlID0gdGhpcy5nZXRTY2FsYXJFeGFtcGxlKGJhc2VUeXBlLCBsb2NhbGUpO1xuICAgICAgcmV0dXJuIGBbJHtiYXNlRXhhbXBsZX1dYDtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5nZXRTY2FsYXJFeGFtcGxlKHByb3BUeXBlLCBsb2NhbGUpO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRTY2FsYXJFeGFtcGxlKHByb3BUeXBlOiBzdHJpbmcsIGxvY2FsZTogTG9jYWxlKTogc3RyaW5nIHtcbiAgICBjb25zdCBpc0tvcmVhbiA9IGxvY2FsZSA9PT0gXCJrb1wiO1xuXG4gICAgc3dpdGNoIChwcm9wVHlwZSkge1xuICAgICAgY2FzZSBcImludGVnZXJcIjpcbiAgICAgIGNhc2UgXCJiaWdJbnRlZ2VyXCI6XG4gICAgICAgIHJldHVybiBcIjQyXCI7XG4gICAgICBjYXNlIFwiZmxvYXRcIjpcbiAgICAgIGNhc2UgXCJudW1iZXJcIjpcbiAgICAgIGNhc2UgXCJudW1lcmljXCI6XG4gICAgICAgIHJldHVybiBcIjMuMTRcIjtcbiAgICAgIGNhc2UgXCJib29sZWFuXCI6XG4gICAgICAgIHJldHVybiBcInRydWVcIjtcbiAgICAgIGNhc2UgXCJkYXRlXCI6XG4gICAgICAgIHJldHVybiBcIjIwMjQtMDEtMDFcIjtcbiAgICAgIGNhc2UgXCJqc29uXCI6XG4gICAgICAgIHJldHVybiAne1wia2V5XCI6IFwidmFsdWVcIn0nO1xuICAgICAgY2FzZSBcInV1aWRcIjpcbiAgICAgICAgcmV0dXJuIFwiNTUwZTg0MDAtZTI5Yi00MWQ0LWE3MTYtNDQ2NjU1NDQwMDAwXCI7XG4gICAgICBjYXNlIFwiZW51bVwiOlxuICAgICAgICByZXR1cm4gXCJFTlVNX1ZBTFVFXCI7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gaXNLb3JlYW4gPyBcIuyViOuFle2VmOyEuOyalFwiIDogXCJIZWxsb1wiO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBMTE0g7LqQ7IucIO2GteqzhOulvCDrsJjtmZjtlanri4jri6QuXG4gICAqL1xuICBnZXRMTE1DYWNoZVN0YXRzKCkge1xuICAgIHJldHVybiB7XG4gICAgICBzaXplOiB0aGlzLmxsbUNhY2hlLnNpemUsXG4gICAgICBlbmFibGVkOiB0aGlzLm9wdGlvbnMuZW5hYmxlTExNQ2FjaGUsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBMTE0g7LqQ7Iuc66W8IOy0iOq4sO2ZlO2VqeuLiOuLpC5cbiAgICovXG4gIGNsZWFyTExNQ2FjaGUoKSB7XG4gICAgdGhpcy5sbG1DYWNoZS5jbGVhcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIOy7qO2FjeyKpO2KuCDsg53shLFcbiAgICovXG4gIHByaXZhdGUgY3JlYXRlQ29udGV4dCgpOiBHZW5lcmF0b3JDb250ZXh0IHtcbiAgICByZXR1cm4ge1xuICAgICAgZml4dHVyZXM6IG5ldyBNYXAoKSxcbiAgICAgIHJlZmVyZW5jZUNhY2hlOiBuZXcgTWFwKCksXG4gICAgICBpbXBvcnRlZFJlY29yZHM6IG5ldyBTZXQoKSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIOuwsOy5mCDsg53shLEg67CPIOyekOuPmSDsoIDsnqVcbiAgICpcbiAgICogMS4g6rCBIHNwZWPrs4TroZwgZml4dHVyZSDsg53shLEgKOuplOuqqOumrClcbiAgICogMi4gRml4dHVyZVJlY29yZOuhnCDrs4DtmZhcbiAgICogMy4gRml4dHVyZU1hbmFnZXIuaW5zZXJ0Rml4dHVyZXMoKeuhnCB0YXJnZXREYuyXkCDsoIDsnqVcbiAgICpcbiAgICogQHJldHVybnMg7KCA7J6l65CcIGZpeHR1cmUg642w7J207YSwICjsi6TsoJwgREIgSUQg7Y+s7ZWoKVxuICAgKi9cbiAgYXN5bmMgZ2VuZXJhdGVCYXRjaChcbiAgICBzcGVjczogQXJyYXk8eyBlbnRpdHk6IHN0cmluZzsgY291bnQ6IG51bWJlcjsgb3ZlcnJpZGVzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfT4sXG4gICk6IFByb21pc2U8Rml4dHVyZUltcG9ydFJlc3VsdFtdPiB7XG4gICAgY29uc3QgY29udGV4dCA9IHRoaXMuY3JlYXRlQ29udGV4dCgpO1xuICAgIGNvbnN0IGdlbmVyYXRlZEZpeHR1cmVzOiBBcnJheTx7IGVudGl0eTogc3RyaW5nOyBkYXRhOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB9PiA9IFtdO1xuXG4gICAgLy8gMS4g6rCBIHNwZWPrs4TroZwgZml4dHVyZSDsg53shLFcbiAgICBmb3IgKGNvbnN0IHNwZWMgb2Ygc3BlY3MpIHtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc3BlYy5jb3VudDsgaSsrKSB7XG4gICAgICAgIGNvbnN0IGZpeHR1cmUgPSBhd2FpdCB0aGlzLmdlbmVyYXRlKHNwZWMuZW50aXR5LCBzcGVjLm92ZXJyaWRlcyB8fCB7fSwgY29udGV4dCk7XG4gICAgICAgIGdlbmVyYXRlZEZpeHR1cmVzLnB1c2goe1xuICAgICAgICAgIGVudGl0eTogc3BlYy5lbnRpdHksXG4gICAgICAgICAgZGF0YTogZml4dHVyZSxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gMi4gRml4dHVyZVJlY29yZOuhnCDrs4DtmZhcbiAgICBjb25zdCBmaXh0dXJlUmVjb3JkczogRml4dHVyZVJlY29yZFtdID0gW107XG4gICAgZm9yIChjb25zdCB7IGVudGl0eTogZW50aXR5TmFtZSwgZGF0YSB9IG9mIGdlbmVyYXRlZEZpeHR1cmVzKSB7XG4gICAgICBjb25zdCBlbnRpdHkgPSB0aGlzLmVudGl0eU1hbmFnZXIuZ2V0KGVudGl0eU5hbWUpO1xuXG4gICAgICAvLyDsnoTsi5wgSUQg7IOd7ISxICh0YXJnZXREYuyXkCBJTlNFUlQg7ZuEIOyLpOygnCBJROulvCDrsJvsnYwpXG4gICAgICBjb25zdCB0ZW1wSWQgPSBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAxMDAwMDAwKTtcbiAgICAgIGNvbnN0IHJlY29yZHMgPSBhd2FpdCBGaXh0dXJlTWFuYWdlci5jcmVhdGVGaXh0dXJlUmVjb3JkKFxuICAgICAgICBlbnRpdHksXG4gICAgICAgIHsgLi4uZGF0YSwgaWQ6IHRlbXBJZCB9IGFzIHsgaWQ6IG51bWJlcjsgW2tleTogc3RyaW5nXTogc3RyaW5nIHwgbnVtYmVyIHwgYm9vbGVhbiB8IG51bGwgfSxcbiAgICAgICAgeyBzaW5nbGVSZWNvcmQ6IHRydWUgfSxcbiAgICAgICk7XG4gICAgICBmaXh0dXJlUmVjb3Jkcy5wdXNoKC4uLnJlY29yZHMpO1xuICAgIH1cblxuICAgIC8vIDMuIHRhcmdldERi7JeQIOyCveyehSAoRml4dHVyZU1hbmFnZXLqsIAg7J2Y7KG07ISxIOygleugrCDsspjrpqwpXG4gICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IEZpeHR1cmVNYW5hZ2VyLmluc2VydEZpeHR1cmVzKHRoaXMudGFyZ2V0RGJOYW1lLCBmaXh0dXJlUmVjb3Jkcyk7XG5cbiAgICBjb25zb2xlLmxvZyhcbiAgICAgIGNoYWxrLmdyZWVuKGBHZW5lcmF0ZWQgYW5kIHNhdmVkICR7cmVzdWx0cy5sZW5ndGh9IGZpeHR1cmVzIHRvICR7dGhpcy50YXJnZXREYk5hbWV9YCksXG4gICAgKTtcbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfVxuXG4gIC8qKlxuICAgKiDsi6TsoJwgREIoc291cmNlRGIp7JeQ7IScIOuNsOydtO2EsOulvCDsobDtmoztlZjsl6wgZml4dHVyZSBEQih0YXJnZXREYinsl5AgaW1wb3J07ZWp64uI64ukLlxuICAgKlxuICAgKiAxLiBEYXRhRXhwbG9yZXLroZwgc291cmNlRGLsl5DshJwg642w7J207YSwIOyhsO2ajCAo6rSA66CoIOuNsOydtO2EsCDtj6ztlagpXG4gICAqIDIuIEZpeHR1cmVSZWNvcmTroZwg67OA7ZmYXG4gICAqIDMuIHRhcmdldERi7JeQIOyCveyehVxuICAgKlxuICAgKiBAcGFyYW0gZW50aXR5TmFtZSAtIOyhsO2ajO2VoCBlbnRpdHkg7J2066aEXG4gICAqIEBwYXJhbSBvcHRpb25zIC0g7KGw7ZqMIOyYteyFmCAoc3RyYXRlZ3ksIGxpbWl0LCBpbmNsdWRlUmVsYXRpb25zIOuTsSlcbiAgICogQHJldHVybnMg7KCA7J6l65CcIGZpeHR1cmUg642w7J207YSwICjsi6TsoJwgREIgSUQg7Y+s7ZWoKVxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiAvLyDtlITroZzrjZXshZggRELsl5DshJwgVXNlciAxMOuqhSArIOq0gOugqCBFbXBsb3llZSwgRGVwYXJ0bWVudCDqsIDsoLjsmKTquLBcbiAgICogYXdhaXQgZ2VuZXJhdG9yLmltcG9ydEZyb21Tb3VyY2UoXCJVc2VyXCIsIHtcbiAgICogICBzdHJhdGVneTogXCJzYW1wbGVcIixcbiAgICogICBsaW1pdDogMTAsXG4gICAqICAgaW5jbHVkZVJlbGF0aW9uczogdHJ1ZSxcbiAgICogICBtYXhEZXB0aDogMlxuICAgKiB9KTtcbiAgICovXG4gIGFzeW5jIGltcG9ydEZyb21Tb3VyY2UoXG4gICAgZW50aXR5TmFtZTogc3RyaW5nLFxuICAgIG9wdGlvbnM6IEV4cGxvcmVXaXRoUmVsYXRpb25zT3B0aW9ucyxcbiAgKTogUHJvbWlzZTxGaXh0dXJlSW1wb3J0UmVzdWx0W10+IHtcbiAgICBjb25zb2xlLmxvZyhcbiAgICAgIGNoYWxrLmJsdWUoXG4gICAgICAgIGBJbXBvcnRpbmcgJHtlbnRpdHlOYW1lfSBmcm9tIHNvdXJjZSBEQiB3aXRoIG9wdGlvbnM6ICR7SlNPTi5zdHJpbmdpZnkoeyBzdHJhdGVneTogb3B0aW9ucy5zdHJhdGVneSwgbGltaXQ6IG9wdGlvbnMubGltaXQsIGluY2x1ZGVSZWxhdGlvbnM6IG9wdGlvbnMuaW5jbHVkZVJlbGF0aW9ucywgbWF4RGVwdGg6IG9wdGlvbnMubWF4RGVwdGggfSl9YCxcbiAgICAgICksXG4gICAgKTtcblxuICAgIC8vIDEuIERhdGFFeHBsb3JlcuuhnCBzb3VyY2VEYuyXkOyEnCDrjbDsnbTthLAg7KGw7ZqMICjqtIDroKgg642w7J207YSwIO2PrO2VqClcbiAgICBjb25zdCBleHBsb3JlUmVzdWx0ID0gYXdhaXQgdGhpcy5kYXRhRXhwbG9yZXIuZXhwbG9yZVdpdGhSZWxhdGlvbnMoZW50aXR5TmFtZSwgb3B0aW9ucyk7XG5cbiAgICBjb25zb2xlLmxvZyhcbiAgICAgIGNoYWxrLmN5YW4oXG4gICAgICAgIGBGb3VuZCAke2V4cGxvcmVSZXN1bHQubWFpbi5yZWNvcmRzLmxlbmd0aH0gJHtlbnRpdHlOYW1lfSByZWNvcmRzIGFuZCAke2V4cGxvcmVSZXN1bHQucmVsYXRlZC5zaXplfSByZWxhdGVkIGVudGl0aWVzYCxcbiAgICAgICksXG4gICAgKTtcblxuICAgIC8vIDIuIEZpeHR1cmVSZWNvcmTroZwg67OA7ZmYXG4gICAgY29uc3QgZml4dHVyZVJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSA9IFtdO1xuXG4gICAgLy8g66mU7J24IGVudGl0eeydmCByZWNvcmRz66W8IEZpeHR1cmVSZWNvcmTroZwg67OA7ZmYXG4gICAgY29uc3QgbWFpbkVudGl0eSA9IHRoaXMuZW50aXR5TWFuYWdlci5nZXQoZW50aXR5TmFtZSk7XG4gICAgZm9yIChjb25zdCByZWNvcmQgb2YgZXhwbG9yZVJlc3VsdC5tYWluLnJlY29yZHMpIHtcbiAgICAgIGNvbnN0IHJlY29yZHMgPSBhd2FpdCBGaXh0dXJlTWFuYWdlci5jcmVhdGVGaXh0dXJlUmVjb3JkKFxuICAgICAgICBtYWluRW50aXR5LFxuICAgICAgICByZWNvcmQgYXMgeyBpZDogbnVtYmVyIHwgc3RyaW5nOyBba2V5OiBzdHJpbmddOiBzdHJpbmcgfCBudW1iZXIgfCBib29sZWFuIHwgbnVsbCB9LFxuICAgICAgICB7IF9kYjogdGhpcy5zb3VyY2VEYiwgc2luZ2xlUmVjb3JkOiB0cnVlIH0sXG4gICAgICApO1xuICAgICAgZml4dHVyZVJlY29yZHMucHVzaCguLi5yZWNvcmRzKTtcbiAgICB9XG5cbiAgICAvLyDqtIDroKggZW50aXR57J2YIHJlY29yZHPrpbwgRml4dHVyZVJlY29yZOuhnCDrs4DtmZhcbiAgICBmb3IgKGNvbnN0IFtyZWxhdGVkRW50aXR5TmFtZSwgcmVsYXRlZFJlY29yZHNdIG9mIGV4cGxvcmVSZXN1bHQucmVsYXRlZC5lbnRyaWVzKCkpIHtcbiAgICAgIGNvbnN0IHJlbGF0ZWRFbnRpdHkgPSB0aGlzLmVudGl0eU1hbmFnZXIuZ2V0KHJlbGF0ZWRFbnRpdHlOYW1lKTtcbiAgICAgIGZvciAoY29uc3QgcmVjb3JkIG9mIHJlbGF0ZWRSZWNvcmRzKSB7XG4gICAgICAgIGNvbnN0IHJlY29yZHMgPSBhd2FpdCBGaXh0dXJlTWFuYWdlci5jcmVhdGVGaXh0dXJlUmVjb3JkKFxuICAgICAgICAgIHJlbGF0ZWRFbnRpdHksXG4gICAgICAgICAgcmVjb3JkIGFzIHsgaWQ6IG51bWJlciB8IHN0cmluZzsgW2tleTogc3RyaW5nXTogc3RyaW5nIHwgbnVtYmVyIHwgYm9vbGVhbiB8IG51bGwgfSxcbiAgICAgICAgICB7IF9kYjogdGhpcy5zb3VyY2VEYiwgc2luZ2xlUmVjb3JkOiB0cnVlIH0sXG4gICAgICAgICk7XG4gICAgICAgIGZpeHR1cmVSZWNvcmRzLnB1c2goLi4ucmVjb3Jkcyk7XG4gICAgICB9XG5cbiAgICAgIGNvbnNvbGUubG9nKGNoYWxrLmdyYXkoYCAgLSAke3JlbGF0ZWRFbnRpdHlOYW1lfTogJHtyZWxhdGVkUmVjb3Jkcy5sZW5ndGh9IHJlY29yZHNgKSk7XG4gICAgfVxuXG4gICAgLy8gMy4gdGFyZ2V0RGLsl5Ag7IK97J6FIChGaXh0dXJlTWFuYWdlcuqwgCDsnZjsobTshLEg7KCV66CsIOyymOumrClcbiAgICBjb25zdCByZXN1bHRzID0gYXdhaXQgRml4dHVyZU1hbmFnZXIuaW5zZXJ0Rml4dHVyZXModGhpcy50YXJnZXREYk5hbWUsIGZpeHR1cmVSZWNvcmRzKTtcblxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgY2hhbGsuZ3JlZW4oXG4gICAgICAgIGBTdWNjZXNzZnVsbHkgaW1wb3J0ZWQgJHtyZXN1bHRzLmxlbmd0aH0gcmVjb3JkcyB0byAke3RoaXMudGFyZ2V0RGJOYW1lfSAoJHtleHBsb3JlUmVzdWx0Lm1haW4ucmVjb3Jkcy5sZW5ndGh9ICR7ZW50aXR5TmFtZX0gKyAke3Jlc3VsdHMubGVuZ3RoIC0gZXhwbG9yZVJlc3VsdC5tYWluLnJlY29yZHMubGVuZ3RofSByZWxhdGVkKWAsXG4gICAgICApLFxuICAgICk7XG5cbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfVxufVxuIl0sIm5hbWVzIjpbImNoYWxrIiwiaXNCZWxvbmdzVG9PbmVSZWxhdGlvblByb3AiLCJpc09uZVRvT25lUmVsYXRpb25Qcm9wIiwiaXNSZWxhdGlvblByb3AiLCJEYXRhRXhwbG9yZXIiLCJmYWtlck1hcHBpbmdzIiwiRml4dHVyZU1hbmFnZXIiLCJGaXh0dXJlR2VuZXJhdG9yIiwiZGF0YUV4cGxvcmVyIiwibG9jYWxlIiwibWFwcGluZ3MiLCJsbG1DYWNoZSIsIk1hcCIsIm9wdGlvbnMiLCJzb3VyY2VEYiIsIl90YXJnZXREYiIsInRhcmdldERiTmFtZSIsImVudGl0eU1hbmFnZXIiLCJ1c2VMTE0iLCJlbmFibGVMTE1DYWNoZSIsImxsbU1vZGVsIiwiZ2VuZXJhdGUiLCJlbnRpdHlOYW1lIiwib3ZlcnJpZGVzIiwiY29udGV4dCIsImNyZWF0ZUNvbnRleHQiLCJlbnRpdHkiLCJnZXQiLCJ0ZW1wSWQiLCJEYXRlIiwibm93IiwiZml4dHVyZSIsInByb3AiLCJwcm9wcyIsInZpcnR1YWwiLCJuYW1lIiwiY29uZSIsInJlbGF0aW9uVmFsdWUiLCJnZW5lcmF0ZVJlbGF0aW9uVmFsdWUiLCJoYXNKb2luQ29sdW1uIiwiZml4dHVyZUdlbmVyYXRvciIsImV4ZWN1dGVHZW5lcmF0b3IiLCJmaXh0dXJlSGludCIsImdlbmVyYXRlV2l0aExMTSIsImVycm9yIiwiY29uc29sZSIsIndhcm4iLCJpZCIsIkVycm9yIiwibWVzc2FnZSIsImZpeHR1cmVEZWZhdWx0IiwidW5kZWZpbmVkIiwiZ2VuZXJhdGVEZWZhdWx0VmFsdWUiLCJwYXNzd29yZCIsImJjcnlwdCIsImhhc2giLCJmaXh0dXJlcyIsInNldCIsImRhdGFTb3VyY2UiLCJjYWNoZUtleSIsIndpdGgiLCJKU09OIiwic3RyaW5naWZ5IiwicmVmZXJlbmNlQ2FjaGUiLCJoYXMiLCJleHBsb3JlUmVzdWx0IiwiZXhwbG9yZVdpdGhSZWxhdGlvbnMiLCJzdHJhdGVneSIsImxpbWl0IiwiY29uZmlnIiwiaW5jbHVkZVJlbGF0aW9ucyIsIm1heERlcHRoIiwibWFpbiIsInJlY29yZHMiLCJpbXBvcnRFeHBsb3JlUmVzdWx0IiwiY2FuZGlkYXRlcyIsImxlbmd0aCIsInNlbGVjdGVkIiwiTWF0aCIsImZsb29yIiwicmFuZG9tIiwiYXV0b0tleSIsImF1dG9FeHBsb3JlUmVzdWx0IiwiYXV0b0NhbmRpZGF0ZXMiLCJudWxsYWJsZSIsImFsbEZpeHR1cmVSZWNvcmRzIiwiZW50aXR5SWQiLCJyZWxhdGVkIiwiZW50cmllcyIsInJlY29yZHNUb0ltcG9ydCIsImxvZyIsImN5YW4iLCJyZWNvcmQiLCJyZWNvcmRLZXkiLCJpbXBvcnRlZFJlY29yZHMiLCJwdXNoIiwiYWRkIiwiZ3JheSIsInNsaWNlIiwiZml4dHVyZVJlY29yZHMiLCJjcmVhdGVGaXh0dXJlUmVjb3JkIiwiX2RiIiwic2luZ2xlUmVjb3JkIiwibWFpbkVudGl0eSIsIm1haW5SZWNvcmRzVG9JbXBvcnQiLCJpbnNlcnRGaXh0dXJlcyIsImdyZWVuIiwic2l6ZSIsImdlbmVyYXRvciIsInN0YXJ0c1dpdGgiLCJpc05hbWVGaWVsZCIsImZha2VyTW9kdWxlIiwiZmFrZXIiLCJmYWtlcktPIiwiZXhwciIsIm1hdGNoIiwicGF0aCIsImFyZ3NTdHIiLCJwYXJ0cyIsInNwbGl0IiwiZm4iLCJwYXJ0IiwiYXJncyIsInRyaW0iLCJwYXJzZWQiLCJwYXJzZSIsIkFycmF5IiwiaXNBcnJheSIsInRyaW1tZWQiLCJOdW1iZXIiLCJpc05hTiIsImVuZHNXaXRoIiwieWVsbG93IiwiZmFrZXJKQSIsImxvY2FsZUZha2VyIiwibG9jYWxlTWFwcGluZ3MiLCJlbiIsIm5vcm1hbGl6ZWROYW1lIiwidG9Mb3dlckNhc2UiLCJyZXBsYWNlIiwicGF0dGVybiIsIk9iamVjdCIsImZpZWxkX3BhdHRlcm5zIiwiaW5jbHVkZXMiLCJleGVjdXRlRmFrZXJFeHByZXNzaW9uIiwiZGVwYXJ0bWVudHMiLCJwcmVmaXhlcyIsInN1ZmZpeGVzIiwiZGVwdCIsImhlbHBlcnMiLCJhcnJheUVsZW1lbnQiLCJwcmVmaXgiLCJzdWZmaXgiLCJ0eXBlIiwiZ2VuZXJhdGVBcnJheVZhbHVlIiwiZW51bVZhbHVlcyIsImVudW0iLCJlbnVtTGFiZWxzIiwia2V5cyIsInR5cGVEZWZhdWx0IiwidHlwZV9kZWZhdWx0cyIsImxvcmVtIiwid29yZHMiLCJudW1iZXIiLCJpbnQiLCJtaW4iLCJtYXgiLCJiaWdJbnQiLCJmbG9hdCIsImRhdGF0eXBlIiwiYm9vbGVhbiIsImRhdGUiLCJwYXN0Iiwic3RyaW5nIiwidXVpZCIsIl9lbnRpdHkiLCJfbG9jYWxlRmFrZXIiLCJjb3VudCIsImZyb20iLCJ1cmwiLCJpbWFnZSIsInN5c3RlbSIsImZpbGVOYW1lIiwibWltZV90eXBlIiwiaW50ZXJuZXQiLCJ3b3JkIiwiZXhwcmVzc2lvbiIsImZha2VyTmFtZSIsInNlbGVjdGVkRmFrZXIiLCJmdW5jTWF0Y2giLCJhcGlLZXkiLCJnZXRBcGlLZXkiLCJjcmVhdGVBbnRocm9waWMiLCJnZW5lcmF0ZVRleHQiLCJ0ZXh0IiwibW9kZWwiLCJwcm9tcHQiLCJidWlsZExMTVByb21wdCIsInZhbHVlIiwicGFyc2VMTE1SZXNwb25zZSIsImhpbnQiLCJsYW5ndWFnZSIsImdldEV4cGVjdGVkRm9ybWF0Iiwiam9pbiIsImdldEV4YW1wbGVGb3JUeXBlIiwicHJvcFR5cGUiLCJjbGVhbmVkIiwiYmFzZVR5cGUiLCJtYXAiLCJpdGVtIiwiZ2V0RGVmYXVsdFZhbHVlRm9yVHlwZSIsInBhcnNlU2NhbGFyVmFsdWUiLCJTdHJpbmciLCJudW0iLCJwYXJzZUludCIsIkJpZ0ludCIsInBhcnNlRmxvYXQiLCJnZXRUaW1lIiwiU29uYW11IiwicmVxdWlyZSIsInNlY3JldCIsImFudGhyb3BpY19hcGlfa2V5IiwicHJvY2VzcyIsImVudiIsIkFOVEhST1BJQ19BUElfS0VZIiwiYmFzZUZvcm1hdCIsImdldFNjYWxhckZvcm1hdCIsImJhc2VFeGFtcGxlIiwiZ2V0U2NhbGFyRXhhbXBsZSIsImlzS29yZWFuIiwiZ2V0TExNQ2FjaGVTdGF0cyIsImVuYWJsZWQiLCJjbGVhckxMTUNhY2hlIiwiY2xlYXIiLCJTZXQiLCJnZW5lcmF0ZUJhdGNoIiwic3BlY3MiLCJnZW5lcmF0ZWRGaXh0dXJlcyIsInNwZWMiLCJpIiwiZGF0YSIsInJlc3VsdHMiLCJpbXBvcnRGcm9tU291cmNlIiwiYmx1ZSIsInJlbGF0ZWRFbnRpdHlOYW1lIiwicmVsYXRlZFJlY29yZHMiLCJyZWxhdGVkRW50aXR5Il0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPQSxXQUFXLFFBQVE7QUFLMUIsU0FBU0MsMEJBQTBCLEVBQUVDLHNCQUFzQixFQUFFQyxjQUFjLFFBQVEsb0JBQWlCO0FBQ3BHLFNBQ0VDLFlBQVksUUFHUCxxQkFBa0I7QUFDekIsU0FBNkJDLGFBQWEsUUFBUSxzQkFBbUI7QUFDckUsU0FBU0MsY0FBYyxRQUFRLHVCQUFvQjtBQXNCbkQsT0FBTyxNQUFNQzs7OztJQUNIQyxhQUEyQjtJQUMzQkMsT0FBZTtJQUNmQyxTQUF3QjtJQUN4QkMsV0FBaUMsSUFBSUMsTUFBTTtJQUMzQ0MsUUFBaUM7SUFFekMsWUFDRSxBQUFRQyxRQUFjLEVBQ3RCLGlFQUFpRTtJQUNqRSxnQ0FBZ0M7SUFDaENDLFNBQWUsRUFDZixBQUFRQyxZQUFzRCxFQUM5RCxBQUFRQyxhQUFtQyxFQUMzQ0osT0FBaUMsQ0FDakM7YUFQUUMsV0FBQUE7YUFJQUUsZUFBQUE7YUFDQUMsZ0JBQUFBO1FBR1IsSUFBSSxDQUFDVCxZQUFZLEdBQUcsSUFBSUosYUFBYVUsVUFBVUc7UUFDL0MsSUFBSSxDQUFDUixNQUFNLEdBQUdJLFNBQVNKLFVBQVU7UUFDakMsSUFBSSxDQUFDQyxRQUFRLEdBQUdMO1FBQ2hCLElBQUksQ0FBQ1EsT0FBTyxHQUFHO1lBQ2JKLFFBQVFJLFNBQVNKLFVBQVU7WUFDM0JTLFFBQVFMLFNBQVNLLFVBQVU7WUFDM0JDLGdCQUFnQk4sU0FBU00sbUJBQW1CO1lBQzVDQyxVQUFVUCxTQUFTTyxZQUFZO1FBQ2pDO0lBQ0Y7SUFFQTs7O0dBR0MsR0FDRCxNQUFNQyxTQUNKQyxVQUFrQixFQUNsQkMsWUFBcUMsQ0FBQyxDQUFDLEVBQ3ZDQyxVQUE0QixJ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
|