sonamu 0.7.16 → 0.7.18
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/ai/providers/rtzr/error.d.ts +1 -1
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/api/config.d.ts +1 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +1 -1
- package/dist/api/sonamu.d.ts +3 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +48 -38
- package/dist/syncer/checksum.d.ts +8 -3
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +17 -9
- package/dist/syncer/code-generator.js +7 -2
- package/dist/syncer/syncer.d.ts +6 -6
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +27 -13
- package/dist/template/implementations/model.template.js +5 -5
- package/dist/template/implementations/services.template.d.ts +17 -0
- package/dist/template/implementations/services.template.d.ts.map +1 -0
- package/dist/template/implementations/services.template.js +180 -0
- package/dist/template/implementations/view_form.template.js +2 -2
- package/dist/template/implementations/view_id_async_select.template.js +2 -2
- package/dist/template/implementations/view_list.template.js +5 -5
- package/dist/types/types.d.ts +2 -14
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +3 -15
- package/dist/ui/ai-api.d.ts +2 -0
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +43 -49
- package/dist/ui/ai-client.d.ts +10 -0
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +457 -437
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +3 -1
- package/dist/ui-web/assets/index-DFqVuxOB.js +92 -0
- package/dist/ui-web/index.html +1 -1
- package/package.json +9 -5
- package/src/api/config.ts +3 -0
- package/src/api/decorators.ts +6 -1
- package/src/api/sonamu.ts +68 -50
- package/src/shared/app.shared.ts.txt +1 -1
- package/src/shared/web.shared.ts.txt +0 -43
- package/src/syncer/checksum.ts +31 -9
- package/src/syncer/code-generator.ts +8 -1
- package/src/syncer/syncer.ts +38 -26
- package/src/template/implementations/model.template.ts +4 -4
- package/src/template/implementations/services.template.ts +265 -0
- package/src/template/implementations/view_form.template.ts +1 -1
- package/src/template/implementations/view_id_async_select.template.ts +1 -1
- package/src/template/implementations/view_list.template.ts +4 -4
- package/src/types/types.ts +2 -14
- package/src/ui/ai-api.ts +61 -60
- package/src/ui/ai-client.ts +535 -499
- package/src/ui/api.ts +3 -0
- package/src/ui/entity.instructions.md +536 -0
- package/dist/template/implementations/service.template.d.ts +0 -29
- package/dist/template/implementations/service.template.d.ts.map +0 -1
- package/dist/template/implementations/service.template.js +0 -202
- package/dist/ui-web/assets/index-BcbbB-BB.js +0 -95
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +0 -1
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +0 -1
- package/src/template/implementations/service.template.ts +0 -328
package/dist/ui/ai-client.js
CHANGED
|
@@ -1,438 +1,458 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// relations: entity.relations,
|
|
34
|
-
// enumLabels: entity.enumLabels,
|
|
35
|
-
// };
|
|
36
|
-
// });
|
|
37
|
-
// const systemMessage = `
|
|
38
|
-
// 당신은 픽스쳐 레코드를 수정하고 생성할 수 있는 도우미입니다.
|
|
39
|
-
// 현재 픽스쳐 레코드:
|
|
40
|
-
// ${JSON.stringify(fixtureRecords, null, 2)}
|
|
41
|
-
// 엔티티 구조 정보:
|
|
42
|
-
// ${JSON.stringify(entityStructures, null, 2)}
|
|
43
|
-
// ## 픽스쳐 수정
|
|
44
|
-
// 사용자가 픽스쳐 값 수정을 요청하면 updateFixtures 도구를 사용하여 변경사항을 적용하세요.
|
|
45
|
-
// - fixtureId: 수정할 픽스쳐 ID (형식: "EntityId#id")
|
|
46
|
-
// - updates: 컬럼명을 키로, 새 값을 값으로 하는 객체
|
|
47
|
-
// 예시: "User#1" 픽스쳐의 "name" 컬럼을 "홍길동"으로 변경하려면:
|
|
48
|
-
// updateFixtures({ updates: [{ fixtureId: "User#1", updates: { name: "홍길동" } }] })
|
|
49
|
-
// 변경될 컬럼의 type이 relation인 경우, 관련 엔티티에도 반영되어야 할 컬럼이 있는지 확인하세요.
|
|
50
|
-
// ## 픽스쳐 생성
|
|
51
|
-
// 사용자가 새로운 픽스쳐 생성을 요청하면 createFixtures 도구를 사용하세요.
|
|
52
|
-
// - entityId: 생성할 엔티티 ID
|
|
53
|
-
// - id: 새 레코드의 ID (기존 픽스쳐와 중복되지 않는 음수 사용 권장, 예: -1, -2)
|
|
54
|
-
// - columns: 컬럼명을 키로, 값을 값으로 하는 객체 (엔티티 구조 참고)
|
|
55
|
-
// 예시: 새로운 User 픽스쳐를 생성하려면:
|
|
56
|
-
// createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
|
|
57
|
-
// `;
|
|
58
|
-
// return streamText({
|
|
59
|
-
// model: this.model,
|
|
60
|
-
// system: systemMessage,
|
|
61
|
-
// messages,
|
|
62
|
-
// tools: {
|
|
63
|
-
// updateFixtures: tool({
|
|
64
|
-
// description:
|
|
65
|
-
// "픽스쳐 레코드의 값을 수정합니다. 사용자가 특정 컬럼이나 값을 변경해달라고 요청할 때 사용하세요.",
|
|
66
|
-
// inputSchema: z.object({
|
|
67
|
-
// updates: z.array(
|
|
68
|
-
// z.object({
|
|
69
|
-
// fixtureId: z.string().describe("수정할 픽스쳐 ID (형식: EntityId#id)"),
|
|
70
|
-
// updates: z
|
|
71
|
-
// .record(z.string(), z.unknown())
|
|
72
|
-
// .describe("컬럼명을 키로, 새 값을 값으로 하는 객체"),
|
|
73
|
-
// }),
|
|
74
|
-
// ),
|
|
75
|
-
// }),
|
|
76
|
-
// execute: async ({
|
|
77
|
-
// updates,
|
|
78
|
-
// }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
|
|
79
|
-
// // fixtureRecords를 복사하고 업데이트 적용
|
|
80
|
-
// const updatedRecords: FixtureRecord[] = fixtureRecords.map((record) => {
|
|
81
|
-
// const update = updates.find((u) => u.fixtureId === record.fixtureId);
|
|
82
|
-
// if (update) {
|
|
83
|
-
// // columns의 value를 업데이트
|
|
84
|
-
// for (const [columnName, newValue] of Object.entries(update.updates)) {
|
|
85
|
-
// record.columns[columnName].value =
|
|
86
|
-
// newValue as FixtureRecord["columns"][string]["value"];
|
|
87
|
-
// }
|
|
88
|
-
// return record;
|
|
89
|
-
// }
|
|
90
|
-
// return record;
|
|
91
|
-
// });
|
|
92
|
-
// return { success: true, updatedRecords };
|
|
93
|
-
// },
|
|
94
|
-
// }),
|
|
95
|
-
// createFixtures: tool({
|
|
96
|
-
// description:
|
|
97
|
-
// "새로운 픽스쳐 레코드를 생성합니다. 사용자가 새로운 데이터를 추가해달라고 요청할 때 사용하세요.",
|
|
98
|
-
// inputSchema: z.object({
|
|
99
|
-
// fixtures: z.array(
|
|
100
|
-
// z.object({
|
|
101
|
-
// entityId: z.string().describe("생성할 엔티티 ID"),
|
|
102
|
-
// id: z.number().describe("새 레코드의 ID (음수 권장, 예: -1, -2)"),
|
|
103
|
-
// columns: z
|
|
104
|
-
// .record(z.string(), z.unknown())
|
|
105
|
-
// .describe("컬럼명을 키로, 값을 값으로 하는 객체"),
|
|
106
|
-
// }),
|
|
107
|
-
// ),
|
|
108
|
-
// }),
|
|
109
|
-
// execute: async ({
|
|
110
|
-
// fixtures,
|
|
111
|
-
// }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
|
|
112
|
-
// const newRecords: FixtureRecord[] = fixtures.map((fixture) => {
|
|
113
|
-
// const entity = EntityManager.get(fixture.entityId);
|
|
114
|
-
// // 엔티티 props를 기반으로 columns 구성
|
|
115
|
-
// const columns: FixtureRecord["columns"] = {};
|
|
116
|
-
// for (const prop of entity.props) {
|
|
117
|
-
// if (prop.type === "virtual") continue;
|
|
118
|
-
// let value = fixture.columns[prop.name] ?? null;
|
|
119
|
-
// if (prop.name === "created_at") {
|
|
120
|
-
// // 현재 시간으로 설정
|
|
121
|
-
// value = new Date().toISOString();
|
|
122
|
-
// } else if (
|
|
123
|
-
// prop.type === "relation" &&
|
|
124
|
-
// (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")
|
|
125
|
-
// ) {
|
|
126
|
-
// // 배열로 변환
|
|
127
|
-
// value = Array.isArray(value) ? value : [value].filter(nonNullable);
|
|
128
|
-
// }
|
|
129
|
-
// columns[prop.name] = {
|
|
130
|
-
// prop,
|
|
131
|
-
// value: value as FixtureRecord["columns"][string]["value"],
|
|
132
|
-
// };
|
|
133
|
-
// }
|
|
134
|
-
// return {
|
|
135
|
-
// fixtureId: `${fixture.entityId}#${fixture.id}`,
|
|
136
|
-
// entityId: fixture.entityId,
|
|
137
|
-
// id: fixture.id,
|
|
138
|
-
// columns,
|
|
139
|
-
// fetchedRecords: [],
|
|
140
|
-
// belongsRecords: [],
|
|
141
|
-
// override: false,
|
|
142
|
-
// };
|
|
143
|
-
// });
|
|
144
|
-
// // 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
|
|
145
|
-
// for (const newRecord of newRecords) {
|
|
146
|
-
// for (const [_colName, col] of Object.entries(newRecord.columns)) {
|
|
147
|
-
// if (col.prop.type !== "relation" || col.value === null) continue;
|
|
148
|
-
// const relatedEntityId = col.prop.with;
|
|
149
|
-
// const relatedIds = Array.isArray(col.value) ? col.value : [col.value];
|
|
150
|
-
// for (const relatedId of relatedIds) {
|
|
151
|
-
// const relatedFixtureId = `${relatedEntityId}#${relatedId}`;
|
|
152
|
-
// const relatedRecord = newRecords.find((r) => r.fixtureId === relatedFixtureId);
|
|
153
|
-
// if (relatedRecord) {
|
|
154
|
-
// // 역방향 relation 찾기
|
|
155
|
-
// const reverseCol = Object.entries(relatedRecord.columns).find(
|
|
156
|
-
// ([, c]) => c.prop.type === "relation" && c.prop.with === newRecord.entityId,
|
|
157
|
-
// );
|
|
158
|
-
// if (reverseCol) {
|
|
159
|
-
// const [reverseColName, reverseColValue] = reverseCol;
|
|
160
|
-
// const currentValue = reverseColValue.value;
|
|
161
|
-
// // 역방향이 배열인 경우 (HasMany, ManyToMany)
|
|
162
|
-
// if (
|
|
163
|
-
// reverseColValue.prop.type === "relation" &&
|
|
164
|
-
// (reverseColValue.prop.relationType === "HasMany" ||
|
|
165
|
-
// reverseColValue.prop.relationType === "ManyToMany")
|
|
166
|
-
// ) {
|
|
167
|
-
// assert(Array.isArray(currentValue), "currentValue must be an array");
|
|
168
|
-
// if (!currentValue.includes(newRecord.id)) {
|
|
169
|
-
// relatedRecord.columns[reverseColName] = {
|
|
170
|
-
// ...reverseColValue,
|
|
171
|
-
// value: [...currentValue, newRecord.id],
|
|
172
|
-
// };
|
|
173
|
-
// }
|
|
174
|
-
// } else {
|
|
175
|
-
// // 역방향이 단일 값인 경우 (BelongsToOne, OneToOne)
|
|
176
|
-
// relatedRecord.columns[reverseColName] = {
|
|
177
|
-
// ...reverseColValue,
|
|
178
|
-
// value: newRecord.id,
|
|
179
|
-
// };
|
|
180
|
-
// }
|
|
181
|
-
// }
|
|
182
|
-
// }
|
|
183
|
-
// }
|
|
184
|
-
// }
|
|
185
|
-
// }
|
|
186
|
-
// return { success: true, updatedRecords: newRecords };
|
|
187
|
-
// },
|
|
188
|
-
// }),
|
|
189
|
-
// },
|
|
190
|
-
// });
|
|
191
|
-
// }
|
|
192
|
-
// handleEntity(messages: ModelMessage[]) {
|
|
193
|
-
// // entity.instructions.md 파일 읽기
|
|
194
|
-
// const instructionsPath = path.join(import.meta.dirname, "..", "entity.instructions.md");
|
|
195
|
-
// const instructions = fs.readFileSync(instructionsPath, "utf-8");
|
|
196
|
-
// // 현재 등록된 엔티티 정보 수집
|
|
197
|
-
// const entityIds = EntityManager.getAllIds();
|
|
198
|
-
// const existingEntities = entityIds.map((entityId) => {
|
|
199
|
-
// const entity = EntityManager.get(entityId);
|
|
200
|
-
// return {
|
|
201
|
-
// id: entity.id,
|
|
202
|
-
// title: entity.title,
|
|
203
|
-
// table: entity.table,
|
|
204
|
-
// props: entity.props.map((p) => ({
|
|
205
|
-
// name: p.name,
|
|
206
|
-
// type: p.type,
|
|
207
|
-
// desc: p.desc,
|
|
208
|
-
// })),
|
|
209
|
-
// };
|
|
210
|
-
// });
|
|
211
|
-
// const systemMessage = `
|
|
212
|
-
// 당신은 Sonamu 프레임워크에서 Entity와 Enum을 생성하는 도우미입니다.
|
|
213
|
-
// ${instructions}
|
|
214
|
-
// ## 현재 등록된 Entity 목록
|
|
215
|
-
// 다른 엔티티와 관계(relation)를 맺거나 subset에서 참조할 때 반드시 아래 정보를 확인하세요.
|
|
216
|
-
// ${JSON.stringify(existingEntities, null, 2)}
|
|
217
|
-
// ## Tool 사용 가이드
|
|
218
|
-
// ### Entity 생성 (createEntity)
|
|
219
|
-
// 사용자가 새로운 Entity 생성을 요청하면 createEntity 도구를 사용하세요.
|
|
220
|
-
// - entityId: PascalCase로 된 Entity ID (예: "User", "ProductCategory")
|
|
221
|
-
// - title: 한글 제목 (예: "사용자", "상품 카테고리")
|
|
222
|
-
// - table: snake_case로 된 테이블명 (예: "users", "product_categories")
|
|
223
|
-
// - parentId: 부모 Entity ID (선택사항)
|
|
224
|
-
// - props: Entity의 프로퍼티 배열 (위 문서의 Property Types 참고)
|
|
225
|
-
// - indexes: 인덱스 배열
|
|
226
|
-
// - subsets: 서브셋 정의 (기본값: { A: ["id"] })
|
|
227
|
-
// - enums: Enum 정의
|
|
228
|
-
// ### Entity 수정 (updateEntity)
|
|
229
|
-
// 기존 Entity를 수정할 때 updateEntity 도구를 사용하세요. Enum 추가, props 추가/수정, indexes 수정 등 모든 수정 작업에 사용합니다.
|
|
230
|
-
// - entityId: 수정할 Entity ID
|
|
231
|
-
// - updates: 수정할 필드들 (부분 업데이트)
|
|
232
|
-
// - title: 엔티티 한글 제목
|
|
233
|
-
// - table: 테이블명
|
|
234
|
-
// - props: 추가할 프로퍼티 배열 (기존 props에 추가, 같은 이름이면 교체)
|
|
235
|
-
// - indexes: 추가할 인덱스 배열 (기존 indexes에 추가)
|
|
236
|
-
// - subsets: 서브셋 정의 (기존 subsets에 병합)
|
|
237
|
-
// - enumLabels: Enum 정의 (기존 enumLabels에 병합)
|
|
238
|
-
// - mode: "merge"(기본값) 또는 "replace"
|
|
239
|
-
// - merge: 기존 값에 병합
|
|
240
|
-
// - replace: 해당 필드 전체 교체
|
|
241
|
-
// 예시: Employee에 새 Enum 추가
|
|
242
|
-
// updateEntity({ entityId: "Employee", updates: { enumLabels: { "EmployeeRole": { "admin": "관리자", "user": "일반" } } } })
|
|
243
|
-
// 예시: Project에 새 프로퍼티 추가
|
|
244
|
-
// updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type: "integer", desc: "우선순위" }] } })
|
|
245
|
-
// ## 필수 사항
|
|
246
|
-
// - Entity의 props에는 최소한 id(integer, unsigned), created_at(timestamp)가 포함되어야 합니다.
|
|
247
|
-
// - relation 필드는 onUpdate, onDelete가 필수입니다. (예외: OneToOne에서 hasJoinColumn이 false인 경우)
|
|
248
|
-
// - Enum ID는 보통 EntityId + 속성명 형태입니다 (예: UserStatus, ProductType)
|
|
249
|
-
// - subset에서 다른 엔티티의 프로퍼티를 참조할 때는 반드시 해당 엔티티의 실제 프로퍼티명을 사용하세요.
|
|
250
|
-
// ## 검증 오류 처리
|
|
251
|
-
// 도구 호출 결과로 검증 오류(validationErrors)가 반환되면:
|
|
252
|
-
// 1. 오류 메시지를 분석하여 문제점을 파악하세요.
|
|
253
|
-
// 2. 오류를 수정한 데이터로 createEntity를 다시 호출하세요.
|
|
254
|
-
// 3. 사용자에게 오류를 그대로 전달하지 말고, 수정 후 재시도하세요.
|
|
255
|
-
// ### 일반적인 검증 오류와 수정 방법
|
|
256
|
-
// | 오류 메시지 | 수정 방법 |
|
|
257
|
-
// |------------|----------|
|
|
258
|
-
// | "id 프로퍼티가 필수" | props에 { name: "id", type: "integer", unsigned: true } 추가 |
|
|
259
|
-
// | "created_at 프로퍼티가 필수" | props에 { name: "created_at", type: "timestamp", dbDefault: "CURRENT_TIMESTAMP" } 추가 |
|
|
260
|
-
// | "XxxOrderBy enum이 필수" | enums에 { "XxxOrderBy": { "id-desc": "ID최신순" } } 추가 |
|
|
261
|
-
// | "XxxSearchField enum이 필수" | enums에 { "XxxSearchField": { "id": "ID" } } 추가 |
|
|
262
|
-
// | "string 타입은 length가 필수" | 해당 prop에 length 추가 (예: 255) |
|
|
263
|
-
// | "text 타입은 textType이 필수" | 해당 prop에 textType 추가 ("text", "mediumtext", "longtext") |
|
|
264
|
-
// | "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
|
|
265
|
-
// `;
|
|
266
|
-
// return streamText({
|
|
267
|
-
// model: this.model,
|
|
268
|
-
// system: systemMessage,
|
|
269
|
-
// messages,
|
|
270
|
-
// stopWhen: stepCountIs(2),
|
|
271
|
-
// tools: {
|
|
272
|
-
// createEntity: tool({
|
|
273
|
-
// description:
|
|
274
|
-
// "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
|
|
275
|
-
// inputSchema: TemplateOptions.shape.entity,
|
|
276
|
-
// execute: async (
|
|
277
|
-
// entity,
|
|
278
|
-
// ): Promise<{
|
|
279
|
-
// success: boolean;
|
|
280
|
-
// entityId: string;
|
|
281
|
-
// error?: string;
|
|
282
|
-
// validationErrors?: ValidationError[];
|
|
283
|
-
// }> => {
|
|
284
|
-
// try {
|
|
285
|
-
// // 입력 검증
|
|
286
|
-
// const validationErrors = validateEntityJson(entity);
|
|
287
|
-
// if (validationErrors.length > 0) {
|
|
288
|
-
// return {
|
|
289
|
-
// success: false,
|
|
290
|
-
// entityId: entity.entityId,
|
|
291
|
-
// error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
|
|
292
|
-
// validationErrors,
|
|
293
|
-
// };
|
|
294
|
-
// }
|
|
295
|
-
// await Sonamu.syncer.createEntity({
|
|
296
|
-
// subsets: { A: ["id"] },
|
|
297
|
-
// enums: {},
|
|
298
|
-
// ...entity,
|
|
299
|
-
// });
|
|
300
|
-
// // EntityManager 리로드
|
|
301
|
-
// await EntityManager.reload();
|
|
302
|
-
// return { success: true, entityId: entity.entityId };
|
|
303
|
-
// } catch (e) {
|
|
304
|
-
// const error = e instanceof Error ? e.message : "Unknown error";
|
|
305
|
-
// return { success: false, entityId: entity.entityId, error };
|
|
306
|
-
// }
|
|
307
|
-
// },
|
|
308
|
-
// }),
|
|
309
|
-
// updateEntity: tool({
|
|
310
|
-
// description:
|
|
311
|
-
// "기존 Entity를 수정합니다. Enum 추가, props 추가/수정, indexes 수정, subsets 수정 등 모든 엔티티 수정 작업에 사용하세요.",
|
|
312
|
-
// inputSchema: z.object({
|
|
313
|
-
// entityId: z.string().describe("수정할 Entity ID"),
|
|
314
|
-
// updates: TemplateOptions.shape.entity.partial().describe("수정할 필드들"),
|
|
315
|
-
// mode: z
|
|
316
|
-
// .enum(["merge", "replace"])
|
|
317
|
-
// .optional()
|
|
318
|
-
// .describe("수정 모드: merge(기본값, 기존 값에 병합) 또는 replace(전체 교체)"),
|
|
319
|
-
// }),
|
|
320
|
-
// execute: async ({
|
|
321
|
-
// entityId,
|
|
322
|
-
// updates,
|
|
323
|
-
// mode = "merge",
|
|
324
|
-
// }): Promise<{
|
|
325
|
-
// success: boolean;
|
|
326
|
-
// entityId: string;
|
|
327
|
-
// error?: string;
|
|
328
|
-
// validationErrors?: ValidationError[];
|
|
329
|
-
// }> => {
|
|
330
|
-
// try {
|
|
331
|
-
// const entity = EntityManager.get(entityId);
|
|
332
|
-
// for (const [key, value] of Object.entries(updates)) {
|
|
333
|
-
// if (
|
|
334
|
-
// ["entityId", "parentId", "title", "table"].includes(key) &&
|
|
335
|
-
// value !== undefined
|
|
336
|
-
// ) {
|
|
337
|
-
// entity[key] = value;
|
|
338
|
-
// }
|
|
339
|
-
// }
|
|
340
|
-
// // props: merge 시 이름 기준 병합, replace 시 교체
|
|
341
|
-
// if (updates.props !== undefined) {
|
|
342
|
-
// if (mode === "replace") {
|
|
343
|
-
// entity.props = updates.props as EntityProp[];
|
|
344
|
-
// } else {
|
|
345
|
-
// for (const newProp of updates.props) {
|
|
346
|
-
// const existingIndex = entity.props.findIndex((p) => p.name === newProp.name);
|
|
347
|
-
// if (existingIndex >= 0) {
|
|
348
|
-
// entity.props[existingIndex] = newProp as EntityProp;
|
|
349
|
-
// } else {
|
|
350
|
-
// entity.props.push(newProp as EntityProp);
|
|
351
|
-
// }
|
|
352
|
-
// }
|
|
353
|
-
// }
|
|
354
|
-
// }
|
|
355
|
-
// // indexes: merge 시 추가, replace 시 교체
|
|
356
|
-
// if (updates.indexes !== undefined) {
|
|
357
|
-
// entity.indexes =
|
|
358
|
-
// mode === "replace" ? updates.indexes : [...entity.indexes, ...updates.indexes];
|
|
359
|
-
// }
|
|
360
|
-
// // subsets, enumLabels: assign으로 병합 또는 교체
|
|
361
|
-
// if (updates.subsets !== undefined) {
|
|
362
|
-
// entity.subsets =
|
|
363
|
-
// mode === "replace" ? updates.subsets : { ...entity.subsets, ...updates.subsets };
|
|
364
|
-
// }
|
|
365
|
-
// if (updates.enums !== undefined) {
|
|
366
|
-
// entity.enumLabels =
|
|
367
|
-
// mode === "replace" ? updates.enums : { ...entity.enumLabels, ...updates.enums };
|
|
368
|
-
// }
|
|
369
|
-
// // 저장 전 검증
|
|
370
|
-
// const validationErrors = validateEntityJson({
|
|
371
|
-
// ...entity,
|
|
372
|
-
// entityId: entity.id,
|
|
373
|
-
// enums: entity.enumLabels,
|
|
374
|
-
// });
|
|
375
|
-
// if (validationErrors.length > 0) {
|
|
376
|
-
// return {
|
|
377
|
-
// success: false,
|
|
378
|
-
// entityId,
|
|
379
|
-
// error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
|
|
380
|
-
// validationErrors,
|
|
381
|
-
// };
|
|
382
|
-
// }
|
|
383
|
-
// await entity.save();
|
|
384
|
-
// return { success: true, entityId };
|
|
385
|
-
// } catch (e) {
|
|
386
|
-
// const error = e instanceof Error ? e.message : "Unknown error";
|
|
387
|
-
// return { success: false, entityId, error };
|
|
388
|
-
// }
|
|
389
|
-
// },
|
|
390
|
-
// }),
|
|
391
|
-
// },
|
|
392
|
-
// });
|
|
393
|
-
// }
|
|
394
|
-
// }
|
|
395
|
-
// /**
|
|
396
|
-
// * Entity JSON이 entity.instructions.md의 규칙을 따르는지 검증합니다.
|
|
397
|
-
// */
|
|
398
|
-
// function validateEntityJson(input: TemplateOptions["entity"]): ValidationError[] {
|
|
399
|
-
// const errors: ValidationError[] = [];
|
|
400
|
-
// const { entityId, props, enums } = input;
|
|
401
|
-
// // 1. id, created_at prop 필수
|
|
402
|
-
// const hasIdProp = props?.some((p) => p.name === "id");
|
|
403
|
-
// if (!hasIdProp) {
|
|
404
|
-
// errors.push({ field: "props", message: "id 프로퍼티가 필수입니다." });
|
|
405
|
-
// }
|
|
406
|
-
// const hasCreatedAtProp = props?.some((p) => p.name === "created_at");
|
|
407
|
-
// if (!hasCreatedAtProp) {
|
|
408
|
-
// errors.push({ field: "props", message: "created_at 프로퍼티가 필수입니다." });
|
|
409
|
-
// }
|
|
410
|
-
// // 2. 필수 enum 검증: EntityNameOrderBy, EntityNameSearchField
|
|
411
|
-
// const orderByEnumId = `${entityId}OrderBy`;
|
|
412
|
-
// const searchFieldEnumId = `${entityId}SearchField`;
|
|
413
|
-
// if (!enums?.[orderByEnumId]) {
|
|
414
|
-
// errors.push({
|
|
415
|
-
// field: "enums",
|
|
416
|
-
// message: `${orderByEnumId} enum이 필수입니다. (예: { "id-desc": "ID최신순" })`,
|
|
417
|
-
// });
|
|
418
|
-
// }
|
|
419
|
-
// if (!enums?.[searchFieldEnumId]) {
|
|
420
|
-
// errors.push({
|
|
421
|
-
// field: "enums",
|
|
422
|
-
// message: `${searchFieldEnumId} enum이 필수입니다. (예: { "id": "ID" })`,
|
|
423
|
-
// });
|
|
424
|
-
// }
|
|
425
|
-
// // 3. enum prop의 id가 enums에 정의되어 있는지 확인 (cross-field 검증)
|
|
426
|
-
// for (const prop of props ?? []) {
|
|
427
|
-
// if (prop.type === "enum" && !enums?.[prop.id]) {
|
|
428
|
-
// errors.push({
|
|
429
|
-
// field: `props.${prop.name}`,
|
|
430
|
-
// message: `enum id "${prop.id}"가 enums에 정의되어 있지 않습니다.`,
|
|
431
|
-
// });
|
|
432
|
-
// }
|
|
433
|
-
// }
|
|
434
|
-
// return errors;
|
|
435
|
-
// }
|
|
436
|
-
// export const aiClient = new AIClient();
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: AI SDK의 타입이 명확하지 않아 any를 허용함 */ import { anthropic } from "@ai-sdk/anthropic";
|
|
2
|
+
import { stepCountIs, streamText, tool } from "ai";
|
|
3
|
+
import assert from "assert";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { Sonamu } from "../api/index.js";
|
|
8
|
+
import { EntityManager } from "../entity/entity-manager.js";
|
|
9
|
+
import { isInternalSubsetField, normalizeSubsetField, TemplateOptions } from "../types/types.js";
|
|
10
|
+
import { nonNullable } from "../utils/utils.js";
|
|
11
|
+
class AIClient {
|
|
12
|
+
model = anthropic("claude-sonnet-4-5");
|
|
13
|
+
async init() {
|
|
14
|
+
console.log("AI client initialized with AI SDK");
|
|
15
|
+
}
|
|
16
|
+
handleFixture(messages, fixtureRecords) {
|
|
17
|
+
// 현재 fixtureRecords에서 사용된 엔티티들의 구조 정보 수집
|
|
18
|
+
const usedEntityIds = [
|
|
19
|
+
...new Set(fixtureRecords.map((r)=>r.entityId))
|
|
20
|
+
];
|
|
21
|
+
const entityStructures = usedEntityIds.map((entityId)=>{
|
|
22
|
+
const entity = EntityManager.get(entityId);
|
|
23
|
+
return {
|
|
24
|
+
entityId: entity.id,
|
|
25
|
+
table: entity.table,
|
|
26
|
+
props: entity.props,
|
|
27
|
+
relations: entity.relations,
|
|
28
|
+
enumLabels: entity.enumLabels
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const systemMessage = `
|
|
32
|
+
당신은 픽스쳐 레코드를 수정하고 생성할 수 있는 도우미입니다.
|
|
437
33
|
|
|
438
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91aS9haS1jbGllbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gaW1wb3J0IHsgYW50aHJvcGljIH0gZnJvbSBcIkBhaS1zZGsvYW50aHJvcGljXCI7XG4vLyBpbXBvcnQgeyB0eXBlIE1vZGVsTWVzc2FnZSwgc3RlcENvdW50SXMsIHN0cmVhbVRleHQsIHRvb2wgfSBmcm9tIFwiYWlcIjtcbi8vIGltcG9ydCBhc3NlcnQgZnJvbSBcImFzc2VydFwiO1xuLy8gaW1wb3J0IGZzIGZyb20gXCJmc1wiO1xuLy8gaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbi8vIGltcG9ydCB7XG4vLyAgIEVudGl0eU1hbmFnZXIsXG4vLyAgIHR5cGUgRW50aXR5UHJvcCxcbi8vICAgdHlwZSBGaXh0dXJlUmVjb3JkLFxuLy8gICBub25OdWxsYWJsZSxcbi8vICAgU29uYW11LFxuLy8gICBUZW1wbGF0ZU9wdGlvbnMsXG4vLyB9IGZyb20gXCJzb25hbXVcIjtcbi8vIGltcG9ydCB7IHogfSBmcm9tIFwiem9kXCI7XG5cbi8vIHR5cGUgVmFsaWRhdGlvbkVycm9yID0ge1xuLy8gICBmaWVsZDogc3RyaW5nO1xuLy8gICBtZXNzYWdlOiBzdHJpbmc7XG4vLyB9O1xuXG4vLyBjbGFzcyBBSUNsaWVudCB7XG4vLyAgIHByaXZhdGUgbW9kZWwgPSBhbnRocm9waWMoXCJjbGF1ZGUtc29ubmV0LTQtNVwiKTtcblxuLy8gICBhc3luYyBpbml0KCkge1xuLy8gICAgIGNvbnNvbGUubG9nKFwiQUkgY2xpZW50IGluaXRpYWxpemVkIHdpdGggQUkgU0RLXCIpO1xuLy8gICB9XG5cbi8vICAgaGFuZGxlRml4dHVyZShtZXNzYWdlczogTW9kZWxNZXNzYWdlW10sIGZpeHR1cmVSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10pIHtcbi8vICAgICAvLyDtmITsnqwgZml4dHVyZVJlY29yZHPsl5DshJwg7IKs7Jqp65CcIOyXlO2LsO2LsOuTpOydmCDqtazsobAg7KCV67O0IOyImOynkVxuLy8gICAgIGNvbnN0IHVzZWRFbnRpdHlJZHMgPSBbLi4ubmV3IFNldChmaXh0dXJlUmVjb3Jkcy5tYXAoKHIpID0+IHIuZW50aXR5SWQpKV07XG4vLyAgICAgY29uc3QgZW50aXR5U3RydWN0dXJlcyA9IHVzZWRFbnRpdHlJZHMubWFwKChlbnRpdHlJZCkgPT4ge1xuLy8gICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZW50aXR5SWQpO1xuXG4vLyAgICAgICByZXR1cm4ge1xuLy8gICAgICAgICBlbnRpdHlJZDogZW50aXR5LmlkLFxuLy8gICAgICAgICB0YWJsZTogZW50aXR5LnRhYmxlLFxuLy8gICAgICAgICBwcm9wczogZW50aXR5LnByb3BzLFxuLy8gICAgICAgICByZWxhdGlvbnM6IGVudGl0eS5yZWxhdGlvbnMsXG4vLyAgICAgICAgIGVudW1MYWJlbHM6IGVudGl0eS5lbnVtTGFiZWxzLFxuLy8gICAgICAgfTtcbi8vICAgICB9KTtcblxuLy8gICAgIGNvbnN0IHN5c3RlbU1lc3NhZ2UgPSBgXG4vLyAgICAgICAgIOuLueyLoOydgCDtlL3siqTss5Ag66CI7L2U65Oc66W8IOyImOygle2VmOqzoCDsg53shLHtlaAg7IiYIOyeiOuKlCDrj4TsmrDrr7jsnoXri4jri6QuXG5cbi8vICAgICAgICAg7ZiE7J6sIO2UveyKpOyzkCDroIjsvZTrk5w6XG4vLyAgICAgICAgICR7SlNPTi5zdHJpbmdpZnkoZml4dHVyZVJlY29yZHMsIG51bGwsIDIpfVxuXG4vLyAgICAgICAgIOyXlO2LsO2LsCDqtazsobAg7KCV67O0OlxuLy8gICAgICAgICAke0pTT04uc3RyaW5naWZ5KGVudGl0eVN0cnVjdHVyZXMsIG51bGwsIDIpfVxuXG4vLyAgICAgICAgICMjIO2UveyKpOyzkCDsiJjsoJVcbi8vICAgICAgICAg7IKs7Jqp7J6Q6rCAIO2UveyKpOyzkCDqsJIg7IiY7KCV7J2EIOyalOyyre2VmOuptCB1cGRhdGVGaXh0dXJlcyDrj4Tqtazrpbwg7IKs7Jqp7ZWY7JesIOuzgOqyveyCrO2VreydhCDsoIHsmqntlZjshLjsmpQuXG4vLyAgICAgICAgIC0gZml4dHVyZUlkOiDsiJjsoJXtlaAg7ZS97Iqk7LOQIElEICjtmJXsi506IFwiRW50aXR5SWQjaWRcIilcbi8vICAgICAgICAgLSB1cGRhdGVzOiDsu6zrn7zrqoXsnYQg7YKk66GcLCDsg4gg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XG5cbi8vICAgICAgICAg7JiI7IucOiBcIlVzZXIjMVwiIO2UveyKpOyzkOydmCBcIm5hbWVcIiDsu6zrn7zsnYQgXCLtmY3quLjrj5lcIuycvOuhnCDrs4Dqsr3tlZjroKTrqbQ6XG4vLyAgICAgICAgIHVwZGF0ZUZpeHR1cmVzKHsgdXBkYXRlczogW3sgZml4dHVyZUlkOiBcIlVzZXIjMVwiLCB1cGRhdGVzOiB7IG5hbWU6IFwi7ZmN6ri464+ZXCIgfSB9XSB9KVxuXG4vLyAgICAgICAgIOuzgOqyveuQoCDsu6zrn7zsnZggdHlwZeydtCByZWxhdGlvbuyduCDqsr3smrAsIOq0gOugqCDsl5Tti7Dti7Dsl5Drj4Qg67CY7JiB65CY7Ja07JW8IO2VoCDsu6zrn7zsnbQg7J6I64qU7KeAIO2ZleyduO2VmOyEuOyalC5cblxuLy8gICAgICAgICAjIyDtlL3siqTss5Ag7IOd7ISxXG4vLyAgICAgICAgIOyCrOyaqeyekOqwgCDsg4jroZzsmrQg7ZS97Iqk7LOQIOyDneyEseydhCDsmpTssq3tlZjrqbQgY3JlYXRlRml4dHVyZXMg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC5cbi8vICAgICAgICAgLSBlbnRpdHlJZDog7IOd7ISx7ZWgIOyXlO2LsO2LsCBJRFxuLy8gICAgICAgICAtIGlkOiDsg4gg66CI7L2U65Oc7J2YIElEICjquLDsobQg7ZS97Iqk7LOQ7JmAIOykkeuzteuQmOyngCDslYrripQg7J2M7IiYIOyCrOyaqSDqtozsnqUsIOyYiDogLTEsIC0yKVxuLy8gICAgICAgICAtIGNvbHVtbnM6IOy7rOufvOuqheydhCDtgqTroZwsIOqwkuydhCDqsJLsnLzroZwg7ZWY64qUIOqwneyytCAo7JeU7Yuw7YuwIOq1rOyhsCDssLjqs6ApXG5cbi8vICAgICAgICAg7JiI7IucOiDsg4jroZzsmrQgVXNlciDtlL3siqTss5Drpbwg7IOd7ISx7ZWY66Ck66m0OlxuLy8gICAgICAgICBjcmVhdGVGaXh0dXJlcyh7IGZpeHR1cmVzOiBbeyBlbnRpdHlJZDogXCJVc2VyXCIsIGlkOiAtMSwgY29sdW1uczogeyBuYW1lOiBcIu2Zjeq4uOuPmVwiLCBlbWFpbDogXCJob25nQGV4YW1wbGUuY29tXCIgfSB9XSB9KVxuLy8gICAgICAgYDtcblxuLy8gICAgIHJldHVybiBzdHJlYW1UZXh0KHtcbi8vICAgICAgIG1vZGVsOiB0aGlzLm1vZGVsLFxuLy8gICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuLy8gICAgICAgbWVzc2FnZXMsXG4vLyAgICAgICB0b29sczoge1xuLy8gICAgICAgICB1cGRhdGVGaXh0dXJlczogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIu2UveyKpOyzkCDroIjsvZTrk5zsnZgg6rCS7J2EIOyImOygle2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIO2KueyglSDsu6zrn7zsnbTrgpgg6rCS7J2EIOuzgOqyve2VtOuLrOudvOqzoCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbi8vICAgICAgICAgICBpbnB1dFNjaGVtYTogei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgdXBkYXRlczogei5hcnJheShcbi8vICAgICAgICAgICAgICAgei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgICAgIGZpeHR1cmVJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCDtlL3siqTss5AgSUQgKO2YleyLnTogRW50aXR5SWQjaWQpXCIpLFxuLy8gICAgICAgICAgICAgICAgIHVwZGF0ZXM6IHpcbi8vICAgICAgICAgICAgICAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgei51bmtub3duKCkpXG4vLyAgICAgICAgICAgICAgICAgICAuZGVzY3JpYmUoXCLsu6zrn7zrqoXsnYQg7YKk66GcLCDsg4gg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XCIpLFxuLy8gICAgICAgICAgICAgICB9KSxcbi8vICAgICAgICAgICAgICksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIHVwZGF0ZXMsXG4vLyAgICAgICAgICAgfSk6IFByb21pc2U8eyBzdWNjZXNzOiBib29sZWFuOyB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdIH0+ID0+IHtcbi8vICAgICAgICAgICAgIC8vIGZpeHR1cmVSZWNvcmRz66W8IOuzteyCrO2VmOqzoCDsl4XrjbDsnbTtirgg7KCB7JqpXG4vLyAgICAgICAgICAgICBjb25zdCB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdID0gZml4dHVyZVJlY29yZHMubWFwKChyZWNvcmQpID0+IHtcbi8vICAgICAgICAgICAgICAgY29uc3QgdXBkYXRlID0gdXBkYXRlcy5maW5kKCh1KSA9PiB1LmZpeHR1cmVJZCA9PT0gcmVjb3JkLmZpeHR1cmVJZCk7XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGUpIHtcbi8vICAgICAgICAgICAgICAgICAvLyBjb2x1bW5z7J2YIHZhbHVl66W8IOyXheuNsOydtO2KuFxuLy8gICAgICAgICAgICAgICAgIGZvciAoY29uc3QgW2NvbHVtbk5hbWUsIG5ld1ZhbHVlXSBvZiBPYmplY3QuZW50cmllcyh1cGRhdGUudXBkYXRlcykpIHtcbi8vICAgICAgICAgICAgICAgICAgIHJlY29yZC5jb2x1bW5zW2NvbHVtbk5hbWVdLnZhbHVlID1cbi8vICAgICAgICAgICAgICAgICAgICAgbmV3VmFsdWUgYXMgRml4dHVyZVJlY29yZFtcImNvbHVtbnNcIl1bc3RyaW5nXVtcInZhbHVlXCJdO1xuLy8gICAgICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICAgICAgICByZXR1cm4gcmVjb3JkO1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgcmV0dXJuIHJlY29yZDtcbi8vICAgICAgICAgICAgIH0pO1xuXG4vLyAgICAgICAgICAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCB1cGRhdGVkUmVjb3JkcyB9O1xuLy8gICAgICAgICAgIH0sXG4vLyAgICAgICAgIH0pLFxuLy8gICAgICAgICBjcmVhdGVGaXh0dXJlczogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIuyDiOuhnOyatCDtlL3siqTss5Ag66CI7L2U65Oc66W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDrjbDsnbTthLDrpbwg7LaU6rCA7ZW064us65286rOgIOyalOyyre2VoCDrlYwg7IKs7Jqp7ZWY7IS47JqULlwiLFxuLy8gICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4vLyAgICAgICAgICAgICBmaXh0dXJlczogei5hcnJheShcbi8vICAgICAgICAgICAgICAgei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiB6LnN0cmluZygpLmRlc2NyaWJlKFwi7IOd7ISx7ZWgIOyXlO2LsO2LsCBJRFwiKSxcbi8vICAgICAgICAgICAgICAgICBpZDogei5udW1iZXIoKS5kZXNjcmliZShcIuyDiCDroIjsvZTrk5zsnZggSUQgKOydjOyImCDqtozsnqUsIOyYiDogLTEsIC0yKVwiKSxcbi8vICAgICAgICAgICAgICAgICBjb2x1bW5zOiB6XG4vLyAgICAgICAgICAgICAgICAgICAucmVjb3JkKHouc3RyaW5nKCksIHoudW5rbm93bigpKVxuLy8gICAgICAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7Lus65+866qF7J2EIO2CpOuhnCwg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XCIpLFxuLy8gICAgICAgICAgICAgICB9KSxcbi8vICAgICAgICAgICAgICksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIGZpeHR1cmVzLFxuLy8gICAgICAgICAgIH0pOiBQcm9taXNlPHsgc3VjY2VzczogYm9vbGVhbjsgdXBkYXRlZFJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSB9PiA9PiB7XG4vLyAgICAgICAgICAgICBjb25zdCBuZXdSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gPSBmaXh0dXJlcy5tYXAoKGZpeHR1cmUpID0+IHtcbi8vICAgICAgICAgICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZml4dHVyZS5lbnRpdHlJZCk7XG5cbi8vICAgICAgICAgICAgICAgLy8g7JeU7Yuw7YuwIHByb3Bz66W8IOq4sOuwmOycvOuhnCBjb2x1bW5zIOq1rOyEsVxuLy8gICAgICAgICAgICAgICBjb25zdCBjb2x1bW5zOiBGaXh0dXJlUmVjb3JkW1wiY29sdW1uc1wiXSA9IHt9O1xuLy8gICAgICAgICAgICAgICBmb3IgKGNvbnN0IHByb3Agb2YgZW50aXR5LnByb3BzKSB7XG4vLyAgICAgICAgICAgICAgICAgaWYgKHByb3AudHlwZSA9PT0gXCJ2aXJ0dWFsXCIpIGNvbnRpbnVlO1xuXG4vLyAgICAgICAgICAgICAgICAgbGV0IHZhbHVlID0gZml4dHVyZS5jb2x1bW5zW3Byb3AubmFtZV0gPz8gbnVsbDtcblxuLy8gICAgICAgICAgICAgICAgIGlmIChwcm9wLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKSB7XG4vLyAgICAgICAgICAgICAgICAgICAvLyDtmITsnqwg7Iuc6rCE7Jy866GcIOyEpOyglVxuLy8gICAgICAgICAgICAgICAgICAgdmFsdWUgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4vLyAgICAgICAgICAgICAgICAgfSBlbHNlIGlmIChcbi8vICAgICAgICAgICAgICAgICAgIHByb3AudHlwZSA9PT0gXCJyZWxhdGlvblwiICYmXG4vLyAgICAgICAgICAgICAgICAgICAocHJvcC5yZWxhdGlvblR5cGUgPT09IFwiSGFzTWFueVwiIHx8IHByb3AucmVsYXRpb25UeXBlID09PSBcIk1hbnlUb01hbnlcIilcbi8vICAgICAgICAgICAgICAgICApIHtcbi8vICAgICAgICAgICAgICAgICAgIC8vIOuwsOyXtOuhnCDrs4DtmZhcbi8vICAgICAgICAgICAgICAgICAgIHZhbHVlID0gQXJyYXkuaXNBcnJheSh2YWx1ZSkgPyB2YWx1ZSA6IFt2YWx1ZV0uZmlsdGVyKG5vbk51bGxhYmxlKTtcbi8vICAgICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgICBjb2x1bW5zW3Byb3AubmFtZV0gPSB7XG4vLyAgICAgICAgICAgICAgICAgICBwcm9wLFxuLy8gICAgICAgICAgICAgICAgICAgdmFsdWU6IHZhbHVlIGFzIEZpeHR1cmVSZWNvcmRbXCJjb2x1bW5zXCJdW3N0cmluZ11bXCJ2YWx1ZVwiXSxcbi8vICAgICAgICAgICAgICAgICB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgcmV0dXJuIHtcbi8vICAgICAgICAgICAgICAgICBmaXh0dXJlSWQ6IGAke2ZpeHR1cmUuZW50aXR5SWR9IyR7Zml4dHVyZS5pZH1gLFxuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiBmaXh0dXJlLmVudGl0eUlkLFxuLy8gICAgICAgICAgICAgICAgIGlkOiBmaXh0dXJlLmlkLFxuLy8gICAgICAgICAgICAgICAgIGNvbHVtbnMsXG4vLyAgICAgICAgICAgICAgICAgZmV0Y2hlZFJlY29yZHM6IFtdLFxuLy8gICAgICAgICAgICAgICAgIGJlbG9uZ3NSZWNvcmRzOiBbXSxcbi8vICAgICAgICAgICAgICAgICBvdmVycmlkZTogZmFsc2UsXG4vLyAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgLy8g7IOIIOugiOy9lOuTnOuTpOydmCByZWxhdGlvbiDsu6zrn7zsnYQg7ZmV7J247ZWY7JesIOq4sOyhtCDroIjsvZTrk5zrk6TsnZgg7Jet67Cp7ZalIHJlbGF0aW9uIOyXheuNsOydtO2KuFxuLy8gICAgICAgICAgICAgZm9yIChjb25zdCBuZXdSZWNvcmQgb2YgbmV3UmVjb3Jkcykge1xuLy8gICAgICAgICAgICAgICBmb3IgKGNvbnN0IFtfY29sTmFtZSwgY29sXSBvZiBPYmplY3QuZW50cmllcyhuZXdSZWNvcmQuY29sdW1ucykpIHtcbi8vICAgICAgICAgICAgICAgICBpZiAoY29sLnByb3AudHlwZSAhPT0gXCJyZWxhdGlvblwiIHx8IGNvbC52YWx1ZSA9PT0gbnVsbCkgY29udGludWU7XG5cbi8vICAgICAgICAgICAgICAgICBjb25zdCByZWxhdGVkRW50aXR5SWQgPSBjb2wucHJvcC53aXRoO1xuLy8gICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRJZHMgPSBBcnJheS5pc0FycmF5KGNvbC52YWx1ZSkgPyBjb2wudmFsdWUgOiBbY29sLnZhbHVlXTtcblxuLy8gICAgICAgICAgICAgICAgIGZvciAoY29uc3QgcmVsYXRlZElkIG9mIHJlbGF0ZWRJZHMpIHtcbi8vICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRGaXh0dXJlSWQgPSBgJHtyZWxhdGVkRW50aXR5SWR9IyR7cmVsYXRlZElkfWA7XG4vLyAgICAgICAgICAgICAgICAgICBjb25zdCByZWxhdGVkUmVjb3JkID0gbmV3UmVjb3Jkcy5maW5kKChyKSA9PiByLmZpeHR1cmVJZCA9PT0gcmVsYXRlZEZpeHR1cmVJZCk7XG5cbi8vICAgICAgICAgICAgICAgICAgIGlmIChyZWxhdGVkUmVjb3JkKSB7XG4vLyAgICAgICAgICAgICAgICAgICAgIC8vIOyXreuwqe2WpSByZWxhdGlvbiDssL7quLBcbi8vICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmV2ZXJzZUNvbCA9IE9iamVjdC5lbnRyaWVzKHJlbGF0ZWRSZWNvcmQuY29sdW1ucykuZmluZChcbi8vICAgICAgICAgICAgICAgICAgICAgICAoWywgY10pID0+IGMucHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiYgYy5wcm9wLndpdGggPT09IG5ld1JlY29yZC5lbnRpdHlJZCxcbi8vICAgICAgICAgICAgICAgICAgICAgKTtcblxuLy8gICAgICAgICAgICAgICAgICAgICBpZiAocmV2ZXJzZUNvbCkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IFtyZXZlcnNlQ29sTmFtZSwgcmV2ZXJzZUNvbFZhbHVlXSA9IHJldmVyc2VDb2w7XG4vLyAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgY3VycmVudFZhbHVlID0gcmV2ZXJzZUNvbFZhbHVlLnZhbHVlO1xuXG4vLyAgICAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7Zal7J20IOuwsOyXtOyduCDqsr3smrAgKEhhc01hbnksIE1hbnlUb01hbnkpXG4vLyAgICAgICAgICAgICAgICAgICAgICAgaWYgKFxuLy8gICAgICAgICAgICAgICAgICAgICAgICAgcmV2ZXJzZUNvbFZhbHVlLnByb3AudHlwZSA9PT0gXCJyZWxhdGlvblwiICYmXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAocmV2ZXJzZUNvbFZhbHVlLnByb3AucmVsYXRpb25UeXBlID09PSBcIkhhc01hbnlcIiB8fFxuLy8gICAgICAgICAgICAgICAgICAgICAgICAgICByZXZlcnNlQ29sVmFsdWUucHJvcC5yZWxhdGlvblR5cGUgPT09IFwiTWFueVRvTWFueVwiKVxuLy8gICAgICAgICAgICAgICAgICAgICAgICkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgICAgYXNzZXJ0KEFycmF5LmlzQXJyYXkoY3VycmVudFZhbHVlKSwgXCJjdXJyZW50VmFsdWUgbXVzdCBiZSBhbiBhcnJheVwiKTtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghY3VycmVudFZhbHVlLmluY2x1ZGVzKG5ld1JlY29yZC5pZCkpIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFJlY29yZC5jb2x1bW5zW3JldmVyc2VDb2xOYW1lXSA9IHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi5yZXZlcnNlQ29sVmFsdWUsXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IFsuLi5jdXJyZW50VmFsdWUsIG5ld1JlY29yZC5pZF0sXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIC8vIOyXreuwqe2WpeydtCDri6jsnbwg6rCS7J24IOqyveyasCAoQmVsb25nc1RvT25lLCBPbmVUb09uZSlcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0ZWRSZWNvcmQuY29sdW1uc1tyZXZlcnNlQ29sTmFtZV0gPSB7XG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLnJldmVyc2VDb2xWYWx1ZSxcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IG5ld1JlY29yZC5pZCxcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHVwZGF0ZWRSZWNvcmRzOiBuZXdSZWNvcmRzIH07XG4vLyAgICAgICAgICAgfSxcbi8vICAgICAgICAgfSksXG4vLyAgICAgICB9LFxuLy8gICAgIH0pO1xuLy8gICB9XG5cbi8vICAgaGFuZGxlRW50aXR5KG1lc3NhZ2VzOiBNb2RlbE1lc3NhZ2VbXSkge1xuLy8gICAgIC8vIGVudGl0eS5pbnN0cnVjdGlvbnMubWQg7YyM7J28IOydveq4sFxuLy8gICAgIGNvbnN0IGluc3RydWN0aW9uc1BhdGggPSBwYXRoLmpvaW4oaW1wb3J0Lm1ldGEuZGlybmFtZSwgXCIuLlwiLCBcImVudGl0eS5pbnN0cnVjdGlvbnMubWRcIik7XG4vLyAgICAgY29uc3QgaW5zdHJ1Y3Rpb25zID0gZnMucmVhZEZpbGVTeW5jKGluc3RydWN0aW9uc1BhdGgsIFwidXRmLThcIik7XG5cbi8vICAgICAvLyDtmITsnqwg65Ox66Gd65CcIOyXlO2LsO2LsCDsoJXrs7Qg7IiY7KeRXG4vLyAgICAgY29uc3QgZW50aXR5SWRzID0gRW50aXR5TWFuYWdlci5nZXRBbGxJZHMoKTtcbi8vICAgICBjb25zdCBleGlzdGluZ0VudGl0aWVzID0gZW50aXR5SWRzLm1hcCgoZW50aXR5SWQpID0+IHtcbi8vICAgICAgIGNvbnN0IGVudGl0eSA9IEVudGl0eU1hbmFnZXIuZ2V0KGVudGl0eUlkKTtcbi8vICAgICAgIHJldHVybiB7XG4vLyAgICAgICAgIGlkOiBlbnRpdHkuaWQsXG4vLyAgICAgICAgIHRpdGxlOiBlbnRpdHkudGl0bGUsXG4vLyAgICAgICAgIHRhYmxlOiBlbnRpdHkudGFibGUsXG4vLyAgICAgICAgIHByb3BzOiBlbnRpdHkucHJvcHMubWFwKChwKSA9PiAoe1xuLy8gICAgICAgICAgIG5hbWU6IHAubmFtZSxcbi8vICAgICAgICAgICB0eXBlOiBwLnR5cGUsXG4vLyAgICAgICAgICAgZGVzYzogcC5kZXNjLFxuLy8gICAgICAgICB9KSksXG4vLyAgICAgICB9O1xuLy8gICAgIH0pO1xuXG4vLyAgICAgY29uc3Qgc3lzdGVtTWVzc2FnZSA9IGBcbi8vIOuLueyLoOydgCBTb25hbXUg7ZSE66CI7J6E7JuM7YGs7JeQ7IScIEVudGl0eeyZgCBFbnVt7J2EIOyDneyEse2VmOuKlCDrj4TsmrDrr7jsnoXri4jri6QuXG5cbi8vICR7aW5zdHJ1Y3Rpb25zfVxuXG4vLyAjIyDtmITsnqwg65Ox66Gd65CcIEVudGl0eSDrqqnroZ1cbi8vIOuLpOuluCDsl5Tti7Dti7DsmYAg6rSA6rOEKHJlbGF0aW9uKeulvCDrp7rqsbDrgpggc3Vic2V07JeQ7IScIOywuOyhsO2VoCDrlYwg67CY65Oc7IucIOyVhOuemCDsoJXrs7Trpbwg7ZmV7J247ZWY7IS47JqULlxuXG4vLyAke0pTT04uc3RyaW5naWZ5KGV4aXN0aW5nRW50aXRpZXMsIG51bGwsIDIpfVxuXG4vLyAjIyBUb29sIOyCrOyaqSDqsIDsnbTrk5xcblxuLy8gIyMjIEVudGl0eSDsg53shLEgKGNyZWF0ZUVudGl0eSlcbi8vIOyCrOyaqeyekOqwgCDsg4jroZzsmrQgRW50aXR5IOyDneyEseydhCDsmpTssq3tlZjrqbQgY3JlYXRlRW50aXR5IOuPhOq1rOulvCDsgqzsmqntlZjshLjsmpQuXG4vLyAtIGVudGl0eUlkOiBQYXNjYWxDYXNl66GcIOuQnCBFbnRpdHkgSUQgKOyYiDogXCJVc2VyXCIsIFwiUHJvZHVjdENhdGVnb3J5XCIpXG4vLyAtIHRpdGxlOiDtlZzquIAg7KCc66qpICjsmIg6IFwi7IKs7Jqp7J6QXCIsIFwi7IOB7ZKIIOy5tO2FjOqzoOumrFwiKVxuLy8gLSB0YWJsZTogc25ha2VfY2FzZeuhnCDrkJwg7YWM7J2067iU66qFICjsmIg6IFwidXNlcnNcIiwgXCJwcm9kdWN0X2NhdGVnb3JpZXNcIilcbi8vIC0gcGFyZW50SWQ6IOu2gOuqqCBFbnRpdHkgSUQgKOyEoO2DneyCrO2VrSlcbi8vIC0gcHJvcHM6IEVudGl0eeydmCDtlITroZztjbzti7Ag67Cw7Je0ICjsnIQg66y47ISc7J2YIFByb3BlcnR5IFR5cGVzIOywuOqzoClcbi8vIC0gaW5kZXhlczog7J24642x7IqkIOuwsOyXtFxuLy8gLSBzdWJzZXRzOiDshJzruIzshYsg7KCV7J2YICjquLDrs7jqsJI6IHsgQTogW1wiaWRcIl0gfSlcbi8vIC0gZW51bXM6IEVudW0g7KCV7J2YXG5cbi8vICMjIyBFbnRpdHkg7IiY7KCVICh1cGRhdGVFbnRpdHkpXG4vLyDquLDsobQgRW50aXR566W8IOyImOygle2VoCDrlYwgdXBkYXRlRW50aXR5IOuPhOq1rOulvCDsgqzsmqntlZjshLjsmpQuIEVudW0g7LaU6rCALCBwcm9wcyDstpTqsIAv7IiY7KCVLCBpbmRleGVzIOyImOyglSDrk7Eg66qo65OgIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWp64uI64ukLlxuLy8gLSBlbnRpdHlJZDog7IiY7KCV7ZWgIEVudGl0eSBJRFxuLy8gLSB1cGRhdGVzOiDsiJjsoJXtlaAg7ZWE65Oc65OkICjrtoDrtoQg7JeF642w7J207Yq4KVxuLy8gICAtIHRpdGxlOiDsl5Tti7Dti7Ag7ZWc6riAIOygnOuqqVxuLy8gICAtIHRhYmxlOiDthYzsnbTruJTrqoVcbi8vICAgLSBwcm9wczog7LaU6rCA7ZWgIO2UhOuhnO2NvO2LsCDrsLDsl7QgKOq4sOyhtCBwcm9wc+yXkCDstpTqsIAsIOqwmeydgCDsnbTrpoTsnbTrqbQg6rWQ7LK0KVxuLy8gICAtIGluZGV4ZXM6IOy2lOqwgO2VoCDsnbjrjbHsiqQg67Cw7Je0ICjquLDsobQgaW5kZXhlc+yXkCDstpTqsIApXG4vLyAgIC0gc3Vic2V0czog7ISc67iM7IWLIOygleydmCAo6riw7KG0IHN1YnNldHPsl5Ag67OR7ZWpKVxuLy8gICAtIGVudW1MYWJlbHM6IEVudW0g7KCV7J2YICjquLDsobQgZW51bUxhYmVsc+yXkCDrs5HtlakpXG4vLyAtIG1vZGU6IFwibWVyZ2VcIijquLDrs7jqsJIpIOuYkOuKlCBcInJlcGxhY2VcIlxuLy8gICAtIG1lcmdlOiDquLDsobQg6rCS7JeQIOuzke2VqVxuLy8gICAtIHJlcGxhY2U6IO2VtOuLuSDtlYTrk5wg7KCE7LK0IOq1kOyytFxuXG4vLyDsmIjsi5w6IEVtcGxveWVl7JeQIOyDiCBFbnVtIOy2lOqwgFxuLy8gdXBkYXRlRW50aXR5KHsgZW50aXR5SWQ6IFwiRW1wbG95ZWVcIiwgdXBkYXRlczogeyBlbnVtTGFiZWxzOiB7IFwiRW1wbG95ZWVSb2xlXCI6IHsgXCJhZG1pblwiOiBcIuq0gOumrOyekFwiLCBcInVzZXJcIjogXCLsnbzrsJhcIiB9IH0gfSB9KVxuXG4vLyDsmIjsi5w6IFByb2plY3Tsl5Ag7IOIIO2UhOuhnO2NvO2LsCDstpTqsIBcbi8vIHVwZGF0ZUVudGl0eSh7IGVudGl0eUlkOiBcIlByb2plY3RcIiwgdXBkYXRlczogeyBwcm9wczogW3sgbmFtZTogXCJwcmlvcml0eVwiLCB0eXBlOiBcImludGVnZXJcIiwgZGVzYzogXCLsmrDshKDsiJzsnIRcIiB9XSB9IH0pXG5cbi8vICMjIO2VhOyImCDsgqztla1cbi8vIC0gRW50aXR57J2YIHByb3Bz7JeQ64qUIOy1nOyGjO2VnCBpZChpbnRlZ2VyLCB1bnNpZ25lZCksIGNyZWF0ZWRfYXQodGltZXN0YW1wKeqwgCDtj6ztlajrkJjslrTslbwg7ZWp64uI64ukLlxuLy8gLSByZWxhdGlvbiDtlYTrk5zripQgb25VcGRhdGUsIG9uRGVsZXRl6rCAIO2VhOyImOyeheuLiOuLpC4gKOyYiOyZuDogT25lVG9PbmXsl5DshJwgaGFzSm9pbkNvbHVtbuydtCBmYWxzZeyduCDqsr3smrApXG4vLyAtIEVudW0gSUTripQg67O07Ya1IEVudGl0eUlkICsg7IaN7ISx66qFIO2Yle2DnOyeheuLiOuLpCAo7JiIOiBVc2VyU3RhdHVzLCBQcm9kdWN0VHlwZSlcbi8vIC0gc3Vic2V07JeQ7IScIOuLpOuluCDsl5Tti7Dti7DsnZgg7ZSE66Gc7Y287Yuw66W8IOywuOyhsO2VoCDrlYzripQg67CY65Oc7IucIO2VtOuLuSDsl5Tti7Dti7DsnZgg7Iuk7KCcIO2UhOuhnO2NvO2LsOuqheydhCDsgqzsmqntlZjshLjsmpQuXG5cbi8vICMjIOqygOymnSDsmKTrpZgg7LKY66asXG4vLyDrj4Tqtawg7Zi47LacIOqysOqzvOuhnCDqsoDspp0g7Jik66WYKHZhbGlkYXRpb25FcnJvcnMp6rCAIOuwmO2ZmOuQmOuptDpcbi8vIDEuIOyYpOulmCDrqZTsi5zsp4Drpbwg67aE7ISd7ZWY7JesIOusuOygnOygkOydhCDtjIzslYXtlZjshLjsmpQuXG4vLyAyLiDsmKTrpZjrpbwg7IiY7KCV7ZWcIOuNsOydtO2EsOuhnCBjcmVhdGVFbnRpdHnrpbwg64uk7IucIO2YuOy2nO2VmOyEuOyalC5cbi8vIDMuIOyCrOyaqeyekOyXkOqyjCDsmKTrpZjrpbwg6re464yA66GcIOyghOuLrO2VmOyngCDrp5Dqs6AsIOyImOyglSDtm4Qg7J6s7Iuc64+E7ZWY7IS47JqULlxuXG4vLyAjIyMg7J2867CY7KCB7J24IOqygOymnSDsmKTrpZjsmYAg7IiY7KCVIOuwqeuylVxuLy8gfCDsmKTrpZgg66mU7Iuc7KeAIHwg7IiY7KCVIOuwqeuylSB8XG4vLyB8LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18XG4vLyB8IFwiaWQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImFwiIHwgcHJvcHPsl5AgeyBuYW1lOiBcImlkXCIsIHR5cGU6IFwiaW50ZWdlclwiLCB1bnNpZ25lZDogdHJ1ZSB9IOy2lOqwgCB8XG4vLyB8IFwiY3JlYXRlZF9hdCDtlITroZztjbzti7DqsIAg7ZWE7IiYXCIgfCBwcm9wc+yXkCB7IG5hbWU6IFwiY3JlYXRlZF9hdFwiLCB0eXBlOiBcInRpbWVzdGFtcFwiLCBkYkRlZmF1bHQ6IFwiQ1VSUkVOVF9USU1FU1RBTVBcIiB9IOy2lOqwgCB8XG4vLyB8IFwiWHh4T3JkZXJCeSBlbnVt7J20IO2VhOyImFwiIHwgZW51bXPsl5AgeyBcIlh4eE9yZGVyQnlcIjogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0gfSDstpTqsIAgfFxuLy8gfCBcIlh4eFNlYXJjaEZpZWxkIGVudW3snbQg7ZWE7IiYXCIgfCBlbnVtc+yXkCB7IFwiWHh4U2VhcmNoRmllbGRcIjogeyBcImlkXCI6IFwiSURcIiB9IH0g7LaU6rCAIHxcbi8vIHwgXCJzdHJpbmcg7YOA7J6F7J2AIGxlbmd0aOqwgCDtlYTsiJhcIiB8IO2VtOuLuSBwcm9w7JeQIGxlbmd0aCDstpTqsIAgKOyYiDogMjU1KSB8XG4vLyB8IFwidGV4dCDtg4DsnoXsnYAgdGV4dFR5cGXsnbQg7ZWE7IiYXCIgfCDtlbTri7kgcHJvcOyXkCB0ZXh0VHlwZSDstpTqsIAgKFwidGV4dFwiLCBcIm1lZGl1bXRleHRcIiwgXCJsb25ndGV4dFwiKSB8XG4vLyB8IFwib25VcGRhdGXqsIAg7ZWE7IiYXCIgfCDtlbTri7kgcmVsYXRpb24gcHJvcOyXkCBvblVwZGF0ZSwgb25EZWxldGUg7LaU6rCAIChcIkNBU0NBREVcIikgfFxuLy8gICAgICAgYDtcblxuLy8gICAgIHJldHVybiBzdHJlYW1UZXh0KHtcbi8vICAgICAgIG1vZGVsOiB0aGlzLm1vZGVsLFxuLy8gICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuLy8gICAgICAgbWVzc2FnZXMsXG4vLyAgICAgICBzdG9wV2hlbjogc3RlcENvdW50SXMoMiksXG4vLyAgICAgICB0b29sczoge1xuLy8gICAgICAgICBjcmVhdGVFbnRpdHk6IHRvb2woe1xuLy8gICAgICAgICAgIGRlc2NyaXB0aW9uOlxuLy8gICAgICAgICAgICAgXCLsg4jroZzsmrQgRW50aXR566W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDsl5Tti7Dti7Drgpgg7YWM7J2067iUIOyDneyEseydhCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbi8vICAgICAgICAgICBpbnB1dFNjaGVtYTogVGVtcGxhdGVPcHRpb25zLnNoYXBlLmVudGl0eSxcbi8vICAgICAgICAgICBleGVjdXRlOiBhc3luYyAoXG4vLyAgICAgICAgICAgICBlbnRpdHksXG4vLyAgICAgICAgICAgKTogUHJvbWlzZTx7XG4vLyAgICAgICAgICAgICBzdWNjZXNzOiBib29sZWFuO1xuLy8gICAgICAgICAgICAgZW50aXR5SWQ6IHN0cmluZztcbi8vICAgICAgICAgICAgIGVycm9yPzogc3RyaW5nO1xuLy8gICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycz86IFZhbGlkYXRpb25FcnJvcltdO1xuLy8gICAgICAgICAgIH0+ID0+IHtcbi8vICAgICAgICAgICAgIHRyeSB7XG4vLyAgICAgICAgICAgICAgIC8vIOyeheugpSDqsoDspp1cbi8vICAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbihlbnRpdHkpO1xuXG4vLyAgICAgICAgICAgICAgIGlmICh2YWxpZGF0aW9uRXJyb3JzLmxlbmd0aCA+IDApIHtcbi8vICAgICAgICAgICAgICAgICByZXR1cm4ge1xuLy8gICAgICAgICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuLy8gICAgICAgICAgICAgICAgICAgZXJyb3I6IGDqsoDspp0g7Jik66WYOiAke3ZhbGlkYXRpb25FcnJvcnMubWFwKChlKSA9PiBgWyR7ZS5maWVsZH1dICR7ZS5tZXNzYWdlfWApLmpvaW4oXCIsIFwiKX1gLFxuLy8gICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycyxcbi8vICAgICAgICAgICAgICAgICB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgYXdhaXQgU29uYW11LnN5bmNlci5jcmVhdGVFbnRpdHkoe1xuLy8gICAgICAgICAgICAgICAgIHN1YnNldHM6IHsgQTogW1wiaWRcIl0gfSxcbi8vICAgICAgICAgICAgICAgICBlbnVtczoge30sXG4vLyAgICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuLy8gICAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgICAvLyBFbnRpdHlNYW5hZ2VyIOumrOuhnOuTnFxuLy8gICAgICAgICAgICAgICBhd2FpdCBFbnRpdHlNYW5hZ2VyLnJlbG9hZCgpO1xuXG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQgfTtcbi8vICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbi8vICAgICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBlIGluc3RhbmNlb2YgRXJyb3IgPyBlLm1lc3NhZ2UgOiBcIlVua25vd24gZXJyb3JcIjtcbi8vICAgICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQsIGVycm9yIH07XG4vLyAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgfSxcbi8vICAgICAgICAgfSksXG4vLyAgICAgICAgIHVwZGF0ZUVudGl0eTogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWp64uI64ukLiBFbnVtIOy2lOqwgCwgcHJvcHMg7LaU6rCAL+yImOyglSwgaW5kZXhlcyDsiJjsoJUsIHN1YnNldHMg7IiY7KCVIOuTsSDrqqjrk6Ag7JeU7Yuw7YuwIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWY7IS47JqULlwiLFxuLy8gICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4vLyAgICAgICAgICAgICBlbnRpdHlJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCBFbnRpdHkgSURcIiksXG4vLyAgICAgICAgICAgICB1cGRhdGVzOiBUZW1wbGF0ZU9wdGlvbnMuc2hhcGUuZW50aXR5LnBhcnRpYWwoKS5kZXNjcmliZShcIuyImOygle2VoCDtlYTrk5zrk6RcIiksXG4vLyAgICAgICAgICAgICBtb2RlOiB6XG4vLyAgICAgICAgICAgICAgIC5lbnVtKFtcIm1lcmdlXCIsIFwicmVwbGFjZVwiXSlcbi8vICAgICAgICAgICAgICAgLm9wdGlvbmFsKClcbi8vICAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7IiY7KCVIOuqqOuTnDogbWVyZ2Uo6riw67O46rCSLCDquLDsobQg6rCS7JeQIOuzke2VqSkg65iQ64qUIHJlcGxhY2Uo7KCE7LK0IOq1kOyytClcIiksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIGVudGl0eUlkLFxuLy8gICAgICAgICAgICAgdXBkYXRlcyxcbi8vICAgICAgICAgICAgIG1vZGUgPSBcIm1lcmdlXCIsXG4vLyAgICAgICAgICAgfSk6IFByb21pc2U8e1xuLy8gICAgICAgICAgICAgc3VjY2VzczogYm9vbGVhbjtcbi8vICAgICAgICAgICAgIGVudGl0eUlkOiBzdHJpbmc7XG4vLyAgICAgICAgICAgICBlcnJvcj86IHN0cmluZztcbi8vICAgICAgICAgICAgIHZhbGlkYXRpb25FcnJvcnM/OiBWYWxpZGF0aW9uRXJyb3JbXTtcbi8vICAgICAgICAgICB9PiA9PiB7XG4vLyAgICAgICAgICAgICB0cnkge1xuLy8gICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbi8vICAgICAgICAgICAgICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXModXBkYXRlcykpIHtcbi8vICAgICAgICAgICAgICAgICBpZiAoXG4vLyAgICAgICAgICAgICAgICAgICBbXCJlbnRpdHlJZFwiLCBcInBhcmVudElkXCIsIFwidGl0bGVcIiwgXCJ0YWJsZVwiXS5pbmNsdWRlcyhrZXkpICYmXG4vLyAgICAgICAgICAgICAgICAgICB2YWx1ZSAhPT0gdW5kZWZpbmVkXG4vLyAgICAgICAgICAgICAgICAgKSB7XG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHlba2V5XSA9IHZhbHVlO1xuLy8gICAgICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICAgICAgfVxuXG4vLyAgICAgICAgICAgICAgIC8vIHByb3BzOiBtZXJnZSDsi5wg7J2066aEIOq4sOykgCDrs5HtlaksIHJlcGxhY2Ug7IucIOq1kOyytFxuLy8gICAgICAgICAgICAgICBpZiAodXBkYXRlcy5wcm9wcyAhPT0gdW5kZWZpbmVkKSB7XG4vLyAgICAgICAgICAgICAgICAgaWYgKG1vZGUgPT09IFwicmVwbGFjZVwiKSB7XG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHMgPSB1cGRhdGVzLnByb3BzIGFzIEVudGl0eVByb3BbXTtcbi8vICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuLy8gICAgICAgICAgICAgICAgICAgZm9yIChjb25zdCBuZXdQcm9wIG9mIHVwZGF0ZXMucHJvcHMpIHtcbi8vICAgICAgICAgICAgICAgICAgICAgY29uc3QgZXhpc3RpbmdJbmRleCA9IGVudGl0eS5wcm9wcy5maW5kSW5kZXgoKHApID0+IHAubmFtZSA9PT0gbmV3UHJvcC5uYW1lKTtcbi8vICAgICAgICAgICAgICAgICAgICAgaWYgKGV4aXN0aW5nSW5kZXggPj0gMCkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgIGVudGl0eS5wcm9wc1tleGlzdGluZ0luZGV4XSA9IG5ld1Byb3AgYXMgRW50aXR5UHJvcDtcbi8vICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHMucHVzaChuZXdQcm9wIGFzIEVudGl0eVByb3ApO1xuLy8gICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8gaW5kZXhlczogbWVyZ2Ug7IucIOy2lOqwgCwgcmVwbGFjZSDsi5wg6rWQ7LK0XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGVzLmluZGV4ZXMgIT09IHVuZGVmaW5lZCkge1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eS5pbmRleGVzID1cbi8vICAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiID8gdXBkYXRlcy5pbmRleGVzIDogWy4uLmVudGl0eS5pbmRleGVzLCAuLi51cGRhdGVzLmluZGV4ZXNdO1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8gc3Vic2V0cywgZW51bUxhYmVsczogYXNzaWdu7Jy866GcIOuzke2VqSDrmJDripQg6rWQ7LK0XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGVzLnN1YnNldHMgIT09IHVuZGVmaW5lZCkge1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzID1cbi8vICAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiID8gdXBkYXRlcy5zdWJzZXRzIDogeyAuLi5lbnRpdHkuc3Vic2V0cywgLi4udXBkYXRlcy5zdWJzZXRzIH07XG4vLyAgICAgICAgICAgICAgIH1cblxuLy8gICAgICAgICAgICAgICBpZiAodXBkYXRlcy5lbnVtcyAhPT0gdW5kZWZpbmVkKSB7XG4vLyAgICAgICAgICAgICAgICAgZW50aXR5LmVudW1MYWJlbHMgPVxuLy8gICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCIgPyB1cGRhdGVzLmVudW1zIDogeyAuLi5lbnRpdHkuZW51bUxhYmVscywgLi4udXBkYXRlcy5lbnVtcyB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8g7KCA7J6lIOyghCDqsoDspp1cbi8vICAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbih7XG4vLyAgICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4vLyAgICAgICAgICAgICAgICAgZW51bXM6IGVudGl0eS5lbnVtTGFiZWxzLFxuLy8gICAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvbkVycm9ycy5sZW5ndGggPiAwKSB7XG4vLyAgICAgICAgICAgICAgICAgcmV0dXJuIHtcbi8vICAgICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuLy8gICAgICAgICAgICAgICAgICAgZW50aXR5SWQsXG4vLyAgICAgICAgICAgICAgICAgICBlcnJvcjogYOqygOymnSDsmKTrpZg6ICR7dmFsaWRhdGlvbkVycm9ycy5tYXAoKGUpID0+IGBbJHtlLmZpZWxkfV0gJHtlLm1lc3NhZ2V9YCkuam9pbihcIiwgXCIpfWAsXG4vLyAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uRXJyb3JzLFxuLy8gICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgIH1cblxuLy8gICAgICAgICAgICAgICBhd2FpdCBlbnRpdHkuc2F2ZSgpO1xuXG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkIH07XG4vLyAgICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4vLyAgICAgICAgICAgICAgIGNvbnN0IGVycm9yID0gZSBpbnN0YW5jZW9mIEVycm9yID8gZS5tZXNzYWdlIDogXCJVbmtub3duIGVycm9yXCI7XG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlbnRpdHlJZCwgZXJyb3IgfTtcbi8vICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICB9LFxuLy8gICAgICAgICB9KSxcbi8vICAgICAgIH0sXG4vLyAgICAgfSk7XG4vLyAgIH1cbi8vIH1cblxuLy8gLyoqXG4vLyAgKiBFbnRpdHkgSlNPTuydtCBlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1k7J2YIOq3nOy5meydhCDrlLDrpbTripTsp4Ag6rKA7Kad7ZWp64uI64ukLlxuLy8gICovXG4vLyBmdW5jdGlvbiB2YWxpZGF0ZUVudGl0eUpzb24oaW5wdXQ6IFRlbXBsYXRlT3B0aW9uc1tcImVudGl0eVwiXSk6IFZhbGlkYXRpb25FcnJvcltdIHtcbi8vICAgY29uc3QgZXJyb3JzOiBWYWxpZGF0aW9uRXJyb3JbXSA9IFtdO1xuLy8gICBjb25zdCB7IGVudGl0eUlkLCBwcm9wcywgZW51bXMgfSA9IGlucHV0O1xuXG4vLyAgIC8vIDEuIGlkLCBjcmVhdGVkX2F0IHByb3Ag7ZWE7IiYXG4vLyAgIGNvbnN0IGhhc0lkUHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiaWRcIik7XG4vLyAgIGlmICghaGFzSWRQcm9wKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImlkIO2UhOuhnO2NvO2LsOqwgCDtlYTsiJjsnoXri4jri6QuXCIgfSk7XG4vLyAgIH1cbi8vICAgY29uc3QgaGFzQ3JlYXRlZEF0UHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKTtcbi8vICAgaWYgKCFoYXNDcmVhdGVkQXRQcm9wKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImNyZWF0ZWRfYXQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImOyeheuLiOuLpC5cIiB9KTtcbi8vICAgfVxuXG4vLyAgIC8vIDIuIO2VhOyImCBlbnVtIOqygOymnTogRW50aXR5TmFtZU9yZGVyQnksIEVudGl0eU5hbWVTZWFyY2hGaWVsZFxuLy8gICBjb25zdCBvcmRlckJ5RW51bUlkID0gYCR7ZW50aXR5SWR9T3JkZXJCeWA7XG4vLyAgIGNvbnN0IHNlYXJjaEZpZWxkRW51bUlkID0gYCR7ZW50aXR5SWR9U2VhcmNoRmllbGRgO1xuXG4vLyAgIGlmICghZW51bXM/LltvcmRlckJ5RW51bUlkXSkge1xuLy8gICAgIGVycm9ycy5wdXNoKHtcbi8vICAgICAgIGZpZWxkOiBcImVudW1zXCIsXG4vLyAgICAgICBtZXNzYWdlOiBgJHtvcmRlckJ5RW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0pYCxcbi8vICAgICB9KTtcbi8vICAgfVxuLy8gICBpZiAoIWVudW1zPy5bc2VhcmNoRmllbGRFbnVtSWRdKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goe1xuLy8gICAgICAgZmllbGQ6IFwiZW51bXNcIixcbi8vICAgICAgIG1lc3NhZ2U6IGAke3NlYXJjaEZpZWxkRW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkXCI6IFwiSURcIiB9KWAsXG4vLyAgICAgfSk7XG4vLyAgIH1cblxuLy8gICAvLyAzLiBlbnVtIHByb3DsnZggaWTqsIAgZW51bXPsl5Ag7KCV7J2Y65CY7Ja0IOyeiOuKlOyngCDtmZXsnbggKGNyb3NzLWZpZWxkIOqygOymnSlcbi8vICAgZm9yIChjb25zdCBwcm9wIG9mIHByb3BzID8/IFtdKSB7XG4vLyAgICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtXCIgJiYgIWVudW1zPy5bcHJvcC5pZF0pIHtcbi8vICAgICAgIGVycm9ycy5wdXNoKHtcbi8vICAgICAgICAgZmllbGQ6IGBwcm9wcy4ke3Byb3AubmFtZX1gLFxuLy8gICAgICAgICBtZXNzYWdlOiBgZW51bSBpZCBcIiR7cHJvcC5pZH1cIuqwgCBlbnVtc+yXkCDsoJXsnZjrkJjslrQg7J6I7KeAIOyViuyKteuLiOuLpC5gLFxuLy8gICAgICAgfSk7XG4vLyAgICAgfVxuLy8gICB9XG5cbi8vICAgcmV0dXJuIGVycm9ycztcbi8vIH1cblxuLy8gZXhwb3J0IGNvbnN0IGFpQ2xpZW50ID0gbmV3IEFJQ2xpZW50KCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsaURBQWlEO0FBQ2pELHlFQUF5RTtBQUN6RSwrQkFBK0I7QUFDL0IsdUJBQXVCO0FBQ3ZCLDJCQUEyQjtBQUMzQixXQUFXO0FBQ1gsbUJBQW1CO0FBQ25CLHFCQUFxQjtBQUNyQix3QkFBd0I7QUFDeEIsaUJBQWlCO0FBQ2pCLFlBQVk7QUFDWixxQkFBcUI7QUFDckIsbUJBQW1CO0FBQ25CLDJCQUEyQjtBQUUzQiwyQkFBMkI7QUFDM0IsbUJBQW1CO0FBQ25CLHFCQUFxQjtBQUNyQixLQUFLO0FBRUwsbUJBQW1CO0FBQ25CLG9EQUFvRDtBQUVwRCxtQkFBbUI7QUFDbkIsd0RBQXdEO0FBQ3hELE1BQU07QUFFTiwrRUFBK0U7QUFDL0UsZ0RBQWdEO0FBQ2hELGlGQUFpRjtBQUNqRixpRUFBaUU7QUFDakUsb0RBQW9EO0FBRXBELGlCQUFpQjtBQUNqQiwrQkFBK0I7QUFDL0IsK0JBQStCO0FBQy9CLCtCQUErQjtBQUMvQix1Q0FB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
|
|
34
|
+
현재 픽스쳐 레코드:
|
|
35
|
+
${JSON.stringify(fixtureRecords, null, 2)}
|
|
36
|
+
|
|
37
|
+
엔티티 구조 정보:
|
|
38
|
+
${JSON.stringify(entityStructures, null, 2)}
|
|
39
|
+
|
|
40
|
+
## 픽스쳐 수정
|
|
41
|
+
사용자가 픽스쳐 값 수정을 요청하면 updateFixtures 도구를 사용하여 변경사항을 적용하세요.
|
|
42
|
+
- fixtureId: 수정할 픽스쳐 ID (형식: "EntityId#id")
|
|
43
|
+
- updates: 컬럼명을 키로, 새 값을 값으로 하는 객체
|
|
44
|
+
|
|
45
|
+
예시: "User#1" 픽스쳐의 "name" 컬럼을 "홍길동"으로 변경하려면:
|
|
46
|
+
updateFixtures({ updates: [{ fixtureId: "User#1", updates: { name: "홍길동" } }] })
|
|
47
|
+
|
|
48
|
+
변경될 컬럼의 type이 relation인 경우, 관련 엔티티에도 반영되어야 할 컬럼이 있는지 확인하세요.
|
|
49
|
+
|
|
50
|
+
## 픽스쳐 생성
|
|
51
|
+
사용자가 새로운 픽스쳐 생성을 요청하면 createFixtures 도구를 사용하세요.
|
|
52
|
+
- entityId: 생성할 엔티티 ID
|
|
53
|
+
- id: 새 레코드의 ID (기존 픽스쳐와 중복되지 않는 음수 사용 권장, 예: -1, -2)
|
|
54
|
+
- columns: 컬럼명을 키로, 값을 값으로 하는 객체 (엔티티 구조 참고)
|
|
55
|
+
|
|
56
|
+
예시: 새로운 User 픽스쳐를 생성하려면:
|
|
57
|
+
createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
|
|
58
|
+
`;
|
|
59
|
+
return streamText({
|
|
60
|
+
model: this.model,
|
|
61
|
+
system: systemMessage,
|
|
62
|
+
messages,
|
|
63
|
+
tools: {
|
|
64
|
+
updateFixtures: tool({
|
|
65
|
+
description: "픽스쳐 레코드의 값을 수정합니다. 사용자가 특정 컬럼이나 값을 변경해달라고 요청할 때 사용하세요.",
|
|
66
|
+
inputSchema: z.object({
|
|
67
|
+
updates: z.array(z.object({
|
|
68
|
+
fixtureId: z.string().describe("수정할 픽스쳐 ID (형식: EntityId#id)"),
|
|
69
|
+
updates: z.record(z.string(), z.unknown()).describe("컬럼명을 키로, 새 값을 값으로 하는 객체")
|
|
70
|
+
}))
|
|
71
|
+
}),
|
|
72
|
+
execute: async ({ updates })=>{
|
|
73
|
+
// fixtureRecords를 복사하고 업데이트 적용
|
|
74
|
+
const updatedRecords = fixtureRecords.map((record)=>{
|
|
75
|
+
const update = updates.find((u)=>u.fixtureId === record.fixtureId);
|
|
76
|
+
if (update) {
|
|
77
|
+
// columns의 value를 업데이트
|
|
78
|
+
for (const [columnName, newValue] of Object.entries(update.updates)){
|
|
79
|
+
record.columns[columnName].value = newValue;
|
|
80
|
+
}
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
return record;
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
updatedRecords
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
createFixtures: tool({
|
|
92
|
+
description: "새로운 픽스쳐 레코드를 생성합니다. 사용자가 새로운 데이터를 추가해달라고 요청할 때 사용하세요.",
|
|
93
|
+
inputSchema: z.object({
|
|
94
|
+
fixtures: z.array(z.object({
|
|
95
|
+
entityId: z.string().describe("생성할 엔티티 ID"),
|
|
96
|
+
id: z.number().describe("새 레코드의 ID (음수 권장, 예: -1, -2)"),
|
|
97
|
+
columns: z.record(z.string(), z.unknown()).describe("컬럼명을 키로, 값을 값으로 하는 객체")
|
|
98
|
+
}))
|
|
99
|
+
}),
|
|
100
|
+
execute: async ({ fixtures })=>{
|
|
101
|
+
const newRecords = fixtures.map((fixture)=>{
|
|
102
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
103
|
+
// 엔티티 props를 기반으로 columns 구성
|
|
104
|
+
const columns = {};
|
|
105
|
+
for (const prop of entity.props){
|
|
106
|
+
if (prop.type === "virtual") continue;
|
|
107
|
+
let value = fixture.columns[prop.name] ?? null;
|
|
108
|
+
if (prop.name === "created_at") {
|
|
109
|
+
// 현재 시간으로 설정
|
|
110
|
+
value = new Date().toISOString();
|
|
111
|
+
} else if (prop.type === "relation" && (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")) {
|
|
112
|
+
// 배열로 변환
|
|
113
|
+
value = Array.isArray(value) ? value : [
|
|
114
|
+
value
|
|
115
|
+
].filter(nonNullable);
|
|
116
|
+
}
|
|
117
|
+
columns[prop.name] = {
|
|
118
|
+
prop,
|
|
119
|
+
value: value
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
fixtureId: `${fixture.entityId}#${fixture.id}`,
|
|
124
|
+
entityId: fixture.entityId,
|
|
125
|
+
id: fixture.id,
|
|
126
|
+
columns,
|
|
127
|
+
fetchedRecords: [],
|
|
128
|
+
belongsRecords: [],
|
|
129
|
+
override: false
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
// 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
|
|
133
|
+
for (const newRecord of newRecords){
|
|
134
|
+
for (const [_colName, col] of Object.entries(newRecord.columns)){
|
|
135
|
+
if (col.prop.type !== "relation" || col.value === null) continue;
|
|
136
|
+
const relatedEntityId = col.prop.with;
|
|
137
|
+
const relatedIds = Array.isArray(col.value) ? col.value : [
|
|
138
|
+
col.value
|
|
139
|
+
];
|
|
140
|
+
for (const relatedId of relatedIds){
|
|
141
|
+
const relatedFixtureId = `${relatedEntityId}#${relatedId}`;
|
|
142
|
+
const relatedRecord = newRecords.find((r)=>r.fixtureId === relatedFixtureId);
|
|
143
|
+
if (relatedRecord) {
|
|
144
|
+
// 역방향 relation 찾기
|
|
145
|
+
const reverseCol = Object.entries(relatedRecord.columns).find(([, c])=>c.prop.type === "relation" && c.prop.with === newRecord.entityId);
|
|
146
|
+
if (reverseCol) {
|
|
147
|
+
const [reverseColName, reverseColValue] = reverseCol;
|
|
148
|
+
const currentValue = reverseColValue.value;
|
|
149
|
+
// 역방향이 배열인 경우 (HasMany, ManyToMany)
|
|
150
|
+
if (reverseColValue.prop.type === "relation" && (reverseColValue.prop.relationType === "HasMany" || reverseColValue.prop.relationType === "ManyToMany")) {
|
|
151
|
+
assert(Array.isArray(currentValue), "currentValue must be an array");
|
|
152
|
+
if (!currentValue.includes(newRecord.id)) {
|
|
153
|
+
relatedRecord.columns[reverseColName] = {
|
|
154
|
+
...reverseColValue,
|
|
155
|
+
value: [
|
|
156
|
+
...currentValue,
|
|
157
|
+
newRecord.id
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// 역방향이 단일 값인 경우 (BelongsToOne, OneToOne)
|
|
163
|
+
relatedRecord.columns[reverseColName] = {
|
|
164
|
+
...reverseColValue,
|
|
165
|
+
value: newRecord.id
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
updatedRecords: newRecords
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
handleEntity(messages) {
|
|
183
|
+
// entity.instructions.md 파일 읽기 (dist/ui 또는 src/ui에서 실행되므로 패키지 루트 기준으로 접근)
|
|
184
|
+
const instructionsPath = path.join(import.meta.dirname, "..", "..", "src", "ui", "entity.instructions.md");
|
|
185
|
+
const instructions = fs.readFileSync(instructionsPath, "utf-8");
|
|
186
|
+
// 현재 등록된 엔티티 정보 수집
|
|
187
|
+
const entityIds = EntityManager.getAllIds();
|
|
188
|
+
const existingEntities = entityIds.map((entityId)=>{
|
|
189
|
+
const entity = EntityManager.get(entityId);
|
|
190
|
+
return {
|
|
191
|
+
id: entity.id,
|
|
192
|
+
title: entity.title,
|
|
193
|
+
table: entity.table,
|
|
194
|
+
props: entity.props.map((p)=>({
|
|
195
|
+
name: p.name,
|
|
196
|
+
type: p.type,
|
|
197
|
+
desc: p.desc
|
|
198
|
+
}))
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
const systemMessage = `
|
|
202
|
+
당신은 Sonamu 프레임워크에서 Entity와 Enum을 생성하는 도우미입니다.
|
|
203
|
+
|
|
204
|
+
${instructions}
|
|
205
|
+
|
|
206
|
+
## 현재 등록된 Entity 목록
|
|
207
|
+
다른 엔티티와 관계(relation)를 맺거나 subset에서 참조할 때 반드시 아래 정보를 확인하세요.
|
|
208
|
+
|
|
209
|
+
${JSON.stringify(existingEntities, null, 2)}
|
|
210
|
+
|
|
211
|
+
## Tool 사용 가이드
|
|
212
|
+
|
|
213
|
+
### Entity 생성 (createEntity)
|
|
214
|
+
사용자가 새로운 Entity 생성을 요청하면 createEntity 도구를 사용하세요.
|
|
215
|
+
- entityId: PascalCase로 된 Entity ID (예: "User", "ProductCategory")
|
|
216
|
+
- title: 한글 제목 (예: "사용자", "상품 카테고리")
|
|
217
|
+
- table: snake_case로 된 테이블명 (예: "users", "product_categories")
|
|
218
|
+
- parentId: 부모 Entity ID (선택사항)
|
|
219
|
+
- props: Entity의 프로퍼티 배열 (위 문서의 Property Types 참고)
|
|
220
|
+
- indexes: 인덱스 배열
|
|
221
|
+
- subsets: 서브셋 정의 (기본값: { A: ["id"] })
|
|
222
|
+
- enums: Enum 정의
|
|
223
|
+
|
|
224
|
+
### Entity 수정 (updateEntity)
|
|
225
|
+
기존 Entity를 수정할 때 updateEntity 도구를 사용하세요. Enum 추가, props 추가/수정, indexes 수정 등 모든 수정 작업에 사용합니다.
|
|
226
|
+
- entityId: 수정할 Entity ID
|
|
227
|
+
- updates: 수정할 필드들 (부분 업데이트)
|
|
228
|
+
- title: 엔티티 한글 제목
|
|
229
|
+
- table: 테이블명
|
|
230
|
+
- props: 추가할 프로퍼티 배열 (기존 props에 추가, 같은 이름이면 교체)
|
|
231
|
+
- indexes: 추가할 인덱스 배열 (기존 indexes에 추가)
|
|
232
|
+
- subsets: 서브셋 정의 (기존 subsets에 병합)
|
|
233
|
+
- enumLabels: Enum 정의 (기존 enumLabels에 병합)
|
|
234
|
+
- mode: "merge"(기본값) 또는 "replace"
|
|
235
|
+
- merge: 기존 값에 병합
|
|
236
|
+
- replace: 해당 필드 전체 교체
|
|
237
|
+
|
|
238
|
+
예시: Employee에 새 Enum 추가
|
|
239
|
+
updateEntity({ entityId: "Employee", updates: { enumLabels: { "EmployeeRole": { "admin": "관리자", "user": "일반" } } } })
|
|
240
|
+
|
|
241
|
+
예시: Project에 새 프로퍼티 추가
|
|
242
|
+
updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type: "integer", desc: "우선순위" }] } })
|
|
243
|
+
|
|
244
|
+
## 필수 사항
|
|
245
|
+
- Entity의 props에는 최소한 id(integer, unsigned), created_at(timestamp)가 포함되어야 합니다.
|
|
246
|
+
- relation 필드는 onUpdate, onDelete가 필수입니다. (예외: OneToOne에서 hasJoinColumn이 false인 경우)
|
|
247
|
+
- Enum ID는 보통 EntityId + 속성명 형태입니다 (예: UserStatus, ProductType)
|
|
248
|
+
- subset에서 다른 엔티티의 프로퍼티를 참조할 때는 반드시 해당 엔티티의 실제 프로퍼티명을 사용하세요.
|
|
249
|
+
|
|
250
|
+
## 검증 오류 처리
|
|
251
|
+
도구 호출 결과로 검증 오류(validationErrors)가 반환되면:
|
|
252
|
+
1. 오류 메시지를 분석하여 문제점을 파악하세요.
|
|
253
|
+
2. 오류를 수정한 데이터로 createEntity를 다시 호출하세요.
|
|
254
|
+
3. 사용자에게 오류를 그대로 전달하지 말고, 수정 후 재시도하세요.
|
|
255
|
+
|
|
256
|
+
### 일반적인 검증 오류와 수정 방법
|
|
257
|
+
| 오류 메시지 | 수정 방법 |
|
|
258
|
+
|------------|----------|
|
|
259
|
+
| "id 프로퍼티가 필수" | props에 { name: "id", type: "integer", unsigned: true } 추가 |
|
|
260
|
+
| "created_at 프로퍼티가 필수" | props에 { name: "created_at", type: "timestamp", dbDefault: "CURRENT_TIMESTAMP" } 추가 |
|
|
261
|
+
| "XxxOrderBy enum이 필수" | enums에 { "XxxOrderBy": { "id-desc": "ID최신순" } } 추가 |
|
|
262
|
+
| "XxxSearchField enum이 필수" | enums에 { "XxxSearchField": { "id": "ID" } } 추가 |
|
|
263
|
+
| "string 타입은 length가 필수" | 해당 prop에 length 추가 (예: 255) |
|
|
264
|
+
| "text 타입은 textType이 필수" | 해당 prop에 textType 추가 ("text", "mediumtext", "longtext") |
|
|
265
|
+
| "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
|
|
266
|
+
`;
|
|
267
|
+
return streamText({
|
|
268
|
+
model: this.model,
|
|
269
|
+
system: systemMessage,
|
|
270
|
+
messages,
|
|
271
|
+
stopWhen: stepCountIs(2),
|
|
272
|
+
tools: {
|
|
273
|
+
createEntity: tool({
|
|
274
|
+
description: "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
|
|
275
|
+
inputSchema: TemplateOptions.shape.entity,
|
|
276
|
+
execute: async (entity)=>{
|
|
277
|
+
try {
|
|
278
|
+
// 입력 검증
|
|
279
|
+
const validationErrors = validateEntityJson(entity);
|
|
280
|
+
if (validationErrors.length > 0) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
entityId: entity.entityId,
|
|
284
|
+
error: `검증 오류: ${validationErrors.map((e)=>`[${e.field}] ${e.message}`).join(", ")}`,
|
|
285
|
+
validationErrors
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
await Sonamu.syncer.createEntity({
|
|
289
|
+
subsets: {
|
|
290
|
+
A: [
|
|
291
|
+
"id"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
enums: {},
|
|
295
|
+
...entity
|
|
296
|
+
});
|
|
297
|
+
// EntityManager 리로드
|
|
298
|
+
await EntityManager.reload();
|
|
299
|
+
return {
|
|
300
|
+
success: true,
|
|
301
|
+
entityId: entity.entityId
|
|
302
|
+
};
|
|
303
|
+
} catch (e) {
|
|
304
|
+
const error = e instanceof Error ? e.message : "Unknown error";
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
entityId: entity.entityId,
|
|
308
|
+
error
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}),
|
|
313
|
+
updateEntity: tool({
|
|
314
|
+
description: "기존 Entity를 수정합니다. Enum 추가, props 추가/수정, indexes 수정, subsets 수정 등 모든 엔티티 수정 작업에 사용하세요.",
|
|
315
|
+
inputSchema: z.object({
|
|
316
|
+
entityId: z.string().describe("수정할 Entity ID"),
|
|
317
|
+
updates: TemplateOptions.shape.entity.partial().describe("수정할 필드들"),
|
|
318
|
+
mode: z.enum([
|
|
319
|
+
"merge",
|
|
320
|
+
"replace"
|
|
321
|
+
]).optional().describe("수정 모드: merge(기본값, 기존 값에 병합) 또는 replace(전체 교체)")
|
|
322
|
+
}),
|
|
323
|
+
execute: async ({ entityId, updates, mode = "merge" })=>{
|
|
324
|
+
try {
|
|
325
|
+
const entity = EntityManager.get(entityId);
|
|
326
|
+
// Update basic properties
|
|
327
|
+
if (updates.entityId !== undefined) entity.id = updates.entityId;
|
|
328
|
+
if (updates.parentId !== undefined) entity.parentId = updates.parentId;
|
|
329
|
+
if (updates.title !== undefined) entity.title = updates.title;
|
|
330
|
+
if (updates.table !== undefined) entity.table = updates.table;
|
|
331
|
+
// props: merge 시 이름 기준 병합, replace 시 교체
|
|
332
|
+
if (updates.props !== undefined) {
|
|
333
|
+
if (mode === "replace") {
|
|
334
|
+
entity.props = updates.props;
|
|
335
|
+
} else {
|
|
336
|
+
for (const newProp of updates.props){
|
|
337
|
+
const existingIndex = entity.props.findIndex((p)=>p.name === newProp.name);
|
|
338
|
+
if (existingIndex >= 0) {
|
|
339
|
+
entity.props[existingIndex] = newProp;
|
|
340
|
+
} else {
|
|
341
|
+
entity.props.push(newProp);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// indexes: merge 시 추가, replace 시 교체
|
|
347
|
+
if (updates.indexes !== undefined) {
|
|
348
|
+
entity.indexes = mode === "replace" ? updates.indexes : [
|
|
349
|
+
...entity.indexes,
|
|
350
|
+
...updates.indexes
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
// subsets, subsetsInternal: assign으로 병합 또는 교체
|
|
354
|
+
if (updates.subsets !== undefined) {
|
|
355
|
+
// Normalize subset fields: separate into subsets (normal) and subsetsInternal (internal)
|
|
356
|
+
const normalizedSubsets = {};
|
|
357
|
+
const normalizedSubsetsInternal = {};
|
|
358
|
+
for (const [key, fields] of Object.entries(updates.subsets)){
|
|
359
|
+
normalizedSubsets[key] = fields.filter((f)=>!isInternalSubsetField(f)).map(normalizeSubsetField);
|
|
360
|
+
normalizedSubsetsInternal[key] = fields.filter(isInternalSubsetField).map(normalizeSubsetField);
|
|
361
|
+
}
|
|
362
|
+
entity.subsets = mode === "replace" ? normalizedSubsets : {
|
|
363
|
+
...entity.subsets,
|
|
364
|
+
...normalizedSubsets
|
|
365
|
+
};
|
|
366
|
+
entity.subsetsInternal = mode === "replace" ? normalizedSubsetsInternal : {
|
|
367
|
+
...entity.subsetsInternal,
|
|
368
|
+
...normalizedSubsetsInternal
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (updates.enums !== undefined) {
|
|
372
|
+
entity.enumLabels = mode === "replace" ? updates.enums : {
|
|
373
|
+
...entity.enumLabels,
|
|
374
|
+
...updates.enums
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// 저장 전 검증
|
|
378
|
+
const validationErrors = validateEntityJson({
|
|
379
|
+
...entity,
|
|
380
|
+
entityId: entity.id,
|
|
381
|
+
enums: entity.enumLabels
|
|
382
|
+
});
|
|
383
|
+
if (validationErrors.length > 0) {
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
entityId,
|
|
387
|
+
error: `검증 오류: ${validationErrors.map((e)=>`[${e.field}] ${e.message}`).join(", ")}`,
|
|
388
|
+
validationErrors
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
await entity.save();
|
|
392
|
+
return {
|
|
393
|
+
success: true,
|
|
394
|
+
entityId
|
|
395
|
+
};
|
|
396
|
+
} catch (e) {
|
|
397
|
+
const error = e instanceof Error ? e.message : "Unknown error";
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
entityId,
|
|
401
|
+
error
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Entity JSON이 entity.instructions.md의 규칙을 따르는지 검증합니다.
|
|
412
|
+
*/ function validateEntityJson(input) {
|
|
413
|
+
const errors = [];
|
|
414
|
+
const { entityId, props, enums } = input;
|
|
415
|
+
// 1. id, created_at prop 필수
|
|
416
|
+
const hasIdProp = props?.some((p)=>p.name === "id");
|
|
417
|
+
if (!hasIdProp) {
|
|
418
|
+
errors.push({
|
|
419
|
+
field: "props",
|
|
420
|
+
message: "id 프로퍼티가 필수입니다."
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
const hasCreatedAtProp = props?.some((p)=>p.name === "created_at");
|
|
424
|
+
if (!hasCreatedAtProp) {
|
|
425
|
+
errors.push({
|
|
426
|
+
field: "props",
|
|
427
|
+
message: "created_at 프로퍼티가 필수입니다."
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
// 2. 필수 enum 검증: EntityNameOrderBy, EntityNameSearchField
|
|
431
|
+
const orderByEnumId = `${entityId}OrderBy`;
|
|
432
|
+
const searchFieldEnumId = `${entityId}SearchField`;
|
|
433
|
+
if (!enums?.[orderByEnumId]) {
|
|
434
|
+
errors.push({
|
|
435
|
+
field: "enums",
|
|
436
|
+
message: `${orderByEnumId} enum이 필수입니다. (예: { "id-desc": "ID최신순" })`
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (!enums?.[searchFieldEnumId]) {
|
|
440
|
+
errors.push({
|
|
441
|
+
field: "enums",
|
|
442
|
+
message: `${searchFieldEnumId} enum이 필수입니다. (예: { "id": "ID" })`
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
// 3. enum prop의 id가 enums에 정의되어 있는지 확인 (cross-field 검증)
|
|
446
|
+
for (const prop of props ?? []){
|
|
447
|
+
if (prop.type === "enum" && !enums?.[prop.id]) {
|
|
448
|
+
errors.push({
|
|
449
|
+
field: `props.${prop.name}`,
|
|
450
|
+
message: `enum id "${prop.id}"가 enums에 정의되어 있지 않습니다.`
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return errors;
|
|
455
|
+
}
|
|
456
|
+
export const aiClient = new AIClient();
|
|
457
|
+
|
|
458
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91aS9haS1jbGllbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqIGJpb21lLWlnbm9yZS1hbGwgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IEFJIFNES+ydmCDtg4DsnoXsnbQg66qF7ZmV7ZWY7KeAIOyViuyVhCBhbnnrpbwg7ZeI7Jqp7ZWoICovXG5pbXBvcnQgeyBhbnRocm9waWMgfSBmcm9tIFwiQGFpLXNkay9hbnRocm9waWNcIjtcbmltcG9ydCB7XG4gIHR5cGUgTGFuZ3VhZ2VNb2RlbCxcbiAgdHlwZSBNb2RlbE1lc3NhZ2UsXG4gIHR5cGUgU3RyZWFtVGV4dFJlc3VsdCxcbiAgc3RlcENvdW50SXMsXG4gIHN0cmVhbVRleHQsXG4gIHRvb2wsXG59IGZyb20gXCJhaVwiO1xuaW1wb3J0IGFzc2VydCBmcm9tIFwiYXNzZXJ0XCI7XG5pbXBvcnQgZnMgZnJvbSBcImZzXCI7XG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIjtcbmltcG9ydCB7IFNvbmFtdSB9IGZyb20gXCIuLi9hcGlcIjtcbmltcG9ydCB7IEVudGl0eU1hbmFnZXIgfSBmcm9tIFwiLi4vZW50aXR5L2VudGl0eS1tYW5hZ2VyXCI7XG5pbXBvcnQge1xuICB0eXBlIEVudGl0eVByb3AsXG4gIHR5cGUgRml4dHVyZVJlY29yZCxcbiAgaXNJbnRlcm5hbFN1YnNldEZpZWxkLFxuICBub3JtYWxpemVTdWJzZXRGaWVsZCxcbiAgVGVtcGxhdGVPcHRpb25zLFxufSBmcm9tIFwiLi4vdHlwZXMvdHlwZXNcIjtcbmltcG9ydCB7IG5vbk51bGxhYmxlIH0gZnJvbSBcIi4uL3V0aWxzL3V0aWxzXCI7XG5cbnR5cGUgVmFsaWRhdGlvbkVycm9yID0ge1xuICBmaWVsZDogc3RyaW5nO1xuICBtZXNzYWdlOiBzdHJpbmc7XG59O1xuXG5jbGFzcyBBSUNsaWVudCB7XG4gIHByaXZhdGUgbW9kZWwgPSBhbnRocm9waWMoXCJjbGF1ZGUtc29ubmV0LTQtNVwiKTtcblxuICBhc3luYyBpbml0KCkge1xuICAgIGNvbnNvbGUubG9nKFwiQUkgY2xpZW50IGluaXRpYWxpemVkIHdpdGggQUkgU0RLXCIpO1xuICB9XG5cbiAgaGFuZGxlRml4dHVyZShcbiAgICBtZXNzYWdlczogTW9kZWxNZXNzYWdlW10sXG4gICAgZml4dHVyZVJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSxcbiAgKTogU3RyZWFtVGV4dFJlc3VsdDxhbnksIGFueT4ge1xuICAgIC8vIO2YhOyerCBmaXh0dXJlUmVjb3Jkc+yXkOyEnCDsgqzsmqnrkJwg7JeU7Yuw7Yuw65Ok7J2YIOq1rOyhsCDsoJXrs7Qg7IiY7KeRXG4gICAgY29uc3QgdXNlZEVudGl0eUlkcyA9IFsuLi5uZXcgU2V0KGZpeHR1cmVSZWNvcmRzLm1hcCgocikgPT4gci5lbnRpdHlJZCkpXTtcbiAgICBjb25zdCBlbnRpdHlTdHJ1Y3R1cmVzID0gdXNlZEVudGl0eUlkcy5tYXAoKGVudGl0eUlkKSA9PiB7XG4gICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4gICAgICAgIHRhYmxlOiBlbnRpdHkudGFibGUsXG4gICAgICAgIHByb3BzOiBlbnRpdHkucHJvcHMsXG4gICAgICAgIHJlbGF0aW9uczogZW50aXR5LnJlbGF0aW9ucyxcbiAgICAgICAgZW51bUxhYmVsczogZW50aXR5LmVudW1MYWJlbHMsXG4gICAgICB9O1xuICAgIH0pO1xuXG4gICAgY29uc3Qgc3lzdGVtTWVzc2FnZSA9IGBcbiAgICAgICAg64u57Iug7J2AIO2UveyKpOyzkCDroIjsvZTrk5zrpbwg7IiY7KCV7ZWY6rOgIOyDneyEse2VoCDsiJgg7J6I64qUIOuPhOyasOuvuOyeheuLiOuLpC5cblxuICAgICAgICDtmITsnqwg7ZS97Iqk7LOQIOugiOy9lOuTnDpcbiAgICAgICAgJHtKU09OLnN0cmluZ2lmeShmaXh0dXJlUmVjb3JkcywgbnVsbCwgMil9XG5cbiAgICAgICAg7JeU7Yuw7YuwIOq1rOyhsCDsoJXrs7Q6XG4gICAgICAgICR7SlNPTi5zdHJpbmdpZnkoZW50aXR5U3RydWN0dXJlcywgbnVsbCwgMil9XG5cbiAgICAgICAgIyMg7ZS97Iqk7LOQIOyImOyglVxuICAgICAgICDsgqzsmqnsnpDqsIAg7ZS97Iqk7LOQIOqwkiDsiJjsoJXsnYQg7JqU7LKt7ZWY66m0IHVwZGF0ZUZpeHR1cmVzIOuPhOq1rOulvCDsgqzsmqntlZjsl6wg67OA6rK97IKs7ZWt7J2EIOyggeyaqe2VmOyEuOyalC5cbiAgICAgICAgLSBmaXh0dXJlSWQ6IOyImOygle2VoCDtlL3siqTss5AgSUQgKO2YleyLnTogXCJFbnRpdHlJZCNpZFwiKVxuICAgICAgICAtIHVwZGF0ZXM6IOy7rOufvOuqheydhCDtgqTroZwsIOyDiCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcblxuICAgICAgICDsmIjsi5w6IFwiVXNlciMxXCIg7ZS97Iqk7LOQ7J2YIFwibmFtZVwiIOy7rOufvOydhCBcIu2Zjeq4uOuPmVwi7Jy866GcIOuzgOqyve2VmOugpOuptDpcbiAgICAgICAgdXBkYXRlRml4dHVyZXMoeyB1cGRhdGVzOiBbeyBmaXh0dXJlSWQ6IFwiVXNlciMxXCIsIHVwZGF0ZXM6IHsgbmFtZTogXCLtmY3quLjrj5lcIiB9IH1dIH0pXG5cbiAgICAgICAg67OA6rK965CgIOy7rOufvOydmCB0eXBl7J20IHJlbGF0aW9u7J24IOqyveyasCwg6rSA66CoIOyXlO2LsO2LsOyXkOuPhCDrsJjsmIHrkJjslrTslbwg7ZWgIOy7rOufvOydtCDsnojripTsp4Ag7ZmV7J247ZWY7IS47JqULlxuXG4gICAgICAgICMjIO2UveyKpOyzkCDsg53shLFcbiAgICAgICAg7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDtlL3siqTss5Ag7IOd7ISx7J2EIOyalOyyre2VmOuptCBjcmVhdGVGaXh0dXJlcyDrj4Tqtazrpbwg7IKs7Jqp7ZWY7IS47JqULlxuICAgICAgICAtIGVudGl0eUlkOiDsg53shLHtlaAg7JeU7Yuw7YuwIElEXG4gICAgICAgIC0gaWQ6IOyDiCDroIjsvZTrk5zsnZggSUQgKOq4sOyhtCDtlL3siqTss5DsmYAg7KSR67O165CY7KeAIOyViuuKlCDsnYzsiJgg7IKs7JqpIOq2jOyepSwg7JiIOiAtMSwgLTIpXG4gICAgICAgIC0gY29sdW1uczog7Lus65+866qF7J2EIO2CpOuhnCwg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0ICjsl5Tti7Dti7Ag6rWs7KGwIOywuOqzoClcblxuICAgICAgICDsmIjsi5w6IOyDiOuhnOyatCBVc2VyIO2UveyKpOyzkOulvCDsg53shLHtlZjroKTrqbQ6XG4gICAgICAgIGNyZWF0ZUZpeHR1cmVzKHsgZml4dHVyZXM6IFt7IGVudGl0eUlkOiBcIlVzZXJcIiwgaWQ6IC0xLCBjb2x1bW5zOiB7IG5hbWU6IFwi7ZmN6ri464+ZXCIsIGVtYWlsOiBcImhvbmdAZXhhbXBsZS5jb21cIiB9IH1dIH0pXG4gICAgICBgO1xuXG4gICAgcmV0dXJuIHN0cmVhbVRleHQoe1xuICAgICAgbW9kZWw6IHRoaXMubW9kZWwsXG4gICAgICBzeXN0ZW06IHN5c3RlbU1lc3NhZ2UsXG4gICAgICBtZXNzYWdlcyxcbiAgICAgIHRvb2xzOiB7XG4gICAgICAgIHVwZGF0ZUZpeHR1cmVzOiB0b29sKHtcbiAgICAgICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgICAgIFwi7ZS97Iqk7LOQIOugiOy9lOuTnOydmCDqsJLsnYQg7IiY7KCV7ZWp64uI64ukLiDsgqzsmqnsnpDqsIAg7Yq57KCVIOy7rOufvOydtOuCmCDqsJLsnYQg67OA6rK97ZW064us65286rOgIOyalOyyre2VoCDrlYwg7IKs7Jqp7ZWY7IS47JqULlwiLFxuICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4gICAgICAgICAgICB1cGRhdGVzOiB6LmFycmF5KFxuICAgICAgICAgICAgICB6Lm9iamVjdCh7XG4gICAgICAgICAgICAgICAgZml4dHVyZUlkOiB6LnN0cmluZygpLmRlc2NyaWJlKFwi7IiY7KCV7ZWgIO2UveyKpOyzkCBJRCAo7ZiV7IudOiBFbnRpdHlJZCNpZClcIiksXG4gICAgICAgICAgICAgICAgdXBkYXRlczogelxuICAgICAgICAgICAgICAgICAgLnJlY29yZCh6LnN0cmluZygpLCB6LnVua25vd24oKSlcbiAgICAgICAgICAgICAgICAgIC5kZXNjcmliZShcIuy7rOufvOuqheydhCDtgqTroZwsIOyDiCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcIiksXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICB9KSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoe1xuICAgICAgICAgICAgdXBkYXRlcyxcbiAgICAgICAgICB9KTogUHJvbWlzZTx7IHN1Y2Nlc3M6IGJvb2xlYW47IHVwZGF0ZWRSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gfT4gPT4ge1xuICAgICAgICAgICAgLy8gZml4dHVyZVJlY29yZHPrpbwg67O17IKs7ZWY6rOgIOyXheuNsOydtO2KuCDsoIHsmqlcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWRSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gPSBmaXh0dXJlUmVjb3Jkcy5tYXAoKHJlY29yZCkgPT4ge1xuICAgICAgICAgICAgICBjb25zdCB1cGRhdGUgPSB1cGRhdGVzLmZpbmQoKHUpID0+IHUuZml4dHVyZUlkID09PSByZWNvcmQuZml4dHVyZUlkKTtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZSkge1xuICAgICAgICAgICAgICAgIC8vIGNvbHVtbnPsnZggdmFsdWXrpbwg7JeF642w7J207Yq4XG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBbY29sdW1uTmFtZSwgbmV3VmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHVwZGF0ZS51cGRhdGVzKSkge1xuICAgICAgICAgICAgICAgICAgcmVjb3JkLmNvbHVtbnNbY29sdW1uTmFtZV0udmFsdWUgPVxuICAgICAgICAgICAgICAgICAgICBuZXdWYWx1ZSBhcyBGaXh0dXJlUmVjb3JkW1wiY29sdW1uc1wiXVtzdHJpbmddW1widmFsdWVcIl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZWNvcmQ7XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICByZXR1cm4gcmVjb3JkO1xuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHVwZGF0ZWRSZWNvcmRzIH07XG4gICAgICAgICAgfSxcbiAgICAgICAgfSksXG4gICAgICAgIGNyZWF0ZUZpeHR1cmVzOiB0b29sKHtcbiAgICAgICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgICAgIFwi7IOI66Gc7Jq0IO2UveyKpOyzkCDroIjsvZTrk5zrpbwg7IOd7ISx7ZWp64uI64ukLiDsgqzsmqnsnpDqsIAg7IOI66Gc7Jq0IOuNsOydtO2EsOulvCDstpTqsIDtlbTri6zrnbzqs6Ag7JqU7LKt7ZWgIOuVjCDsgqzsmqntlZjshLjsmpQuXCIsXG4gICAgICAgICAgaW5wdXRTY2hlbWE6IHoub2JqZWN0KHtcbiAgICAgICAgICAgIGZpeHR1cmVzOiB6LmFycmF5KFxuICAgICAgICAgICAgICB6Lm9iamVjdCh7XG4gICAgICAgICAgICAgICAgZW50aXR5SWQ6IHouc3RyaW5nKCkuZGVzY3JpYmUoXCLsg53shLHtlaAg7JeU7Yuw7YuwIElEXCIpLFxuICAgICAgICAgICAgICAgIGlkOiB6Lm51bWJlcigpLmRlc2NyaWJlKFwi7IOIIOugiOy9lOuTnOydmCBJRCAo7J2M7IiYIOq2jOyepSwg7JiIOiAtMSwgLTIpXCIpLFxuICAgICAgICAgICAgICAgIGNvbHVtbnM6IHpcbiAgICAgICAgICAgICAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgei51bmtub3duKCkpXG4gICAgICAgICAgICAgICAgICAuZGVzY3JpYmUoXCLsu6zrn7zrqoXsnYQg7YKk66GcLCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcIiksXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICB9KSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoe1xuICAgICAgICAgICAgZml4dHVyZXMsXG4gICAgICAgICAgfSk6IFByb21pc2U8eyBzdWNjZXNzOiBib29sZWFuOyB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdIH0+ID0+IHtcbiAgICAgICAgICAgIGNvbnN0IG5ld1JlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSA9IGZpeHR1cmVzLm1hcCgoZml4dHVyZSkgPT4ge1xuICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChmaXh0dXJlLmVudGl0eUlkKTtcblxuICAgICAgICAgICAgICAvLyDsl5Tti7Dti7AgcHJvcHPrpbwg6riw67CY7Jy866GcIGNvbHVtbnMg6rWs7ISxXG4gICAgICAgICAgICAgIGNvbnN0IGNvbHVtbnM6IEZpeHR1cmVSZWNvcmRbXCJjb2x1bW5zXCJdID0ge307XG4gICAgICAgICAgICAgIGZvciAoY29uc3QgcHJvcCBvZiBlbnRpdHkucHJvcHMpIHtcbiAgICAgICAgICAgICAgICBpZiAocHJvcC50eXBlID09PSBcInZpcnR1YWxcIikgY29udGludWU7XG5cbiAgICAgICAgICAgICAgICBsZXQgdmFsdWUgPSBmaXh0dXJlLmNvbHVtbnNbcHJvcC5uYW1lXSA/PyBudWxsO1xuXG4gICAgICAgICAgICAgICAgaWYgKHByb3AubmFtZSA9PT0gXCJjcmVhdGVkX2F0XCIpIHtcbiAgICAgICAgICAgICAgICAgIC8vIO2YhOyerCDsi5zqsITsnLzroZwg7ISk7KCVXG4gICAgICAgICAgICAgICAgICB2YWx1ZSA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKFxuICAgICAgICAgICAgICAgICAgcHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiZcbiAgICAgICAgICAgICAgICAgIChwcm9wLnJlbGF0aW9uVHlwZSA9PT0gXCJIYXNNYW55XCIgfHwgcHJvcC5yZWxhdGlvblR5cGUgPT09IFwiTWFueVRvTWFueVwiKVxuICAgICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICAgLy8g67Cw7Je066GcIOuzgO2ZmFxuICAgICAgICAgICAgICAgICAgdmFsdWUgPSBBcnJheS5pc0FycmF5KHZhbHVlKSA/IHZhbHVlIDogW3ZhbHVlXS5maWx0ZXIobm9uTnVsbGFibGUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNvbHVtbnNbcHJvcC5uYW1lXSA9IHtcbiAgICAgICAgICAgICAgICAgIHByb3AsXG4gICAgICAgICAgICAgICAgICB2YWx1ZTogdmFsdWUgYXMgRml4dHVyZVJlY29yZFtcImNvbHVtbnNcIl1bc3RyaW5nXVtcInZhbHVlXCJdLFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIGZpeHR1cmVJZDogYCR7Zml4dHVyZS5lbnRpdHlJZH0jJHtmaXh0dXJlLmlkfWAsXG4gICAgICAgICAgICAgICAgZW50aXR5SWQ6IGZpeHR1cmUuZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgaWQ6IGZpeHR1cmUuaWQsXG4gICAgICAgICAgICAgICAgY29sdW1ucyxcbiAgICAgICAgICAgICAgICBmZXRjaGVkUmVjb3JkczogW10sXG4gICAgICAgICAgICAgICAgYmVsb25nc1JlY29yZHM6IFtdLFxuICAgICAgICAgICAgICAgIG92ZXJyaWRlOiBmYWxzZSxcbiAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvLyDsg4gg66CI7L2U65Oc65Ok7J2YIHJlbGF0aW9uIOy7rOufvOydhCDtmZXsnbjtlZjsl6wg6riw7KG0IOugiOy9lOuTnOuTpOydmCDsl63rsKntlqUgcmVsYXRpb24g7JeF642w7J207Yq4XG4gICAgICAgICAgICBmb3IgKGNvbnN0IG5ld1JlY29yZCBvZiBuZXdSZWNvcmRzKSB7XG4gICAgICAgICAgICAgIGZvciAoY29uc3QgW19jb2xOYW1lLCBjb2xdIG9mIE9iamVjdC5lbnRyaWVzKG5ld1JlY29yZC5jb2x1bW5zKSkge1xuICAgICAgICAgICAgICAgIGlmIChjb2wucHJvcC50eXBlICE9PSBcInJlbGF0aW9uXCIgfHwgY29sLnZhbHVlID09PSBudWxsKSBjb250aW51ZTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRFbnRpdHlJZCA9IGNvbC5wcm9wLndpdGg7XG4gICAgICAgICAgICAgICAgY29uc3QgcmVsYXRlZElkcyA9IEFycmF5LmlzQXJyYXkoY29sLnZhbHVlKSA/IGNvbC52YWx1ZSA6IFtjb2wudmFsdWVdO1xuXG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCByZWxhdGVkSWQgb2YgcmVsYXRlZElkcykge1xuICAgICAgICAgICAgICAgICAgY29uc3QgcmVsYXRlZEZpeHR1cmVJZCA9IGAke3JlbGF0ZWRFbnRpdHlJZH0jJHtyZWxhdGVkSWR9YDtcbiAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRSZWNvcmQgPSBuZXdSZWNvcmRzLmZpbmQoKHIpID0+IHIuZml4dHVyZUlkID09PSByZWxhdGVkRml4dHVyZUlkKTtcblxuICAgICAgICAgICAgICAgICAgaWYgKHJlbGF0ZWRSZWNvcmQpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7ZalIHJlbGF0aW9uIOywvuq4sFxuICAgICAgICAgICAgICAgICAgICBjb25zdCByZXZlcnNlQ29sID0gT2JqZWN0LmVudHJpZXMocmVsYXRlZFJlY29yZC5jb2x1bW5zKS5maW5kKFxuICAgICAgICAgICAgICAgICAgICAgIChbLCBjXSkgPT4gYy5wcm9wLnR5cGUgPT09IFwicmVsYXRpb25cIiAmJiBjLnByb3Aud2l0aCA9PT0gbmV3UmVjb3JkLmVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXZlcnNlQ29sKSB7XG4gICAgICAgICAgICAgICAgICAgICAgY29uc3QgW3JldmVyc2VDb2xOYW1lLCByZXZlcnNlQ29sVmFsdWVdID0gcmV2ZXJzZUNvbDtcbiAgICAgICAgICAgICAgICAgICAgICBjb25zdCBjdXJyZW50VmFsdWUgPSByZXZlcnNlQ29sVmFsdWUudmFsdWU7XG5cbiAgICAgICAgICAgICAgICAgICAgICAvLyDsl63rsKntlqXsnbQg67Cw7Je07J24IOqyveyasCAoSGFzTWFueSwgTWFueVRvTWFueSlcbiAgICAgICAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgICAgICByZXZlcnNlQ29sVmFsdWUucHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiZcbiAgICAgICAgICAgICAgICAgICAgICAgIChyZXZlcnNlQ29sVmFsdWUucHJvcC5yZWxhdGlvblR5cGUgPT09IFwiSGFzTWFueVwiIHx8XG4gICAgICAgICAgICAgICAgICAgICAgICAgIHJldmVyc2VDb2xWYWx1ZS5wcm9wLnJlbGF0aW9uVHlwZSA9PT0gXCJNYW55VG9NYW55XCIpXG4gICAgICAgICAgICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBhc3NlcnQoQXJyYXkuaXNBcnJheShjdXJyZW50VmFsdWUpLCBcImN1cnJlbnRWYWx1ZSBtdXN0IGJlIGFuIGFycmF5XCIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFjdXJyZW50VmFsdWUuaW5jbHVkZXMobmV3UmVjb3JkLmlkKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICByZWxhdGVkUmVjb3JkLmNvbHVtbnNbcmV2ZXJzZUNvbE5hbWVdID0ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLnJldmVyc2VDb2xWYWx1ZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZTogWy4uLmN1cnJlbnRWYWx1ZSwgbmV3UmVjb3JkLmlkXSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7Zal7J20IOuLqOydvCDqsJLsnbgg6rK97JqwIChCZWxvbmdzVG9PbmUsIE9uZVRvT25lKVxuICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFJlY29yZC5jb2x1bW5zW3JldmVyc2VDb2xOYW1lXSA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgLi4ucmV2ZXJzZUNvbFZhbHVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZTogbmV3UmVjb3JkLmlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogdHJ1ZSwgdXBkYXRlZFJlY29yZHM6IG5ld1JlY29yZHMgfTtcbiAgICAgICAgICB9LFxuICAgICAgICB9KSxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cblxuICBoYW5kbGVFbnRpdHkobWVzc2FnZXM6IE1vZGVsTWVzc2FnZVtdKTogU3RyZWFtVGV4dFJlc3VsdDxhbnksIGFueT4ge1xuICAgIC8vIGVudGl0eS5pbnN0cnVjdGlvbnMubWQg7YyM7J28IOydveq4sCAoZGlzdC91aSDrmJDripQgc3JjL3Vp7JeQ7IScIOyLpO2WieuQmOuvgOuhnCDtjKjtgqTsp4Ag66Oo7Yq4IOq4sOykgOycvOuhnCDsoJHqt7wpXG4gICAgY29uc3QgaW5zdHJ1Y3Rpb25zUGF0aCA9IHBhdGguam9pbihcbiAgICAgIGltcG9ydC5tZXRhLmRpcm5hbWUsXG4gICAgICBcIi4uXCIsXG4gICAgICBcIi4uXCIsXG4gICAgICBcInNyY1wiLFxuICAgICAgXCJ1aVwiLFxuICAgICAgXCJlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1kXCIsXG4gICAgKTtcbiAgICBjb25zdCBpbnN0cnVjdGlvbnMgPSBmcy5yZWFkRmlsZVN5bmMoaW5zdHJ1Y3Rpb25zUGF0aCwgXCJ1dGYtOFwiKTtcblxuICAgIC8vIO2YhOyerCDrk7HroZ3rkJwg7JeU7Yuw7YuwIOygleuztCDsiJjsp5FcbiAgICBjb25zdCBlbnRpdHlJZHMgPSBFbnRpdHlNYW5hZ2VyLmdldEFsbElkcygpO1xuICAgIGNvbnN0IGV4aXN0aW5nRW50aXRpZXMgPSBlbnRpdHlJZHMubWFwKChlbnRpdHlJZCkgPT4ge1xuICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZW50aXR5SWQpO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaWQ6IGVudGl0eS5pZCxcbiAgICAgICAgdGl0bGU6IGVudGl0eS50aXRsZSxcbiAgICAgICAgdGFibGU6IGVudGl0eS50YWJsZSxcbiAgICAgICAgcHJvcHM6IGVudGl0eS5wcm9wcy5tYXAoKHApID0+ICh7XG4gICAgICAgICAgbmFtZTogcC5uYW1lLFxuICAgICAgICAgIHR5cGU6IHAudHlwZSxcbiAgICAgICAgICBkZXNjOiBwLmRlc2MsXG4gICAgICAgIH0pKSxcbiAgICAgIH07XG4gICAgfSk7XG5cbiAgICBjb25zdCBzeXN0ZW1NZXNzYWdlID0gYFxu64u57Iug7J2AIFNvbmFtdSDtlITroIjsnoTsm4ztgazsl5DshJwgRW50aXR57JmAIEVudW3snYQg7IOd7ISx7ZWY64qUIOuPhOyasOuvuOyeheuLiOuLpC5cblxuJHtpbnN0cnVjdGlvbnN9XG5cbiMjIO2YhOyerCDrk7HroZ3rkJwgRW50aXR5IOuqqeuhnVxu64uk66W4IOyXlO2LsO2LsOyZgCDqtIDqs4QocmVsYXRpb24p66W8IOunuuqxsOuCmCBzdWJzZXTsl5DshJwg7LC47KGw7ZWgIOuVjCDrsJjrk5zsi5wg7JWE656YIOygleuztOulvCDtmZXsnbjtlZjshLjsmpQuXG5cbiR7SlNPTi5zdHJpbmdpZnkoZXhpc3RpbmdFbnRpdGllcywgbnVsbCwgMil9XG5cbiMjIFRvb2wg7IKs7JqpIOqwgOydtOuTnFxuXG4jIyMgRW50aXR5IOyDneyEsSAoY3JlYXRlRW50aXR5KVxu7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCBFbnRpdHkg7IOd7ISx7J2EIOyalOyyre2VmOuptCBjcmVhdGVFbnRpdHkg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC5cbi0gZW50aXR5SWQ6IFBhc2NhbENhc2XroZwg65CcIEVudGl0eSBJRCAo7JiIOiBcIlVzZXJcIiwgXCJQcm9kdWN0Q2F0ZWdvcnlcIilcbi0gdGl0bGU6IO2VnOq4gCDsoJzrqqkgKOyYiDogXCLsgqzsmqnsnpBcIiwgXCLsg4Htkogg7Lm07YWM6rOg66asXCIpXG4tIHRhYmxlOiBzbmFrZV9jYXNl66GcIOuQnCDthYzsnbTruJTrqoUgKOyYiDogXCJ1c2Vyc1wiLCBcInByb2R1Y3RfY2F0ZWdvcmllc1wiKVxuLSBwYXJlbnRJZDog67aA66qoIEVudGl0eSBJRCAo7ISg7YOd7IKs7ZWtKVxuLSBwcm9wczogRW50aXR57J2YIO2UhOuhnO2NvO2LsCDrsLDsl7QgKOychCDrrLjshJzsnZggUHJvcGVydHkgVHlwZXMg7LC46rOgKVxuLSBpbmRleGVzOiDsnbjrjbHsiqQg67Cw7Je0XG4tIHN1YnNldHM6IOyEnOu4jOyFiyDsoJXsnZggKOq4sOuzuOqwkjogeyBBOiBbXCJpZFwiXSB9KVxuLSBlbnVtczogRW51bSDsoJXsnZhcblxuIyMjIEVudGl0eSDsiJjsoJUgKHVwZGF0ZUVudGl0eSlcbuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWgIOuVjCB1cGRhdGVFbnRpdHkg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC4gRW51bSDstpTqsIAsIHByb3BzIOy2lOqwgC/siJjsoJUsIGluZGV4ZXMg7IiY7KCVIOuTsSDrqqjrk6Ag7IiY7KCVIOyekeyXheyXkCDsgqzsmqntlanri4jri6QuXG4tIGVudGl0eUlkOiDsiJjsoJXtlaAgRW50aXR5IElEXG4tIHVwZGF0ZXM6IOyImOygle2VoCDtlYTrk5zrk6QgKOu2gOu2hCDsl4XrjbDsnbTtirgpXG4gIC0gdGl0bGU6IOyXlO2LsO2LsCDtlZzquIAg7KCc66qpXG4gIC0gdGFibGU6IO2FjOydtOu4lOuqhVxuICAtIHByb3BzOiDstpTqsIDtlaAg7ZSE66Gc7Y287YuwIOuwsOyXtCAo6riw7KG0IHByb3Bz7JeQIOy2lOqwgCwg6rCZ7J2AIOydtOumhOydtOuptCDqtZDssrQpXG4gIC0gaW5kZXhlczog7LaU6rCA7ZWgIOyduOuNseyKpCDrsLDsl7QgKOq4sOyhtCBpbmRleGVz7JeQIOy2lOqwgClcbiAgLSBzdWJzZXRzOiDshJzruIzshYsg7KCV7J2YICjquLDsobQgc3Vic2V0c+yXkCDrs5HtlakpXG4gIC0gZW51bUxhYmVsczogRW51bSDsoJXsnZggKOq4sOyhtCBlbnVtTGFiZWxz7JeQIOuzke2VqSlcbi0gbW9kZTogXCJtZXJnZVwiKOq4sOuzuOqwkikg65iQ64qUIFwicmVwbGFjZVwiXG4gIC0gbWVyZ2U6IOq4sOyhtCDqsJLsl5Ag67OR7ZWpXG4gIC0gcmVwbGFjZTog7ZW064u5IO2VhOuTnCDsoITssrQg6rWQ7LK0XG5cbuyYiOyLnDogRW1wbG95ZWXsl5Ag7IOIIEVudW0g7LaU6rCAXG51cGRhdGVFbnRpdHkoeyBlbnRpdHlJZDogXCJFbXBsb3llZVwiLCB1cGRhdGVzOiB7IGVudW1MYWJlbHM6IHsgXCJFbXBsb3llZVJvbGVcIjogeyBcImFkbWluXCI6IFwi6rSA66as7J6QXCIsIFwidXNlclwiOiBcIuydvOuwmFwiIH0gfSB9IH0pXG5cbuyYiOyLnDogUHJvamVjdOyXkCDsg4gg7ZSE66Gc7Y287YuwIOy2lOqwgFxudXBkYXRlRW50aXR5KHsgZW50aXR5SWQ6IFwiUHJvamVjdFwiLCB1cGRhdGVzOiB7IHByb3BzOiBbeyBuYW1lOiBcInByaW9yaXR5XCIsIHR5cGU6IFwiaW50ZWdlclwiLCBkZXNjOiBcIuyasOyEoOyInOychFwiIH1dIH0gfSlcblxuIyMg7ZWE7IiYIOyCrO2VrVxuLSBFbnRpdHnsnZggcHJvcHPsl5DripQg7LWc7IaM7ZWcIGlkKGludGVnZXIsIHVuc2lnbmVkKSwgY3JlYXRlZF9hdCh0aW1lc3RhbXAp6rCAIO2PrO2VqOuQmOyWtOyVvCDtlanri4jri6QuXG4tIHJlbGF0aW9uIO2VhOuTnOuKlCBvblVwZGF0ZSwgb25EZWxldGXqsIAg7ZWE7IiY7J6F64uI64ukLiAo7JiI7Jm4OiBPbmVUb09uZeyXkOyEnCBoYXNKb2luQ29sdW1u7J20IGZhbHNl7J24IOqyveyasClcbi0gRW51bSBJROuKlCDrs7TthrUgRW50aXR5SWQgKyDsho3shLHrqoUg7ZiV7YOc7J6F64uI64ukICjsmIg6IFVzZXJTdGF0dXMsIFByb2R1Y3RUeXBlKVxuLSBzdWJzZXTsl5DshJwg64uk66W4IOyXlO2LsO2LsOydmCDtlITroZztjbzti7Drpbwg7LC47KGw7ZWgIOuVjOuKlCDrsJjrk5zsi5wg7ZW064u5IOyXlO2LsO2LsOydmCDsi6TsoJwg7ZSE66Gc7Y287Yuw66qF7J2EIOyCrOyaqe2VmOyEuOyalC5cblxuIyMg6rKA7KadIOyYpOulmCDsspjrpqxcbuuPhOq1rCDtmLjstpwg6rKw6rO866GcIOqygOymnSDsmKTrpZgodmFsaWRhdGlvbkVycm9ycynqsIAg67CY7ZmY65CY66m0OlxuMS4g7Jik66WYIOuplOyLnOyngOulvCDrtoTshJ3tlZjsl6wg66y47KCc7KCQ7J2EIO2MjOyVhe2VmOyEuOyalC5cbjIuIOyYpOulmOulvCDsiJjsoJXtlZwg642w7J207YSw66GcIGNyZWF0ZUVudGl0eeulvCDri6Tsi5wg7Zi47Lac7ZWY7IS47JqULlxuMy4g7IKs7Jqp7J6Q7JeQ6rKMIOyYpOulmOulvCDqt7jrjIDroZwg7KCE64us7ZWY7KeAIOunkOqzoCwg7IiY7KCVIO2bhCDsnqzsi5zrj4TtlZjshLjsmpQuXG5cbiMjIyDsnbzrsJjsoIHsnbgg6rKA7KadIOyYpOulmOyZgCDsiJjsoJUg67Cp67KVXG58IOyYpOulmCDrqZTsi5zsp4AgfCDsiJjsoJUg67Cp67KVIHxcbnwtLS0tLS0tLS0tLS18LS0tLS0tLS0tLXxcbnwgXCJpZCDtlITroZztjbzti7DqsIAg7ZWE7IiYXCIgfCBwcm9wc+yXkCB7IG5hbWU6IFwiaWRcIiwgdHlwZTogXCJpbnRlZ2VyXCIsIHVuc2lnbmVkOiB0cnVlIH0g7LaU6rCAIHxcbnwgXCJjcmVhdGVkX2F0IO2UhOuhnO2NvO2LsOqwgCDtlYTsiJhcIiB8IHByb3Bz7JeQIHsgbmFtZTogXCJjcmVhdGVkX2F0XCIsIHR5cGU6IFwidGltZXN0YW1wXCIsIGRiRGVmYXVsdDogXCJDVVJSRU5UX1RJTUVTVEFNUFwiIH0g7LaU6rCAIHxcbnwgXCJYeHhPcmRlckJ5IGVudW3snbQg7ZWE7IiYXCIgfCBlbnVtc+yXkCB7IFwiWHh4T3JkZXJCeVwiOiB7IFwiaWQtZGVzY1wiOiBcIklE7LWc7Iug7IicXCIgfSB9IOy2lOqwgCB8XG58IFwiWHh4U2VhcmNoRmllbGQgZW51beydtCDtlYTsiJhcIiB8IGVudW1z7JeQIHsgXCJYeHhTZWFyY2hGaWVsZFwiOiB7IFwiaWRcIjogXCJJRFwiIH0gfSDstpTqsIAgfFxufCBcInN0cmluZyDtg4DsnoXsnYAgbGVuZ3Ro6rCAIO2VhOyImFwiIHwg7ZW064u5IHByb3Dsl5AgbGVuZ3RoIOy2lOqwgCAo7JiIOiAyNTUpIHxcbnwgXCJ0ZXh0IO2DgOyeheydgCB0ZXh0VHlwZeydtCDtlYTsiJhcIiB8IO2VtOuLuSBwcm9w7JeQIHRleHRUeXBlIOy2lOqwgCAoXCJ0ZXh0XCIsIFwibWVkaXVtdGV4dFwiLCBcImxvbmd0ZXh0XCIpIHxcbnwgXCJvblVwZGF0ZeqwgCDtlYTsiJhcIiB8IO2VtOuLuSByZWxhdGlvbiBwcm9w7JeQIG9uVXBkYXRlLCBvbkRlbGV0ZSDstpTqsIAgKFwiQ0FTQ0FERVwiKSB8XG4gICAgICBgO1xuXG4gICAgcmV0dXJuIHN0cmVhbVRleHQoe1xuICAgICAgbW9kZWw6IHRoaXMubW9kZWwgYXMgdW5rbm93biBhcyBMYW5ndWFnZU1vZGVsLFxuICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuICAgICAgbWVzc2FnZXMsXG4gICAgICBzdG9wV2hlbjogc3RlcENvdW50SXMoMiksXG4gICAgICB0b29sczoge1xuICAgICAgICBjcmVhdGVFbnRpdHk6IHRvb2woe1xuICAgICAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAgICAgXCLsg4jroZzsmrQgRW50aXR566W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDsl5Tti7Dti7Drgpgg7YWM7J2067iUIOyDneyEseydhCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbiAgICAgICAgICBpbnB1dFNjaGVtYTogVGVtcGxhdGVPcHRpb25zLnNoYXBlLmVudGl0eSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoXG4gICAgICAgICAgICBlbnRpdHksXG4gICAgICAgICAgKTogUHJvbWlzZTx7XG4gICAgICAgICAgICBzdWNjZXNzOiBib29sZWFuO1xuICAgICAgICAgICAgZW50aXR5SWQ6IHN0cmluZztcbiAgICAgICAgICAgIGVycm9yPzogc3RyaW5nO1xuICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycz86IFZhbGlkYXRpb25FcnJvcltdO1xuICAgICAgICAgIH0+ID0+IHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgIC8vIOyeheugpSDqsoDspp1cbiAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbihlbnRpdHkpO1xuXG4gICAgICAgICAgICAgIGlmICh2YWxpZGF0aW9uRXJyb3JzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgZXJyb3I6IGDqsoDspp0g7Jik66WYOiAke3ZhbGlkYXRpb25FcnJvcnMubWFwKChlKSA9PiBgWyR7ZS5maWVsZH1dICR7ZS5tZXNzYWdlfWApLmpvaW4oXCIsIFwiKX1gLFxuICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycyxcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgYXdhaXQgU29uYW11LnN5bmNlci5jcmVhdGVFbnRpdHkoe1xuICAgICAgICAgICAgICAgIHN1YnNldHM6IHsgQTogW1wiaWRcIl0gfSxcbiAgICAgICAgICAgICAgICBlbnVtczoge30sXG4gICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAvLyBFbnRpdHlNYW5hZ2VyIOumrOuhnOuTnFxuICAgICAgICAgICAgICBhd2FpdCBFbnRpdHlNYW5hZ2VyLnJlbG9hZCgpO1xuXG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQgfTtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBlIGluc3RhbmNlb2YgRXJyb3IgPyBlLm1lc3NhZ2UgOiBcIlVua25vd24gZXJyb3JcIjtcbiAgICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQsIGVycm9yIH07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgfSksXG4gICAgICAgIHVwZGF0ZUVudGl0eTogdG9vbCh7XG4gICAgICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICAgICBcIuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWp64uI64ukLiBFbnVtIOy2lOqwgCwgcHJvcHMg7LaU6rCAL+yImOyglSwgaW5kZXhlcyDsiJjsoJUsIHN1YnNldHMg7IiY7KCVIOuTsSDrqqjrk6Ag7JeU7Yuw7YuwIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWY7IS47JqULlwiLFxuICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4gICAgICAgICAgICBlbnRpdHlJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCBFbnRpdHkgSURcIiksXG4gICAgICAgICAgICB1cGRhdGVzOiBUZW1wbGF0ZU9wdGlvbnMuc2hhcGUuZW50aXR5LnBhcnRpYWwoKS5kZXNjcmliZShcIuyImOygle2VoCDtlYTrk5zrk6RcIiksXG4gICAgICAgICAgICBtb2RlOiB6XG4gICAgICAgICAgICAgIC5lbnVtKFtcIm1lcmdlXCIsIFwicmVwbGFjZVwiXSlcbiAgICAgICAgICAgICAgLm9wdGlvbmFsKClcbiAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7IiY7KCVIOuqqOuTnDogbWVyZ2Uo6riw67O46rCSLCDquLDsobQg6rCS7JeQIOuzke2VqSkg65iQ64qUIHJlcGxhY2Uo7KCE7LK0IOq1kOyytClcIiksXG4gICAgICAgICAgfSksXG4gICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbiAgICAgICAgICAgIGVudGl0eUlkLFxuICAgICAgICAgICAgdXBkYXRlcyxcbiAgICAgICAgICAgIG1vZGUgPSBcIm1lcmdlXCIsXG4gICAgICAgICAgfSk6IFByb21pc2U8e1xuICAgICAgICAgICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICAgICAgICAgIGVudGl0eUlkOiBzdHJpbmc7XG4gICAgICAgICAgICBlcnJvcj86IHN0cmluZztcbiAgICAgICAgICAgIHZhbGlkYXRpb25FcnJvcnM/OiBWYWxpZGF0aW9uRXJyb3JbXTtcbiAgICAgICAgICB9PiA9PiB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbiAgICAgICAgICAgICAgLy8gVXBkYXRlIGJhc2ljIHByb3BlcnRpZXNcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMuZW50aXR5SWQgIT09IHVuZGVmaW5lZCkgZW50aXR5LmlkID0gdXBkYXRlcy5lbnRpdHlJZDtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMucGFyZW50SWQgIT09IHVuZGVmaW5lZCkgZW50aXR5LnBhcmVudElkID0gdXBkYXRlcy5wYXJlbnRJZDtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMudGl0bGUgIT09IHVuZGVmaW5lZCkgZW50aXR5LnRpdGxlID0gdXBkYXRlcy50aXRsZTtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMudGFibGUgIT09IHVuZGVmaW5lZCkgZW50aXR5LnRhYmxlID0gdXBkYXRlcy50YWJsZTtcblxuICAgICAgICAgICAgICAvLyBwcm9wczogbWVyZ2Ug7IucIOydtOumhCDquLDspIAg67OR7ZWpLCByZXBsYWNlIOyLnCDqtZDssrRcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMucHJvcHMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIGlmIChtb2RlID09PSBcInJlcGxhY2VcIikge1xuICAgICAgICAgICAgICAgICAgZW50aXR5LnByb3BzID0gdXBkYXRlcy5wcm9wcyBhcyBFbnRpdHlQcm9wW107XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIGZvciAoY29uc3QgbmV3UHJvcCBvZiB1cGRhdGVzLnByb3BzKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGV4aXN0aW5nSW5kZXggPSBlbnRpdHkucHJvcHMuZmluZEluZGV4KChwKSA9PiBwLm5hbWUgPT09IG5ld1Byb3AubmFtZSk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChleGlzdGluZ0luZGV4ID49IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHNbZXhpc3RpbmdJbmRleF0gPSBuZXdQcm9wIGFzIEVudGl0eVByb3A7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgZW50aXR5LnByb3BzLnB1c2gobmV3UHJvcCBhcyBFbnRpdHlQcm9wKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIC8vIGluZGV4ZXM6IG1lcmdlIOyLnCDstpTqsIAsIHJlcGxhY2Ug7IucIOq1kOyytFxuICAgICAgICAgICAgICBpZiAodXBkYXRlcy5pbmRleGVzICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICBlbnRpdHkuaW5kZXhlcyA9XG4gICAgICAgICAgICAgICAgICBtb2RlID09PSBcInJlcGxhY2VcIiA/IHVwZGF0ZXMuaW5kZXhlcyA6IFsuLi5lbnRpdHkuaW5kZXhlcywgLi4udXBkYXRlcy5pbmRleGVzXTtcbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIC8vIHN1YnNldHMsIHN1YnNldHNJbnRlcm5hbDogYXNzaWdu7Jy866GcIOuzke2VqSDrmJDripQg6rWQ7LK0XG4gICAgICAgICAgICAgIGlmICh1cGRhdGVzLnN1YnNldHMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIC8vIE5vcm1hbGl6ZSBzdWJzZXQgZmllbGRzOiBzZXBhcmF0ZSBpbnRvIHN1YnNldHMgKG5vcm1hbCkgYW5kIHN1YnNldHNJbnRlcm5hbCAoaW50ZXJuYWwpXG4gICAgICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZFN1YnNldHM6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nW10gfSA9IHt9O1xuICAgICAgICAgICAgICAgIGNvbnN0IG5vcm1hbGl6ZWRTdWJzZXRzSW50ZXJuYWw6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nW10gfSA9IHt9O1xuXG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBba2V5LCBmaWVsZHNdIG9mIE9iamVjdC5lbnRyaWVzKHVwZGF0ZXMuc3Vic2V0cykpIHtcbiAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWRTdWJzZXRzW2tleV0gPSBmaWVsZHNcbiAgICAgICAgICAgICAgICAgICAgLmZpbHRlcigoZikgPT4gIWlzSW50ZXJuYWxTdWJzZXRGaWVsZChmKSlcbiAgICAgICAgICAgICAgICAgICAgLm1hcChub3JtYWxpemVTdWJzZXRGaWVsZCk7XG4gICAgICAgICAgICAgICAgICBub3JtYWxpemVkU3Vic2V0c0ludGVybmFsW2tleV0gPSBmaWVsZHNcbiAgICAgICAgICAgICAgICAgICAgLmZpbHRlcihpc0ludGVybmFsU3Vic2V0RmllbGQpXG4gICAgICAgICAgICAgICAgICAgIC5tYXAobm9ybWFsaXplU3Vic2V0RmllbGQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzID1cbiAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiXG4gICAgICAgICAgICAgICAgICAgID8gbm9ybWFsaXplZFN1YnNldHNcbiAgICAgICAgICAgICAgICAgICAgOiB7IC4uLmVudGl0eS5zdWJzZXRzLCAuLi5ub3JtYWxpemVkU3Vic2V0cyB9O1xuICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzSW50ZXJuYWwgPVxuICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCJcbiAgICAgICAgICAgICAgICAgICAgPyBub3JtYWxpemVkU3Vic2V0c0ludGVybmFsXG4gICAgICAgICAgICAgICAgICAgIDogeyAuLi5lbnRpdHkuc3Vic2V0c0ludGVybmFsLCAuLi5ub3JtYWxpemVkU3Vic2V0c0ludGVybmFsIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICBpZiAodXBkYXRlcy5lbnVtcyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgZW50aXR5LmVudW1MYWJlbHMgPVxuICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCIgPyB1cGRhdGVzLmVudW1zIDogeyAuLi5lbnRpdHkuZW51bUxhYmVscywgLi4udXBkYXRlcy5lbnVtcyB9O1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgLy8g7KCA7J6lIOyghCDqsoDspp1cbiAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbih7XG4gICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuICAgICAgICAgICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4gICAgICAgICAgICAgICAgZW51bXM6IGVudGl0eS5lbnVtTGFiZWxzLFxuICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvbkVycm9ycy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgICBlcnJvcjogYOqygOymnSDsmKTrpZg6ICR7dmFsaWRhdGlvbkVycm9ycy5tYXAoKGUpID0+IGBbJHtlLmZpZWxkfV0gJHtlLm1lc3NhZ2V9YCkuam9pbihcIiwgXCIpfWAsXG4gICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uRXJyb3JzLFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICBhd2FpdCBlbnRpdHkuc2F2ZSgpO1xuXG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkIH07XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGVycm9yID0gZSBpbnN0YW5jZW9mIEVycm9yID8gZS5tZXNzYWdlIDogXCJVbmtub3duIGVycm9yXCI7XG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlbnRpdHlJZCwgZXJyb3IgfTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICB9KSxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cbn1cblxuLyoqXG4gKiBFbnRpdHkgSlNPTuydtCBlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1k7J2YIOq3nOy5meydhCDrlLDrpbTripTsp4Ag6rKA7Kad7ZWp64uI64ukLlxuICovXG5mdW5jdGlvbiB2YWxpZGF0ZUVudGl0eUpzb24oaW5wdXQ6IFRlbXBsYXRlT3B0aW9uc1tcImVudGl0eVwiXSk6IFZhbGlkYXRpb25FcnJvcltdIHtcbiAgY29uc3QgZXJyb3JzOiBWYWxpZGF0aW9uRXJyb3JbXSA9IFtdO1xuICBjb25zdCB7IGVudGl0eUlkLCBwcm9wcywgZW51bXMgfSA9IGlucHV0O1xuXG4gIC8vIDEuIGlkLCBjcmVhdGVkX2F0IHByb3Ag7ZWE7IiYXG4gIGNvbnN0IGhhc0lkUHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiaWRcIik7XG4gIGlmICghaGFzSWRQcm9wKSB7XG4gICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImlkIO2UhOuhnO2NvO2LsOqwgCDtlYTsiJjsnoXri4jri6QuXCIgfSk7XG4gIH1cbiAgY29uc3QgaGFzQ3JlYXRlZEF0UHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKTtcbiAgaWYgKCFoYXNDcmVhdGVkQXRQcm9wKSB7XG4gICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImNyZWF0ZWRfYXQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImOyeheuLiOuLpC5cIiB9KTtcbiAgfVxuXG4gIC8vIDIuIO2VhOyImCBlbnVtIOqygOymnTogRW50aXR5TmFtZU9yZGVyQnksIEVudGl0eU5hbWVTZWFyY2hGaWVsZFxuICBjb25zdCBvcmRlckJ5RW51bUlkID0gYCR7ZW50aXR5SWR9T3JkZXJCeWA7XG4gIGNvbnN0IHNlYXJjaEZpZWxkRW51bUlkID0gYCR7ZW50aXR5SWR9U2VhcmNoRmllbGRgO1xuXG4gIGlmICghZW51bXM/LltvcmRlckJ5RW51bUlkXSkge1xuICAgIGVycm9ycy5wdXNoKHtcbiAgICAgIGZpZWxkOiBcImVudW1zXCIsXG4gICAgICBtZXNzYWdlOiBgJHtvcmRlckJ5RW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0pYCxcbiAgICB9KTtcbiAgfVxuICBpZiAoIWVudW1zPy5bc2VhcmNoRmllbGRFbnVtSWRdKSB7XG4gICAgZXJyb3JzLnB1c2goe1xuICAgICAgZmllbGQ6IFwiZW51bXNcIixcbiAgICAgIG1lc3NhZ2U6IGAke3NlYXJjaEZpZWxkRW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkXCI6IFwiSURcIiB9KWAsXG4gICAgfSk7XG4gIH1cblxuICAvLyAzLiBlbnVtIHByb3DsnZggaWTqsIAgZW51bXPsl5Ag7KCV7J2Y65CY7Ja0IOyeiOuKlOyngCDtmZXsnbggKGNyb3NzLWZpZWxkIOqygOymnSlcbiAgZm9yIChjb25zdCBwcm9wIG9mIHByb3BzID8/IFtdKSB7XG4gICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtXCIgJiYgIWVudW1zPy5bcHJvcC5pZF0pIHtcbiAgICAgIGVycm9ycy5wdXNoKHtcbiAgICAgICAgZmllbGQ6IGBwcm9wcy4ke3Byb3AubmFtZX1gLFxuICAgICAgICBtZXNzYWdlOiBgZW51bSBpZCBcIiR7cHJvcC5pZH1cIuqwgCBlbnVtc+yXkCDsoJXsnZjrkJjslrQg7J6I7KeAIOyViuyKteuLiOuLpC5gLFxuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGVycm9ycztcbn1cblxuZXhwb3J0IGNvbnN0IGFpQ2xpZW50ID0gbmV3IEFJQ2xpZW50KCk7XG4iXSwibmFtZXMiOlsiYW50aHJvcGljIiwic3RlcENvdW50SXMiLCJzdHJlYW1UZXh0IiwidG9vbCIsImFzc2VydCIsImZzIiwicGF0aCIsInoiLCJTb25hbXUiLCJFbnRpdHlNYW5hZ2VyIiwiaXNJbnRlcm5hbFN1YnNldEZpZWxkIiwibm9ybWFsaXplU3Vic2V0RmllbGQiLCJUZW1wbGF0ZU9wdGlvbnMiLCJub25OdWxsYWJsZSIsIkFJQ2xpZW50IiwibW9kZWwiLCJpbml0IiwiY29uc29sZSIsImxvZyIsImhhbmRsZUZpeHR1cmUiLCJtZXNzYWdlcyIsImZpeHR1cmVSZWNvcmRzIiwidXNlZEVudGl0eUlkcyIsIlNldCIsIm1hcCIsInIiLCJlbnRpdHlJZCIsImVudGl0eVN0cnVjdHVyZXMiLCJlbnRpdHkiLCJnZXQiLCJpZCIsInRhYmxlIiwicHJvcHMiLCJyZWxhdGlvbnMiLCJlbnVtTGFiZWxzIiwic3lzdGVtTWVzc2FnZSIsIkpTT04iLCJzdHJpbmdpZnkiLCJzeXN0ZW0iLCJ0b29scyIsInVwZGF0ZUZpeHR1cmVzIiwiZGVzY3JpcHRpb24iLCJpbnB1dFNjaGVtYSIsIm9iamVjdCIsInVwZGF0ZXMiLCJhcnJheSIsImZpeHR1cmVJZCIsInN0cmluZyIsImRlc2NyaWJlIiwicmVjb3JkIiwidW5rbm93biIsImV4ZWN1dGUiLCJ1cGRhdGVkUmVjb3JkcyIsInVwZGF0ZSIsImZpbmQiLCJ1IiwiY29sdW1uTmFtZSIsIm5ld1ZhbHVlIiwiT2JqZWN0IiwiZW50cmllcyIsImNvbHVtbnMiLCJ2YWx1ZSIsInN1Y2Nlc3MiLCJjcmVhdGVGaXh0dXJlcyIsImZpeHR1cmVzIiwibnVtYmVyIiwibmV3UmVjb3JkcyIsImZpeHR1cmUiLCJwcm9wIiwidHlwZSIsIm5hbWUiLCJEYXRlIiwidG9JU09TdHJpbmciLCJyZWxhdGlvblR5cGUiLCJBcnJheSIsImlzQXJyYXkiLCJmaWx0ZXIiLCJmZXRjaGVkUmVjb3JkcyIsImJlbG9uZ3NSZWNvcmRzIiwib3ZlcnJpZGUiLCJuZXdSZWNvcmQiLCJfY29sTmFtZSIsImNvbCIsInJlbGF0ZWRFbnRpdHlJZCIsIndpdGgiLCJyZWxhdGVkSWRzIiwicmVsYXRlZElkIiwicmVsYXRlZEZpeHR1cmVJZCIsInJlbGF0ZWRSZWNvcmQiLCJyZXZlcnNlQ29sIiwiYyIsInJldmVyc2VDb2xOYW1lIiwicmV2ZXJzZUNvbFZhbHVlIiwiY3VycmVudFZhbHVlIiwiaW5jbHVkZXMiLCJoYW5kbGVFbnRpdHkiLCJpbnN0cnVjdGlvbnNQYXRoIiwiam9pbiIsImRpcm5hbWUiLCJpbnN0cnVjdGlvbnMiLCJyZWFkRmlsZVN5bmMiLCJlbnRpdHlJZHMiLCJnZXRBbGxJZHMiLCJleGlzdGluZ0VudGl0aWVzIiwidGl0bGUiLCJwIiwiZGVzYyIsInN0b3BXaGVuIiwiY3JlYXRlRW50aXR5Iiwic2hhcGUiLCJ2YWxpZGF0aW9uRXJyb3JzIiwidmFsaWRhdGVFbnRpdHlKc29uIiwibGVuZ3RoIiwiZXJyb3IiLCJlIiwiZmllbGQiLCJtZXNzYWdlIiwic3luY2VyIiwic3Vic2V0cyIsIkEiLCJlbnVtcyIsInJlbG9hZCIsIkVycm9yIiwidXBkYXRlRW50aXR5IiwicGFydGlhbCIsIm1vZGUiLCJlbnVtIiwib3B0aW9uYWwiLCJ1bmRlZmluZWQiLCJwYXJlbnRJZCIsIm5ld1Byb3AiLCJleGlzdGluZ0luZGV4IiwiZmluZEluZGV4IiwicHVzaCIsImluZGV4ZXMiLCJub3JtYWxpemVkU3Vic2V0cyIsIm5vcm1hbGl6ZWRTdWJzZXRzSW50ZXJuYWwiLCJrZXkiLCJmaWVsZHMiLCJmIiwic3Vic2V0c0ludGVybmFsIiwic2F2ZSIsImlucHV0IiwiZXJyb3JzIiwiaGFzSWRQcm9wIiwic29tZSIsImhhc0NyZWF0ZWRBdFByb3AiLCJvcmRlckJ5RW51bUlkIiwic2VhcmNoRmllbGRFbnVtSWQiLCJhaUNsaWVudCJdLCJtYXBwaW5ncyI6IkFBQUEsaUZBQWlGLEdBQ2pGLFNBQVNBLFNBQVMsUUFBUSxvQkFBb0I7QUFDOUMsU0FJRUMsV0FBVyxFQUNYQyxVQUFVLEVBQ1ZDLElBQUksUUFDQyxLQUFLO0FBQ1osT0FBT0MsWUFBWSxTQUFTO0FBQzVCLE9BQU9DLFFBQVEsS0FBSztBQUNwQixPQUFPQyxVQUFVLE9BQU87QUFDeEIsU0FBU0MsQ0FBQyxRQUFRLE1BQU07QUFDeEIsU0FBU0MsTUFBTSxRQUFRLGtCQUFTO0FBQ2hDLFNBQVNDLGFBQWEsUUFBUSw4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
|