@sitblueprint/website-construct 0.1.4 → 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 +85 -0
- package/docs/CHANGELOG.md +26 -0
- package/lambda/index.d.ts +11 -0
- package/lambda/index.js +238 -0
- package/lambda/index.ts +335 -0
- package/lib/index.d.ts +44 -0
- package/lib/index.js +173 -10
- package/lib/index.ts +250 -8
- package/package.json +5 -5
- package/test/website-construct.test.js +212 -8
- package/test/website-construct.test.ts +229 -0
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A reusable [AWS CDK](https://docs.aws.amazon.com/cdk/) construct to deploy a web
|
|
|
6
6
|
|
|
7
7
|
- CDN caching via CloudFont
|
|
8
8
|
- Deployment via S3
|
|
9
|
+
- Dual domain support (e.g., deploy to both `www.example.com` and `example.com` simultaneously)
|
|
9
10
|
- Hardened S3 bucket defaults with bucket-owner-only ACLs and automatic SSE
|
|
10
11
|
- Direct access to the underlying S3 bucket and CloudFront distribution for advanced customization
|
|
11
12
|
|
|
@@ -39,6 +40,7 @@ export class MyWebsiteStack extends cdk.Stack {
|
|
|
39
40
|
domainName: "example.com",
|
|
40
41
|
subdomainName: "www",
|
|
41
42
|
certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc123",
|
|
43
|
+
includeRootDomain: true, // Optional: also deploy to example.com
|
|
42
44
|
},
|
|
43
45
|
});
|
|
44
46
|
|
|
@@ -48,6 +50,89 @@ export class MyWebsiteStack extends cdk.Stack {
|
|
|
48
50
|
}
|
|
49
51
|
```
|
|
50
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
|
+
|
|
51
136
|
## Development
|
|
52
137
|
|
|
53
138
|
- Build: `npm run build`
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ 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
|
+
|
|
30
|
+
## [v0.1.5]
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `includeRootDomain` option to `DomainConfig` to deploy to both subdomain and root domain simultaneously.
|
|
35
|
+
|
|
10
36
|
## [v0.1.4] - 2025-09-30
|
|
11
37
|
|
|
12
38
|
### Security
|
|
@@ -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 {};
|
package/lambda/index.js
ADDED
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFXQSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFrQi9CLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxjQUFjLEVBQUUsQ0FBQztBQUM5QyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSxFQUFFLENBQUM7QUFDL0MsTUFBTSxlQUFlLEdBQXFCLElBQUksQ0FBQyxLQUFLLENBQ2xELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLElBQUksSUFBSSxDQUNyQyxDQUFDO0FBQ0YsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLFVBQVUsQ0FBQyxDQUFDO0FBRWxFLE1BQU0sRUFBRSxHQUFHLENBQUMsSUFBNkIsRUFBeUIsRUFBRSxDQUFDLENBQUM7SUFDcEUsVUFBVSxFQUFFLEdBQUc7SUFDZixPQUFPLEVBQUUsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUU7SUFDL0MsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDO0NBQzNCLENBQUMsQ0FBQztBQUVILE1BQU0sVUFBVSxHQUFHLENBQUMsT0FBZSxFQUF5QixFQUFFLENBQUMsQ0FBQztJQUM5RCxVQUFVLEVBQUUsR0FBRztJQUNmLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRTtJQUMvQyxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsQ0FBQztDQUN6QyxDQUFDLENBQUM7QUFFSCxNQUFNLFFBQVEsR0FBRyxDQUFDLE9BQWUsRUFBeUIsRUFBRSxDQUFDLENBQUM7SUFDNUQsVUFBVSxFQUFFLEdBQUc7SUFDZixPQUFPLEVBQUUsRUFBRSxjQUFj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QUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUU5QixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUM1QixNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekQsT0FBTyxLQUFLLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pELE9BQU8sU0FBUyxDQUFDLElBQUksRUFBRSxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVELElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQzlCLE1BQU0sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hELE9BQU8sT0FBTyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztJQUNqQyxDQUFDO0lBRUQsT0FBTyxVQUFVLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUM7QUF0QlcsUUFBQSxPQUFPLFdBc0JsQiIsInNvdXJjZXNDb250ZW50IjpbInR5cGUgQVBJR2F0ZXdheVByb3h5RXZlbnQgPSB7XG4gIGJvZHk6IHN0cmluZyB8IG51bGw7XG4gIHBhdGg6IHN0cmluZztcbn07XG5cbnR5cGUgQVBJR2F0ZXdheVByb3h5UmVzdWx0ID0ge1xuICBzdGF0dXNDb2RlOiBudW1iZXI7XG4gIGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG4gIGJvZHk6IHN0cmluZztcbn07XG5cbmNvbnN0IEFXUyA9IHJlcXVpcmUoXCJhd3Mtc2RrXCIpO1xuXG50eXBlIFNsb3REZWZpbml0aW9uID0ge1xuICBzbG90SWQ6IG51bWJlcjtcbiAgYnVja2V0TmFtZTogc3RyaW5nO1xuICBwcmV2aWV3VXJsOiBzdHJpbmc7XG59O1xuXG50eXBlIFNsb3RMZWFzZSA9IHtcbiAgc2xvdElkOiBzdHJpbmc7XG4gIHJlcG9QcktleTogc3RyaW5nO1xuICBidWNrZXROYW1lOiBzdHJpbmc7XG4gIHByZXZpZXdVcmw6IHN0cmluZztcbiAgbGFzdFVzZWRBdD86IG51bWJlcjtcbiAgbGVhc2VFeHBpcmVzQXQ/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=
|