├── jest-setup.js ├── cdk.json ├── cdk.context.json ├── dist ├── cdk-test │ ├── lambda │ │ ├── index.d.ts │ │ └── index.js │ ├── josh-stack.d.ts │ ├── index.d.ts │ ├── index.js │ └── josh-stack.js ├── test-util │ ├── index.d.ts │ ├── mock-cdk-test-custom-diff.d.ts │ ├── index.js │ └── mock-cdk-test-raw-diff.d.ts ├── index.d.ts ├── transform.d.ts ├── util.d.ts ├── raw-diff.d.ts ├── render.d.ts ├── custom-diff.d.ts ├── cdk-reverse-engineered.d.ts ├── raw-diff.js ├── index.js ├── types.d.ts ├── util.js ├── custom-diff.js ├── types.js ├── pretty-diff-template.html.d.ts ├── render.js ├── cdk-reverse-engineered.js ├── transform.js └── pretty-diff-template.html.js ├── .gitignore ├── pretty-diff-html-sample.png ├── src ├── cdk-test │ ├── lambda │ │ └── index.ts │ ├── index.ts │ └── josh-stack.ts ├── test-util │ └── index.ts ├── index.ts ├── raw-diff.ts ├── util.ts ├── render.spec.ts ├── custom-diff.ts ├── types.ts ├── render.ts ├── transform.ts ├── cdk-reverse-engineered.ts └── pretty-diff-template.html.ts ├── jest.config.js ├── tsconfig.json ├── bin ├── diff-to-stdout.ts ├── diff-to-html.ts └── diff-to-html-with-cli-args.ts ├── package.json └── README.md /jest-setup.js: -------------------------------------------------------------------------------- 1 | jest.mock('source-map-support'); 2 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "node dist/cdk-test/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws-cdk:enableDiffNoFail": "true" 3 | } 4 | -------------------------------------------------------------------------------- /dist/cdk-test/lambda/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const handler: (event: any) => Promise; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !jest.config.js 2 | node_modules 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /pretty-diff-html-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshweir/cdk-pretty-diff/HEAD/pretty-diff-html-sample.png -------------------------------------------------------------------------------- /src/cdk-test/lambda/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export const handler = async (event: any) => { 3 | console.log('a lambda 2'); 4 | } -------------------------------------------------------------------------------- /src/test-util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-cdk-test-raw-diff'; 2 | export * from './mock-cdk-test-custom-diff'; 3 | -------------------------------------------------------------------------------- /dist/test-util/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-cdk-test-raw-diff'; 2 | export * from './mock-cdk-test-custom-diff'; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './raw-diff'; 2 | export * from './custom-diff'; 3 | export * from './render'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './raw-diff'; 2 | export * from './custom-diff'; 3 | export * from './render'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /dist/test-util/mock-cdk-test-custom-diff.d.ts: -------------------------------------------------------------------------------- 1 | import { NicerStackDiff } from "../types"; 2 | export declare const mockCdkTestCustomDiff: () => NicerStackDiff[]; 3 | -------------------------------------------------------------------------------- /dist/transform.d.ts: -------------------------------------------------------------------------------- 1 | import { NicerStackDiff, StackRawDiff } from "./types"; 2 | export declare const transformDiff: (diff: StackRawDiff) => Promise; 3 | -------------------------------------------------------------------------------- /dist/util.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as stream from 'stream'; 3 | export declare const streamToString: (stream: stream.Writable) => Promise; 4 | -------------------------------------------------------------------------------- /dist/cdk-test/josh-stack.d.ts: -------------------------------------------------------------------------------- 1 | import { Stack, StackProps, App } from "aws-cdk-lib"; 2 | export declare class JoshStack extends Stack { 3 | constructor(scope: App, id: string, props?: StackProps); 4 | } 5 | -------------------------------------------------------------------------------- /dist/cdk-test/index.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as cdk from "aws-cdk-lib"; 3 | import { JoshStack } from "./josh-stack"; 4 | export declare const app: cdk.App; 5 | export declare const stack: JoshStack; 6 | -------------------------------------------------------------------------------- /dist/raw-diff.d.ts: -------------------------------------------------------------------------------- 1 | import { DiffOptions, StackRawDiff } from './types'; 2 | import * as cdk from "aws-cdk-lib"; 3 | export declare const getRawDiff: (app: cdk.App, options?: DiffOptions) => Promise; 4 | -------------------------------------------------------------------------------- /dist/render.d.ts: -------------------------------------------------------------------------------- 1 | import { NicerStackDiff } from "./types"; 2 | export declare const renderCustomDiffToHtmlNodeString: (diffs: NicerStackDiff[]) => string; 3 | export declare const renderCustomDiffToHtmlString: (diffs: NicerStackDiff[], title: string) => string; 4 | -------------------------------------------------------------------------------- /src/cdk-test/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as cdk from "aws-cdk-lib"; 3 | import { JoshStack } from "./josh-stack"; 4 | 5 | export const app = new cdk.App({ 6 | context: { 7 | hello: 'you', 8 | } 9 | }); 10 | export const stack = new JoshStack(app, "JoshStack"); 11 | -------------------------------------------------------------------------------- /dist/custom-diff.d.ts: -------------------------------------------------------------------------------- 1 | import { DiffOptions, NicerStackDiff, StackRawDiff } from './types'; 2 | import * as cdk from "aws-cdk-lib"; 3 | export declare const getCustomDiff: (app: cdk.App, props?: { 4 | rawDiff?: StackRawDiff[]; 5 | options?: DiffOptions; 6 | }) => Promise; 7 | -------------------------------------------------------------------------------- /dist/cdk-reverse-engineered.d.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { DiffOptions, StackRawDiff } from './types'; 3 | export declare const deepSubstituteBracedLogicalIds: (logicalToPathMap: any) => (rows: any) => any; 4 | export declare function getDiffObject(app: cdk.App, options?: DiffOptions): Promise; 5 | -------------------------------------------------------------------------------- /src/raw-diff.ts: -------------------------------------------------------------------------------- 1 | import { DiffOptions, StackRawDiff } from './types'; 2 | import { getDiffObject } from './cdk-reverse-engineered'; 3 | import * as cdk from "aws-cdk-lib"; 4 | 5 | export const getRawDiff = async (app: cdk.App, options?: DiffOptions): Promise => { 6 | return await getDiffObject(app, options); 7 | }; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | 'ts-jest': { 4 | isolatedModules: true 5 | } 6 | }, 7 | testEnvironment: 'node', 8 | testRegex: '(/__tests__/.*|(src|test)/.*(\\.|-|/)(test|spec))\\.(jsx?|tsx?)$', 9 | transform: { 10 | "^.+\\.tsx?$": "ts-jest" 11 | }, 12 | setupFiles: [ 13 | "./jest-setup.js" 14 | ], 15 | moduleFileExtensions: [ 16 | 'ts', 17 | 'tsx', 18 | 'json', 19 | 'js', 20 | 'jsx' 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as stream from 'stream'; 2 | 3 | export const streamToString = async (stream: stream.Writable): Promise => { 4 | let str: string = '' 5 | 6 | return new Promise (function (resolve, reject) { 7 | stream.on('data', function (data) { 8 | str += data.toString() 9 | }) 10 | stream.on('end', function () { 11 | resolve(str) 12 | }) 13 | stream.on('error', function (err) { 14 | reject(err) 15 | }) 16 | }) 17 | }; 18 | -------------------------------------------------------------------------------- /src/render.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | import { mockCdkTestCustomDiff } from './test-util'; 4 | import { renderCustomDiffToHtmlString } from './render'; 5 | 6 | describe('renderCustomDiffToHtmlString', () => { 7 | it('renders the cdk custom diff to html (for the cdk in cdk-test/ directory)', async () => { 8 | const html = await renderCustomDiffToHtmlString(mockCdkTestCustomDiff(), 'CDK Diff'); 9 | const expectedHtml = readFileSync(resolve(__dirname, './test-util/cdk-test-diff.html'), 'utf8'); 10 | expect(html).toEqual(expectedHtml); 11 | }) 12 | }); 13 | -------------------------------------------------------------------------------- /src/custom-diff.ts: -------------------------------------------------------------------------------- 1 | import { DiffOptions, NicerStackDiff, StackRawDiff } from './types'; 2 | import { transformDiff } from './transform'; 3 | import { getRawDiff } from './raw-diff'; 4 | import * as cdk from "aws-cdk-lib"; 5 | 6 | export const getCustomDiff = async (app: cdk.App, props?: { rawDiff?: StackRawDiff[]; options?: DiffOptions }): Promise => { 7 | const rawDiffs = props?.rawDiff || await getRawDiff(app, props?.options); 8 | const nicerDiffs: NicerStackDiff[] = []; 9 | for (const rawDiff of rawDiffs) { 10 | nicerDiffs.push(await transformDiff(rawDiff)); 11 | } 12 | 13 | return nicerDiffs; 14 | }; 15 | -------------------------------------------------------------------------------- /dist/cdk-test/lambda/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.handler = void 0; 4 | const handler = async (event) => { 5 | console.log('a lambda 2'); 6 | }; 7 | exports.handler = handler; 8 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY2RrLXRlc3QvbGFtYmRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNPLE1BQU0sT0FBTyxHQUFHLEtBQUssRUFBRSxLQUFVLEVBQUUsRUFBRTtJQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBQzVCLENBQUMsQ0FBQTtBQUZZLFFBQUEsT0FBTyxXQUVuQiIsInNvdXJjZXNDb250ZW50IjpbIlxuZXhwb3J0IGNvbnN0IGhhbmRsZXIgPSBhc3luYyAoZXZlbnQ6IGFueSkgPT4ge1xuICBjb25zb2xlLmxvZygnYSBsYW1iZGEgMicpO1xufSJdfQ== -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "rootDir": "src", 5 | "target": "ES2018", 6 | "module": "commonjs", 7 | "lib": ["es2018"], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": ["./node_modules/@types"] 23 | }, 24 | "include": ["src/**/*"], 25 | "exclude": ["node_modules", "cdk.out", "**/*.d.ts", "**/*.spec.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /bin/diff-to-stdout.ts: -------------------------------------------------------------------------------- 1 | import { getCustomDiff } from '../src/index'; 2 | import { app } from '../src/cdk-test' 3 | 4 | const noop = (...args: any[]) => undefined; 5 | 6 | let exiting: boolean = false; 7 | 8 | const verboseMode = process.argv.indexOf('--verbose') !== -1; 9 | const quietMode = !verboseMode && process.argv.indexOf('--quiet') !== -1; 10 | 11 | const info = quietMode ? noop : console.info; 12 | const debug = verboseMode ? console.debug : noop; 13 | 14 | const main = async () => { 15 | const nicerDiffs = await getCustomDiff(app); 16 | console.log('****** START CUSTOM DIFF ******'); 17 | console.log(JSON.stringify(nicerDiffs, null, 2)); 18 | console.log('****** END CUSTOM DIFF ******'); 19 | }; 20 | 21 | process.on('SIGTERM', () => { 22 | console.info('SIGTERM signal received.'); 23 | exiting = true; 24 | }); 25 | 26 | process.on('SIGINT', () => { 27 | console.info('SIGTERM signal received.'); 28 | exiting = true; 29 | }); 30 | 31 | main() 32 | .then(() => { 33 | info('done'); 34 | }) 35 | .catch((err) => { 36 | console.error('oh dear!', err); 37 | }); 38 | -------------------------------------------------------------------------------- /bin/diff-to-html.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { writeFileSync } from 'fs'; 3 | import { getCustomDiff, renderCustomDiffToHtmlString } from '../src/index'; 4 | import { app } from '../src/cdk-test' 5 | 6 | const noop = (...args: any[]) => undefined; 7 | 8 | let exiting: boolean = false; 9 | 10 | const verboseMode = process.argv.indexOf('--verbose') !== -1; 11 | const quietMode = !verboseMode && process.argv.indexOf('--quiet') !== -1; 12 | 13 | const info = quietMode ? noop : console.info; 14 | const debug = verboseMode ? console.debug : noop; 15 | 16 | const main = async () => { 17 | const nicerDiffs = await getCustomDiff(app); 18 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 19 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 20 | }; 21 | 22 | process.on('SIGTERM', () => { 23 | console.info('SIGTERM signal received.'); 24 | exiting = true; 25 | }); 26 | 27 | process.on('SIGINT', () => { 28 | console.info('SIGTERM signal received.'); 29 | exiting = true; 30 | }); 31 | 32 | main() 33 | .then(() => { 34 | info('done'); 35 | }) 36 | .catch((err) => { 37 | console.error('oh dear!', err); 38 | }); 39 | -------------------------------------------------------------------------------- /dist/raw-diff.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getRawDiff = void 0; 4 | const cdk_reverse_engineered_1 = require("./cdk-reverse-engineered"); 5 | const getRawDiff = async (app, options) => { 6 | return await (0, cdk_reverse_engineered_1.getDiffObject)(app, options); 7 | }; 8 | exports.getRawDiff = getRawDiff; 9 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF3LWRpZmYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcmF3LWRpZmYudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscUVBQXlEO0FBR2xELE1BQU0sVUFBVSxHQUFHLEtBQUssRUFBRSxHQUFZLEVBQUUsT0FBcUIsRUFBMkIsRUFBRTtJQUMvRixPQUFPLE1BQU0sSUFBQSxzQ0FBYSxFQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMzQyxDQUFDLENBQUM7QUFGVyxRQUFBLFVBQVUsY0FFckIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEaWZmT3B0aW9ucywgU3RhY2tSYXdEaWZmIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQgeyBnZXREaWZmT2JqZWN0IH0gZnJvbSAnLi9jZGstcmV2ZXJzZS1lbmdpbmVlcmVkJztcbmltcG9ydCAqIGFzIGNkayBmcm9tIFwiYXdzLWNkay1saWJcIjtcblxuZXhwb3J0IGNvbnN0IGdldFJhd0RpZmYgPSBhc3luYyAoYXBwOiBjZGsuQXBwLCBvcHRpb25zPzogRGlmZk9wdGlvbnMpOiBQcm9taXNlPFN0YWNrUmF3RGlmZltdPiA9PiB7XG4gIHJldHVybiBhd2FpdCBnZXREaWZmT2JqZWN0KGFwcCwgb3B0aW9ucyk7XG59O1xuIl19 -------------------------------------------------------------------------------- /dist/cdk-test/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | exports.stack = exports.app = void 0; 5 | const cdk = require("aws-cdk-lib"); 6 | const josh_stack_1 = require("./josh-stack"); 7 | exports.app = new cdk.App({ 8 | context: { 9 | hello: 'you', 10 | } 11 | }); 12 | exports.stack = new josh_stack_1.JoshStack(exports.app, "JoshStack"); 13 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2RrLXRlc3QvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUNBLG1DQUFtQztBQUNuQyw2Q0FBeUM7QUFFNUIsUUFBQSxHQUFHLEdBQUcsSUFBSSxHQUFHLENBQUMsR0FBRyxDQUFDO0lBQzdCLE9BQU8sRUFBRTtRQUNQLEtBQUssRUFBRSxLQUFLO0tBQ2I7Q0FDRixDQUFDLENBQUM7QUFDVSxRQUFBLEtBQUssR0FBRyxJQUFJLHNCQUFTLENBQUMsV0FBRyxFQUFFLFdBQVcsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiIyEvdXNyL2Jpbi9lbnYgbm9kZVxuaW1wb3J0ICogYXMgY2RrIGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgSm9zaFN0YWNrIH0gZnJvbSBcIi4vam9zaC1zdGFja1wiO1xuXG5leHBvcnQgY29uc3QgYXBwID0gbmV3IGNkay5BcHAoe1xuICBjb250ZXh0OiB7XG4gICAgaGVsbG86ICd5b3UnLFxuICB9XG59KTtcbmV4cG9ydCBjb25zdCBzdGFjayA9IG5ldyBKb3NoU3RhY2soYXBwLCBcIkpvc2hTdGFja1wiKTtcbiJdfQ== -------------------------------------------------------------------------------- /bin/diff-to-html-with-cli-args.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { writeFileSync } from 'fs'; 3 | import { getCustomDiff, renderCustomDiffToHtmlString } from '../src/index'; 4 | import { app } from '../src/cdk-test' 5 | 6 | const noop = (...args: any[]) => undefined; 7 | 8 | let exiting: boolean = false; 9 | 10 | const verboseMode = process.argv.indexOf('--verbose') !== -1; 11 | const quietMode = !verboseMode && process.argv.indexOf('--quiet') !== -1; 12 | 13 | const info = quietMode ? noop : console.info; 14 | const debug = verboseMode ? console.debug : noop; 15 | 16 | const main = async () => { 17 | const nicerDiffs = await getCustomDiff(app, { options: { context: { hello: 'world' } } }); 18 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 19 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 20 | }; 21 | 22 | process.on('SIGTERM', () => { 23 | console.info('SIGTERM signal received.'); 24 | exiting = true; 25 | }); 26 | 27 | process.on('SIGINT', () => { 28 | console.info('SIGTERM signal received.'); 29 | exiting = true; 30 | }); 31 | 32 | main() 33 | .then(() => { 34 | info('done'); 35 | }) 36 | .catch((err) => { 37 | console.error('oh dear!', err); 38 | }); 39 | -------------------------------------------------------------------------------- /dist/test-util/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./mock-cdk-test-raw-diff"), exports); 18 | __exportStar(require("./mock-cdk-test-custom-diff"), exports); 19 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVzdC11dGlsL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwyREFBeUM7QUFDekMsOERBQTRDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0ICogZnJvbSAnLi9tb2NrLWNkay10ZXN0LXJhdy1kaWZmJztcbmV4cG9ydCAqIGZyb20gJy4vbW9jay1jZGstdGVzdC1jdXN0b20tZGlmZic7XG4iXX0= -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./raw-diff"), exports); 18 | __exportStar(require("./custom-diff"), exports); 19 | __exportStar(require("./render"), exports); 20 | __exportStar(require("./types"), exports); 21 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDZDQUEyQjtBQUMzQixnREFBOEI7QUFDOUIsMkNBQXlCO0FBQ3pCLDBDQUF3QiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCAqIGZyb20gJy4vcmF3LWRpZmYnO1xuZXhwb3J0ICogZnJvbSAnLi9jdXN0b20tZGlmZic7XG5leHBvcnQgKiBmcm9tICcuL3JlbmRlcic7XG5leHBvcnQgKiBmcm9tICcuL3R5cGVzJztcbiJdfQ== -------------------------------------------------------------------------------- /src/cdk-test/josh-stack.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { Stack, StackProps, App, Duration, CfnOutput } from "aws-cdk-lib"; 4 | import { Runtime, Function, AssetCode } from "aws-cdk-lib/aws-lambda"; 5 | import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb"; 6 | import { Topic } from "aws-cdk-lib/aws-sns"; 7 | import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; 8 | import { Queue } from "aws-cdk-lib/aws-sqs"; 9 | import { SqsSubscription } from "aws-cdk-lib/aws-sns-subscriptions"; 10 | 11 | const TABLE_NAME = "josh-poop3"; 12 | const PARTITION_KEY = "id"; 13 | 14 | export class JoshStack extends Stack { 15 | constructor(scope: App, id: string, props?: StackProps) { 16 | super(scope, id, props); 17 | 18 | console.log('context check:', this.node.tryGetContext('foo'), this.node.tryGetContext('hello')); 19 | 20 | const table = new Table(this, TABLE_NAME, { 21 | partitionKey: { 22 | name: PARTITION_KEY, 23 | type: AttributeType.STRING, 24 | }, 25 | billingMode: BillingMode.PAY_PER_REQUEST, 26 | }); 27 | 28 | const role = new Role(this, 'MyRole', { 29 | assumedBy: new ServicePrincipal('sns.amazonaws.com'), 30 | }); 31 | 32 | new Function(this, 'JoshLambda', { 33 | runtime: Runtime.NODEJS_12_X, 34 | handler: 'index.handler', 35 | code: new AssetCode(path.join(__dirname, './lambda')), 36 | environment: { 37 | 'POOP': 'FOO2', 38 | 'BAZ': 'BAR', 39 | 'hello': this.node.tryGetContext('hello'), 40 | } 41 | }) 42 | 43 | const myTopic = new Topic(this, 'JoshTopic'); 44 | const myQueue = new Queue(this, 'JoshQueue', { deliveryDelay: Duration.seconds(5) }); 45 | myTopic.addSubscription(new SqsSubscription(myQueue)); 46 | 47 | new Queue(this, 'JoshQueue2'); 48 | 49 | new CfnOutput(this, 'thequeue', { value: myTopic.topicArn }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /dist/types.d.ts: -------------------------------------------------------------------------------- 1 | import * as cfnDiff from '@aws-cdk/cloudformation-diff'; 2 | export declare const cdkDiffCategories: readonly ["iamChanges", "securityGroup", "resources", "parameters", "metadata", "mappings", "conditions", "outputs", "unknown", "description"]; 3 | export type CdkDiffCategories = typeof cdkDiffCategories; 4 | export type CdkDiffCategory = CdkDiffCategories[number]; 5 | export type StackRawDiff = { 6 | stackName: string; 7 | rawDiff: cfnDiff.TemplateDiff; 8 | logicalToPathMap: Record; 9 | }; 10 | export type NicerDiffChange = { 11 | label: string; 12 | from?: any; 13 | to: any; 14 | action: 'ADDITION' | 'UPDATE' | 'REMOVAL'; 15 | }; 16 | export type NicerDiff = { 17 | label: string; 18 | cdkDiffRaw: string; 19 | nicerDiff?: { 20 | cdkDiffCategory: CdkDiffCategory; 21 | resourceAction: 'ADDITION' | 'UPDATE' | 'REMOVAL'; 22 | resourceType: string; 23 | resourceLabel: string; 24 | changes: NicerDiffChange[]; 25 | }; 26 | }; 27 | export declare const nicerDiffGuard: (thing: any) => thing is NicerDiff; 28 | export type NicerStackDiff = { 29 | diff?: NicerDiff[]; 30 | raw: string; 31 | stackName: string; 32 | }; 33 | export declare const nicerStackDiffGuard: (thing: any) => thing is NicerStackDiff; 34 | export declare const nicerStackDiffValidator: (thing: any) => NicerStackDiff[]; 35 | export declare const guardResourceDiff: (thing: any) => thing is cfnDiff.ResourceDifference; 36 | export declare const diffValidator: (thing: any) => { 37 | diffCollectionKey: CdkDiffCategory; 38 | diffCollection: cfnDiff.DifferenceCollection>; 39 | } | { 40 | diffKey: CdkDiffCategory; 41 | diff: cfnDiff.Difference; 42 | }; 43 | export type CdkToolkitDeploymentsProp = 'cloudFormation' | 'deployments'; 44 | export interface DiffOptions { 45 | context?: Record; 46 | profile?: string; 47 | } 48 | -------------------------------------------------------------------------------- /dist/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.streamToString = void 0; 4 | const streamToString = async (stream) => { 5 | let str = ''; 6 | return new Promise(function (resolve, reject) { 7 | stream.on('data', function (data) { 8 | str += data.toString(); 9 | }); 10 | stream.on('end', function () { 11 | resolve(str); 12 | }); 13 | stream.on('error', function (err) { 14 | reject(err); 15 | }); 16 | }); 17 | }; 18 | exports.streamToString = streamToString; 19 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVPLE1BQU0sY0FBYyxHQUFHLEtBQUssRUFBRSxNQUF1QixFQUFtQixFQUFFO0lBQy9FLElBQUksR0FBRyxHQUFXLEVBQUUsQ0FBQTtJQUVwQixPQUFPLElBQUksT0FBTyxDQUFFLFVBQVUsT0FBTyxFQUFFLE1BQU07UUFDekMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsVUFBVSxJQUFJO1lBQzVCLEdBQUcsSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUE7UUFDMUIsQ0FBQyxDQUFDLENBQUE7UUFDRixNQUFNLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRTtZQUNiLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUNoQixDQUFDLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRztZQUM1QixNQUFNLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDZixDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUFDO0FBZFcsUUFBQSxjQUFjLGtCQWN6QiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIHN0cmVhbSBmcm9tICdzdHJlYW0nO1xuXG5leHBvcnQgY29uc3Qgc3RyZWFtVG9TdHJpbmcgPSBhc3luYyAoc3RyZWFtOiBzdHJlYW0uV3JpdGFibGUpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICBsZXQgc3RyOiBzdHJpbmcgPSAnJ1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZSAoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgc3RyZWFtLm9uKCdkYXRhJywgZnVuY3Rpb24gKGRhdGEpIHtcbiAgICAgICAgICBzdHIgKz0gZGF0YS50b1N0cmluZygpXG4gICAgICB9KVxuICAgICAgc3RyZWFtLm9uKCdlbmQnLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgcmVzb2x2ZShzdHIpXG4gICAgICB9KVxuICAgICAgc3RyZWFtLm9uKCdlcnJvcicsIGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgICAgICByZWplY3QoZXJyKVxuICAgICAgfSlcbiAgfSlcbn07XG4iXX0= -------------------------------------------------------------------------------- /dist/custom-diff.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getCustomDiff = void 0; 4 | const transform_1 = require("./transform"); 5 | const raw_diff_1 = require("./raw-diff"); 6 | const getCustomDiff = async (app, props) => { 7 | const rawDiffs = (props === null || props === void 0 ? void 0 : props.rawDiff) || await (0, raw_diff_1.getRawDiff)(app, props === null || props === void 0 ? void 0 : props.options); 8 | const nicerDiffs = []; 9 | for (const rawDiff of rawDiffs) { 10 | nicerDiffs.push(await (0, transform_1.transformDiff)(rawDiff)); 11 | } 12 | return nicerDiffs; 13 | }; 14 | exports.getCustomDiff = getCustomDiff; 15 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tLWRpZmYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY3VzdG9tLWRpZmYudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EsMkNBQTRDO0FBQzVDLHlDQUF3QztBQUdqQyxNQUFNLGFBQWEsR0FBRyxLQUFLLEVBQUUsR0FBWSxFQUFFLEtBQTJELEVBQTZCLEVBQUU7SUFDMUksTUFBTSxRQUFRLEdBQUcsQ0FBQSxLQUFLLGFBQUwsS0FBSyx1QkFBTCxLQUFLLENBQUUsT0FBTyxLQUFJLE1BQU0sSUFBQSxxQkFBVSxFQUFDLEdBQUcsRUFBRSxLQUFLLGFBQUwsS0FBSyx1QkFBTCxLQUFLLENBQUUsT0FBTyxDQUFDLENBQUM7SUFDekUsTUFBTSxVQUFVLEdBQXFCLEVBQUUsQ0FBQztJQUN4QyxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRTtRQUM5QixVQUFVLENBQUMsSUFBSSxDQUFDLE1BQU0sSUFBQSx5QkFBYSxFQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7S0FDL0M7SUFFRCxPQUFPLFVBQVUsQ0FBQztBQUNwQixDQUFDLENBQUM7QUFSVyxRQUFBLGFBQWEsaUJBUXhCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRGlmZk9wdGlvbnMsIE5pY2VyU3RhY2tEaWZmLCBTdGFja1Jhd0RpZmYgfSBmcm9tICcuL3R5cGVzJztcbmltcG9ydCB7IHRyYW5zZm9ybURpZmYgfSBmcm9tICcuL3RyYW5zZm9ybSc7XG5pbXBvcnQgeyBnZXRSYXdEaWZmIH0gZnJvbSAnLi9yYXctZGlmZic7XG5pbXBvcnQgKiBhcyBjZGsgZnJvbSBcImF3cy1jZGstbGliXCI7XG5cbmV4cG9ydCBjb25zdCBnZXRDdXN0b21EaWZmID0gYXN5bmMgKGFwcDogY2RrLkFwcCwgcHJvcHM/OiB7IHJhd0RpZmY/OiBTdGFja1Jhd0RpZmZbXTsgb3B0aW9ucz86IERpZmZPcHRpb25zIH0pOiBQcm9taXNlPE5pY2VyU3RhY2tEaWZmW10+ID0+IHtcbiAgY29uc3QgcmF3RGlmZnMgPSBwcm9wcz8ucmF3RGlmZiB8fCBhd2FpdCBnZXRSYXdEaWZmKGFwcCwgcHJvcHM/Lm9wdGlvbnMpO1xuICBjb25zdCBuaWNlckRpZmZzOiBOaWNlclN0YWNrRGlmZltdID0gW107XG4gIGZvciAoY29uc3QgcmF3RGlmZiBvZiByYXdEaWZmcykge1xuICAgIG5pY2VyRGlmZnMucHVzaChhd2FpdCB0cmFuc2Zvcm1EaWZmKHJhd0RpZmYpKTtcbiAgfVxuXG4gIHJldHVybiBuaWNlckRpZmZzO1xufTtcbiJdfQ== -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-pretty-diff", 3 | "description": "Formatting tool for CDK Diff output. Inspired by Terraform prettyplan (https://github.com/chrislewisdev/prettyplan)", 4 | "author": "joshweir", 5 | "license": "MIT", 6 | "version": "3.0.1", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/joshweir/cdk-pretty-diff.git" 10 | }, 11 | "keywords": [ 12 | "cdk", 13 | "cdk diff", 14 | "cdk pretty diff", 15 | "cdk diff pretty", 16 | "cdk deploy", 17 | "pretty", 18 | "cdk diff formatter" 19 | ], 20 | "scripts": { 21 | "build": "tsc", 22 | "build-live": "rm -rf dist && tsc && rm -rf dist/test-util && rm -rf dist/cdk-test", 23 | "watch": "tsc -w", 24 | "test": "jest --silent", 25 | "cdk": "cdk" 26 | }, 27 | "devDependencies": { 28 | "@aws-cdk/cloudformation-diff": ">= 2.182.0", 29 | "@aws-cdk/aws-apigatewayv2-alpha": "2.61.0-alpha.0", 30 | "@aws-cdk/aws-apigatewayv2-integrations-alpha": "2.61.0-alpha.0", 31 | "@aws-cdk/cloud-assembly-schema": ">= 2.1018.1", 32 | "@aws-cdk/region-info": ">= 2.0.0", 33 | "@aws-cdk/toolkit-lib": ">= 1.1.1", 34 | "@aws-cdk/cli-plugin-contract": ">= 2.181.0", 35 | "cdk-assets": ">= 2.1002.0", 36 | "constructs": "^10.0.0", 37 | "@aws-cdk/cx-api": ">= 2.201.0", 38 | "@types/diff": "^5.0.1", 39 | "@types/hogan.js": "^3.0.1", 40 | "@types/jest": "^26.0.0", 41 | "@types/node": "^24.0.1", 42 | "@types/through2": "^2.0.36", 43 | "aws-cdk": "2.1017.0", 44 | "aws-cdk-lib": ">= 2.201.0", 45 | "aws-sdk": "2.1492.0", 46 | "jest": "^26.0.0", 47 | "lodash": "^4.17.21", 48 | "source-map-support": "^0.5.19", 49 | "ts-jest": "^26.0.0", 50 | "ts-node": "^8.6.2", 51 | "typescript": "^4.9.3", 52 | "uuid": "^7.0.2" 53 | }, 54 | "dependencies": { 55 | "archiver": "5.3.1", 56 | "cdk-from-cfn": "0.83.0", 57 | "chalk": "4.1.2", 58 | "camelcase": "6.3.0", 59 | "decamelize": "5.0.1", 60 | "chokidar": "3.5.3", 61 | "colors": "1.4.0", 62 | "diff": "^5.0.0", 63 | "diff2html": "^3.4.7", 64 | "p-queue": "6.6.2", 65 | "promptly": "3.2.0", 66 | "proxy-agent": "^6.3.0", 67 | "semver": "^7.5.4", 68 | "strip-ansi": "6.0.1", 69 | "through2": "^4.0.2", 70 | "wrap-ansi": "7.0.0", 71 | "yaml": "1.10.2" 72 | }, 73 | "peerDependencies": { 74 | "@aws-cdk/cloudformation-diff": ">= 2.182.0", 75 | "@aws-cdk/cx-api": ">= 2.201.0", 76 | "@aws-cdk/toolkit-lib": ">= 1.1.1", 77 | "@aws-cdk/cli-plugin-contract": ">= 2.181.0", 78 | "aws-cdk": ">= 2.1017.0", 79 | "aws-cdk-lib": ">= 2.201.0" 80 | }, 81 | "bugs": { 82 | "url": "https://github.com/joshweir/cdk-pretty-diff/issues" 83 | }, 84 | "homepage": "https://github.com/joshweir/cdk-pretty-diff#readme", 85 | "main": "dist/index.js", 86 | "files": [ 87 | "dist/**/*.js", 88 | "dist/**/*.d.ts", 89 | "!dist/test-util/**", 90 | "!dist/cdk-test/**", 91 | "README.md", 92 | "package.json" 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as cfnDiff from '@aws-cdk/cloudformation-diff'; 2 | import { CdkToolkitProps } from 'aws-cdk/lib/cli/cdk-toolkit'; 3 | 4 | export const cdkDiffCategories = ['iamChanges', 'securityGroup', 'resources', 'parameters', 'metadata', 'mappings', 'conditions', 'outputs', 'unknown', 'description'] as const; 5 | export type CdkDiffCategories = typeof cdkDiffCategories; 6 | export type CdkDiffCategory = CdkDiffCategories[number]; 7 | export type StackRawDiff = { 8 | stackName: string; 9 | rawDiff: cfnDiff.TemplateDiff, 10 | logicalToPathMap: Record 11 | }; 12 | 13 | export type NicerDiffChange = { 14 | label: string; 15 | from?: any; 16 | to: any; 17 | action: 'ADDITION' | 'UPDATE' | 'REMOVAL'; 18 | } 19 | export type NicerDiff = { 20 | label: string; 21 | cdkDiffRaw: string; 22 | nicerDiff?: { 23 | cdkDiffCategory: CdkDiffCategory; 24 | resourceAction: 'ADDITION' | 'UPDATE' | 'REMOVAL'; 25 | resourceType: string; 26 | resourceLabel: string; 27 | changes: NicerDiffChange[]; 28 | } 29 | } 30 | export const nicerDiffGuard = (thing: any): thing is NicerDiff => 31 | typeof thing === 'object' && 32 | typeof thing.label === 'string' && 33 | typeof thing.cdkDiffRaw === 'string' && 34 | ['undefined', 'object'].includes(typeof thing.nicerDiff); 35 | 36 | export type NicerStackDiff = { 37 | diff?: NicerDiff[]; 38 | raw: string; 39 | stackName: string; 40 | } 41 | 42 | export const nicerStackDiffGuard = (thing: any): thing is NicerStackDiff => { 43 | if (typeof thing === 'object') { 44 | if (typeof thing.raw === 'string' && typeof thing.stackName === 'string') { 45 | if (!!thing.diff) { 46 | if (thing.diff.filter(nicerDiffGuard).length === thing.diff.length) { 47 | return true; 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | } 54 | 55 | return false; 56 | } 57 | 58 | export const nicerStackDiffValidator = (thing: any): NicerStackDiff[] => { 59 | if (typeof thing === 'object') { 60 | if (thing.filter(nicerStackDiffGuard).length === thing.length) { 61 | return thing; 62 | } 63 | } 64 | 65 | throw new Error(`input is not a NicerStackDiff[]: ${JSON.stringify(thing, null, 2)}`); 66 | } 67 | 68 | export const guardResourceDiff = (thing: any): thing is cfnDiff.ResourceDifference => 69 | typeof thing === 'object' && 70 | typeof thing.forEachDifference === 'function'; 71 | 72 | export const diffValidator = (thing: any): { diffCollectionKey: CdkDiffCategory; diffCollection: cfnDiff.DifferenceCollection> } | { diffKey: CdkDiffCategory; diff: cfnDiff.Difference } => { 73 | if (typeof thing === 'object') { 74 | if (thing.length === 2) { 75 | const [diffKey, diff] = thing; 76 | 77 | if (!cdkDiffCategories.includes(diffKey)) { 78 | throw new Error(`unexpected diff category: ${diffKey}`); 79 | } 80 | 81 | if (diffKey === 'description') { 82 | return { diffKey, diff }; 83 | } else if (typeof diff === 'object' && diff.hasOwnProperty('diffs')) { 84 | return { diffCollectionKey: diffKey, diffCollection: diff }; 85 | } 86 | } 87 | } 88 | 89 | throw new Error(`invalid diff: ${JSON.stringify(thing, null, 2)}`); 90 | } 91 | 92 | export type CdkToolkitDeploymentsProp = 'cloudFormation' | 'deployments'; 93 | 94 | export interface DiffOptions { 95 | context?: Record; 96 | profile?: string; 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDK Pretty Diff 2 | 3 | Format `cdk diff` output to html making review easier. Inspired by [Terraform prettyplan](https://github.com/chrislewisdev/prettyplan). 4 | 5 | ## Installation 6 | 7 | If you are using `aws-cdk` <= v1: 8 | 9 | ``` 10 | npm install cdk-pretty-diff@1.x 11 | ``` 12 | 13 | or `aws-cdk` >= v2: 14 | 15 | ``` 16 | npm install cdk-pretty-diff 17 | ``` 18 | 19 | If using `aws-cdk` v2 < v2.1017.0 then install `cdk-pretty-diff` v2.2.6 20 | If using `aws-cdk` >= v2.1017.0 then install `cdk-pretty-diff` v3 21 | 22 | ## Usage 23 | 24 | Instead of running `cdk diff` command line and receiving diff output, use `cdk-pretty-diff` (in javascript). Examples below. 25 | 26 | ### Get cdk diff as an object (cdk-pretty-diff >= v3.0.0, aws-cdk >= v2.1017.0) 27 | 28 | ``` typescript 29 | import { getCustomDiff } from 'cdk-pretty-diff'; 30 | import * as cdk from "aws-cdk-lib"; 31 | 32 | // Your existing cdk app and stack(s) 33 | const app = new cdk.App(); 34 | const stack = new cdk.Stack(app, "YourStack"); 35 | 36 | const nicerDiffs = await getCustomDiff(app); 37 | console.log(JSON.stringify(nicerDiffs, null, 2)); 38 | ``` 39 | 40 | ### Get cdk diff as an object (cdk-pretty-diff < v3, aws-cdk < v2.1017.0) 41 | 42 | ``` typescript 43 | import { getCustomDiff } from 'cdk-pretty-diff'; 44 | 45 | const nicerDiffs = await getCustomDiff(); 46 | console.log(JSON.stringify(nicerDiffs, null, 2)); 47 | ``` 48 | 49 | ### Render Pretty CDK Diff to html 50 | 51 | html sample screenshot: 52 | 53 | ![HTML Sample Screenshot](https://github.com/joshweir/cdk-pretty-diff/blob/master/pretty-diff-html-sample.png?raw=true) 54 | 55 | * Original CDK Diff output is available (click the `Orig CDK Diff` button) 56 | 57 | #### For (cdk-pretty-diff >= v3, aws-cdk >= v2.1017.0): 58 | 59 | ``` typescript 60 | import { resolve } from 'path'; 61 | import { writeFileSync } from 'fs'; 62 | import { getCustomDiff, renderCustomDiffToHtmlString } from 'cdk-pretty-diff'; 63 | // import your cdk.App 64 | import { app } from './your-cdk-app' 65 | 66 | const nicerDiffs = await getCustomDiff(app); 67 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 68 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 69 | ``` 70 | 71 | optionally, provide command line input args (as you could with `cdk diff` command): 72 | 73 | ``` typescript 74 | import { resolve } from 'path'; 75 | import { writeFileSync } from 'fs'; 76 | import { Command, ConfigurationProps } from 'aws-cdk/lib/settings'; 77 | import { getCustomDiff, renderCustomDiffToHtmlString } from 'cdk-pretty-diff'; 78 | // import your cdk.App 79 | import { app } from './your-cdk-app' 80 | 81 | const configProps = { options: { context: { hello: 'world' } } } 82 | const nicerDiffs = await getCustomDiff(app, configProps); 83 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 84 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 85 | ``` 86 | example: [bin/diff-to-html-with-cli-args.ts](https://github.com/joshweir/cdk-pretty-diff/blob/master/bin/diff-to-html-with-cli-args.ts) 87 | 88 | 89 | #### For (cdk-pretty-diff < v3, aws-cdk < v2.1017.0): 90 | 91 | ``` typescript 92 | import { resolve } from 'path'; 93 | import { writeFileSync } from 'fs'; 94 | import { getCustomDiff, renderCustomDiffToHtmlString } from 'cdk-pretty-diff'; 95 | 96 | const nicerDiffs = await getCustomDiff(); 97 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 98 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 99 | ``` 100 | 101 | optionally, provide command line input args (as you could with `cdk diff` command): 102 | 103 | ``` typescript 104 | import { resolve } from 'path'; 105 | import { writeFileSync } from 'fs'; 106 | import { Command, ConfigurationProps } from 'aws-cdk/lib/settings'; 107 | import { getCustomDiff, renderCustomDiffToHtmlString } from 'cdk-pretty-diff'; 108 | 109 | const configProps: ConfigurationProps = { 110 | commandLineArguments: { 111 | _: [Command.DIFF], 112 | context: [ 113 | 'foo=bar', 114 | 'hello=world', 115 | ], 116 | } 117 | } 118 | const nicerDiffs = await getCustomDiff({ configProps }); 119 | const html = renderCustomDiffToHtmlString(nicerDiffs, 'CDK Diff'); 120 | writeFileSync(resolve(__dirname, '../cdk.out/diff.html'), html); 121 | ``` 122 | example: [bin/diff-to-html-with-cli-args.ts](https://github.com/joshweir/cdk-pretty-diff/blob/master/bin/diff-to-html-with-cli-args.ts) 123 | 124 | ## Development 125 | 126 | ``` 127 | npm i 128 | npm run build 129 | 130 | # run cdk pretty diff for the example stack: 131 | AWS_PROFILE= npx ts-node bin/diff-to-html.ts 132 | # pretty diff location: cdk.out/diff.html 133 | ``` 134 | -------------------------------------------------------------------------------- /src/render.ts: -------------------------------------------------------------------------------- 1 | import { createTwoFilesPatch } from 'diff'; 2 | import * as diff2html from 'diff2html'; 3 | 4 | import { NicerDiff, NicerDiffChange, NicerStackDiff } from "./types"; 5 | import htmlTemplate from './pretty-diff-template.html'; 6 | 7 | const prettify = (valueIn: any): string => { 8 | // fallback to empty string (eg. JSON.stringify of undefined is undefined) 9 | const value = 10 | (typeof valueIn === "string" ? valueIn : JSON.stringify(valueIn, null, 2)) || ''; 11 | 12 | if (value === "") { 13 | return `<computed>`; 14 | } 15 | 16 | if (value.startsWith("${") && value.endsWith("}")) { 17 | return `${value}`; 18 | } 19 | 20 | if (value.indexOf("\\n") >= 0 || value.indexOf('\\"') >= 0) { 21 | const sanitisedValue = value 22 | .replace(new RegExp("\\\\n", "g"), "\n") 23 | .replace(new RegExp('\\\\"', "g"), '"'); 24 | 25 | return `
${prettifyJson(sanitisedValue)}
`; 26 | } 27 | 28 | return value; 29 | }; 30 | 31 | const prettifyJson = (maybeJson: string): string => { 32 | try { 33 | return JSON.stringify(JSON.parse(maybeJson), null, 2); 34 | } catch (e) { 35 | return maybeJson; 36 | } 37 | }; 38 | 39 | const components = { 40 | badge: (label: string): string => ` 41 | ${label} 42 | `, 43 | 44 | id: (id: any): string => ` 45 | 46 | ${id.resourceType} 47 | ${id.resourceLabel} 48 | 49 | `, 50 | 51 | warning: (warning: any): string => ` 52 |
  • 53 | ${components.badge("warning")} 54 | ${components.id(warning.id)} 55 | ${warning.detail} 56 |
  • 57 | `, 58 | 59 | changeCount: (count: number): string => ` 60 | 61 | ${`${count} change${count > 1 ? "s" : ""}`} 62 | 63 | `, 64 | 65 | changeNoDiff: ({ action, to, label }: NicerDiffChange): string => ` 66 | 67 | 68 | ${label} 69 | ${`
    (${action})`} 70 | 71 | ${prettify(to)} 72 | 73 | `, 74 | 75 | changeDiff: ({ from, to, label }: NicerDiffChange): string => ` 76 |
    77 | ${diff2html.html( 78 | createTwoFilesPatch(label, label, prettify(from), prettify(to)), 79 | { 80 | outputFormat: 'line-by-line', 81 | drawFileList: false, 82 | matching: 'words', 83 | matchWordsThreshold: 0.25, 84 | matchingMaxComparisons: 200, 85 | } 86 | )} 87 |
    88 | `, 89 | 90 | changes: (changes: NicerDiffChange[]) => { 91 | const diffChanges = changes.filter(({ from }) => !!from); 92 | const noDiffChanges = changes.filter(({ from }) => !from); 93 | 94 | return ` 95 |
    96 |
    97 | ${noDiffChanges.length ? (` 98 | 99 | ${noDiffChanges.map(components.changeNoDiff).join("")} 100 |
    101 | `) : ''} 102 |
    103 | ${diffChanges.map(components.changeDiff).join("")} 104 | 105 | ` 106 | }, 107 | 108 | action: ({ cdkDiffRaw, nicerDiff, label }: NicerDiff): string => ` 109 |
  • 110 |
    111 | ${components.badge(nicerDiff?.resourceAction || "")} 112 | ${components.id( 113 | nicerDiff || { resourceType: "", resourceLabel: label } 114 | )} 115 |
    116 | 127 |
  • 128 | `, 129 | 130 | modal: (content: string): string => ` 131 | 132 | 136 | `, 137 | 138 | rawDiff: ( 139 | raw: string, 140 | toggleCaption: string, 141 | opts?: { collapsed: boolean; showButton?: boolean } 142 | ): string => ` 143 |
    144 | ${ 145 | typeof opts?.showButton === "boolean" && opts?.showButton === false 146 | ? "" 147 | : `` 148 | } 149 |
    150 |
    ${raw}
    151 |
    152 |
    153 | `, 154 | 155 | stackDiff: ({ stackName, raw, diff }: NicerStackDiff): string => ` 156 |
    157 |

    ${stackName}

    158 | ${components.rawDiff(raw, "Orig CDK Diff", { collapsed: true })} 159 | ${!diff?.length ? `
    No changes
    ` : ""} 160 |
      161 | ${diff 162 | ?.filter( 163 | ({ nicerDiff }) => 164 | !nicerDiff || 165 | !["parameters"].includes(nicerDiff?.cdkDiffCategory) 166 | ) 167 | .map(components.action) 168 | .join("\n")} 169 |
    170 |
    171 | `, 172 | }; 173 | 174 | export const renderCustomDiffToHtmlNodeString = (diffs: NicerStackDiff[]): string => 175 | diffs.map(components.stackDiff).join(' '); 176 | 177 | export const renderCustomDiffToHtmlString = ( 178 | diffs: NicerStackDiff[], 179 | title: string 180 | ): string => { 181 | let html = htmlTemplate; 182 | html = html 183 | .replace(`

    prettyplan

    `, `

    ${title}

    `) 184 | .replace(`prettyplan`, `${title}`); 185 | 186 | html = html.replace( 187 | `
    `, 188 | `
    ${renderCustomDiffToHtmlNodeString(diffs)}
    ` 189 | ); 190 | 191 | return html; 192 | }; 193 | -------------------------------------------------------------------------------- /dist/cdk-test/josh-stack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.JoshStack = void 0; 4 | const path = require("path"); 5 | const aws_cdk_lib_1 = require("aws-cdk-lib"); 6 | const aws_lambda_1 = require("aws-cdk-lib/aws-lambda"); 7 | const aws_dynamodb_1 = require("aws-cdk-lib/aws-dynamodb"); 8 | const aws_sns_1 = require("aws-cdk-lib/aws-sns"); 9 | const aws_iam_1 = require("aws-cdk-lib/aws-iam"); 10 | const aws_sqs_1 = require("aws-cdk-lib/aws-sqs"); 11 | const aws_sns_subscriptions_1 = require("aws-cdk-lib/aws-sns-subscriptions"); 12 | const TABLE_NAME = "josh-poop3"; 13 | const PARTITION_KEY = "id"; 14 | class JoshStack extends aws_cdk_lib_1.Stack { 15 | constructor(scope, id, props) { 16 | super(scope, id, props); 17 | console.log('context check:', this.node.tryGetContext('foo'), this.node.tryGetContext('hello')); 18 | const table = new aws_dynamodb_1.Table(this, TABLE_NAME, { 19 | partitionKey: { 20 | name: PARTITION_KEY, 21 | type: aws_dynamodb_1.AttributeType.STRING, 22 | }, 23 | billingMode: aws_dynamodb_1.BillingMode.PAY_PER_REQUEST, 24 | }); 25 | const role = new aws_iam_1.Role(this, 'MyRole', { 26 | assumedBy: new aws_iam_1.ServicePrincipal('sns.amazonaws.com'), 27 | }); 28 | new aws_lambda_1.Function(this, 'JoshLambda', { 29 | runtime: aws_lambda_1.Runtime.NODEJS_12_X, 30 | handler: 'index.handler', 31 | code: new aws_lambda_1.AssetCode(path.join(__dirname, './lambda')), 32 | environment: { 33 | 'POOP': 'FOO2', 34 | 'BAZ': 'BAR', 35 | 'hello': this.node.tryGetContext('hello'), 36 | } 37 | }); 38 | const myTopic = new aws_sns_1.Topic(this, 'JoshTopic'); 39 | const myQueue = new aws_sqs_1.Queue(this, 'JoshQueue', { deliveryDelay: aws_cdk_lib_1.Duration.seconds(5) }); 40 | myTopic.addSubscription(new aws_sns_subscriptions_1.SqsSubscription(myQueue)); 41 | new aws_sqs_1.Queue(this, 'JoshQueue2'); 42 | new aws_cdk_lib_1.CfnOutput(this, 'thequeue', { value: myTopic.topicArn }); 43 | } 44 | } 45 | exports.JoshStack = JoshStack; 46 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiam9zaC1zdGFjay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jZGstdGVzdC9qb3NoLXN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZCQUE2QjtBQUU3Qiw2Q0FBMEU7QUFDMUUsdURBQXNFO0FBQ3RFLDJEQUE2RTtBQUM3RSxpREFBNEM7QUFDNUMsaURBQTZEO0FBQzdELGlEQUE0QztBQUM1Qyw2RUFBb0U7QUFFcEUsTUFBTSxVQUFVLEdBQUcsWUFBWSxDQUFDO0FBQ2hDLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQztBQUUzQixNQUFhLFNBQVUsU0FBUSxtQkFBSztJQUNsQyxZQUFZLEtBQVUsRUFBRSxFQUFVLEVBQUUsS0FBa0I7UUFDcEQsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFeEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBRWhHLE1BQU0sS0FBSyxHQUFHLElBQUksb0JBQUssQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFO1lBQ3hDLFlBQVksRUFBRTtnQkFDWixJQUFJLEVBQUUsYUFBYTtnQkFDbkIsSUFBSSxFQUFFLDRCQUFhLENBQUMsTUFBTTthQUMzQjtZQUNELFdBQVcsRUFBRSwwQkFBVyxDQUFDLGVBQWU7U0FDekMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxJQUFJLEdBQUcsSUFBSSxjQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRTtZQUNwQyxTQUFTLEVBQUUsSUFBSSwwQkFBZ0IsQ0FBQyxtQkFBbUIsQ0FBQztTQUNyRCxDQUFDLENBQUM7UUFFSCxJQUFJLHFCQUFRLENBQUMsSUFBSSxFQUFFLFlBQVksRUFBRTtZQUMvQixPQUFPLEVBQUUsb0JBQU8sQ0FBQyxXQUFXO1lBQzVCLE9BQU8sRUFBRSxlQUFlO1lBQ3hCLElBQUksRUFBRSxJQUFJLHNCQUFTLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFDckQsV0FBVyxFQUFFO2dCQUNYLE1BQU0sRUFBRSxNQUFNO2dCQUNkLEtBQUssRUFBRSxLQUFLO2dCQUNaLE9BQU8sRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUM7YUFDMUM7U0FDRixDQUFDLENBQUE7UUFFRixNQUFNLE9BQU8sR0FBRyxJQUFJLGVBQUssQ0FBQyxJQUFJLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDN0MsTUFBTSxPQUFPLEdBQUcsSUFBSSxlQUFLLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRSxFQUFFLGFBQWEsRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDckYsT0FBTyxDQUFDLGVBQWUsQ0FBQyxJQUFJLHVDQUFlLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztRQUV0RCxJQUFJLGVBQUssQ0FBQyxJQUFJLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFFOUIsSUFBSSx1QkFBUyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsRUFBRSxLQUFLLEVBQUUsT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDL0QsQ0FBQztDQUNGO0FBckNELDhCQXFDQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIHBhdGggZnJvbSBcInBhdGhcIjtcblxuaW1wb3J0IHsgU3RhY2ssIFN0YWNrUHJvcHMsIEFwcCwgRHVyYXRpb24sIENmbk91dHB1dCB9IGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgUnVudGltZSwgRnVuY3Rpb24sIEFzc2V0Q29kZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtbGFtYmRhXCI7XG5pbXBvcnQgeyBBdHRyaWJ1dGVUeXBlLCBCaWxsaW5nTW9kZSwgVGFibGUgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWR5bmFtb2RiXCI7XG5pbXBvcnQgeyBUb3BpYyB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3Mtc25zXCI7XG5pbXBvcnQgeyBSb2xlLCBTZXJ2aWNlUHJpbmNpcGFsIH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1pYW1cIjtcbmltcG9ydCB7IFF1ZXVlIH0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1zcXNcIjtcbmltcG9ydCB7IFNxc1N1YnNjcmlwdGlvbiB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3Mtc25zLXN1YnNjcmlwdGlvbnNcIjtcblxuY29uc3QgVEFCTEVfTkFNRSA9IFwiam9zaC1wb29wM1wiO1xuY29uc3QgUEFSVElUSU9OX0tFWSA9IFwiaWRcIjtcblxuZXhwb3J0IGNsYXNzIEpvc2hTdGFjayBleHRlbmRzIFN0YWNrIHtcbiAgY29uc3RydWN0b3Ioc2NvcGU6IEFwcCwgaWQ6IHN0cmluZywgcHJvcHM/OiBTdGFja1Byb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkLCBwcm9wcyk7XG5cbiAgICBjb25zb2xlLmxvZygnY29udGV4dCBjaGVjazonLCB0aGlzLm5vZGUudHJ5R2V0Q29udGV4dCgnZm9vJyksIHRoaXMubm9kZS50cnlHZXRDb250ZXh0KCdoZWxsbycpKTtcblxuICAgIGNvbnN0IHRhYmxlID0gbmV3IFRhYmxlKHRoaXMsIFRBQkxFX05BTUUsIHtcbiAgICAgIHBhcnRpdGlvbktleToge1xuICAgICAgICBuYW1lOiBQQVJUSVRJT05fS0VZLFxuICAgICAgICB0eXBlOiBBdHRyaWJ1dGVUeXBlLlNUUklORyxcbiAgICAgIH0sXG4gICAgICBiaWxsaW5nTW9kZTogQmlsbGluZ01vZGUuUEFZX1BFUl9SRVFVRVNULFxuICAgIH0pO1xuXG4gICAgY29uc3Qgcm9sZSA9IG5ldyBSb2xlKHRoaXMsICdNeVJvbGUnLCB7XG4gICAgICBhc3N1bWVkQnk6IG5ldyBTZXJ2aWNlUHJpbmNpcGFsKCdzbnMuYW1hem9uYXdzLmNvbScpLFxuICAgIH0pO1xuXG4gICAgbmV3IEZ1bmN0aW9uKHRoaXMsICdKb3NoTGFtYmRhJywge1xuICAgICAgcnVudGltZTogUnVudGltZS5OT0RFSlNfMTJfWCxcbiAgICAgIGhhbmRsZXI6ICdpbmRleC5oYW5kbGVyJyxcbiAgICAgIGNvZGU6IG5ldyBBc3NldENvZGUocGF0aC5qb2luKF9fZGlybmFtZSwgJy4vbGFtYmRhJykpLFxuICAgICAgZW52aXJvbm1lbnQ6IHtcbiAgICAgICAgJ1BPT1AnOiAnRk9PMicsXG4gICAgICAgICdCQVonOiAnQkFSJyxcbiAgICAgICAgJ2hlbGxvJzogdGhpcy5ub2RlLnRyeUdldENvbnRleHQoJ2hlbGxvJyksXG4gICAgICB9XG4gICAgfSlcblxuICAgIGNvbnN0IG15VG9waWMgPSBuZXcgVG9waWModGhpcywgJ0pvc2hUb3BpYycpO1xuICAgIGNvbnN0IG15UXVldWUgPSBuZXcgUXVldWUodGhpcywgJ0pvc2hRdWV1ZScsIHsgZGVsaXZlcnlEZWxheTogRHVyYXRpb24uc2Vjb25kcyg1KSB9KTtcbiAgICBteVRvcGljLmFkZFN1YnNjcmlwdGlvbihuZXcgU3FzU3Vic2NyaXB0aW9uKG15UXVldWUpKTtcblxuICAgIG5ldyBRdWV1ZSh0aGlzLCAnSm9zaFF1ZXVlMicpO1xuXG4gICAgbmV3IENmbk91dHB1dCh0aGlzLCAndGhlcXVldWUnLCB7IHZhbHVlOiBteVRvcGljLnRvcGljQXJuIH0pO1xuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /src/transform.ts: -------------------------------------------------------------------------------- 1 | import * as cfnDiff from "@aws-cdk/cloudformation-diff"; 2 | import * as through2 from "through2"; 3 | 4 | import { streamToString } from "./util"; 5 | import { 6 | CdkDiffCategory, 7 | diffValidator, 8 | guardResourceDiff, 9 | NicerDiff, 10 | NicerDiffChange, 11 | NicerStackDiff, 12 | StackRawDiff, 13 | } from "./types"; 14 | import { deepSubstituteBracedLogicalIds } from "./cdk-reverse-engineered"; 15 | 16 | // unable to emulate the --no-colors option, (tried passing no-colors option to cdk Configuration class to no avail) 17 | // this is workaround to remove the colors tty elements 18 | const fixRemoveColors = (input: string): string => JSON.parse(JSON.stringify(input).replace(/\\u001b\[[^m]+m/g, '')) 19 | 20 | const buildRaw = async (diff: StackRawDiff): Promise => { 21 | const strm = through2(); 22 | cfnDiff.formatDifferences(strm, diff.rawDiff, diff.logicalToPathMap); 23 | strm.end(); 24 | return fixRemoveColors(await streamToString(strm)); 25 | }; 26 | 27 | const buildChangeAction = (oldValue: any, newValue: any) => { 28 | if (oldValue !== undefined && newValue !== undefined) { 29 | return "UPDATE"; 30 | } else if (oldValue !== undefined) { 31 | return "REMOVAL"; 32 | } else { 33 | return "ADDITION"; 34 | } 35 | }; 36 | 37 | const transformIamChanges = async ( 38 | diff: StackRawDiff 39 | ): Promise => { 40 | if (!diff.rawDiff.iamChanges.hasChanges) { 41 | return []; 42 | } 43 | 44 | const result: NicerDiff[] = []; 45 | if (diff.rawDiff.iamChanges.statements.hasChanges) { 46 | const statementsSummarized = diff.rawDiff.iamChanges.summarizeStatements(); 47 | result.push({ 48 | label: "IAM Statement Changes", 49 | cdkDiffRaw: fixRemoveColors( 50 | cfnDiff.formatTable( 51 | deepSubstituteBracedLogicalIds(diff.logicalToPathMap)( 52 | statementsSummarized 53 | ), 54 | undefined 55 | ) 56 | ), 57 | }); 58 | } 59 | 60 | if (diff.rawDiff.iamChanges.managedPolicies.hasChanges) { 61 | const managedPoliciesSummarized = 62 | diff.rawDiff.iamChanges.summarizeManagedPolicies(); 63 | result.push({ 64 | label: "IAM Policy Changes", 65 | cdkDiffRaw: fixRemoveColors( 66 | cfnDiff.formatTable( 67 | deepSubstituteBracedLogicalIds(diff.logicalToPathMap)( 68 | managedPoliciesSummarized 69 | ), 70 | undefined 71 | ) 72 | ), 73 | }); 74 | } 75 | 76 | return result; 77 | }; 78 | 79 | const transformSecurityGroupChanges = async ( 80 | diff: StackRawDiff 81 | ): Promise => { 82 | if (!diff.rawDiff.securityGroupChanges.hasChanges) { 83 | return []; 84 | } 85 | 86 | const summarized = diff.rawDiff.securityGroupChanges.summarize(); 87 | 88 | return [ 89 | { 90 | label: "Security Group Changes", 91 | cdkDiffRaw: fixRemoveColors( 92 | cfnDiff.formatTable( 93 | deepSubstituteBracedLogicalIds(diff.logicalToPathMap)(summarized), 94 | undefined 95 | ) 96 | ), 97 | }, 98 | ]; 99 | }; 100 | 101 | const processIndividualDiff = 102 | (result: NicerDiff[], cdkDiffCategory: CdkDiffCategory) => 103 | (id: string, rdiff: cfnDiff.Difference) => { 104 | if (rdiff.isDifferent) { 105 | const resourceType: string = guardResourceDiff(rdiff) 106 | ? (rdiff.isRemoval ? rdiff.oldValue?.Type : rdiff.newValue?.Type) || 107 | cdkDiffCategory 108 | : (rdiff.oldValue?.Type || rdiff.newValue?.Type || cdkDiffCategory); 109 | const changes: NicerDiffChange[] = []; 110 | if (guardResourceDiff(rdiff) && rdiff.isUpdate) { 111 | rdiff.forEachDifference((_, label, values) => { 112 | changes.push({ 113 | label, 114 | action: buildChangeAction(values.oldValue, values.newValue), 115 | from: values.oldValue, 116 | to: values.newValue, 117 | }); 118 | }); 119 | } 120 | 121 | result.push({ 122 | label: cdkDiffCategory, 123 | cdkDiffRaw: JSON.stringify({ id, diff: rdiff }, null, 2), 124 | nicerDiff: { 125 | resourceType, 126 | changes, 127 | cdkDiffCategory, 128 | resourceAction: rdiff.isAddition 129 | ? "ADDITION" 130 | : rdiff.isRemoval 131 | ? "REMOVAL" 132 | : "UPDATE", 133 | resourceLabel: id, 134 | }, 135 | }); 136 | } 137 | }; 138 | 139 | const transformDiffForResourceTypes = async (diff: StackRawDiff): Promise => { 140 | const result: NicerDiff[] = []; 141 | for (const d of Object.entries(diff.rawDiff).filter(([k]) => !["iamChanges", "securityGroupChanges"].includes(k))) { 142 | const validatedDiff = diffValidator(d); 143 | if ('diffCollection' in validatedDiff) { 144 | const { diffCollectionKey, diffCollection } = validatedDiff; 145 | if (diffCollection.differenceCount > 0) { 146 | diffCollection.forEachDifference( 147 | processIndividualDiff(result, diffCollectionKey) 148 | ); 149 | } 150 | } else if ('diffKey' in validatedDiff) { 151 | const { diffKey, diff } = validatedDiff; 152 | if (diff.isDifferent) { 153 | result.push({ 154 | label: diffKey, 155 | cdkDiffRaw: JSON.stringify({ id: diffKey, diff }, null, 2), 156 | }); 157 | } 158 | } 159 | } 160 | 161 | return result; 162 | }; 163 | 164 | const transformDescriptionChanges = (diff: StackRawDiff): NicerDiff | null => { 165 | if (diff.rawDiff.description?.isDifferent) { 166 | return { 167 | label: 'Description', 168 | cdkDiffRaw: JSON.stringify({ description: diff.rawDiff.description }, null, 2), 169 | nicerDiff: { 170 | resourceType: 'Description', 171 | changes: [{ 172 | label: 'Description', 173 | action: buildChangeAction(diff.rawDiff.description?.oldValue, diff.rawDiff.description?.newValue), 174 | from: diff.rawDiff.description?.oldValue, 175 | to: diff.rawDiff.description?.newValue 176 | }], 177 | cdkDiffCategory: 'description', 178 | resourceAction: diff.rawDiff.description?.isAddition 179 | ? "ADDITION" 180 | : diff.rawDiff.description?.isRemoval 181 | ? "REMOVAL" 182 | : "UPDATE", 183 | resourceLabel: 'Description', 184 | }, 185 | }; 186 | } 187 | return null; 188 | }; 189 | 190 | export const transformDiff = async ( 191 | diff: StackRawDiff 192 | ): Promise => { 193 | if (diff.rawDiff.isEmpty) { 194 | return { 195 | stackName: diff.stackName, 196 | raw: "There were no differences", 197 | diff: [], 198 | }; 199 | } 200 | 201 | const descriptionDiff = transformDescriptionChanges(diff); 202 | return { 203 | stackName: diff.stackName, 204 | raw: await buildRaw(diff), 205 | diff: [ 206 | ...(await transformIamChanges(diff)), 207 | ...(await transformSecurityGroupChanges(diff)), 208 | ...(await transformDiffForResourceTypes(diff)), 209 | ...(descriptionDiff ? [descriptionDiff] : []), 210 | ], 211 | }; 212 | }; 213 | 214 | -------------------------------------------------------------------------------- /src/cdk-reverse-engineered.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import * as cfnDiff from '@aws-cdk/cloudformation-diff'; 3 | import * as cxschema from '@aws-cdk/cloud-assembly-schema'; 4 | import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; 5 | import * as colors from 'colors/safe'; 6 | import { CdkToolkitDeploymentsProp, DiffOptions, StackRawDiff } from './types'; 7 | 8 | // reverse engineered from: 9 | // aws-cdk/lib/diff (printStackDiff) 10 | const filterCDKMetadata = ( 11 | diff: StackRawDiff['rawDiff'] 12 | ): StackRawDiff['rawDiff'] => { 13 | // filter out 'AWS::CDK::Metadata' resources from the template 14 | if (diff.resources) { 15 | diff.resources = diff.resources.filter((change) => { 16 | if (!change) { 17 | return true; 18 | } 19 | if (change.newResourceType === 'AWS::CDK::Metadata') { 20 | return false; 21 | } 22 | if (change.oldResourceType === 'AWS::CDK::Metadata') { 23 | return false; 24 | } 25 | return true; 26 | }); 27 | } 28 | 29 | return diff; 30 | }; 31 | 32 | // reverse engineered from: 33 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 34 | /** 35 | * Substitute all strings like ${LogId.xxx} with the path instead of the logical ID 36 | */ 37 | const substituteBracedLogicalIds = (logicalToPathMap: any) => (source: any) => { 38 | return source.replace( 39 | /\$\{([^.}]+)(.[^}]+)?\}/gi, 40 | (_match: any, logId: any, suffix: any) => { 41 | return ( 42 | '${' + 43 | (normalizedLogicalIdPath(logicalToPathMap)(logId) || logId) + 44 | (suffix || '') + 45 | '}' 46 | ); 47 | } 48 | ); 49 | }; 50 | 51 | // reverse engineered from: 52 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 53 | export const deepSubstituteBracedLogicalIds = 54 | (logicalToPathMap: any) => (rows: any) => { 55 | return rows.map((row: any[]) => 56 | row.map(substituteBracedLogicalIds(logicalToPathMap)) 57 | ); 58 | }; 59 | 60 | // reverse engineered from: 61 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 62 | const normalizedLogicalIdPath = (logicalToPathMap: any) => (logicalId: any) => { 63 | // if we have a path in the map, return it 64 | const path = logicalToPathMap[logicalId]; 65 | return path ? normalizePath(path) : undefined; 66 | /** 67 | * Path is supposed to start with "/stack-name". If this is the case (i.e. path has more than 68 | * two components, we remove the first part. Otherwise, we just use the full path. 69 | * @param p 70 | */ 71 | function normalizePath(p: string) { 72 | if (p.startsWith('/')) { 73 | p = p.substr(1); 74 | } 75 | let parts = p.split('/'); 76 | if (parts.length > 1) { 77 | parts = parts.slice(1); 78 | // remove the last component if it's "Resource" or "Default" (if we have more than a single component) 79 | if (parts.length > 1) { 80 | const last = parts[parts.length - 1]; 81 | if (last === 'Resource' || last === 'Default') { 82 | parts = parts.slice(0, parts.length - 1); 83 | } 84 | } 85 | p = parts.join('/'); 86 | } 87 | return p; 88 | } 89 | }; 90 | 91 | // copied from 92 | // aws-cdk/lib/diff (function not exported) 93 | const buildLogicalToPathMap = ( 94 | stack: cdk.cx_api.CloudFormationStackArtifact 95 | ): Record => { 96 | const map: Record = {}; 97 | for (const md of stack.findMetadataByType( 98 | cxschema.ArtifactMetadataEntryType.LOGICAL_ID 99 | )) { 100 | map[md.data as string] = md.path; 101 | } 102 | return map; 103 | }; 104 | 105 | const dynamicallyInstantiateDeployments = (sdkProvider: SdkProvider) => { 106 | let Deployments; 107 | let cdkToolkitDeploymentsProp: CdkToolkitDeploymentsProp = 'deployments'; 108 | 109 | try { 110 | Deployments = require('aws-cdk/lib/api/deployments').Deployments; 111 | } catch(err) { 112 | Deployments = require('aws-cdk/lib/api/cloudformation-deployments').CloudFormationDeployments; 113 | cdkToolkitDeploymentsProp = 'cloudFormation'; 114 | } 115 | 116 | const deployments = new Deployments({ 117 | sdkProvider, 118 | ioHelper: { 119 | defaults: { 120 | debug: (input: string) => { console.debug(input) }, 121 | } 122 | }, 123 | }); 124 | 125 | return { 126 | deployments, 127 | cdkToolkitDeploymentsProp, 128 | } 129 | } 130 | 131 | export async function getDiffObject(app: cdk.App, options?: DiffOptions) { 132 | // If we have new context, we need to create a new app with the merged context 133 | if (options?.context) { 134 | // Get existing context 135 | const existingContext = app.node.tryGetContext(''); 136 | 137 | // Create new merged context 138 | const mergedContext = { 139 | ...existingContext, 140 | ...options.context 141 | }; 142 | 143 | // Create a new App with merged context 144 | const tempApp = new cdk.App({ 145 | context: mergedContext, 146 | }); 147 | 148 | // For each stack in the original app, create a new stack in the temp app 149 | for (const child of app.node.children) { 150 | if (child instanceof cdk.Stack) { 151 | const originalStack = child as cdk.Stack; 152 | 153 | // Create a new stack of the same type 154 | const stackProps = { 155 | env: { 156 | account: originalStack.account, 157 | region: originalStack.region 158 | }, 159 | // Copy other stack properties that might be important 160 | stackName: originalStack.stackName, 161 | description: originalStack.templateOptions.description, 162 | terminationProtection: originalStack.terminationProtection, 163 | tags: originalStack.tags.tagValues(), 164 | }; 165 | 166 | // Use reflection to create a new instance of the same stack class 167 | const stackClass = Object.getPrototypeOf(originalStack).constructor; 168 | new stackClass(tempApp, originalStack.node.id, stackProps); 169 | } 170 | } 171 | 172 | // Use the temporary app for synthesis 173 | const assembly = tempApp.synth(); 174 | return await generateDiffs(assembly, options); 175 | } 176 | 177 | // If no new context, use the original app 178 | const assembly = app.synth(); 179 | return await generateDiffs(assembly, options); 180 | } 181 | 182 | // Helper function to generate diffs from an assembly 183 | async function generateDiffs(assembly: cdk.cx_api.CloudAssembly, options?: DiffOptions): Promise { 184 | const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ 185 | ioHelper: { 186 | defaults: { 187 | debug: (input: string) => { console.debug(input) }, 188 | } 189 | }, 190 | } as any, options?.profile); 191 | 192 | colors.disable(); 193 | 194 | const { deployments } = dynamicallyInstantiateDeployments(sdkProvider); 195 | const diffs: StackRawDiff[] = []; 196 | 197 | for (const stack of assembly.stacks) { 198 | const currentTemplate = await deployments.readCurrentTemplate(stack); 199 | 200 | diffs.push({ 201 | stackName: stack.displayName, 202 | rawDiff: filterCDKMetadata( 203 | cfnDiff.diffTemplate(currentTemplate, stack.template) 204 | ), 205 | logicalToPathMap: buildLogicalToPathMap(stack) 206 | }); 207 | } 208 | 209 | return diffs; 210 | } 211 | -------------------------------------------------------------------------------- /dist/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.diffValidator = exports.guardResourceDiff = exports.nicerStackDiffValidator = exports.nicerStackDiffGuard = exports.nicerDiffGuard = exports.cdkDiffCategories = void 0; 4 | exports.cdkDiffCategories = ['iamChanges', 'securityGroup', 'resources', 'parameters', 'metadata', 'mappings', 'conditions', 'outputs', 'unknown', 'description']; 5 | const nicerDiffGuard = (thing) => typeof thing === 'object' && 6 | typeof thing.label === 'string' && 7 | typeof thing.cdkDiffRaw === 'string' && 8 | ['undefined', 'object'].includes(typeof thing.nicerDiff); 9 | exports.nicerDiffGuard = nicerDiffGuard; 10 | const nicerStackDiffGuard = (thing) => { 11 | if (typeof thing === 'object') { 12 | if (typeof thing.raw === 'string' && typeof thing.stackName === 'string') { 13 | if (!!thing.diff) { 14 | if (thing.diff.filter(exports.nicerDiffGuard).length === thing.diff.length) { 15 | return true; 16 | } 17 | } 18 | return true; 19 | } 20 | } 21 | return false; 22 | }; 23 | exports.nicerStackDiffGuard = nicerStackDiffGuard; 24 | const nicerStackDiffValidator = (thing) => { 25 | if (typeof thing === 'object') { 26 | if (thing.filter(exports.nicerStackDiffGuard).length === thing.length) { 27 | return thing; 28 | } 29 | } 30 | throw new Error(`input is not a NicerStackDiff[]: ${JSON.stringify(thing, null, 2)}`); 31 | }; 32 | exports.nicerStackDiffValidator = nicerStackDiffValidator; 33 | const guardResourceDiff = (thing) => typeof thing === 'object' && 34 | typeof thing.forEachDifference === 'function'; 35 | exports.guardResourceDiff = guardResourceDiff; 36 | const diffValidator = (thing) => { 37 | if (typeof thing === 'object') { 38 | if (thing.length === 2) { 39 | const [diffKey, diff] = thing; 40 | if (!exports.cdkDiffCategories.includes(diffKey)) { 41 | throw new Error(`unexpected diff category: ${diffKey}`); 42 | } 43 | if (diffKey === 'description') { 44 | return { diffKey, diff }; 45 | } 46 | else if (typeof diff === 'object' && diff.hasOwnProperty('diffs')) { 47 | return { diffCollectionKey: diffKey, diffCollection: diff }; 48 | } 49 | } 50 | } 51 | throw new Error(`invalid diff: ${JSON.stringify(thing, null, 2)}`); 52 | }; 53 | exports.diffValidator = diffValidator; 54 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR2EsUUFBQSxpQkFBaUIsR0FBRyxDQUFDLFlBQVksRUFBRSxlQUFlLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLGFBQWEsQ0FBVSxDQUFDO0FBMEJ6SyxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQVUsRUFBc0IsRUFBRSxDQUMvRCxPQUFPLEtBQUssS0FBSyxRQUFRO0lBQ3pCLE9BQU8sS0FBSyxDQUFDLEtBQUssS0FBSyxRQUFRO0lBQy9CLE9BQU8sS0FBSyxDQUFDLFVBQVUsS0FBSyxRQUFRO0lBQ3BDLENBQUMsV0FBVyxFQUFFLFFBQVEsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUo5QyxRQUFBLGNBQWMsa0JBSWdDO0FBUXBELE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxLQUFVLEVBQTJCLEVBQUU7SUFDekUsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUU7UUFDN0IsSUFBSSxPQUFPLEtBQUssQ0FBQyxHQUFHLEtBQUssUUFBUSxJQUFJLE9BQU8sS0FBSyxDQUFDLFNBQVMsS0FBSyxRQUFRLEVBQUU7WUFDeEUsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRTtnQkFDaEIsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxzQkFBYyxDQUFDLENBQUMsTUFBTSxLQUFLLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFO29CQUNsRSxPQUFPLElBQUksQ0FBQztpQkFDYjthQUNGO1lBRUQsT0FBTyxJQUFJLENBQUM7U0FDYjtLQUNGO0lBRUQsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDLENBQUE7QUFkWSxRQUFBLG1CQUFtQix1QkFjL0I7QUFFTSxNQUFNLHVCQUF1QixHQUFHLENBQUMsS0FBVSxFQUFvQixFQUFFO0lBQ3RFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFO1FBQzdCLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQywyQkFBbUIsQ0FBQyxDQUFDLE1BQU0sS0FBSyxLQUFLLENBQUMsTUFBTSxFQUFFO1lBQzdELE9BQU8sS0FBSyxDQUFDO1NBQ2Q7S0FDRjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMsb0NBQW9DLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDeEYsQ0FBQyxDQUFBO0FBUlksUUFBQSx1QkFBdUIsMkJBUW5DO0FBRU0sTUFBTSxpQkFBaUIsR0FBRyxDQUFDLEtBQVUsRUFBdUMsRUFBRSxDQUNuRixPQUFPLEtBQUssS0FBSyxRQUFRO0lBQ3pCLE9BQU8sS0FBSyxDQUFDLGlCQUFpQixLQUFLLFVBQVUsQ0FBQztBQUZuQyxRQUFBLGlCQUFpQixxQkFFa0I7QUFFekMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxLQUFVLEVBQW9MLEVBQUU7SUFDNU4sSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUU7UUFDN0IsSUFBSSxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtZQUN0QixNQUFNLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxHQUFHLEtBQUssQ0FBQztZQUU5QixJQUFJLENBQUMseUJBQWlCLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUN4QyxNQUFNLElBQUksS0FBSyxDQUFDLDZCQUE2QixPQUFPLEVBQUUsQ0FBQyxDQUFDO2FBQ3pEO1lBRUQsSUFBSSxPQUFPLEtBQUssYUFBYSxFQUFFO2dCQUM3QixPQUFPLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDO2FBQzFCO2lCQUFNLElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksQ0FBQyxjQUFjLENBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ25FLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxPQUFPLEVBQUUsY0FBYyxFQUFFLElBQUksRUFBRSxDQUFDO2FBQzdEO1NBQ0Y7S0FDRjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMsaUJBQWlCLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDckUsQ0FBQyxDQUFBO0FBbEJZLFFBQUEsYUFBYSxpQkFrQnpCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2ZuRGlmZiBmcm9tICdAYXdzLWNkay9jbG91ZGZvcm1hdGlvbi1kaWZmJztcbmltcG9ydCB7IENka1Rvb2xraXRQcm9wcyB9IGZyb20gJ2F3cy1jZGsvbGliL2NsaS9jZGstdG9vbGtpdCc7XG5cbmV4cG9ydCBjb25zdCBjZGtEaWZmQ2F0ZWdvcmllcyA9IFsnaWFtQ2hhbmdlcycsICdzZWN1cml0eUdyb3VwJywgJ3Jlc291cmNlcycsICdwYXJhbWV0ZXJzJywgJ21ldGFkYXRhJywgJ21hcHBpbmdzJywgJ2NvbmRpdGlvbnMnLCAnb3V0cHV0cycsICd1bmtub3duJywgJ2Rlc2NyaXB0aW9uJ10gYXMgY29uc3Q7XG5leHBvcnQgdHlwZSBDZGtEaWZmQ2F0ZWdvcmllcyA9IHR5cGVvZiBjZGtEaWZmQ2F0ZWdvcmllcztcbmV4cG9ydCB0eXBlIENka0RpZmZDYXRlZ29yeSA9IENka0RpZmZDYXRlZ29yaWVzW251bWJlcl07XG5leHBvcnQgdHlwZSBTdGFja1Jhd0RpZmYgPSB7XG4gIHN0YWNrTmFtZTogc3RyaW5nO1xuICByYXdEaWZmOiBjZm5EaWZmLlRlbXBsYXRlRGlmZixcbiAgbG9naWNhbFRvUGF0aE1hcDogUmVjb3JkPHN0cmluZywgc3RyaW5nPlxufTtcblxuZXhwb3J0IHR5cGUgTmljZXJEaWZmQ2hhbmdlID0ge1xuICBsYWJlbDogc3RyaW5nO1xuICBmcm9tPzogYW55O1xuICB0bzogYW55O1xuICBhY3Rpb246ICdBRERJVElPTicgfCAnVVBEQVRFJyB8ICdSRU1PVkFMJztcbn1cbmV4cG9ydCB0eXBlIE5pY2VyRGlmZiA9IHtcbiAgbGFiZWw6IHN0cmluZztcbiAgY2RrRGlmZlJhdzogc3RyaW5nO1xuICBuaWNlckRpZmY/OiB7XG4gICAgY2RrRGlmZkNhdGVnb3J5OiBDZGtEaWZmQ2F0ZWdvcnk7XG4gICAgcmVzb3VyY2VBY3Rpb246ICdBRERJVElPTicgfCAnVVBEQVRFJyB8ICdSRU1PVkFMJztcbiAgICByZXNvdXJjZVR5cGU6IHN0cmluZztcbiAgICByZXNvdXJjZUxhYmVsOiBzdHJpbmc7XG4gICAgY2hhbmdlczogTmljZXJEaWZmQ2hhbmdlW107XG4gIH1cbn1cbmV4cG9ydCBjb25zdCBuaWNlckRpZmZHdWFyZCA9ICh0aGluZzogYW55KTogdGhpbmcgaXMgTmljZXJEaWZmID0+XG4gIHR5cGVvZiB0aGluZyA9PT0gJ29iamVjdCcgJiZcbiAgdHlwZW9mIHRoaW5nLmxhYmVsID09PSAnc3RyaW5nJyAmJlxuICB0eXBlb2YgdGhpbmcuY2RrRGlmZlJhdyA9PT0gJ3N0cmluZycgJiZcbiAgWyd1bmRlZmluZWQnLCAnb2JqZWN0J10uaW5jbHVkZXModHlwZW9mIHRoaW5nLm5pY2VyRGlmZik7XG5cbmV4cG9ydCB0eXBlIE5pY2VyU3RhY2tEaWZmID0ge1xuICBkaWZmPzogTmljZXJEaWZmW107XG4gIHJhdzogc3RyaW5nO1xuICBzdGFja05hbWU6IHN0cmluZztcbn1cblxuZXhwb3J0IGNvbnN0IG5pY2VyU3RhY2tEaWZmR3VhcmQgPSAodGhpbmc6IGFueSk6IHRoaW5nIGlzIE5pY2VyU3RhY2tEaWZmID0+IHtcbiAgaWYgKHR5cGVvZiB0aGluZyA9PT0gJ29iamVjdCcpIHtcbiAgICBpZiAodHlwZW9mIHRoaW5nLnJhdyA9PT0gJ3N0cmluZycgJiYgdHlwZW9mIHRoaW5nLnN0YWNrTmFtZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICghIXRoaW5nLmRpZmYpIHtcbiAgICAgICAgaWYgKHRoaW5nLmRpZmYuZmlsdGVyKG5pY2VyRGlmZkd1YXJkKS5sZW5ndGggPT09IHRoaW5nLmRpZmYubGVuZ3RoKSB7XG4gICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG5leHBvcnQgY29uc3QgbmljZXJTdGFja0RpZmZWYWxpZGF0b3IgPSAodGhpbmc6IGFueSk6IE5pY2VyU3RhY2tEaWZmW10gPT4ge1xuICBpZiAodHlwZW9mIHRoaW5nID09PSAnb2JqZWN0Jykge1xuICAgIGlmICh0aGluZy5maWx0ZXIobmljZXJTdGFja0RpZmZHdWFyZCkubGVuZ3RoID09PSB0aGluZy5sZW5ndGgpIHtcbiAgICAgIHJldHVybiB0aGluZztcbiAgICB9XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoYGlucHV0IGlzIG5vdCBhIE5pY2VyU3RhY2tEaWZmW106ICR7SlNPTi5zdHJpbmdpZnkodGhpbmcsIG51bGwsIDIpfWApO1xufVxuXG5leHBvcnQgY29uc3QgZ3VhcmRSZXNvdXJjZURpZmYgPSAodGhpbmc6IGFueSk6IHRoaW5nIGlzIGNmbkRpZmYuUmVzb3VyY2VEaWZmZXJlbmNlID0+XG4gIHR5cGVvZiB0aGluZyA9PT0gJ29iamVjdCcgJiZcbiAgdHlwZW9mIHRoaW5nLmZvckVhY2hEaWZmZXJlbmNlID09PSAnZnVuY3Rpb24nO1xuXG5leHBvcnQgY29uc3QgZGlmZlZhbGlkYXRvciA9ICh0aGluZzogYW55KTogeyBkaWZmQ29sbGVjdGlvbktleTogQ2RrRGlmZkNhdGVnb3J5OyBkaWZmQ29sbGVjdGlvbjogY2ZuRGlmZi5EaWZmZXJlbmNlQ29sbGVjdGlvbjxhbnksIGNmbkRpZmYuRGlmZmVyZW5jZTxhbnk+PiB9IHwgeyBkaWZmS2V5OiBDZGtEaWZmQ2F0ZWdvcnk7IGRpZmY6IGNmbkRpZmYuRGlmZmVyZW5jZTxhbnk+IH0gPT4ge1xuICBpZiAodHlwZW9mIHRoaW5nID09PSAnb2JqZWN0Jykge1xuICAgIGlmICh0aGluZy5sZW5ndGggPT09IDIpIHtcbiAgICAgIGNvbnN0IFtkaWZmS2V5LCBkaWZmXSA9IHRoaW5nO1xuXG4gICAgICBpZiAoIWNka0RpZmZDYXRlZ29yaWVzLmluY2x1ZGVzKGRpZmZLZXkpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgdW5leHBlY3RlZCBkaWZmIGNhdGVnb3J5OiAke2RpZmZLZXl9YCk7XG4gICAgICB9XG5cbiAgICAgIGlmIChkaWZmS2V5ID09PSAnZGVzY3JpcHRpb24nKSB7XG4gICAgICAgIHJldHVybiB7IGRpZmZLZXksIGRpZmYgfTtcbiAgICAgIH0gZWxzZSBpZiAodHlwZW9mIGRpZmYgPT09ICdvYmplY3QnICYmIGRpZmYuaGFzT3duUHJvcGVydHkoJ2RpZmZzJykpIHtcbiAgICAgICAgcmV0dXJuIHsgZGlmZkNvbGxlY3Rpb25LZXk6IGRpZmZLZXksIGRpZmZDb2xsZWN0aW9uOiBkaWZmIH07XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgdGhyb3cgbmV3IEVycm9yKGBpbnZhbGlkIGRpZmY6ICR7SlNPTi5zdHJpbmdpZnkodGhpbmcsIG51bGwsIDIpfWApO1xufVxuXG5leHBvcnQgdHlwZSBDZGtUb29sa2l0RGVwbG95bWVudHNQcm9wID0gJ2Nsb3VkRm9ybWF0aW9uJyB8ICdkZXBsb3ltZW50cyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRGlmZk9wdGlvbnMge1xuICBjb250ZXh0PzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcbiAgcHJvZmlsZT86IHN0cmluZztcbn1cbiJdfQ== -------------------------------------------------------------------------------- /dist/pretty-diff-template.html.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: "\n\n\n \n \n prettyplan\n \n \n \n \n \n
    \n
    \n

    prettyplan

    \n
    \n That doesn't look like a Terraform plan. Did you copy the entire output (without colouring) from the plan\n command?\n
    \n
    \n
      \n
        \n \n \n
        \n
          \n
          \n      
          \n
          \n \n \n\n"; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /src/pretty-diff-template.html.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 4 | 5 | 6 | prettyplan 7 | 8 | 9 | 358 | 359 | 360 |
          361 |
          362 |

          prettyplan

          363 | 367 |
          368 |
            369 |
              370 | 371 | 372 |
              373 |
                374 |
                
                375 |       
                376 |
                377 | 443 | 444 | 445 | `; 446 | -------------------------------------------------------------------------------- /dist/test-util/mock-cdk-test-raw-diff.d.ts: -------------------------------------------------------------------------------- 1 | export declare const mockCdkTestRawDiff: () => { 2 | stackName: string; 3 | rawDiff: { 4 | conditions: { 5 | diffs: { 6 | CDKMetadataAvailable: { 7 | oldValue: { 8 | "Fn::Or": { 9 | "Fn::Or": { 10 | "Fn::Equals": (string | { 11 | Ref: string; 12 | })[]; 13 | }[]; 14 | }[]; 15 | }; 16 | newValue: { 17 | "Fn::Or": { 18 | "Fn::Or": { 19 | "Fn::Equals": (string | { 20 | Ref: string; 21 | })[]; 22 | }[]; 23 | }[]; 24 | }; 25 | isDifferent: boolean; 26 | }; 27 | }; 28 | }; 29 | mappings: { 30 | diffs: {}; 31 | }; 32 | metadata: { 33 | diffs: {}; 34 | }; 35 | outputs: { 36 | diffs: { 37 | thequeue: { 38 | oldValue: { 39 | Value: { 40 | "Fn::GetAtt": string[]; 41 | }; 42 | }; 43 | newValue: { 44 | Value: { 45 | Ref: string; 46 | }; 47 | }; 48 | isDifferent: boolean; 49 | }; 50 | }; 51 | }; 52 | parameters: { 53 | diffs: { 54 | AssetParameters804034ebb823673f97b6ff287df451eb98d6cf368cbf2fe6f877d61a431b2a97S3Bucket302ABB09: { 55 | oldValue: { 56 | Type: string; 57 | Description: string; 58 | }; 59 | isDifferent: boolean; 60 | }; 61 | AssetParameters804034ebb823673f97b6ff287df451eb98d6cf368cbf2fe6f877d61a431b2a97S3VersionKey4C878B0E: { 62 | oldValue: { 63 | Type: string; 64 | Description: string; 65 | }; 66 | isDifferent: boolean; 67 | }; 68 | AssetParameters804034ebb823673f97b6ff287df451eb98d6cf368cbf2fe6f877d61a431b2a97ArtifactHash6F137924: { 69 | oldValue: { 70 | Type: string; 71 | Description: string; 72 | }; 73 | isDifferent: boolean; 74 | }; 75 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3S3Bucket77CF327B: { 76 | newValue: { 77 | Type: string; 78 | Description: string; 79 | }; 80 | isDifferent: boolean; 81 | }; 82 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3S3VersionKey0F68B949: { 83 | newValue: { 84 | Type: string; 85 | Description: string; 86 | }; 87 | isDifferent: boolean; 88 | }; 89 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3ArtifactHashE106A955: { 90 | newValue: { 91 | Type: string; 92 | Description: string; 93 | }; 94 | isDifferent: boolean; 95 | }; 96 | }; 97 | }; 98 | resources: { 99 | diffs: { 100 | JoshLambdaC8236207: { 101 | oldValue: { 102 | Type: string; 103 | Properties: { 104 | Code: { 105 | S3Bucket: { 106 | Ref: string; 107 | }; 108 | S3Key: { 109 | "Fn::Join": (string | { 110 | "Fn::Select": (number | { 111 | "Fn::Split": (string | { 112 | Ref: string; 113 | })[]; 114 | })[]; 115 | }[])[]; 116 | }; 117 | }; 118 | Handler: string; 119 | Role: { 120 | "Fn::GetAtt": string[]; 121 | }; 122 | Runtime: string; 123 | Environment: { 124 | Variables: { 125 | POOP: string; 126 | }; 127 | }; 128 | }; 129 | DependsOn: string[]; 130 | Metadata: { 131 | "aws:cdk:path": string; 132 | "aws:asset:path": string; 133 | "aws:asset:property": string; 134 | }; 135 | }; 136 | newValue: { 137 | Type: string; 138 | Properties: { 139 | Code: { 140 | S3Bucket: { 141 | Ref: string; 142 | }; 143 | S3Key: { 144 | "Fn::Join": (string | { 145 | "Fn::Select": (number | { 146 | "Fn::Split": (string | { 147 | Ref: string; 148 | })[]; 149 | })[]; 150 | }[])[]; 151 | }; 152 | }; 153 | Role: { 154 | "Fn::GetAtt": string[]; 155 | }; 156 | Environment: { 157 | Variables: { 158 | POOP: string; 159 | BAZ: string; 160 | }; 161 | }; 162 | Handler: string; 163 | Runtime: string; 164 | }; 165 | DependsOn: string[]; 166 | Metadata: { 167 | "aws:cdk:path": string; 168 | "aws:asset:path": string; 169 | "aws:asset:property": string; 170 | }; 171 | }; 172 | resourceTypes: { 173 | oldType: string; 174 | newType: string; 175 | }; 176 | propertyDiffs: { 177 | Code: { 178 | oldValue: { 179 | S3Bucket: { 180 | Ref: string; 181 | }; 182 | S3Key: { 183 | "Fn::Join": (string | { 184 | "Fn::Select": (number | { 185 | "Fn::Split": (string | { 186 | Ref: string; 187 | })[]; 188 | })[]; 189 | }[])[]; 190 | }; 191 | }; 192 | newValue: { 193 | S3Bucket: { 194 | Ref: string; 195 | }; 196 | S3Key: { 197 | "Fn::Join": (string | { 198 | "Fn::Select": (number | { 199 | "Fn::Split": (string | { 200 | Ref: string; 201 | })[]; 202 | })[]; 203 | }[])[]; 204 | }; 205 | }; 206 | isDifferent: boolean; 207 | changeImpact: string; 208 | }; 209 | Handler: { 210 | oldValue: string; 211 | newValue: string; 212 | isDifferent: boolean; 213 | changeImpact: string; 214 | }; 215 | Role: { 216 | oldValue: { 217 | "Fn::GetAtt": string[]; 218 | }; 219 | newValue: { 220 | "Fn::GetAtt": string[]; 221 | }; 222 | isDifferent: boolean; 223 | changeImpact: string; 224 | }; 225 | Runtime: { 226 | oldValue: string; 227 | newValue: string; 228 | isDifferent: boolean; 229 | changeImpact: string; 230 | }; 231 | Environment: { 232 | oldValue: { 233 | Variables: { 234 | POOP: string; 235 | }; 236 | }; 237 | newValue: { 238 | Variables: { 239 | POOP: string; 240 | BAZ: string; 241 | }; 242 | }; 243 | isDifferent: boolean; 244 | changeImpact: string; 245 | }; 246 | }; 247 | otherDiffs: { 248 | Type: { 249 | oldValue: string; 250 | newValue: string; 251 | isDifferent: boolean; 252 | }; 253 | DependsOn: { 254 | oldValue: string[]; 255 | newValue: string[]; 256 | isDifferent: boolean; 257 | }; 258 | Metadata: { 259 | oldValue: { 260 | "aws:cdk:path": string; 261 | "aws:asset:path": string; 262 | "aws:asset:property": string; 263 | }; 264 | newValue: { 265 | "aws:cdk:path": string; 266 | "aws:asset:path": string; 267 | "aws:asset:property": string; 268 | }; 269 | isDifferent: boolean; 270 | }; 271 | }; 272 | isAddition: boolean; 273 | isRemoval: boolean; 274 | }; 275 | JoshQueueEB99F847: { 276 | oldValue: { 277 | Type: string; 278 | Metadata: { 279 | "aws:cdk:path": string; 280 | }; 281 | }; 282 | newValue: { 283 | Type: string; 284 | Properties: { 285 | DelaySeconds: number; 286 | }; 287 | UpdateReplacePolicy: string; 288 | DeletionPolicy: string; 289 | Metadata: { 290 | "aws:cdk:path": string; 291 | }; 292 | }; 293 | resourceTypes: { 294 | oldType: string; 295 | newType: string; 296 | }; 297 | propertyDiffs: { 298 | DelaySeconds: { 299 | newValue: number; 300 | isDifferent: boolean; 301 | changeImpact: string; 302 | }; 303 | }; 304 | otherDiffs: { 305 | Type: { 306 | oldValue: string; 307 | newValue: string; 308 | isDifferent: boolean; 309 | }; 310 | Metadata: { 311 | oldValue: { 312 | "aws:cdk:path": string; 313 | }; 314 | newValue: { 315 | "aws:cdk:path": string; 316 | }; 317 | isDifferent: boolean; 318 | }; 319 | UpdateReplacePolicy: { 320 | newValue: string; 321 | isDifferent: boolean; 322 | }; 323 | DeletionPolicy: { 324 | newValue: string; 325 | isDifferent: boolean; 326 | }; 327 | }; 328 | isAddition: boolean; 329 | isRemoval: boolean; 330 | }; 331 | JoshQueue2C9D19A77: { 332 | newValue: { 333 | Type: string; 334 | UpdateReplacePolicy: string; 335 | DeletionPolicy: string; 336 | Metadata: { 337 | "aws:cdk:path": string; 338 | }; 339 | }; 340 | resourceTypes: { 341 | newType: string; 342 | }; 343 | propertyDiffs: {}; 344 | otherDiffs: {}; 345 | isAddition: boolean; 346 | isRemoval: boolean; 347 | }; 348 | }; 349 | }; 350 | unknown: { 351 | diffs: {}; 352 | }; 353 | iamChanges: { 354 | statements: { 355 | additions: never[]; 356 | removals: never[]; 357 | oldElements: never[]; 358 | newElements: never[]; 359 | }; 360 | managedPolicies: { 361 | additions: never[]; 362 | removals: never[]; 363 | oldElements: never[]; 364 | newElements: never[]; 365 | }; 366 | }; 367 | securityGroupChanges: { 368 | ingress: { 369 | additions: never[]; 370 | removals: never[]; 371 | oldElements: never[]; 372 | newElements: never[]; 373 | }; 374 | egress: { 375 | additions: never[]; 376 | removals: never[]; 377 | oldElements: never[]; 378 | newElements: never[]; 379 | }; 380 | }; 381 | }; 382 | logicalToPathMap: { 383 | joshpoop360D5A6B7: string; 384 | MyRoleF48FFE04: string; 385 | JoshLambdaServiceRoleDEC0C426: string; 386 | JoshLambdaC8236207: string; 387 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3S3Bucket77CF327B: string; 388 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3S3VersionKey0F68B949: string; 389 | AssetParameters476c839c35f9d3d72e0e6b896683458e4943fdbeb6bb6a1393f2dda249c90de3ArtifactHashE106A955: string; 390 | JoshTopicA4ECB805: string; 391 | JoshQueueEB99F847: string; 392 | JoshQueuePolicy5D4DD568: string; 393 | JoshQueueJoshStackJoshTopicA950703A630F8DC9: string; 394 | JoshQueue2C9D19A77: string; 395 | thequeue: string; 396 | CDKMetadata: string; 397 | CDKMetadataAvailable: string; 398 | }; 399 | }[]; 400 | -------------------------------------------------------------------------------- /dist/render.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.renderCustomDiffToHtmlString = exports.renderCustomDiffToHtmlNodeString = void 0; 4 | const diff_1 = require("diff"); 5 | const diff2html = require("diff2html"); 6 | const pretty_diff_template_html_1 = require("./pretty-diff-template.html"); 7 | const prettify = (valueIn) => { 8 | // fallback to empty string (eg. JSON.stringify of undefined is undefined) 9 | const value = (typeof valueIn === "string" ? valueIn : JSON.stringify(valueIn, null, 2)) || ''; 10 | if (value === "") { 11 | return `<computed>`; 12 | } 13 | if (value.startsWith("${") && value.endsWith("}")) { 14 | return `${value}`; 15 | } 16 | if (value.indexOf("\\n") >= 0 || value.indexOf('\\"') >= 0) { 17 | const sanitisedValue = value 18 | .replace(new RegExp("\\\\n", "g"), "\n") 19 | .replace(new RegExp('\\\\"', "g"), '"'); 20 | return `
                ${prettifyJson(sanitisedValue)}
                `; 21 | } 22 | return value; 23 | }; 24 | const prettifyJson = (maybeJson) => { 25 | try { 26 | return JSON.stringify(JSON.parse(maybeJson), null, 2); 27 | } 28 | catch (e) { 29 | return maybeJson; 30 | } 31 | }; 32 | const components = { 33 | badge: (label) => ` 34 | ${label} 35 | `, 36 | id: (id) => ` 37 | 38 | ${id.resourceType} 39 | ${id.resourceLabel} 40 | 41 | `, 42 | warning: (warning) => ` 43 |
              • 44 | ${components.badge("warning")} 45 | ${components.id(warning.id)} 46 | ${warning.detail} 47 |
              • 48 | `, 49 | changeCount: (count) => ` 50 | 51 | ${`${count} change${count > 1 ? "s" : ""}`} 52 | 53 | `, 54 | changeNoDiff: ({ action, to, label }) => ` 55 | 56 | 57 | ${label} 58 | ${`
                (${action})`} 59 | 60 | ${prettify(to)} 61 | 62 | `, 63 | changeDiff: ({ from, to, label }) => ` 64 |
                65 | ${diff2html.html((0, diff_1.createTwoFilesPatch)(label, label, prettify(from), prettify(to)), { 66 | outputFormat: 'line-by-line', 67 | drawFileList: false, 68 | matching: 'words', 69 | matchWordsThreshold: 0.25, 70 | matchingMaxComparisons: 200, 71 | })} 72 |
                73 | `, 74 | changes: (changes) => { 75 | const diffChanges = changes.filter(({ from }) => !!from); 76 | const noDiffChanges = changes.filter(({ from }) => !from); 77 | return ` 78 |
                79 |
                80 | ${noDiffChanges.length ? (` 81 | 82 | ${noDiffChanges.map(components.changeNoDiff).join("")} 83 |
                84 | `) : ''} 85 |
                86 | ${diffChanges.map(components.changeDiff).join("")} 87 | 88 | `; 89 | }, 90 | action: ({ cdkDiffRaw, nicerDiff, label }) => ` 91 |
              • 92 |
                93 | ${components.badge((nicerDiff === null || nicerDiff === void 0 ? void 0 : nicerDiff.resourceAction) || "")} 94 | ${components.id(nicerDiff || { resourceType: "", resourceLabel: label })} 95 |
                96 | 105 |
              • 106 | `, 107 | modal: (content) => ` 108 | 109 | 113 | `, 114 | rawDiff: (raw, toggleCaption, opts) => ` 115 |
                116 | ${typeof (opts === null || opts === void 0 ? void 0 : opts.showButton) === "boolean" && (opts === null || opts === void 0 ? void 0 : opts.showButton) === false 117 | ? "" 118 | : ``} 119 |
                120 |
                ${raw}
                121 |
                122 |
                123 | `, 124 | stackDiff: ({ stackName, raw, diff }) => ` 125 |
                126 |

                ${stackName}

                127 | ${components.rawDiff(raw, "Orig CDK Diff", { collapsed: true })} 128 | ${!(diff === null || diff === void 0 ? void 0 : diff.length) ? `
                No changes
                ` : ""} 129 |
                  130 | ${diff === null || diff === void 0 ? void 0 : diff.filter(({ nicerDiff }) => !nicerDiff || 131 | !["parameters"].includes(nicerDiff === null || nicerDiff === void 0 ? void 0 : nicerDiff.cdkDiffCategory)).map(components.action).join("\n")} 132 |
                133 |
                134 | `, 135 | }; 136 | const renderCustomDiffToHtmlNodeString = (diffs) => diffs.map(components.stackDiff).join(' '); 137 | exports.renderCustomDiffToHtmlNodeString = renderCustomDiffToHtmlNodeString; 138 | const renderCustomDiffToHtmlString = (diffs, title) => { 139 | let html = pretty_diff_template_html_1.default; 140 | html = html 141 | .replace(`

                prettyplan

                `, `

                ${title}

                `) 142 | .replace(`prettyplan`, `${title}`); 143 | html = html.replace(`
                `, `
                ${(0, exports.renderCustomDiffToHtmlNodeString)(diffs)}
                `); 144 | return html; 145 | }; 146 | exports.renderCustomDiffToHtmlString = renderCustomDiffToHtmlString; 147 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVuZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3JlbmRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBMkM7QUFDM0MsdUNBQXVDO0FBR3ZDLDJFQUF1RDtBQUV2RCxNQUFNLFFBQVEsR0FBRyxDQUFDLE9BQVksRUFBVSxFQUFFO0lBQ3hDLDBFQUEwRTtJQUMxRSxNQUFNLEtBQUssR0FDVCxDQUFDLE9BQU8sT0FBTyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFFbkYsSUFBSSxLQUFLLEtBQUssWUFBWSxFQUFFO1FBQzFCLE9BQU8sMkJBQTJCLENBQUM7S0FDcEM7SUFFRCxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNqRCxPQUFPLE9BQU8sS0FBSyxPQUFPLENBQUM7S0FDNUI7SUFFRCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQzFELE1BQU0sY0FBYyxHQUFHLEtBQUs7YUFDekIsT0FBTyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxJQUFJLENBQUM7YUFDdkMsT0FBTyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUUxQyxPQUFPLFFBQVEsWUFBWSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUM7S0FDckQ7SUFFRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMsQ0FBQztBQUVGLE1BQU0sWUFBWSxHQUFHLENBQUMs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QUFRLENBQ3BFLENBQUM7SUFFRixPQUFPLElBQUksQ0FBQztBQUNkLENBQUMsQ0FBQztBQWZXLFFBQUEsNEJBQTRCLGdDQWV2QyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNyZWF0ZVR3b0ZpbGVzUGF0Y2ggfSBmcm9tICdkaWZmJztcbmltcG9ydCAqIGFzIGRpZmYyaHRtbCBmcm9tICdkaWZmMmh0bWwnO1xuXG5pbXBvcnQgeyBOaWNlckRpZmYsIE5pY2VyRGlmZkNoYW5nZSwgTmljZXJTdGFja0RpZmYgfSBmcm9tIFwiLi90eXBlc1wiO1xuaW1wb3J0IGh0bWxUZW1wbGF0ZSBmcm9tICcuL3ByZXR0eS1kaWZmLXRlbXBsYXRlLmh0bWwnO1xuXG5jb25zdCBwcmV0dGlmeSA9ICh2YWx1ZUluOiBhbnkpOiBzdHJpbmcgPT4ge1xuICAvLyBmYWxsYmFjayB0byBlbXB0eSBzdHJpbmcgKGVnLiBKU09OLnN0cmluZ2lmeSBvZiB1bmRlZmluZWQgaXMgdW5kZWZpbmVkKVxuICBjb25zdCB2YWx1ZSA9XG4gICAgKHR5cGVvZiB2YWx1ZUluID09PSBcInN0cmluZ1wiID8gdmFsdWVJbiA6IEpTT04uc3RyaW5naWZ5KHZhbHVlSW4sIG51bGwsIDIpKSB8fCAnJztcblxuICBpZiAodmFsdWUgPT09IFwiPGNvbXB1dGVkPlwiKSB7XG4gICAgcmV0dXJuIGA8ZW0+Jmx0O2NvbXB1dGVkJmd0OzwvZW0+YDtcbiAgfVxuXG4gIGlmICh2YWx1ZS5zdGFydHNXaXRoKFwiJHtcIikgJiYgdmFsdWUuZW5kc1dpdGgoXCJ9XCIpKSB7XG4gICAgcmV0dXJuIGA8ZW0+JHt2YWx1ZX08L2VtPmA7XG4gIH1cblxuICBpZiAodmFsdWUuaW5kZXhPZihcIlxcXFxuXCIpID49IDAgfHwgdmFsdWUuaW5kZXhPZignXFxcXFwiJykgPj0gMCkge1xuICAgIGNvbnN0IHNhbml0aXNlZFZhbHVlID0gdmFsdWVcbiAgICAgIC5yZXBsYWNlKG5ldyBSZWdFeHAoXCJcXFxcXFxcXG5cIiwgXCJnXCIpLCBcIlxcblwiKVxuICAgICAgLnJlcGxhY2UobmV3IFJlZ0V4cCgnXFxcXFxcXFxcIicsIFwiZ1wiKSwgJ1wiJyk7XG5cbiAgICByZXR1cm4gYDxwcmU+JHtwcmV0dGlmeUpzb24oc2FuaXRpc2VkVmFsdWUpfTwvcHJlPmA7XG4gIH1cblxuICByZXR1cm4gdmFsdWU7XG59O1xuXG5jb25zdCBwcmV0dGlmeUpzb24gPSAobWF5YmVKc29uOiBzdHJpbmcpOiBzdHJpbmcgPT4ge1xuICB0cnkge1xuICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShKU09OLnBhcnNlKG1heWJlSnNvbiksIG51bGwsIDIpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgcmV0dXJuIG1heWJlSnNvbjtcbiAgfVxufTtcblxuY29uc3QgY29tcG9uZW50cyA9IHtcbiAgYmFkZ2U6IChsYWJlbDogc3RyaW5nKTogc3RyaW5nID0+IGBcbiAgICA8c3BhbiBjbGFzcz1cImJhZGdlXCI+JHtsYWJlbH08L3NwYW4+XG4gIGAsXG5cbiAgaWQ6IChpZDogYW55KTogc3RyaW5nID0+IGBcbiAgICA8c3BhbiBjbGFzcz1cImlkXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cImlkLXNlZ21lbnQgdHlwZVwiPiR7aWQucmVzb3VyY2VUeXBlfTwvc3Bhbj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiaWQtc2VnbWVudCBuYW1lXCI+JHtpZC5yZXNvdXJjZUxhYmVsfTwvc3Bhbj5cbiAgICA8L3NwYW4+XG4gIGAsXG5cbiAgd2FybmluZzogKHdhcm5pbmc6IGFueSk6IHN0cmluZyA9PiBgXG4gICAgPGxpPlxuICAgICAgJHtjb21wb25lbnRzLmJhZGdlKFwid2FybmluZ1wiKX1cbiAgICAgICR7Y29tcG9uZW50cy5pZCh3YXJuaW5nLmlkKX1cbiAgICAgIDxzcGFuPiR7d2FybmluZy5kZXRhaWx9PC9zcGFuPlxuICAgIDwvbGk+XG4gIGAsXG5cbiAgY2hhbmdlQ291bnQ6IChjb3VudDogbnVtYmVyKTogc3RyaW5nID0+IGBcbiAgICA8c3BhbiBjbGFzcz1cImNoYW5nZS1jb3VudFwiPlxuICAgICAgJHtgJHtjb3VudH0gY2hhbmdlJHtjb3VudCA+IDEgPyBcInNcIiA6IFwiXCJ9YH1cbiAgICA8L3NwYW4+XG4gIGAsXG5cbiAgY2hhbmdlTm9EaWZmOiAoeyBhY3Rpb24sIHRvLCBsYWJlbCB9OiBOaWNlckRpZmZDaGFuZ2UpOiBzdHJpbmcgPT4gYFxuICAgIDx0cj5cbiAgICAgIDx0ZCBjbGFzcz1cInByb3BlcnR5XCI+XG4gICAgICAgICR7bGFiZWx9XG4gICAgICAgICR7YDxiciAvPjxzcGFuIGNsYXNzPVwiZm9yY2VzLW5ldy1yZXNvdXJjZVwiPigke2FjdGlvbn0pPC9zcGFuPmB9XG4gICAgICA8L3RkPlxuICAgICAgPHRkIGNsYXNzPVwibmV3LXZhbHVlXCI+JHtwcmV0dGlmeSh0byl9PC90ZD5cbiAgICA8L3RyPlxuICBgLFxuXG4gIGNoYW5nZURpZmY6ICh7IGZyb20sIHRvLCBsYWJlbCB9OiBOaWNlckRpZmZDaGFuZ2UpOiBzdHJpbmcgPT4gYFxuICAgIDxkaXY+XG4gICAgICAke2RpZmYyaHRtbC5odG1sKFxuICAgICAgICBjcmVhdGVUd29GaWxlc1BhdGNoKGxhYmVsLCBsYWJlbCwgcHJldHRpZnkoZnJvbSksIHByZXR0aWZ5KHRvKSksXG4gICAgICAgIHtcbiAgICAgICAgICBvdXRwdXRGb3JtYXQ6ICdsaW5lLWJ5LWxpbmUnLFxuICAgICAgICAgIGRyYXdGaWxlTGlzdDogZmFsc2UsXG4gICAgICAgICAgbWF0Y2hpbmc6ICd3b3JkcycsXG4gICAgICAgICAgbWF0Y2hXb3Jkc1RocmVzaG9sZDogMC4yNSxcbiAgICAgICAgICBtYXRjaGluZ01heENvbXBhcmlzb25zOiAyMDAsXG4gICAgICAgIH1cbiAgICAgICl9XG4gICAgPC9kaXY+XG4gIGAsXG5cbiAgY2hhbmdlczogKGNoYW5nZXM6IE5pY2VyRGlmZkNoYW5nZVtdKSA9PiB7XG4gICAgY29uc3QgZGlmZkNoYW5nZXMgPSBjaGFuZ2VzLmZpbHRlcigoeyBmcm9tIH0pID0+ICEhZnJvbSk7XG4gICAgY29uc3Qgbm9EaWZmQ2hhbmdlcyA9IGNoYW5nZXMuZmlsdGVyKCh7IGZyb20gfSkgPT4gIWZyb20pO1xuXG4gICAgcmV0dXJuIGBcbiAgICAgIDxkaXYgY2xhc3M9XCJjaGFuZ2VzLWJyZWFrZG93blwiPlxuICAgICAgICA8ZGl2IGNsYXNzPVwibm8tZGlmZi1jaGFuZ2VzLWJyZWFrZG93blwiPlxuICAgICAgICAgICR7bm9EaWZmQ2hhbmdlcy5sZW5ndGggPyAoYFxuICAgICAgICAgICAgPHRhYmxlPlxuICAgICAgICAgICAgICAke25vRGlmZkNoYW5nZXMubWFwKGNvbXBvbmVudHMuY2hhbmdlTm9EaWZmKS5qb2luKFwiXCIpfVxuICAgICAgICAgICAgPC90YWJsZT5cbiAgICAgICAgICBgKSA6ICcnfVxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgJHtkaWZmQ2hhbmdlcy5tYXAoY29tcG9uZW50cy5jaGFuZ2VEaWZmKS5qb2luKFwiXCIpfVxuICAgICAgPC90YWJsZT5cbiAgICBgXG4gIH0sXG5cbiAgYWN0aW9uOiAoeyBjZGtEaWZmUmF3LCBuaWNlckRpZmYsIGxhYmVsIH06IE5pY2VyRGlmZik6IHN0cmluZyA9PiBgXG4gICAgPGxpIGNsYXNzPVwiJHtuaWNlckRpZmY/LnJlc291cmNlQWN0aW9uLnRvTG9jYWxlTG93ZXJDYXNlKCkgfHwgXCJjcmVhdGVcIn1cIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzdW1tYXJ5XCIgb25jbGljaz1cImFjY29yZGlvbih0aGlzKVwiPlxuICAgICAgICAke2NvbXBvbmVudHMuYmFkZ2UobmljZXJEaWZmPy5yZXNvdXJjZUFjdGlvbiB8fCBcIlwiKX1cbiAgICAgICAgJHtjb21wb25lbnRzLmlkKFxuICAgICAgICAgIG5pY2VyRGlmZiB8fCB7IHJlc291cmNlVHlwZTogXCJcIiwgcmVzb3VyY2VMYWJlbDogbGFiZWwgfVxuICAgICAgICApfVxuICAgICAgPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiY2hhbmdlcyBjb2xsYXBzZWRcIj5cbiAgICAgICAgJHtcbiAgICAgICAgICBuaWNlckRpZmY/LmNoYW5nZXMubGVuZ3RoXG4gICAgICAgICAgICA/IGNvbXBvbmVudHMuY2hhbmdlcyhuaWNlckRpZmYuY2hhbmdlcylcbiAgICAgICAgICAgIDogXCJcIlxuICAgICAgICB9XG4gICAgICAgICR7Y29tcG9uZW50cy5yYXdEaWZmKGNka0RpZmZSYXcsIFwiQ0RLIERpZmYgT3V0cHV0XCIsIHtcbiAgICAgICAgICBjb2xsYXBzZWQ6ICFuaWNlckRpZmY/LmNoYW5nZXMubGVuZ3RoLFxuICAgICAgICAgIHNob3dCdXR0b246ICEhbmljZXJEaWZmPy5jaGFuZ2VzLmxlbmd0aCxcbiAgICAgICAgfSl9XG4gICAgICA8L2Rpdj5cbiAgICA8L2xpPlxuICBgLFxuXG4gIG1vZGFsOiAoY29udGVudDogc3RyaW5nKTogc3RyaW5nID0+IGBcbiAgICAgIDxkaXYgY2xhc3M9XCJtb2RhbC1wYW5lXCIgb25jbGljaz1cImNsb3NlTW9kYWwoKVwiPjwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cIm1vZGFsLWNvbnRlbnRcIj5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwibW9kYWwtY2xvc2VcIj48YnV0dG9uIGNsYXNzPVwidGV4dC1idXR0b25cIiBvbmNsaWNrPVwiY2xvc2VNb2RhbCgpXCI+Y2xvc2U8L2J1dHRvbj48L2Rpdj5cbiAgICAgICAgICAke2NvbnRlbnR9XG4gICAgICA8L2Rpdj5cbiAgYCxcblxuICByYXdEaWZmOiAoXG4gICAgcmF3OiBzdHJpbmcsXG4gICAgdG9nZ2xlQ2FwdGlvbjogc3RyaW5nLFxuICAgIG9wdHM/OiB7IGNvbGxhcHNlZDogYm9vbGVhbjsgc2hvd0J1dHRvbj86IGJvb2xlYW4gfVxuICApOiBzdHJpbmcgPT4gYFxuICAgIDxkaXYgY2xhc3M9XCJyYXctZGlmZlwiPlxuICAgICAgJHtcbiAgICAgICAgdHlwZW9mIG9wdHM/LnNob3dCdXR0b24gPT09IFwiYm9vbGVhblwiICYmIG9wdHM/LnNob3dCdXR0b24gPT09IGZhbHNlXG4gICAgICAgICAgPyBcIlwiXG4gICAgICAgICAgOiBgPGJ1dHRvbiBvbmNsaWNrPVwiYWNjb3JkaW9uKHRoaXMpXCI+JHt0b2dnbGVDYXB0aW9ufTwvYnV0dG9uPmBcbiAgICAgIH1cbiAgICAgIDxkaXYgY2xhc3M9XCJjaGFuZ2VzICR7b3B0cz8uY29sbGFwc2VkID8gXCJjb2xsYXBzZWRcIiA6IFwiXCJ9XCI+XG4gICAgICAgICAgPHByZT4ke3Jhd308L3ByZT5cbiAgICAgIDwvZGl2PlxuICAgIDwvZGl2PlxuICBgLFxuXG4gIHN0YWNrRGlmZjogKHsgc3RhY2tOYW1lLCByYXcsIGRpZmYgfTogTmljZXJTdGFja0RpZmYpOiBzdHJpbmcgPT4gYFxuICAgIDxkaXYgY2xhc3M9XCJzdGFja1wiPlxuICAgICAgPGgyPiR7c3RhY2tOYW1lfTwvaDI+XG4gICAgICAke2NvbXBvbmVudHMucmF3RGlmZihyYXcsIFwiT3JpZyBDREsgRGlmZlwiLCB7IGNvbGxhcHNlZDogdHJ1ZSB9KX1cbiAgICAgICR7IWRpZmY/Lmxlbmd0aCA/IGA8ZGl2Pk5vIGNoYW5nZXM8L2Rpdj5gIDogXCJcIn1cbiAgICAgIDx1bCBjbGFzcz1cImFjdGlvbnNcIj5cbiAgICAgICAgJHtkaWZmXG4gICAgICAgICAgPy5maWx0ZXIoXG4gICAgICAgICAgICAoeyBuaWNlckRpZmYgfSkgPT5cbiAgICAgICAgICAgICAgIW5pY2VyRGlmZiB8fFxuICAgICAgICAgICAgICAhW1wicGFyYW1ldGVyc1wiXS5pbmNsdWRlcyhuaWNlckRpZmY/LmNka0RpZmZDYXRlZ29yeSlcbiAgICAgICAgICApXG4gICAgICAgICAgLm1hcChjb21wb25lbnRzLmFjdGlvbilcbiAgICAgICAgICAuam9pbihcIlxcblwiKX1cbiAgICAgIDwvdWw+XG4gICAgPC9kaXY+XG4gIGAsXG59O1xuXG5leHBvcnQgY29uc3QgcmVuZGVyQ3VzdG9tRGlmZlRvSHRtbE5vZGVTdHJpbmcgPSAoZGlmZnM6IE5pY2VyU3RhY2tEaWZmW10pOiBzdHJpbmcgPT5cbiAgZGlmZnMubWFwKGNvbXBvbmVudHMuc3RhY2tEaWZmKS5qb2luKCcgJyk7XG5cbmV4cG9ydCBjb25zdCByZW5kZXJDdXN0b21EaWZmVG9IdG1sU3RyaW5nID0gKFxuICBkaWZmczogTmljZXJTdGFja0RpZmZbXSxcbiAgdGl0bGU6IHN0cmluZ1xuKTogc3RyaW5nID0+IHtcbiAgbGV0IGh0bWwgPSBodG1sVGVtcGxhdGU7XG4gIGh0bWwgPSBodG1sXG4gICAgLnJlcGxhY2UoYDxoMT5wcmV0dHlwbGFuPC9oMT5gLCBgPGgxPiR7dGl0bGV9PC9oMT5gKVxuICAgIC5yZXBsYWNlKGA8dGl0bGU+cHJldHR5cGxhbjwvdGl0bGU+YCwgYDx0aXRsZT4ke3RpdGxlfTwvdGl0bGU+YCk7XG5cbiAgaHRtbCA9IGh0bWwucmVwbGFjZShcbiAgICBgPGRpdiBpZD1cInN0YWNrc1wiPjwvZGl2PmAsXG4gICAgYDxkaXYgaWQ9XCJzdGFja3NcIj4ke3JlbmRlckN1c3RvbURpZmZUb0h0bWxOb2RlU3RyaW5nKGRpZmZzKX08L2Rpdj5gXG4gICk7XG5cbiAgcmV0dXJuIGh0bWw7XG59O1xuIl19 -------------------------------------------------------------------------------- /dist/cdk-reverse-engineered.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getDiffObject = exports.deepSubstituteBracedLogicalIds = void 0; 4 | const cdk = require("aws-cdk-lib"); 5 | const cfnDiff = require("@aws-cdk/cloudformation-diff"); 6 | const cxschema = require("@aws-cdk/cloud-assembly-schema"); 7 | const aws_auth_1 = require("aws-cdk/lib/api/aws-auth"); 8 | const colors = require("colors/safe"); 9 | // reverse engineered from: 10 | // aws-cdk/lib/diff (printStackDiff) 11 | const filterCDKMetadata = (diff) => { 12 | // filter out 'AWS::CDK::Metadata' resources from the template 13 | if (diff.resources) { 14 | diff.resources = diff.resources.filter((change) => { 15 | if (!change) { 16 | return true; 17 | } 18 | if (change.newResourceType === 'AWS::CDK::Metadata') { 19 | return false; 20 | } 21 | if (change.oldResourceType === 'AWS::CDK::Metadata') { 22 | return false; 23 | } 24 | return true; 25 | }); 26 | } 27 | return diff; 28 | }; 29 | // reverse engineered from: 30 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 31 | /** 32 | * Substitute all strings like ${LogId.xxx} with the path instead of the logical ID 33 | */ 34 | const substituteBracedLogicalIds = (logicalToPathMap) => (source) => { 35 | return source.replace(/\$\{([^.}]+)(.[^}]+)?\}/gi, (_match, logId, suffix) => { 36 | return ('${' + 37 | (normalizedLogicalIdPath(logicalToPathMap)(logId) || logId) + 38 | (suffix || '') + 39 | '}'); 40 | }); 41 | }; 42 | // reverse engineered from: 43 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 44 | const deepSubstituteBracedLogicalIds = (logicalToPathMap) => (rows) => { 45 | return rows.map((row) => row.map(substituteBracedLogicalIds(logicalToPathMap))); 46 | }; 47 | exports.deepSubstituteBracedLogicalIds = deepSubstituteBracedLogicalIds; 48 | // reverse engineered from: 49 | // @aws-cdk/cloudformation-diff/lib/format (Formatter class is not exported) 50 | const normalizedLogicalIdPath = (logicalToPathMap) => (logicalId) => { 51 | // if we have a path in the map, return it 52 | const path = logicalToPathMap[logicalId]; 53 | return path ? normalizePath(path) : undefined; 54 | /** 55 | * Path is supposed to start with "/stack-name". If this is the case (i.e. path has more than 56 | * two components, we remove the first part. Otherwise, we just use the full path. 57 | * @param p 58 | */ 59 | function normalizePath(p) { 60 | if (p.startsWith('/')) { 61 | p = p.substr(1); 62 | } 63 | let parts = p.split('/'); 64 | if (parts.length > 1) { 65 | parts = parts.slice(1); 66 | // remove the last component if it's "Resource" or "Default" (if we have more than a single component) 67 | if (parts.length > 1) { 68 | const last = parts[parts.length - 1]; 69 | if (last === 'Resource' || last === 'Default') { 70 | parts = parts.slice(0, parts.length - 1); 71 | } 72 | } 73 | p = parts.join('/'); 74 | } 75 | return p; 76 | } 77 | }; 78 | // copied from 79 | // aws-cdk/lib/diff (function not exported) 80 | const buildLogicalToPathMap = (stack) => { 81 | const map = {}; 82 | for (const md of stack.findMetadataByType(cxschema.ArtifactMetadataEntryType.LOGICAL_ID)) { 83 | map[md.data] = md.path; 84 | } 85 | return map; 86 | }; 87 | const dynamicallyInstantiateDeployments = (sdkProvider) => { 88 | let Deployments; 89 | let cdkToolkitDeploymentsProp = 'deployments'; 90 | try { 91 | Deployments = require('aws-cdk/lib/api/deployments').Deployments; 92 | } 93 | catch (err) { 94 | Deployments = require('aws-cdk/lib/api/cloudformation-deployments').CloudFormationDeployments; 95 | cdkToolkitDeploymentsProp = 'cloudFormation'; 96 | } 97 | const deployments = new Deployments({ 98 | sdkProvider, 99 | ioHelper: { 100 | defaults: { 101 | debug: (input) => { console.debug(input); }, 102 | } 103 | }, 104 | }); 105 | return { 106 | deployments, 107 | cdkToolkitDeploymentsProp, 108 | }; 109 | }; 110 | async function getDiffObject(app, options) { 111 | // If we have new context, we need to create a new app with the merged context 112 | if (options === null || options === void 0 ? void 0 : options.context) { 113 | // Get existing context 114 | const existingContext = app.node.tryGetContext(''); 115 | // Create new merged context 116 | const mergedContext = { 117 | ...existingContext, 118 | ...options.context 119 | }; 120 | // Create a new App with merged context 121 | const tempApp = new cdk.App({ 122 | context: mergedContext, 123 | }); 124 | // For each stack in the original app, create a new stack in the temp app 125 | for (const child of app.node.children) { 126 | if (child instanceof cdk.Stack) { 127 | const originalStack = child; 128 | // Create a new stack of the same type 129 | const stackProps = { 130 | env: { 131 | account: originalStack.account, 132 | region: originalStack.region 133 | }, 134 | // Copy other stack properties that might be important 135 | stackName: originalStack.stackName, 136 | description: originalStack.templateOptions.description, 137 | terminationProtection: originalStack.terminationProtection, 138 | tags: originalStack.tags.tagValues(), 139 | }; 140 | // Use reflection to create a new instance of the same stack class 141 | const stackClass = Object.getPrototypeOf(originalStack).constructor; 142 | new stackClass(tempApp, originalStack.node.id, stackProps); 143 | } 144 | } 145 | // Use the temporary app for synthesis 146 | const assembly = tempApp.synth(); 147 | return await generateDiffs(assembly, options); 148 | } 149 | // If no new context, use the original app 150 | const assembly = app.synth(); 151 | return await generateDiffs(assembly, options); 152 | } 153 | exports.getDiffObject = getDiffObject; 154 | // Helper function to generate diffs from an assembly 155 | async function generateDiffs(assembly, options) { 156 | const sdkProvider = await aws_auth_1.SdkProvider.withAwsCliCompatibleDefaults({ 157 | ioHelper: { 158 | defaults: { 159 | debug: (input) => { console.debug(input); }, 160 | } 161 | }, 162 | }, options === null || options === void 0 ? void 0 : options.profile); 163 | colors.disable(); 164 | const { deployments } = dynamicallyInstantiateDeployments(sdkProvider); 165 | const diffs = []; 166 | for (const stack of assembly.stacks) { 167 | const currentTemplate = await deployments.readCurrentTemplate(stack); 168 | diffs.push({ 169 | stackName: stack.displayName, 170 | rawDiff: filterCDKMetadata(cfnDiff.diffTemplate(currentTemplate, stack.template)), 171 | logicalToPathMap: buildLogicalToPathMap(stack) 172 | }); 173 | } 174 | return diffs; 175 | } 176 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLXJldmVyc2UtZW5naW5lZXJlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jZGstcmV2ZXJzZS1lbmdpbmVlcmVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUFtQztBQUNuQyx3REFBd0Q7QUFDeEQsMkRBQTJEO0FBQzNELHVEQUF1RDtBQUN2RCxzQ0FBc0M7QUFHdEMsMkJBQTJCO0FBQzNCLG9DQUFvQztBQUNwQyxNQUFNLGlCQUFpQixHQUFHLENBQ3hCLElBQTZCLEVBQ0osRUFBRTtJQUMzQiw4REFBOEQ7SUFDOUQsSUFBSSxJQUFJLENBQUMsU0FBUyxFQUFFO1FBQ2xCLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUNoRCxJQUFJLENBQUMsTUFBTSxFQUFFO2dCQUNYLE9BQU8sSUFBSSxDQUFDO2FBQ2I7WUFDRCxJQUFJLE1BQU0sQ0FBQyxlQUFlLEtBQUssb0JBQW9CLEVBQUU7Z0JBQ25ELE9BQU8sS0FBSyxDQUFDO2FBQ2Q7WUFDRCxJQUFJLE1BQU0sQ0FBQyxlQUFlLEtBQUssb0JBQW9CLEVBQUU7Z0JBQ25ELE9BQU8sS0FBSyxDQUFDO2FBQ2Q7WUFDRCxPQUFPLElBQUksQ0FBQztRQUNkLENBQUMsQ0FBQyxDQUFDO0tBQ0o7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMsQ0FBQztBQUVGLDJCQUEyQjtBQUMzQiw0RUFBNEU7QUFDNUU7O0dBRUc7QUFDSCxNQUFNLDBCQUEwQixHQUFHLENBQUMsZ0JBQXFCLEVBQUUsRUFBRSxDQUFDLENBQUMsTUFBVyxFQUFFLEVBQUU7SUFDNUUsT0FBTyxNQUFNLENBQUMsT0FBTyxDQUNuQiwyQkFBMkIsRUFDM0IsQ0FBQyxNQUFXLEVBQUUsS0FBVSxFQUFFLE1BQVcsRUFBRSxFQUFFO1FBQ3ZDLE9BQU8sQ0FDTCxJQUFJO1lBQ0osQ0FBQyx1QkFBdUIsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssQ0FBQztZQUMzRCxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFDZCxHQUFHLENBQ0osQ0FBQztJQUNKLENBQUMsQ0FDRixDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBRUYsMkJBQTJCO0FBQzNCLDRFQUE0RTtBQUNyRSxNQUFNLDhCQUE4QixHQUN6QyxDQUFDLGdCQUFxQixFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQVMsRUFBRSxFQUFFO0lBQ3ZDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQVUsRUFBRSxFQUFFLENBQzdCLEdBQUcsQ0FBQyxHQUFHLENBQUMsMEJBQTBCLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUN0RCxDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBTFMsUUFBQSw4QkFBOEIsa0NBS3ZDO0FBRUosMkJBQTJCO0FBQzNCLDRFQUE0RTtBQUM1RSxNQUFNLHVCQUF1QixHQUFHLENBQUMsZ0JBQXFCLEVBQUUsRUFBRSxDQUFDLENBQUMsU0FBYyxFQUFFLEVBQUU7SUFDNUUsMENBQTBDO0lBQzFDLE1BQU0sSUFBSSxHQUFHLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3pDLE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUM5Qzs7OztPQUlHO0lBQ0gsU0FBUyxhQUFhLENBQUMsQ0FBUztRQUM5QixJQUFJLENBQUMsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUU7WUFDckIsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDakI7UUFDRCxJQUFJLEtBQUssR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pCLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUU7WUFDcEIsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDdkIsc0dBQXNHO1lBQ3RHLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUU7Z0JBQ3BCLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUNyQyxJQUFJLElBQUksS0FBSyxVQUFVLElBQUksSUFBSSxLQUFLLFNBQVMsRUFBRTtvQkFDN0MsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7aUJBQzFDO2FBQ0Y7WUFDRCxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUNyQjtRQUNELE9BQU8sQ0FBQyxDQUFDO0lBQ1gsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLGNBQWM7QUFDZCwyQ0FBMkM7QUFDM0MsTUFBTSxxQkFBcUIsR0FBRyxDQUM1QixLQUE2QyxFQUNyQixFQUFFO0lBQzFCLE1BQU0sR0FBRyxHQUF3QixFQUFFLENBQUM7SUFDcEMsS0FBSyxNQUFNLEVBQUUsSUFBSSxLQUFLLENBQUMsa0JBQWtCLENBQ3ZDLFFBQVEsQ0FBQyx5QkFBeUIsQ0FBQyxVQUFVLENBQzlDLEVBQUU7UUFDRCxHQUFHLENBQUMsRUFBRSxDQUFDLElBQWMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUM7S0FDbEM7SUFDRCxPQUFPLEdBQUcsQ0FBQztBQUNiLENBQUMsQ0FBQztBQUVGLE1BQU0saUNBQWlDLEdBQUcsQ0FBQyxXQUF3QixFQUFFLEVBQUU7SUFDckUsSUFBSSxXQUFXLENBQUM7SUFDaEIsSUFBSSx5QkFBeUIsR0FBOEIsYUFBYSxDQUFDO0lBRXpFLElBQUk7UUFDRixXQUFXLEdBQUcsT0FBTyxDQUFDLDZCQUE2QixDQUFDLENBQUMsV0FBVyxDQUFDO0tBQ2xFO0lBQUMsT0FBTSxHQUFHLEVBQUU7UUFDWCxXQUFXLEdBQUcsT0FBTyxDQUFDLDRDQUE0QyxDQUFDLENBQUMseUJBQXlCLENBQUM7UUFDOUYseUJBQXlCLEdBQUcsZ0JBQWdCLENBQUM7S0FDOUM7SUFFRCxNQUFNLFdBQVcsR0FBRyxJQUFJLFdBQVcsQ0FBQztRQUNsQyxXQUFXO1FBQ1gsUUFBUSxFQUFFO1lBQ1IsUUFBUSxFQUFFO2dCQUNSLEtBQUssRUFBRSxDQUFDLEtBQWEsRUFBRSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQSxDQUFDLENBQUM7YUFDbkQ7U0FDRjtLQUNGLENBQUMsQ0FBQztJQUVILE9BQU87UUFDTCxXQUFXO1FBQ1gseUJBQXlCO0tBQzFCLENBQUE7QUFDSCxDQUFDLENBQUE7QUFFTSxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQVksRUFBRSxPQUFxQjtJQUNyRSw4RUFBOEU7SUFDOUUsSUFBSSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsT0FBTyxFQUFFO1FBQ3BCLHVCQUF1QjtRQUN2QixNQUFNLGVBQWUsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUVuRCw0QkFBNEI7UUFDNUIsTUFBTSxhQUFhLEdBQUc7WUFDcEIsR0FBRyxlQUFlO1lBQ2xCLEdBQUcsT0FBTyxDQUFDLE9BQU87U0FDbkIsQ0FBQztRQUVGLHVDQUF1QztRQUN2QyxNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUM7WUFDMUIsT0FBTyxFQUFFLGFBQWE7U0FDdkIsQ0FBQyxDQUFDO1FBRUgseUVBQXlFO1FBQ3pFLEtBQUssTUFBTSxLQUFLLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDckMsSUFBSSxLQUFLLFlBQVksR0FBRyxDQUFDLEtBQUssRUFBRTtnQkFDOUIsTUFBTSxhQUFhLEdBQUcsS0FBa0IsQ0FBQztnQkFFekMsc0NBQXNDO2dCQUN0QyxNQUFNLFVBQVUsR0FBRztvQkFDakIsR0FBRyxFQUFFO3dCQUNILE9BQU8sRUFBRSxhQUFhLENBQUMsT0FBTzt3QkFDOUIsTUFBTSxFQUFFLGFBQWEsQ0FBQyxNQUFNO3FCQUM3QjtvQkFDRCxzREFBc0Q7b0JBQ3RELFNBQVMsRUFBRSxhQUFhLENBQUMsU0FBUztvQkFDbEMsV0FBVyxFQUFFLGFBQWEsQ0FBQyxlQUFlLENBQUMsV0FBVztvQkFDdEQscUJBQXFCLEVBQUUsYUFBYSxDQUFDLHFCQUFxQjtvQkFDMUQsSUFBSSxFQUFFLGFBQWEsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFO2lCQUNyQyxDQUFDO2dCQUVGLGtFQUFrRTtnQkFDbEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxXQUFXLENBQUM7Z0JBQ3BFLElBQUksVUFBVSxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxVQUFVLENBQUMsQ0FBQzthQUM1RDtTQUNGO1FBRUQsc0NBQXNDO1FBQ3RDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNqQyxPQUFPLE1BQU0sYUFBYSxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztLQUMvQztJQUVELDBDQUEwQztJQUMxQyxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDN0IsT0FBTyxNQUFNLGFBQWEsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDaEQsQ0FBQztBQWpERCxzQ0FpREM7QUFFRCxxREFBcUQ7QUFDckQsS0FBSyxVQUFVLGFBQWEsQ0FBQyxRQUFrQyxFQUFFLE9BQXFCO0lBQ3BGLE1BQU0sV0FBVyxHQUFHLE1BQU0sc0JBQVcsQ0FBQyw0QkFBNEIsQ0FBQztRQUNqRSxRQUFRLEVBQUU7WUFDUixRQUFRLEVBQUU7Z0JBQ1IsS0FBSyxFQUFFLENBQUMsS0FBYSxFQUFFLEVBQUUsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFBLENBQUMsQ0FBQzthQUNuRDtTQUNGO0tBQ0ssRUFBRSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsT0FBTyxDQUFDLENBQUM7SUFFNUIsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRWpCLE1BQU0sRUFBRSxXQUFXLEVBQUUsR0FBRyxpQ0FBaUMsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN2RSxNQUFNLEtBQUssR0FBbUIsRUFBRSxDQUFDO0lBRWpDLEtBQUssTUFBTSxLQUFLLElBQUksUUFBUSxDQUFDLE1BQU0sRUFBRTtRQUNuQyxNQUFNLGVBQWUsR0FBRyxNQUFNLFdBQVcsQ0FBQyxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVyRSxLQUFLLENBQUMsSUFBSSxDQUFDO1lBQ1QsU0FBUyxFQUFFLEtBQUssQ0FBQyxXQUFXO1lBQzVCLE9BQU8sRUFBRSxpQkFBaUIsQ0FDeEIsT0FBTyxDQUFDLFlBQVksQ0FBQyxlQUFlLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUN0RDtZQUNELGdCQUFnQixFQUFFLHFCQUFxQixDQUFDLEtBQUssQ0FBQztTQUMvQyxDQUFDLENBQUM7S0FDSjtJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGNkayBmcm9tICdhd3MtY2RrLWxpYic7XG5pbXBvcnQgKiBhcyBjZm5EaWZmIGZyb20gJ0Bhd3MtY2RrL2Nsb3VkZm9ybWF0aW9uLWRpZmYnO1xuaW1wb3J0ICogYXMgY3hzY2hlbWEgZnJvbSAnQGF3cy1jZGsvY2xvdWQtYXNzZW1ibHktc2NoZW1hJztcbmltcG9ydCB7IFNka1Byb3ZpZGVyIH0gZnJvbSAnYXdzLWNkay9saWIvYXBpL2F3cy1hdXRoJztcbmltcG9ydCAqIGFzIGNvbG9ycyBmcm9tICdjb2xvcnMvc2FmZSc7XG5pbXBvcnQgeyBDZGtUb29sa2l0RGVwbG95bWVudHNQcm9wLCBEaWZmT3B0aW9ucywgU3RhY2tSYXdEaWZmIH0gZnJvbSAnLi90eXBlcyc7XG5cbi8vIHJldmVyc2UgZW5naW5lZXJlZCBmcm9tOlxuLy8gYXdzLWNkay9saWIvZGlmZiAocHJpbnRTdGFja0RpZmYpXG5jb25zdCBmaWx0ZXJDREtNZXRhZGF0YSA9IChcbiAgZGlmZjogU3RhY2tSYXdEaWZmWydyYXdEaWZmJ11cbik6IFN0YWNrUmF3RGlmZlsncmF3RGlmZiddID0+IHtcbiAgLy8gZmlsdGVyIG91dCAnQVdTOjpDREs6Ok1ldGFkYXRhJyByZXNvdXJjZXMgZnJvbSB0aGUgdGVtcGxhdGVcbiAgaWYgKGRpZmYucmVzb3VyY2VzKSB7XG4gICAgZGlmZi5yZXNvdXJjZXMgPSBkaWZmLnJlc291cmNlcy5maWx0ZXIoKGNoYW5nZSkgPT4ge1xuICAgICAgaWYgKCFjaGFuZ2UpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgICBpZiAoY2hhbmdlLm5ld1Jlc291cmNlVHlwZSA9PT0gJ0FXUzo6Q0RLOjpNZXRhZGF0YScpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgaWYgKGNoYW5nZS5vbGRSZXNvdXJjZVR5cGUgPT09ICdBV1M6OkNESzo6TWV0YWRhdGEnKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0pO1xuICB9XG5cbiAgcmV0dXJuIGRpZmY7XG59O1xuXG4vLyByZXZlcnNlIGVuZ2luZWVyZWQgZnJvbTpcbi8vIEBhd3MtY2RrL2Nsb3VkZm9ybWF0aW9uLWRpZmYvbGliL2Zvcm1hdCAoRm9ybWF0dGVyIGNsYXNzIGlzIG5vdCBleHBvcnRlZClcbi8qKlxuICogU3Vic3RpdHV0ZSBhbGwgc3RyaW5ncyBsaWtlICR7TG9nSWQueHh4fSB3aXRoIHRoZSBwYXRoIGluc3RlYWQgb2YgdGhlIGxvZ2ljYWwgSURcbiAqL1xuY29uc3Qgc3Vic3RpdHV0ZUJyYWNlZExvZ2ljYWxJZHMgPSAobG9naWNhbFRvUGF0aE1hcDogYW55KSA9PiAoc291cmNlOiBhbnkpID0+IHtcbiAgcmV0dXJuIHNvdXJjZS5yZXBsYWNlKFxuICAgIC9cXCRcXHsoW14ufV0rKSguW159XSspP1xcfS9naSxcbiAgICAoX21hdGNoOiBhbnksIGxvZ0lkOiBhbnksIHN1ZmZpeDogYW55KSA9PiB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICAnJHsnICtcbiAgICAgICAgKG5vcm1hbGl6ZWRMb2dpY2FsSWRQYXRoKGxvZ2ljYWxUb1BhdGhNYXApKGxvZ0lkKSB8fCBsb2dJZCkgK1xuICAgICAgICAoc3VmZml4IHx8ICcnKSArXG4gICAgICAgICd9J1xuICAgICAgKTtcbiAgICB9XG4gICk7XG59O1xuXG4vLyByZXZlcnNlIGVuZ2luZWVyZWQgZnJvbTpcbi8vIEBhd3MtY2RrL2Nsb3VkZm9ybWF0aW9uLWRpZmYvbGliL2Zvcm1hdCAoRm9ybWF0dGVyIGNsYXNzIGlzIG5vdCBleHBvcnRlZClcbmV4cG9ydCBjb25zdCBkZWVwU3Vic3RpdHV0ZUJyYWNlZExvZ2ljYWxJZHMgPVxuICAobG9naWNhbFRvUGF0aE1hcDogYW55KSA9PiAocm93czogYW55KSA9PiB7XG4gICAgcmV0dXJuIHJvd3MubWFwKChyb3c6IGFueVtdKSA9PlxuICAgICAgcm93Lm1hcChzdWJzdGl0dXRlQnJhY2VkTG9naWNhbElkcyhsb2dpY2FsVG9QYXRoTWFwKSlcbiAgICApO1xuICB9O1xuXG4vLyByZXZlcnNlIGVuZ2luZWVyZWQgZnJvbTpcbi8vIEBhd3MtY2RrL2Nsb3VkZm9ybWF0aW9uLWRpZmYvbGliL2Zvcm1hdCAoRm9ybWF0dGVyIGNsYXNzIGlzIG5vdCBleHBvcnRlZClcbmNvbnN0IG5vcm1hbGl6ZWRMb2dpY2FsSWRQYXRoID0gKGxvZ2ljYWxUb1BhdGhNYXA6IGFueSkgPT4gKGxvZ2ljYWxJZDogYW55KSA9PiB7XG4gIC8vIGlmIHdlIGhhdmUgYSBwYXRoIGluIHRoZSBtYXAsIHJldHVybiBpdFxuICBjb25zdCBwYXRoID0gbG9naWNhbFRvUGF0aE1hcFtsb2dpY2FsSWRdO1xuICByZXR1cm4gcGF0aCA/IG5vcm1hbGl6ZVBhdGgocGF0aCkgOiB1bmRlZmluZWQ7XG4gIC8qKlxuICAgKiBQYXRoIGlzIHN1cHBvc2VkIHRvIHN0YXJ0IHdpdGggXCIvc3RhY2stbmFtZVwiLiBJZiB0aGlzIGlzIHRoZSBjYXNlIChpLmUuIHBhdGggaGFzIG1vcmUgdGhhblxuICAgKiB0d28gY29tcG9uZW50cywgd2UgcmVtb3ZlIHRoZSBmaXJzdCBwYXJ0LiBPdGhlcndpc2UsIHdlIGp1c3QgdXNlIHRoZSBmdWxsIHBhdGguXG4gICAqIEBwYXJhbSBwXG4gICAqL1xuICBmdW5jdGlvbiBub3JtYWxpemVQYXRoKHA6IHN0cmluZykge1xuICAgIGlmIChwLnN0YXJ0c1dpdGgoJy8nKSkge1xuICAgICAgcCA9IHAuc3Vic3RyKDEpO1xuICAgIH1cbiAgICBsZXQgcGFydHMgPSBwLnNwbGl0KCcvJyk7XG4gICAgaWYgKHBhcnRzLmxlbmd0aCA+IDEpIHtcbiAgICAgIHBhcnRzID0gcGFydHMuc2xpY2UoMSk7XG4gICAgICAvLyByZW1vdmUgdGhlIGxhc3QgY29tcG9uZW50IGlmIGl0J3MgXCJSZXNvdXJjZVwiIG9yIFwiRGVmYXVsdFwiIChpZiB3ZSBoYXZlIG1vcmUgdGhhbiBhIHNpbmdsZSBjb21wb25lbnQpXG4gICAgICBpZiAocGFydHMubGVuZ3RoID4gMSkge1xuICAgICAgICBjb25zdCBsYXN0ID0gcGFydHNbcGFydHMubGVuZ3RoIC0gMV07XG4gICAgICAgIGlmIChsYXN0ID09PSAnUmVzb3VyY2UnIHx8IGxhc3QgPT09ICdEZWZhdWx0Jykge1xuICAgICAgICAgIHBhcnRzID0gcGFydHMuc2xpY2UoMCwgcGFydHMubGVuZ3RoIC0gMSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHAgPSBwYXJ0cy5qb2luKCcvJyk7XG4gICAgfVxuICAgIHJldHVybiBwO1xuICB9XG59O1xuXG4vLyBjb3BpZWQgZnJvbVxuLy8gYXdzLWNkay9saWIvZGlmZiAoZnVuY3Rpb24gbm90IGV4cG9ydGVkKVxuY29uc3QgYnVpbGRMb2dpY2FsVG9QYXRoTWFwID0gKFxuICBzdGFjazogY2RrLmN4X2FwaS5DbG91ZEZvcm1hdGlvblN0YWNrQXJ0aWZhY3Rcbik6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPT4ge1xuICBjb25zdCBtYXA6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcbiAgZm9yIChjb25zdCBtZCBvZiBzdGFjay5maW5kTWV0YWRhdGFCeVR5cGUoXG4gICAgY3hzY2hlbWEuQXJ0aWZhY3RNZXRhZGF0YUVudHJ5VHlwZS5MT0dJQ0FMX0lEXG4gICkpIHtcbiAgICBtYXBbbWQuZGF0YSBhcyBzdHJpbmddID0gbWQucGF0aDtcbiAgfVxuICByZXR1cm4gbWFwO1xufTtcblxuY29uc3QgZHluYW1pY2FsbHlJbnN0YW50aWF0ZURlcGxveW1lbnRzID0gKHNka1Byb3ZpZGVyOiBTZGtQcm92aWRlcikgPT4ge1xuICBsZXQgRGVwbG95bWVudHM7XG4gIGxldCBjZGtUb29sa2l0RGVwbG95bWVudHNQcm9wOiBDZGtUb29sa2l0RGVwbG95bWVudHNQcm9wID0gJ2RlcGxveW1lbnRzJztcblxuICB0cnkge1xuICAgIERlcGxveW1lbnRzID0gcmVxdWlyZSgnYXdzLWNkay9saWIvYXBpL2RlcGxveW1lbnRzJykuRGVwbG95bWVudHM7XG4gIH0gY2F0Y2goZXJyKSB7XG4gICAgRGVwbG95bWVudHMgPSByZXF1aXJlKCdhd3MtY2RrL2xpYi9hcGkvY2xvdWRmb3JtYXRpb24tZGVwbG95bWVudHMnKS5DbG91ZEZvcm1hdGlvbkRlcGxveW1lbnRzO1xuICAgIGNka1Rvb2xraXREZXBsb3ltZW50c1Byb3AgPSAnY2xvdWRGb3JtYXRpb24nO1xuICB9XG5cbiAgY29uc3QgZGVwbG95bWVudHMgPSBuZXcgRGVwbG95bWVudHMoe1xuICAgIHNka1Byb3ZpZGVyLFxuICAgIGlvSGVscGVyOiB7XG4gICAgICBkZWZhdWx0czoge1xuICAgICAgICBkZWJ1ZzogKGlucHV0OiBzdHJpbmcpID0+IHsgY29uc29sZS5kZWJ1ZyhpbnB1dCkgfSxcbiAgICAgIH1cbiAgICB9LFxuICB9KTtcblxuICByZXR1cm4ge1xuICAgIGRlcGxveW1lbnRzLFxuICAgIGNka1Rvb2xraXREZXBsb3ltZW50c1Byb3AsXG4gIH1cbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldERpZmZPYmplY3QoYXBwOiBjZGsuQXBwLCBvcHRpb25zPzogRGlmZk9wdGlvbnMpIHtcbiAgLy8gSWYgd2UgaGF2ZSBuZXcgY29udGV4dCwgd2UgbmVlZCB0byBjcmVhdGUgYSBuZXcgYXBwIHdpdGggdGhlIG1lcmdlZCBjb250ZXh0XG4gIGlmIChvcHRpb25zPy5jb250ZXh0KSB7XG4gICAgLy8gR2V0IGV4aXN0aW5nIGNvbnRleHRcbiAgICBjb25zdCBleGlzdGluZ0NvbnRleHQgPSBhcHAubm9kZS50cnlHZXRDb250ZXh0KCcnKTtcbiAgICBcbiAgICAvLyBDcmVhdGUgbmV3IG1lcmdlZCBjb250ZXh0XG4gICAgY29uc3QgbWVyZ2VkQ29udGV4dCA9IHtcbiAgICAgIC4uLmV4aXN0aW5nQ29udGV4dCxcbiAgICAgIC4uLm9wdGlvbnMuY29udGV4dFxuICAgIH07XG4gICAgXG4gICAgLy8gQ3JlYXRlIGEgbmV3IEFwcCB3aXRoIG1lcmdlZCBjb250ZXh0XG4gICAgY29uc3QgdGVtcEFwcCA9IG5ldyBjZGsuQXBwKHtcbiAgICAgIGNvbnRleHQ6IG1lcmdlZENvbnRleHQsXG4gICAgfSk7XG5cbiAgICAvLyBGb3IgZWFjaCBzdGFjayBpbiB0aGUgb3JpZ2luYWwgYXBwLCBjcmVhdGUgYSBuZXcgc3RhY2sgaW4gdGhlIHRlbXAgYXBwXG4gICAgZm9yIChjb25zdCBjaGlsZCBvZiBhcHAubm9kZS5jaGlsZHJlbikge1xuICAgICAgaWYgKGNoaWxkIGluc3RhbmNlb2YgY2RrLlN0YWNrKSB7XG4gICAgICAgIGNvbnN0IG9yaWdpbmFsU3RhY2sgPSBjaGlsZCBhcyBjZGsuU3RhY2s7XG4gICAgICAgIFxuICAgICAgICAvLyBDcmVhdGUgYSBuZXcgc3RhY2sgb2YgdGhlIHNhbWUgdHlwZVxuICAgICAgICBjb25zdCBzdGFja1Byb3BzID0ge1xuICAgICAgICAgIGVudjoge1xuICAgICAgICAgICAgYWNjb3VudDogb3JpZ2luYWxTdGFjay5hY2NvdW50LFxuICAgICAgICAgICAgcmVnaW9uOiBvcmlnaW5hbFN0YWNrLnJlZ2lvblxuICAgICAgICAgIH0sXG4gICAgICAgICAgLy8gQ29weSBvdGhlciBzdGFjayBwcm9wZXJ0aWVzIHRoYXQgbWlnaHQgYmUgaW1wb3J0YW50XG4gICAgICAgICAgc3RhY2tOYW1lOiBvcmlnaW5hbFN0YWNrLnN0YWNrTmFtZSxcbiAgICAgICAgICBkZXNjcmlwdGlvbjogb3JpZ2luYWxTdGFjay50ZW1wbGF0ZU9wdGlvbnMuZGVzY3JpcHRpb24sXG4gICAgICAgICAgdGVybWluYXRpb25Qcm90ZWN0aW9uOiBvcmlnaW5hbFN0YWNrLnRlcm1pbmF0aW9uUHJvdGVjdGlvbixcbiAgICAgICAgICB0YWdzOiBvcmlnaW5hbFN0YWNrLnRhZ3MudGFnVmFsdWVzKCksXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gVXNlIHJlZmxlY3Rpb24gdG8gY3JlYXRlIGEgbmV3IGluc3RhbmNlIG9mIHRoZSBzYW1lIHN0YWNrIGNsYXNzXG4gICAgICAgIGNvbnN0IHN0YWNrQ2xhc3MgPSBPYmplY3QuZ2V0UHJvdG90eXBlT2Yob3JpZ2luYWxTdGFjaykuY29uc3RydWN0b3I7XG4gICAgICAgIG5ldyBzdGFja0NsYXNzKHRlbXBBcHAsIG9yaWdpbmFsU3RhY2subm9kZS5pZCwgc3RhY2tQcm9wcyk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gVXNlIHRoZSB0ZW1wb3JhcnkgYXBwIGZvciBzeW50aGVzaXNcbiAgICBjb25zdCBhc3NlbWJseSA9IHRlbXBBcHAuc3ludGgoKTtcbiAgICByZXR1cm4gYXdhaXQgZ2VuZXJhdGVEaWZmcyhhc3NlbWJseSwgb3B0aW9ucyk7XG4gIH1cblxuICAvLyBJZiBubyBuZXcgY29udGV4dCwgdXNlIHRoZSBvcmlnaW5hbCBhcHBcbiAgY29uc3QgYXNzZW1ibHkgPSBhcHAuc3ludGgoKTtcbiAgcmV0dXJuIGF3YWl0IGdlbmVyYXRlRGlmZnMoYXNzZW1ibHksIG9wdGlvbnMpO1xufVxuXG4vLyBIZWxwZXIgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgZGlmZnMgZnJvbSBhbiBhc3NlbWJseVxuYXN5bmMgZnVuY3Rpb24gZ2VuZXJhdGVEaWZmcyhhc3NlbWJseTogY2RrLmN4X2FwaS5DbG91ZEFzc2VtYmx5LCBvcHRpb25zPzogRGlmZk9wdGlvbnMpOiBQcm9taXNlPFN0YWNrUmF3RGlmZltdPiB7XG4gIGNvbnN0IHNka1Byb3ZpZGVyID0gYXdhaXQgU2RrUHJvdmlkZXIud2l0aEF3c0NsaUNvbXBhdGlibGVEZWZhdWx0cyh7XG4gICAgaW9IZWxwZXI6IHtcbiAgICAgIGRlZmF1bHRzOiB7XG4gICAgICAgIGRlYnVnOiAoaW5wdXQ6IHN0cmluZykgPT4geyBjb25zb2xlLmRlYnVnKGlucHV0KSB9LFxuICAgICAgfVxuICAgIH0sXG4gIH0gYXMgYW55LCBvcHRpb25zPy5wcm9maWxlKTtcblxuICBjb2xvcnMuZGlzYWJsZSgpO1xuXG4gIGNvbnN0IHsgZGVwbG95bWVudHMgfSA9IGR5bmFtaWNhbGx5SW5zdGFudGlhdGVEZXBsb3ltZW50cyhzZGtQcm92aWRlcik7XG4gIGNvbnN0IGRpZmZzOiBTdGFja1Jhd0RpZmZbXSA9IFtdO1xuICBcbiAgZm9yIChjb25zdCBzdGFjayBvZiBhc3NlbWJseS5zdGFja3MpIHtcbiAgICBjb25zdCBjdXJyZW50VGVtcGxhdGUgPSBhd2FpdCBkZXBsb3ltZW50cy5yZWFkQ3VycmVudFRlbXBsYXRlKHN0YWNrKTtcbiAgICBcbiAgICBkaWZmcy5wdXNoKHtcbiAgICAgIHN0YWNrTmFtZTogc3RhY2suZGlzcGxheU5hbWUsXG4gICAgICByYXdEaWZmOiBmaWx0ZXJDREtNZXRhZGF0YShcbiAgICAgICAgY2ZuRGlmZi5kaWZmVGVtcGxhdGUoY3VycmVudFRlbXBsYXRlLCBzdGFjay50ZW1wbGF0ZSlcbiAgICAgICksXG4gICAgICBsb2dpY2FsVG9QYXRoTWFwOiBidWlsZExvZ2ljYWxUb1BhdGhNYXAoc3RhY2spXG4gICAgfSk7XG4gIH1cblxuICByZXR1cm4gZGlmZnM7XG59XG4iXX0= -------------------------------------------------------------------------------- /dist/transform.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.transformDiff = void 0; 4 | const cfnDiff = require("@aws-cdk/cloudformation-diff"); 5 | const through2 = require("through2"); 6 | const util_1 = require("./util"); 7 | const types_1 = require("./types"); 8 | const cdk_reverse_engineered_1 = require("./cdk-reverse-engineered"); 9 | // unable to emulate the --no-colors option, (tried passing no-colors option to cdk Configuration class to no avail) 10 | // this is workaround to remove the colors tty elements 11 | const fixRemoveColors = (input) => JSON.parse(JSON.stringify(input).replace(/\\u001b\[[^m]+m/g, '')); 12 | const buildRaw = async (diff) => { 13 | const strm = through2(); 14 | cfnDiff.formatDifferences(strm, diff.rawDiff, diff.logicalToPathMap); 15 | strm.end(); 16 | return fixRemoveColors(await (0, util_1.streamToString)(strm)); 17 | }; 18 | const buildChangeAction = (oldValue, newValue) => { 19 | if (oldValue !== undefined && newValue !== undefined) { 20 | return "UPDATE"; 21 | } 22 | else if (oldValue !== undefined) { 23 | return "REMOVAL"; 24 | } 25 | else { 26 | return "ADDITION"; 27 | } 28 | }; 29 | const transformIamChanges = async (diff) => { 30 | if (!diff.rawDiff.iamChanges.hasChanges) { 31 | return []; 32 | } 33 | const result = []; 34 | if (diff.rawDiff.iamChanges.statements.hasChanges) { 35 | const statementsSummarized = diff.rawDiff.iamChanges.summarizeStatements(); 36 | result.push({ 37 | label: "IAM Statement Changes", 38 | cdkDiffRaw: fixRemoveColors(cfnDiff.formatTable((0, cdk_reverse_engineered_1.deepSubstituteBracedLogicalIds)(diff.logicalToPathMap)(statementsSummarized), undefined)), 39 | }); 40 | } 41 | if (diff.rawDiff.iamChanges.managedPolicies.hasChanges) { 42 | const managedPoliciesSummarized = diff.rawDiff.iamChanges.summarizeManagedPolicies(); 43 | result.push({ 44 | label: "IAM Policy Changes", 45 | cdkDiffRaw: fixRemoveColors(cfnDiff.formatTable((0, cdk_reverse_engineered_1.deepSubstituteBracedLogicalIds)(diff.logicalToPathMap)(managedPoliciesSummarized), undefined)), 46 | }); 47 | } 48 | return result; 49 | }; 50 | const transformSecurityGroupChanges = async (diff) => { 51 | if (!diff.rawDiff.securityGroupChanges.hasChanges) { 52 | return []; 53 | } 54 | const summarized = diff.rawDiff.securityGroupChanges.summarize(); 55 | return [ 56 | { 57 | label: "Security Group Changes", 58 | cdkDiffRaw: fixRemoveColors(cfnDiff.formatTable((0, cdk_reverse_engineered_1.deepSubstituteBracedLogicalIds)(diff.logicalToPathMap)(summarized), undefined)), 59 | }, 60 | ]; 61 | }; 62 | const processIndividualDiff = (result, cdkDiffCategory) => (id, rdiff) => { 63 | var _a, _b, _c, _d; 64 | if (rdiff.isDifferent) { 65 | const resourceType = (0, types_1.guardResourceDiff)(rdiff) 66 | ? (rdiff.isRemoval ? (_a = rdiff.oldValue) === null || _a === void 0 ? void 0 : _a.Type : (_b = rdiff.newValue) === null || _b === void 0 ? void 0 : _b.Type) || 67 | cdkDiffCategory 68 | : (((_c = rdiff.oldValue) === null || _c === void 0 ? void 0 : _c.Type) || ((_d = rdiff.newValue) === null || _d === void 0 ? void 0 : _d.Type) || cdkDiffCategory); 69 | const changes = []; 70 | if ((0, types_1.guardResourceDiff)(rdiff) && rdiff.isUpdate) { 71 | rdiff.forEachDifference((_, label, values) => { 72 | changes.push({ 73 | label, 74 | action: buildChangeAction(values.oldValue, values.newValue), 75 | from: values.oldValue, 76 | to: values.newValue, 77 | }); 78 | }); 79 | } 80 | result.push({ 81 | label: cdkDiffCategory, 82 | cdkDiffRaw: JSON.stringify({ id, diff: rdiff }, null, 2), 83 | nicerDiff: { 84 | resourceType, 85 | changes, 86 | cdkDiffCategory, 87 | resourceAction: rdiff.isAddition 88 | ? "ADDITION" 89 | : rdiff.isRemoval 90 | ? "REMOVAL" 91 | : "UPDATE", 92 | resourceLabel: id, 93 | }, 94 | }); 95 | } 96 | }; 97 | const transformDiffForResourceTypes = async (diff) => { 98 | const result = []; 99 | for (const d of Object.entries(diff.rawDiff).filter(([k]) => !["iamChanges", "securityGroupChanges"].includes(k))) { 100 | const validatedDiff = (0, types_1.diffValidator)(d); 101 | if ('diffCollection' in validatedDiff) { 102 | const { diffCollectionKey, diffCollection } = validatedDiff; 103 | if (diffCollection.differenceCount > 0) { 104 | diffCollection.forEachDifference(processIndividualDiff(result, diffCollectionKey)); 105 | } 106 | } 107 | else if ('diffKey' in validatedDiff) { 108 | const { diffKey, diff } = validatedDiff; 109 | if (diff.isDifferent) { 110 | result.push({ 111 | label: diffKey, 112 | cdkDiffRaw: JSON.stringify({ id: diffKey, diff }, null, 2), 113 | }); 114 | } 115 | } 116 | } 117 | return result; 118 | }; 119 | const transformDescriptionChanges = (diff) => { 120 | var _a, _b, _c, _d, _e, _f, _g; 121 | if ((_a = diff.rawDiff.description) === null || _a === void 0 ? void 0 : _a.isDifferent) { 122 | return { 123 | label: 'Description', 124 | cdkDiffRaw: JSON.stringify({ description: diff.rawDiff.description }, null, 2), 125 | nicerDiff: { 126 | resourceType: 'Description', 127 | changes: [{ 128 | label: 'Description', 129 | action: buildChangeAction((_b = diff.rawDiff.description) === null || _b === void 0 ? void 0 : _b.oldValue, (_c = diff.rawDiff.description) === null || _c === void 0 ? void 0 : _c.newValue), 130 | from: (_d = diff.rawDiff.description) === null || _d === void 0 ? void 0 : _d.oldValue, 131 | to: (_e = diff.rawDiff.description) === null || _e === void 0 ? void 0 : _e.newValue 132 | }], 133 | cdkDiffCategory: 'description', 134 | resourceAction: ((_f = diff.rawDiff.description) === null || _f === void 0 ? void 0 : _f.isAddition) 135 | ? "ADDITION" 136 | : ((_g = diff.rawDiff.description) === null || _g === void 0 ? void 0 : _g.isRemoval) 137 | ? "REMOVAL" 138 | : "UPDATE", 139 | resourceLabel: 'Description', 140 | }, 141 | }; 142 | } 143 | return null; 144 | }; 145 | const transformDiff = async (diff) => { 146 | if (diff.rawDiff.isEmpty) { 147 | return { 148 | stackName: diff.stackName, 149 | raw: "There were no differences", 150 | diff: [], 151 | }; 152 | } 153 | const descriptionDiff = transformDescriptionChanges(diff); 154 | return { 155 | stackName: diff.stackName, 156 | raw: await buildRaw(diff), 157 | diff: [ 158 | ...(await transformIamChanges(diff)), 159 | ...(await transformSecurityGroupChanges(diff)), 160 | ...(await transformDiffForResourceTypes(diff)), 161 | ...(descriptionDiff ? [descriptionDiff] : []), 162 | ], 163 | }; 164 | }; 165 | exports.transformDiff = transformDiff; 166 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhbnNmb3JtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3RyYW5zZm9ybS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx3REFBd0Q7QUFDeEQscUNBQXFDO0FBRXJDLGlDQUF3QztBQUN4QyxtQ0FRaUI7QUFDakIscUVBQTBFO0FBRTFFLG9IQUFvSDtBQUNwSCx1REFBdUQ7QUFDdkQsTUFBTSxlQUFlLEdBQUcsQ0FBQyxLQUFhLEVBQVUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUVwSCxNQUFNLFFBQVEsR0FBRyxLQUFLLEVBQUUsSUFBa0IsRUFBbUIsRUFBRTtJQUM3RCxNQUFNLElBQUksR0FBRyxRQUFRLEVBQUUsQ0FBQztJQUN4QixPQUFPLENBQUMsaUJBQWlCLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7SUFDckUsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ1gsT0FBTyxlQUFlLENBQUMsTUFBTSxJQUFBLHFCQUFjLEVBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUNyRCxDQUFDLENBQUM7QUFFRixNQUFNLGlCQUFpQixHQUFHLENBQUMsUUFBYSxFQUFFLFFBQWEsRUFBRSxFQUFFO0lBQ3pELElBQUksUUFBUSxLQUFLLFNBQVMsSUFBSSxRQUFRLEtBQUssU0FBUyxFQUFFO1FBQ3BELE9BQU8sUUFBUSxDQUFDO0tBQ2pCO1NBQU0sSUFBSSxRQUFRLEtBQUssU0FBUyxFQUFFO1FBQ2pDLE9BQU8sU0FBUyxDQUFDO0tBQ2xCO1NBQU07UUFDTCxPQUFPLFVBQVUsQ0FBQztLQUNuQjtBQUNILENBQUMsQ0FBQztBQUVGLE1BQU0sbUJBQW1CLEdBQUcsS0FBSyxFQUMvQixJQUFrQixFQUNJLEVB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LElBQUksQ0FBQyxDQUFDO1lBQzlDLEdBQUcsQ0FBQyxNQUFNLDZCQUE2QixDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzlDLEdBQUcsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUM5QztLQUNGLENBQUM7QUFDSixDQUFDLENBQUM7QUF0QlcsUUFBQSxhQUFhLGlCQXNCeEIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjZm5EaWZmIGZyb20gXCJAYXdzLWNkay9jbG91ZGZvcm1hdGlvbi1kaWZmXCI7XG5pbXBvcnQgKiBhcyB0aHJvdWdoMiBmcm9tIFwidGhyb3VnaDJcIjtcblxuaW1wb3J0IHsgc3RyZWFtVG9TdHJpbmcgfSBmcm9tIFwiLi91dGlsXCI7XG5pbXBvcnQge1xuICBDZGtEaWZmQ2F0ZWdvcnksXG4gIGRpZmZWYWxpZGF0b3IsXG4gIGd1YXJkUmVzb3VyY2VEaWZmLFxuICBOaWNlckRpZmYsXG4gIE5pY2VyRGlmZkNoYW5nZSxcbiAgTmljZXJTdGFja0RpZmYsXG4gIFN0YWNrUmF3RGlmZixcbn0gZnJvbSBcIi4vdHlwZXNcIjtcbmltcG9ydCB7IGRlZXBTdWJzdGl0dXRlQnJhY2VkTG9naWNhbElkcyB9IGZyb20gXCIuL2Nkay1yZXZlcnNlLWVuZ2luZWVyZWRcIjtcblxuLy8gdW5hYmxlIHRvIGVtdWxhdGUgdGhlIC0tbm8tY29sb3JzIG9wdGlvbiwgKHRyaWVkIHBhc3Npbmcgbm8tY29sb3JzIG9wdGlvbiB0byBjZGsgQ29uZmlndXJhdGlvbiBjbGFzcyB0byBubyBhdmFpbClcbi8vIHRoaXMgaXMgd29ya2Fyb3VuZCB0byByZW1vdmUgdGhlIGNvbG9ycyB0dHkgZWxlbWVudHNcbmNvbnN0IGZpeFJlbW92ZUNvbG9ycyA9IChpbnB1dDogc3RyaW5nKTogc3RyaW5nID0+IEpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkoaW5wdXQpLnJlcGxhY2UoL1xcXFx1MDAxYlxcW1tebV0rbS9nLCAnJykpXG5cbmNvbnN0IGJ1aWxkUmF3ID0gYXN5bmMgKGRpZmY6IFN0YWNrUmF3RGlmZik6IFByb21pc2U8c3RyaW5nPiA9PiB7XG4gIGNvbnN0IHN0cm0gPSB0aHJvdWdoMigpO1xuICBjZm5EaWZmLmZvcm1hdERpZmZlcmVuY2VzKHN0cm0sIGRpZmYucmF3RGlmZiwgZGlmZi5sb2dpY2FsVG9QYXRoTWFwKTtcbiAgc3RybS5lbmQoKTtcbiAgcmV0dXJuIGZpeFJlbW92ZUNvbG9ycyhhd2FpdCBzdHJlYW1Ub1N0cmluZyhzdHJtKSk7XG59O1xuXG5jb25zdCBidWlsZENoYW5nZUFjdGlvbiA9IChvbGRWYWx1ZTogYW55LCBuZXdWYWx1ZTogYW55KSA9PiB7XG4gIGlmIChvbGRWYWx1ZSAhPT0gdW5kZWZpbmVkICYmIG5ld1ZhbHVlICE9PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm4gXCJVUERBVEVcIjtcbiAgfSBlbHNlIGlmIChvbGRWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuIFwiUkVNT1ZBTFwiO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBcIkFERElUSU9OXCI7XG4gIH1cbn07XG5cbmNvbnN0IHRyYW5zZm9ybUlhbUNoYW5nZXMgPSBhc3luYyAoXG4gIGRpZmY6IFN0YWNrUmF3RGlmZlxuKTogUHJvbWlzZTxOaWNlckRpZmZbXT4gPT4ge1xuICBpZiAoIWRpZmYucmF3RGlmZi5pYW1DaGFuZ2VzLmhhc0NoYW5nZXMpIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICBjb25zdCByZXN1bHQ6IE5pY2VyRGlmZltdID0gW107XG4gIGlmIChkaWZmLnJhd0RpZmYuaWFtQ2hhbmdlcy5zdGF0ZW1lbnRzLmhhc0NoYW5nZXMpIHtcbiAgICBjb25zdCBzdGF0ZW1lbnRzU3VtbWFyaXplZCA9IGRpZmYucmF3RGlmZi5pYW1DaGFuZ2VzLnN1bW1hcml6ZVN0YXRlbWVudHMoKTtcbiAgICByZXN1bHQucHVzaCh7XG4gICAgICBsYWJlbDogXCJJQU0gU3RhdGVtZW50IENoYW5nZXNcIixcbiAgICAgIGNka0RpZmZSYXc6IGZpeFJlbW92ZUNvbG9ycyhcbiAgICAgICAgY2ZuRGlmZi5mb3JtYXRUYWJsZShcbiAgICAgICAgICBkZWVwU3Vic3RpdHV0ZUJyYWNlZExvZ2ljYWxJZHMoZGlmZi5sb2dpY2FsVG9QYXRoTWFwKShcbiAgICAgICAgICAgIHN0YXRlbWVudHNTdW1tYXJpemVkXG4gICAgICAgICAgKSxcbiAgICAgICAgICB1bmRlZmluZWRcbiAgICAgICAgKVxuICAgICAgKSxcbiAgICB9KTtcbiAgfVxuXG4gIGlmIChkaWZmLnJhd0RpZmYuaWFtQ2hhbmdlcy5tYW5hZ2VkUG9saWNpZXMuaGFzQ2hhbmdlcykge1xuICAgIGNvbnN0IG1hbmFnZWRQb2xpY2llc1N1bW1hcml6ZWQgPVxuICAgICAgZGlmZi5yYXdEaWZmLmlhbUNoYW5nZXMuc3VtbWFyaXplTWFuYWdlZFBvbGljaWVzKCk7XG4gICAgcmVzdWx0LnB1c2goe1xuICAgICAgbGFiZWw6IFwiSUFNIFBvbGljeSBDaGFuZ2VzXCIsXG4gICAgICBjZGtEaWZmUmF3OiBmaXhSZW1vdmVDb2xvcnMoXG4gICAgICAgIGNmbkRpZmYuZm9ybWF0VGFibGUoXG4gICAgICAgICAgZGVlcFN1YnN0aXR1dGVCcmFjZWRMb2dpY2FsSWRzKGRpZmYubG9naWNhbFRvUGF0aE1hcCkoXG4gICAgICAgICAgICBtYW5hZ2VkUG9saWNpZXNTdW1tYXJpemVkXG4gICAgICAgICAgKSxcbiAgICAgICAgICB1bmRlZmluZWRcbiAgICAgICAgKVxuICAgICAgKSxcbiAgICB9KTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59O1xuXG5jb25zdCB0cmFuc2Zvcm1TZWN1cml0eUdyb3VwQ2hhbmdlcyA9IGFzeW5jIChcbiAgZGlmZjogU3RhY2tSYXdEaWZmXG4pOiBQcm9taXNlPE5pY2VyRGlmZltdPiA9PiB7XG4gIGlmICghZGlmZi5yYXdEaWZmLnNlY3VyaXR5R3JvdXBDaGFuZ2VzLmhhc0NoYW5nZXMpIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICBjb25zdCBzdW1tYXJpemVkID0gZGlmZi5yYXdEaWZmLnNlY3VyaXR5R3JvdXBDaGFuZ2VzLnN1bW1hcml6ZSgpO1xuXG4gIHJldHVybiBbXG4gICAge1xuICAgICAgbGFiZWw6IFwiU2VjdXJpdHkgR3JvdXAgQ2hhbmdlc1wiLFxuICAgICAgY2RrRGlmZlJhdzogZml4UmVtb3ZlQ29sb3JzKFxuICAgICAgICBjZm5EaWZmLmZvcm1hdFRhYmxlKFxuICAgICAgICAgIGRlZXBTdWJzdGl0dXRlQnJhY2VkTG9naWNhbElkcyhkaWZmLmxvZ2ljYWxUb1BhdGhNYXApKHN1bW1hcml6ZWQpLFxuICAgICAgICAgIHVuZGVmaW5lZFxuICAgICAgICApXG4gICAgICApLFxuICAgIH0sXG4gIF07XG59O1xuXG5jb25zdCBwcm9jZXNzSW5kaXZpZHVhbERpZmYgPVxuICAocmVzdWx0OiBOaWNlckRpZmZbXSwgY2RrRGlmZkNhdGVnb3J5OiBDZGtEaWZmQ2F0ZWdvcnkpID0+XG4gICAgKGlkOiBzdHJpbmcsIHJkaWZmOiBjZm5EaWZmLkRpZmZlcmVuY2U8YW55PikgPT4ge1xuICAgICAgaWYgKHJkaWZmLmlzRGlmZmVyZW50KSB7XG4gICAgICAgIGNvbnN0IHJlc291cmNlVHlwZTogc3RyaW5nID0gZ3VhcmRSZXNvdXJjZURpZmYocmRpZmYpXG4gICAgICAgICAgPyAocmRpZmYuaXNSZW1vdmFsID8gcmRpZmYub2xkVmFsdWU/LlR5cGUgOiByZGlmZi5uZXdWYWx1ZT8uVHlwZSkgfHxcbiAgICAgICAgICBjZGtEaWZmQ2F0ZWdvcnlcbiAgICAgICAgICA6IChyZGlmZi5vbGRWYWx1ZT8uVHlwZSB8fCByZGlmZi5uZXdWYWx1ZT8uVHlwZSB8fCBjZGtEaWZmQ2F0ZWdvcnkpO1xuICAgICAgICBjb25zdCBjaGFuZ2VzOiBOaWNlckRpZmZDaGFuZ2VbXSA9IFtdO1xuICAgICAgICBpZiAoZ3VhcmRSZXNvdXJjZURpZmYocmRpZmYpICYmIHJkaWZmLmlzVXBkYXRlKSB7XG4gICAgICAgICAgcmRpZmYuZm9yRWFjaERpZmZlcmVuY2UoKF8sIGxhYmVsLCB2YWx1ZXMpID0+IHtcbiAgICAgICAgICAgIGNoYW5nZXMucHVzaCh7XG4gICAgICAgICAgICAgIGxhYmVsLFxuICAgICAgICAgICAgICBhY3Rpb246IGJ1aWxkQ2hhbmdlQWN0aW9uKHZhbHVlcy5vbGRWYWx1ZSwgdmFsdWVzLm5ld1ZhbHVlKSxcbiAgICAgICAgICAgICAgZnJvbTogdmFsdWVzLm9sZFZhbHVlLFxuICAgICAgICAgICAgICB0bzogdmFsdWVzLm5ld1ZhbHVlLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgICAgbGFiZWw6IGNka0RpZmZDYXRlZ29yeSxcbiAgICAgICAgICBjZGtEaWZmUmF3OiBKU09OLnN0cmluZ2lmeSh7IGlkLCBkaWZmOiByZGlmZiB9LCBudWxsLCAyKSxcbiAgICAgICAgICBuaWNlckRpZmY6IHtcbiAgICAgICAgICAgIHJlc291cmNlVHlwZSxcbiAgICAgICAgICAgIGNoYW5nZXMsXG4gICAgICAgICAgICBjZGtEaWZmQ2F0ZWdvcnksXG4gICAgICAgICAgICByZXNvdXJjZUFjdGlvbjogcmRpZmYuaXNBZGRpdGlvblxuICAgICAgICAgICAgICA/IFwiQURESVRJT05cIlxuICAgICAgICAgICAgICA6IHJkaWZmLmlzUmVtb3ZhbFxuICAgICAgICAgICAgICAgID8gXCJSRU1PVkFMXCJcbiAgICAgICAgICAgICAgICA6IFwiVVBEQVRFXCIsXG4gICAgICAgICAgICByZXNvdXJjZUxhYmVsOiBpZCxcbiAgICAgICAgICB9LFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9O1xuXG5jb25zdCB0cmFuc2Zvcm1EaWZmRm9yUmVzb3VyY2VUeXBlcyA9IGFzeW5jIChkaWZmOiBTdGFja1Jhd0RpZmYpOiBQcm9taXNlPE5pY2VyRGlmZltdPiA9PiB7XG4gIGNvbnN0IHJlc3VsdDogTmljZXJEaWZmW10gPSBbXTtcbiAgZm9yIChjb25zdCBkIG9mIE9iamVjdC5lbnRyaWVzKGRpZmYucmF3RGlmZikuZmlsdGVyKChba10pID0+ICFbXCJpYW1DaGFuZ2VzXCIsIFwic2VjdXJpdHlHcm91cENoYW5nZXNcIl0uaW5jbHVkZXMoaykpKSB7XG4gICAgY29uc3QgdmFsaWRhdGVkRGlmZiA9IGRpZmZWYWxpZGF0b3IoZCk7XG4gICAgaWYgKCdkaWZmQ29sbGVjdGlvbicgaW4gdmFsaWRhdGVkRGlmZikge1xuICAgICAgY29uc3QgeyBkaWZmQ29sbGVjdGlvbktleSwgZGlmZkNvbGxlY3Rpb24gfSA9IHZhbGlkYXRlZERpZmY7XG4gICAgICBpZiAoZGlmZkNvbGxlY3Rpb24uZGlmZmVyZW5jZUNvdW50ID4gMCkge1xuICAgICAgICBkaWZmQ29sbGVjdGlvbi5mb3JFYWNoRGlmZmVyZW5jZShcbiAgICAgICAgICBwcm9jZXNzSW5kaXZpZHVhbERpZmYocmVzdWx0LCBkaWZmQ29sbGVjdGlvbktleSlcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKCdkaWZmS2V5JyBpbiB2YWxpZGF0ZWREaWZmKSB7XG4gICAgICBjb25zdCB7IGRpZmZLZXksIGRpZmYgfSA9IHZhbGlkYXRlZERpZmY7XG4gICAgICBpZiAoZGlmZi5pc0RpZmZlcmVudCkge1xuICAgICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgICAgbGFiZWw6IGRpZmZLZXksXG4gICAgICAgICAgY2RrRGlmZlJhdzogSlNPTi5zdHJpbmdpZnkoeyBpZDogZGlmZktleSwgZGlmZiB9LCBudWxsLCAyKSxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbmNvbnN0IHRyYW5zZm9ybURlc2NyaXB0aW9uQ2hhbmdlcyA9IChkaWZmOiBTdGFja1Jhd0RpZmYpOiBOaWNlckRpZmYgfCBudWxsID0+IHtcbiAgaWYgKGRpZmYucmF3RGlmZi5kZXNjcmlwdGlvbj8uaXNEaWZmZXJlbnQpIHtcbiAgICByZXR1cm4ge1xuICAgICAgbGFiZWw6ICdEZXNjcmlwdGlvbicsXG4gICAgICBjZGtEaWZmUmF3OiBKU09OLnN0cmluZ2lmeSh7IGRlc2NyaXB0aW9uOiBkaWZmLnJhd0RpZmYuZGVzY3JpcHRpb24gfSwgbnVsbCwgMiksXG4gICAgICBuaWNlckRpZmY6IHtcbiAgICAgICAgcmVzb3VyY2VUeXBlOiAnRGVzY3JpcHRpb24nLFxuICAgICAgICBjaGFuZ2VzOiBbe1xuICAgICAgICAgIGxhYmVsOiAnRGVzY3JpcHRpb24nLFxuICAgICAgICAgIGFjdGlvbjogYnVpbGRDaGFuZ2VBY3Rpb24oZGlmZi5yYXdEaWZmLmRlc2NyaXB0aW9uPy5vbGRWYWx1ZSwgZGlmZi5yYXdEaWZmLmRlc2NyaXB0aW9uPy5uZXdWYWx1ZSksXG4gICAgICAgICAgZnJvbTogZGlmZi5yYXdEaWZmLmRlc2NyaXB0aW9uPy5vbGRWYWx1ZSxcbiAgICAgICAgICB0bzogZGlmZi5yYXdEaWZmLmRlc2NyaXB0aW9uPy5uZXdWYWx1ZVxuICAgICAgICB9XSxcbiAgICAgICAgY2RrRGlmZkNhdGVnb3J5OiAnZGVzY3JpcHRpb24nLFxuICAgICAgICByZXNvdXJjZUFjdGlvbjogZGlmZi5yYXdEaWZmLmRlc2NyaXB0aW9uPy5pc0FkZGl0aW9uXG4gICAgICAgICAgPyBcIkFERElUSU9OXCJcbiAgICAgICAgICA6IGRpZmYucmF3RGlmZi5kZXNjcmlwdGlvbj8uaXNSZW1vdmFsXG4gICAgICAgICAgICA/IFwiUkVNT1ZBTFwiXG4gICAgICAgICAgICA6IFwiVVBEQVRFXCIsXG4gICAgICAgIHJlc291cmNlTGFiZWw6ICdEZXNjcmlwdGlvbicsXG4gICAgICB9LFxuICAgIH07XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59O1xuXG5leHBvcnQgY29uc3QgdHJhbnNmb3JtRGlmZiA9IGFzeW5jIChcbiAgZGlmZjogU3RhY2tSYXdEaWZmXG4pOiBQcm9taXNlPE5pY2VyU3RhY2tEaWZmPiA9PiB7XG4gIGlmIChkaWZmLnJhd0RpZmYuaXNFbXB0eSkge1xuICAgIHJldHVybiB7XG4gICAgICBzdGFja05hbWU6IGRpZmYuc3RhY2tOYW1lLFxuICAgICAgcmF3OiBcIlRoZXJlIHdlcmUgbm8gZGlmZmVyZW5jZXNcIixcbiAgICAgIGRpZmY6IFtdLFxuICAgIH07XG4gIH1cblxuICBjb25zdCBkZXNjcmlwdGlvbkRpZmYgPSB0cmFuc2Zvcm1EZXNjcmlwdGlvbkNoYW5nZXMoZGlmZik7XG4gIHJldHVybiB7XG4gICAgc3RhY2tOYW1lOiBkaWZmLnN0YWNrTmFtZSxcbiAgICByYXc6IGF3YWl0IGJ1aWxkUmF3KGRpZmYpLFxuICAgIGRpZmY6IFtcbiAgICAgIC4uLihhd2FpdCB0cmFuc2Zvcm1JYW1DaGFuZ2VzKGRpZmYpKSxcbiAgICAgIC4uLihhd2FpdCB0cmFuc2Zvcm1TZWN1cml0eUdyb3VwQ2hhbmdlcyhkaWZmKSksXG4gICAgICAuLi4oYXdhaXQgdHJhbnNmb3JtRGlmZkZvclJlc291cmNlVHlwZXMoZGlmZikpLFxuICAgICAgLi4uKGRlc2NyaXB0aW9uRGlmZiA/IFtkZXNjcmlwdGlvbkRpZmZdIDogW10pLFxuICAgIF0sXG4gIH07XG59O1xuXG4iXX0= -------------------------------------------------------------------------------- /dist/pretty-diff-template.html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.default = ` 4 | 5 | 6 | 7 | 8 | prettyplan 9 | 10 | 11 | 360 | 361 | 362 |
                363 |
                364 |

                prettyplan

                365 | 369 |
                370 |
                  371 |
                    372 | 373 | 374 |
                    375 |
                      376 |
                      
                      377 |       
                      378 |
                      379 | 445 | 446 | 447 | `; 448 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJldHR5LWRpZmYtdGVtcGxhdGUuaHRtbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wcmV0dHktZGlmZi10ZW1wbGF0ZS5odG1sLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsa0JBQWU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQTRiZCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgYFxuPCFET0NUWVBFIGh0bWw+XG48aHRtbD5cbiAgPGhlYWQ+XG4gICAgPG1ldGEgY2hhcnNldD1cInV0Zi04XCIgLz5cbiAgICA8dGl0bGU+cHJldHR5cGxhbjwvdGl0bGU+XG4gICAgPG1ldGEgbmFtZT1cInZpZXdwb3J0XCIgY29udGVudD1cIndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xXCIgLz5cbiAgICA8bGluayByZWw9XCJzdHlsZXNoZWV0XCIgdHlwZT1cInRleHQvY3NzXCIgaHJlZj1cImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vZGlmZjJodG1sL2J1bmRsZXMvY3NzL2RpZmYyaHRtbC5taW4uY3NzXCIgLz5cbiAgICA8c3R5bGU+XG4gICAgICBib2R5IHtcbiAgICAgICAgZm9udC1mYW1pbHk6IEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7XG4gICAgICAgIHRleHQtcmVuZGVyaW5nOiBvcHRpbWl6ZUxlZ2liaWxpdHk7XG4gICAgICAgIGJhY2tncm91bmQ6ICNlY2Y3ZmU7XG4gICAgICAgIGNvbG9yOiAjMDAwMDAwYzA7XG4gICAgICAgIGZvbnQtc2l6ZTogMTVweDtcbiAgICAgICAgbWFyZ2luOiAwO1xuICAgICAgfVxuICAgICAgQGtleWZyYW1lcyBmYWRlLWluIHtcbiAgICAgICAgMCUge1xuICAgICAgICAgIG9wYWNpdHk6IDA7XG4gICAgICAgIH1cbiAgICAgICAgMTAwJSB7XG4gICAgICAgICAgb3BhY2l0eTogMTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAuc3RyaXBlIHtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIGhlaWdodDogNXB4O1xuICAgICAgICBiYWNrZ3JvdW5kOiAjNWM0Y2U0O1xuICAgICAgICBhbmltYXRpb24tbmFtZTogd2lwZS1pbjtcbiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiAxcztcbiAgICAgIH1cbiAgICAgIEBrZXlmcmFtZXMgd2lwZS1pbiB7XG4gICAgICAgIDAlIHtcbiAgICAgICAgICB3aWR0aDogMCU7XG4gICAgICAgIH1cbiAgICAgICAgMTAwJSB7XG4gICAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgI3JlbGVhc2Utbm90aWZpY2F0aW9uIHtcbiAgICAgICAgYmFja2dyb3VuZDogIzVjNGNlNDtcbiAgICAgICAgY29sb3I6IHdoaXRlO1xuICAgICAgICBmb250LXdlaWdodDogYm9sZDtcbiAgICAgICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgICAgICBvdmVyZmxvdzogaGlkZGVuO1xuICAgICAgICBwYWRkaW5nOiAxMHB4IDAgMTVweCAwO1xuICAgICAgICBoZWlnaHQ6IDIwcHg7XG4gICAgICAgIGFuaW1hdGlvbi1uYW1lOiBub3RpZmljYXRpb24tcG9wLWluO1xuICAgICAgICBhbmltYXRpb24tZHVyYXRpb246IDJzO1xuICAgICAgfVxuICAgICAgI3JlbGVhc2Utbm90aWZpY2F0aW9uIGEge1xuICAgICAgICBjb2xvcjogd2hpdGU7XG4gICAgICB9XG4gICAgICAjcmVsZWFzZS1ub3RpZmljYXRpb24uZGlzbWlzc2VkIHtcbiAgICAgICAgYW5pbWF0aW9uLW5hbWU6IG5vdGlmaWNhdGlvbi1wb3Atb3V0O1xuICAgICAgICBhbmltYXRpb24tZHVyYXRpb246IDAuNXM7XG4gICAgICAgIGhlaWdodDogMDtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgIH1cbiAgICAgIEBrZXlmcmFtZXMgbm90aWZpY2F0aW9uLXBvcC1pbiB7XG4gICAgICAgIDAlIHtcbiAgICAgICAgICBoZWlnaHQ6IDA7XG4gICAgICAgICAgcGFkZGluZzogMDtcbiAgICAgICAgfVxuICAgICAgICA1MCUge1xuICAgICAgICAgIGhlaWdodDogMDtcbiAgICAgICAgICBwYWRkaW5nOiAwO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBAa2V5ZnJhbWVzIG5vdGlmaWNhdGlvbi1wb3Atb3V0IHtcbiAgICAgICAgMCUge1xuICAgICAgICAgIGhlaWdodDogMjBweDtcbiAgICAgICAgICBwYWRkaW5nOiAxMHB4IDAgMTVweCAwO1xuICAgICAgICB9XG4gICAgICAgIDEwMCUge1xuICAgICAgICAgIGhlaWdodDogMDtcbiAgICAgICAgICBwYWRkaW5nOiAwO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgICNtb2RhbC1jb250YWluZXIge1xuICAgICAgICBhbmltYXRpb24tbmFtZTogZmFkZS1pbjtcbiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiAwLjJzO1xuICAgICAgfVxuICAgICAgLm1vZGFsLXBhbmUge1xuICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgIHRvcDogMDtcbiAgICAgICAgbGVmdDogMDtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZmZmZmU2O1xuICAgICAgICB6LWluZGV4OiAxMDtcbiAgICAgIH1cbiAgICAgIC5tb2RhbC1jb250ZW50IHtcbiAgICAgICAgcG9zaXRpb246IGZpeGVkO1xuICAgICAgICB3aWR0aDogNjAlO1xuICAgICAgICBoZWlnaHQ6IDYwJTtcbiAgICAgICAgdG9wOiA1MCU7XG4gICAgICAgIGxlZnQ6IDUwJTtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSk7XG4gICAgICAgIGJhY2tncm91bmQ6ICNmZmZmZmY7XG4gICAgICAgIGJveC1zaGFkb3c6IDAgMnB4IDZweCAwIGhzbGEoMCwgMCUsIDAlLCAwLjIpO1xuICAgICAgICB6LWluZGV4OiAyMDtcbiAgICAgIH1cbiAgICAgIC5tb2RhbC1jbG9zZSB7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgcmlnaHQ6IDA7XG4gICAgICAgIHBhZGRpbmc6IDEwcHg7XG4gICAgICB9XG4gICAgICAubW9kYWwtY2xvc2UgYnV0dG9uLnRleHQtYnV0dG9uIHtcbiAgICAgICAgY29sb3I6ICM0NTI2YWM7XG4gICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbiAgICAgIH1cbiAgICAgIC5yZWxlYXNlLW5vdGVzIHtcbiAgICAgICAgbWF4LXdpZHRoOiA4MCU7XG4gICAgICAgIG1hcmdpbjogMCBhdXRvIDAgYXV0bztcbiAgICAgICAgb3ZlcmZsb3cteTogYXV0bztcbiAgICAgICAgbWF4LWhlaWdodDogMTAwJTtcbiAgICAgIH1cblxuICAgICAgI2JyYW5kaW5nIHtcbiAgICAgICAgZmxvYXQ6IHJpZ2h0O1xuICAgICAgICBwYWRkaW5nLXRvcDogMTBweDtcbiAgICAgICAgcGFkZGluZy1yaWdodDogMTBweDtcbiAgICAgICAgZm9udC1zaXplOiAxMHB4O1xuICAgICAgICBjb2xvcjogIzQ1MjZhYztcbiAgICAgICAgdGV4dC1hbGlnbjogcmlnaHQ7XG4gICAgICB9XG4gICAgICAjYnJhbmRpbmcgYSB7XG4gICAgICAgIGNvbG9yOiAjNDUyNmFjO1xuICAgICAgfVxuXG4gICAgICAuY29udGFpbmVyIHtcbiAgICAgICAgbWFyZ2luOiAxMHB4IDEwcHggMCAxMHB4O1xuICAgICAgICBhbmltYXRpb24tbmFtZTogZmFkZS1pbjtcbiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiAxcztcbiAgICAgIH1cbiAgICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNjAwcHgpIHtcbiAgICAgICAgLmNvbnRhaW5lciB7XG4gICAgICAgICAgbWF4LXdpZHRoOiA4MCU7XG4gICAgICAgICAgbWFyZ2luLWxlZnQ6IGF1dG87XG4gICAgICAgICAgbWFyZ2luLXJpZ2h0OiBhdXRvO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGgxLFxuICAgICAgaDIge1xuICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgIGNvbG9yOiAjNDUyNmFjO1xuICAgICAgfVxuXG4gICAgICAjdGVycmFmb3JtLXBsYW4ge1xuICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgICAgbWluLWhlaWdodDogMzAwcHg7XG4gICAgICAgIGJvcmRlcjogbm9uZTtcbiAgICAgICAgYm94LXNoYWRvdzogMCAycHggNnB4IDAgaHNsYSgwLCAwJSwgMCUsIDAuMik7XG4gICAgICAgIHBhZGRpbmc6IDEwcHg7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gICAgICAgIHJlc2l6ZTogbm9uZTtcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZmZmZmU2O1xuICAgICAgfVxuXG4gICAgICBidXR0b24ge1xuICAgICAgICBmb250LXNpemU6IDE4cHg7XG4gICAgICAgIGJhY2tncm91bmQ6ICM1YzRjZTQ7XG4gICAgICAgIGNvbG9yOiAjZmZmO1xuICAgICAgICBib3gtc2hhZG93OiAwIDJweCA2cHggMCBoc2xhKDAsIDAlLCAwJSwgMC4yKTtcbiAgICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgICBib3JkZXItcmFkaXVzOiAycHg7XG4gICAgICAgIG1pbi13aWR0aDogMTcwcHg7XG4gICAgICAgIGhlaWdodDogNDBweDtcbiAgICAgIH1cbiAgICAgIGJ1dHRvbjpob3ZlciB7XG4gICAgICAgIGJhY2tncm91bmQ6ICM2NTY3ZWE7XG4gICAgICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICAgIH1cbiAgICAgIGJ1dHRvbjphY3RpdmUge1xuICAgICAgICBiYWNrZ3JvdW5kOiAjNTAzN2NhO1xuICAgICAgfVxuICAgICAgYnV0dG9uLnRleHQtYnV0dG9uIHtcbiAgICAgICAgYmFja2dyb3VuZDogbm9uZTtcbiAgICAgICAgYm94LXNoYWRvdzogbm9uZTtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogMDtcbiAgICAgICAgd2lkdGg6IGF1dG87XG4gICAgICAgIGhlaWdodDogYXV0bztcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG4gICAgICAgIGZvbnQtc2l6ZTogaW5oZXJpdDtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGluaGVyaXQ7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmO1xuICAgICAgICBjb2xvcjogaW5oZXJpdDtcbiAgICAgICAgdGV4dC1hbGlnbjogaW5oZXJpdDtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgIH1cblxuICAgICAgI3BhcnNpbmctZXJyb3ItbWVzc2FnZSB7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmZmZmZmY7XG4gICAgICAgIHBhZGRpbmc6IDEwcHg7XG4gICAgICAgIGNvbG9yOiAjMDAwMDAwYzA7XG4gICAgICAgIG1hcmdpbjogNHB4O1xuICAgICAgICBib3gtc2hhZG93OiAwIDJweCA2cHggMCBoc2xhKDAsIDAlLCAwJSwgMC4yKTtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gICAgICAgIGJvcmRlci1sZWZ0OiAycHggc29saWQgcmVkO1xuICAgICAgICBhbmltYXRpb24tbmFtZTogZXJyb3I7XG4gICAgICAgIGFuaW1hdGlvbi1kdXJhdGlvbjogMXM7XG4gICAgICB9XG5cbiAgICAgIEBrZXlmcmFtZXMgZXJyb3Ige1xuICAgICAgICAwJSB7XG4gICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogcmVkO1xuICAgICAgICB9XG4gICAgICAgIDEwMCUge1xuICAgICAgICAgIGJhY2tncm91bmQtY29sb3I6IHdoaXRlO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC5wcmV0dHlwbGFuIHVsIHtcbiAgICAgICAgcGFkZGluZy1sZWZ0OiAwO1xuICAgICAgICBmb250LXNpemU6IDEzcHg7XG4gICAgICB9XG5cbiAgICAgIC5wcmV0dHlwbGFuIGxpIHtcbiAgICAgICAgbGlzdC1zdHlsZTogbm9uZTtcbiAgICAgICAgYmFja2dyb3VuZDogI2ZmZmZmZmU2O1xuICAgICAgICBwYWRkaW5nOiAxMHB4O1xuICAgICAgICBjb2xvcjogIzAwMDAwMGMwO1xuICAgICAgICBtYXJnaW46IDRweDtcbiAgICAgICAgYm94LXNoYWRvdzogMCAycHggNnB4IDAgaHNsYSgwLCAwJSwgMCUsIDAuMik7XG4gICAgICB9XG5cbiAgICAgIC5wcmV0dHlwbGFuIHVsLndhcm5pbmdzIGxpIHtcbiAgICAgICAgYm9yZGVyLWxlZnQ6IDNweCBzb2xpZCAjNzU3NTc1O1xuICAgICAgfVxuXG4gICAgICAucHJldHR5cGxhbiB1bC5hY3Rpb25zIGxpLnVwZGF0ZSB7XG4gICAgICAgIGJvcmRlci1sZWZ0OiAzcHggc29saWQgI2ZmOGYwMDtcbiAgICAgIH1cbiAgICAgIC5wcmV0dHlwbGFuIHVsLmFjdGlvbnMgbGkuY3JlYXRlIHtcbiAgICAgICAgYm9yZGVyLWxlZnQ6IDNweCBzb2xpZCAjMmU3ZDMyO1xuICAgICAgfVxuICAgICAgLnByZXR0eXBsYW4gdWwuYWN0aW9ucyBsaS5hZGRpdGlvbiB7XG4gICAgICAgIGJvcmRlci1sZWZ0OiAzcHggc29saWQgIzJlN2QzMjtcbiAgICAgIH1cbiAgICAgIC5wcmV0dHlwbGFuIHVsLmFjdGlvbnMgbGkuZGVzdHJveSB7XG4gICAgICAgIGJvcmRlci1sZWZ0OiAzcHggc29saWQgI2I3MWMxYztcbiAgICAgIH1cbiAgICAgIC5wcmV0dHlwbGFuIHVsLmFjdGlvbnMgbGkucmVtb3ZhbCB7XG4gICAgICAgIGJvcmRlci1sZWZ0OiAzcHggc29saWQgI2I3MWMxYztcbiAgICAgIH1cbiAgICAgIC5wcmV0dHlwbGFuIHVsLmFjdGlvbnMgbGkucmVjcmVhdGUge1xuICAgICAgICBib3JkZXItbGVmdDogM3B4IHNvbGlkICMxNTY1YzA7XG4gICAgICB9XG4gICAgICAucHJldHR5cGxhbiB1bC5hY3Rpb25zIGxpLnJlYWQge1xuICAgICAgICBib3JkZXItbGVmdDogM3B4IHNvbGlkICM1MTliZjA7XG4gICAgICB9XG5cbiAgICAgIC5iYWRnZSB7XG4gICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICAgICAgdGV4dC10cmFuc2Zvcm06IHVwcGVyY2FzZTtcbiAgICAgICAgbWFyZ2luLXJpZ2h0OiAxMHB4O1xuICAgICAgICBwYWRkaW5nOiAzcHg7XG4gICAgICAgIGZvbnQtc2l6ZTogMTJweDtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gICAgICB9XG4gICAgICAud2FybmluZ3MgLmJhZGdlIHtcbiAgICAgICAgY29sb3I6ICM3NTc1NzU7XG4gICAgICB9XG4gICAgICBsaS51cGRhdGUgLmJhZGdlIHtcbiAgICAgICAgY29sb3I6ICNmZjhmMDA7XG4gICAgICB9XG4gICAgICBsaS5jcmVhdGUgLmJhZGdlIHtcbiAgICAgICAgY29sb3I6ICMyZTdkMzI7XG4gICAgICB9XG4gICAgICBsaS5hZGRpdGlvbiAuYmFkZ2Uge1xuICAgICAgICBjb2xvcjogIzJlN2QzMjtcbiAgICAgIH1cbiAgICAgIGxpLmRlc3Ryb3kgLmJhZGdlIHtcbiAgICAgICAgY29sb3I6ICNiNzFjMWM7XG4gICAgICB9XG4gICAgICBsaS5yZW1vdmFsIC5iYWRnZSB7XG4gICAgICAgIGNvbG9yOiAjYjcxYzFjO1xuICAgICAgfVxuICAgICAgbGkucmVjcmVhdGUgLmJhZGdlIHtcbiAgICAgICAgY29sb3I6ICMxNTY1YzA7XG4gICAgICB9XG4gICAgICBsaS5yZWFkIC5iYWRnZSB7XG4gICAgICAgIGNvbG9yOiAjNTE5YmYwO1xuICAgICAgfVxuXG4gICAgICAuaWQtc2VnbWVudDpub3QoOmxhc3QtY2hpbGQpOjphZnRlciB7XG4gICAgICAgIGNvbnRlbnQ6ICcgPiAnO1xuICAgICAgfVxuICAgICAgLmlkLXNlZ21lbnQubmFtZSxcbiAgICAgIC5pZC1zZWdtZW50LnR5cGUge1xuICAgICAgICBmb250LXdlaWdodDogYm9sZDtcbiAgICAgIH1cblxuICAgICAgLmNoYW5nZS1jb3VudCB7XG4gICAgICAgIGZsb2F0OiByaWdodDtcbiAgICAgIH1cblxuICAgICAgLnN1bW1hcnkge1xuICAgICAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgICB9XG5cbiAgICAgIC5uby1kaWZmLWNoYW5nZXMtYnJlYWtkb3duIHtcbiAgICAgICAgbWFyZ2luOiA1cHggYXV0byAwIGF1dG87XG4gICAgICAgIHBhZGRpbmc6IDVweDtcbiAgICAgIH1cbiAgICAgIC5uby1kaWZmLWNoYW5nZXMtYnJlYWtkb3duIHRhYmxlIHtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIHdvcmQtYnJlYWs6IGJyZWFrLWFsbDtcbiAgICAgICAgZm9udC1zaXplOiAxM3B4O1xuICAgICAgfVxuICAgICAgLm5vLWRpZmYtY2hhbmdlcy1icmVha2Rvd24gdGFibGUgdGQge1xuICAgICAgICBwYWRkaW5nOiAxMHB4O1xuICAgICAgICB3aWR0aDogNDAlO1xuICAgICAgfVxuICAgICAgcHJlIHtcbiAgICAgICAgd2hpdGUtc3BhY2U6IHByZS13cmFwO1xuICAgICAgICBiYWNrZ3JvdW5kOiAjZjNmM2YzO1xuICAgICAgfVxuICAgICAgLm5vLWRpZmYtY2hhbmdlcy1icmVha2Rvd24gdGFibGUgdGQucHJvcGVydHkge1xuICAgICAgICB3aWR0aDogMjAlO1xuICAgICAgICB0ZXh0LWFsaWduOiByaWdodDtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gICAgICB9XG4gICAgICAubm8tZGlmZi1jaGFuZ2VzLWJyZWFrZG93biB0YWJsZSB0cjpudGgtY2hpbGQoZXZlbikge1xuICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjVmNWY1O1xuICAgICAgfVxuXG4gICAgICAuZm9yY2VzLW5ldy1yZXNvdXJjZSB7XG4gICAgICAgIGNvbG9yOiAjYjcxYzFjO1xuICAgICAgfVxuXG4gICAgICAuY29sbGFwc2VkLFxuICAgICAgLmhpZGRlbiB7XG4gICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICB9XG5cbiAgICAgIC5hY3Rpb25zIGJ1dHRvbiB7XG4gICAgICAgIGJhY2tncm91bmQ6IG5vbmU7XG4gICAgICAgIGJvcmRlcjogbm9uZTtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG4gICAgICAgIGNvbG9yOiBibGFjaztcbiAgICAgICAgYm94LXNoYWRvdzogbm9uZTtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gICAgICAgIGZvbnQtc2l6ZTogMTRweDtcbiAgICAgIH1cblxuICAgICAgLmQyaC1pY29uIHtcbiAgICAgICAgZGlzcGxheTogbm9uZTtcbiAgICAgIH1cbiAgICA8L3N0eWxlPlxuICA8L2hlYWQ+XG4gIDxib2R5PlxuICAgIDxkaXYgY2xhc3M9XCJzdHJpcGVcIj48L2Rpdj5cbiAgICA8ZGl2IGNsYXNzPVwiY29udGFpbmVyXCI+XG4gICAgICA8aDE+cHJldHR5cGxhbjwvaDE+XG4gICAgICA8ZGl2IGlkPVwicGFyc2luZy1lcnJvci1tZXNzYWdlXCIgY2xhc3M9XCJoaWRkZW5cIj5cbiAgICAgICAgVGhhdCBkb2Vzbid0IGxvb2sgbGlrZSBhIFRlcnJhZm9ybSBwbGFuLiBEaWQgeW91IGNvcHkgdGhlIGVudGlyZSBvdXRwdXQgKHdpdGhvdXQgY29sb3VyaW5nKSBmcm9tIHRoZSBwbGFuXG4gICAgICAgIGNvbW1hbmQ/XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgaWQ9XCJwcmV0dHlwbGFuXCIgY2xhc3M9XCJwcmV0dHlwbGFuXCI+XG4gICAgICAgIDx1bCBpZD1cImVycm9yc1wiIGNsYXNzPVwiZXJyb3JzXCI+PC91bD5cbiAgICAgICAgPHVsIGlkPVwid2FybmluZ3NcIiBjbGFzcz1cIndhcm5pbmdzXCI+PC91bD5cbiAgICAgICAgPGJ1dHRvbiBjbGFzcz1cImV4cGFuZC1hbGxcIiBvbmNsaWNrPVwiZXhwYW5kQWxsKClcIj5FeHBhbmQgYWxsPC9idXR0b24+XG4gICAgICAgIDxidXR0b24gY2xhc3M9XCJjb2xsYXBzZS1hbGwgaGlkZGVuXCIgb25jbGljaz1cImNvbGxhcHNlQWxsKClcIj5Db2xsYXBzZSBhbGw8L2J1dHRvbj5cbiAgICAgICAgPGRpdiBpZD1cInN0YWNrc1wiPjwvZGl2PlxuICAgICAgICA8dWwgaWQ9XCJhY3Rpb25zXCIgY2xhc3M9XCJhY3Rpb25zXCI+PC91bD5cbiAgICAgICAgPHByZSBpZD1cImRpZmZcIj48L3ByZT5cbiAgICAgIDwvZGl2PlxuICAgIDwvZGl2PlxuICAgIDxzY3JpcHQ+XG4gICAgICBmdW5jdGlvbiBhY2NvcmRpb24oZWxlbWVudCkge1xuICAgICAgICBjb25zdCBjaGFuZ2VzID0gZWxlbWVudC5wYXJlbnRFbGVtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NoYW5nZXMnKTtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjaGFuZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgdG9nZ2xlQ2xhc3MoY2hhbmdlc1tpXSwgJ2NvbGxhcHNlZCcpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGZ1bmN0aW9uIHRvZ2dsZUNsYXNzKGVsZW1lbnQsIGNsYXNzTmFtZSkge1xuICAgICAgICBpZiAoIWVsZW1lbnQuY2xhc3NOYW1lLm1hdGNoKGNsYXNzTmFtZSkpIHtcbiAgICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnICcgKyBjbGFzc05hbWU7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZWxlbWVudC5jbGFzc05hbWUgPSBlbGVtZW50LmNsYXNzTmFtZS5yZXBsYWNlKGNsYXNzTmFtZSwgJycpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGZ1bmN0aW9uIGFkZENsYXNzKGVsZW1lbnQsIGNsYXNzTmFtZSkge1xuICAgICAgICBpZiAoIWVsZW1lbnQuY2xhc3NOYW1lLm1hdGNoKGNsYXNzTmFtZSkpIGVsZW1lbnQuY2xhc3NOYW1lICs9ICcgJyArIGNsYXNzTmFtZTtcbiAgICAgIH1cblxuICAgICAgZnVuY3Rpb24gcmVtb3ZlQ2xhc3MoZWxlbWVudCwgY2xhc3NOYW1lKSB7XG4gICAgICAgIGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZShjbGFzc05hbWUsICcnKTtcbiAgICAgIH1cblxuICAgICAgZnVuY3Rpb24gZXhwYW5kQWxsKCkge1xuICAgICAgICBjb25zdCBzZWN0aW9ucyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJy5jaGFuZ2VzLmNvbGxhcHNlZCcpO1xuXG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICB0b2dnbGVDbGFzcyhzZWN0aW9uc1tpXSwgJ2NvbGxhcHNlZCcpO1xuICAgICAgICB9XG5cbiAgICAgICAgdG9nZ2xlQ2xhc3MoZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLmV4cGFuZC1hbGwnKSwgJ2hpZGRlbicpO1xuICAgICAgICB0b2dnbGVDbGFzcyhkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcuY29sbGFwc2UtYWxsJyksICdoaWRkZW4nKTtcbiAgICAgIH1cblxuICAgICAgZnVuY3Rpb24gY29sbGFwc2VBbGwoKSB7XG4gICAgICAgIGNvbnN0IHNlY3Rpb25zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLmNoYW5nZXM6bm90KC5jb2xsYXBzZWQpJyk7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgIHRvZ2dsZUNsYXNzKHNlY3Rpb25zW2ldLCAnY29sbGFwc2VkJyk7XG4gICAgICAgIH1cblxuICAgICAgICB0b2dnbGVDbGFzcyhkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcuZXhwYW5kLWFsbCcpLCAnaGlkZGVuJyk7XG4gICAgICAgIHRvZ2dsZUNsYXNzKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5jb2xsYXBzZS1hbGwnKSwgJ2hpZGRlbicpO1xuICAgICAgfVxuXG4gICAgICBmdW5jdGlvbiByZW1vdmVDaGlsZHJlbihlbGVtZW50KSB7XG4gICAgICAgIHdoaWxlIChlbGVtZW50Lmxhc3RDaGlsZCkge1xuICAgICAgICAgIGVsZW1lbnQucmVtb3ZlQ2hpbGQoZWxlbWVudC5sYXN0Q2hpbGQpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGZ1bmN0aW9uIGNyZWF0ZU1vZGFsQ29udGFpbmVyKCkge1xuICAgICAgICBjb25zdCBtb2RhbEVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcbiAgICAgICAgbW9kYWxFbGVtZW50LmlkID0gJ21vZGFsLWNvbnRhaW5lcic7XG5cbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChtb2RhbEVsZW1lbnQpO1xuXG4gICAgICAgIHJldHVybiBtb2RhbEVsZW1lbnQ7XG4gICAgICB9XG5cbiAgICAgIGZ1bmN0aW9uIGNsb3NlTW9kYWwoKSB7XG4gICAgICAgIGNvbnN0IG1vZGFsRWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdtb2RhbC1jb250YWluZXInKTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZChtb2RhbEVsZW1lbnQpO1xuICAgICAgfVxuICAgIDwvc2NyaXB0PlxuICA8L2JvZHk+XG48L2h0bWw+XG5gO1xuIl19 --------------------------------------------------------------------------------