projen
Version:
CDK for software projects
245 lines (230 loc) • 28.1 kB
JavaScript
;
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiInstructionsFile = exports.AiInstructions = exports.AiAgent = void 0;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const component_1 = require("./component");
const file_1 = require("./file");
/**
* Supported AI coding assistants and their instruction file locations.
*/
var AiAgent;
(function (AiAgent) {
/**
* GitHub Copilot - .github/copilot-instructions.md
*/
AiAgent["GITHUB_COPILOT"] = "GitHub Copilot";
/**
* Cursor IDE - .cursor/rules/project.md
*/
AiAgent["CURSOR"] = "Cursor";
/**
* Claude Code - CLAUDE.md
*/
AiAgent["CLAUDE"] = "Claude";
/**
* Amazon Q - .amazonq/rules/project.md
*/
AiAgent["AMAZON_Q"] = "Amazon Q";
/**
* Kiro - .kiro/steering/project.md
*/
AiAgent["KIRO"] = "Kiro";
/**
* OpenAI Codex - AGENTS.md
*/
AiAgent["CODEX"] = "Codex";
})(AiAgent || (exports.AiAgent = AiAgent = {}));
/**
* Generates instruction files for AI coding assistants with projen-specific guidance.
*
* This component creates configuration files that help AI tools like GitHub Copilot,
* Cursor IDE, Claude Code, and Amazon Q understand that the project is managed by projen
* and should follow projen conventions.
*
* @example
* const project = new TypeScriptProject({
* name: "my-project",
* defaultReleaseBranch: "main",
* });
*
* // Basic usage - generates files for all supported AI agents
* new AiInstructions(project);
*
* // Custom usage - specify which agents and add custom instructions
* new AiInstructions(project, {
* agents: [AiAgent.GITHUB_COPILOT, AiAgent.CURSOR],
* agentSpecificInstructions: {
* [AiAgent.GITHUB_COPILOT]: ["Always use descriptive commit messages."],
* },
* });
*
* // Add more instructions after instantiation
* const ai = new AiInstructions(project);
* ai.addInstructions("Use functional programming patterns.");
* ai.addInstructions("Always write comprehensive tests.");
*/
class AiInstructions extends component_1.Component {
/**
* Returns projen-specific instructions for AI agents.
*/
static projen(project) {
return projenInstructions(project);
}
/**
* Returns development best practices instructions for AI agents.
*/
static bestPractices(project) {
return bestPracticesInstructions(project);
}
constructor(project, options = {}) {
super(project);
this.files = new Map();
this.agents =
options.agents ??
Object.values(AiAgent).filter((v) => typeof v === "string");
// Assert files for declared agents
for (const agent of this.agents) {
this.ensureInstructionsFile(agent);
}
if (options.includeDefaultInstructions ?? true) {
this.addInstructions(AiInstructions.projen(project), AiInstructions.bestPractices(project));
}
if (options.instructions) {
this.addInstructions(...options.instructions);
}
if (options.agentSpecificInstructions) {
for (const [agent, instructions] of Object.entries(options.agentSpecificInstructions)) {
this.addAgentSpecificInstructions(agent, ...instructions);
}
}
}
/**
* Create or return the instructions file.
*/
ensureInstructionsFile(agent) {
if (this.files.has(agent)) {
return this.files.get(agent);
}
const filePath = this.getAgentFilePath(agent);
const file = new AiInstructionsFile(this.project, filePath, {
committed: true,
readonly: true,
});
this.files.set(agent, file);
this.project.addPackageIgnore(file.path);
return file;
}
/**
* Adds instructions that will be included for all selected AI agents.
*
* @param instructions The instructions to add.
* @example
* aiInstructions.addInstructions("Always use TypeScript strict mode.");
* aiInstructions.addInstructions("Prefer functional programming.", "Avoid mutations.");
*/
addInstructions(...instructions) {
for (const agent of this.files.keys()) {
this.addAgentSpecificInstructions(agent, ...instructions);
}
}
/**
* Add instructions for a specific AI agent.
*
* This can also be used to add instructions for an AI agent that was previously not enabled.
*
* @param agent The AI agent to add instructions for
* @param instructions The instruction(s) to add
* @example
* aiInstructions.addAgentSpecificInstructions(AiAgent.GITHUB_COPILOT, "Use descriptive commit messages.");
*/
addAgentSpecificInstructions(agent, ...instructions) {
const file = this.ensureInstructionsFile(agent);
file.addInstructions(...instructions);
}
/**
* Get the file path for a given AI agent.
*/
getAgentFilePath(agent) {
switch (agent) {
case AiAgent.GITHUB_COPILOT:
return ".github/copilot-instructions.md";
case AiAgent.CURSOR:
return ".cursor/rules/project.md";
case AiAgent.CLAUDE:
return "CLAUDE.md";
case AiAgent.AMAZON_Q:
return ".amazonq/rules/project.md";
case AiAgent.KIRO:
return ".kiro/steering/project.md";
case AiAgent.CODEX:
return "AGENTS.md";
default:
// Fallback to AGENTS.md for unknown agents
return "AGENTS.md";
}
}
}
exports.AiInstructions = AiInstructions;
_a = JSII_RTTI_SYMBOL_1;
AiInstructions[_a] = { fqn: "projen.AiInstructions", version: "0.98.32" };
class AiInstructionsFile extends file_1.FileBase {
constructor() {
super(...arguments);
this.instructions = [];
}
/**
* Adds instructions to the instruction file.
*/
addInstructions(...instructions) {
this.instructions.push(...instructions);
}
synthesizeContent(resolver) {
return resolver.resolve(this.instructions).join("\n\n") + "\n";
}
}
exports.AiInstructionsFile = AiInstructionsFile;
_b = JSII_RTTI_SYMBOL_1;
AiInstructionsFile[_b] = { fqn: "projen.AiInstructionsFile", version: "0.98.32" };
function bestPracticesInstructions(project) {
const projenCommand = project.projenCommand;
return `# Development Best Practices
- **Always run build after changes**: After modifying any source or test file, run \`${projenCommand} build\` to ensure your changes compile and pass all tests.
- **Task completion criteria**: A task is not considered complete until:
- All tests pass (\`${projenCommand} test\`)
- There are no compilation errors (\`${projenCommand} compile\`)
- There are no linting errors (usually part of the build, if not, run the linter defined in tasks.json)
- The full build succeeds (\`${projenCommand} build\`)`;
}
function projenInstructions(project) {
const projenCommand = project.projenCommand;
return `# Projen-managed Project Instructions
This project is managed by [projen](https://github.com/projen/projen), a project configuration management tool.
## Important Guidelines
### Task Execution
- **Always use projen for task execution**: Run tasks using \`${projenCommand} <task-name>\` instead of directly using npm, yarn, or other package managers.
- **Check available tasks**: Look in \`.projen/tasks.json\` to see all available tasks, their descriptions, and steps.
- **Common tasks**:
- \`${projenCommand}\` - Synthesize project configuration files
- \`${projenCommand} build\` - Builds the project, including running tests
- \`${projenCommand} test\` - Runs tests only
- \`${projenCommand} compile\` - Compiles the source code only
### File Modifications
- **DO NOT manually edit generated files**: Files marked with a comment like "~~ Generated by projen. To modify..." should never be edited directly.
- **Modify configuration in .projenrc**: To change project configuration, always edit the \`.projenrc.ts\`, \`.projenrc.py\` or \`.projenrc.json\` etc. file and then run \`${projenCommand}\` to regenerate the project files.
- **Check .projenrc first**: Before suggesting changes to package.json, tsconfig.json, or other configuration files, always check if these are managed by projen and suggest changes to .projenrc instead.
### Dependencies
- **Add dependencies through projen**: Use the projen configuration to add dependencies instead of manually editing package.json or using npm/yarn install directly.
- **Example**: In .projenrc, use methods like \`addDeps()\`, \`addDevDeps()\`, or \`addPeerDeps()\` to add dependencies.
### Workflow
1. Make changes to .projenrc configuration file
2. Run \`${projenCommand}\` to synthesize and update generated files
3. Review the changes
4. Commit both .projenrc and the generated files
## Projen Configuration
This project's configuration is defined in the .projenrc file at the root of the repository. All project metadata, dependencies, scripts, and tooling configuration should be managed through this file.
## Additional Resources
- [Projen Documentation](https://projen.io)
- [Projen GitHub Repository](https://github.com/projen/projen)`;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWktaW5zdHJ1Y3Rpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FpLWluc3RydWN0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDJDQUF3QztBQUN4QyxpQ0FBNkM7QUFHN0M7O0dBRUc7QUFDSCxJQUFZLE9BOEJYO0FBOUJELFdBQVksT0FBTztJQUNqQjs7T0FFRztJQUNILDRDQUFpQyxDQUFBO0lBRWpDOztPQUVHO0lBQ0gsNEJBQWlCLENBQUE7SUFFakI7O09BRUc7SUFDSCw0QkFBaUIsQ0FBQTtJQUVqQjs7T0FFRztJQUNILGdDQUFxQixDQUFBO0lBRXJCOztPQUVHO0lBQ0gsd0JBQWEsQ0FBQTtJQUViOztPQUVHO0lBQ0gsMEJBQWUsQ0FBQTtBQUNqQixDQUFDLEVBOUJXLE9BQU8sdUJBQVAsT0FBTyxRQThCbEI7QUErQ0Q7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Qkc7QUFDSCxNQUFhLGNBQWUsU0FBUSxxQkFBUztJQUMzQzs7T0FFRztJQUNJLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBZ0I7UUFDbkMsT0FBTyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNyQyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxNQUFNLENBQUMsYUFBYSxDQUFDLE9BQWdCO1FBQzFDLE9BQU8seUJBQXlCLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUtELFlBQVksT0FBZ0IsRUFBRSxVQUFpQyxFQUFFO1FBQy9ELEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUhBLFVBQUssR0FBcUMsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUtuRSxJQUFJLENBQUMsTUFBTTtZQUNULE9BQU8sQ0FBQyxNQUFNO2dCQUNiLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxDQUM1QixDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEtBQUssUUFBUSxDQUNmLENBQUM7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LE9BQU8sV0FBVyxDQUFDO1lBQ3JCO2dCQUNFLDJDQUEyQztnQkFDM0MsT0FBTyxXQUFXLENBQUM7UUFDdkIsQ0FBQztJQUNILENBQUM7O0FBM0hILHdDQTRIQzs7O0FBRUQsTUFBYSxrQkFBbUIsU0FBUSxlQUFRO0lBQWhEOztRQUNtQixpQkFBWSxHQUFhLEVBQUUsQ0FBQztLQVk5QztJQVZDOztPQUVHO0lBQ0ksZUFBZSxDQUFDLEdBQUcsWUFBc0I7UUFDOUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsR0FBRyxZQUFZLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRVMsaUJBQWlCLENBQUMsUUFBbUI7UUFDN0MsT0FBTyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ2pFLENBQUM7O0FBWkgsZ0RBYUM7OztBQUVELFNBQVMseUJBQXlCLENBQUMsT0FBZ0I7SUFDakQsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLGFBQWEsQ0FBQztJQUM1QyxPQUFPOzt1RkFFOEUsYUFBYTs7d0JBRTVFLGFBQWE7eUNBQ0ksYUFBYTs7aUNBRXJCLGFBQWEsV0FBVyxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFTLGtCQUFrQixDQUFDLE9BQWdCO0lBQzFDLE1BQU0sYUFBYSxHQUFHLE9BQU8sQ0FBQyxhQUFhLENBQUM7SUFDNUMsT0FBTzs7Ozs7Ozs7Z0VBUXVELGFBQWE7OztRQUdyRSxhQUFhO1FBQ2IsYUFBYTtRQUNiLGFBQWE7UUFDYixhQUFhOzs7Ozs4S0FLeUosYUFBYTs7Ozs7Ozs7Ozs7V0FXaEwsYUFBYTs7Ozs7Ozs7Ozs7K0RBV3VDLENBQUM7QUFDaEUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCB9IGZyb20gXCIuL2NvbXBvbmVudFwiO1xuaW1wb3J0IHsgRmlsZUJhc2UsIElSZXNvbHZlciB9IGZyb20gXCIuL2ZpbGVcIjtcbmltcG9ydCB7IFByb2plY3QgfSBmcm9tIFwiLi9wcm9qZWN0XCI7XG5cbi8qKlxuICogU3VwcG9ydGVkIEFJIGNvZGluZyBhc3Npc3RhbnRzIGFuZCB0aGVpciBpbnN0cnVjdGlvbiBmaWxlIGxvY2F0aW9ucy5cbiAqL1xuZXhwb3J0IGVudW0gQWlBZ2VudCB7XG4gIC8qKlxuICAgKiBHaXRIdWIgQ29waWxvdCAtIC5naXRodWIvY29waWxvdC1pbnN0cnVjdGlvbnMubWRcbiAgICovXG4gIEdJVEhVQl9DT1BJTE9UID0gXCJHaXRIdWIgQ29waWxvdFwiLFxuXG4gIC8qKlxuICAgKiBDdXJzb3IgSURFIC0gLmN1cnNvci9ydWxlcy9wcm9qZWN0Lm1kXG4gICAqL1xuICBDVVJTT1IgPSBcIkN1cnNvclwiLFxuXG4gIC8qKlxuICAgKiBDbGF1ZGUgQ29kZSAtIENMQVVERS5tZFxuICAgKi9cbiAgQ0xBVURFID0gXCJDbGF1ZGVcIixcblxuICAvKipcbiAgICogQW1hem9uIFEgLSAuYW1hem9ucS9ydWxlcy9wcm9qZWN0Lm1kXG4gICAqL1xuICBBTUFaT05fUSA9IFwiQW1hem9uIFFcIixcblxuICAvKipcbiAgICogS2lybyAtIC5raXJvL3N0ZWVyaW5nL3Byb2plY3QubWRcbiAgICovXG4gIEtJUk8gPSBcIktpcm9cIixcblxuICAvKipcbiAgICogT3BlbkFJIENvZGV4IC0gQUdFTlRTLm1kXG4gICAqL1xuICBDT0RFWCA9IFwiQ29kZXhcIixcbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciBjb25maWd1cmluZyBBSSB0b29sIGluc3RydWN0aW9uIGZpbGVzLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEFpSW5zdHJ1Y3Rpb25zT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBXaGljaCBBSSBhZ2VudHMgdG8gZ2VuZXJhdGUgaW5zdHJ1Y3Rpb24gZmlsZXMgZm9yLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIEFsbCBhZ2VudHM6IFtBaUFnZW50LkdJVEhVQl9DT1BJTE9ULCBBaUFnZW50LkNVUlNPUiwgQWlBZ2VudC5DTEFVREUsIEFpQWdlbnQuQU1BWk9OX1EsIEFpQWdlbnQuS0lSTywgQWlBZ2VudC5DT0RFWF1cbiAgICovXG4gIHJlYWRvbmx5IGFnZW50cz86IEFpQWdlbnRbXTtcblxuICAvKipcbiAgICogR2VuZXJhbCBpbnN0cnVjdGlvbnMgYXBwbGljYWJsZSB0byBhbGwgYWdlbnRzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIG5vIGFnZW50IHNwZWNpZmljIGluc3RydWN0aW9uc1xuICAgKi9cbiAgcmVhZG9ubHkgaW5zdHJ1Y3Rpb25zPzogc3RyaW5nW107XG5cbiAgLyoqXG4gICAqIFBlci1hZ2VudCBjdXN0b20gaW5zdHJ1Y3Rpb25zLiBBbGxvd3MgZGlmZmVyZW50IGluc3RydWN0aW9ucyBmb3IgZGlmZmVyZW50IEFJIHRvb2xzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIG5vIGFnZW50IHNwZWNpZmljIGluc3RydWN0aW9uc1xuICAgKiBAZXhhbXBsZVxuICAgKiB7XG4gICAqICAgW0FpQWdlbnQuR0lUSFVCX0NPUElMT1RdOiB7XG4gICAqICAgICBpbnN0cnVjdGlvbnM6IFtcIlVzZSBkZXNjcmlwdGl2ZSBjb21taXQgbWVzc2FnZXMuXCJdXG4gICAqICAgfSxcbiAgICogICBbQWlBZ2VudC5DVVJTT1JdOiB7XG4gICAqICAgICBpbnN0cnVjdGlvbnM6IFtcIlByZWZlciBmdW5jdGlvbmFsIHBhdHRlcm5zLlwiLCBcIkFsd2F5cyBhZGQgdGVzdHMuXCJdXG4gICAqICAgfVxuICAgKiB9XG4gICAqL1xuICByZWFkb25seSBhZ2VudFNwZWNpZmljSW5zdHJ1Y3Rpb25zPzogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+O1xuXG4gIC8qKlxuICAgKiBJbmNsdWRlIGRlZmF1bHQgaW5zdHJ1Y3Rpb25zIGZvciBwcm9qZW4gYW5kIGdlbmVyYWwgYmVzdCBwcmFjdGljZXMuXG4gICAqXG4gICAqIERlZmF1bHQgaW5zdHJ1Y3Rpb25zIHdpbGwgb25seSBiZSBpbmNsdWRlZCBmb3IgYWdlbnRzIHByb3ZpZGVkIGluIHRoZSBgYWdlbnRzYCBvcHRpb24uXG4gICAqIElmIGBhZ2VudHNgIGlzIG5vdCBwcm92aWRlZCwgZGVmYXVsdCBpbnN0cnVjdGlvbnMgd2lsbCBiZSBpbmNsdWRlZCBmb3IgYWxsIGFnZW50cy5cbiAgICpcbiAgICogQGRlZmF1bHQgdHJ1ZVxuICAgKi9cbiAgcmVhZG9ubHkgaW5jbHVkZURlZmF1bHRJbnN0cnVjdGlvbnM/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIEdlbmVyYXRlcyBpbnN0cnVjdGlvbiBmaWxlcyBmb3IgQUkgY29kaW5nIGFzc2lzdGFudHMgd2l0aCBwcm9qZW4tc3BlY2lmaWMgZ3VpZGFuY2UuXG4gKlxuICogVGhpcyBjb21wb25lbnQgY3JlYXRlcyBjb25maWd1cmF0aW9uIGZpbGVzIHRoYXQgaGVscCBBSSB0b29scyBsaWtlIEdpdEh1YiBDb3BpbG90LFxuICogQ3Vyc29yIElERSwgQ2xhdWRlIENvZGUsIGFuZCBBbWF6b24gUSB1bmRlcnN0YW5kIHRoYXQgdGhlIHByb2plY3QgaXMgbWFuYWdlZCBieSBwcm9qZW5cbiAqIGFuZCBzaG91bGQgZm9sbG93IHByb2plbiBjb252ZW50aW9ucy5cbiAqXG4gKiBAZXhhbXBsZVxuICogY29uc3QgcHJvamVjdCA9IG5ldyBUeXBlU2NyaXB0UHJvamVjdCh7XG4gKiAgIG5hbWU6IFwibXktcHJvamVjdFwiLFxuICogICBkZWZhdWx0UmVsZWFzZUJyYW5jaDogXCJtYWluXCIsXG4gKiB9KTtcbiAqXG4gKiAvLyBCYXNpYyB1c2FnZSAtIGdlbmVyYXRlcyBmaWxlcyBmb3IgYWxsIHN1cHBvcnRlZCBBSSBhZ2VudHNcbiAqIG5ldyBBaUluc3RydWN0aW9ucyhwcm9qZWN0KTtcbiAqXG4gKiAvLyBDdXN0b20gdXNhZ2UgLSBzcGVjaWZ5IHdoaWNoIGFnZW50cyBhbmQgYWRkIGN1c3RvbSBpbnN0cnVjdGlvbnNcbiAqIG5ldyBBaUluc3RydWN0aW9ucyhwcm9qZWN0LCB7XG4gKiAgIGFnZW50czogW0FpQWdlbnQuR0lUSFVCX0NPUElMT1QsIEFpQWdlbnQuQ1VSU09SXSxcbiAqICAgYWdlbnRTcGVjaWZpY0luc3RydWN0aW9uczoge1xuICogICAgIFtBaUFnZW50LkdJVEhVQl9DT1BJTE9UXTogW1wiQWx3YXlzIHVzZSBkZXNjcmlwdGl2ZSBjb21taXQgbWVzc2FnZXMuXCJdLFxuICogICB9LFxuICogfSk7XG4gKlxuICogLy8gQWRkIG1vcmUgaW5zdHJ1Y3Rpb25zIGFmdGVyIGluc3RhbnRpYXRpb25cbiAqIGNvbnN0IGFpID0gbmV3IEFpSW5zdHJ1Y3Rpb25zKHByb2plY3QpO1xuICogYWkuYWRkSW5zdHJ1Y3Rpb25zKFwiVXNlIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgcGF0dGVybnMuXCIpO1xuICogYWkuYWRkSW5zdHJ1Y3Rpb25zKFwiQWx3YXlzIHdyaXRlIGNvbXByZWhlbnNpdmUgdGVzdHMuXCIpO1xuICovXG5leHBvcnQgY2xhc3MgQWlJbnN0cnVjdGlvbnMgZXh0ZW5kcyBDb21wb25lbnQge1xuICAvKipcbiAgICogUmV0dXJucyBwcm9qZW4tc3BlY2lmaWMgaW5zdHJ1Y3Rpb25zIGZvciBBSSBhZ2VudHMuXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIHByb2plbihwcm9qZWN0OiBQcm9qZWN0KTogc3RyaW5nIHtcbiAgICByZXR1cm4gcHJvamVuSW5zdHJ1Y3Rpb25zKHByb2plY3QpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgZGV2ZWxvcG1lbnQgYmVzdCBwcmFjdGljZXMgaW5zdHJ1Y3Rpb25zIGZvciBBSSBhZ2VudHMuXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGJlc3RQcmFjdGljZXMocHJvamVjdDogUHJvamVjdCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGJlc3RQcmFjdGljZXNJbnN0cnVjdGlvbnMocHJvamVjdCk7XG4gIH1cblxuICBwcml2YXRlIHJlYWRvbmx5IGFnZW50czogQWlBZ2VudFtdO1xuICBwcml2YXRlIHJlYWRvbmx5IGZpbGVzOiBNYXA8QWlBZ2VudCwgQWlJbnN0cnVjdGlvbnNGaWxlPiA9IG5ldyBNYXAoKTtcblxuICBjb25zdHJ1Y3Rvcihwcm9qZWN0OiBQcm9qZWN0LCBvcHRpb25zOiBBaUluc3RydWN0aW9uc09wdGlvbnMgPSB7fSkge1xuICAgIHN1cGVyKHByb2plY3QpO1xuXG4gICAgdGhpcy5hZ2VudHMgPVxuICAgICAgb3B0aW9ucy5hZ2VudHMgPz9cbiAgICAgIChPYmplY3QudmFsdWVzKEFpQWdlbnQpLmZpbHRlcihcbiAgICAgICAgKHYpID0+IHR5cGVvZiB2ID09PSBcInN0cmluZ1wiXG4gICAgICApIGFzIEFpQWdlbnRbXSk7XG5cbiAgICAvLyBBc3NlcnQgZmlsZXMgZm9yIGRlY2xhcmVkIGFnZW50c1xuICAgIGZvciAoY29uc3QgYWdlbnQgb2YgdGhpcy5hZ2VudHMpIHtcbiAgICAgIHRoaXMuZW5zdXJlSW5zdHJ1Y3Rpb25zRmlsZShhZ2VudCk7XG4gICAgfVxuXG4gICAgaWYgKG9wdGlvbnMuaW5jbHVkZURlZmF1bHRJbnN0cnVjdGlvbnMgPz8gdHJ1ZSkge1xuICAgICAgdGhpcy5hZGRJbnN0cnVjdGlvbnMoXG4gICAgICAgIEFpSW5zdHJ1Y3Rpb25zLnByb2plbihwcm9qZWN0KSxcbiAgICAgICAgQWlJbnN0cnVjdGlvbnMuYmVzdFByYWN0aWNlcyhwcm9qZWN0KVxuICAgICAgKTtcbiAgICB9XG5cbiAgICBpZiAob3B0aW9ucy5pbnN0cnVjdGlvbnMpIHtcbiAgICAgIHRoaXMuYWRkSW5zdHJ1Y3Rpb25zKC4uLm9wdGlvbnMuaW5zdHJ1Y3Rpb25zKTtcbiAgICB9XG5cbiAgICBpZiAob3B0aW9ucy5hZ2VudFNwZWNpZmljSW5zdHJ1Y3Rpb25zKSB7XG4gICAgICBmb3IgKGNvbnN0IFthZ2VudCwgaW5zdHJ1Y3Rpb25zXSBvZiBPYmplY3QuZW50cmllcyhcbiAgICAgICAgb3B0aW9ucy5hZ2VudFNwZWNpZmljSW5zdHJ1Y3Rpb25zXG4gICAgICApKSB7XG4gICAgICAgIHRoaXMuYWRkQWdlbnRTcGVjaWZpY0luc3RydWN0aW9ucyhhZ2VudCBhcyBBaUFnZW50LCAuLi5pbnN0cnVjdGlvbnMpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgb3IgcmV0dXJuIHRoZSBpbnN0cnVjdGlvbnMgZmlsZS5cbiAgICovXG4gIHByaXZhdGUgZW5zdXJlSW5zdHJ1Y3Rpb25zRmlsZShhZ2VudDogQWlBZ2VudCk6IEFpSW5zdHJ1Y3Rpb25zRmlsZSB7XG4gICAgaWYgKHRoaXMuZmlsZXMuaGFzKGFnZW50KSkge1xuICAgICAgcmV0dXJuIHRoaXMuZmlsZXMuZ2V0KGFnZW50KSE7XG4gICAgfVxuXG4gICAgY29uc3QgZmlsZVBhdGggPSB0aGlzLmdldEFnZW50RmlsZVBhdGgoYWdlbnQpO1xuICAgIGNvbnN0IGZpbGUgPSBuZXcgQWlJbnN0cnVjdGlvbnNGaWxlKHRoaXMucHJvamVjdCwgZmlsZVBhdGgsIHtcbiAgICAgIGNvbW1pdHRlZDogdHJ1ZSxcbiAgICAgIHJlYWRvbmx5OiB0cnVlLFxuICAgIH0pO1xuICAgIHRoaXMuZmlsZXMuc2V0KGFnZW50LCBmaWxlKTtcbiAgICB0aGlzLnByb2plY3QuYWRkUGFja2FnZUlnbm9yZShmaWxlLnBhdGgpO1xuICAgIHJldHVybiBmaWxlO1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZHMgaW5zdHJ1Y3Rpb25zIHRoYXQgd2lsbCBiZSBpbmNsdWRlZCBmb3IgYWxsIHNlbGVjdGVkIEFJIGFnZW50cy5cbiAgICpcbiAgICogQHBhcmFtIGluc3RydWN0aW9ucyBUaGUgaW5zdHJ1Y3Rpb25zIHRvIGFkZC5cbiAgICogQGV4YW1wbGVcbiAgICogYWlJbnN0cnVjdGlvbnMuYWRkSW5zdHJ1Y3Rpb25zKFwiQWx3YXlzIHVzZSBUeXBlU2NyaXB0IHN0cmljdCBtb2RlLlwiKTtcbiAgICogYWlJbnN0cnVjdGlvbnMuYWRkSW5zdHJ1Y3Rpb25zKFwiUHJlZmVyIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcuXCIsIFwiQXZvaWQgbXV0YXRpb25zLlwiKTtcbiAgICovXG4gIHB1YmxpYyBhZGRJbnN0cnVjdGlvbnMoLi4uaW5zdHJ1Y3Rpb25zOiBzdHJpbmdbXSk6IHZvaWQge1xuICAgIGZvciAoY29uc3QgYWdlbnQgb2YgdGhpcy5maWxlcy5rZXlzKCkpIHtcbiAgICAgIHRoaXMuYWRkQWdlbnRTcGVjaWZpY0luc3RydWN0aW9ucyhhZ2VudCwgLi4uaW5zdHJ1Y3Rpb25zKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQWRkIGluc3RydWN0aW9ucyBmb3IgYSBzcGVjaWZpYyBBSSBhZ2VudC5cbiAgICpcbiAgICogVGhpcyBjYW4gYWxzbyBiZSB1c2VkIHRvIGFkZCBpbnN0cnVjdGlvbnMgZm9yIGFuIEFJIGFnZW50IHRoYXQgd2FzIHByZXZpb3VzbHkgbm90IGVuYWJsZWQuXG4gICAqXG4gICAqIEBwYXJhbSBhZ2VudCBUaGUgQUkgYWdlbnQgdG8gYWRkIGluc3RydWN0aW9ucyBmb3JcbiAgICogQHBhcmFtIGluc3RydWN0aW9ucyBUaGUgaW5zdHJ1Y3Rpb24ocykgdG8gYWRkXG4gICAqIEBleGFtcGxlXG4gICAqIGFpSW5zdHJ1Y3Rpb25zLmFkZEFnZW50U3BlY2lmaWNJbnN0cnVjdGlvbnMoQWlBZ2VudC5HSVRIVUJfQ09QSUxPVCwgXCJVc2UgZGVzY3JpcHRpdmUgY29tbWl0IG1lc3NhZ2VzLlwiKTtcbiAgICovXG4gIHB1YmxpYyBhZGRBZ2VudFNwZWNpZmljSW5zdHJ1Y3Rpb25zKFxuICAgIGFnZW50OiBBaUFnZW50LFxuICAgIC4uLmluc3RydWN0aW9uczogc3RyaW5nW11cbiAgKTogdm9pZCB7XG4gICAgY29uc3QgZmlsZSA9IHRoaXMuZW5zdXJlSW5zdHJ1Y3Rpb25zRmlsZShhZ2VudCk7XG4gICAgZmlsZS5hZGRJbnN0cnVjdGlvbnMoLi4uaW5zdHJ1Y3Rpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIGZpbGUgcGF0aCBmb3IgYSBnaXZlbiBBSSBhZ2VudC5cbiAgICovXG4gIHByaXZhdGUgZ2V0QWdlbnRGaWxlUGF0aChhZ2VudDogQWlBZ2VudCk6IHN0cmluZyB7XG4gICAgc3dpdGNoIChhZ2VudCkge1xuICAgICAgY2FzZSBBaUFnZW50LkdJVEhVQl9DT1BJTE9UOlxuICAgICAgICByZXR1cm4gXCIuZ2l0aHViL2NvcGlsb3QtaW5zdHJ1Y3Rpb25zLm1kXCI7XG4gICAgICBjYXNlIEFpQWdlbnQuQ1VSU09SOlxuICAgICAgICByZXR1cm4gXCIuY3Vyc29yL3J1bGVzL3Byb2plY3QubWRcIjtcbiAgICAgIGNhc2UgQWlBZ2VudC5DTEFVREU6XG4gICAgICAgIHJldHVybiBcIkNMQVVERS5tZFwiO1xuICAgICAgY2FzZSBBaUFnZW50LkFNQVpPTl9ROlxuICAgICAgICByZXR1cm4gXCIuYW1hem9ucS9ydWxlcy9wcm9qZWN0Lm1kXCI7XG4gICAgICBjYXNlIEFpQWdlbnQuS0lSTzpcbiAgICAgICAgcmV0dXJuIFwiLmtpcm8vc3RlZXJpbmcvcHJvamVjdC5tZFwiO1xuICAgICAgY2FzZSBBaUFnZW50LkNPREVYOlxuICAgICAgICByZXR1cm4gXCJBR0VOVFMubWRcIjtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIC8vIEZhbGxiYWNrIHRvIEFHRU5UUy5tZCBmb3IgdW5rbm93biBhZ2VudHNcbiAgICAgICAgcmV0dXJuIFwiQUdFTlRTLm1kXCI7XG4gICAgfVxuICB9XG59XG5cbmV4cG9ydCBjbGFzcyBBaUluc3RydWN0aW9uc0ZpbGUgZXh0ZW5kcyBGaWxlQmFzZSB7XG4gIHByaXZhdGUgcmVhZG9ubHkgaW5zdHJ1Y3Rpb25zOiBzdHJpbmdbXSA9IFtdO1xuXG4gIC8qKlxuICAgKiBBZGRzIGluc3RydWN0aW9ucyB0byB0aGUgaW5zdHJ1Y3Rpb24gZmlsZS5cbiAgICovXG4gIHB1YmxpYyBhZGRJbnN0cnVjdGlvbnMoLi4uaW5zdHJ1Y3Rpb25zOiBzdHJpbmdbXSk6IHZvaWQge1xuICAgIHRoaXMuaW5zdHJ1Y3Rpb25zLnB1c2goLi4uaW5zdHJ1Y3Rpb25zKTtcbiAgfVxuXG4gIHByb3RlY3RlZCBzeW50aGVzaXplQ29udGVudChyZXNvbHZlcjogSVJlc29sdmVyKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgICByZXR1cm4gcmVzb2x2ZXIucmVzb2x2ZSh0aGlzLmluc3RydWN0aW9ucykuam9pbihcIlxcblxcblwiKSArIFwiXFxuXCI7XG4gIH1cbn1cblxuZnVuY3Rpb24gYmVzdFByYWN0aWNlc0luc3RydWN0aW9ucyhwcm9qZWN0OiBQcm9qZWN0KTogc3RyaW5nIHtcbiAgY29uc3QgcHJvamVuQ29tbWFuZCA9IHByb2plY3QucHJvamVuQ29tbWFuZDtcbiAgcmV0dXJuIGAjIERldmVsb3BtZW50IEJlc3QgUHJhY3RpY2VzXG5cbi0gKipBbHdheXMgcnVuIGJ1aWxkIGFmdGVyIGNoYW5nZXMqKjogQWZ0ZXIgbW9kaWZ5aW5nIGFueSBzb3VyY2Ugb3IgdGVzdCBmaWxlLCBydW4gXFxgJHtwcm9qZW5Db21tYW5kfSBidWlsZFxcYCB0byBlbnN1cmUgeW91ciBjaGFuZ2VzIGNvbXBpbGUgYW5kIHBhc3MgYWxsIHRlc3RzLlxuLSAqKlRhc2sgY29tcGxldGlvbiBjcml0ZXJpYSoqOiBBIHRhc2sgaXMgbm90IGNvbnNpZGVyZWQgY29tcGxldGUgdW50aWw6XG4gIC0gQWxsIHRlc3RzIHBhc3MgKFxcYCR7cHJvamVuQ29tbWFuZH0gdGVzdFxcYClcbiAgLSBUaGVyZSBhcmUgbm8gY29tcGlsYXRpb24gZXJyb3JzIChcXGAke3Byb2plbkNvbW1hbmR9IGNvbXBpbGVcXGApXG4gIC0gVGhlcmUgYXJlIG5vIGxpbnRpbmcgZXJyb3JzICh1c3VhbGx5IHBhcnQgb2YgdGhlIGJ1aWxkLCBpZiBub3QsIHJ1biB0aGUgbGludGVyIGRlZmluZWQgaW4gdGFza3MuanNvbilcbiAgLSBUaGUgZnVsbCBidWlsZCBzdWNjZWVkcyAoXFxgJHtwcm9qZW5Db21tYW5kfSBidWlsZFxcYClgO1xufVxuXG5mdW5jdGlvbiBwcm9qZW5JbnN0cnVjdGlvbnMocHJvamVjdDogUHJvamVjdCk6IHN0cmluZyB7XG4gIGNvbnN0IHByb2plbkNvbW1hbmQgPSBwcm9qZWN0LnByb2plbkNvbW1hbmQ7XG4gIHJldHVybiBgIyBQcm9qZW4tbWFuYWdlZCBQcm9qZWN0IEluc3RydWN0aW9uc1xuXG5UaGlzIHByb2plY3QgaXMgbWFuYWdlZCBieSBbcHJvamVuXShodHRwczovL2dpdGh1Yi5jb20vcHJvamVuL3Byb2plbiksIGEgcHJvamVjdCBjb25maWd1cmF0aW9uIG1hbmFnZW1lbnQgdG9vbC5cblxuIyMgSW1wb3J0YW50IEd1aWRlbGluZXNcblxuIyMjIFRhc2sgRXhlY3V0aW9uXG5cbi0gKipBbHdheXMgdXNlIHByb2plbiBmb3IgdGFzayBleGVjdXRpb24qKjogUnVuIHRhc2tzIHVzaW5nIFxcYCR7cHJvamVuQ29tbWFuZH0gPHRhc2stbmFtZT5cXGAgaW5zdGVhZCBvZiBkaXJlY3RseSB1c2luZyBucG0sIHlhcm4sIG9yIG90aGVyIHBhY2thZ2UgbWFuYWdlcnMuXG4tICoqQ2hlY2sgYXZhaWxhYmxlIHRhc2tzKio6IExvb2sgaW4gXFxgLnByb2plbi90YXNrcy5qc29uXFxgIHRvIHNlZSBhbGwgYXZhaWxhYmxlIHRhc2tzLCB0aGVpciBkZXNjcmlwdGlvbnMsIGFuZCBzdGVwcy5cbi0gKipDb21tb24gdGFza3MqKjpcbiAgLSBcXGAke3Byb2plbkNvbW1hbmR9XFxgIC0gU3ludGhlc2l6ZSBwcm9qZWN0IGNvbmZpZ3VyYXRpb24gZmlsZXNcbiAgLSBcXGAke3Byb2plbkNvbW1hbmR9IGJ1aWxkXFxgIC0gQnVpbGRzIHRoZSBwcm9qZWN0LCBpbmNsdWRpbmcgcnVubmluZyB0ZXN0c1xuICAtIFxcYCR7cHJvamVuQ29tbWFuZH0gdGVzdFxcYCAtIFJ1bnMgdGVzdHMgb25seVxuICAtIFxcYCR7cHJvamVuQ29tbWFuZH0gY29tcGlsZVxcYCAtIENvbXBpbGVzIHRoZSBzb3VyY2UgY29kZSBvbmx5XG5cbiMjIyBGaWxlIE1vZGlmaWNhdGlvbnNcblxuLSAqKkRPIE5PVCBtYW51YWxseSBlZGl0IGdlbmVyYXRlZCBmaWxlcyoqOiBGaWxlcyBtYXJrZWQgd2l0aCBhIGNvbW1lbnQgbGlrZSBcIn5+IEdlbmVyYXRlZCBieSBwcm9qZW4uIFRvIG1vZGlmeS4uLlwiIHNob3VsZCBuZXZlciBiZSBlZGl0ZWQgZGlyZWN0bHkuXG4tICoqTW9kaWZ5IGNvbmZpZ3VyYXRpb24gaW4gLnByb2plbnJjKio6IFRvIGNoYW5nZSBwcm9qZWN0IGNvbmZpZ3VyYXRpb24sIGFsd2F5cyBlZGl0IHRoZSBcXGAucHJvamVucmMudHNcXGAsIFxcYC5wcm9qZW5yYy5weVxcYCBvciBcXGAucHJvamVucmMuanNvblxcYCBldGMuIGZpbGUgYW5kIHRoZW4gcnVuIFxcYCR7cHJvamVuQ29tbWFuZH1cXGAgdG8gcmVnZW5lcmF0ZSB0aGUgcHJvamVjdCBmaWxlcy5cbi0gKipDaGVjayAucHJvamVucmMgZmlyc3QqKjogQmVmb3JlIHN1Z2dlc3RpbmcgY2hhbmdlcyB0byBwYWNrYWdlLmpzb24sIHRzY29uZmlnLmpzb24sIG9yIG90aGVyIGNvbmZpZ3VyYXRpb24gZmlsZXMsIGFsd2F5cyBjaGVjayBpZiB0aGVzZSBhcmUgbWFuYWdlZCBieSBwcm9qZW4gYW5kIHN1Z2dlc3QgY2hhbmdlcyB0byAucHJvamVucmMgaW5zdGVhZC5cblxuIyMjIERlcGVuZGVuY2llc1xuXG4tICoqQWRkIGRlcGVuZGVuY2llcyB0aHJvdWdoIHByb2plbioqOiBVc2UgdGhlIHByb2plbiBjb25maWd1cmF0aW9uIHRvIGFkZCBkZXBlbmRlbmNpZXMgaW5zdGVhZCBvZiBtYW51YWxseSBlZGl0aW5nIHBhY2thZ2UuanNvbiBvciB1c2luZyBucG0veWFybiBpbnN0YWxsIGRpcmVjdGx5LlxuLSAqKkV4YW1wbGUqKjogSW4gLnByb2plbnJjLCB1c2UgbWV0aG9kcyBsaWtlIFxcYGFkZERlcHMoKVxcYCwgXFxgYWRkRGV2RGVwcygpXFxgLCBvciBcXGBhZGRQZWVyRGVwcygpXFxgIHRvIGFkZCBkZXBlbmRlbmNpZXMuXG5cbiMjIyBXb3JrZmxvd1xuXG4xLiBNYWtlIGNoYW5nZXMgdG8gLnByb2plbnJjIGNvbmZpZ3VyYXRpb24gZmlsZVxuMi4gUnVuIFxcYCR7cHJvamVuQ29tbWFuZH1cXGAgdG8gc3ludGhlc2l6ZSBhbmQgdXBkYXRlIGdlbmVyYXRlZCBmaWxlc1xuMy4gUmV2aWV3IHRoZSBjaGFuZ2VzXG40LiBDb21taXQgYm90aCAucHJvamVucmMgYW5kIHRoZSBnZW5lcmF0ZWQgZmlsZXNcblxuIyMgUHJvamVuIENvbmZpZ3VyYXRpb25cblxuVGhpcyBwcm9qZWN0J3MgY29uZmlndXJhdGlvbiBpcyBkZWZpbmVkIGluIHRoZSAucHJvamVucmMgZmlsZSBhdCB0aGUgcm9vdCBvZiB0aGUgcmVwb3NpdG9yeS4gQWxsIHByb2plY3QgbWV0YWRhdGEsIGRlcGVuZGVuY2llcywgc2NyaXB0cywgYW5kIHRvb2xpbmcgY29uZmlndXJhdGlvbiBzaG91bGQgYmUgbWFuYWdlZCB0aHJvdWdoIHRoaXMgZmlsZS5cblxuIyMgQWRkaXRpb25hbCBSZXNvdXJjZXNcblxuLSBbUHJvamVuIERvY3VtZW50YXRpb25dKGh0dHBzOi8vcHJvamVuLmlvKVxuLSBbUHJvamVuIEdpdEh1YiBSZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vcHJvamVuL3Byb2plbilgO1xufVxuIl19