@sitblueprint/website-construct 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,89 @@ export class MyWebsiteStack extends cdk.Stack {
50
50
  }
51
51
  ```
52
52
 
53
+ ## Pull Request Preview Environments
54
+
55
+ Use `previewConfig` on `Website` to create a pool of preview buckets (default: 2). CI can claim a slot using LRU, deploy artifacts to the assigned bucket, and release the slot when the pull request closes.
56
+
57
+ ```ts
58
+ export class WebsiteWithPreviewStack extends cdk.Stack {
59
+ constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
60
+ super(scope, id, props);
61
+
62
+ const website = new Website(this, "MyWebsite", {
63
+ bucketName: "my-static-site-bucket",
64
+ indexFile: "index.html",
65
+ errorFile: "error.html",
66
+ previewConfig: {
67
+ bucketPrefix: "my-frontend-preview",
68
+ bucketCount: 2, // default
69
+ maxLeaseHours: 24,
70
+ },
71
+ });
72
+
73
+ new cdk.CfnOutput(this, "PreviewClaimEndpoint", {
74
+ value: website.previewEnvironment!.claimEndpoint,
75
+ });
76
+ new cdk.CfnOutput(this, "PreviewReleaseEndpoint", {
77
+ value: website.previewEnvironment!.releaseEndpoint,
78
+ });
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### API contract for CI
84
+
85
+ - `POST /claim` with body `{"repo":"owner/repo","prNumber":123,"commitSha":"abc"}`
86
+ - `POST /heartbeat` with body `{"repo":"owner/repo","prNumber":123,"commitSha":"abc"}`
87
+ - `POST /release` with body `{"repo":"owner/repo","prNumber":123}`
88
+
89
+ `claim` and `heartbeat` return:
90
+
91
+ ```json
92
+ {
93
+ "slotId": 0,
94
+ "bucketName": "my-frontend-preview-0",
95
+ "previewUrl": "https://....cloudfront.net"
96
+ }
97
+ ```
98
+
99
+ ### GitHub Actions shape
100
+
101
+ ```yaml
102
+ name: preview
103
+ on:
104
+ pull_request:
105
+ types: [opened, reopened, synchronize, closed]
106
+
107
+ jobs:
108
+ preview:
109
+ runs-on: ubuntu-latest
110
+ concurrency: preview-${{ github.event.pull_request.number }}
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - if: github.event.action != 'closed'
114
+ run: npm ci && npm run build
115
+ - name: Claim or release slot
116
+ env:
117
+ REPO: ${{ github.repository }}
118
+ PR: ${{ github.event.pull_request.number }}
119
+ SHA: ${{ github.sha }}
120
+ CLAIM_URL: ${{ secrets.PREVIEW_CLAIM_ENDPOINT }}
121
+ RELEASE_URL: ${{ secrets.PREVIEW_RELEASE_ENDPOINT }}
122
+ run: |
123
+ if [ "${{ github.event.action }}" = "closed" ]; then
124
+ curl -sS -X POST "$RELEASE_URL" -H "content-type: application/json" -d "{\"repo\":\"$REPO\",\"prNumber\":$PR}"
125
+ exit 0
126
+ fi
127
+
128
+ RESPONSE=$(curl -sS -X POST "$CLAIM_URL" -H "content-type: application/json" -d "{\"repo\":\"$REPO\",\"prNumber\":$PR,\"commitSha\":\"$SHA\"}")
129
+ echo "$RESPONSE" > preview-slot.json
130
+ BUCKET=$(jq -r '.bucketName' preview-slot.json)
131
+ URL=$(jq -r '.previewUrl' preview-slot.json)
132
+ aws s3 sync ./dist "s3://$BUCKET" --delete
133
+ echo "Preview URL: $URL"
134
+ ```
135
+
53
136
  ## Development
54
137
 
55
138
  - Build: `npm run build`
package/docs/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [v0.1.6] - 2026-02-15
11
+
12
+ ### Added
13
+
14
+ - `PreviewConfig` on `WebsiteProps` to enable pull request preview environments from the `Website` construct.
15
+ - Preview slot infrastructure with configurable bucket pool size (`bucketCount`, default `2`), LRU lease behavior, and optional per-slot CloudFront distributions.
16
+ - Preview lease API endpoints for CI workflows:
17
+ - `POST /claim`
18
+ - `POST /heartbeat`
19
+ - `POST /release`
20
+ - DynamoDB-backed lease state with `RepoPrKeyIndex` for PR-to-slot lookup.
21
+ - `previewEnvironment` exposure on `Website` for accessing endpoints and resources from downstream stacks.
22
+ - `grantDeploymentAccess(...)` helper to grant CI principals access to preview buckets and CloudFront invalidation.
23
+
24
+ ### Changed
25
+
26
+ - Refactored preview lease Lambda source from inline code to `lambda/index.ts` for better maintainability.
27
+ - Updated tests to validate preview behavior via `Website.previewConfig` instead of constructing preview infrastructure directly.
28
+ - Updated README preview usage to configure previews through `Website`.
29
+
10
30
  ## [v0.1.5]
11
31
 
12
32
  ### Added
@@ -0,0 +1,11 @@
1
+ type APIGatewayProxyEvent = {
2
+ body: string | null;
3
+ path: string;
4
+ };
5
+ type APIGatewayProxyResult = {
6
+ statusCode: number;
7
+ headers: Record<string, string>;
8
+ body: string;
9
+ };
10
+ export declare const handler: (event: APIGatewayProxyEvent) => Promise<APIGatewayProxyResult>;
11
+ export {};
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handler = void 0;
4
+ const AWS = require("aws-sdk");
5
+ const ddb = new AWS.DynamoDB.DocumentClient();
6
+ const tableName = process.env.TABLE_NAME ?? "";
7
+ const slotDefinitions = JSON.parse(process.env.SLOT_DEFINITIONS ?? "[]");
8
+ const maxLeaseMs = Number(process.env.MAX_LEASE_MS ?? "86400000");
9
+ const ok = (body) => ({
10
+ statusCode: 200,
11
+ headers: { "content-type": "application/json" },
12
+ body: JSON.stringify(body),
13
+ });
14
+ const badRequest = (message) => ({
15
+ statusCode: 400,
16
+ headers: { "content-type": "application/json" },
17
+ body: JSON.stringify({ error: message }),
18
+ });
19
+ const conflict = (message) => ({
20
+ statusCode: 409,
21
+ headers: { "content-type": "application/json" },
22
+ body: JSON.stringify({ error: message }),
23
+ });
24
+ const toRepoPrKey = (repo, prNumber) => `${repo}#${prNumber}`;
25
+ const nowMs = () => Date.now();
26
+ const nowEpochSeconds = () => Math.floor(Date.now() / 1000);
27
+ const isConditionalCheckFailure = (error) => {
28
+ if (!(error instanceof Error))
29
+ return false;
30
+ return error.name === "ConditionalCheckFailedException";
31
+ };
32
+ const parseBody = (event) => {
33
+ if (!event.body)
34
+ return {};
35
+ return JSON.parse(event.body);
36
+ };
37
+ const getClaimBody = (body) => {
38
+ const repo = typeof body.repo === "string" ? body.repo : "";
39
+ const prNumber = Number(body.prNumber);
40
+ const commitSha = typeof body.commitSha === "string" ? body.commitSha : "";
41
+ return { repo, prNumber, commitSha };
42
+ };
43
+ const getReleaseBody = (body) => {
44
+ const repo = typeof body.repo === "string" ? body.repo : "";
45
+ const prNumber = Number(body.prNumber);
46
+ return { repo, prNumber };
47
+ };
48
+ const queryLeaseByRepoPr = async (repoPrKey) => {
49
+ const result = await ddb
50
+ .query({
51
+ TableName: tableName,
52
+ IndexName: "RepoPrKeyIndex",
53
+ KeyConditionExpression: "repoPrKey = :repoPrKey",
54
+ ExpressionAttributeValues: {
55
+ ":repoPrKey": repoPrKey,
56
+ },
57
+ Limit: 1,
58
+ })
59
+ .promise();
60
+ if (!result.Items || result.Items.length === 0) {
61
+ return null;
62
+ }
63
+ return result.Items[0];
64
+ };
65
+ const loadSlots = async () => {
66
+ if (slotDefinitions.length === 0) {
67
+ return [];
68
+ }
69
+ const result = await ddb
70
+ .batchGet({
71
+ RequestItems: {
72
+ [tableName]: {
73
+ Keys: slotDefinitions.map((slot) => ({
74
+ slotId: String(slot.slotId),
75
+ })),
76
+ },
77
+ },
78
+ })
79
+ .promise();
80
+ const tableItems = (result.Responses?.[tableName] ?? []);
81
+ const bySlotId = new Map();
82
+ tableItems.forEach((item) => bySlotId.set(item.slotId, item));
83
+ return slotDefinitions.map((slot) => ({
84
+ ...slot,
85
+ lease: bySlotId.get(String(slot.slotId)) ?? null,
86
+ }));
87
+ };
88
+ const chooseSlot = (slots, repoPrKey, now) => {
89
+ const existing = slots.find((slot) => slot.lease?.repoPrKey === repoPrKey);
90
+ if (existing) {
91
+ return {
92
+ slot: existing,
93
+ expectedLastUsedAt: Number(existing.lease?.lastUsedAt ?? 0),
94
+ };
95
+ }
96
+ const available = slots
97
+ .filter((slot) => !slot.lease || Number(slot.lease.leaseExpiresAt ?? 0) < now)
98
+ .sort((a, b) => Number(a.lease?.lastUsedAt ?? 0) - Number(b.lease?.lastUsedAt ?? 0));
99
+ if (available.length > 0) {
100
+ return {
101
+ slot: available[0],
102
+ expectedLastUsedAt: available[0].lease
103
+ ? Number(available[0].lease.lastUsedAt ?? 0)
104
+ : null,
105
+ };
106
+ }
107
+ const lru = [...slots].sort((a, b) => Number(a.lease?.lastUsedAt ?? 0) - Number(b.lease?.lastUsedAt ?? 0))[0];
108
+ return {
109
+ slot: lru,
110
+ expectedLastUsedAt: Number(lru.lease?.lastUsedAt ?? 0),
111
+ };
112
+ };
113
+ const claim = async (repo, prNumber, commitSha) => {
114
+ if (!repo || !Number.isInteger(prNumber)) {
115
+ return badRequest("repo and integer prNumber are required");
116
+ }
117
+ const repoPrKey = toRepoPrKey(repo, prNumber);
118
+ for (let attempts = 0; attempts < 5; attempts += 1) {
119
+ const now = nowMs();
120
+ const slots = await loadSlots();
121
+ if (slots.length === 0) {
122
+ return badRequest("No preview slots configured");
123
+ }
124
+ const selection = chooseSlot(slots, repoPrKey, now);
125
+ const slot = selection.slot;
126
+ const expressionAttributeValues = {
127
+ ":repo": repo,
128
+ ":prNumber": prNumber,
129
+ ":repoPrKey": repoPrKey,
130
+ ":commitSha": commitSha,
131
+ ":now": now,
132
+ ":expiresAt": now + maxLeaseMs,
133
+ ":ttlEpochSeconds": nowEpochSeconds() + Math.floor(maxLeaseMs / 1000),
134
+ ":bucketName": slot.bucketName,
135
+ ":previewUrl": slot.previewUrl,
136
+ };
137
+ let conditionExpression = "attribute_not_exists(slotId)";
138
+ if (selection.expectedLastUsedAt !== null) {
139
+ conditionExpression =
140
+ "lastUsedAt = :expectedLastUsedAt OR repoPrKey = :repoPrKey";
141
+ expressionAttributeValues[":expectedLastUsedAt"] =
142
+ selection.expectedLastUsedAt;
143
+ }
144
+ try {
145
+ await ddb
146
+ .update({
147
+ TableName: tableName,
148
+ Key: { slotId: String(slot.slotId) },
149
+ UpdateExpression: "SET repo = :repo, prNumber = :prNumber, repoPrKey = :repoPrKey, commitSha = :commitSha, bucketName = :bucketName, previewUrl = :previewUrl, leasedAt = if_not_exists(leasedAt, :now), lastUsedAt = :now, leaseExpiresAt = :expiresAt, ttlEpochSeconds = :ttlEpochSeconds",
150
+ ConditionExpression: conditionExpression,
151
+ ExpressionAttributeValues: expressionAttributeValues,
152
+ })
153
+ .promise();
154
+ return ok({
155
+ slotId: slot.slotId,
156
+ bucketName: slot.bucketName,
157
+ previewUrl: slot.previewUrl,
158
+ });
159
+ }
160
+ catch (error) {
161
+ if (!isConditionalCheckFailure(error)) {
162
+ throw error;
163
+ }
164
+ }
165
+ }
166
+ return conflict("Failed to claim preview slot due to concurrent updates");
167
+ };
168
+ const heartbeat = async (repo, prNumber, commitSha) => {
169
+ if (!repo || !Number.isInteger(prNumber)) {
170
+ return badRequest("repo and integer prNumber are required");
171
+ }
172
+ const repoPrKey = toRepoPrKey(repo, prNumber);
173
+ const existing = await queryLeaseByRepoPr(repoPrKey);
174
+ if (!existing) {
175
+ return badRequest("No active lease found for this pull request");
176
+ }
177
+ const now = nowMs();
178
+ await ddb
179
+ .update({
180
+ TableName: tableName,
181
+ Key: { slotId: existing.slotId },
182
+ UpdateExpression: "SET commitSha = :commitSha, lastUsedAt = :now, leaseExpiresAt = :expiresAt, ttlEpochSeconds = :ttlEpochSeconds",
183
+ ConditionExpression: "repoPrKey = :repoPrKey",
184
+ ExpressionAttributeValues: {
185
+ ":repoPrKey": repoPrKey,
186
+ ":commitSha": commitSha || existing.commitSha || "",
187
+ ":now": now,
188
+ ":expiresAt": now + maxLeaseMs,
189
+ ":ttlEpochSeconds": nowEpochSeconds() + Math.floor(maxLeaseMs / 1000),
190
+ },
191
+ })
192
+ .promise();
193
+ return ok({
194
+ slotId: existing.slotId,
195
+ bucketName: existing.bucketName,
196
+ previewUrl: existing.previewUrl,
197
+ });
198
+ };
199
+ const release = async (repo, prNumber) => {
200
+ if (!repo || !Number.isInteger(prNumber)) {
201
+ return badRequest("repo and integer prNumber are required");
202
+ }
203
+ const repoPrKey = toRepoPrKey(repo, prNumber);
204
+ const existing = await queryLeaseByRepoPr(repoPrKey);
205
+ if (!existing) {
206
+ return ok({ released: false });
207
+ }
208
+ await ddb
209
+ .delete({
210
+ TableName: tableName,
211
+ Key: { slotId: existing.slotId },
212
+ ConditionExpression: "repoPrKey = :repoPrKey",
213
+ ExpressionAttributeValues: {
214
+ ":repoPrKey": repoPrKey,
215
+ },
216
+ })
217
+ .promise();
218
+ return ok({ released: true, slotId: existing.slotId });
219
+ };
220
+ const handler = async (event) => {
221
+ const body = parseBody(event);
222
+ const path = event.path ?? "";
223
+ if (path.endsWith("/claim")) {
224
+ const { repo, prNumber, commitSha } = getClaimBody(body);
225
+ return claim(repo, prNumber, commitSha);
226
+ }
227
+ if (path.endsWith("/heartbeat")) {
228
+ const { repo, prNumber, commitSha } = getClaimBody(body);
229
+ return heartbeat(repo, prNumber, commitSha);
230
+ }
231
+ if (path.endsWith("/release")) {
232
+ const { repo, prNumber } = getReleaseBody(body);
233
+ return release(repo, prNumber);
234
+ }
235
+ return badRequest("Unsupported route");
236
+ };
237
+ exports.handler = handler;
238
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFXQSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFrQi9CLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxjQUFjLEVBQUUsQ0FBQztBQUM5QyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSxFQUFFLENBQUM7QUFDL0MsTUFBTSxlQUFlLEdBQXFCLElBQUksQ0FBQyxLQUFLLENBQ2xELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLElBQUksSUFBSSxDQUNyQyxDQUFDO0FBQ0YsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLFVBQVUsQ0FBQyxDQUFDO0FBRWxFLE1BQU0sRUFBRSxHQUFHLENBQUMsSUFBNkIsRUFBeUIsRUFBRSxDQUFDLENBQUM7SUFDcEUsVUFBVSxFQUFFLEdBQUc7SUFDZixPQUFPLEVBQUUsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUU7SUFDL0MsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDO0NBQzNCLENBQUMsQ0FBQztBQUVILE1BQU0sVUFBVSxHQUFHLENBQUMsT0FBZSxFQUF5QixFQUFFLENBQUMsQ0FBQztJQUM5RCxVQUFVLEVBQUUsR0FBRztJQUNmLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRTtJQUMvQyxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsQ0FBQztDQUN6QyxDQUFDLENBQUM7QUFFSCxNQUFNLFFBQVEsR0FBRyxDQUFDLE9BQWUsRUFBeUIsRUFBRSxDQUFDLENBQUM7SUFDNUQsVUFBVSxFQUFFLEdBQUc7SUFDZixPQUFPLEVBQUUsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUU7SUFDL0MsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLENBQUM7Q0FDekMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxXQUFXLEdBQUcsQ0FBQyxJQUFZLEVBQUUsUUFBZ0IsRUFBVSxFQUFFLENBQzdELEdBQUcsSUFBSSxJQUFJLFFBQVEsRUFBRSxDQUFDO0FBRXhCLE1BQU0sS0FBSyxHQUFHLEdBQVcsRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztBQUN2QyxNQUFNLGVBQWUsR0FBRyxHQUFXLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUVwRSxNQUFNLHlCQUF5QixHQUFHLENBQUMsS0FBYyxFQUFXLEVBQUU7SUFDNUQsSUFBSSxDQUFDLENBQUMsS0FBSyxZQUFZLEtBQUssQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzVDLE9BQU8sS0FBSyxDQUFDLElBQUksS0FBSyxpQ0FBaUMsQ0FBQztBQUMxRCxDQUFDLENBQUM7QUFFRixNQUFNLFNBQVMsR0FBRyxDQUFDLEtBQTJCLEVBQTJCLEVBQUU7SUFDekUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDM0IsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQTRCLENBQUM7QUFDM0QsQ0FBQyxDQUFDO0FBRUYsTUFBTSxZQUFZLEdBQUcsQ0FBQyxJQUE2QixFQUFFLEVBQUU7SUFDckQsTUFBTSxJQUFJLEdBQUcsT0FBTyxJQUFJLENBQUMsSUFBSSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQzVELE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDdkMsTUFBTSxTQUFTLEdBQUcsT0FBTyxJQUFJLENBQUMsU0FBUyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQzNFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxDQUFDO0FBQ3ZDLENBQUMsQ0FBQztBQUVGLE1BQU0sY0FBYyxHQUFHLENBQUMsSUFBNkIsRUFBRSxFQUFFO0lBQ3ZELE1BQU0sSUFBSSxHQUFHLE9BQU8sSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUM1RCxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDNUIsQ0FBQyxDQUFDO0FBRUYsTUFBTSxrQkFBa0IsR0FBRyxLQUFLLEVBQzlCLFNBQWlCLEVBQ1UsRUFBRTtJQUM3QixNQUFNLE1BQU0sR0FBRyxNQUFNLEdBQUc7U0FDckIsS0FBSyxDQUFDO1FBQ0wsU0FBUyxFQUFFLFNBQVM7UUFDcEIsU0FBUyxFQUFFLGdCQUFnQjtRQUMzQixzQkFBc0IsRUFBRSx3QkFBd0I7UUFDaEQseUJBQXlCLEVBQUU7WUFDekIsWUFBWSxFQUFFLFNBQVM7U0FDeEI7UUFDRCxLQUFLLEVBQUUsQ0FBQztLQUNULENBQUM7U0FDRCxPQUFPLEVBQUUsQ0FBQztJQUViLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxJQUFJLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQy9DLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQWMsQ0FBQztBQUN0QyxDQUFDLENBQUM7QUFFRixNQUFNLFNBQVMsR0FBRyxLQUFLLElBRXJCLEVBQUU7SUFDRixJQUFJLGVBQWUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDakMsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsTUFBTSxHQUFHO1NBQ3JCLFFBQVEsQ0FBQztRQUNSLFlBQVksRUFBRTtZQUNaLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ1gsSUFBSSxFQUFFLGVBQWUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7b0JBQ25DLE1BQU0sRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztpQkFDNUIsQ0FBQyxDQUFDO2FBQ0o7U0FDRjtLQUNGLENBQUM7U0FDRCxPQUFPLEVBQUUsQ0FBQztJQUViLE1BQU0sVUFBVSxHQUFHLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBZ0IsQ0FBQztJQUN4RSxNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBcUIsQ0FBQztJQUM5QyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUU5RCxPQUFPLGVBQWUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDcEMsR0FBRyxJQUFJO1FBQ1AsS0FBSyxFQUFFLFFBQVEsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLElBQUk7S0FDakQsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDLENBQUM7QUFFRixNQUFNLFVBQVUsR0FBRyxDQUNqQixLQUEwRCxFQUMxRCxTQUFpQixFQUNqQixHQUFXLEVBSVgsRUFBRTtJQUNGLE1BQU0sUUFBUSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsU0FBUyxLQUFLLFNBQVMsQ0FBQyxDQUFDO0lBQzNFLElBQUksUUFBUSxFQUFFLENBQUM7UUFDYixPQUFPO1lBQ0wsSUFBSSxFQUFFLFFBQVE7WUFDZCxrQkFBa0IsRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxVQUFVLElBQUksQ0FBQyxDQUFDO1NBQzVELENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxTQUFTLEdBQUcsS0FBSztTQUNwQixNQUFNLENBQ0wsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLElBQUksQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUN0RTtTQUNBLElBQUksQ0FDSCxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUNQLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFFLFVBQVUsSUFBSSxDQUFDLENBQUMsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRSxVQUFVLElBQUksQ0FBQyxDQUFDLENBQ3RFLENBQUM7SUFDSixJQUFJLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDekIsT0FBTztZQUNMLElBQUksRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDO1lBQ2xCLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLO2dCQUNwQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsVUFBVSxJQUFJLENBQUMsQ0FBQztnQkFDNUMsQ0FBQyxDQUFDLElBQUk7U0FDVCxDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQ3pCLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQ1AsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsVUFBVSxJQUFJLENBQUMsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFFLFVBQVUsSUFBSSxDQUFDLENBQUMsQ0FDdEUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNMLE9BQU87UUFDTCxJQUFJLEVBQUUsR0FBRztRQUNULGtCQUFrQixFQUFFLE1BQU0sQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLFVBQVUsSUFBSSxDQUFDLENBQUM7S0FDdkQsQ0FBQztBQUNKLENBQUMsQ0FBQztBQUVGLE1BQU0sS0FBSyxHQUFHLEtBQUssRUFDakIsSUFBWSxFQUNaLFFBQWdCLEVBQ2hCLFNBQWlCLEVBQ2UsRUFBRTtJQUNsQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ3pDLE9BQU8sVUFBVSxDQUFDLHdDQUF3QyxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDOUMsS0FBSyxJQUFJLFFBQVEsR0FBRyxDQUFDLEVBQUUsUUFBUSxHQUFHLENBQUMsRUFBRSxRQUFRLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDbkQsTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUFFLENBQUM7UUFDcEIsTUFBTSxLQUFLLEdBQUcsTUFBTSxTQUFTLEVBQUUsQ0FBQztRQUNoQyxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDdkIsT0FBTyxVQUFVLENBQUMsNkJBQTZCLENBQUMsQ0FBQztRQUNuRCxDQUFDO1FBRUQsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDcEQsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQztRQUU1QixNQUFNLHlCQUF5QixHQUE0QjtZQUN6RCxPQUFPLEVBQUUsSUFBSTtZQUNiLFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFlBQVksRUFBRSxTQUFTO1lBQ3ZCLFlBQVksRUFBRSxTQUFTO1lBQ3ZCLE1BQU0sRUFBRSxHQUFHO1lBQ1gsWUFBWSxFQUFFLEdBQUcsR0FBRyxVQUFVO1lBQzlCLGtCQUFrQixFQUFFLGVBQWUsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztZQUNyRSxhQUFhLEVBQUUsSUFBSSxDQUFDLFVBQVU7WUFDOUIsYUFBYSxFQUFFLElBQUksQ0FBQyxVQUFVO1NBQy9CLENBQUM7UUFFRixJQUFJLG1CQUFtQixHQUFHLDhCQUE4QixDQUFDO1FBQ3pELElBQUksU0FBUyxDQUFDLGtCQUFrQixLQUFLLElBQUksRUFBRSxDQUFDO1lBQzFDLG1CQUFtQjtnQkFDakIsNERBQTRELENBQUM7WUFDL0QseUJBQXlCLENBQUMscUJBQXFCLENBQUM7Z0JBQzlDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQztRQUNqQyxDQUFDO1FBRUQsSUFBSSxDQUFDO1lBQ0gsTUFBTSxHQUFHO2lCQUNOLE1BQU0sQ0FBQztnQkFDTixTQUFTLEVBQUUsU0FBUztnQkFDcEIsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUU7Z0JBQ3BDLGdCQUFnQixFQUNkLDBRQUEwUTtnQkFDNVEsbUJBQW1CLEVBQUUsbUJBQW1CO2dCQUN4Qyx5QkFBeUIsRUFBRSx5QkFBeUI7YUFDckQsQ0FBQztpQkFDRCxPQUFPLEVBQUUsQ0FBQztZQUViLE9BQU8sRUFBRSxDQUFDO2dCQUNSLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTTtnQkFDbkIsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO2dCQUMzQixVQUFVLEVBQUUsSUFBSSxDQUFDLFVBQVU7YUFDNUIsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMseUJBQXlCLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDdEMsTUFBTSxLQUFLLENBQUM7WUFDZCxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLFFBQVEsQ0FBQyx3REFBd0QsQ0FBQyxDQUFDO0FBQzVFLENBQUMsQ0FBQztBQUVGLE1BQU0sU0FBUyxHQUFHLEtBQUssRUFDckIsSUFBWSxFQUNaLFFBQWdCLEVBQ2hCLFNBQWlCLEVBQ2UsRUFBRTtJQUNsQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ3pDLE9BQU8sVUFBVSxDQUFDLHdDQUF3QyxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDOUMsTUFBTSxRQUFRLEdBQUcsTUFBTSxrQkFBa0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNyRCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxPQUFPLFVBQVUsQ0FBQyw2Q0FBNkMsQ0FBQyxDQUFDO0lBQ25FLENBQUM7SUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsQ0FBQztJQUNwQixNQUFNLEdBQUc7U0FDTixNQUFNLENBQUM7UUFDTixTQUFTLEVBQUUsU0FBUztRQUNwQixHQUFHLEVBQUUsRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sRUFBRTtRQUNoQyxnQkFBZ0IsRUFDZCxnSEFBZ0g7UUFDbEgsbUJBQW1CLEVBQUUsd0JBQXdCO1FBQzdDLHlCQUF5QixFQUFFO1lBQ3pCLFlBQVksRUFBRSxTQUFTO1lBQ3ZCLFlBQVksRUFBRSxTQUFTLElBQUksUUFBUSxDQUFDLFNBQVMsSUFBSSxFQUFFO1lBQ25ELE1BQU0sRUFBRSxHQUFHO1lBQ1gsWUFBWSxFQUFFLEdBQUcsR0FBRyxVQUFVO1lBQzlCLGtCQUFrQixFQUFFLGVBQWUsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztTQUN0RTtLQUNGLENBQUM7U0FDRCxPQUFPLEVBQUUsQ0FBQztJQUViLE9BQU8sRUFBRSxDQUFDO1FBQ1IsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1FBQ3ZCLFVBQVUsRUFBRSxRQUFRLENBQUMsVUFBVTtRQUMvQixVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVU7S0FDaEMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDO0FBRUYsTUFBTSxPQUFPLEdBQUcsS0FBSyxFQUNuQixJQUFZLEVBQ1osUUFBZ0IsRUFDZ0IsRUFBRTtJQUNsQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ3pDLE9BQU8sVUFBVSxDQUFDLHdDQUF3QyxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDOUMsTUFBTSxRQUFRLEdBQUcsTUFBTSxrQkFBa0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNyRCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxPQUFPLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRCxNQUFNLEdBQUc7U0FDTixNQUFNLENBQUM7UUFDTixTQUFTLEVBQUUsU0FBUztRQUNwQixHQUFHLEVBQUUsRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sRUFBRTtRQUNoQyxtQkFBbUIsRUFBRSx3QkFBd0I7UUFDN0MseUJBQXlCLEVBQUU7WUFDekIsWUFBWSxFQUFFLFNBQVM7U0FDeEI7S0FDRixDQUFDO1NBQ0QsT0FBTyxFQUFFLENBQUM7SUFFYixPQUFPLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0FBQ3pELENBQUMsQ0FBQztBQUVLLE1BQU0sT0FBTyxHQUFHLEtBQUssRUFDMUIsS0FBMkIsRUFDSyxFQUFFO0lBQ2xDLE1BQU0sSUFBSSxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUM5QixNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUU5QixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUM1QixNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekQsT0FBTyxLQUFLLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pELE9BQU8sU0FBUyxDQUFDLElBQUksRUFBRSxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVELElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQzlCLE1BQU0sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hELE9BQU8sT0FBTyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztJQUNqQyxDQUFDO0lBRUQsT0FBTyxVQUFVLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUM7QUF0QlcsUUFBQSxPQUFPLFdBc0JsQiIsInNvdXJjZXNDb250ZW50IjpbInR5cGUgQVBJR2F0ZXdheVByb3h5RXZlbnQgPSB7XG4gIGJvZHk6IHN0cmluZyB8IG51bGw7XG4gIHBhdGg6IHN0cmluZztcbn07XG5cbnR5cGUgQVBJR2F0ZXdheVByb3h5UmVzdWx0ID0ge1xuICBzdGF0dXNDb2RlOiBudW1iZXI7XG4gIGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG4gIGJvZHk6IHN0cmluZztcbn07XG5cbmNvbnN0IEFXUyA9IHJlcXVpcmUoXCJhd3Mtc2RrXCIpO1xuXG50eXBlIFNsb3REZWZpbml0aW9uID0ge1xuICBzbG90SWQ6IG51bWJlcjtcbiAgYnVja2V0TmFtZTogc3RyaW5nO1xuICBwcmV2aWV3VXJsOiBzdHJpbmc7XG59O1xuXG50eXBlIFNsb3RMZWFzZSA9IHtcbiAgc2xvdElkOiBzdHJpbmc7XG4gIHJlcG9QcktleTogc3RyaW5nO1xuICBidWNrZXROYW1lOiBzdHJpbmc7XG4gIHByZXZpZXdVcmw6IHN0cmluZztcbiAgbGFzdFVzZWRBdD86IG51bWJlcjtcbiAgbGVhc2VFeHBpcmVzQXQ/OiBudW1iZXI7XG4gIGNvbW1pdFNoYT86IHN0cmluZztcbn07XG5cbmNvbnN0IGRkYiA9IG5ldyBBV1MuRHluYW1vREIuRG9jdW1lbnRDbGllbnQoKTtcbmNvbnN0IHRhYmxlTmFtZSA9IHByb2Nlc3MuZW52LlRBQkxFX05BTUUgPz8gXCJcIjtcbmNvbnN0IHNsb3REZWZpbml0aW9uczogU2xvdERlZmluaXRpb25bXSA9IEpTT04ucGFyc2UoXG4gIHByb2Nlc3MuZW52LlNMT1RfREVGSU5JVElPTlMgPz8gXCJbXVwiLFxuKTtcbmNvbnN0IG1heExlYXNlTXMgPSBOdW1iZXIocHJvY2Vzcy5lbnYuTUFYX0xFQVNFX01TID8/IFwiODY0MDAwMDBcIik7XG5cbmNvbnN0IG9rID0gKGJvZHk6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogQVBJR2F0ZXdheVByb3h5UmVzdWx0ID0+ICh7XG4gIHN0YXR1c0NvZGU6IDIwMCxcbiAgaGVhZGVyczogeyBcImNvbnRlbnQtdHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIiB9LFxuICBib2R5OiBKU09OLnN0cmluZ2lmeShib2R5KSxcbn0pO1xuXG5jb25zdCBiYWRSZXF1ZXN0ID0gKG1lc3NhZ2U6IHN0cmluZyk6IEFQSUdhdGV3YXlQcm94eVJlc3VsdCA9PiAoe1xuICBzdGF0dXNDb2RlOiA0MDAsXG4gIGhlYWRlcnM6IHsgXCJjb250ZW50LXR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIgfSxcbiAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBlcnJvcjogbWVzc2FnZSB9KSxcbn0pO1xuXG5jb25zdCBjb25mbGljdCA9IChtZXNzYWdlOiBzdHJpbmcpOiBBUElHYXRld2F5UHJveHlSZXN1bHQgPT4gKHtcbiAgc3RhdHVzQ29kZTogNDA5LFxuICBoZWFkZXJzOiB7IFwiY29udGVudC10eXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gIGJvZHk6IEpTT04uc3RyaW5naWZ5KHsgZXJyb3I6IG1lc3NhZ2UgfSksXG59KTtcblxuY29uc3QgdG9SZXBvUHJLZXkgPSAocmVwbzogc3RyaW5nLCBwck51bWJlcjogbnVtYmVyKTogc3RyaW5nID0+XG4gIGAke3JlcG99IyR7cHJOdW1iZXJ9YDtcblxuY29uc3Qgbm93TXMgPSAoKTogbnVtYmVyID0+IERhdGUubm93KCk7XG5jb25zdCBub3dFcG9jaFNlY29uZHMgPSAoKTogbnVtYmVyID0+IE1hdGguZmxvb3IoRGF0ZS5ub3coKSAvIDEwMDApO1xuXG5jb25zdCBpc0NvbmRpdGlvbmFsQ2hlY2tGYWlsdXJlID0gKGVycm9yOiB1bmtub3duKTogYm9vbGVhbiA9PiB7XG4gIGlmICghKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpKSByZXR1cm4gZmFsc2U7XG4gIHJldHVybiBlcnJvci5uYW1lID09PSBcIkNvbmRpdGlvbmFsQ2hlY2tGYWlsZWRFeGNlcHRpb25cIjtcbn07XG5cbmNvbnN0IHBhcnNlQm9keSA9IChldmVudDogQVBJR2F0ZXdheVByb3h5RXZlbnQpOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9PiB7XG4gIGlmICghZXZlbnQuYm9keSkgcmV0dXJuIHt9O1xuICByZXR1cm4gSlNPTi5wYXJzZShldmVudC5ib2R5KSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbn07XG5cbmNvbnN0IGdldENsYWltQm9keSA9IChib2R5OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikgPT4ge1xuICBjb25zdCByZXBvID0gdHlwZW9mIGJvZHkucmVwbyA9PT0gXCJzdHJpbmdcIiA/IGJvZHkucmVwbyA6IFwiXCI7XG4gIGNvbnN0IHByTnVtYmVyID0gTnVtYmVyKGJvZHkucHJOdW1iZXIpO1xuICBjb25zdCBjb21taXRTaGEgPSB0eXBlb2YgYm9keS5jb21taXRTaGEgPT09IFwic3RyaW5nXCIgPyBib2R5LmNvbW1pdFNoYSA6IFwiXCI7XG4gIHJldHVybiB7IHJlcG8sIHByTnVtYmVyLCBjb21taXRTaGEgfTtcbn07XG5cbmNvbnN0IGdldFJlbGVhc2VCb2R5ID0gKGJvZHk6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KSA9PiB7XG4gIGNvbnN0IHJlcG8gPSB0eXBlb2YgYm9keS5yZXBvID09PSBcInN0cmluZ1wiID8gYm9keS5yZXBvIDogXCJcIjtcbiAgY29uc3QgcHJOdW1iZXIgPSBOdW1iZXIoYm9keS5wck51bWJlcik7XG4gIHJldHVybiB7IHJlcG8sIHByTnVtYmVyIH07XG59O1xuXG5jb25zdCBxdWVyeUxlYXNlQnlSZXBvUHIgPSBhc3luYyAoXG4gIHJlcG9QcktleTogc3RyaW5nLFxuKTogUHJvbWlzZTxTbG90TGVhc2UgfCBudWxsPiA9PiB7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGRkYlxuICAgIC5xdWVyeSh7XG4gICAgICBUYWJsZU5hbWU6IHRhYmxlTmFtZSxcbiAgICAgIEluZGV4TmFtZTogXCJSZXBvUHJLZXlJbmRleFwiLFxuICAgICAgS2V5Q29uZGl0aW9uRXhwcmVzc2lvbjogXCJyZXBvUHJLZXkgPSA6cmVwb1ByS2V5XCIsXG4gICAgICBFeHByZXNzaW9uQXR0cmlidXRlVmFsdWVzOiB7XG4gICAgICAgIFwiOnJlcG9QcktleVwiOiByZXBvUHJLZXksXG4gICAgICB9LFxuICAgICAgTGltaXQ6IDEsXG4gICAgfSlcbiAgICAucHJvbWlzZSgpO1xuXG4gIGlmICghcmVzdWx0Lkl0ZW1zIHx8IHJlc3VsdC5JdGVtcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQuSXRlbXNbMF0gYXMgU2xvdExlYXNlO1xufTtcblxuY29uc3QgbG9hZFNsb3RzID0gYXN5bmMgKCk6IFByb21pc2U8XG4gIEFycmF5PFNsb3REZWZpbml0aW9uICYgeyBsZWFzZTogU2xvdExlYXNlIHwgbnVsbCB9PlxuPiA9PiB7XG4gIGlmIChzbG90RGVmaW5pdGlvbnMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG5cbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZGRiXG4gICAgLmJhdGNoR2V0KHtcbiAgICAgIFJlcXVlc3RJdGVtczoge1xuICAgICAgICBbdGFibGVOYW1lXToge1xuICAgICAgICAgIEtleXM6IHNsb3REZWZpbml0aW9ucy5tYXAoKHNsb3QpID0+ICh7XG4gICAgICAgICAgICBzbG90SWQ6IFN0cmluZyhzbG90LnNsb3RJZCksXG4gICAgICAgICAgfSkpLFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9KVxuICAgIC5wcm9taXNlKCk7XG5cbiAgY29uc3QgdGFibGVJdGVtcyA9IChyZXN1bHQuUmVzcG9uc2VzPy5bdGFibGVOYW1lXSA/PyBbXSkgYXMgU2xvdExlYXNlW107XG4gIGNvbnN0IGJ5U2xvdElkID0gbmV3IE1hcDxzdHJpbmcsIFNsb3RMZWFzZT4oKTtcbiAgdGFibGVJdGVtcy5mb3JFYWNoKChpdGVtKSA9PiBieVNsb3RJZC5zZXQoaXRlbS5zbG90SWQsIGl0ZW0pKTtcblxuICByZXR1cm4gc2xvdERlZmluaXRpb25zLm1hcCgoc2xvdCkgPT4gKHtcbiAgICAuLi5zbG90LFxuICAgIGxlYXNlOiBieVNsb3RJZC5nZXQoU3RyaW5nKHNsb3Quc2xvdElkKSkgPz8gbnVsbCxcbiAgfSkpO1xufTtcblxuY29uc3QgY2hvb3NlU2xvdCA9IChcbiAgc2xvdHM6IEFycmF5PFNsb3REZWZpbml0aW9uICYgeyBsZWFzZTogU2xvdExlYXNlIHwgbnVsbCB9PixcbiAgcmVwb1ByS2V5OiBzdHJpbmcsXG4gIG5vdzogbnVtYmVyLFxuKToge1xuICBzbG90OiBTbG90RGVmaW5pdGlvbiAmIHsgbGVhc2U6IFNsb3RMZWFzZSB8IG51bGwgfTtcbiAgZXhwZWN0ZWRMYXN0VXNlZEF0OiBudW1iZXIgfCBudWxsO1xufSA9PiB7XG4gIGNvbnN0IGV4aXN0aW5nID0gc2xvdHMuZmluZCgoc2xvdCkgPT4gc2xvdC5sZWFzZT8ucmVwb1ByS2V5ID09PSByZXBvUHJLZXkpO1xuICBpZiAoZXhpc3RpbmcpIHtcbiAgICByZXR1cm4ge1xuICAgICAgc2xvdDogZXhpc3RpbmcsXG4gICAgICBleHBlY3RlZExhc3RVc2VkQXQ6IE51bWJlcihleGlzdGluZy5sZWFzZT8ubGFzdFVzZWRBdCA/PyAwKSxcbiAgICB9O1xuICB9XG5cbiAgY29uc3QgYXZhaWxhYmxlID0gc2xvdHNcbiAgICAuZmlsdGVyKFxuICAgICAgKHNsb3QpID0+ICFzbG90LmxlYXNlIHx8IE51bWJlcihzbG90LmxlYXNlLmxlYXNlRXhwaXJlc0F0ID8/IDApIDwgbm93LFxuICAgIClcbiAgICAuc29ydChcbiAgICAgIChhLCBiKSA9PlxuICAgICAgICBOdW1iZXIoYS5sZWFzZT8ubGFzdFVzZWRBdCA/PyAwKSAtIE51bWJlcihiLmxlYXNlPy5sYXN0VXNlZEF0ID8/IDApLFxuICAgICk7XG4gIGlmIChhdmFpbGFibGUubGVuZ3RoID4gMCkge1xuICAgIHJldHVybiB7XG4gICAgICBzbG90OiBhdmFpbGFibGVbMF0sXG4gICAgICBleHBlY3RlZExhc3RVc2VkQXQ6IGF2YWlsYWJsZVswXS5sZWFzZVxuICAgICAgICA/IE51bWJlcihhdmFpbGFibGVbMF0ubGVhc2UubGFzdFVzZWRBdCA/PyAwKVxuICAgICAgICA6IG51bGwsXG4gICAgfTtcbiAgfVxuXG4gIGNvbnN0IGxydSA9IFsuLi5zbG90c10uc29ydChcbiAgICAoYSwgYikgPT5cbiAgICAgIE51bWJlcihhLmxlYXNlPy5sYXN0VXNlZEF0ID8/IDApIC0gTnVtYmVyKGIubGVhc2U/Lmxhc3RVc2VkQXQgPz8gMCksXG4gIClbMF07XG4gIHJldHVybiB7XG4gICAgc2xvdDogbHJ1LFxuICAgIGV4cGVjdGVkTGFzdFVzZWRBdDogTnVtYmVyKGxydS5sZWFzZT8ubGFzdFVzZWRBdCA/PyAwKSxcbiAgfTtcbn07XG5cbmNvbnN0IGNsYWltID0gYXN5bmMgKFxuICByZXBvOiBzdHJpbmcsXG4gIHByTnVtYmVyOiBudW1iZXIsXG4gIGNvbW1pdFNoYTogc3RyaW5nLFxuKTogUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHQ+ID0+IHtcbiAgaWYgKCFyZXBvIHx8ICFOdW1iZXIuaXNJbnRlZ2VyKHByTnVtYmVyKSkge1xuICAgIHJldHVybiBiYWRSZXF1ZXN0KFwicmVwbyBhbmQgaW50ZWdlciBwck51bWJlciBhcmUgcmVxdWlyZWRcIik7XG4gIH1cblxuICBjb25zdCByZXBvUHJLZXkgPSB0b1JlcG9QcktleShyZXBvLCBwck51bWJlcik7XG4gIGZvciAobGV0IGF0dGVtcHRzID0gMDsgYXR0ZW1wdHMgPCA1OyBhdHRlbXB0cyArPSAxKSB7XG4gICAgY29uc3Qgbm93ID0gbm93TXMoKTtcbiAgICBjb25zdCBzbG90cyA9IGF3YWl0IGxvYWRTbG90cygpO1xuICAgIGlmIChzbG90cy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiBiYWRSZXF1ZXN0KFwiTm8gcHJldmlldyBzbG90cyBjb25maWd1cmVkXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IHNlbGVjdGlvbiA9IGNob29zZVNsb3Qoc2xvdHMsIHJlcG9QcktleSwgbm93KTtcbiAgICBjb25zdCBzbG90ID0gc2VsZWN0aW9uLnNsb3Q7XG5cbiAgICBjb25zdCBleHByZXNzaW9uQXR0cmlidXRlVmFsdWVzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHtcbiAgICAgIFwiOnJlcG9cIjogcmVwbyxcbiAgICAgIFwiOnByTnVtYmVyXCI6IHByTnVtYmVyLFxuICAgICAgXCI6cmVwb1ByS2V5XCI6IHJlcG9QcktleSxcbiAgICAgIFwiOmNvbW1pdFNoYVwiOiBjb21taXRTaGEsXG4gICAgICBcIjpub3dcIjogbm93LFxuICAgICAgXCI6ZXhwaXJlc0F0XCI6IG5vdyArIG1heExlYXNlTXMsXG4gICAgICBcIjp0dGxFcG9jaFNlY29uZHNcIjogbm93RXBvY2hTZWNvbmRzKCkgKyBNYXRoLmZsb29yKG1heExlYXNlTXMgLyAxMDAwKSxcbiAgICAgIFwiOmJ1Y2tldE5hbWVcIjogc2xvdC5idWNrZXROYW1lLFxuICAgICAgXCI6cHJldmlld1VybFwiOiBzbG90LnByZXZpZXdVcmwsXG4gICAgfTtcblxuICAgIGxldCBjb25kaXRpb25FeHByZXNzaW9uID0gXCJhdHRyaWJ1dGVfbm90X2V4aXN0cyhzbG90SWQpXCI7XG4gICAgaWYgKHNlbGVjdGlvbi5leHBlY3RlZExhc3RVc2VkQXQgIT09IG51bGwpIHtcbiAgICAgIGNvbmRpdGlvbkV4cHJlc3Npb24gPVxuICAgICAgICBcImxhc3RVc2VkQXQgPSA6ZXhwZWN0ZWRMYXN0VXNlZEF0IE9SIHJlcG9QcktleSA9IDpyZXBvUHJLZXlcIjtcbiAgICAgIGV4cHJlc3Npb25BdHRyaWJ1dGVWYWx1ZXNbXCI6ZXhwZWN0ZWRMYXN0VXNlZEF0XCJdID1cbiAgICAgICAgc2VsZWN0aW9uLmV4cGVjdGVkTGFzdFVzZWRBdDtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgZGRiXG4gICAgICAgIC51cGRhdGUoe1xuICAgICAgICAgIFRhYmxlTmFtZTogdGFibGVOYW1lLFxuICAgICAgICAgIEtleTogeyBzbG90SWQ6IFN0cmluZyhzbG90LnNsb3RJZCkgfSxcbiAgICAgICAgICBVcGRhdGVFeHByZXNzaW9uOlxuICAgICAgICAgICAgXCJTRVQgcmVwbyA9IDpyZXBvLCBwck51bWJlciA9IDpwck51bWJlciwgcmVwb1ByS2V5ID0gOnJlcG9QcktleSwgY29tbWl0U2hhID0gOmNvbW1pdFNoYSwgYnVja2V0TmFtZSA9IDpidWNrZXROYW1lLCBwcmV2aWV3VXJsID0gOnByZXZpZXdVcmwsIGxlYXNlZEF0ID0gaWZfbm90X2V4aXN0cyhsZWFzZWRBdCwgOm5vdyksIGxhc3RVc2VkQXQgPSA6bm93LCBsZWFzZUV4cGlyZXNBdCA9IDpleHBpcmVzQXQsIHR0bEVwb2NoU2Vjb25kcyA9IDp0dGxFcG9jaFNlY29uZHNcIixcbiAgICAgICAgICBDb25kaXRpb25FeHByZXNzaW9uOiBjb25kaXRpb25FeHByZXNzaW9uLFxuICAgICAgICAgIEV4cHJlc3Npb25BdHRyaWJ1dGVWYWx1ZXM6IGV4cHJlc3Npb25BdHRyaWJ1dGVWYWx1ZXMsXG4gICAgICAgIH0pXG4gICAgICAgIC5wcm9taXNlKCk7XG5cbiAgICAgIHJldHVybiBvayh7XG4gICAgICAgIHNsb3RJZDogc2xvdC5zbG90SWQsXG4gICAgICAgIGJ1Y2tldE5hbWU6IHNsb3QuYnVja2V0TmFtZSxcbiAgICAgICAgcHJldmlld1VybDogc2xvdC5wcmV2aWV3VXJsLFxuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmICghaXNDb25kaXRpb25hbENoZWNrRmFpbHVyZShlcnJvcikpIHtcbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGNvbmZsaWN0KFwiRmFpbGVkIHRvIGNsYWltIHByZXZpZXcgc2xvdCBkdWUgdG8gY29uY3VycmVudCB1cGRhdGVzXCIpO1xufTtcblxuY29uc3QgaGVhcnRiZWF0ID0gYXN5bmMgKFxuICByZXBvOiBzdHJpbmcsXG4gIHByTnVtYmVyOiBudW1iZXIsXG4gIGNvbW1pdFNoYTogc3RyaW5nLFxuKTogUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHQ+ID0+IHtcbiAgaWYgKCFyZXBvIHx8ICFOdW1iZXIuaXNJbnRlZ2VyKHByTnVtYmVyKSkge1xuICAgIHJldHVybiBiYWRSZXF1ZXN0KFwicmVwbyBhbmQgaW50ZWdlciBwck51bWJlciBhcmUgcmVxdWlyZWRcIik7XG4gIH1cblxuICBjb25zdCByZXBvUHJLZXkgPSB0b1JlcG9QcktleShyZXBvLCBwck51bWJlcik7XG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgcXVlcnlMZWFzZUJ5UmVwb1ByKHJlcG9QcktleSk7XG4gIGlmICghZXhpc3RpbmcpIHtcbiAgICByZXR1cm4gYmFkUmVxdWVzdChcIk5vIGFjdGl2ZSBsZWFzZSBmb3VuZCBmb3IgdGhpcyBwdWxsIHJlcXVlc3RcIik7XG4gIH1cblxuICBjb25zdCBub3cgPSBub3dNcygpO1xuICBhd2FpdCBkZGJcbiAgICAudXBkYXRlKHtcbiAgICAgIFRhYmxlTmFtZTogdGFibGVOYW1lLFxuICAgICAgS2V5OiB7IHNsb3RJZDogZXhpc3Rpbmcuc2xvdElkIH0sXG4gICAgICBVcGRhdGVFeHByZXNzaW9uOlxuICAgICAgICBcIlNFVCBjb21taXRTaGEgPSA6Y29tbWl0U2hhLCBsYXN0VXNlZEF0ID0gOm5vdywgbGVhc2VFeHBpcmVzQXQgPSA6ZXhwaXJlc0F0LCB0dGxFcG9jaFNlY29uZHMgPSA6dHRsRXBvY2hTZWNvbmRzXCIsXG4gICAgICBDb25kaXRpb25FeHByZXNzaW9uOiBcInJlcG9QcktleSA9IDpyZXBvUHJLZXlcIixcbiAgICAgIEV4cHJlc3Npb25BdHRyaWJ1dGVWYWx1ZXM6IHtcbiAgICAgICAgXCI6cmVwb1ByS2V5XCI6IHJlcG9QcktleSxcbiAgICAgICAgXCI6Y29tbWl0U2hhXCI6IGNvbW1pdFNoYSB8fCBleGlzdGluZy5jb21taXRTaGEgfHwgXCJcIixcbiAgICAgICAgXCI6bm93XCI6IG5vdyxcbiAgICAgICAgXCI6ZXhwaXJlc0F0XCI6IG5vdyArIG1heExlYXNlTXMsXG4gICAgICAgIFwiOnR0bEVwb2NoU2Vjb25kc1wiOiBub3dFcG9jaFNlY29uZHMoKSArIE1hdGguZmxvb3IobWF4TGVhc2VNcyAvIDEwMDApLFxuICAgICAgfSxcbiAgICB9KVxuICAgIC5wcm9taXNlKCk7XG5cbiAgcmV0dXJuIG9rKHtcbiAgICBzbG90SWQ6IGV4aXN0aW5nLnNsb3RJZCxcbiAgICBidWNrZXROYW1lOiBleGlzdGluZy5idWNrZXROYW1lLFxuICAgIHByZXZpZXdVcmw6IGV4aXN0aW5nLnByZXZpZXdVcmwsXG4gIH0pO1xufTtcblxuY29uc3QgcmVsZWFzZSA9IGFzeW5jIChcbiAgcmVwbzogc3RyaW5nLFxuICBwck51bWJlcjogbnVtYmVyLFxuKTogUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHQ+ID0+IHtcbiAgaWYgKCFyZXBvIHx8ICFOdW1iZXIuaXNJbnRlZ2VyKHByTnVtYmVyKSkge1xuICAgIHJldHVybiBiYWRSZXF1ZXN0KFwicmVwbyBhbmQgaW50ZWdlciBwck51bWJlciBhcmUgcmVxdWlyZWRcIik7XG4gIH1cblxuICBjb25zdCByZXBvUHJLZXkgPSB0b1JlcG9QcktleShyZXBvLCBwck51bWJlcik7XG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgcXVlcnlMZWFzZUJ5UmVwb1ByKHJlcG9QcktleSk7XG4gIGlmICghZXhpc3RpbmcpIHtcbiAgICByZXR1cm4gb2soeyByZWxlYXNlZDogZmFsc2UgfSk7XG4gIH1cblxuICBhd2FpdCBkZGJcbiAgICAuZGVsZXRlKHtcbiAgICAgIFRhYmxlTmFtZTogdGFibGVOYW1lLFxuICAgICAgS2V5OiB7IHNsb3RJZDogZXhpc3Rpbmcuc2xvdElkIH0sXG4gICAgICBDb25kaXRpb25FeHByZXNzaW9uOiBcInJlcG9QcktleSA9IDpyZXBvUHJLZXlcIixcbiAgICAgIEV4cHJlc3Npb25BdHRyaWJ1dGVWYWx1ZXM6IHtcbiAgICAgICAgXCI6cmVwb1ByS2V5XCI6IHJlcG9QcktleSxcbiAgICAgIH0sXG4gICAgfSlcbiAgICAucHJvbWlzZSgpO1xuXG4gIHJldHVybiBvayh7IHJlbGVhc2VkOiB0cnVlLCBzbG90SWQ6IGV4aXN0aW5nLnNsb3RJZCB9KTtcbn07XG5cbmV4cG9ydCBjb25zdCBoYW5kbGVyID0gYXN5bmMgKFxuICBldmVudDogQVBJR2F0ZXdheVByb3h5RXZlbnQsXG4pOiBQcm9taXNlPEFQSUdhdGV3YXlQcm94eVJlc3VsdD4gPT4ge1xuICBjb25zdCBib2R5ID0gcGFyc2VCb2R5KGV2ZW50KTtcbiAgY29uc3QgcGF0aCA9IGV2ZW50LnBhdGggPz8gXCJcIjtcblxuICBpZiAocGF0aC5lbmRzV2l0aChcIi9jbGFpbVwiKSkge1xuICAgIGNvbnN0IHsgcmVwbywgcHJOdW1iZXIsIGNvbW1pdFNoYSB9ID0gZ2V0Q2xhaW1Cb2R5KGJvZHkpO1xuICAgIHJldHVybiBjbGFpbShyZXBvLCBwck51bWJlciwgY29tbWl0U2hhKTtcbiAgfVxuXG4gIGlmIChwYXRoLmVuZHNXaXRoKFwiL2hlYXJ0YmVhdFwiKSkge1xuICAgIGNvbnN0IHsgcmVwbywgcHJOdW1iZXIsIGNvbW1pdFNoYSB9ID0gZ2V0Q2xhaW1Cb2R5KGJvZHkpO1xuICAgIHJldHVybiBoZWFydGJlYXQocmVwbywgcHJOdW1iZXIsIGNvbW1pdFNoYSk7XG4gIH1cblxuICBpZiAocGF0aC5lbmRzV2l0aChcIi9yZWxlYXNlXCIpKSB7XG4gICAgY29uc3QgeyByZXBvLCBwck51bWJlciB9ID0gZ2V0UmVsZWFzZUJvZHkoYm9keSk7XG4gICAgcmV0dXJuIHJlbGVhc2UocmVwbywgcHJOdW1iZXIpO1xuICB9XG5cbiAgcmV0dXJuIGJhZFJlcXVlc3QoXCJVbnN1cHBvcnRlZCByb3V0ZVwiKTtcbn07XG4iXX0=