aws-delivlib 13.5.19 → 13.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,316 @@
1
+ import { execSync } from 'child_process';
2
+ import type { RequestOptions, IncomingMessage } from 'http';
3
+ import * as https from 'https';
4
+ import * as os from 'os';
5
+ import * as path from 'path';
6
+ // eslint-disable-next-line import/no-extraneous-dependencies
7
+ import AdmZip from 'adm-zip';
8
+ // eslint-disable-next-line import/no-extraneous-dependencies
9
+ import * as fs from 'fs-extra';
10
+ // eslint-disable-next-line import/no-extraneous-dependencies
11
+ import * as jstream from 'JSONStream';
12
+ // eslint-disable-next-line import/no-extraneous-dependencies
13
+ import * as tar from 'tar';
14
+ import { Repository } from './repository';
15
+
16
+
17
+ /**
18
+ * Published package.
19
+ */
20
+ export interface PublishedPackage {
21
+
22
+ /**
23
+ * Name of the package as stored in the package manager.
24
+ */
25
+ readonly name: string;
26
+
27
+ /**
28
+ * Version of the package as stored in the package manager.
29
+ */
30
+ readonly version: string;
31
+ }
32
+
33
+ /**
34
+ * Integrity class for validating a local artifact against its published counterpart.
35
+ *
36
+ * Implementations differ based on the package manager in question.
37
+ */
38
+ export abstract class ArtifactIntegrity {
39
+
40
+ /**
41
+ * The file extenstion of artifacts produced for this check. (e.g 'whl')
42
+ */
43
+ protected abstract readonly ext: string;
44
+
45
+ /**
46
+ * Download a package to the target file.
47
+ *
48
+ * @param pkg The package to download.
49
+ * @param targetFile The file path to download the package to.
50
+ */
51
+ protected abstract download(pkg: PublishedPackage, targetFile: string): Promise<void>;
52
+
53
+ /**
54
+ * Extract the artifact into the target directory.
55
+ *
56
+ * @param artifact Path to an artifact file.
57
+ * @param targetDir The directory to extract to. It will exist by the time this method is invoked.
58
+ */
59
+ protected abstract extract(artifact: string, targetDir: string): Promise<void>;
60
+
61
+ /**
62
+ * Parse a local artifact file name into a structured package.
63
+ *
64
+ * @param artifactName Base name of the local artifact file.
65
+ * @returns The package this artifact correlates to.
66
+ */
67
+ protected abstract parseArtifactName(artifactName: string): PublishedPackage;
68
+
69
+ /**
70
+ * Validate a local artifact against its published counterpart.
71
+ *
72
+ * @param localArtifactDir The directory of the local artifact. Must contain exactly one file with the appropriate extenstion.
73
+ */
74
+ public async validate(localArtifactDir: string) {
75
+
76
+ const artifactPath = this.findOne(localArtifactDir);
77
+ const name = this.constructor.name;
78
+
79
+ this.log(`Validating ${artifactPath}`);
80
+ const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'integrity-check'));
81
+
82
+ try {
83
+ const downloaded = path.join(workdir, `${name}.downloaded`);
84
+ const remote = path.join(workdir, `${name}.remote`);
85
+ const local = path.join(workdir, `${name}.local`);
86
+
87
+ // parse the artifact name into a package.
88
+ const pkg = this.parseArtifactName(path.basename(artifactPath));
89
+
90
+ fs.mkdirSync(remote);
91
+ fs.mkdirSync(local);
92
+
93
+ // download the package
94
+ this.log(`Downloading ${pkg.name}@${pkg.version} to ${downloaded}`);
95
+ await this.download(pkg, downloaded);
96
+
97
+ // extract the downlaoded package
98
+ this.log(`Extracting remote artifact from ${downloaded} to ${remote}`);
99
+ await this.extract(downloaded, remote);
100
+
101
+ // extract the local artfiact
102
+ this.log(`Extracting local artifact from ${artifactPath} to ${local}`);
103
+ await this.extract(artifactPath, local);
104
+
105
+ this.log(`Comparing ${local} <> ${remote}`);
106
+ try {
107
+ execSync(`diff ${local} ${remote}`, { stdio: ['ignore', 'inherit', 'inherit'] });
108
+ } catch (error) {
109
+ throw new Error(`${name} validation failed`);
110
+ }
111
+ this.log('Success');
112
+
113
+ } finally {
114
+ fs.removeSync(workdir);
115
+ }
116
+
117
+ }
118
+
119
+ protected log(message: string) {
120
+ console.log(`${this.constructor.name} | ${message} `);
121
+ }
122
+
123
+ private findOne(dir: string): string {
124
+ const files = fs.readdirSync(dir).filter(f => f.endsWith(this.ext));
125
+ if (files.length === 0) {
126
+ throw new Error(`No files found in ${dir} with extension ${this.ext}`);
127
+ }
128
+ const [first, ...rest] = files;
129
+ if (rest.length > 0) {
130
+ throw new Error(`Multiple files found in ${dir} with extension ${this.ext}: ${first}, ${rest.join(', ')}`);
131
+ }
132
+ return path.join(dir, first);
133
+ }
134
+
135
+ }
136
+
137
+ /**
138
+ * Properties for `RepositoryIntegrity`.
139
+ */
140
+ export interface RepositoryIntegrityProps {
141
+ /**
142
+ * Repository to validate.
143
+ */
144
+ readonly repository: Repository;
145
+
146
+ /**
147
+ * The command that produces the local artifacts.
148
+ *
149
+ * @default 'npx projen release'
150
+ */
151
+ readonly packCommand?: string;
152
+ }
153
+
154
+ /**
155
+ * Integrity class for validating the artifacts produced by this repository against their published counterparts.
156
+ */
157
+ export class RepositoryIntegrity {
158
+
159
+ public constructor(private readonly props: RepositoryIntegrityProps) {}
160
+
161
+ /**
162
+ * Validate the artifacts of this repo against its published counterpart.
163
+ */
164
+ public async validate() {
165
+
166
+ // note that run 'release' by default to preserve the version number.
167
+ // this won't do a bump since the commit we are on is already tagged.
168
+ const artifacts = this.props.repository.pack(this.props.packCommand ?? 'npx projen release');
169
+
170
+ let integrity = undefined;
171
+ for (const artifact of artifacts) {
172
+ switch (artifact.lang) {
173
+ case 'js':
174
+ integrity = new NpmArtifactIntegrity();
175
+ break;
176
+ case 'python':
177
+ integrity = new PyPIArtifactIntegrity();
178
+ break;
179
+ case 'java':
180
+ case 'dotnet':
181
+ case 'go':
182
+ // we don't have integrity checks for these
183
+ // artifacts yet.
184
+ break;
185
+ default:
186
+ throw new Error(`Unsupported artifact language: ${artifact.lang}`);
187
+ }
188
+ if (integrity) {
189
+ await integrity.validate(artifact.directory);
190
+ }
191
+ }
192
+ console.log('Validation done');
193
+ }
194
+
195
+ }
196
+
197
+ /**
198
+ * NpmIntegiry is able to perform integiry checks against packages stored on npmjs.com
199
+ */
200
+ export class NpmArtifactIntegrity extends ArtifactIntegrity {
201
+
202
+ protected readonly ext = 'tgz';
203
+
204
+ protected async download(pkg: PublishedPackage, target: string): Promise<void> {
205
+ const tarballUrl = await jsonGet(`https://registry.npmjs.org/${encodeURIComponent(pkg.name)}/${encodeURIComponent(pkg.version)}`, ['dist', 'tarball']);
206
+ await download(tarballUrl, target);
207
+ }
208
+
209
+ public async extract(file: string, targetDir: string): Promise<void> {
210
+ return tar.x({ cwd: targetDir, file: file, strip: 1 });
211
+ }
212
+
213
+ protected parseArtifactName(artifactName: string): PublishedPackage {
214
+
215
+ // cdk8s@1.0.0-beta.59.jsii.tgz
216
+ const jsiiArtifact = /(.*)@(.*)\.jsii./;
217
+
218
+ // npm artifact: cdk8s-cli-1.0.0-beta59.tgz
219
+ // yarn artifact: cdk8s-cli-v1.0.0-beta59.tgz (add a 'v' before the version)
220
+ const npmOrYarnArtifact = /(.*)-v?(\d.*).(tgz|tar.gz)/;
221
+
222
+ const regex = artifactName.includes('.jsii.') ? jsiiArtifact : npmOrYarnArtifact;
223
+
224
+ const match = artifactName.match(regex);
225
+ if (!match) {
226
+ throw new Error(`Unable to parse artifact: ${artifactName}`);
227
+ }
228
+
229
+ return { name: match[1], version: match[2] };
230
+
231
+ }
232
+
233
+ }
234
+
235
+ /**
236
+ * PyPIIntegiry is able to perform integiry checks against packages stored on pypi.org
237
+ */
238
+ export class PyPIArtifactIntegrity extends ArtifactIntegrity {
239
+
240
+ protected readonly ext = 'whl';
241
+
242
+ protected async download(pkg: PublishedPackage, target: string): Promise<void> {
243
+
244
+ const files = await jsonGet(`https://pypi.org/pypi/${encodeURIComponent(pkg.name)}/json`, ['releases', pkg.version]);
245
+ const wheels: string[] = files.filter((f: any) => f.url.endsWith('whl')).map((f: any) => f.url);
246
+
247
+ if (wheels.length === 0) {
248
+ throw new Error(`No wheels found for package ${pkg.name}-${pkg.version}`);
249
+ }
250
+
251
+ if (wheels.length > 1) {
252
+ throw new Error(`Multiple wheels found for package ${pkg.name}-${pkg.version}: ${wheels.join(',')}`);
253
+ }
254
+
255
+ await download(wheels[0], target);
256
+ }
257
+
258
+ public async extract(artifact: string, target: string): Promise<void> {
259
+ const zip = new AdmZip(artifact);
260
+ return zip.extractAllTo(target);
261
+ }
262
+
263
+ protected parseArtifactName(artifactName: string): PublishedPackage {
264
+
265
+ // cdk8s-1.0.0b63-py3-none-any.whl
266
+ const regex = /(.*)-v?(\d.*)-py.*.whl/;
267
+
268
+ const match = artifactName.match(regex);
269
+ if (!match) {
270
+ throw new Error(`Unable to parse artifact: ${artifactName}`);
271
+ }
272
+
273
+ return { name: match[1], version: match[2] };
274
+
275
+ }
276
+
277
+ }
278
+
279
+ export function jsonGet(url: string, jsonPath?: string[]): Promise<any> {
280
+ return get(url, (res, ok, ko) => {
281
+ const json = jstream.parse(jsonPath);
282
+ json.once('data', ok);
283
+ json.once('error', ko);
284
+
285
+ res.pipe(json, { end: true });
286
+ }, { headers: { 'Accept': 'application/json', 'Accept-Encoding': 'identity' } });
287
+ }
288
+
289
+ export async function download(url: string, targetFile: string): Promise<any> {
290
+ return get(url, (res, ok, ko) => {
291
+ const file = fs.createWriteStream(targetFile);
292
+ file.on('finish', ok);
293
+ file.on('error', ko);
294
+ res.pipe(file, { end: true });
295
+ });
296
+ }
297
+
298
+ export async function get(
299
+ url: string,
300
+ handler: (res: IncomingMessage, ok: (value: unknown) => void, ko: (err: Error) => void) => void,
301
+ options: RequestOptions = {}) {
302
+
303
+ return new Promise((ok, ko) => {
304
+ const request = https.get(url, options, (res: IncomingMessage) => {
305
+ if (res.statusCode !== 200) {
306
+ const error = new Error(`GET ${url} - HTTP ${res.statusCode} (${res.statusMessage})`);
307
+ Error.captureStackTrace(error);
308
+ return ko(error);
309
+ }
310
+ res.once('error', ko);
311
+ handler(res, ok, ko);
312
+ });
313
+ request.on('error', ko);
314
+ });
315
+
316
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Properties for `Repository`.
3
+ */
4
+ export interface RepositoryProps {
5
+ /**
6
+ * Local directory where the repository was cloned to.
7
+ */
8
+ readonly repoDir: string;
9
+ }
10
+ /**
11
+ * Artifact produced by this repository.
12
+ */
13
+ export interface Artifact {
14
+ /**
15
+ * Language of the artifact.
16
+ */
17
+ readonly lang: string;
18
+ /**
19
+ * Directory containing the artifact.
20
+ */
21
+ readonly directory: string;
22
+ }
23
+ /**
24
+ * Options for `Repository.fromGitHub`
25
+ */
26
+ export interface RepositoryFromGitHubOptions {
27
+ /**
28
+ * Repository slug (e.g cdk8s-team/cdk8s-core)
29
+ */
30
+ readonly slug: string;
31
+ /**
32
+ * Repository tag.
33
+ *
34
+ * @default - latest tag based on creation date.
35
+ */
36
+ readonly tag?: string;
37
+ /**
38
+ * Prefix for detecting the latest tag of the repo. Only applies if `tag` isn't specified.
39
+ * This is useful for repositories that produce multiple packages, and hence multiple tags
40
+ * for example: https://github.com/cdk8s-team/cdk8s-plus/tags.
41
+ */
42
+ readonly tagPrefix?: string;
43
+ /**
44
+ * ARN of an AWS secrets manager secret containing a GitHub token.
45
+ * Required for private repositories. Recommended for public ones, to avoid throtlling issues.
46
+ *
47
+ * @default - the repository is cloned without credentials.
48
+ */
49
+ readonly githubTokenSecretArn?: string;
50
+ }
51
+ /**
52
+ * Options for `Repository.fromDir`
53
+ */
54
+ export interface RepositoryFromDirOptions {
55
+ /**
56
+ * The directory of the repo.
57
+ */
58
+ readonly repoDir: string;
59
+ }
60
+ /**
61
+ * Repository containing a node project.
62
+ */
63
+ export declare class Repository {
64
+ readonly repoDir: string;
65
+ /**
66
+ * Create a repository from a local directory.
67
+ */
68
+ static fromDir(options: RepositoryFromDirOptions): Promise<Repository>;
69
+ /**
70
+ * Create a repository from a GitHub repository.
71
+ */
72
+ static fromGitHub(options: RepositoryFromGitHubOptions): Promise<Repository>;
73
+ private readonly isJsii;
74
+ private readonly manifest;
75
+ private constructor();
76
+ /**
77
+ * Pack the repository to produce the artifacts.
78
+ */
79
+ pack(command: string): Artifact[];
80
+ private _shell;
81
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.Repository = void 0;
23
+ const child_process_1 = require("child_process");
24
+ const fs = __importStar(require("fs"));
25
+ const os = __importStar(require("os"));
26
+ const path = __importStar(require("path"));
27
+ // eslint-disable-next-line import/no-extraneous-dependencies
28
+ const AWS = __importStar(require("aws-sdk"));
29
+ /**
30
+ * Repository containing a node project.
31
+ */
32
+ class Repository {
33
+ constructor(repoDir) {
34
+ this.repoDir = repoDir;
35
+ const manifestPath = path.join(repoDir, 'package.json');
36
+ this.manifest = JSON.parse(fs.readFileSync(manifestPath, { encoding: 'utf-8' }));
37
+ const isProjen = fs.existsSync(path.join(repoDir, '.projen'));
38
+ const isYarn = fs.existsSync(path.join(repoDir, 'yarn.lock'));
39
+ if (!isProjen) {
40
+ // this makes packing much simpler since projen standrardizes it.
41
+ // for now it will suffice, re-evaluate if a use-case arrises.
42
+ throw new Error('Only projen managed repositories are supported at this time');
43
+ }
44
+ if (!isYarn) {
45
+ // the projen version we use has to match the one in the repo since otherwise
46
+ // synthesis may result in a diff. so we use and enforce a yarn.lock file
47
+ // to make it simpler and not have to worry about other package managers.
48
+ // for now it will suffice, re-evaluate if a use-case arrises.
49
+ throw new Error('Only yarn managed repositories are supported at this time');
50
+ }
51
+ this.isJsii = !!this.manifest.jsii;
52
+ }
53
+ /**
54
+ * Create a repository from a local directory.
55
+ */
56
+ static async fromDir(options) {
57
+ return new Repository(options.repoDir);
58
+ }
59
+ /**
60
+ * Create a repository from a GitHub repository.
61
+ */
62
+ static async fromGitHub(options) {
63
+ const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'work'));
64
+ const sm = new AWS.SecretsManager();
65
+ let token = undefined;
66
+ if (options.githubTokenSecretArn) {
67
+ const secret = await sm.getSecretValue({ SecretId: options.githubTokenSecretArn }).promise();
68
+ token = secret.SecretString;
69
+ }
70
+ const repoDir = fs.mkdtempSync(path.join(workdir, 'repo'));
71
+ console.log(`Cloning ${options.slug} into ${repoDir}`);
72
+ (0, child_process_1.execSync)(`git clone https://${token ? `${token}@` : ''}github.com/${options.slug}.git ${repoDir}`);
73
+ const latestTag = findLatestTag(repoDir, options.tagPrefix);
74
+ (0, child_process_1.execSync)(`git checkout ${latestTag}`, { cwd: repoDir });
75
+ return new Repository(repoDir);
76
+ }
77
+ /**
78
+ * Pack the repository to produce the artifacts.
79
+ */
80
+ pack(command) {
81
+ var _a;
82
+ const installCommand = 'yarn install --frozen-lockfile';
83
+ console.log(`Installing | ${installCommand}`);
84
+ this._shell(installCommand);
85
+ const dist = this.isJsii ? (_a = this.manifest.jsii.outdir) !== null && _a !== void 0 ? _a : 'dist' : 'dist';
86
+ const outdir = path.join(this.repoDir, dist);
87
+ console.log(`Packing | ${command}`);
88
+ // crapy: https://github.com/projen/projen/pull/1631
89
+ this._shell(this.isJsii ? `unset CI && ${command}` : command);
90
+ const artifacts = [];
91
+ for (const lang of fs.readdirSync(outdir)) {
92
+ const langDir = path.join(outdir, lang);
93
+ if (!fs.lstatSync(langDir).isDirectory()) {
94
+ // dist folder may contain files such as changelog.md
95
+ // so we ignore these
96
+ continue;
97
+ }
98
+ artifacts.push({ lang, directory: path.join(outdir, lang) });
99
+ }
100
+ return artifacts;
101
+ }
102
+ _shell(command) {
103
+ (0, child_process_1.execSync)(command, { cwd: this.repoDir, stdio: ['ignore', 'inherit', 'inherit'] });
104
+ }
105
+ }
106
+ exports.Repository = Repository;
107
+ function findLatestTag(repoDir, prefix) {
108
+ const tags = (0, child_process_1.execSync)(`git tag -l --sort=-creatordate "${prefix !== null && prefix !== void 0 ? prefix : ''}*"`, { cwd: repoDir }).toString();
109
+ return tags.split(os.EOL)[0].trim();
110
+ }
111
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3NpdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInJlcG9zaXRvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLGlEQUF5QztBQUN6Qyx1Q0FBeUI7QUFDekIsdUNBQXlCO0FBQ3pCLDJDQUE2QjtBQUM3Qiw2REFBNkQ7QUFDN0QsNkNBQStCO0FBeUUvQjs7R0FFRztBQUNILE1BQWEsVUFBVTtJQXFDckIsWUFBb0MsT0FBZTtRQUFmLFlBQU8sR0FBUCxPQUFPLENBQVE7UUFDakQsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFFeEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUVqRixNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFDOUQsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRTlELElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDYixpRUFBaUU7WUFDakUsOERBQThEO1lBQzlELE1BQU0sSUFBSSxLQUFLLENBQUMsNkRBQTZELENBQUMsQ0FBQztTQUNoRjtRQUVELElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDWCw2RUFBNkU7WUFDN0UseUVBQXlFO1lBQ3pFLHlFQUF5RTtZQUN6RSw4REFBOEQ7WUFDOUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyREFBMkQsQ0FBQyxDQUFDO1NBQzlFO1FBRUQsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsSUFB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V0FBVyxFQUFFLEVBQUU7Z0JBQ3hDLHFEQUFxRDtnQkFDckQscUJBQXFCO2dCQUNyQixTQUFTO2FBQ1Y7WUFDRCxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDOUQ7UUFFRCxPQUFPLFNBQVMsQ0FBQztJQUVuQixDQUFDO0lBRU8sTUFBTSxDQUFDLE9BQWU7UUFDNUIsSUFBQSx3QkFBUSxFQUFDLE9BQU8sRUFBRSxFQUFFLEdBQUcsRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFDLFFBQVEsRUFBRSxTQUFTLEVBQUUsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3BGLENBQUM7Q0FFRjtBQWxHRCxnQ0FrR0M7QUFFRCxTQUFTLGFBQWEsQ0FBQyxPQUFlLEVBQUUsTUFBZTtJQUNyRCxNQUFNLElBQUksR0FBRyxJQUFBLHdCQUFRLEVBQUMsbUNBQW1DLE1BQU0sYUFBTixNQUFNLGNBQU4sTUFBTSxHQUFJLEVBQUUsSUFBSSxFQUFFLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDeEcsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUN0QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZXhlY1N5bmMgfSBmcm9tICdjaGlsZF9wcm9jZXNzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgKiBhcyBBV1MgZnJvbSAnYXdzLXNkayc7XG5cbi8qKlxuICogUHJvcGVydGllcyBmb3IgYFJlcG9zaXRvcnlgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9zaXRvcnlQcm9wcyB7XG4gIC8qKlxuICAgKiBMb2NhbCBkaXJlY3Rvcnkgd2hlcmUgdGhlIHJlcG9zaXRvcnkgd2FzIGNsb25lZCB0by5cbiAgICovXG4gIHJlYWRvbmx5IHJlcG9EaXI6IHN0cmluZztcbn1cblxuLyoqXG4gKiBBcnRpZmFjdCBwcm9kdWNlZCBieSB0aGlzIHJlcG9zaXRvcnkuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXJ0aWZhY3Qge1xuICAvKipcbiAgICogTGFuZ3VhZ2Ugb2YgdGhlIGFydGlmYWN0LlxuICAgKi9cbiAgcmVhZG9ubHkgbGFuZzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBEaXJlY3RvcnkgY29udGFpbmluZyB0aGUgYXJ0aWZhY3QuXG4gICAqL1xuICByZWFkb25seSBkaXJlY3Rvcnk6IHN0cmluZztcbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciBgUmVwb3NpdG9yeS5mcm9tR2l0SHViYFxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9zaXRvcnlGcm9tR2l0SHViT3B0aW9ucyB7XG5cbiAgLyoqXG4gICAqIFJlcG9zaXRvcnkgc2x1ZyAoZS5nIGNkazhzLXRlYW0vY2RrOHMtY29yZSlcbiAgICovXG4gIHJlYWRvbmx5IHNsdWc6IHN0cmluZztcblxuICAvKipcbiAgICogUmVwb3NpdG9yeSB0YWcuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gbGF0ZXN0IHRhZyBiYXNlZCBvbiBjcmVhdGlvbiBkYXRlLlxuICAgKi9cbiAgcmVhZG9ubHkgdGFnPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgICogUHJlZml4IGZvciBkZXRlY3RpbmcgdGhlIGxhdGVzdCB0YWcgb2YgdGhlIHJlcG8uIE9ubHkgYXBwbGllcyBpZiBgdGFnYCBpc24ndCBzcGVjaWZpZWQuXG4gICAgKiBUaGlzIGlzIHVzZWZ1bCBmb3IgcmVwb3NpdG9yaWVzIHRoYXQgcHJvZHVjZSBtdWx0aXBsZSBwYWNrYWdlcywgYW5kIGhlbmNlIG11bHRpcGxlIHRhZ3NcbiAgICAqIGZvciBleGFtcGxlOiBodHRwczovL2dpdGh1Yi5jb20vY2RrOHMtdGVhbS9jZGs4cy1wbHVzL3RhZ3MuXG4gICAgKi9cbiAgcmVhZG9ubHkgdGFnUHJlZml4Pzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBBUk4gb2YgYW4gQVdTIHNlY3JldHMgbWFuYWdlciBzZWNyZXQgY29udGFpbmluZyBhIEdpdEh1YiB0b2tlbi5cbiAgICogUmVxdWlyZWQgZm9yIHByaXZhdGUgcmVwb3NpdG9yaWVzLiBSZWNvbW1lbmRlZCBmb3IgcHVibGljIG9uZXMsIHRvIGF2b2lkIHRocm90bGxpbmcgaXNzdWVzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIHRoZSByZXBvc2l0b3J5IGlzIGNsb25lZCB3aXRob3V0IGNyZWRlbnRpYWxzLlxuICAgKi9cbiAgcmVhZG9ubHkgZ2l0aHViVG9rZW5TZWNyZXRBcm4/OiBzdHJpbmc7XG5cbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciBgUmVwb3NpdG9yeS5mcm9tRGlyYFxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9zaXRvcnlGcm9tRGlyT3B0aW9ucyB7XG5cbiAgLyoqXG4gICAqIFRoZSBkaXJlY3Rvcnkgb2YgdGhlIHJlcG8uXG4gICAqL1xuICByZWFkb25seSByZXBvRGlyOiBzdHJpbmc7XG5cbn1cblxuLyoqXG4gKiBSZXBvc2l0b3J5IGNvbnRhaW5pbmcgYSBub2RlIHByb2plY3QuXG4gKi9cbmV4cG9ydCBjbGFzcyBSZXBvc2l0b3J5IHtcblxuICAvKipcbiAgICogQ3JlYXRlIGEgcmVwb3NpdG9yeSBmcm9tIGEgbG9jYWwgZGlyZWN0b3J5LlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBhc3luYyBmcm9tRGlyKG9wdGlvbnM6IFJlcG9zaXRvcnlGcm9tRGlyT3B0aW9ucyk6IFByb21pc2U8UmVwb3NpdG9yeT4ge1xuICAgIHJldHVybiBuZXcgUmVwb3NpdG9yeShvcHRpb25zLnJlcG9EaXIpO1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIHJlcG9zaXRvcnkgZnJvbSBhIEdpdEh1YiByZXBvc2l0b3J5LlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBhc3luYyBmcm9tR2l0SHViKG9wdGlvbnM6IFJlcG9zaXRvcnlGcm9tR2l0SHViT3B0aW9ucyk6IFByb21pc2U8UmVwb3NpdG9yeT4ge1xuXG4gICAgY29uc3Qgd29ya2RpciA9IGZzLm1rZHRlbXBTeW5jKHBhdGguam9pbihvcy50bXBkaXIoKSwgJ3dvcmsnKSk7XG4gICAgY29uc3Qgc20gPSBuZXcgQVdTLlNlY3JldHNNYW5hZ2VyKCk7XG5cbiAgICBsZXQgdG9rZW4gPSB1bmRlZmluZWQ7XG4gICAgaWYgKG9wdGlvbnMuZ2l0aHViVG9rZW5TZWNyZXRBcm4pIHtcbiAgICAgIGNvbnN0IHNlY3JldCA9IGF3YWl0IHNtLmdldFNlY3JldFZhbHVlKHsgU2VjcmV0SWQ6IG9wdGlvbnMuZ2l0aHViVG9rZW5TZWNyZXRBcm4gfSkucHJvbWlzZSgpO1xuICAgICAgdG9rZW4gPSBzZWNyZXQuU2VjcmV0U3RyaW5nO1xuICAgIH1cbiAgICBjb25zdCByZXBvRGlyID0gZnMubWtkdGVtcFN5bmMocGF0aC5qb2luKHdvcmtkaXIsICdyZXBvJykpO1xuXG4gICAgY29uc29sZS5sb2coYENsb25pbmcgJHtvcHRpb25zLnNsdWd9IGludG8gJHtyZXBvRGlyfWApO1xuICAgIGV4ZWNTeW5jKGBnaXQgY2xvbmUgaHR0cHM6Ly8ke3Rva2VuID8gYCR7dG9rZW59QGAgOiAnJ31naXRodWIuY29tLyR7b3B0aW9ucy5zbHVnfS5naXQgJHtyZXBvRGlyfWApO1xuXG4gICAgY29uc3QgbGF0ZXN0VGFnID0gZmluZExhdGVzdFRhZyhyZXBvRGlyLCBvcHRpb25zLnRhZ1ByZWZpeCk7XG4gICAgZXhlY1N5bmMoYGdpdCBjaGVja291dCAke2xhdGVzdFRhZ31gLCB7IGN3ZDogcmVwb0RpciB9KTtcblxuICAgIHJldHVybiBuZXcgUmVwb3NpdG9yeShyZXBvRGlyKTtcblxuICB9XG5cbiAgcHJpdmF0ZSByZWFkb25seSBpc0pzaWk6IGJvb2xlYW47XG4gIHByaXZhdGUgcmVhZG9ubHkgbWFuaWZlc3Q6IGFueTtcblxuICBwcml2YXRlIGNvbnN0cnVjdG9yKHB1YmxpYyByZWFkb25seSByZXBvRGlyOiBzdHJpbmcpIHtcbiAgICBjb25zdCBtYW5pZmVzdFBhdGggPSBwYXRoLmpvaW4ocmVwb0RpciwgJ3BhY2thZ2UuanNvbicpO1xuXG4gICAgdGhpcy5tYW5pZmVzdCA9IEpTT04ucGFyc2UoZnMucmVhZEZpbGVTeW5jKG1hbmlmZXN0UGF0aCwgeyBlbmNvZGluZzogJ3V0Zi04JyB9KSk7XG5cbiAgICBjb25zdCBpc1Byb2plbiA9IGZzLmV4aXN0c1N5bmMocGF0aC5qb2luKHJlcG9EaXIsICcucHJvamVuJykpO1xuICAgIGNvbnN0IGlzWWFybiA9IGZzLmV4aXN0c1N5bmMocGF0aC5qb2luKHJlcG9EaXIsICd5YXJuLmxvY2snKSk7XG5cbiAgICBpZiAoIWlzUHJvamVuKSB7XG4gICAgICAvLyB0aGlzIG1ha2VzIHBhY2tpbmcgbXVjaCBzaW1wbGVyIHNpbmNlIHByb2plbiBzdGFuZHJhcmRpemVzIGl0LlxuICAgICAgLy8gZm9yIG5vdyBpdCB3aWxsIHN1ZmZpY2UsIHJlLWV2YWx1YXRlIGlmIGEgdXNlLWNhc2UgYXJyaXNlcy5cbiAgICAgIHRocm93IG5ldyBFcnJvcignT25seSBwcm9qZW4gbWFuYWdlZCByZXBvc2l0b3JpZXMgYXJlIHN1cHBvcnRlZCBhdCB0aGlzIHRpbWUnKTtcbiAgICB9XG5cbiAgICBpZiAoIWlzWWFybikge1xuICAgICAgLy8gdGhlIHByb2plbiB2ZXJzaW9uIHdlIHVzZSBoYXMgdG8gbWF0Y2ggdGhlIG9uZSBpbiB0aGUgcmVwbyBzaW5jZSBvdGhlcndpc2VcbiAgICAgIC8vIHN5bnRoZXNpcyBtYXkgcmVzdWx0IGluIGEgZGlmZi4gc28gd2UgdXNlIGFuZCBlbmZvcmNlIGEgeWFybi5sb2NrIGZpbGVcbiAgICAgIC8vIHRvIG1ha2UgaXQgc2ltcGxlciBhbmQgbm90IGhhdmUgdG8gd29ycnkgYWJvdXQgb3RoZXIgcGFja2FnZSBtYW5hZ2Vycy5cbiAgICAgIC8vIGZvciBub3cgaXQgd2lsbCBzdWZmaWNlLCByZS1ldmFsdWF0ZSBpZiBhIHVzZS1jYXNlIGFycmlzZXMuXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ09ubHkgeWFybiBtYW5hZ2VkIHJlcG9zaXRvcmllcyBhcmUgc3VwcG9ydGVkIGF0IHRoaXMgdGltZScpO1xuICAgIH1cblxuICAgIHRoaXMuaXNKc2lpID0gISF0aGlzLm1hbmlmZXN0LmpzaWk7XG4gIH1cblxuICAvKipcbiAgICogUGFjayB0aGUgcmVwb3NpdG9yeSB0byBwcm9kdWNlIHRoZSBhcnRpZmFjdHMuXG4gICAqL1xuICBwdWJsaWMgcGFjayhjb21tYW5kOiBzdHJpbmcpOiBBcnRpZmFjdFtdIHtcblxuICAgIGNvbnN0IGluc3RhbGxDb21tYW5kID0gJ3lhcm4gaW5zdGFsbCAtLWZyb3plbi1sb2NrZmlsZSc7XG4gICAgY29uc29sZS5sb2coYEluc3RhbGxpbmcgfCAke2luc3RhbGxDb21tYW5kfWApO1xuICAgIHRoaXMuX3NoZWxsKGluc3RhbGxDb21tYW5kKTtcblxuICAgIGNvbnN0IGRpc3QgPSB0aGlzLmlzSnNpaSA/IHRoaXMubWFuaWZlc3QuanNpaS5vdXRkaXIgPz8gJ2Rpc3QnIDogJ2Rpc3QnO1xuICAgIGNvbnN0IG91dGRpciA9IHBhdGguam9pbih0aGlzLnJlcG9EaXIsIGRpc3QpO1xuXG4gICAgY29uc29sZS5sb2coYFBhY2tpbmcgfCAke2NvbW1hbmR9YCk7XG5cbiAgICAvLyBjcmFweTogaHR0cHM6Ly9naXRodWIuY29tL3Byb2plbi9wcm9qZW4vcHVsbC8xNjMxXG4gICAgdGhpcy5fc2hlbGwodGhpcy5pc0pzaWkgPyBgdW5zZXQgQ0kgJiYgJHtjb21tYW5kfWAgOiBjb21tYW5kKTtcblxuICAgIGNvbnN0IGFydGlmYWN0czogQXJ0aWZhY3RbXSA9IFtdO1xuICAgIGZvciAoY29uc3QgbGFuZyBvZiBmcy5yZWFkZGlyU3luYyhvdXRkaXIpKSB7XG4gICAgICBjb25zdCBsYW5nRGlyID0gcGF0aC5qb2luKG91dGRpciwgbGFuZyk7XG4gICAgICBpZiAoIWZzLmxzdGF0U3luYyhsYW5nRGlyKS5pc0RpcmVjdG9yeSgpKSB7XG4gICAgICAgIC8vIGRpc3QgZm9sZGVyIG1heSBjb250YWluIGZpbGVzIHN1Y2ggYXMgY2hhbmdlbG9nLm1kXG4gICAgICAgIC8vIHNvIHdlIGlnbm9yZSB0aGVzZVxuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGFydGlmYWN0cy5wdXNoKHsgbGFuZywgZGlyZWN0b3J5OiBwYXRoLmpvaW4ob3V0ZGlyLCBsYW5nKSB9KTtcbiAgICB9XG5cbiAgICByZXR1cm4gYXJ0aWZhY3RzO1xuXG4gIH1cblxuICBwcml2YXRlIF9zaGVsbChjb21tYW5kOiBzdHJpbmcpIHtcbiAgICBleGVjU3luYyhjb21tYW5kLCB7IGN3ZDogdGhpcy5yZXBvRGlyLCBzdGRpbzogWydpZ25vcmUnLCAnaW5oZXJpdCcsICdpbmhlcml0J10gfSk7XG4gIH1cblxufVxuXG5mdW5jdGlvbiBmaW5kTGF0ZXN0VGFnKHJlcG9EaXI6IHN0cmluZywgcHJlZml4Pzogc3RyaW5nKSB7XG4gIGNvbnN0IHRhZ3MgPSBleGVjU3luYyhgZ2l0IHRhZyAtbCAtLXNvcnQ9LWNyZWF0b3JkYXRlIFwiJHtwcmVmaXggPz8gJyd9KlwiYCwgeyBjd2Q6IHJlcG9EaXIgfSkudG9TdHJpbmcoKTtcbiAgcmV0dXJuIHRhZ3Muc3BsaXQob3MuRU9MKVswXS50cmltKCk7XG59XG4iXX0=