gitlab-ci-local
Version:
Tired of pushing to test your .gitlab-ci.yml?
151 lines (150 loc) • 26.1 kB
JavaScript
import Ajv from "ajv";
import assert from "assert";
import chalk from "chalk";
import schema from "./schema/index.js";
import { betterAjvErrors } from "./schema-error.js";
import terminalLink from "terminal-link";
const MAX_ERRORS = 5;
export class Validator {
static jsonSchemaValidation({ pathToExpandedGitLabCi, gitLabCiConfig, argv }) {
const ajv = new Ajv({
verbose: true,
allErrors: true,
allowUnionTypes: true,
validateFormats: false,
strictTypes: false, // to suppress the missing types defined in the gitlab-ci json schema
keywords: ["markdownDescription"],
});
const validate = ajv.compile(schema);
const valid = validate(gitLabCiConfig);
if (valid)
return;
const betterErrors = betterAjvErrors({
data: gitLabCiConfig,
errors: validate.errors,
}).filter(betterError => !argv.ignoreSchemaPaths.includes(betterError.schemaPath));
let e = "";
for (let i = 0, len = betterErrors.length; i < len; i++) {
if (i + 1 > MAX_ERRORS) {
e += `\t... and ${len - MAX_ERRORS} more`;
break;
}
e += chalk `\t• {redBright ${betterErrors[i].message}} at {blueBright ${betterErrors[i].path}} {grey [${betterErrors[i].schemaPath}]}\n`;
}
assert(valid || betterErrors.length == 0, chalk `
{reset Invalid .gitlab-ci.yml configuration!
${e.trimEnd()}
For further troubleshooting, consider either of the following:
\t• Copy the content of {blueBright ${terminalLink(".gitlab-ci-local/expanded-gitlab-ci.yml", pathToExpandedGitLabCi)}} to the ${terminalLink("pipeline editor", "https://docs.gitlab.com/ee/ci/pipeline_editor/")} to debug it
\t• Use --ignore-schema-paths= "#/definitions/tags/minItems" --ignore-schema-paths "#/additionalProperties" to partially disable certain validation rule
\t• Use --json-schema-validation=false to disable schema validation (not recommended)}
`);
}
static needs(jobs, stages) {
const warnings = [];
for (const job of jobs) {
if (job.needs === null || job.needs.length === 0)
continue;
for (const [i, need] of job.needs.entries()) {
if (need.pipeline) {
warnings.push(`${job.name}.needs[${i}].job:${need.job} ignored, pipeline key not supported`);
continue;
}
if (need.project) {
warnings.push(`${job.name}.needs[${i}] ignored, project key not supported`);
continue;
}
const needJob = jobs.find(j => j.baseName === need.job);
if (need.optional && !needJob)
continue;
assert(needJob != null, chalk `needs: [{blueBright ${need.job}}] for {blueBright ${job.baseName}} could not be found`);
const needJobStageIndex = stages.indexOf(needJob.stage);
const jobStageIndex = stages.indexOf(job.stage);
assert(needJobStageIndex <= jobStageIndex, chalk `needs: [{blueBright ${needJob.name}}] for {blueBright ${job.name}} is in a future stage`);
}
}
return warnings;
}
static dependencies(jobs, stages) {
for (const job of jobs) {
if (job.dependencies === null || job.dependencies.length === 0)
continue;
const undefDeps = job.dependencies.filter((j) => !jobs.some(n => n.baseName === j));
assert(undefDeps.length !== job.dependencies.length, chalk `dependencies: [{blueBright ${undefDeps.join(",")}}] for {blueBright ${job.name}} cannot be found`);
for (const dep of job.dependencies) {
const depJob = jobs.find(j => j.baseName === dep);
assert(depJob != null, chalk `dependencies: [{blueBright ${dep}}] for {blueBright ${job.baseName}} could not be found`);
const depJobStageIndex = stages.indexOf(depJob.stage);
const jobStageIndex = stages.indexOf(job.stage);
assert(depJobStageIndex <= jobStageIndex, chalk `dependencies: [{blueBright ${depJob.name}}] for {blueBright ${job.name}} is in a future stage`);
}
}
}
static dependenciesContainment(jobs) {
for (const job of jobs) {
const needs = job.needs;
const dependencies = job.dependencies;
if (needs && needs.length === 0)
continue;
if (!dependencies || !needs)
continue;
const everyIncluded = dependencies.every((dep) => {
return needs.some(n => n.job === dep);
});
const assertMsg = `${job.formattedJobName} needs: '${needs.map(n => n.job).join(",")}' doesn't fully contain dependencies: '${dependencies.join(",")}'`;
assert(everyIncluded, assertMsg);
}
}
/**
* These jobs named are reserved keywords in GitLab CI but does not prevent the pipeline from running
* https://github.com/firecow/gitlab-ci-local/issues/1263
* @param jobsNames
* @private
*/
static potentialIllegalJobName(jobsNames) {
const warnings = [];
for (const jobName of jobsNames) {
if (new Set(["types", "true", "false", "nil"]).has(jobName)) {
warnings.push(`Job name "${jobName}" is a reserved keyword. (https://docs.gitlab.com/ee/ci/jobs/#job-name-limitations)`);
}
}
return warnings;
}
static scriptBlank(jobs) {
for (const job of jobs) {
if (job.trigger)
continue; // Jobs with trigger are allowed to have empty script
assert(job.scripts.length > 0, chalk `{blue ${job.name}} has empty script`);
}
}
static arrayOfStrings(jobs) {
for (const job of jobs) {
if (job.trigger)
continue;
job.beforeScripts.forEach((s) => assert(typeof s === "string", chalk `{blue ${job.name}} before_script contains non string value`));
job.afterScripts.forEach((s) => assert(typeof s === "string", chalk `{blue ${job.name}} after_script contains non string value`));
job.scripts.forEach((s) => assert(typeof s === "string", chalk `{blue ${job.name}} script contains non string value`));
}
}
static async run(jobs, stages) {
const warnings = [];
this.scriptBlank(jobs);
this.arrayOfStrings(jobs);
warnings.push(...this.needs(jobs, stages));
this.dependencies(jobs, stages);
this.dependenciesContainment(jobs);
warnings.push(...this.potentialIllegalJobName(jobs.map(j => j.baseName)));
warnings.push(...this.artifacts(jobs));
return warnings;
}
static artifacts(jobs) {
const warnings = [];
for (const job of jobs) {
if (job.artifacts === null) {
warnings.push(`${job.name}.artifacts is null, ignoring.`);
}
}
return warnings;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sR0FBRyxNQUFNLEtBQUssQ0FBQztBQUV0QixPQUFPLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFDNUIsT0FBTyxLQUFLLE1BQU0sT0FBTyxDQUFDO0FBQzFCLE9BQU8sTUFBTSxNQUFNLG1CQUFtQixDQUFDO0FBQ3ZDLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQztBQUNsRCxPQUFPLFlBQVksTUFBTSxlQUFlLENBQUM7QUFHekMsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFDO0FBRXJCLE1BQU0sT0FBTyxTQUFTO0lBQ2xCLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBRSxFQUFDLHNCQUFzQixFQUFFLGNBQWMsRUFBRSxJQUFJLEVBSXpFO1FBQ0csTUFBTSxHQUFHLEdBQUcsSUFBSSxHQUFHLENBQUM7WUFDaEIsT0FBTyxFQUFFLElBQUk7WUFDYixTQUFTLEVBQUUsSUFBSTtZQUNmLGVBQWUsRUFBRSxJQUFJO1lBQ3JCLGVBQWUsRUFBRSxLQUFLO1lBQ3RCLFdBQVcsRUFBRSxLQUFLLEVBQUUscUVBQXFFO1lBQ3pGLFFBQVEsRUFBRSxDQUFDLHFCQUFxQixDQUFDO1NBQ3BDLENBQUMsQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDckMsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3ZDLElBQUksS0FBSztZQUFFLE9BQU87UUFDbEIsTUFBTSxZQUFZLEdBQUcsZUFBZSxDQUFDO1lBQ2pDLElBQUksRUFBRSxjQUFjO1lBQ3BCLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTTtTQUMxQixDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMs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QztZQUM5RCxDQUFDO1FBQ0wsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDO0lBQ3BCLENBQUM7Q0FDSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBanYgZnJvbSBcImFqdlwiO1xuaW1wb3J0IHtKb2J9IGZyb20gXCIuL2pvYi5qc1wiO1xuaW1wb3J0IGFzc2VydCBmcm9tIFwiYXNzZXJ0XCI7XG5pbXBvcnQgY2hhbGsgZnJvbSBcImNoYWxrXCI7XG5pbXBvcnQgc2NoZW1hIGZyb20gXCIuL3NjaGVtYS9pbmRleC5qc1wiO1xuaW1wb3J0IHtiZXR0ZXJBanZFcnJvcnN9IGZyb20gXCIuL3NjaGVtYS1lcnJvci5qc1wiO1xuaW1wb3J0IHRlcm1pbmFsTGluayBmcm9tIFwidGVybWluYWwtbGlua1wiO1xuaW1wb3J0IHtBcmd2fSBmcm9tIFwiLi9hcmd2LmpzXCI7XG5cbmNvbnN0IE1BWF9FUlJPUlMgPSA1O1xuXG5leHBvcnQgY2xhc3MgVmFsaWRhdG9yIHtcbiAgICBzdGF0aWMganNvblNjaGVtYVZhbGlkYXRpb24gKHtwYXRoVG9FeHBhbmRlZEdpdExhYkNpLCBnaXRMYWJDaUNvbmZpZywgYXJndn06IHtcbiAgICAgICAgcGF0aFRvRXhwYW5kZWRHaXRMYWJDaTogc3RyaW5nO1xuICAgICAgICBnaXRMYWJDaUNvbmZpZzogb2JqZWN0O1xuICAgICAgICBhcmd2OiBBcmd2O1xuICAgIH0pIHtcbiAgICAgICAgY29uc3QgYWp2ID0gbmV3IEFqdih7XG4gICAgICAgICAgICB2ZXJib3NlOiB0cnVlLFxuICAgICAgICAgICAgYWxsRXJyb3JzOiB0cnVlLFxuICAgICAgICAgICAgYWxsb3dVbmlvblR5cGVzOiB0cnVlLFxuICAgICAgICAgICAgdmFsaWRhdGVGb3JtYXRzOiBmYWxzZSxcbiAgICAgICAgICAgIHN0cmljdFR5cGVzOiBmYWxzZSwgLy8gdG8gc3VwcHJlc3MgdGhlIG1pc3NpbmcgdHlwZXMgZGVmaW5lZCBpbiB0aGUgZ2l0bGFiLWNpIGpzb24gc2NoZW1hXG4gICAgICAgICAgICBrZXl3b3JkczogW1wibWFya2Rvd25EZXNjcmlwdGlvblwiXSxcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnN0IHZhbGlkYXRlID0gYWp2LmNvbXBpbGUoc2NoZW1hKTtcbiAgICAgICAgY29uc3QgdmFsaWQgPSB2YWxpZGF0ZShnaXRMYWJDaUNvbmZpZyk7XG4gICAgICAgIGlmICh2YWxpZCkgcmV0dXJuO1xuICAgICAgICBjb25zdCBiZXR0ZXJFcnJvcnMgPSBiZXR0ZXJBanZFcnJvcnMoe1xuICAgICAgICAgICAgZGF0YTogZ2l0TGFiQ2lDb25maWcsXG4gICAgICAgICAgICBlcnJvcnM6IHZhbGlkYXRlLmVycm9ycyxcbiAgICAgICAgfSkuZmlsdGVyKGJldHRlckVycm9yID0+ICFhcmd2Lmlnbm9yZVNjaGVtYVBhdGhzLmluY2x1ZGVzKGJldHRlckVycm9yLnNjaGVtYVBhdGgpKTtcblxuICAgICAgICBsZXQgZTogc3RyaW5nID0gXCJcIjtcbiAgICAgICAgZm9yIChsZXQgaSA9IDAsIGxlbiA9IGJldHRlckVycm9ycy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgaWYgKGkgKyAxID4gTUFYX0VSUk9SUykge1xuICAgICAgICAgICAgICAgIGUgKz0gYFxcdC4uLiBhbmQgJHtsZW4gLSBNQVhfRVJST1JTfSBtb3JlYDtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGUgKz0gY2hhbGtgXFx04oCiIHtyZWRCcmlnaHQgJHtiZXR0ZXJFcnJvcnNbaV0ubWVzc2FnZX19IGF0IHtibHVlQnJpZ2h0ICR7YmV0dGVyRXJyb3JzW2ldLnBhdGh9fSB7Z3JleSBbJHtiZXR0ZXJFcnJvcnNbaV0uc2NoZW1hUGF0aH1dfVxcbmA7XG4gICAgICAgIH1cblxuICAgICAgICBhc3NlcnQodmFsaWQgfHwgYmV0dGVyRXJyb3JzLmxlbmd0aCA9PSAwLCBjaGFsa2BcbntyZXNldCBJbnZhbGlkIC5naXRsYWItY2kueW1sIGNvbmZpZ3VyYXRpb24hXG4ke2UudHJpbUVuZCgpfVxuXG5Gb3IgZnVydGhlciB0cm91Ymxlc2hvb3RpbmcsIGNvbnNpZGVyIGVpdGhlciBvZiB0aGUgZm9sbG93aW5nOlxuXFx04oCiIENvcHkgdGhlIGNvbnRlbnQgb2Yge2JsdWVCcmlnaHQgJHt0ZXJtaW5hbExpbmsoXCIuZ2l0bGFiLWNpLWxvY2FsL2V4cGFuZGVkLWdpdGxhYi1jaS55bWxcIiwgcGF0aFRvRXhwYW5kZWRHaXRMYWJDaSl9fSB0byB0aGUgJHt0ZXJtaW5hbExpbmsoXCJwaXBlbGluZSBlZGl0b3JcIiwgXCJodHRwczovL2RvY3MuZ2l0bGFiLmNvbS9lZS9jaS9waXBlbGluZV9lZGl0b3IvXCIpfSB0byBkZWJ1ZyBpdFxuXFx04oCiIFVzZSAtLWlnbm9yZS1zY2hlbWEtcGF0aHM9IFwiIy9kZWZpbml0aW9ucy90YWdzL21pbkl0ZW1zXCIgLS1pZ25vcmUtc2NoZW1hLXBhdGhzIFwiIy9hZGRpdGlvbmFsUHJvcGVydGllc1wiIHRvIHBhcnRpYWxseSBkaXNhYmxlIGNlcnRhaW4gdmFsaWRhdGlvbiBydWxlXG5cXHTigKIgVXNlIC0tanNvbi1zY2hlbWEtdmFsaWRhdGlvbj1mYWxzZSB0byBkaXNhYmxlIHNjaGVtYSB2YWxpZGF0aW9uIChub3QgcmVjb21tZW5kZWQpfVxuYCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBzdGF0aWMgbmVlZHMgKGpvYnM6IFJlYWRvbmx5QXJyYXk8Sm9iPiwgc3RhZ2VzOiByZWFkb25seSBzdHJpbmdbXSk6IHN0cmluZ1tdIHtcbiAgICAgICAgY29uc3Qgd2FybmluZ3M6IHN0cmluZ1tdID0gW107XG4gICAgICAgIGZvciAoY29uc3Qgam9iIG9mIGpvYnMpIHtcbiAgICAgICAgICAgIGlmIChqb2IubmVlZHMgPT09IG51bGwgfHwgam9iLm5lZWRzLmxlbmd0aCA9PT0gMCkgY29udGludWU7XG5cbiAgICAgICAgICAgIGZvciAoY29uc3QgW2ksIG5lZWRdIG9mIGpvYi5uZWVkcy5lbnRyaWVzKCkpIHtcbiAgICAgICAgICAgICAgICBpZiAobmVlZC5waXBlbGluZSkge1xuICAgICAgICAgICAgICAgICAgICB3YXJuaW5ncy5wdXNoKGAke2pvYi5uYW1lfS5uZWVkc1ske2l9XS5qb2I6JHtuZWVkLmpvYn0gaWdub3JlZCwgcGlwZWxpbmUga2V5IG5vdCBzdXBwb3J0ZWRgKTtcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChuZWVkLnByb2plY3QpIHtcbiAgICAgICAgICAgICAgICAgICAgd2FybmluZ3MucHVzaChgJHtqb2IubmFtZX0ubmVlZHNbJHtpfV0gaWdub3JlZCwgcHJvamVjdCBrZXkgbm90IHN1cHBvcnRlZGApO1xuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY29uc3QgbmVlZEpvYiA9IGpvYnMuZmluZChqID0+IGouYmFzZU5hbWUgPT09IG5lZWQuam9iKTtcbiAgICAgICAgICAgICAgICBpZiAobmVlZC5vcHRpb25hbCAmJiAhbmVlZEpvYikgY29udGludWU7XG4gICAgICAgICAgICAgICAgYXNzZXJ0KG5lZWRKb2IgIT0gbnVsbCwgY2hhbGtgbmVlZHM6IFt7Ymx1ZUJyaWdodCAke25lZWQuam9ifX1dIGZvciB7Ymx1ZUJyaWdodCAke2pvYi5iYXNlTmFtZX19IGNvdWxkIG5vdCBiZSBmb3VuZGApO1xuICAgICAgICAgICAgICAgIGNvbnN0IG5lZWRKb2JTdGFnZUluZGV4ID0gc3RhZ2VzLmluZGV4T2YobmVlZEpvYi5zdGFnZSk7XG4gICAgICAgICAgICAgICAgY29uc3Qgam9iU3RhZ2VJbmRleCA9IHN0YWdlcy5pbmRleE9mKGpvYi5zdGFnZSk7XG4gICAgICAgICAgICAgICAgYXNzZXJ0KG5lZWRKb2JTdGFnZUluZGV4IDw9IGpvYlN0YWdlSW5kZXgsIGNoYWxrYG5lZWRzOiBbe2JsdWVCcmlnaHQgJHtuZWVkSm9iLm5hbWV9fV0gZm9yIHtibHVlQnJpZ2h0ICR7am9iLm5hbWV9fSBpcyBpbiBhIGZ1dHVyZSBzdGFnZWApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHdhcm5pbmdzO1xuICAgIH1cblxuICAgIHByaXZhdGUgc3RhdGljIGRlcGVuZGVuY2llcyAoam9iczogUmVhZG9ubHlBcnJheTxKb2I+LCBzdGFnZXM6IHJlYWRvbmx5IHN0cmluZ1tdKSB7XG4gICAgICAgIGZvciAoY29uc3Qgam9iIG9mIGpvYnMpIHtcbiAgICAgICAgICAgIGlmIChqb2IuZGVwZW5kZW5jaWVzID09PSBudWxsIHx8IGpvYi5kZXBlbmRlbmNpZXMubGVuZ3RoID09PSAwKSBjb250aW51ZTtcblxuICAgICAgICAgICAgY29uc3QgdW5kZWZEZXBzID0gam9iLmRlcGVuZGVuY2llcy5maWx0ZXIoKGopID0+ICFqb2JzLnNvbWUobiA9PiBuLmJhc2VOYW1lID09PSBqKSk7XG4gICAgICAgICAgICBhc3NlcnQodW5kZWZEZXBzLmxlbmd0aCAhPT0gam9iLmRlcGVuZGVuY2llcy5sZW5ndGgsIGNoYWxrYGRlcGVuZGVuY2llczogW3tibHVlQnJpZ2h0ICR7dW5kZWZEZXBzLmpvaW4oXCIsXCIpfX1dIGZvciB7Ymx1ZUJyaWdodCAke2pvYi5uYW1lfX0gY2Fubm90IGJlIGZvdW5kYCk7XG5cbiAgICAgICAgICAgIGZvciAoY29uc3QgZGVwIG9mIGpvYi5kZXBlbmRlbmNpZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBkZXBKb2IgPSBqb2JzLmZpbmQoaiA9PiBqLmJhc2VOYW1lID09PSBkZXApO1xuICAgICAgICAgICAgICAgIGFzc2VydChkZXBKb2IgIT0gbnVsbCwgY2hhbGtgZGVwZW5kZW5jaWVzOiBbe2JsdWVCcmlnaHQgJHtkZXB9fV0gZm9yIHtibHVlQnJpZ2h0ICR7am9iLmJhc2VOYW1lfX0gY291bGQgbm90IGJlIGZvdW5kYCk7XG4gICAgICAgICAgICAgICAgY29uc3QgZGVwSm9iU3RhZ2VJbmRleCA9IHN0YWdlcy5pbmRleE9mKGRlcEpvYi5zdGFnZSk7XG4gICAgICAgICAgICAgICAgY29uc3Qgam9iU3RhZ2VJbmRleCA9IHN0YWdlcy5pbmRleE9mKGpvYi5zdGFnZSk7XG4gICAgICAgICAgICAgICAgYXNzZXJ0KGRlcEpvYlN0YWdlSW5kZXggPD0gam9iU3RhZ2VJbmRleCwgY2hhbGtgZGVwZW5kZW5jaWVzOiBbe2JsdWVCcmlnaHQgJHtkZXBKb2IubmFtZX19XSBmb3Ige2JsdWVCcmlnaHQgJHtqb2IubmFtZX19IGlzIGluIGEgZnV0dXJlIHN0YWdlYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwcml2YXRlIHN0YXRpYyBkZXBlbmRlbmNpZXNDb250YWlubWVudCAoam9iczogUmVhZG9ubHlBcnJheTxKb2I+KSB7XG4gICAgICAgIGZvciAoY29uc3Qgam9iIG9mIGpvYnMpIHtcbiAgICAgICAgICAgIGNvbnN0IG5lZWRzID0gam9iLm5lZWRzO1xuICAgICAgICAgICAgY29uc3QgZGVwZW5kZW5jaWVzID0gam9iLmRlcGVuZGVuY2llcztcbiAgICAgICAgICAgIGlmIChuZWVkcyAmJiBuZWVkcy5sZW5ndGggPT09IDApIGNvbnRpbnVlO1xuICAgICAgICAgICAgaWYgKCFkZXBlbmRlbmNpZXMgfHwgIW5lZWRzKSBjb250aW51ZTtcblxuXG4gICAgICAgICAgICBjb25zdCBldmVyeUluY2x1ZGVkID0gZGVwZW5kZW5jaWVzLmV2ZXJ5KChkZXA6IHN0cmluZykgPT4ge1xuICAgICAgICAgICAgICAgIHJldHVybiBuZWVkcy5zb21lKG4gPT4gbi5qb2IgPT09IGRlcCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGNvbnN0IGFzc2VydE1zZyA9IGAke2pvYi5mb3JtYXR0ZWRKb2JOYW1lfSBuZWVkczogJyR7bmVlZHMubWFwKG4gPT4gbi5qb2IpLmpvaW4oXCIsXCIpfScgZG9lc24ndCBmdWxseSBjb250YWluIGRlcGVuZGVuY2llczogJyR7ZGVwZW5kZW5jaWVzLmpvaW4oXCIsXCIpfSdgO1xuICAgICAgICAgICAgYXNzZXJ0KGV2ZXJ5SW5jbHVkZWQsIGFzc2VydE1zZyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGVzZSBqb2JzIG5hbWVkIGFyZSByZXNlcnZlZCBrZXl3b3JkcyBpbiBHaXRMYWIgQ0kgYnV0IGRvZXMgbm90IHByZXZlbnQgdGhlIHBpcGVsaW5lIGZyb20gcnVubmluZ1xuICAgICAqIGh0dHBzOi8vZ2l0aHViLmNvbS9maXJlY293L2dpdGxhYi1jaS1sb2NhbC9pc3N1ZXMvMTI2M1xuICAgICAqIEBwYXJhbSBqb2JzTmFtZXNcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIHByaXZhdGUgc3RhdGljIHBvdGVudGlhbElsbGVnYWxKb2JOYW1lIChqb2JzTmFtZXM6IHN0cmluZ1tdKSB7XG4gICAgICAgIGNvbnN0IHdhcm5pbmdzID0gW107XG4gICAgICAgIGZvciAoY29uc3Qgam9iTmFtZSBvZiBqb2JzTmFtZXMpIHtcbiAgICAgICAgICAgIGlmIChuZXcgU2V0KFtcInR5cGVzXCIsIFwidHJ1ZVwiLCBcImZhbHNlXCIsIFwibmlsXCJdKS5oYXMoam9iTmFtZSkpIHtcbiAgICAgICAgICAgICAgICB3YXJuaW5ncy5wdXNoKGBKb2IgbmFtZSBcIiR7am9iTmFtZX1cIiBpcyBhIHJlc2VydmVkIGtleXdvcmQuIChodHRwczovL2RvY3MuZ2l0bGFiLmNvbS9lZS9jaS9qb2JzLyNqb2ItbmFtZS1saW1pdGF0aW9ucylgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gd2FybmluZ3M7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBzdGF0aWMgc2NyaXB0QmxhbmsgKGpvYnM6IFJlYWRvbmx5QXJyYXk8Sm9iPikge1xuICAgICAgICBmb3IgKGNvbnN0IGpvYiBvZiBqb2JzKSB7XG4gICAgICAgICAgICBpZiAoam9iLnRyaWdnZXIpIGNvbnRpbnVlOyAvLyBKb2JzIHdpdGggdHJpZ2dlciBhcmUgYWxsb3dlZCB0byBoYXZlIGVtcHR5IHNjcmlwdFxuICAgICAgICAgICAgYXNzZXJ0KGpvYi5zY3JpcHRzLmxlbmd0aCA+IDAsIGNoYWxrYHtibHVlICR7am9iLm5hbWV9fSBoYXMgZW1wdHkgc2NyaXB0YCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwcml2YXRlIHN0YXRpYyBhcnJheU9mU3RyaW5ncyAoam9iczogUmVhZG9ubHlBcnJheTxKb2I+KSB7XG4gICAgICAgIGZvciAoY29uc3Qgam9iIG9mIGpvYnMpIHtcbiAgICAgICAgICAgIGlmIChqb2IudHJpZ2dlcikgY29udGludWU7XG4gICAgICAgICAgICBqb2IuYmVmb3JlU2NyaXB0cy5mb3JFYWNoKChzOiBhbnkpID0+IGFzc2VydCh0eXBlb2YgcyA9PT0gXCJzdHJpbmdcIiwgY2hhbGtge2JsdWUgJHtqb2IubmFtZX19IGJlZm9yZV9zY3JpcHQgY29udGFpbnMgbm9uIHN0cmluZyB2YWx1ZWApKTtcbiAgICAgICAgICAgIGpvYi5hZnRlclNjcmlwdHMuZm9yRWFjaCgoczogYW55KSA9PiBhc3NlcnQodHlwZW9mIHMgPT09IFwic3RyaW5nXCIsIGNoYWxrYHtibHVlICR7am9iLm5hbWV9fSBhZnRlcl9zY3JpcHQgY29udGFpbnMgbm9uIHN0cmluZyB2YWx1ZWApKTtcbiAgICAgICAgICAgIGpvYi5zY3JpcHRzLmZvckVhY2goKHM6IGFueSkgPT4gYXNzZXJ0KHR5cGVvZiBzID09PSBcInN0cmluZ1wiLCBjaGFsa2B7Ymx1ZSAke2pvYi5uYW1lfX0gc2NyaXB0IGNvbnRhaW5zIG5vbiBzdHJpbmcgdmFsdWVgKSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBzdGF0aWMgYXN5bmMgcnVuIChqb2JzOiBSZWFkb25seUFycmF5PEpvYj4sIHN0YWdlczogcmVhZG9ubHkgc3RyaW5nW10pIHtcbiAgICAgICAgY29uc3Qgd2FybmluZ3M6IHN0cmluZ1tdID0gW107XG4gICAgICAgIHRoaXMuc2NyaXB0Qmxhbmsoam9icyk7XG4gICAgICAgIHRoaXMuYXJyYXlPZlN0cmluZ3Moam9icyk7XG4gICAgICAgIHdhcm5pbmdzLnB1c2goLi4udGhpcy5uZWVkcyhqb2JzLCBzdGFnZXMpKTtcbiAgICAgICAgdGhpcy5kZXBlbmRlbmNpZXMoam9icywgc3RhZ2VzKTtcbiAgICAgICAgdGhpcy5kZXBlbmRlbmNpZXNDb250YWlubWVudChqb2JzKTtcbiAgICAgICAgd2FybmluZ3MucHVzaCguLi50aGlzLnBvdGVudGlhbElsbGVnYWxKb2JOYW1lKGpvYnMubWFwKGogPT4gai5iYXNlTmFtZSkpKTtcbiAgICAgICAgd2FybmluZ3MucHVzaCguLi50aGlzLmFydGlmYWN0cyhqb2JzKSk7XG4gICAgICAgIHJldHVybiB3YXJuaW5ncztcbiAgICB9XG5cbiAgICBwcml2YXRlIHN0YXRpYyBhcnRpZmFjdHMgKGpvYnM6IFJlYWRvbmx5QXJyYXk8Sm9iPikge1xuICAgICAgICBjb25zdCB3YXJuaW5nczogc3RyaW5nW10gPSBbXTtcbiAgICAgICAgZm9yIChjb25zdCBqb2Igb2Ygam9icykge1xuICAgICAgICAgICAgaWYgKGpvYi5hcnRpZmFjdHMgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICB3YXJuaW5ncy5wdXNoKGAke2pvYi5uYW1lfS5hcnRpZmFjdHMgaXMgbnVsbCwgaWdub3JpbmcuYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHdhcm5pbmdzO1xuICAgIH1cbn1cbiJdfQ==